Comparing version 0.1.4 to 0.2.0
{ | ||
"name": "corridor", | ||
"version": "0.1.4", | ||
"version": "0.2.0", | ||
"description": "JSON/DOM data corridor for data-binding", | ||
@@ -5,0 +5,0 @@ "repository": { |
148
README.md
@@ -41,4 +41,2 @@ # corridor | ||
It knows how to shuttle data back and forth by looking at HTML5 data attributes on the DOM elements. | ||
Let's take a look at how this works by using the practical example of a [`package.json`](https://npmjs.org/doc/json.html) file. | ||
@@ -51,3 +49,3 @@ We'll build out a single-page web app for manipulating a package.json file. | ||
For a `package.json`, you need at least the following data: | ||
For a `package.json` file, you need at least the following data: | ||
@@ -77,3 +75,2 @@ * name - the name of the project. | ||
corridor makes it easy to write a UI that controls this data structure. | ||
Let's start with the `name` field. | ||
@@ -83,11 +80,7 @@ Here's the HTML you'd need: | ||
```html | ||
<input type="text" data-field='{"name":$$$}' /> | ||
<input type="text" name="name" /> | ||
``` | ||
The `data-field` attribute tells corridor that this `input` provides the value for the `name` property. | ||
The `$$$` token is necessary here, it tells corridor how to insert the value. | ||
Other than the `$$$` token, the `data-field` should contain proper JSON. | ||
Let's try it out. | ||
Make sure you have the `<input>` HTML on a page and the corridor library included. | ||
Make sure you have the `<input>` HTML on a page and the `corridor.js` library included. | ||
Then you can call the corridor function with no arguments to extract all the data on the page. | ||
@@ -107,13 +100,13 @@ | ||
All options to corridor are done by adding a `data-opts` attribute to the node. | ||
To give options to corridor for a particular HTML element, give it a `data-opts` attribute. | ||
Let's see how this applies to the `keywords` field of a package.json. | ||
The corridor HTML for the keywords field looks like this: | ||
The HTML for the keywords field should look like this: | ||
```html | ||
<textarea data-field='{"keywords":$$$}' data-opts='{"type":"list"}'></textarea> | ||
<textarea name="keywords" data-opts='{"type":"list"}'></textarea> | ||
``` | ||
Here the `type` property indicates that we have a `list` value. | ||
corridor will try to parse the text value of the `<textarea>` as a list of items, and will output an array. | ||
corridor will try to parse the text in the `<textarea>` as a list of items, and will output an array. | ||
@@ -152,6 +145,6 @@ Let's give it a try! | ||
```html | ||
<p data-field='{"dependencies":$$$}'> | ||
<fieldset> | ||
<label> | ||
foo: | ||
<select data-field='{"foo":$$$}'> | ||
<select name="dependencies.foo"> | ||
<option value="~1.1.0">foo: version 1</option> | ||
@@ -163,3 +156,3 @@ <option value="~2.0.0">foo: version 2</option> | ||
bar: | ||
<select data-field='{"bar":$$$}'> | ||
<select name="dependencies.bar"> | ||
<option value="~3.5.0">bar: version 3</option> | ||
@@ -172,4 +165,2 @@ <option value="~4.1.0">bar: version 4</option> | ||
The paragraph element's `data-field` attribute tells corridor that any fields under it should roll up under it when creating the data object. | ||
Running `corridor()` on this gives us: | ||
@@ -186,5 +177,107 @@ | ||
But it doesn't end there! | ||
Since both the `foo` and `bar` select boxes live under `dependencies`, giving a `name` to the fieldset would have the same effect: | ||
```html | ||
<fieldset name="dependencies"> | ||
<label> | ||
foo: | ||
<select name="foo"> | ||
<option value="~1.1.0">foo: version 1</option> | ||
<option value="~2.0.0">foo: version 2</option> | ||
</select> | ||
</label> | ||
<label> | ||
bar: | ||
<select name="bar"> | ||
<option value="~3.5.0">bar: version 3</option> | ||
<option value="~4.1.0">bar: version 4</option> | ||
</select> | ||
</label> | ||
</p> | ||
``` | ||
If you run corridor in this, you'll get the same JSON listed above. | ||
Merging works best for objects like the `dependencies` object we just looked at. | ||
But corridor can also merge arrays. | ||
### rich path names | ||
In the last section we saw a rudimentary example of how to create nested data structures. | ||
The range of supported names is quite rich. | ||
These are best explained by example. | ||
Let's say you wanted to add `authors` to your package.json form, with a separate input for each author. | ||
The HTML for that might look like this: | ||
```html | ||
<fieldset> | ||
<label> | ||
first author: | ||
<input type="text" name="authors[]" value="your name" /> | ||
</label> | ||
<label> | ||
second author: | ||
<input type="text" name="authors[]" data-opts='{"empty":"omit"}' /> | ||
</label> | ||
<label> | ||
third author: | ||
<input type="text" name="authors[]" data-opts='{"empty":"omit"}' /> | ||
</label> | ||
</fieldset> | ||
``` | ||
The name attribute for each author input is `authors[]`. | ||
The trailing square brackets means that the input value should contribute to an array. | ||
Running corridor on the above would give you JSON like this: | ||
```js | ||
{ | ||
authors: [ | ||
"your name" | ||
] | ||
} | ||
``` | ||
Notice that there's only one element in this array. | ||
That's due to the `empty:omit` option on each of the other inputs. | ||
By default, corridor will include empty values in the output JSON it produces, but you can disable this feature by setting `empty` to `omit`. | ||
Just like with the `dependencies.foo` case from last section, here we could split up the parts of the name between the fieldset and the inputs. | ||
E.g. | ||
```html | ||
<fieldset name="authors"> | ||
<label> | ||
first author: | ||
<input type="text" name="[]" value="your name" /> | ||
``` | ||
You can mix and match dot delimited paths and square brackets to create even richer structures. | ||
```html | ||
<input type="text" name="stock.ticker[]symbols" value="BCOV AMZN" data-opts='{"type":"list"}' /> | ||
``` | ||
Produces this: | ||
```js | ||
"stock": { | ||
"ticker": [ | ||
{ | ||
"symbols": [ | ||
"BCOV", | ||
"AMZN" | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
Whitespace around key names is stripped, but whitespace inside them is preserved. | ||
For example `name=" foo bar "` would produce an object with a `foo bar` property. | ||
### toggling sections | ||
@@ -195,6 +288,6 @@ | ||
For example, say you wanted a checkbox to control whether `keywords` were going to be included in the output. | ||
The corridor HTML for that might look like this: | ||
The HTML for that might look like this: | ||
```html | ||
<div data-opts='{"role":"toggleable"}'> | ||
<fieldset data-opts='{"role":"toggleable"}'> | ||
<p> | ||
@@ -209,18 +302,17 @@ <label> | ||
keywords (list format): | ||
<textarea data-field='{"keywords":$$$}' data-opts='{"type":"list"}'></textarea> | ||
<textarea name="keywords" data-opts='{"type":"list"}'></textarea> | ||
</label> | ||
</p> | ||
</div> | ||
</fieldset> | ||
``` | ||
Adding the `toggleable` role to the outer `<div>` signals to corridor that this section can be turned on and off. | ||
Adding the `toggleable` role to the `<frameset>` signals to corridor that this section can be turned on and off. | ||
The checkbox with the role `toggle` controls it. | ||
You can nest toggleable sections inside each other. | ||
In each case, the toggle that control the toggleable container is the nearest child. | ||
In each case, the toggle that controls the toggleable container is the nearest child. | ||
### inserting data | ||
Just as corridor can pull data out of the DOM, it can put the data back in as well. | ||
This feature is still a bit experimental, there are some bugs around reinserting arrays (we're working on it). | ||
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. | ||
@@ -236,3 +328,3 @@ To insert data back into the DOM, call the corridor function with a root element and a data structure object. | ||
corridor uses the same `data-field` and `data-opts` parameters to determine where data values should be inserted. | ||
corridor uses the same `name` and `data-opts` attributes to determine where data values should be inserted. | ||
@@ -239,0 +331,0 @@ ## issues and feature requests |
@@ -39,2 +39,5 @@ /** | ||
* @param {object} opts Hash of options (optional) | ||
* Relevant options are: | ||
* - enabledOnly - Whether to only include enabled elements | ||
* - namedFields - Whether to include fields with a 'name' attribute | ||
*/ | ||
@@ -49,3 +52,3 @@ corridor = context[property] = function(root, data, opts) { | ||
* @param {HTMLElement} root The root element to scan for data. | ||
* @param {object} opts Hash of options (optional) | ||
* @param {object} opts Hash of options (optional, see corridor options) | ||
*/ | ||
@@ -64,3 +67,3 @@ extract = corridor.extract = function(root, opts) { | ||
data = {}, | ||
fields = slice.call(root.querySelectorAll('[data-field]')).filter(hasVal); | ||
fields = selectFields(root, settings).filter(hasVal); | ||
@@ -100,2 +103,3 @@ if (settings.enabledOnly) { | ||
* @param {mixed} data The data to insert. | ||
* @param {object} opts Hash of options (optional, see corridor options) | ||
*/ | ||
@@ -120,3 +124,3 @@ insert = corridor.insert = function(root, data, opts) { | ||
fields = slice.call(root.querySelectorAll('[data-field]')).filter(hasVal); | ||
fields = selectFields(root, settings).filter(hasVal); | ||
@@ -216,9 +220,31 @@ if (settings.enabledOnly) { | ||
/** | ||
* Only operate on enabled fields when this is true (the default). | ||
* When inserting/extracting, only operate on enabled fields (default: true). | ||
*/ | ||
enabledOnly: true | ||
enabledOnly: true, | ||
/** | ||
* When selecting fields, include any elements with a 'name' attribute (default: true). | ||
*/ | ||
namedFields: true | ||
}, | ||
/** | ||
* Select an array of field elements from the specified root element. | ||
* @param {HTMLElement} root The root element to search for fields. | ||
* @param {object} opts Options hash to affect selection behavior (optional). | ||
* Relevant options are: | ||
* - namedFields - Whether to include elements with a 'name' attribute | ||
*/ | ||
selectFields = corridor.selectFields = function(root, opts) { | ||
var | ||
settings = extend({}, defaults, opts), | ||
selector = '[data-field]' + (settings.namedFields ? ', [name]' : ''); | ||
return slice.call(root.querySelectorAll(selector)); | ||
}, | ||
/** | ||
* Visit each corridor ancestor element up from the specified starting node. | ||
@@ -242,6 +268,15 @@ * An element is a corridor element if it has a data-field or data-opts attribute. | ||
upwalk = corridor.upwalk = function(elem, root, callback) { | ||
var field, opts, res; | ||
while (elem !== null && elem.getAttribute) { | ||
if (elem.hasAttribute('data-field') || elem.hasAttribute('data-opts')) { | ||
field = undefined; | ||
if (elem.hasAttribute('data-field')) { | ||
field = elem.getAttribute('data-field') || undefined; | ||
} else if (elem.hasAttribute('name')) { | ||
field = convertName(elem.getAttribute('name')); | ||
} | ||
if (field || elem.hasAttribute('data-opts')) { | ||
opts = options(elem, defaults); | ||
@@ -253,8 +288,50 @@ res = callback(elem, field, opts); | ||
} | ||
elem = (elem === root) ? null : elem.parentNode; | ||
} | ||
return true; | ||
}, | ||
/** | ||
* Convert a simple name attribute string into a full field string. | ||
* The simple name format is a hybrid of Apple's Key-Value Coding and PHP's array-based form variables. | ||
* | ||
* Examples: | ||
* - 'foo' --> '{"foo":$$$}' | ||
* - 'foo.bar' --> '{"foo":{"bar":$$$}}' | ||
* - '[]' --> '[$$$]' | ||
* - 'list[]' --> 'list[$$$]' | ||
* - 'foo[bar]' --> '{"foo":{"bar":$$$}}' | ||
* - 'foo[bar][]' --> '{"foo":{"bar":[$$$]}}' | ||
* | ||
* @see https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Conceptual/KeyValueCoding/Articles/BasicPrinciples.html | ||
* @see http://php.net/manual/en/faq.html.php#faq.html.arrays | ||
* | ||
* @param {string} name The name string to convert. | ||
* @return {string} The full field string. | ||
*/ | ||
convertName = corridor.convertName = function(name) { | ||
var field = "\ufff0"; // start out with the target mark | ||
name | ||
.replace(/^\s+|\s+$/g, '') // trim whitespace for courtesy | ||
.replace(/\[\s+]/g, '[]') // trim inside bracket vars | ||
.replace(/\[([^\]]+)]/g, '.$1') // convert bracket vars to dot vars | ||
.match(/[^[\].]+|\[\]/g) // grab list of component parts | ||
.forEach(function(p) { | ||
p = p.replace(/^\s+|\s+$/g, ''); // trim each part | ||
field = field.replace("\ufff0", // add part to field specification | ||
p === '[]' ? "[\ufff0]" : "{" + JSON.stringify(p || 'undefined') + ":\ufff0}" | ||
); | ||
}); | ||
return field.replace("\ufff0", '$$$$$$'); | ||
}, | ||
/** | ||
* Walk up the parent chain and perform replacements to build up full contribution. | ||
@@ -394,3 +471,3 @@ * @param {string} value Starting value, must be a string. | ||
listify = corridor.listify = function(arry) { | ||
if (toString.call(arry) !== '[abject Array]') { | ||
if (toString.call(arry) !== '[object Array]') { | ||
return arry; | ||
@@ -436,2 +513,3 @@ } | ||
if (!candidates.length) { | ||
log('No child "toggle" element found for toggelable.', elem); | ||
throw Error('No child "toggle" element found for toggelable.'); | ||
@@ -441,2 +519,3 @@ } | ||
if (candidates.length > 1) { | ||
log('Multiple "toggle" elements have been found for toggleable.', elem); | ||
throw Error('Multiple "toggle" elements have been found for toggleable.'); | ||
@@ -443,0 +522,0 @@ } |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
62039
12
1155
363