|
11 | 11 |
|
12 | 12 | var isNumeric = require('fast-isnumeric'); |
13 | 13 | var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray; |
14 | | -var isPlainObject = require('./is_plain_object'); |
15 | | -var containerArrayMatch = require('../plot_api/container_array_match'); |
16 | 14 |
|
17 | 15 | /** |
18 | 16 | * convert a string s (such as 'xaxis.range[0]') |
@@ -115,44 +113,21 @@ function npGet(cont, parts) { |
115 | 113 | } |
116 | 114 |
|
117 | 115 | /* |
118 | | - * Can this value be deleted? We can delete any empty object (null, undefined, [], {}) |
119 | | - * EXCEPT empty data arrays, {} inside an array, or anything INSIDE an *args* array. |
| 116 | + * Can this value be deleted? We can delete `undefined`, and `null` except INSIDE an |
| 117 | + * *args* array. |
120 | 118 | * |
121 | | - * Info arrays can be safely deleted, but not deleting them has no ill effects other |
122 | | - * than leaving a trace or layout object with some cruft in it. |
| 119 | + * Previously we also deleted some `{}` and `[]`, in order to try and make set/unset |
| 120 | + * a net noop; but this causes far more complication than it's worth, and still had |
| 121 | + * lots of exceptions. See https://github.com/plotly/plotly.js/issues/1410 |
123 | 122 | * |
124 | | - * Deleting data arrays can change the meaning of the object, as `[]` means there is |
125 | | - * data for this attribute, it's just empty right now while `undefined` means the data |
126 | | - * should be filled in with defaults to match other data arrays. |
127 | | - * |
128 | | - * `{}` inside an array means "the default object" which is clearly different from |
129 | | - * popping it off the end of the array, or setting it `undefined` inside the array. |
130 | | - * |
131 | | - * *args* arrays get passed directly to API methods and we should respect precisely |
132 | | - * what the user has put there - although if the whole *args* array is empty it's fine |
133 | | - * to delete that. |
134 | | - * |
135 | | - * So we do some simple tests here to find known non-data arrays but don't worry too |
136 | | - * much about not deleting some arrays that would actually be safe to delete. |
| 123 | + * *args* arrays get passed directly to API methods and we should respect null if |
| 124 | + * the user put it there, but otherwise null is deleted as we use it as code |
| 125 | + * in restyle/relayout/update for "delete this value" whereas undefined means |
| 126 | + * "ignore this edit" |
137 | 127 | */ |
138 | | -var INFO_PATTERNS = /(^|\.)((domain|range)(\.[xy])?|args|parallels)$/; |
139 | 128 | var ARGS_PATTERN = /(^|\.)args\[/; |
140 | 129 | function isDeletable(val, propStr) { |
141 | | - if(!emptyObj(val) || |
142 | | - (isPlainObject(val) && propStr.charAt(propStr.length - 1) === ']') || |
143 | | - (propStr.match(ARGS_PATTERN) && val !== undefined) |
144 | | - ) { |
145 | | - return false; |
146 | | - } |
147 | | - if(!isArrayOrTypedArray(val)) return true; |
148 | | - |
149 | | - if(propStr.match(INFO_PATTERNS)) return true; |
150 | | - |
151 | | - var match = containerArrayMatch(propStr); |
152 | | - // if propStr matches the container array itself, index is an empty string |
153 | | - // otherwise we've matched something inside the container array, which may |
154 | | - // still be a data array. |
155 | | - return match && (match.index === ''); |
| 130 | + return (val === undefined) || (val === null && !propStr.match(ARGS_PATTERN)); |
156 | 131 | } |
157 | 132 |
|
158 | 133 | function npSet(cont, parts, propStr) { |
@@ -194,8 +169,18 @@ function npSet(cont, parts, propStr) { |
194 | 169 | } |
195 | 170 |
|
196 | 171 | if(toDelete) { |
197 | | - if(i === parts.length - 1) delete curCont[parts[i]]; |
198 | | - pruneContainers(containerLevels); |
| 172 | + if(i === parts.length - 1) { |
| 173 | + delete curCont[parts[i]]; |
| 174 | + |
| 175 | + // The one bit of pruning we still do: drop `undefined` from the end of arrays. |
| 176 | + // In case someone has already unset previous items, continue until we hit a |
| 177 | + // non-undefined value. |
| 178 | + if(Array.isArray(curCont) && +parts[i] === curCont.length - 1) { |
| 179 | + while(curCont.length && curCont[curCont.length - 1] === undefined) { |
| 180 | + curCont.pop(); |
| 181 | + } |
| 182 | + } |
| 183 | + } |
199 | 184 | } |
200 | 185 | else curCont[parts[i]] = val; |
201 | 186 | }; |
@@ -249,48 +234,6 @@ function checkNewContainer(container, part, nextPart, toDelete) { |
249 | 234 | return true; |
250 | 235 | } |
251 | 236 |
|
252 | | -function pruneContainers(containerLevels) { |
253 | | - var i, |
254 | | - j, |
255 | | - curCont, |
256 | | - propPart, |
257 | | - keys, |
258 | | - remainingKeys; |
259 | | - for(i = containerLevels.length - 1; i >= 0; i--) { |
260 | | - curCont = containerLevels[i][0]; |
261 | | - propPart = containerLevels[i][1]; |
262 | | - |
263 | | - remainingKeys = false; |
264 | | - if(isArrayOrTypedArray(curCont)) { |
265 | | - for(j = curCont.length - 1; j >= 0; j--) { |
266 | | - if(isDeletable(curCont[j], joinPropStr(propPart, j))) { |
267 | | - if(remainingKeys) curCont[j] = undefined; |
268 | | - else curCont.pop(); |
269 | | - } |
270 | | - else remainingKeys = true; |
271 | | - } |
272 | | - } |
273 | | - else if(typeof curCont === 'object' && curCont !== null) { |
274 | | - keys = Object.keys(curCont); |
275 | | - remainingKeys = false; |
276 | | - for(j = keys.length - 1; j >= 0; j--) { |
277 | | - if(isDeletable(curCont[keys[j]], joinPropStr(propPart, keys[j]))) { |
278 | | - delete curCont[keys[j]]; |
279 | | - } |
280 | | - else remainingKeys = true; |
281 | | - } |
282 | | - } |
283 | | - if(remainingKeys) return; |
284 | | - } |
285 | | -} |
286 | | - |
287 | | -function emptyObj(obj) { |
288 | | - if(obj === undefined || obj === null) return true; |
289 | | - if(typeof obj !== 'object') return false; // any plain value |
290 | | - if(isArrayOrTypedArray(obj)) return !obj.length; // [] |
291 | | - return !Object.keys(obj).length; // {} |
292 | | -} |
293 | | - |
294 | 237 | function badContainer(container, propStr, propParts) { |
295 | 238 | return { |
296 | 239 | set: function() { throw 'bad container'; }, |
|
0 commit comments