Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

corridor

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

corridor - npm Package Compare versions

Comparing version 0.4.0 to 0.5.0

logo/corridor.png

6

package.json
{
"name": "corridor",
"version": "0.4.0",
"description": "JSON/HTML data corridor for data-binding",
"version": "0.5.0",
"description": "JSON -> HTML -> JSON data corridor",
"repository": {

@@ -9,5 +9,7 @@ "type": "git",

},
"main": "./src/corridor.js",
"keywords": [
"json",
"dom",
"template",
"data-binding",

@@ -14,0 +16,0 @@ "corridor"

@@ -10,5 +10,5 @@ # corridor

Your data is in JSON, but your users interact with HTML.
corridor's only mission is to shuttle your data between your JSON and your HTML.
corridor shuttles your data between your JSON and your HTML.
In a nutshell, corridor gives you the power to turn this:
In a nutshell, corridor lets you turn this:

@@ -51,5 +51,5 @@ ```html

* **unobtrusive** — corridor presents just one function, has no dependencies, and causes no side-effects.
* **intelligent** — corridor learns what to do by looking at the data, not by being told (except where you want to).
* **clear** — corridor's code, functionality, tests, milestones and issues are all well documented and easy to follow.
* **intelligent** — it learns what to do by looking at the data on the ground.
* **unobtrusive** — it's just one function, without dependencies or side-effects.
* **clear** — corridor's code, tests, and issues are crisply documented.

@@ -410,3 +410,3 @@ Development of features, bugfixes and documentation are held to these ideals.

Adding the `toggleable` role to the `<frameset>` signals to corridor that this section can be turned on and off.
Adding the `toggleable` role to the `<fieldset>` signals to corridor that this section can be turned on and off.
The checkbox with the role `toggle` controls it.

@@ -419,3 +419,4 @@

This tutorial has focused largely on explaining how data flows from HTML to JSON, but corridor is great at sending data the other way as well.
So far, this tutorial has focused on explaining how data flows from HTML to JSON.
But corridor is great at sending data the other way as well.

@@ -433,2 +434,67 @@ To insert data back into the DOM, call the corridor function with a root element and a data structure object.

### expanding to fit
When you send data from JSON into HTML, there's a chance that there won't be enough room.
This is especilly true when working with arrays.
Consider this input JSON:
```js
{
"company": {
"employees": [{
"name": "Bob",
"email": "bob@company.com"
},{
"name": "Alice",
"email": "alice@company.com"
}]
}
}
```
And this HTML:
```html
<table>
<tr data-name="company.employees[]">
<td><input type="text" name="name" /></td>
<td><input type="text" name="email" /></td>
</tr>
</table>
```
If you ran corridor in insert mode in this scenario, Alice would be lost since there's only one row for `company.employees`.
Fortunately, corridor can expand the DOM for you to make room for the extra elements.
If you set `data-expand` to `auto` on a named element, corridor will duplicate it to make room for data that otherwise wouldn't fit.
Here's the same example again, with the `data-expand` attribute set:
```html
<table>
<tr data-name="company.employees[]" data-expand="auto">
<td><input type="text" name="name" /></td>
<td><input type="text" name="email" /></td>
</tr>
</table>
```
After calling `corridor(table, data)`, the HTML in the page will look like this:
```html
<table>
<tr data-name="company.employees[]" data-expand="auto">
<td><input type="text" name="name" value="Bob" /></td>
<td><input type="text" name="email" value="bob@company.com" /></td>
</tr>
<tr data-name="company.employees[]" data-expand="auto">
<td><input type="text" name="name" value="Alice" /></td>
<td><input type="text" name="email" value="alice@company.com" /></td>
</tr>
</table>
```
Since the expand feature is more intrusive in its side-effects in the DOM, you must enable it explicitly either by setting the `data-enabled` option, or in the options argument to the `corridor()` function.
## corridor API

@@ -518,5 +584,14 @@

Options that apply to any field are `type` and `empty`.
The `role` and `enabledOnly` options apply only to toggleable/toggle functionality.
Available options are:
* `type` - the type of the field (_auto_, _string_, _boolean_, _number_, _list_, or _json_)
* `empty` - whether to include the value in the output if the field is empty (_auto_, _include_, or _omit_)
* `merge` - strategy to use when merging arrays (_auto_, _concat_, or _extend_)
* `include` - whether a non-form element should be considered for insert/extract (_auto_, _always_, or _never_)
* `extract` - strategy for pulling a value from a non-form element when extracting (_auto_, _value_, _text_, or _html_)
* `insert` - strategy for putting a value into a non-form element when inserting (_auto_, _value_, _text_, or _html_)
* `expand` - whether to expand the DOM to accomodate data that otherwise wouldn't fit (_never_, _auto_)
* `role` - what role this element plays (_field_, _toggleable_, _toggle_, _expand_)
* `enabledOnly` - only include enabled elements for consideration during insert/extract (_true_, _false_)
Note that setting options via the `opts` param specifically affects the execution of the corridor function just once.

@@ -532,8 +607,8 @@ Persistent options should be stored in the HTML.

* _auto_ - automatically detect the correct type based on the value (default)
* _string_ - treat the value as a string
* _boolean_ - coerce this value to something true/false
* _number_ - parse this value as a number
* _json_ - leave this value as-is (will choke if it's not actually valid JSON)
* _list_ - parse this value as a list of values
* `auto` - automatically detect the correct type based on the value (default)
* `string` - treat the value as a string
* `boolean` - coerce this value to something true/false
* `number` - parse this value as a number
* `json` - leave this value as-is (will choke if it's not actually valid JSON)
* `list` - parse this value as a list of values

@@ -554,5 +629,5 @@ When automatically detecting the type, corridor uses the following algorithm:

* _auto_ - automatically detect the appropriate behavior based on the circumstances (default)
* _include_ - include the value in the output (default)
* _omit_ - do not add the field at all
* `auto` - automatically detect the appropriate behavior based on the circumstances (default)
* `include` - include the value in the output (default)
* `omit` - do not add the field at all

@@ -568,3 +643,3 @@ When empty is set to `auto`, corridor uses the following algorithm to between `include` and `omit`:

##### merge options
##### merge option

@@ -575,5 +650,5 @@ The `merge` option indicates which merging strategy corridor should use when merging two arrays.

* _auto_ - intelligently choose whether to concatenate the arrays, or deep merge them (default)
* _concat_ - concatenate the arrays
* _extend_ - deep merge each pair of items
* `auto` - intelligently choose whether to concatenate the arrays, or deep merge them (default)
* `concat` - concatenate the arrays
* `extend` - deep merge each pair of items

@@ -603,2 +678,105 @@ When in `auto` mode, the algorithm for choosing whether to concatenate or merge two arrays should work as follows:

##### include option
The `include` option indicates whether a non-form element should be considered for insert/extract.
Choices are:
* `auto` - intelligently choose whether to include the element (default).
* `always` - always include the element.
* `never` - never include the element.
When operating in _auto_ mode, corridor uses the following algorithm to decide whether a non-form element should be considered for insert/extract:
* if the element is a form field (`input`, `textarea`, `select`), include it, otherwise,
* if the element has no children with `name` or `data-name` attributes, include it, otherwise,
* exclude it.
Only elements included by this algorithm will contribute to extracted output or receive inserted data.
##### extract option
The `extract` option indicates how a value should be extracted from an element under consideration.
Choices are:
* `auto` - intelligently choose the best way to get a value (default).
* `value` - get the element's form value (`value` attribute, except for `<select>` elements).
* `text` - get the element's `textContent`.
* `html` - get the element's `innerHTML`.
When operating in _auto_ mode, corridor uses the following algorithm to decide how to extract a value:
* if the element is a form field, use _value_ extraction, otherwise,
* if the element has no child elements (only text), use _text_, otherwise,
* if the element is a `<pre>` or `<code>` element, use _text_, otherwise,
* use _html_.
If you set the extract option, it's much better to set it in the HTML specifically for a particular element.
Most of the time, you'll want the _auto_ detection.
##### insert option
The `insert` option indicates how a value should be inserted into an element under consideration.
Choices are:
* `auto` - intelligently choose the best way to insert the value (default).
* `value` - set the element's form value (`value` attribute, except for `<select>` elements).
* `text` - set the element's `textContent`.
* `html` - set the element's `innerHTML`.
When operating in _auto_ mode, corridor uses the following algorithm to decide how to insert a value:
* if the element is a form field, use _value_ insertion, otherwise,
* if the value appears to contain no HTML elements, use _text_, otherwise,
* if the element is a `<pre>` or `<code>` element, use _text_, otherwise,
* use _html_.
If you set the insert option, it's much better to set it in the HTML specifically for a particular element.
Most of the time, you'll want the _auto_ detection.
##### expand option
The `expand` option indicates whether corridor should make any attempt to expand the DOM to accomodate data that otherwise wouldn't fit.
Choices are:
* `never` - do not add any elements to the DOM (default).
* `auto` - intelligently choose the best way to expand the DOM if necessary.
When operating in _auto_ mode, corridor uses the following algorithm to decide how to expand the DOM:
* identify candidates for expansion; these are elements which:
- have a `name` or `data-name` attribute,
- don't a `data-expand` value of `never`,
- have a name format that ends in `[]`,
- are not the children of such an element (the algorithm does _not_ recurse)
* for each set of candidates:
- find the matching array from the source data,
- if there source data array and list of candidate fields have the same length, abort, otherwise,
- find the best target to clone, then,
- create N clone siblings of the target, appending each sequentially to the target's parent element, where N is the difference between the data array length and the length of the list of candidate elements.
The algorithm for finding a clone target for a set of candidate elements is:
* start with the last element in the list of candidates (called `elem` here),
* if that element is not a value'd form field:
- check for a child with `data-role` set to `expand`, if found, use it
* if the element `elem` is a value'd form field:
- walk up the DOM looking for a parent with `data-role` set to `expand`, if found, use it, otherwise,
- walk up the DOM looking for either a `<li>` element or a `<tr>`, if found, use it
* last resort, use the element `elem`
Known limitations:
* Only the last candidate element is searched for a target to clone. It's not round-robin or based on the content of the data array.
* The shortfall is calculated from the element which starts the process, not the target element.
* The algorithm doesn't recurse, so nested expand elements will not be expanded.
Improving on these limitations will require a major update to the code base to include an intermediate tree structure.
This tree structure would provide the rich information necessary to make more intelligent auto-expand decisions.
See [Issue #39](https://github.com/jimbojw/corridor/issues/39).
##### toggle options

@@ -744,2 +922,4 @@

_Note: field format will probably be removed in a future version of corridor_
Whereas name format resembles how you'd _access_ an object in JavaScript, field format resembles how you describe an object in JavaScript—that is, JSON.

@@ -763,4 +943,3 @@

When possible, you should use the name format for your name attributes.
But there are some rare cases where field format is the better option.
You should use the name format for your name attributes.

@@ -767,0 +946,0 @@ #### data-opts attribute

@@ -133,3 +133,6 @@ /**

data = {},
fields = selectFields(root, settings).filter(hasVal);
fields = selectFields(root, settings)
.filter(function(elem) {
return hasVal(elem, settings);
});

@@ -139,3 +142,3 @@ if (settings.enabledOnly) {

}
fields.forEach(function(elem) {

@@ -146,9 +149,7 @@

value = val(elem),
contrib,
field;
// build out full contribution
contrib = buildup("\ufffc", elem, root),
field = contrib.split("\ufffc").join('$$$');
// build out full contribution
contrib = buildup("\ufffc", elem, root);
field = contrib.split("\ufffc").join('$$$');
// short-circuit if this field should be omitted

@@ -190,2 +191,5 @@ if (!value && !includeEmpty(field, elem, opts)) {

// expand DOM to fit data
expand(root, data, opts);
var

@@ -199,3 +203,6 @@

fields = selectFields(root, settings).filter(hasVal);
fields = selectFields(root, settings)
.filter(function(elem) {
return hasVal(elem, settings);
});

@@ -262,2 +269,55 @@ if (settings.enabledOnly) {

/**
* Expand the DOM to fit the supplied data.
* @param {HTMLElement} root The element to scan for insertion fields (optional).
* @param {mixed} data The data to insert.
* @param {object} opts Hash of options (optional, see corridor options)
*/
expand = corridor.expand = function(root, data, opts) {
var
settings = options(root, extend({}, defaults, opts)),
// search for candidates to expand
candidates = findExpandCandidates(root, settings);
keys(candidates).forEach(function(field){
var candidate, arry, shortfall, target, parent, sibling, cloneElem;
// grab candidate
candidate = candidates[field];
// find array in the data that maps to this candidate
arry = follow(candidate.path, data);
if (!arry || !arry.length) {
return;
}
// compare length of elems to data mapped array to determine shortfall
shortfall = arry.length - candidate.elems.length;
if (shortfall === 0) {
return;
}
// choose best element for clone target
target = findExpandTarget(candidate.elems[candidate.elems.length - 1], root, settings);
if (!target) {
return;
}
sibling = target.nextSibling;
parent = target.parentNode;
// clone last element N times
while (shortfall--) {
cloneElem = target.cloneNode(true);
parent.insertBefore(cloneElem, sibling);
sibling = cloneElem.nextSibling;
}
});
},
/**
* Default values applied to options.

@@ -282,2 +342,3 @@ */

* - toggle - this element is a checkbox which toggles its nearest parent toggleable
* - expand - this element is meant to be expanded (cloned) to accomodate data in the case of a shortfall
*/

@@ -310,7 +371,152 @@ role: "field",

*/
merge: 'auto'
merge: 'auto',
/**
* Whether to include a non-form element when inserting/extracting.
* - auto - intelligently decide whether each element should be included (default)
* - always - always include the element for value consideration
* - never - never include this element for value consideration
*/
include: 'auto',
/**
* Strategy for pulling a value out of a non-form element when extracting.
* - auto - intelligently decide how each element's value should be extracted (default)
* - value - use the value attribute (or equivalent for <select> elements)
* - text - use the element's textContent
* - html - use the element's innerHTML
*/
extract: 'auto',
/**
* Strategy for setting a value into a non-form element when inserting.
* - auto - intelligently decide how each element should receive the value (default)
* - value - set the value attribute (or equivalent for <select> elements)
* - text - set the element's textContent
* - html - set the element's innerHTML
*/
insert: 'auto',
/**
* Strategy for expanding the DOM to accomodate arrays of data.
* - never - do not modify the DOM to try and accomodate data (default)
* - auto - intelligently decide whether to expand based on circumstances
*/
expand: 'never'
},
/**
* Given an element, find candidates for DOM expanssion.
* Once a candidate is found, it is not recursively searched.
* @param {HTMLElement} root The element to start from.
* @param {object} opts Options (optional).
*/
findExpandCandidates = corridor.expand.findExpandCandidates = function(root, opts) {
var
settings = extend({}, defaults, opts),
queue = [root],
candidates = {},
ufc = JSON.stringify("\ufffc"),
// iteration vars
elem, field, path, candidate;
while (queue.length) {
elem = queue.shift();
if (elem.hasAttribute('name') || elem.hasAttribute('data-name')) {
if (options(elem, settings).expand !== 'never') {
field = buildup(ufc, elem, root);
if (field.indexOf("["+ufc+"]") !== -1) {
path = locate(JSON.parse(field), "\ufffc").slice(0, -1);
candidate = candidates[field];
if (!candidate) {
candidate = candidates[field] = {
path: path,
elems: []
};
}
candidate.elems.push(elem);
}
}
}
arrayify(elem.childNodes).forEach(function(child) {
if (child.nodeType === 1) {
queue.push(child);
}
});
}
return candidates;
},
/**
* Given an element, intelligently find the best choice for DOM expanssion.
* @param {HTMLElement} elem The element to start from.
* @param {HTMLElement} root The highest element to consider.
* @param {object} opts Options (optional).
*/
findExpandTarget = corridor.expand.findExpandTarget = function(elem, root, opts) {
var target;
// non-value'd elements inspect downwards
if (!hasVal(elem)) {
// check for child with the data-role expand
target = null;
arrayify(elem.querySelectorAll('[data-role], [data-opts]'))
.forEach(function(child) {
if (!target) {
if (options(child).role === 'expand') {
target = child;
}
}
});
return target || elem;
}
// walk up the DOM looking for an ancestor with the exand role
target = null;
upwalk(elem, root, function(parent, field, opts){
if (opts.role === 'expand') {
target = parent;
return false;
}
});
if (target) {
return target;
}
// if there are no ancestors with role expand, look for special elements
var node = elem;
while (node) {
if ((/^(tr|li)$/i).test(node.tagName)) {
return node;
}
node = node === root ? null : node.parentNode;
}
// couldn't find a better target than the element itself
return elem;
},
/**
* Grab the keys of an object as an array.
*/
keys = corridor.keys = Object.keys || function(obj) {
var ret = [], key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
ret.push(key);
}
}
return arrayify(ret);
},
/**
* Select an array of field elements from the specified root element.

@@ -480,2 +686,3 @@ * @param {HTMLElement} root The root element to search for fields.

follow = corridor.follow = function(path, node) {
path = path.slice(0);
while (node && path.length) {

@@ -673,8 +880,9 @@ node = node[path.shift()];

* @param {mixed} value The value to set (optional).
* @param {mixed} opts Options to pass to override defaults (optional).
*/
val = corridor.val = function(elem, value) {
val = corridor.val = function(elem, value, opts) {
if (value === undefined) {
return getVal(elem);
return getVal(elem, opts);
}
return setVal(elem, value);
return setVal(elem, value, opts);
},

@@ -685,4 +893,31 @@

* @param {HTMLElement} elem The element whose value is to be determined.
* @param {mixed} opts Options to pass to override defaults (optional).
*/
getVal = val.getVal = function(elem) {
getVal = val.getVal = function(elem, opts) {
opts = options(elem, extend({}, defaults, opts));
return (
opts.extract === 'value' ? getFormVal(elem) :
opts.extract === 'text' ? getText(elem) :
opts.extract === 'html' ? elem.innerHTML :
isValued(elem) ? getFormVal(elem) :
elem.querySelectorAll('*').length === 0 ? getText(elem) :
(/^(pre|code)$/i).test(elem.tagName) ? getText(elem) :
elem.innerHTML
);
},
/**
* Grab the text content of an element.
* @param {HTMLElement} elem The element.
* @return {string} The text content of the element.
*/
getText = val.getText = function(elem) {
return elem.textContent || elem.innerText || '';
},
/**
* Get the value of the specified form element.
* @param {HTMLElement} elem The element whose value is to be determined.
*/
getFormVal = val.getFormVal = function(elem) {

@@ -710,17 +945,62 @@ var

* @param {HTMLElement} elem The element whose value is to be set.
* @param {mixed} value The value to set (optional).
* @param {mixed} value The value to set.
* @param {mixed} opts Options to pass to override defaults (optional).
*/
setVal = val.setVal = function(elem, value) {
elem.value = value;
setVal = val.setVal = function(elem, value, opts) {
opts = options(elem, extend({}, defaults, opts));
if (opts.insert === 'value') {
elem.value = value;
} else if (opts.insert === 'text') {
setText(elem, value);
} else if (opts.insert === 'html') {
elem.innerHTML = value;
} else if (isValued(elem)) {
elem.value = value;
} else if (!(/<(\w+)\b[^>]*>/i).test(value)) {
setText(elem, value);
} else if ((/^(pre|code)$/i).test(elem.tagName)) {
setText(elem, value);
} else {
elem.innerHTML = value;
}
},
/**
* Determine whether a specified element could have or receive a val.
* Set the text of an element to the given string.
* @param {HTMLElement} elem The element whose test is to be set.
* @param {string} text The text to set.
*/
setText = val.setText = function(elem, text) {
if ('textContent' in elem) {
elem.textContent = text;
}
if ('innerText' in elem) {
elem.innerText = text;
}
},
/**
* Determine whether a specified element could receive or produce a value.
* @param {HTMLElement} elem The element to test.
* @param {mixed} opts Options to merge into element's options.
*/
hasVal = val.hasVal = function(elem) {
return (/^(input|textarea|select)$/i).test(elem.tagName.toLowerCase());
hasVal = val.hasVal = function(elem, opts) {
opts = options(elem, extend({}, defaults, opts));
return (
opts.include === 'always' ? true :
opts.include === 'never' ? false :
isValued(elem) ? true :
elem.querySelectorAll('*').length === 0
);
},
/**
* Determine whether a specified element is a value'd form element.
* @param {HTMLElement} elem The element to test.
*/
isValued = val.isValued = function(elem) {
return (/^(input|textarea|select)$/i).test(elem.tagName);
},
/**
* Copy properties from one or more objects onto a target.

@@ -901,2 +1181,11 @@ */

},
/**
* Create a deep clone of a plain object.
* @param {mixed} obj The object to clone.
* @return {mixed} A deep clone of the original object.
*/
clone = corridor.clone = function(obj) {
return JSON.parse(JSON.stringify(obj));
};

@@ -903,0 +1192,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc