diff --git a/lib/amd/backbone.syphon.js b/lib/amd/backbone.syphon.js deleted file mode 100644 index a8ca441..0000000 --- a/lib/amd/backbone.syphon.js +++ /dev/null @@ -1,479 +0,0 @@ -// Backbone.Syphon, v0.4.1 -// Copyright (c)2012 Derick Bailey, Muted Solutions, LLC. -// Distributed under MIT license -// http://github.com/derickbailey/backbone.syphon -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['underscore', "jquery", "backbone"], factory); - } -}(this, function (_, jQuery, Backbone) { - Backbone.Syphon = (function(Backbone, $, _){ - var Syphon = {}; - - // Ignore Element Types - // -------------------- - - // Tell Syphon to ignore all elements of these types. You can - // push new types to ignore directly in to this array. - Syphon.ignoredTypes = ["button", "submit", "reset", "fieldset"]; - - // Syphon - // ------ - - // Get a JSON object that represents - // all of the form inputs, in this view. - // Alternately, pass a form element directly - // in place of the view. - Syphon.serialize = function(view, options){ - var data = {}; - - // Build the configuration - var config = buildConfig(options); - - // Get all of the elements to process - var elements = getInputElements(view, config); - - // Process all of the elements - _.each(elements, function(el){ - var $el = $(el); - var type = getElementType($el); - - // Get the key for the input - var keyExtractor = config.keyExtractors.get(type); - var key = keyExtractor($el); - - // Get the value for the input - var inputReader = config.inputReaders.get(type); - var value = inputReader($el); - - // Get the key assignment validator and make sure - // it's valid before assigning the value to the key - var validKeyAssignment = config.keyAssignmentValidators.get(type); - if (validKeyAssignment($el, key, value)){ - var keychain = config.keySplitter(key); - data = assignKeyValue(data, keychain, value); - } - }); - - // Done; send back the results. - return data; - }; - - // Use the given JSON object to populate - // all of the form inputs, in this view. - // Alternately, pass a form element directly - // in place of the view. - Syphon.deserialize = function(view, data, options){ - // Build the configuration - var config = buildConfig(options); - - // Get all of the elements to process - var elements = getInputElements(view, config); - - // Flatten the data structure that we are deserializing - var flattenedData = flattenData(config, data); - - // Process all of the elements - _.each(elements, function(el){ - var $el = $(el); - var type = getElementType($el); - - // Get the key for the input - var keyExtractor = config.keyExtractors.get(type); - var key = keyExtractor($el); - - // Get the input writer and the value to write - var inputWriter = config.inputWriters.get(type); - var value = flattenedData[key]; - - // Write the value to the input - inputWriter($el, value); - }); - }; - - // Helpers - // ------- - - // Retrieve all of the form inputs - // from the form - var getInputElements = function(view, config){ - var form = getForm(view); - var elements = form.elements; - - elements = _.reject(elements, function(el){ - var reject; - var type = getElementType(el); - var extractor = config.keyExtractors.get(type); - var identifier = extractor($(el)); - - var foundInIgnored = _.include(config.ignoredTypes, type); - var foundInInclude = _.include(config.include, identifier); - var foundInExclude = _.include(config.exclude, identifier); - - if (foundInInclude){ - reject = false; - } else { - if (config.include){ - reject = true; - } else { - reject = (foundInExclude || foundInIgnored); - } - } - - return reject; - }); - - return elements; - }; - - // Determine what type of element this is. It - // will either return the `type` attribute of - // an `` element, or the `tagName` of - // the element when the element is not an ``. - var getElementType = function(el){ - var typeAttr; - var $el = $(el); - var tagName = $el[0].tagName; - var type = tagName; - - if (tagName.toLowerCase() === "input"){ - typeAttr = $el.attr("type"); - if (typeAttr){ - type = typeAttr; - } else { - type = "text"; - } - } - - // Always return the type as lowercase - // so it can be matched to lowercase - // type registrations. - return type.toLowerCase(); - }; - - // If a form element is given, just return it. - // Otherwise, get the form element from the view. - var getForm = function(viewOrForm){ - if (_.isUndefined(viewOrForm.$el) && viewOrForm.tagName.toLowerCase() === 'form'){ - return viewOrForm; - } else { - return viewOrForm.$el.is("form") ? viewOrForm.el : viewOrForm.$("form")[0]; - } - }; - - // Build a configuration object and initialize - // default values. - var buildConfig = function(options){ - var config = _.clone(options) || {}; - - config.ignoredTypes = _.clone(Syphon.ignoredTypes); - config.inputReaders = config.inputReaders || Syphon.InputReaders; - config.inputWriters = config.inputWriters || Syphon.InputWriters; - config.keyExtractors = config.keyExtractors || Syphon.KeyExtractors; - config.keySplitter = config.keySplitter || Syphon.KeySplitter; - config.keyJoiner = config.keyJoiner || Syphon.KeyJoiner; - config.keyAssignmentValidators = config.keyAssignmentValidators || Syphon.KeyAssignmentValidators; - - return config; - }; - - // Assigns `value` to a parsed JSON key. - // - // The first parameter is the object which will be - // modified to store the key/value pair. - // - // The second parameter accepts an array of keys as a - // string with an option array containing a - // single string as the last option. - // - // The third parameter is the value to be assigned. - // - // Examples: - // - // `["foo", "bar", "baz"] => {foo: {bar: {baz: "value"}}}` - // - // `["foo", "bar", ["baz"]] => {foo: {bar: {baz: ["value"]}}}` - // - // When the final value is an array with a string, the key - // becomes an array, and values are pushed in to the array, - // allowing multiple fields with the same name to be - // assigned to the array. - var assignKeyValue = function(obj, keychain, value) { - if (!keychain){ return obj; } - - var key = keychain.shift(); - - // build the current object we need to store data - if (!obj[key]){ - obj[key] = _.isArray(key) ? [] : {}; - } - - // if it's the last key in the chain, assign the value directly - if (keychain.length === 0){ - if (_.isArray(obj[key])){ - obj[key].push(value); - } else { - obj[key] = value; - } - } - - // recursive parsing of the array, depth-first - if (keychain.length > 0){ - assignKeyValue(obj[key], keychain, value); - } - - return obj; - }; - - // Flatten the data structure in to nested strings, using the - // provided `KeyJoiner` function. - // - // Example: - // - // This input: - // - // ```js - // { - // widget: "wombat", - // foo: { - // bar: "baz", - // baz: { - // quux: "qux" - // }, - // quux: ["foo", "bar"] - // } - // } - // ``` - // - // With a KeyJoiner that uses [ ] square brackets, - // should produce this output: - // - // ```js - // { - // "widget": "wombat", - // "foo[bar]": "baz", - // "foo[baz][quux]": "qux", - // "foo[quux]": ["foo", "bar"] - // } - // ``` - var flattenData = function(config, data, parentKey){ - var flatData = {}; - - _.each(data, function(value, keyName){ - var hash = {}; - - // If there is a parent key, join it with - // the current, child key. - if (parentKey){ - keyName = config.keyJoiner(parentKey, keyName); - } - - if (_.isArray(value)){ - keyName += "[]"; - hash[keyName] = value; - } else if (_.isObject(value)){ - hash = flattenData(config, value, keyName); - } else { - hash[keyName] = value; - } - - // Store the resulting key/value pairs in the - // final flattened data object - _.extend(flatData, hash); - }); - - return flatData; - }; - - return Syphon; - })(Backbone, jQuery, _); - - // Type Registry - // ------------- - - // Type Registries allow you to register something to - // an input type, and retrieve either the item registered - // for a specific type or the default registration - Backbone.Syphon.TypeRegistry = function(){ - this.registeredTypes = {}; - }; - - // Borrow Backbone's `extend` keyword for our TypeRegistry - Backbone.Syphon.TypeRegistry.extend = Backbone.Model.extend; - - _.extend(Backbone.Syphon.TypeRegistry.prototype, { - - // Get the registered item by type. If nothing is - // found for the specified type, the default is - // returned. - get: function(type){ - var item = this.registeredTypes[type]; - - if (!item){ - item = this.registeredTypes["default"]; - } - - return item; - }, - - // Register a new item for a specified type - register: function(type, item){ - this.registeredTypes[type] = item; - }, - - // Register a default item to be used when no - // item for a specified type is found - registerDefault: function(item){ - this.registeredTypes["default"] = item; - }, - - // Remove an item from a given type registration - unregister: function(type){ - if (this.registeredTypes[type]){ - delete this.registeredTypes[type]; - } - } - }); - - - - - // Key Extractors - // -------------- - - // Key extractors produce the "key" in `{key: "value"}` - // pairs, when serializing. - Backbone.Syphon.KeyExtractorSet = Backbone.Syphon.TypeRegistry.extend(); - - // Built-in Key Extractors - Backbone.Syphon.KeyExtractors = new Backbone.Syphon.KeyExtractorSet(); - - // The default key extractor, which uses the - // input element's "id" attribute - Backbone.Syphon.KeyExtractors.registerDefault(function($el){ - return $el.prop("name"); - }); - - - // Input Readers - // ------------- - - // Input Readers are used to extract the value from - // an input element, for the serialized object result - Backbone.Syphon.InputReaderSet = Backbone.Syphon.TypeRegistry.extend(); - - // Built-in Input Readers - Backbone.Syphon.InputReaders = new Backbone.Syphon.InputReaderSet(); - - // The default input reader, which uses an input - // element's "value" - Backbone.Syphon.InputReaders.registerDefault(function($el){ - return $el.val(); - }); - - // Checkbox reader, returning a boolean value for - // whether or not the checkbox is checked. - Backbone.Syphon.InputReaders.register("checkbox", function($el){ - var checked = $el.prop("checked"); - return checked; - }); - - - // Input Writers - // ------------- - - // Input Writers are used to insert a value from an - // object into an input element. - Backbone.Syphon.InputWriterSet = Backbone.Syphon.TypeRegistry.extend(); - - // Built-in Input Writers - Backbone.Syphon.InputWriters = new Backbone.Syphon.InputWriterSet(); - - // The default input writer, which sets an input - // element's "value" - Backbone.Syphon.InputWriters.registerDefault(function($el, value){ - $el.val(value); - }); - - // Checkbox writer, set whether or not the checkbox is checked - // depending on the boolean value. - Backbone.Syphon.InputWriters.register("checkbox", function($el, value){ - $el.prop("checked", value); - }); - - // Radio button writer, set whether or not the radio button is - // checked. The button should only be checked if it's value - // equals the given value. - Backbone.Syphon.InputWriters.register("radio", function($el, value){ - $el.prop("checked", $el.val() === value); - }); - - // Key Assignment Validators - // ------------------------- - - // Key Assignment Validators are used to determine whether or not a - // key should be assigned to a value, after the key and value have been - // extracted from the element. This is the last opportunity to prevent - // bad data from getting serialized to your object. - - Backbone.Syphon.KeyAssignmentValidatorSet = Backbone.Syphon.TypeRegistry.extend(); - - // Build-in Key Assignment Validators - Backbone.Syphon.KeyAssignmentValidators = new Backbone.Syphon.KeyAssignmentValidatorSet(); - - // Everything is valid by default - Backbone.Syphon.KeyAssignmentValidators.registerDefault(function(){ return true; }); - - // But only the "checked" radio button for a given - // radio button group is valid - Backbone.Syphon.KeyAssignmentValidators.register("radio", function($el, key, value){ - return $el.prop("checked"); - }); - - - // Backbone.Syphon.KeySplitter - // --------------------------- - - // This function is used to split DOM element keys in to an array - // of parts, which are then used to create a nested result structure. - // returning `["foo", "bar"]` results in `{foo: { bar: "value" }}`. - // - // Override this method to use a custom key splitter, such as: - // ``, `return key.split(".")` - Backbone.Syphon.KeySplitter = function(key){ - var matches = key.match(/[^\[\]]+/g); - - if (key.indexOf("[]") === key.length - 2){ - lastKey = matches.pop(); - matches.push([lastKey]); - } - - return matches; - } - - - // Backbone.Syphon.KeyJoiner - // ------------------------- - - // Take two segments of a key and join them together, to create the - // de-normalized key name, when deserializing a data structure back - // in to a form. - // - // Example: - // - // With this data strucutre `{foo: { bar: {baz: "value", quux: "another"} } }`, - // the key joiner will be called with these parameters, and assuming the - // join happens with "[ ]" square brackets, the specified output: - // - // `KeyJoiner("foo", "bar")` //=> "foo[bar]" - // `KeyJoiner("foo[bar]", "baz")` //=> "foo[bar][baz]" - // `KeyJoiner("foo[bar]", "quux")` //=> "foo[bar][quux]" - - Backbone.Syphon.KeyJoiner = function(parentKey, childKey){ - return parentKey + "[" + childKey + "]"; - } - - - return Backbone.Syphon; -})); \ No newline at end of file diff --git a/lib/amd/backbone.syphon.min.js b/lib/amd/backbone.syphon.min.js deleted file mode 100644 index a0409fc..0000000 --- a/lib/amd/backbone.syphon.min.js +++ /dev/null @@ -1,5 +0,0 @@ -// Backbone.Syphon, v0.4.1 -// Copyright (c)2012 Derick Bailey, Muted Solutions, LLC. -// Distributed under MIT license -// http://github.com/derickbailey/backbone.syphon -(function(a,b){typeof define=="function"&&define.amd&&define(["underscore","jquery","backbone"],b)})(this,function(a,b,c){return c.Syphon=function(a,b,c){var d={};d.ignoredTypes=["button","submit","reset","fieldset"],d.serialize=function(a,d){var g={},j=h(d),k=e(a,j);return c.each(k,function(a){var c=b(a),d=f(c),e=j.keyExtractors.get(d),h=e(c),k=j.inputReaders.get(d),l=k(c),m=j.keyAssignmentValidators.get(d);if(m(c,h,l)){var n=j.keySplitter(h);g=i(g,n,l)}}),g},d.deserialize=function(a,d,g){var i=h(g),k=e(a,i),l=j(i,d);c.each(k,function(a){var c=b(a),d=f(c),e=i.keyExtractors.get(d),g=e(c),h=i.inputWriters.get(d),j=l[g];h(c,j)})};var e=function(a,d){var e=g(a),h=e.elements;return h=c.reject(h,function(a){var e,g=f(a),h=d.keyExtractors.get(g),i=h(b(a)),j=c.include(d.ignoredTypes,g),k=c.include(d.include,i),l=c.include(d.exclude,i);return k?e=!1:d.include?e=!0:e=l||j,e}),h},f=function(a){var c,d=b(a),e=d[0].tagName,f=e;return e.toLowerCase()==="input"&&(c=d.attr("type"),c?f=c:f="text"),f.toLowerCase()},g=function(a){return c.isUndefined(a.$el)&&a.tagName.toLowerCase()==="form"?a:a.$el.is("form")?a.el:a.$("form")[0]},h=function(a){var b=c.clone(a)||{};return b.ignoredTypes=c.clone(d.ignoredTypes),b.inputReaders=b.inputReaders||d.InputReaders,b.inputWriters=b.inputWriters||d.InputWriters,b.keyExtractors=b.keyExtractors||d.KeyExtractors,b.keySplitter=b.keySplitter||d.KeySplitter,b.keyJoiner=b.keyJoiner||d.KeyJoiner,b.keyAssignmentValidators=b.keyAssignmentValidators||d.KeyAssignmentValidators,b},i=function(a,b,d){if(!b)return a;var e=b.shift();return a[e]||(a[e]=c.isArray(e)?[]:{}),b.length===0&&(c.isArray(a[e])?a[e].push(d):a[e]=d),b.length>0&&i(a[e],b,d),a},j=function(a,b,d){var e={};return c.each(b,function(b,f){var g={};d&&(f=a.keyJoiner(d,f)),c.isArray(b)?(f+="[]",g[f]=b):c.isObject(b)?g=j(a,b,f):g[f]=b,c.extend(e,g)}),e};return d}(c,b,a),c.Syphon.TypeRegistry=function(){this.registeredTypes={}},c.Syphon.TypeRegistry.extend=c.Model.extend,a.extend(c.Syphon.TypeRegistry.prototype,{get:function(a){var b=this.registeredTypes[a];return b||(b=this.registeredTypes["default"]),b},register:function(a,b){this.registeredTypes[a]=b},registerDefault:function(a){this.registeredTypes["default"]=a},unregister:function(a){this.registeredTypes[a]&&delete this.registeredTypes[a]}}),c.Syphon.KeyExtractorSet=c.Syphon.TypeRegistry.extend(),c.Syphon.KeyExtractors=new c.Syphon.KeyExtractorSet,c.Syphon.KeyExtractors.registerDefault(function(a){return a.prop("name")}),c.Syphon.InputReaderSet=c.Syphon.TypeRegistry.extend(),c.Syphon.InputReaders=new c.Syphon.InputReaderSet,c.Syphon.InputReaders.registerDefault(function(a){return a.val()}),c.Syphon.InputReaders.register("checkbox",function(a){var b=a.prop("checked");return b}),c.Syphon.InputWriterSet=c.Syphon.TypeRegistry.extend(),c.Syphon.InputWriters=new c.Syphon.InputWriterSet,c.Syphon.InputWriters.registerDefault(function(a,b){a.val(b)}),c.Syphon.InputWriters.register("checkbox",function(a,b){a.prop("checked",b)}),c.Syphon.InputWriters.register("radio",function(a,b){a.prop("checked",a.val()===b)}),c.Syphon.KeyAssignmentValidatorSet=c.Syphon.TypeRegistry.extend(),c.Syphon.KeyAssignmentValidators=new c.Syphon.KeyAssignmentValidatorSet,c.Syphon.KeyAssignmentValidators.registerDefault(function(){return!0}),c.Syphon.KeyAssignmentValidators.register("radio",function(a,b,c){return a.prop("checked")}),c.Syphon.KeySplitter=function(a){var b=a.match(/[^\[\]]+/g);return a.indexOf("[]")===a.length-2&&(lastKey=b.pop(),b.push([lastKey])),b},c.Syphon.KeyJoiner=function(a,b){return a+"["+b+"]"},c.Syphon}); \ No newline at end of file diff --git a/lib/backbone.syphon.js b/lib/backbone.syphon.js index 3cd1537..a3816fe 100644 --- a/lib/backbone.syphon.js +++ b/lib/backbone.syphon.js @@ -1,46 +1,70 @@ -// Backbone.Syphon, v0.4.1 -// Copyright (c)2012 Derick Bailey, Muted Solutions, LLC. -// Distributed under MIT license -// http://github.com/derickbailey/backbone.syphon -Backbone.Syphon = (function(Backbone, $, _){ - var Syphon = {}; +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define(['underscore', 'backbone', 'jquery'], function(_, Backbone, $) { + return factory(_, Backbone, $); + }); + } else if (typeof exports !== 'undefined') { + var _ = require('underscore'); + var Backbone = require('backbone'); + var $ = require('jquery'); + module.exports = factory(_, Backbone, $); + } else { + factory(root._, root.Backbone, root.jQuery); + } + +}(this, function(_, Backbone, $) { + 'use strict'; + + var previousSyphon = Backbone.Syphon; + + var Syphon = Backbone.Syphon = {}; + + Syphon.VERSION = '0.5.0'; + + Syphon.noConflict = function() { + Backbone.Syphon = previousSyphon; + return this; + }; + + /* jshint maxstatements: 13, maxlen: 102, maxcomplexity: 8, latedef: false */ + // Ignore Element Types // -------------------- - + // Tell Syphon to ignore all elements of these types. You can // push new types to ignore directly in to this array. - Syphon.ignoredTypes = ["button", "submit", "reset", "fieldset"]; - + Syphon.ignoredTypes = ['button', 'submit', 'reset', 'fieldset']; + // Syphon // ------ - + // Get a JSON object that represents // all of the form inputs, in this view. // Alternately, pass a form element directly // in place of the view. Syphon.serialize = function(view, options){ var data = {}; - + // Build the configuration var config = buildConfig(options); - + // Get all of the elements to process var elements = getInputElements(view, config); - + // Process all of the elements _.each(elements, function(el){ var $el = $(el); - var type = getElementType($el); - + var type = getElementType($el); + // Get the key for the input var keyExtractor = config.keyExtractors.get(type); var key = keyExtractor($el); - + // Get the value for the input var inputReader = config.inputReaders.get(type); var value = inputReader($el); - + // Get the key assignment validator and make sure // it's valid before assigning the value to the key var validKeyAssignment = config.keyAssignmentValidators.get(type); @@ -49,7 +73,7 @@ Backbone.Syphon = (function(Backbone, $, _){ data = assignKeyValue(data, keychain, value); } }); - + // Done; send back the results. return data; }; @@ -61,50 +85,50 @@ Backbone.Syphon = (function(Backbone, $, _){ Syphon.deserialize = function(view, data, options){ // Build the configuration var config = buildConfig(options); - + // Get all of the elements to process var elements = getInputElements(view, config); - + // Flatten the data structure that we are deserializing var flattenedData = flattenData(config, data); - + // Process all of the elements _.each(elements, function(el){ var $el = $(el); - var type = getElementType($el); - + var type = getElementType($el); + // Get the key for the input var keyExtractor = config.keyExtractors.get(type); var key = keyExtractor($el); - + // Get the input writer and the value to write var inputWriter = config.inputWriters.get(type); var value = flattenedData[key]; - + // Write the value to the input inputWriter($el, value); }); }; - + // Helpers // ------- - + // Retrieve all of the form inputs // from the form var getInputElements = function(view, config){ var form = getForm(view); var elements = form.elements; - + elements = _.reject(elements, function(el){ var reject; var type = getElementType(el); var extractor = config.keyExtractors.get(type); var identifier = extractor($(el)); - + var foundInIgnored = _.include(config.ignoredTypes, type); var foundInInclude = _.include(config.include, identifier); var foundInExclude = _.include(config.exclude, identifier); - + if (foundInInclude){ reject = false; } else { @@ -114,13 +138,13 @@ Backbone.Syphon = (function(Backbone, $, _){ reject = (foundInExclude || foundInIgnored); } } - + return reject; }); - + return elements; }; - + // Determine what type of element this is. It // will either return the `type` attribute of // an `` element, or the `tagName` of @@ -130,37 +154,37 @@ Backbone.Syphon = (function(Backbone, $, _){ var $el = $(el); var tagName = $el[0].tagName; var type = tagName; - - if (tagName.toLowerCase() === "input"){ - typeAttr = $el.attr("type"); + + if (tagName.toLowerCase() === 'input'){ + typeAttr = $el.attr('type'); if (typeAttr){ type = typeAttr; } else { - type = "text"; + type = 'text'; } } - + // Always return the type as lowercase // so it can be matched to lowercase // type registrations. return type.toLowerCase(); }; - // If a form element is given, just return it. + // If a form element is given, just return it. // Otherwise, get the form element from the view. var getForm = function(viewOrForm){ if (_.isUndefined(viewOrForm.$el) && viewOrForm.tagName.toLowerCase() === 'form'){ return viewOrForm; } else { - return viewOrForm.$el.is("form") ? viewOrForm.el : viewOrForm.$("form")[0]; + return viewOrForm.$el.is('form') ? viewOrForm.el : viewOrForm.$('form')[0]; } }; - + // Build a configuration object and initialize // default values. var buildConfig = function(options){ var config = _.clone(options) || {}; - + config.ignoredTypes = _.clone(Syphon.ignoredTypes); config.inputReaders = config.inputReaders || Syphon.InputReaders; config.inputWriters = config.inputWriters || Syphon.InputWriters; @@ -168,41 +192,41 @@ Backbone.Syphon = (function(Backbone, $, _){ config.keySplitter = config.keySplitter || Syphon.KeySplitter; config.keyJoiner = config.keyJoiner || Syphon.KeyJoiner; config.keyAssignmentValidators = config.keyAssignmentValidators || Syphon.KeyAssignmentValidators; - + return config; }; - - // Assigns `value` to a parsed JSON key. + + // Assigns `value` to a parsed JSON key. // // The first parameter is the object which will be // modified to store the key/value pair. // - // The second parameter accepts an array of keys as a - // string with an option array containing a + // The second parameter accepts an array of keys as a + // string with an option array containing a // single string as the last option. // // The third parameter is the value to be assigned. // // Examples: // - // `["foo", "bar", "baz"] => {foo: {bar: {baz: "value"}}}` - // - // `["foo", "bar", ["baz"]] => {foo: {bar: {baz: ["value"]}}}` - // + // `['foo', 'bar', 'baz'] => {foo: {bar: {baz: 'value'}}}` + // + // `['foo', 'bar', ['baz']] => {foo: {bar: {baz: ['value']}}}` + // // When the final value is an array with a string, the key // becomes an array, and values are pushed in to the array, - // allowing multiple fields with the same name to be + // allowing multiple fields with the same name to be // assigned to the array. var assignKeyValue = function(obj, keychain, value) { if (!keychain){ return obj; } - + var key = keychain.shift(); - + // build the current object we need to store data if (!obj[key]){ obj[key] = _.isArray(key) ? [] : {}; } - + // if it's the last key in the chain, assign the value directly if (keychain.length === 0){ if (_.isArray(obj[key])){ @@ -211,15 +235,15 @@ Backbone.Syphon = (function(Backbone, $, _){ obj[key] = value; } } - + // recursive parsing of the array, depth-first if (keychain.length > 0){ assignKeyValue(obj[key], keychain, value); } - + return obj; }; - + // Flatten the data structure in to nested strings, using the // provided `KeyJoiner` function. // @@ -229,241 +253,231 @@ Backbone.Syphon = (function(Backbone, $, _){ // // ```js // { - // widget: "wombat", + // widget: 'wombat', // foo: { - // bar: "baz", + // bar: 'baz', // baz: { - // quux: "qux" + // quux: 'qux' // }, - // quux: ["foo", "bar"] + // quux: ['foo', 'bar'] // } // } // ``` // - // With a KeyJoiner that uses [ ] square brackets, + // With a KeyJoiner that uses [ ] square brackets, // should produce this output: // // ```js // { - // "widget": "wombat", - // "foo[bar]": "baz", - // "foo[baz][quux]": "qux", - // "foo[quux]": ["foo", "bar"] + // 'widget': 'wombat', + // 'foo[bar]': 'baz', + // 'foo[baz][quux]': 'qux', + // 'foo[quux]': ['foo', 'bar'] // } // ``` var flattenData = function(config, data, parentKey){ var flatData = {}; - + _.each(data, function(value, keyName){ var hash = {}; - + // If there is a parent key, join it with // the current, child key. if (parentKey){ keyName = config.keyJoiner(parentKey, keyName); } - + if (_.isArray(value)){ - keyName += "[]"; + keyName += '[]'; hash[keyName] = value; } else if (_.isObject(value)){ hash = flattenData(config, value, keyName); } else { hash[keyName] = value; } - + // Store the resulting key/value pairs in the // final flattened data object _.extend(flatData, hash); }); - + return flatData; }; - - return Syphon; -})(Backbone, jQuery, _); - -// Type Registry -// ------------- - -// Type Registries allow you to register something to -// an input type, and retrieve either the item registered -// for a specific type or the default registration -Backbone.Syphon.TypeRegistry = function(){ - this.registeredTypes = {}; -}; - -// Borrow Backbone's `extend` keyword for our TypeRegistry -Backbone.Syphon.TypeRegistry.extend = Backbone.Model.extend; - -_.extend(Backbone.Syphon.TypeRegistry.prototype, { - - // Get the registered item by type. If nothing is - // found for the specified type, the default is - // returned. - get: function(type){ - var item = this.registeredTypes[type]; - - if (!item){ - item = this.registeredTypes["default"]; + + // Type Registry + // ------------- + + // Type Registries allow you to register something to + // an input type, and retrieve either the item registered + // for a specific type or the default registration + var TypeRegistry = Syphon.TypeRegistry = function() { + this.registeredTypes = {}; + }; + + // Borrow Backbone's `extend` keyword for our TypeRegistry + TypeRegistry.extend = Backbone.Model.extend; + + _.extend(TypeRegistry.prototype, { + + // Get the registered item by type. If nothing is + // found for the specified type, the default is + // returned. + get: function(type){ + return this.registeredTypes[type] || this.registeredTypes['default']; + }, + + // Register a new item for a specified type + register: function(type, item) { + this.registeredTypes[type] = item; + }, + + // Register a default item to be used when no + // item for a specified type is found + registerDefault: function(item) { + this.registeredTypes['default'] = item; + }, + + // Remove an item from a given type registration + unregister: function(type) { + if (this.registeredTypes[type]) { + delete this.registeredTypes[type]; + } } - - return item; - }, - - // Register a new item for a specified type - register: function(type, item){ - this.registeredTypes[type] = item; - }, - - // Register a default item to be used when no - // item for a specified type is found - registerDefault: function(item){ - this.registeredTypes["default"] = item; - }, - - // Remove an item from a given type registration - unregister: function(type){ - if (this.registeredTypes[type]){ - delete this.registeredTypes[type]; + }); + + // Key Extractors + // -------------- + + // Key extractors produce the "key" in `{key: "value"}` + // pairs, when serializing. + var KeyExtractorSet = Syphon.KeyExtractorSet = TypeRegistry.extend(); + + // Built-in Key Extractors + var KeyExtractors = Syphon.KeyExtractors = new KeyExtractorSet(); + + // The default key extractor, which uses the + // input element's "name" attribute + KeyExtractors.registerDefault(function($el) { + return $el.prop('name') || ''; + }); + + // Input Readers + // ------------- + + // Input Readers are used to extract the value from + // an input element, for the serialized object result + var InputReaderSet = Syphon.InputReaderSet = TypeRegistry.extend(); + + // Built-in Input Readers + var InputReaders = Syphon.InputReaders = new InputReaderSet(); + + // The default input reader, which uses an input + // element's "value" + InputReaders.registerDefault(function($el){ + return $el.val(); + }); + + // Checkbox reader, returning a boolean value for + // whether or not the checkbox is checked. + InputReaders.register('checkbox', function($el) { + return $el.prop('checked'); + }); + + // Input Writers + // ------------- + + // Input Writers are used to insert a value from an + // object into an input element. + var InputWriterSet = Syphon.InputWriterSet = TypeRegistry.extend(); + + // Built-in Input Writers + var InputWriters = Syphon.InputWriters = new InputWriterSet(); + + // The default input writer, which sets an input + // element's "value" + InputWriters.registerDefault(function($el, value) { + $el.val(value); + }); + + // Checkbox writer, set whether or not the checkbox is checked + // depending on the boolean value. + InputWriters.register('checkbox', function($el, value) { + $el.prop('checked', value); + }); + + // Radio button writer, set whether or not the radio button is + // checked. The button should only be checked if it's value + // equals the given value. + InputWriters.register('radio', function($el, value) { + $el.prop('checked', $el.val() === value.toString()); + }); + + // Key Assignment Validators + // ------------------------- + + // Key Assignment Validators are used to determine whether or not a + // key should be assigned to a value, after the key and value have been + // extracted from the element. This is the last opportunity to prevent + // bad data from getting serialized to your object. + + var KeyAssignmentValidatorSet = Syphon.KeyAssignmentValidatorSet = TypeRegistry.extend(); + + // Build-in Key Assignment Validators + var KeyAssignmentValidators = Syphon.KeyAssignmentValidators = new KeyAssignmentValidatorSet(); + + // Everything is valid by default + KeyAssignmentValidators.registerDefault(function() { + return true; + }); + + // But only the "checked" radio button for a given + // radio button group is valid + KeyAssignmentValidators.register('radio', function($el, key, value) { + return $el.prop('checked'); + }); + + // Backbone.Syphon.KeySplitter + // --------------------------- + + // This function is used to split DOM element keys in to an array + // of parts, which are then used to create a nested result structure. + // returning `["foo", "bar"]` results in `{foo: { bar: "value" }}`. + // + // Override this method to use a custom key splitter, such as: + // ``, `return key.split(".")` + Syphon.KeySplitter = function(key) { + var matches = key.match(/[^\[\]]+/g); + var lastKey; + + if (key.indexOf('[]') === key.length - 2) { + lastKey = matches.pop(); + matches.push([lastKey]); } - } -}); - - - - -// Key Extractors -// -------------- - -// Key extractors produce the "key" in `{key: "value"}` -// pairs, when serializing. -Backbone.Syphon.KeyExtractorSet = Backbone.Syphon.TypeRegistry.extend(); - -// Built-in Key Extractors -Backbone.Syphon.KeyExtractors = new Backbone.Syphon.KeyExtractorSet(); - -// The default key extractor, which uses the -// input element's "id" attribute -Backbone.Syphon.KeyExtractors.registerDefault(function($el){ - return $el.prop("name"); -}); - - -// Input Readers -// ------------- - -// Input Readers are used to extract the value from -// an input element, for the serialized object result -Backbone.Syphon.InputReaderSet = Backbone.Syphon.TypeRegistry.extend(); - -// Built-in Input Readers -Backbone.Syphon.InputReaders = new Backbone.Syphon.InputReaderSet(); - -// The default input reader, which uses an input -// element's "value" -Backbone.Syphon.InputReaders.registerDefault(function($el){ - return $el.val(); -}); - -// Checkbox reader, returning a boolean value for -// whether or not the checkbox is checked. -Backbone.Syphon.InputReaders.register("checkbox", function($el){ - var checked = $el.prop("checked"); - return checked; -}); - - -// Input Writers -// ------------- - -// Input Writers are used to insert a value from an -// object into an input element. -Backbone.Syphon.InputWriterSet = Backbone.Syphon.TypeRegistry.extend(); - -// Built-in Input Writers -Backbone.Syphon.InputWriters = new Backbone.Syphon.InputWriterSet(); - -// The default input writer, which sets an input -// element's "value" -Backbone.Syphon.InputWriters.registerDefault(function($el, value){ - $el.val(value); -}); - -// Checkbox writer, set whether or not the checkbox is checked -// depending on the boolean value. -Backbone.Syphon.InputWriters.register("checkbox", function($el, value){ - $el.prop("checked", value); -}); - -// Radio button writer, set whether or not the radio button is -// checked. The button should only be checked if it's value -// equals the given value. -Backbone.Syphon.InputWriters.register("radio", function($el, value){ - $el.prop("checked", $el.val() === value); -}); - -// Key Assignment Validators -// ------------------------- - -// Key Assignment Validators are used to determine whether or not a -// key should be assigned to a value, after the key and value have been -// extracted from the element. This is the last opportunity to prevent -// bad data from getting serialized to your object. - -Backbone.Syphon.KeyAssignmentValidatorSet = Backbone.Syphon.TypeRegistry.extend(); - -// Build-in Key Assignment Validators -Backbone.Syphon.KeyAssignmentValidators = new Backbone.Syphon.KeyAssignmentValidatorSet(); - -// Everything is valid by default -Backbone.Syphon.KeyAssignmentValidators.registerDefault(function(){ return true; }); - -// But only the "checked" radio button for a given -// radio button group is valid -Backbone.Syphon.KeyAssignmentValidators.register("radio", function($el, key, value){ - return $el.prop("checked"); -}); - - -// Backbone.Syphon.KeySplitter -// --------------------------- - -// This function is used to split DOM element keys in to an array -// of parts, which are then used to create a nested result structure. -// returning `["foo", "bar"]` results in `{foo: { bar: "value" }}`. -// -// Override this method to use a custom key splitter, such as: -// ``, `return key.split(".")` -Backbone.Syphon.KeySplitter = function(key){ - var matches = key.match(/[^\[\]]+/g); - - if (key.indexOf("[]") === key.length - 2){ - lastKey = matches.pop(); - matches.push([lastKey]); - } - - return matches; -} - - -// Backbone.Syphon.KeyJoiner -// ------------------------- - -// Take two segments of a key and join them together, to create the -// de-normalized key name, when deserializing a data structure back -// in to a form. -// -// Example: -// -// With this data strucutre `{foo: { bar: {baz: "value", quux: "another"} } }`, -// the key joiner will be called with these parameters, and assuming the -// join happens with "[ ]" square brackets, the specified output: -// -// `KeyJoiner("foo", "bar")` //=> "foo[bar]" -// `KeyJoiner("foo[bar]", "baz")` //=> "foo[bar][baz]" -// `KeyJoiner("foo[bar]", "quux")` //=> "foo[bar][quux]" + + return matches; + }; + + // Backbone.Syphon.KeyJoiner + // ------------------------- + + // Take two segments of a key and join them together, to create the + // de-normalized key name, when deserializing a data structure back + // in to a form. + // + // Example: + // + // With this data strucutre `{foo: { bar: {baz: "value", quux: "another"} } }`, + // the key joiner will be called with these parameters, and assuming the + // join happens with "[ ]" square brackets, the specified output: + // + // `KeyJoiner("foo", "bar")` //=> "foo[bar]" + // `KeyJoiner("foo[bar]", "baz")` //=> "foo[bar][baz]" + // `KeyJoiner("foo[bar]", "quux")` //=> "foo[bar][quux]" + + Syphon.KeyJoiner = function(parentKey, childKey) { + return parentKey + '[' + childKey + ']'; + }; + -Backbone.Syphon.KeyJoiner = function(parentKey, childKey){ - return parentKey + "[" + childKey + "]"; -} + return Backbone.Syphon; +})); diff --git a/lib/backbone.syphon.min.js b/lib/backbone.syphon.min.js index e9cb073..752ffae 100644 --- a/lib/backbone.syphon.min.js +++ b/lib/backbone.syphon.min.js @@ -1,5 +1,7 @@ -// Backbone.Syphon, v0.4.1 -// Copyright (c)2012 Derick Bailey, Muted Solutions, LLC. +// Backbone.Syphon, v0.5.0 +// Copyright (c) 2014 Derick Bailey, Muted Solutions, LLC. // Distributed under MIT license -// http://github.com/derickbailey/backbone.syphon -Backbone.Syphon=function(a,b,c){var d={};d.ignoredTypes=["button","submit","reset","fieldset"],d.serialize=function(a,d){var g={},j=h(d),k=e(a,j);return c.each(k,function(a){var c=b(a),d=f(c),e=j.keyExtractors.get(d),h=e(c),k=j.inputReaders.get(d),l=k(c),m=j.keyAssignmentValidators.get(d);if(m(c,h,l)){var n=j.keySplitter(h);g=i(g,n,l)}}),g},d.deserialize=function(a,d,g){var i=h(g),k=e(a,i),l=j(i,d);c.each(k,function(a){var c=b(a),d=f(c),e=i.keyExtractors.get(d),g=e(c),h=i.inputWriters.get(d),j=l[g];h(c,j)})};var e=function(a,d){var e=g(a),h=e.elements;return h=c.reject(h,function(a){var e,g=f(a),h=d.keyExtractors.get(g),i=h(b(a)),j=c.include(d.ignoredTypes,g),k=c.include(d.include,i),l=c.include(d.exclude,i);return k?e=!1:d.include?e=!0:e=l||j,e}),h},f=function(a){var c,d=b(a),e=d[0].tagName,f=e;return e.toLowerCase()==="input"&&(c=d.attr("type"),c?f=c:f="text"),f.toLowerCase()},g=function(a){return c.isUndefined(a.$el)&&a.tagName.toLowerCase()==="form"?a:a.$el.is("form")?a.el:a.$("form")[0]},h=function(a){var b=c.clone(a)||{};return b.ignoredTypes=c.clone(d.ignoredTypes),b.inputReaders=b.inputReaders||d.InputReaders,b.inputWriters=b.inputWriters||d.InputWriters,b.keyExtractors=b.keyExtractors||d.KeyExtractors,b.keySplitter=b.keySplitter||d.KeySplitter,b.keyJoiner=b.keyJoiner||d.KeyJoiner,b.keyAssignmentValidators=b.keyAssignmentValidators||d.KeyAssignmentValidators,b},i=function(a,b,d){if(!b)return a;var e=b.shift();return a[e]||(a[e]=c.isArray(e)?[]:{}),b.length===0&&(c.isArray(a[e])?a[e].push(d):a[e]=d),b.length>0&&i(a[e],b,d),a},j=function(a,b,d){var e={};return c.each(b,function(b,f){var g={};d&&(f=a.keyJoiner(d,f)),c.isArray(b)?(f+="[]",g[f]=b):c.isObject(b)?g=j(a,b,f):g[f]=b,c.extend(e,g)}),e};return d}(Backbone,jQuery,_),Backbone.Syphon.TypeRegistry=function(){this.registeredTypes={}},Backbone.Syphon.TypeRegistry.extend=Backbone.Model.extend,_.extend(Backbone.Syphon.TypeRegistry.prototype,{get:function(a){var b=this.registeredTypes[a];return b||(b=this.registeredTypes["default"]),b},register:function(a,b){this.registeredTypes[a]=b},registerDefault:function(a){this.registeredTypes["default"]=a},unregister:function(a){this.registeredTypes[a]&&delete this.registeredTypes[a]}}),Backbone.Syphon.KeyExtractorSet=Backbone.Syphon.TypeRegistry.extend(),Backbone.Syphon.KeyExtractors=new Backbone.Syphon.KeyExtractorSet,Backbone.Syphon.KeyExtractors.registerDefault(function(a){return a.prop("name")}),Backbone.Syphon.InputReaderSet=Backbone.Syphon.TypeRegistry.extend(),Backbone.Syphon.InputReaders=new Backbone.Syphon.InputReaderSet,Backbone.Syphon.InputReaders.registerDefault(function(a){return a.val()}),Backbone.Syphon.InputReaders.register("checkbox",function(a){var b=a.prop("checked");return b}),Backbone.Syphon.InputWriterSet=Backbone.Syphon.TypeRegistry.extend(),Backbone.Syphon.InputWriters=new Backbone.Syphon.InputWriterSet,Backbone.Syphon.InputWriters.registerDefault(function(a,b){a.val(b)}),Backbone.Syphon.InputWriters.register("checkbox",function(a,b){a.prop("checked",b)}),Backbone.Syphon.InputWriters.register("radio",function(a,b){a.prop("checked",a.val()===b)}),Backbone.Syphon.KeyAssignmentValidatorSet=Backbone.Syphon.TypeRegistry.extend(),Backbone.Syphon.KeyAssignmentValidators=new Backbone.Syphon.KeyAssignmentValidatorSet,Backbone.Syphon.KeyAssignmentValidators.registerDefault(function(){return!0}),Backbone.Syphon.KeyAssignmentValidators.register("radio",function(a,b,c){return a.prop("checked")}),Backbone.Syphon.KeySplitter=function(a){var b=a.match(/[^\[\]]+/g);return a.indexOf("[]")===a.length-2&&(lastKey=b.pop(),b.push([lastKey])),b},Backbone.Syphon.KeyJoiner=function(a,b){return a+"["+b+"]"}; \ No newline at end of file +// http://github.com/marionettejs/backbone.syphon + +!function(a,b){if("function"==typeof define&&define.amd)define(["underscore","backbone","jquery"],function(a,c,d){return b(a,c,d)});else if("undefined"!=typeof exports){var c=require("underscore"),d=require("backbone"),e=require("jquery");module.exports=b(c,d,e)}else b(a._,a.Backbone,a.jQuery)}(this,function(a,b,c){"use strict";var d=b.Syphon,e=b.Syphon={};e.VERSION="0.5.0",e.noConflict=function(){return b.Syphon=d,this},e.ignoredTypes=["button","submit","reset","fieldset"],e.serialize=function(b,d){var e={},h=i(d),k=f(b,h);return a.each(k,function(a){var b=c(a),d=g(b),f=h.keyExtractors.get(d),i=f(b),k=h.inputReaders.get(d),l=k(b),m=h.keyAssignmentValidators.get(d);if(m(b,i,l)){var n=h.keySplitter(i);e=j(e,n,l)}}),e},e.deserialize=function(b,d,e){var h=i(e),j=f(b,h),l=k(h,d);a.each(j,function(a){var b=c(a),d=g(b),e=h.keyExtractors.get(d),f=e(b),i=h.inputWriters.get(d),j=l[f];i(b,j)})};var f=function(b,d){var e=h(b),f=e.elements;return f=a.reject(f,function(b){var e,f=g(b),h=d.keyExtractors.get(f),i=h(c(b)),j=a.include(d.ignoredTypes,f),k=a.include(d.include,i),l=a.include(d.exclude,i);return e=k?!1:d.include?!0:l||j})},g=function(a){var b,d=c(a),e=d[0].tagName,f=e;return"input"===e.toLowerCase()&&(b=d.attr("type"),f=b?b:"text"),f.toLowerCase()},h=function(b){return a.isUndefined(b.$el)&&"form"===b.tagName.toLowerCase()?b:b.$el.is("form")?b.el:b.$("form")[0]},i=function(b){var c=a.clone(b)||{};return c.ignoredTypes=a.clone(e.ignoredTypes),c.inputReaders=c.inputReaders||e.InputReaders,c.inputWriters=c.inputWriters||e.InputWriters,c.keyExtractors=c.keyExtractors||e.KeyExtractors,c.keySplitter=c.keySplitter||e.KeySplitter,c.keyJoiner=c.keyJoiner||e.KeyJoiner,c.keyAssignmentValidators=c.keyAssignmentValidators||e.KeyAssignmentValidators,c},j=function(b,c,d){if(!c)return b;var e=c.shift();return b[e]||(b[e]=a.isArray(e)?[]:{}),0===c.length&&(a.isArray(b[e])?b[e].push(d):b[e]=d),c.length>0&&j(b[e],c,d),b},k=function(b,c,d){var e={};return a.each(c,function(c,f){var g={};d&&(f=b.keyJoiner(d,f)),a.isArray(c)?(f+="[]",g[f]=c):a.isObject(c)?g=k(b,c,f):g[f]=c,a.extend(e,g)}),e},l=e.TypeRegistry=function(){this.registeredTypes={}};l.extend=b.Model.extend,a.extend(l.prototype,{get:function(a){return this.registeredTypes[a]||this.registeredTypes["default"]},register:function(a,b){this.registeredTypes[a]=b},registerDefault:function(a){this.registeredTypes["default"]=a},unregister:function(a){this.registeredTypes[a]&&delete this.registeredTypes[a]}});var m=e.KeyExtractorSet=l.extend(),n=e.KeyExtractors=new m;n.registerDefault(function(a){return a.prop("name")||""});var o=e.InputReaderSet=l.extend(),p=e.InputReaders=new o;p.registerDefault(function(a){return a.val()}),p.register("checkbox",function(a){return a.prop("checked")});var q=e.InputWriterSet=l.extend(),r=e.InputWriters=new q;r.registerDefault(function(a,b){a.val(b)}),r.register("checkbox",function(a,b){a.prop("checked",b)}),r.register("radio",function(a,b){a.prop("checked",a.val()===b.toString())});var s=e.KeyAssignmentValidatorSet=l.extend(),t=e.KeyAssignmentValidators=new s;return t.registerDefault(function(){return!0}),t.register("radio",function(a){return a.prop("checked")}),e.KeySplitter=function(a){var b,c=a.match(/[^\[\]]+/g);return a.indexOf("[]")===a.length-2&&(b=c.pop(),c.push([b])),c},e.KeyJoiner=function(a,b){return a+"["+b+"]"},b.Syphon}); +//# sourceMappingURL=backbone.syphon.min.map \ No newline at end of file diff --git a/lib/backbone.syphon.min.map b/lib/backbone.syphon.min.map new file mode 100644 index 0000000..833e277 --- /dev/null +++ b/lib/backbone.syphon.min.map @@ -0,0 +1 @@ +{"version":3,"file":"backbone.syphon.min.js","sources":["backbone.syphon.js"],"names":["root","factory","define","amd","_","Backbone","$","exports","require","module","jQuery","this","previousSyphon","Syphon","VERSION","noConflict","ignoredTypes","serialize","view","options","data","config","buildConfig","elements","getInputElements","each","el","$el","type","getElementType","keyExtractor","keyExtractors","get","key","inputReader","inputReaders","value","validKeyAssignment","keyAssignmentValidators","keychain","keySplitter","assignKeyValue","deserialize","flattenedData","flattenData","inputWriter","inputWriters","form","getForm","reject","extractor","identifier","foundInIgnored","include","foundInInclude","foundInExclude","exclude","typeAttr","tagName","toLowerCase","attr","viewOrForm","isUndefined","is","clone","InputReaders","InputWriters","KeyExtractors","KeySplitter","keyJoiner","KeyJoiner","KeyAssignmentValidators","obj","shift","isArray","length","push","parentKey","flatData","keyName","hash","isObject","extend","TypeRegistry","registeredTypes","Model","prototype","register","item","registerDefault","unregister","KeyExtractorSet","prop","InputReaderSet","val","InputWriterSet","toString","KeyAssignmentValidatorSet","lastKey","matches","match","indexOf","pop","childKey"],"mappings":"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"} \ No newline at end of file