package.src.lib.keyed_container.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plotly.js Show documentation
Show all versions of plotly.js Show documentation
The open source javascript graphing library that powers plotly
The newest version!
'use strict';
var nestedProperty = require('./nested_property');
var SIMPLE_PROPERTY_REGEX = /^\w*$/;
// bitmask for deciding what's updated. Sometimes the name needs to be updated,
// sometimes the value needs to be updated, and sometimes both do. This is just
// a simple way to track what's updated such that it's a simple OR operation to
// assimilate new updates.
//
// The only exception is the UNSET bit that tracks when we need to explicitly
// unset and remove the property. This concrn arises because of the special
// way in which nestedProperty handles null/undefined. When you specify `null`,
// it prunes any unused items in the tree. I ran into some issues with it getting
// null vs undefined confused, so UNSET is just a bit that forces the property
// update to send `null`, removing the property explicitly rather than setting
// it to undefined.
var NONE = 0;
var NAME = 1;
var VALUE = 2;
var BOTH = 3;
var UNSET = 4;
module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
keyName = keyName || 'name';
valueName = valueName || 'value';
var i, arr, baseProp;
var changeTypes = {};
if(path && path.length) {
baseProp = nestedProperty(baseObj, path);
arr = baseProp.get();
} else {
arr = baseObj;
}
path = path || '';
// Construct an index:
var indexLookup = {};
if(arr) {
for(i = 0; i < arr.length; i++) {
indexLookup[arr[i][keyName]] = i;
}
}
var isSimpleValueProp = SIMPLE_PROPERTY_REGEX.test(valueName);
var obj = {
set: function(name, value) {
var changeType = value === null ? UNSET : NONE;
// create the base array if necessary
if(!arr) {
if(!baseProp || changeType === UNSET) return;
arr = [];
baseProp.set(arr);
}
var idx = indexLookup[name];
if(idx === undefined) {
if(changeType === UNSET) return;
changeType = changeType | BOTH;
idx = arr.length;
indexLookup[name] = idx;
} else if(value !== (isSimpleValueProp ? arr[idx][valueName] : nestedProperty(arr[idx], valueName).get())) {
changeType = changeType | VALUE;
}
var newValue = arr[idx] = arr[idx] || {};
newValue[keyName] = name;
if(isSimpleValueProp) {
newValue[valueName] = value;
} else {
nestedProperty(newValue, valueName).set(value);
}
// If it's not an unset, force that bit to be unset. This is all related to the fact
// that undefined and null are a bit specially implemented in nestedProperties.
if(value !== null) {
changeType = changeType & ~UNSET;
}
changeTypes[idx] = changeTypes[idx] | changeType;
return obj;
},
get: function(name) {
if(!arr) return;
var idx = indexLookup[name];
if(idx === undefined) {
return undefined;
} else if(isSimpleValueProp) {
return arr[idx][valueName];
} else {
return nestedProperty(arr[idx], valueName).get();
}
},
rename: function(name, newName) {
var idx = indexLookup[name];
if(idx === undefined) return obj;
changeTypes[idx] = changeTypes[idx] | NAME;
indexLookup[newName] = idx;
delete indexLookup[name];
arr[idx][keyName] = newName;
return obj;
},
remove: function(name) {
var idx = indexLookup[name];
if(idx === undefined) return obj;
var object = arr[idx];
if(Object.keys(object).length > 2) {
// This object contains more than just the key/value, so unset
// the value without modifying the entry otherwise:
changeTypes[idx] = changeTypes[idx] | VALUE;
return obj.set(name, null);
}
if(isSimpleValueProp) {
for(i = idx; i < arr.length; i++) {
changeTypes[i] = changeTypes[i] | BOTH;
}
for(i = idx; i < arr.length; i++) {
indexLookup[arr[i][keyName]]--;
}
arr.splice(idx, 1);
delete(indexLookup[name]);
} else {
// Perform this update *strictly* so we can check whether the result's
// been pruned. If so, it's a removal. If not, it's a value unset only.
nestedProperty(object, valueName).set(null);
// Now check if the top level nested property has any keys left. If so,
// the object still has values so we only want to unset the key. If not,
// the entire object can be removed since there's no other data.
// var topLevelKeys = Object.keys(object[valueName.split('.')[0]] || []);
changeTypes[idx] = changeTypes[idx] | VALUE | UNSET;
}
return obj;
},
constructUpdate: function() {
var astr, idx;
var update = {};
var changed = Object.keys(changeTypes);
for(var i = 0; i < changed.length; i++) {
idx = changed[i];
astr = path + '[' + idx + ']';
if(arr[idx]) {
if(changeTypes[idx] & NAME) {
update[astr + '.' + keyName] = arr[idx][keyName];
}
if(changeTypes[idx] & VALUE) {
if(isSimpleValueProp) {
update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : arr[idx][valueName];
} else {
update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : nestedProperty(arr[idx], valueName).get();
}
}
} else {
update[astr] = null;
}
}
return update;
}
};
return obj;
};