| /** | ||
| * test-convertname.js - tests the convertName() function. | ||
| */ | ||
| var corridor = require('../src/corridor.js'); | ||
| exports.testConvertName = function(test) { | ||
| var suite = [{ | ||
| name: 'name', | ||
| field: '{"name":$$$}', | ||
| reason: 'basic name should become property field' | ||
| },{ | ||
| name: 'foo.bar', | ||
| field: '{"foo":{"bar":$$$}}', | ||
| reason: 'dot nested name parts should become a nested object field' | ||
| },{ | ||
| name: 'foo bar', | ||
| field: '{"foo bar":$$$}', | ||
| reason: 'whitespace inside field names should be preserved' | ||
| },{ | ||
| name: 'a.b.c.d.e', | ||
| field: '{"a":{"b":{"c":{"d":{"e":$$$}}}}}', | ||
| reason: 'dot nested name parts can nest to any level' | ||
| },{ | ||
| name: 'a b.c d.e f', | ||
| field: '{"a b":{"c d":{"e f":$$$}}}', | ||
| reason: 'spaces in dot nested name parts should be preserved' | ||
| },{ | ||
| name: '[]', | ||
| field: '[$$$]', | ||
| reason: 'empty square brackets alone should contribute to an array' | ||
| },{ | ||
| name: '[ ]', | ||
| field: '[$$$]', | ||
| reason: 'whitespace in square brackets alone should be treated as empty' | ||
| },{ | ||
| name: '[].name', | ||
| field: '[{"name":$$$}]', | ||
| reason: 'empty square brackets alone should contribute to an array' | ||
| },{ | ||
| name: 'person[]name', | ||
| field: '{"person":[{"name":$$$}]}', | ||
| reason: 'unprefixed key after square brackets should still be a key' | ||
| },{ | ||
| name: '[][]', | ||
| field: '[[$$$]]', | ||
| reason: 'nested square brackets should create an array in an array' | ||
| },{ | ||
| name: 'list[]', | ||
| field: '{"list":[$$$]}', | ||
| reason: 'empty square brackets after a key adds to that key\'s array' | ||
| },{ | ||
| name: 'foo[bar]', | ||
| field: '{"foo":{"bar":$$$}}', | ||
| reason: 'non-empty square brackets act just like a dot delimited key' | ||
| },{ | ||
| name: ' foo [ bar ] ', | ||
| field: '{"foo":{"bar":$$$}}', | ||
| reason: 'whitespace virtually anywhere should not not change the output' | ||
| },{ | ||
| name: 'foo[bar].baz[]', | ||
| field: '{"foo":{"bar":{"baz":[$$$]}}}', | ||
| reason: 'mixing bracket and dot key styles is fine' | ||
| } | ||
| ]; | ||
| test.expect(suite.length); | ||
| suite.forEach(function(data) { | ||
| var actual = corridor.convertName(data.name); | ||
| test.deepEqual(actual, data.field, data.reason); | ||
| }); | ||
| test.done(); | ||
| }; | ||
+39
-31
@@ -12,2 +12,3 @@ <!doctype html> | ||
| button { font-size: large; } | ||
| textarea { width: 100%; } | ||
| </style> | ||
@@ -28,3 +29,3 @@ </head> | ||
| <div> | ||
| <textarea></textarea> | ||
| <textarea rows="8"></textarea> | ||
| </div> | ||
@@ -42,15 +43,14 @@ </div> | ||
| <div class="input"> | ||
| <h2>important fields</h2> | ||
| <p> | ||
| These fields are the most important to include. | ||
| </p> | ||
| <p> | ||
| <label> | ||
| name: | ||
| <input type="text" data-field='{"name":$$$}' data-opts='{"empty":"include"}'/> | ||
| </label> | ||
| </p> | ||
| <p> | ||
| <label>version: <input type="text" data-field='{"version":$$$}' /> </label> | ||
| </p> | ||
| <fieldset> | ||
| <h2>important fields</h2> | ||
| <p> | ||
| These fields are the most important to include. | ||
| </p> | ||
| <p> | ||
| <label>name: <input type="text" name="name"/></label> | ||
| </p> | ||
| <p> | ||
| <label>version: <input type="text" name='version' /></label> | ||
| </p> | ||
| </fieldset> | ||
| <hr /> | ||
@@ -61,5 +61,13 @@ <h2>less-important fields</h2> | ||
| </p> | ||
| <div data-opts='{"role":"toggleable"}'> | ||
| <fieldset> | ||
| <p> | ||
| <label> | ||
| homepage (will be omitted if empty): | ||
| <input type="text" name="homepage" data-opts='{"empty":"omit"}' value="https://npmjs.org/package/corridor" /> | ||
| </label> | ||
| </p> | ||
| </fieldset> | ||
| <fieldset data-opts='{"role":"toggleable"}'> | ||
| <p> | ||
| <label> | ||
| <input type="checkbox" data-opts='{"role":"toggle"}' checked/> | ||
@@ -69,18 +77,18 @@ include authors? | ||
| </p> | ||
| <p data-field='{"authors":$$$}'> | ||
| <p> | ||
| <label> | ||
| first author: | ||
| <input data-field='[$$$]' data-opts='{"empty":"omit"}' value="jimbojw" /> | ||
| <input type="text" name="authors[]" data-opts='{"empty":"omit"}' value="jimbojw" /> | ||
| </label> | ||
| <label> | ||
| second author: | ||
| <input data-field='[$$$]' data-opts='{"empty":"omit"}' /> | ||
| <input type="text" name="authors[]" data-opts='{"empty":"omit"}' /> | ||
| </label> | ||
| <label> | ||
| third author: | ||
| <input data-field='[$$$]' data-opts='{"empty":"omit"}' /> | ||
| <input type="text" name="authors[]" data-opts='{"empty":"omit"}' /> | ||
| </label> | ||
| </p> | ||
| </div> | ||
| <div data-opts='{"role":"toggleable"}'> | ||
| </fieldset> | ||
| <fieldset data-opts='{"role":"toggleable"}'> | ||
| <p> | ||
@@ -95,7 +103,7 @@ <label> | ||
| keywords (list format): | ||
| <textarea data-field='{"keywords":$$$}' data-opts='{"type":"list"}'></textarea> | ||
| <textarea name="keywords" data-opts='{"type":"list"}'>foo bar</textarea> | ||
| </label> | ||
| </p> | ||
| </div> | ||
| <div data-opts='{"role":"toggleable"}'> | ||
| </fieldset> | ||
| <fieldset data-opts='{"role":"toggleable"}'> | ||
| <p> | ||
@@ -110,7 +118,7 @@ <label> | ||
| bin (must be JSON): | ||
| <textarea data-field='{"bin":$$$}' data-opts='{"type":"json"}'>{ "npm" : "./cli.js" }</textarea> | ||
| <textarea name="bin" data-opts='{"type":"json"}'>{ "npm" : "./cli.js" }</textarea> | ||
| </label> | ||
| </p> | ||
| </div> | ||
| <div data-opts='{"role":"toggleable"}'> | ||
| </fieldset> | ||
| <fieldset data-opts='{"role":"toggleable"}'> | ||
| <p> | ||
@@ -122,6 +130,6 @@ <label> | ||
| </p> | ||
| <p data-field='{"dependencies":$$$}'> | ||
| <p> | ||
| <label> | ||
| foo: | ||
| <select data-field='{"foo":$$$}'> | ||
| <select name="dependencies.foo"> | ||
| <option value="~0.1.0">foo: ~0.1.0</option> | ||
@@ -134,3 +142,3 @@ <option value="~1.1.0">foo: ~1.1.0</option> | ||
| bar: | ||
| <select data-field='{"bar":$$$}'> | ||
| <select name="dependencies.bar"> | ||
| <option value="~0.1.0">bar: ~0.1.0</option> | ||
@@ -142,3 +150,3 @@ <option value="~1.1.0">bar: ~1.1.0</option> | ||
| </p> | ||
| </div> | ||
| </fieldset> | ||
| </div> | ||
@@ -145,0 +153,0 @@ </div> |
+1
-1
| { | ||
| "name": "corridor", | ||
| "version": "0.1.4", | ||
| "version": "0.2.0", | ||
| "description": "JSON/DOM data corridor for data-binding", | ||
@@ -5,0 +5,0 @@ "repository": { |
+120
-28
@@ -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 |
+86
-7
@@ -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 @@ } |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
62039
13.38%12
9.09%1155
12.79%363
33.95%4
33.33%