diff --git a/README.md b/README.md index a3d8e35..40c99fe 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Names get reindexed if an item is added or deleted. The value given to the data-repeater-list attribute will be used as the base of rewritten name attributes. In this example, the first data-repeater-item's name attribute would become group-a[0][text-input], - and the second data-repeater-item woulc become group-a[1][text-input] + and the second data-repeater-item would become group-a[1][text-input] -->
@@ -93,6 +93,53 @@ Names get reindexed if an item is added or deleted. ``` +## Nested Example + +```html + +
+
+
+ + + + +
+
+
+ + +
+
+ +
+ +
+
+ +
+ + + + +``` + + ## repeaterVal Get a structured object of repeater values, without submitting the form. diff --git a/bower.json b/bower.json index 2a0fee3..aac2419 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "jquery.repeater", "main": "jquery.repeater.js", - "version": "1.1.0", + "version": "1.1.3", "homepage": "https://github.com/DubFriend/jquery.repeater", "description": "repeatable form input interface", "keywords": [ diff --git a/index.html b/index.html index 093b5ff..eb79c37 100644 --- a/index.html +++ b/index.html @@ -12,8 +12,8 @@ -

Repeater

-
+

Repeater

+
@@ -73,32 +73,34 @@

Repeater

+

Nested

+
+
+
+ + + +
+
+
+ + +
+
+ +
+ +
+
+ +
+ diff --git a/index.pre.html b/index.pre.html index ded610a..aef103f 100644 --- a/index.pre.html +++ b/index.pre.html @@ -12,34 +12,16 @@ -

Repeater

+

Repeater

+

Nested

+ diff --git a/jquery.repeater.js b/jquery.repeater.js index d993247..d94ea40 100644 --- a/jquery.repeater.js +++ b/jquery.repeater.js @@ -1,4 +1,4 @@ -// jquery.repeater version 1.1.2 +// jquery.repeater version 1.1.3 // https://github.com/DubFriend/jquery.repeater // (MIT) 06-12-2015 // Brian Detering (http://www.briandetering.net/) @@ -17,6 +17,10 @@ var isObject = function (value) { return !isArray(value) && (value instanceof Object); }; +var isNumber = function (value) { + return value instanceof Number; +}; + var isFunction = function (value) { return value instanceof Function; }; @@ -37,10 +41,25 @@ var foreach = function (collection, callback) { } }; + var last = function (array) { return array[array.length - 1]; }; +var argumentsToArray = function (args) { + return Array.prototype.slice.call(args); +}; + +var extend = function () { + var extended = {}; + foreach(argumentsToArray(arguments), function (o) { + foreach(o, function (val, key) { + extended[key] = val; + }); + }); + return extended; +}; + var mapToArray = function (collection, callback) { var mapped = []; foreach(collection, function (value, key, coll) { @@ -64,6 +83,12 @@ var map = function (collection, callback, keyCallback) { mapToObject(collection, callback, keyCallback); }; +var pluck = function (arrayOfObjects, key) { + return map(arrayOfObjects, function (val) { + return val[key]; + }); +}; + var filter = function (collection, callback) { var filtered; @@ -135,6 +160,7 @@ var mixinPubSub = function (object) { return object; }; + // jquery.input version 0.0.0 // https://github.com/DubFriend/jquery.input // (MIT) 09-04-2014 @@ -698,33 +724,77 @@ $.fn.inputClear = function () { }(jQuery)); $.fn.repeaterVal = function () { - var rawData = $(this).inputVal(); - var mapped = {}; - - foreach(rawData, function (val, key) { - var group, index, name; - var matches; - if(key !== "undefined") { - matches = key.match(/^([^\[]+)\[([0-9]+)\]\[([^\]]+)/); - group = matches[1]; - index = matches[2]; - name = matches[3]; - if(!mapped[group]) { - mapped[group] = []; + var parse = function (raw) { + var parsed = []; + + foreach(raw, function (val, key) { + var parsedKey = []; + if(key !== "undefined") { + parsedKey.push(key.match(/^[^\[]*/)[0]); + parsedKey = parsedKey.concat(map( + key.match(/\[[^\]]*\]/g), + function (bracketed) { + return bracketed.replace(/[\[\]]/g, ''); + } + )); + + parsed.push({ + val: val, + key: parsedKey + }); } + }); - if(!mapped[group][index]) { - mapped[group][index] = {}; - } + return parsed; + }; - mapped[group][index][name] = val; + var build = function (parsed) { + if( + parsed.length === 1 && + (parsed[0].key.length === 0 || parsed[0].key.length === 1 && !parsed[0].key[0]) + ) { + return parsed[0].val; } - }); - return mapped; + foreach(parsed, function (p) { + p.head = p.key.shift(); + }); + + var grouped = (function () { + var grouped = {}; + + foreach(parsed, function (p) { + if(!grouped[p.head]) { + grouped[p.head] = []; + } + grouped[p.head].push(p); + }); + + return grouped; + }()); + + var built; + + if(/^[0-9]+$/.test(parsed[0].head)) { + built = []; + foreach(grouped, function (group) { + built.push(build(group)); + }); + } + else { + built = {}; + foreach(grouped, function (group, key) { + built[key] = build(group); + }); + } + + return built; + }; + + return build(parse($(this).inputVal())); }; -$.fn.repeater = function(fig) { +$.fn.repeater = function (fig) { fig = fig || {}; $(this).each(function () { @@ -739,36 +809,89 @@ $.fn.repeater = function(fig) { removeElement(); }; - var $list = $self.find('[data-repeater-list]'); + var $list = $self.find('[data-repeater-list]').first(); + + var $filterNested = function ($items, repeaters) { + return $items.filter(function () { + return repeaters ? + $(this).closest( + pluck(repeaters, 'selector').join(',') + ).length === 0 : true; + }); + }; + + var $items = function () { + return $filterNested($list.find('[data-repeater-item]'), fig.repeaters); + }; var $itemTemplate = $list.find('[data-repeater-item]') - .first().clone().hide(); + .first().clone().hide(); - var $firstDeleteButton = $(this).find('[data-repeater-item]') - .first() + var $firstDeleteButton = $(this).find('[data-repeater-item]').first() .find('[data-repeater-delete]'); if(fig.isFirstItemUndeletable && $firstDeleteButton) { $firstDeleteButton.remove(); } - var groupName = $list.data('repeater-list'); + var getGroupName = function () { + var groupName = $list.data('repeater-list'); + return fig.$parent ? + fig.$parent.data('item-name') + '[' + groupName + ']' : + groupName; + }; + + var initNested = function ($listItems) { + if(fig.repeaters) { + $listItems.each(function () { + var $item = $(this); + foreach(fig.repeaters, function (nestedFig) { + $item.find(nestedFig.selector).repeater(extend( + nestedFig, { $parent: $item } + )); + }); + }); + } + }; - var setIndexes = function () { - $list.find('[data-repeater-item]').each(function (index) { - $(this).find('[name]').each(function () { + var $foreachRepeaterInItem = function (repeaters, $item, cb) { + if(repeaters) { + foreach(repeaters, function (nestedFig) { + cb.call($item.find(nestedFig.selector)[0], nestedFig); + }); + } + }; + + var setIndexes = function ($items, groupName, repeaters) { + $items.each(function (index) { + var $item = $(this); + $item.data('item-name', groupName + '[' + index + ']'); + $filterNested($item.find('[name]'), repeaters) + .each(function () { + var $input = $(this); // match non empty brackets (ex: "[foo]") - var matches = $(this).attr('name').match(/\[[^\]]+\]/g); + var matches = $input.attr('name').match(/\[[^\]]+\]/g); var name = matches ? // strip "[" and "]" characters last(matches).replace(/\[|\]/g, '') : - $(this).attr('name'); + $input.attr('name'); - var newName = groupName + '[' + index + '][' + name + ']' + - ($(this).is(':checkbox') || $(this).attr('multiple') ? '[]' : ''); - $(this).attr('name', newName); + var newName = groupName + '[' + index + '][' + name + ']' + + ($input.is(':checkbox') || $input.attr('multiple') ? '[]' : ''); + + $input.attr('name', newName); + + $foreachRepeaterInItem(repeaters, $item, function (nestedFig) { + var $repeater = $(this); + setIndexes( + $filterNested($repeater.find('[data-repeater-item]'), nestedFig.repeaters || []), + groupName + '[' + index + ']' + + '[' + $repeater.find('[data-repeater-list]').first().data('repeater-list') + ']', + nestedFig.repeaters + ); + }); }); }); @@ -777,59 +900,87 @@ $.fn.repeater = function(fig) { .prop('checked', true); }; - setIndexes(); + setIndexes($items(), getGroupName(), fig.repeaters); + initNested($items()); if(fig.ready) { - fig.ready(setIndexes); + fig.ready(function () { + setIndexes($items(), getGroupName(), fig.repeaters); + }); } - var setItemsValues = function ($item, values) { - var index; - index = $item.find('[name]').first() - .attr('name').match(/\[([0-9]*)\]/)[1]; - $item.inputVal(map(values, identity, function (name) { - var nameIfNotCheckbox = groupName + '[' + index + '][' + name + ']'; - return $item.find('[name="' + nameIfNotCheckbox + '"]').length ? - nameIfNotCheckbox : nameIfNotCheckbox + '[]'; - })); - }; var appendItem = (function () { - var setupTemplate = function ($item) { - var defaultValues = fig.defaultValues; + var setItemsValues = function ($item, values, repeaters) { + if(values) { + var inputNames = {}; + $filterNested($item.find('[name]'), repeaters).each(function () { + var key = $(this).attr('name').match(/\[([^\]]*)(\]|\]\[\])$/)[1]; + inputNames[key] = $(this).attr('name'); + }); + + $item.inputVal(map(values, identity, function (name) { + return inputNames[name]; + })); + } - $item.find('[name]').each(function () { - $(this).inputClear(); + $foreachRepeaterInItem(repeaters, $item, function (nestedFig) { + var $repeater = $(this); + $filterNested( + $repeater.find('[data-repeater-item]'), + nestedFig.repeaters + ) + .each(function () { + setItemsValues( + $(this), + nestedFig.defaultValues, + nestedFig.repeaters || [] + ); + }); }); - - if(defaultValues) { - setItemsValues($item, defaultValues); - } }; return function ($item) { $list.append($item); - setIndexes(); - setupTemplate($item); + setIndexes($items(), getGroupName(), fig.repeaters); + $item.find('[name]').each(function () { + $(this).inputClear(); + }); + setItemsValues($item, fig.defaultValues, fig.repeaters); }; }()); - $self.find('[data-repeater-create]').click(function () { + var addItem = function () { var $item = $itemTemplate.clone(); appendItem($item); + if(fig.repeaters) { + initNested($item); + } show.call($item.get(0)); + }; + + $self.children().each(function () { + if( + !$(this).is('[data-repeater-list]') && + $(this).find('[data-repeater-list]').length === 0 + ) { + if($(this).is('[data-repeater-create]')) { + $(this).click(addItem); + } + else if($(this).find('[data-repeater-create]').length !== 0) { + $(this).find('[data-repeater-create]').click(addItem); + } + } }); $list.on('click', '[data-repeater-delete]', function () { var self = $(this).closest('[data-repeater-item]').get(0); hide.call(self, function () { $(self).remove(); - setIndexes(); + setIndexes($items(), getGroupName(), fig.repeaters); }); }); - - }); return this; diff --git a/jquery.repeater.min.js b/jquery.repeater.min.js index e3ce06f..f3cff56 100644 --- a/jquery.repeater.min.js +++ b/jquery.repeater.min.js @@ -1,5 +1,5 @@ -// jquery.repeater version 1.1.2 +// jquery.repeater version 1.1.3 // https://github.com/DubFriend/jquery.repeater // (MIT) 06-12-2015 // Brian Detering (http://www.briandetering.net/) -!function(a){"use strict";var b=function(a){return a},c=function(b){return a.isArray(b)},d=function(a){return!c(a)&&a instanceof Object},e=function(b,c){return a.inArray(c,b)},f=function(a,b){return-1!==e(a,b)},g=function(a,b){for(var c in a)a.hasOwnProperty(c)&&b(a[c],c,a)},h=function(a){return a[a.length-1]},i=function(a,b){var c=[];return g(a,function(a,d,e){c.push(b(a,d,e))}),c},j=function(a,b,c){var d={};return g(a,function(a,e,f){e=c?c(e,a):e,d[e]=b(a,e,f)}),d},k=function(a,b,d){return c(a)?i(a,b):j(a,b,d)},l=function(a,b,c){return k(a,function(a,d){return a[b].apply(a,c||[])})},m=function(a){a=a||{};var b={};return a.publish=function(a,c){g(b[a],function(a){a(c)})},a.subscribe=function(a,c){b[a]=b[a]||[],b[a].push(c)},a.unsubscribe=function(a){g(b,function(b){var c=e(b,a);-1!==c&&b.splice(c,1)})},a};!function(a){var b=function(a,b){var c=m(),d=a.$;return c.getType=function(){throw'implement me (return type. "text", "radio", etc.)'},c.$=function(a){return a?d.find(a):d},c.disable=function(){c.$().prop("disabled",!0),c.publish("isEnabled",!1)},c.enable=function(){c.$().prop("disabled",!1),c.publish("isEnabled",!0)},b.equalTo=function(a,b){return a===b},b.publishChange=function(){var a;return function(d,e){var f=c.get();b.equalTo(f,a)||c.publish("change",{e:d,domElement:e}),a=f}}(),c},i=function(a,c){var d=b(a,c);return d.get=function(){return d.$().val()},d.set=function(a){d.$().val(a)},d.clear=function(){d.set("")},c.buildSetter=function(a){return function(b){a.call(d,b)}},d},j=function(a,b){a=c(a)?a:[a],b=c(b)?b:[b];var d=!0;return a.length!==b.length?d=!1:g(a,function(a){f(b,a)||(d=!1)}),d},k=function(a){var b={},c=i(a,b);return c.getType=function(){return"button"},c.$().on("change",function(a){b.publishChange(a,this)}),c},n=function(b){var d={},e=i(b,d);return e.getType=function(){return"checkbox"},e.get=function(){var b=[];return e.$().filter(":checked").each(function(){b.push(a(this).val())}),b},e.set=function(b){b=c(b)?b:[b],e.$().each(function(){a(this).prop("checked",!1)}),g(b,function(a){e.$().filter('[value="'+a+'"]').prop("checked",!0)})},d.equalTo=j,e.$().change(function(a){d.publishChange(a,this)}),e},o=function(a){var b={},c=x(a,b);return c.getType=function(){return"email"},c},p=function(c){var d={},e=b(c,d);return e.getType=function(){return"file"},e.get=function(){return h(e.$().val().split("\\"))},e.clear=function(){this.$().each(function(){a(this).wrap("
").closest("form").get(0).reset(),a(this).unwrap()})},e.$().change(function(a){d.publishChange(a,this)}),e},q=function(a){var b={},c=i(a,b);return c.getType=function(){return"hidden"},c.$().change(function(a){b.publishChange(a,this)}),c},r=function(c){var d={},e=b(c,d);return e.getType=function(){return"file[multiple]"},e.get=function(){var a,b=e.$().get(0).files||[],c=[];for(a=0;a<(b.length||0);a+=1)c.push(b[a].name);return c},e.clear=function(){this.$().each(function(){a(this).wrap("").closest("form").get(0).reset(),a(this).unwrap()})},e.$().change(function(a){d.publishChange(a,this)}),e},s=function(a){var b={},d=i(a,b);return d.getType=function(){return"select[multiple]"},d.get=function(){return d.$().val()||[]},d.set=function(a){d.$().val(""===a?[]:c(a)?a:[a])},b.equalTo=j,d.$().change(function(a){b.publishChange(a,this)}),d},t=function(a){var b={},c=x(a,b);return c.getType=function(){return"password"},c},u=function(b){var c={},d=i(b,c);return d.getType=function(){return"radio"},d.get=function(){return d.$().filter(":checked").val()||null},d.set=function(b){b?d.$().filter('[value="'+b+'"]').prop("checked",!0):d.$().each(function(){a(this).prop("checked",!1)})},d.$().change(function(a){c.publishChange(a,this)}),d},v=function(a){var b={},c=i(a,b);return c.getType=function(){return"range"},c.$().change(function(a){b.publishChange(a,this)}),c},w=function(a){var b={},c=i(a,b);return c.getType=function(){return"select"},c.$().change(function(a){b.publishChange(a,this)}),c},x=function(a){var b={},c=i(a,b);return c.getType=function(){return"text"},c.$().on("change keyup keydown",function(a){b.publishChange(a,this)}),c},y=function(a){var b={},c=i(a,b);return c.getType=function(){return"textarea"},c.$().on("change keyup keydown",function(a){b.publishChange(a,this)}),c},z=function(a){var b={},c=x(a,b);return c.getType=function(){return"url"},c},A=function(b){var c={},f=b.$,h=b.constructorOverride||{button:k,text:x,url:z,email:o,password:t,range:v,textarea:y,select:w,"select[multiple]":s,radio:u,checkbox:n,file:p,"file[multiple]":r,hidden:q},i=function(b,e){var g=d(e)?e:f.find(e);g.each(function(){var d=a(this).attr("name");c[d]=h[b]({$:a(this)})})},j=function(b,i){var j=[],k=d(i)?i:f.find(i);d(i)?c[k.attr("name")]=h[b]({$:k}):(k.each(function(){-1===e(j,a(this).attr("name"))&&j.push(a(this).attr("name"))}),g(j,function(a){c[a]=h[b]({$:f.find('input[name="'+a+'"]')})}))};return f.is("input, select, textarea")?f.is('input[type="button"], button, input[type="submit"]')?i("button",f):f.is("textarea")?i("textarea",f):f.is('input[type="text"]')||f.is("input")&&!f.attr("type")?i("text",f):f.is('input[type="password"]')?i("password",f):f.is('input[type="email"]')?i("email",f):f.is('input[type="url"]')?i("url",f):f.is('input[type="range"]')?i("range",f):f.is("select")?f.is("[multiple]")?i("select[multiple]",f):i("select",f):f.is('input[type="file"]')?f.is("[multiple]")?i("file[multiple]",f):i("file",f):f.is('input[type="hidden"]')?i("hidden",f):f.is('input[type="radio"]')?j("radio",f):f.is('input[type="checkbox"]')?j("checkbox",f):i("text",f):(i("button",'input[type="button"], button, input[type="submit"]'),i("text",'input[type="text"]'),i("password",'input[type="password"]'),i("email",'input[type="email"]'),i("url",'input[type="url"]'),i("range",'input[type="range"]'),i("textarea","textarea"),i("select","select:not([multiple])"),i("select[multiple]","select[multiple]"),i("file",'input[type="file"]:not([multiple])'),i("file[multiple]",'input[type="file"][multiple]'),i("hidden",'input[type="hidden"]'),j("radio",'input[type="radio"]'),j("checkbox",'input[type="checkbox"]')),c};a.fn.inputVal=function(b){var c=a(this),d=A({$:c});return c.is("input, textarea, select")?"undefined"==typeof b?d[c.attr("name")].get():(d[c.attr("name")].set(b),c):"undefined"==typeof b?l(d,"get"):(g(b,function(a,b){d[b].set(a)}),c)},a.fn.inputOnChange=function(b){var c=a(this),d=A({$:c});return g(d,function(a){a.subscribe("change",function(a){b.call(a.domElement,a.e)})}),c},a.fn.inputDisable=function(){var b=a(this);return l(A({$:b}),"disable"),b},a.fn.inputEnable=function(){var b=a(this);return l(A({$:b}),"enable"),b},a.fn.inputClear=function(){var b=a(this);return l(A({$:b}),"clear"),b}}(jQuery),a.fn.repeaterVal=function(){var b=a(this).inputVal(),c={};return g(b,function(a,b){var d,e,f,g;"undefined"!==b&&(g=b.match(/^([^\[]+)\[([0-9]+)\]\[([^\]]+)/),d=g[1],e=g[2],f=g[3],c[d]||(c[d]=[]),c[d][e]||(c[d][e]={}),c[d][e][f]=a)}),c},a.fn.repeater=function(c){return c=c||{},a(this).each(function(){var d=a(this),e=c.show||function(){a(this).show()},f=c.hide||function(a){a()},g=d.find("[data-repeater-list]"),i=g.find("[data-repeater-item]").first().clone().hide(),j=a(this).find("[data-repeater-item]").first().find("[data-repeater-delete]");c.isFirstItemUndeletable&&j&&j.remove();var l=g.data("repeater-list"),m=function(){g.find("[data-repeater-item]").each(function(b){a(this).find("[name]").each(function(){var c=a(this).attr("name").match(/\[[^\]]+\]/g),d=c?h(c).replace(/\[|\]/g,""):a(this).attr("name"),e=l+"["+b+"]["+d+"]"+(a(this).is(":checkbox")||a(this).attr("multiple")?"[]":"");a(this).attr("name",e)})}),g.find("input[name][checked]").removeAttr("checked").prop("checked",!0)};m(),c.ready&&c.ready(m);var n=function(a,c){var d;d=a.find("[name]").first().attr("name").match(/\[([0-9]*)\]/)[1],a.inputVal(k(c,b,function(b){var c=l+"["+d+"]["+b+"]";return a.find('[name="'+c+'"]').length?c:c+"[]"}))},o=function(){var b=function(b){var d=c.defaultValues;b.find("[name]").each(function(){a(this).inputClear()}),d&&n(b,d)};return function(a){g.append(a),m(),b(a)}}();d.find("[data-repeater-create]").click(function(){var a=i.clone();o(a),e.call(a.get(0))}),g.on("click","[data-repeater-delete]",function(){var b=a(this).closest("[data-repeater-item]").get(0);f.call(b,function(){a(b).remove(),m()})})}),this}}(jQuery); \ No newline at end of file +!function(a){"use strict";var b=function(a){return a},c=function(b){return a.isArray(b)},d=function(a){return!c(a)&&a instanceof Object},e=function(b,c){return a.inArray(c,b)},f=function(a,b){return-1!==e(a,b)},g=function(a,b){for(var c in a)a.hasOwnProperty(c)&&b(a[c],c,a)},h=function(a){return a[a.length-1]},i=function(a){return Array.prototype.slice.call(a)},j=function(){var a={};return g(i(arguments),function(b){g(b,function(b,c){a[c]=b})}),a},k=function(a,b){var c=[];return g(a,function(a,d,e){c.push(b(a,d,e))}),c},l=function(a,b,c){var d={};return g(a,function(a,e,f){e=c?c(e,a):e,d[e]=b(a,e,f)}),d},m=function(a,b,d){return c(a)?k(a,b):l(a,b,d)},n=function(a,b){return m(a,function(a){return a[b]})},o=function(a,b,c){return m(a,function(a,d){return a[b].apply(a,c||[])})},p=function(a){a=a||{};var b={};return a.publish=function(a,c){g(b[a],function(a){a(c)})},a.subscribe=function(a,c){b[a]=b[a]||[],b[a].push(c)},a.unsubscribe=function(a){g(b,function(b){var c=e(b,a);-1!==c&&b.splice(c,1)})},a};!function(a){var b=function(a,b){var c=p(),d=a.$;return c.getType=function(){throw'implement me (return type. "text", "radio", etc.)'},c.$=function(a){return a?d.find(a):d},c.disable=function(){c.$().prop("disabled",!0),c.publish("isEnabled",!1)},c.enable=function(){c.$().prop("disabled",!1),c.publish("isEnabled",!0)},b.equalTo=function(a,b){return a===b},b.publishChange=function(){var a;return function(d,e){var f=c.get();b.equalTo(f,a)||c.publish("change",{e:d,domElement:e}),a=f}}(),c},i=function(a,c){var d=b(a,c);return d.get=function(){return d.$().val()},d.set=function(a){d.$().val(a)},d.clear=function(){d.set("")},c.buildSetter=function(a){return function(b){a.call(d,b)}},d},j=function(a,b){a=c(a)?a:[a],b=c(b)?b:[b];var d=!0;return a.length!==b.length?d=!1:g(a,function(a){f(b,a)||(d=!1)}),d},k=function(a){var b={},c=i(a,b);return c.getType=function(){return"button"},c.$().on("change",function(a){b.publishChange(a,this)}),c},l=function(b){var d={},e=i(b,d);return e.getType=function(){return"checkbox"},e.get=function(){var b=[];return e.$().filter(":checked").each(function(){b.push(a(this).val())}),b},e.set=function(b){b=c(b)?b:[b],e.$().each(function(){a(this).prop("checked",!1)}),g(b,function(a){e.$().filter('[value="'+a+'"]').prop("checked",!0)})},d.equalTo=j,e.$().change(function(a){d.publishChange(a,this)}),e},m=function(a){var b={},c=x(a,b);return c.getType=function(){return"email"},c},n=function(c){var d={},e=b(c,d);return e.getType=function(){return"file"},e.get=function(){return h(e.$().val().split("\\"))},e.clear=function(){this.$().each(function(){a(this).wrap("").closest("form").get(0).reset(),a(this).unwrap()})},e.$().change(function(a){d.publishChange(a,this)}),e},q=function(a){var b={},c=i(a,b);return c.getType=function(){return"hidden"},c.$().change(function(a){b.publishChange(a,this)}),c},r=function(c){var d={},e=b(c,d);return e.getType=function(){return"file[multiple]"},e.get=function(){var a,b=e.$().get(0).files||[],c=[];for(a=0;a<(b.length||0);a+=1)c.push(b[a].name);return c},e.clear=function(){this.$().each(function(){a(this).wrap("").closest("form").get(0).reset(),a(this).unwrap()})},e.$().change(function(a){d.publishChange(a,this)}),e},s=function(a){var b={},d=i(a,b);return d.getType=function(){return"select[multiple]"},d.get=function(){return d.$().val()||[]},d.set=function(a){d.$().val(""===a?[]:c(a)?a:[a])},b.equalTo=j,d.$().change(function(a){b.publishChange(a,this)}),d},t=function(a){var b={},c=x(a,b);return c.getType=function(){return"password"},c},u=function(b){var c={},d=i(b,c);return d.getType=function(){return"radio"},d.get=function(){return d.$().filter(":checked").val()||null},d.set=function(b){b?d.$().filter('[value="'+b+'"]').prop("checked",!0):d.$().each(function(){a(this).prop("checked",!1)})},d.$().change(function(a){c.publishChange(a,this)}),d},v=function(a){var b={},c=i(a,b);return c.getType=function(){return"range"},c.$().change(function(a){b.publishChange(a,this)}),c},w=function(a){var b={},c=i(a,b);return c.getType=function(){return"select"},c.$().change(function(a){b.publishChange(a,this)}),c},x=function(a){var b={},c=i(a,b);return c.getType=function(){return"text"},c.$().on("change keyup keydown",function(a){b.publishChange(a,this)}),c},y=function(a){var b={},c=i(a,b);return c.getType=function(){return"textarea"},c.$().on("change keyup keydown",function(a){b.publishChange(a,this)}),c},z=function(a){var b={},c=x(a,b);return c.getType=function(){return"url"},c},A=function(b){var c={},f=b.$,h=b.constructorOverride||{button:k,text:x,url:z,email:m,password:t,range:v,textarea:y,select:w,"select[multiple]":s,radio:u,checkbox:l,file:n,"file[multiple]":r,hidden:q},i=function(b,e){var g=d(e)?e:f.find(e);g.each(function(){var d=a(this).attr("name");c[d]=h[b]({$:a(this)})})},j=function(b,i){var j=[],k=d(i)?i:f.find(i);d(i)?c[k.attr("name")]=h[b]({$:k}):(k.each(function(){-1===e(j,a(this).attr("name"))&&j.push(a(this).attr("name"))}),g(j,function(a){c[a]=h[b]({$:f.find('input[name="'+a+'"]')})}))};return f.is("input, select, textarea")?f.is('input[type="button"], button, input[type="submit"]')?i("button",f):f.is("textarea")?i("textarea",f):f.is('input[type="text"]')||f.is("input")&&!f.attr("type")?i("text",f):f.is('input[type="password"]')?i("password",f):f.is('input[type="email"]')?i("email",f):f.is('input[type="url"]')?i("url",f):f.is('input[type="range"]')?i("range",f):f.is("select")?f.is("[multiple]")?i("select[multiple]",f):i("select",f):f.is('input[type="file"]')?f.is("[multiple]")?i("file[multiple]",f):i("file",f):f.is('input[type="hidden"]')?i("hidden",f):f.is('input[type="radio"]')?j("radio",f):f.is('input[type="checkbox"]')?j("checkbox",f):i("text",f):(i("button",'input[type="button"], button, input[type="submit"]'),i("text",'input[type="text"]'),i("password",'input[type="password"]'),i("email",'input[type="email"]'),i("url",'input[type="url"]'),i("range",'input[type="range"]'),i("textarea","textarea"),i("select","select:not([multiple])"),i("select[multiple]","select[multiple]"),i("file",'input[type="file"]:not([multiple])'),i("file[multiple]",'input[type="file"][multiple]'),i("hidden",'input[type="hidden"]'),j("radio",'input[type="radio"]'),j("checkbox",'input[type="checkbox"]')),c};a.fn.inputVal=function(b){var c=a(this),d=A({$:c});return c.is("input, textarea, select")?"undefined"==typeof b?d[c.attr("name")].get():(d[c.attr("name")].set(b),c):"undefined"==typeof b?o(d,"get"):(g(b,function(a,b){d[b].set(a)}),c)},a.fn.inputOnChange=function(b){var c=a(this),d=A({$:c});return g(d,function(a){a.subscribe("change",function(a){b.call(a.domElement,a.e)})}),c},a.fn.inputDisable=function(){var b=a(this);return o(A({$:b}),"disable"),b},a.fn.inputEnable=function(){var b=a(this);return o(A({$:b}),"enable"),b},a.fn.inputClear=function(){var b=a(this);return o(A({$:b}),"clear"),b}}(jQuery),a.fn.repeaterVal=function(){var b=function(a){var b=[];return g(a,function(a,c){var d=[];"undefined"!==c&&(d.push(c.match(/^[^\[]*/)[0]),d=d.concat(m(c.match(/\[[^\]]*\]/g),function(a){return a.replace(/[\[\]]/g,"")})),b.push({val:a,key:d}))}),b},c=function(a){if(1===a.length&&(0===a[0].key.length||1===a[0].key.length&&!a[0].key[0]))return a[0].val;g(a,function(a){a.head=a.key.shift()});var b,d=function(){var b={};return g(a,function(a){b[a.head]||(b[a.head]=[]),b[a.head].push(a)}),b}();return/^[0-9]+$/.test(a[0].head)?(b=[],g(d,function(a){b.push(c(a))})):(b={},g(d,function(a,d){b[d]=c(a)})),b};return c(b(a(this).inputVal()))},a.fn.repeater=function(c){return c=c||{},a(this).each(function(){var d=a(this),e=c.show||function(){a(this).show()},f=c.hide||function(a){a()},i=d.find("[data-repeater-list]").first(),k=function(b,c){return b.filter(function(){return c?0===a(this).closest(n(c,"selector").join(",")).length:!0})},l=function(){return k(i.find("[data-repeater-item]"),c.repeaters)},o=i.find("[data-repeater-item]").first().clone().hide(),p=a(this).find("[data-repeater-item]").first().find("[data-repeater-delete]");c.isFirstItemUndeletable&&p&&p.remove();var q=function(){var a=i.data("repeater-list");return c.$parent?c.$parent.data("item-name")+"["+a+"]":a},r=function(b){c.repeaters&&b.each(function(){var b=a(this);g(c.repeaters,function(a){b.find(a.selector).repeater(j(a,{$parent:b}))})})},s=function(a,b,c){a&&g(a,function(a){c.call(b.find(a.selector)[0],a)})},t=function(b,c,d){b.each(function(b){var e=a(this);e.data("item-name",c+"["+b+"]"),k(e.find("[name]"),d).each(function(){var f=a(this),g=f.attr("name").match(/\[[^\]]+\]/g),i=g?h(g).replace(/\[|\]/g,""):f.attr("name"),j=c+"["+b+"]["+i+"]"+(f.is(":checkbox")||f.attr("multiple")?"[]":"");f.attr("name",j),s(d,e,function(d){var e=a(this);t(k(e.find("[data-repeater-item]"),d.repeaters||[]),c+"["+b+"]["+e.find("[data-repeater-list]").first().data("repeater-list")+"]",d.repeaters)})})}),i.find("input[name][checked]").removeAttr("checked").prop("checked",!0)};t(l(),q(),c.repeaters),r(l()),c.ready&&c.ready(function(){t(l(),q(),c.repeaters)});var u=function(){var d=function(c,e,f){if(e){var g={};k(c.find("[name]"),f).each(function(){var b=a(this).attr("name").match(/\[([^\]]*)(\]|\]\[\])$/)[1];g[b]=a(this).attr("name")}),c.inputVal(m(e,b,function(a){return g[a]}))}s(f,c,function(b){var c=a(this);k(c.find("[data-repeater-item]"),b.repeaters).each(function(){d(a(this),b.defaultValues,b.repeaters||[])})})};return function(b){i.append(b),t(l(),q(),c.repeaters),b.find("[name]").each(function(){a(this).inputClear()}),d(b,c.defaultValues,c.repeaters)}}(),v=function(){var a=o.clone();u(a),c.repeaters&&r(a),e.call(a.get(0))};d.children().each(function(){a(this).is("[data-repeater-list]")||0!==a(this).find("[data-repeater-list]").length||(a(this).is("[data-repeater-create]")?a(this).click(v):0!==a(this).find("[data-repeater-create]").length&&a(this).find("[data-repeater-create]").click(v))}),i.on("click","[data-repeater-delete]",function(){var b=a(this).closest("[data-repeater-item]").get(0);f.call(b,function(){a(b).remove(),t(l(),q(),c.repeaters)})})}),this}}(jQuery); \ No newline at end of file diff --git a/nested-repeater.html b/nested-repeater.html new file mode 100644 index 0000000..a71c5b5 --- /dev/null +++ b/nested-repeater.html @@ -0,0 +1,20 @@ + +
+
+ + + +
+
+
+ + +
+
+ +
+ +
+
+ +
diff --git a/package.json b/package.json index 990c259..86b7b0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.repeater", - "version": "1.1.2", + "version": "1.1.3", "description": "repeatable form input interface", "main": "jquery.repeater.js", "directories": { diff --git a/repeater.html b/repeater.html index 8ca3bf2..e6001cb 100644 --- a/repeater.html +++ b/repeater.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/lib.js b/src/lib.js index 1ebb394..cc82ff3 100644 --- a/src/lib.js +++ b/src/lib.js @@ -10,6 +10,10 @@ var isObject = function (value) { return !isArray(value) && (value instanceof Object); }; +var isNumber = function (value) { + return value instanceof Number; +}; + var isFunction = function (value) { return value instanceof Function; }; @@ -30,10 +34,25 @@ var foreach = function (collection, callback) { } }; + var last = function (array) { return array[array.length - 1]; }; +var argumentsToArray = function (args) { + return Array.prototype.slice.call(args); +}; + +var extend = function () { + var extended = {}; + foreach(argumentsToArray(arguments), function (o) { + foreach(o, function (val, key) { + extended[key] = val; + }); + }); + return extended; +}; + var mapToArray = function (collection, callback) { var mapped = []; foreach(collection, function (value, key, coll) { @@ -57,6 +76,12 @@ var map = function (collection, callback, keyCallback) { mapToObject(collection, callback, keyCallback); }; +var pluck = function (arrayOfObjects, key) { + return map(arrayOfObjects, function (val) { + return val[key]; + }); +}; + var filter = function (collection, callback) { var filtered; @@ -127,4 +152,4 @@ var mixinPubSub = function (object) { }; return object; -}; \ No newline at end of file +}; diff --git a/src/repeater.js b/src/repeater.js index b3cbe41..fb67e89 100644 --- a/src/repeater.js +++ b/src/repeater.js @@ -1,31 +1,75 @@ $.fn.repeaterVal = function () { - var rawData = $(this).inputVal(); - var mapped = {}; - - foreach(rawData, function (val, key) { - var group, index, name; - var matches; - if(key !== "undefined") { - matches = key.match(/^([^\[]+)\[([0-9]+)\]\[([^\]]+)/); - group = matches[1]; - index = matches[2]; - name = matches[3]; - if(!mapped[group]) { - mapped[group] = []; + var parse = function (raw) { + var parsed = []; + + foreach(raw, function (val, key) { + var parsedKey = []; + if(key !== "undefined") { + parsedKey.push(key.match(/^[^\[]*/)[0]); + parsedKey = parsedKey.concat(map( + key.match(/\[[^\]]*\]/g), + function (bracketed) { + return bracketed.replace(/[\[\]]/g, ''); + } + )); + + parsed.push({ + val: val, + key: parsedKey + }); } + }); - if(!mapped[group][index]) { - mapped[group][index] = {}; - } + return parsed; + }; - mapped[group][index][name] = val; + var build = function (parsed) { + if( + parsed.length === 1 && + (parsed[0].key.length === 0 || parsed[0].key.length === 1 && !parsed[0].key[0]) + ) { + return parsed[0].val; } - }); - return mapped; + foreach(parsed, function (p) { + p.head = p.key.shift(); + }); + + var grouped = (function () { + var grouped = {}; + + foreach(parsed, function (p) { + if(!grouped[p.head]) { + grouped[p.head] = []; + } + grouped[p.head].push(p); + }); + + return grouped; + }()); + + var built; + + if(/^[0-9]+$/.test(parsed[0].head)) { + built = []; + foreach(grouped, function (group) { + built.push(build(group)); + }); + } + else { + built = {}; + foreach(grouped, function (group, key) { + built[key] = build(group); + }); + } + + return built; + }; + + return build(parse($(this).inputVal())); }; -$.fn.repeater = function(fig) { +$.fn.repeater = function (fig) { fig = fig || {}; $(this).each(function () { @@ -40,36 +84,89 @@ $.fn.repeater = function(fig) { removeElement(); }; - var $list = $self.find('[data-repeater-list]'); + var $list = $self.find('[data-repeater-list]').first(); + + var $filterNested = function ($items, repeaters) { + return $items.filter(function () { + return repeaters ? + $(this).closest( + pluck(repeaters, 'selector').join(',') + ).length === 0 : true; + }); + }; + + var $items = function () { + return $filterNested($list.find('[data-repeater-item]'), fig.repeaters); + }; var $itemTemplate = $list.find('[data-repeater-item]') - .first().clone().hide(); + .first().clone().hide(); - var $firstDeleteButton = $(this).find('[data-repeater-item]') - .first() + var $firstDeleteButton = $(this).find('[data-repeater-item]').first() .find('[data-repeater-delete]'); if(fig.isFirstItemUndeletable && $firstDeleteButton) { $firstDeleteButton.remove(); } - var groupName = $list.data('repeater-list'); + var getGroupName = function () { + var groupName = $list.data('repeater-list'); + return fig.$parent ? + fig.$parent.data('item-name') + '[' + groupName + ']' : + groupName; + }; - var setIndexes = function () { - $list.find('[data-repeater-item]').each(function (index) { - $(this).find('[name]').each(function () { + var initNested = function ($listItems) { + if(fig.repeaters) { + $listItems.each(function () { + var $item = $(this); + foreach(fig.repeaters, function (nestedFig) { + $item.find(nestedFig.selector).repeater(extend( + nestedFig, { $parent: $item } + )); + }); + }); + } + }; + + var $foreachRepeaterInItem = function (repeaters, $item, cb) { + if(repeaters) { + foreach(repeaters, function (nestedFig) { + cb.call($item.find(nestedFig.selector)[0], nestedFig); + }); + } + }; + + var setIndexes = function ($items, groupName, repeaters) { + $items.each(function (index) { + var $item = $(this); + $item.data('item-name', groupName + '[' + index + ']'); + $filterNested($item.find('[name]'), repeaters) + .each(function () { + var $input = $(this); // match non empty brackets (ex: "[foo]") - var matches = $(this).attr('name').match(/\[[^\]]+\]/g); + var matches = $input.attr('name').match(/\[[^\]]+\]/g); var name = matches ? // strip "[" and "]" characters last(matches).replace(/\[|\]/g, '') : - $(this).attr('name'); + $input.attr('name'); - var newName = groupName + '[' + index + '][' + name + ']' + - ($(this).is(':checkbox') || $(this).attr('multiple') ? '[]' : ''); - $(this).attr('name', newName); + var newName = groupName + '[' + index + '][' + name + ']' + + ($input.is(':checkbox') || $input.attr('multiple') ? '[]' : ''); + + $input.attr('name', newName); + + $foreachRepeaterInItem(repeaters, $item, function (nestedFig) { + var $repeater = $(this); + setIndexes( + $filterNested($repeater.find('[data-repeater-item]'), nestedFig.repeaters || []), + groupName + '[' + index + ']' + + '[' + $repeater.find('[data-repeater-list]').first().data('repeater-list') + ']', + nestedFig.repeaters + ); + }); }); }); @@ -78,59 +175,87 @@ $.fn.repeater = function(fig) { .prop('checked', true); }; - setIndexes(); + setIndexes($items(), getGroupName(), fig.repeaters); + initNested($items()); if(fig.ready) { - fig.ready(setIndexes); + fig.ready(function () { + setIndexes($items(), getGroupName(), fig.repeaters); + }); } - var setItemsValues = function ($item, values) { - var index; - index = $item.find('[name]').first() - .attr('name').match(/\[([0-9]*)\]/)[1]; - $item.inputVal(map(values, identity, function (name) { - var nameIfNotCheckbox = groupName + '[' + index + '][' + name + ']'; - return $item.find('[name="' + nameIfNotCheckbox + '"]').length ? - nameIfNotCheckbox : nameIfNotCheckbox + '[]'; - })); - }; var appendItem = (function () { - var setupTemplate = function ($item) { - var defaultValues = fig.defaultValues; + var setItemsValues = function ($item, values, repeaters) { + if(values) { + var inputNames = {}; + $filterNested($item.find('[name]'), repeaters).each(function () { + var key = $(this).attr('name').match(/\[([^\]]*)(\]|\]\[\])$/)[1]; + inputNames[key] = $(this).attr('name'); + }); + + $item.inputVal(map(values, identity, function (name) { + return inputNames[name]; + })); + } - $item.find('[name]').each(function () { - $(this).inputClear(); + $foreachRepeaterInItem(repeaters, $item, function (nestedFig) { + var $repeater = $(this); + $filterNested( + $repeater.find('[data-repeater-item]'), + nestedFig.repeaters + ) + .each(function () { + setItemsValues( + $(this), + nestedFig.defaultValues, + nestedFig.repeaters || [] + ); + }); }); - - if(defaultValues) { - setItemsValues($item, defaultValues); - } }; return function ($item) { $list.append($item); - setIndexes(); - setupTemplate($item); + setIndexes($items(), getGroupName(), fig.repeaters); + $item.find('[name]').each(function () { + $(this).inputClear(); + }); + setItemsValues($item, fig.defaultValues, fig.repeaters); }; }()); - $self.find('[data-repeater-create]').click(function () { + var addItem = function () { var $item = $itemTemplate.clone(); appendItem($item); + if(fig.repeaters) { + initNested($item); + } show.call($item.get(0)); + }; + + $self.children().each(function () { + if( + !$(this).is('[data-repeater-list]') && + $(this).find('[data-repeater-list]').length === 0 + ) { + if($(this).is('[data-repeater-create]')) { + $(this).click(addItem); + } + else if($(this).find('[data-repeater-create]').length !== 0) { + $(this).find('[data-repeater-create]').click(addItem); + } + } }); $list.on('click', '[data-repeater-delete]', function () { var self = $(this).closest('[data-repeater-item]').get(0); hide.call(self, function () { $(self).remove(); - setIndexes(); + setIndexes($items(), getGroupName(), fig.repeaters); }); }); - - }); return this; diff --git a/test/index.html b/test/index.html index d5b69c1..c327843 100644 --- a/test/index.html +++ b/test/index.html @@ -10,8 +10,7 @@
@@ -79,6 +98,8 @@ + + - \ No newline at end of file + diff --git a/test/index.pre.html b/test/index.pre.html index 8cee554..75372c4 100644 --- a/test/index.pre.html +++ b/test/index.pre.html @@ -10,9 +10,8 @@
@@ -20,6 +19,8 @@ + + - \ No newline at end of file + diff --git a/test/nested.js b/test/nested.js new file mode 100644 index 0000000..0ffc65f --- /dev/null +++ b/test/nested.js @@ -0,0 +1,236 @@ +QUnit.module('nested-repeater', { + setup: function () { + this.$fixture = $('#qunit-fixture'); + this.$fixture.html($('#template').html()); + this.$outerRepeater = this.$fixture.find('.outer-repeater'); + this.$innerRepeater = this.$fixture.find('.inner-repeater'); + this.$outerAddButton = this.$fixture.find('.outer-repeater > [data-repeater-create]'); + this.$innerAddButton = this.$fixture.find('.inner-repeater > [data-repeater-create]'); + } +}); + + +QUnit.test('add item nested outer', function (assert) { + this.$outerRepeater.repeater({ repeaters: [{ selector: '.inner-repeater' }] }); + this.$outerAddButton.click(); + var $items = this.$outerRepeater.find('[data-repeater-list="outer-group"] > [data-repeater-item]'); + + assert.strictEqual($items.length, 2, 'adds a second item to list'); + + assert.strictEqual( + $items.first().find('[data-repeater-list="inner-group"] > [data-repeater-item]').length, + 1, 'does not duplicate first inner repeater' + ); + + assert.strictEqual( + $items.last().find('[data-repeater-list="inner-group"] > [data-repeater-item]').length, + 1, 'does not duplicate last inner repeater' + ); + + assert.deepEqual( + getNamedInputValues($items.first()), + { + "outer-group[0][text-input]": "A", + "outer-group[0][inner-group][0][inner-text-input]": "B" + }, + 'renamed first item' + ); + + assert.deepEqual( + getNamedInputValues($items.last()), + { + "outer-group[1][text-input]": "", + "outer-group[1][inner-group][0][inner-text-input]": "" + }, + 'renamed last item, values cleared' + ); +}); + +QUnit.test('delete added item outer from repeated outer', function (assert) { + this.$outerRepeater.repeater({ repeaters: [{ selector: '.inner-repeater' }] }); + this.$outerAddButton.click(); + + var $lastOuterItem = this.$outerRepeater + .find('[data-repeater-list="outer-group"] > [data-repeater-item]') + .last(); + + $lastOuterItem.find('[data-repeater-delete]').first().click(); + + assert.deepEqual( + getNamedInputValues(this.$outerRepeater), + { + "outer-group[0][text-input]": "A", + "outer-group[0][inner-group][0][inner-text-input]": "B" + } + ); +}); + +QUnit.test('delete added item outer from first outer', function (assert) { + this.$outerRepeater.repeater({ repeaters: [{ selector: '.inner-repeater' }] }); + this.$outerAddButton.click(); + + var $firstOuterItem = this.$outerRepeater + .find('[data-repeater-list="outer-group"] > [data-repeater-item]') + .first(); + + $firstOuterItem.find('[data-repeater-delete]').first().click(); + + assert.deepEqual( + getNamedInputValues(this.$outerRepeater), + { + "outer-group[0][text-input]": "", + "outer-group[0][inner-group][0][inner-text-input]": "" + } + ); +}); + +QUnit.test('add item nested inner', function (assert) { + this.$outerRepeater.repeater({ repeaters: [{ selector: '.inner-repeater' }] }); + this.$innerAddButton.click(); + + assert.strictEqual( + this.$innerRepeater.find('[data-repeater-item]').length, + 2, 'adds item to inner repeater' + ); + + var $items = this.$outerRepeater.find('[data-repeater-list="outer-group"] > [data-repeater-item]'); + + assert.strictEqual($items.length, 1, 'does not add item to outer list'); + + assert.deepEqual( + getNamedInputValues($items.first()), + { + "outer-group[0][text-input]": "A", + "outer-group[0][inner-group][0][inner-text-input]": "B", + "outer-group[0][inner-group][1][inner-text-input]": "", + }, + 'renamed items' + ); +}); + +QUnit.test('add item nested inner from repeated outer', function (assert) { + this.$outerRepeater.repeater({ repeaters: [{ selector: '.inner-repeater' }] }); + this.$outerAddButton.click(); + + var $lastItem = this.$outerRepeater.find('[data-repeater-list="outer-group"] > [data-repeater-item]').last(); + + $lastItem.find('[data-repeater-create]').click(); + + assert.strictEqual( + this.$innerRepeater.find('[data-repeater-item]').length, + 1, 'does not add item to first inner repeater' + ); + + assert.strictEqual( + $lastItem.find('[data-repeater-item]').length, + 2, 'adds item to second inner repeater' + ); + + var $items = this.$outerRepeater.find('[data-repeater-list="outer-group"] > [data-repeater-item]'); + + assert.strictEqual($items.length, 2, 'correct number of terms in outer list'); + + assert.deepEqual( + getNamedInputValues(this.$outerRepeater), + { + "outer-group[0][text-input]": "A", + "outer-group[0][inner-group][0][inner-text-input]": "B", + "outer-group[1][text-input]": "", + "outer-group[1][inner-group][0][inner-text-input]": "", + "outer-group[1][inner-group][1][inner-text-input]": "", + }, + 'renamed items' + ); +}); + +QUnit.test('delete added item nested inner from repeated outer', function (assert) { + this.$outerRepeater.repeater({ repeaters: [{ selector: '.inner-repeater' }] }); + this.$outerAddButton.click(); + + var $lastOuterItem = this.$outerRepeater + .find('[data-repeater-list="outer-group"] > [data-repeater-item]') + .last(); + + $lastOuterItem.find('[data-repeater-create]').click(); + $lastOuterItem.find('[data-repeater-list] [data-repeater-delete]').first().click(); + + assert.deepEqual( + getNamedInputValues(this.$outerRepeater), + { + "outer-group[0][text-input]": "A", + "outer-group[0][inner-group][0][inner-text-input]": "B", + "outer-group[1][text-input]": "", + "outer-group[1][inner-group][0][inner-text-input]": "" + } + ); +}); + +QUnit.test('nested default values first item', function (assert) { + this.$outerRepeater.repeater({ + repeaters: [{ + selector: '.inner-repeater', + defaultValues: { 'inner-text-input': 'foo' } + }] + }); + + this.$innerAddButton.click(); + + assert.deepEqual( + getNamedInputValues(this.$outerRepeater), + { + "outer-group[0][text-input]": "A", + "outer-group[0][inner-group][0][inner-text-input]": "B", + "outer-group[0][inner-group][1][inner-text-input]": "foo" + } + ); +}); + +QUnit.test('nested default values last item', function (assert) { + this.$outerRepeater.repeater({ + repeaters: [{ + selector: '.inner-repeater', + defaultValues: { 'inner-text-input': 'foo' } + }] + }); + + this.$outerAddButton.click(); + var $lastOuterItem = this.$outerRepeater + .find('[data-repeater-list="outer-group"] > [data-repeater-item]') + .last(); + + assert.deepEqual( + getNamedInputValues(this.$outerRepeater), + { + "outer-group[0][text-input]": "A", + "outer-group[0][inner-group][0][inner-text-input]": "B", + "outer-group[1][text-input]": "", + "outer-group[1][inner-group][0][inner-text-input]": "foo" + } + ); + + $lastOuterItem.find('[data-repeater-create]').click(); + + assert.deepEqual( + getNamedInputValues(this.$outerRepeater), + { + "outer-group[0][text-input]": "A", + "outer-group[0][inner-group][0][inner-text-input]": "B", + "outer-group[1][text-input]": "", + "outer-group[1][inner-group][0][inner-text-input]": "foo", + "outer-group[1][inner-group][1][inner-text-input]": "foo" + } + ); +}); + +QUnit.test('repeaterVal nested', function (assert) { + this.$outerRepeater.repeater({ + repeaters: [{ selector: '.inner-repeater' }] + }); + + assert.deepEqual(this.$outerRepeater.repeaterVal(), { + 'outer-group': [{ + 'text-input': 'A', + 'inner-group': [{ 'inner-text-input': 'B' }] + }] + }); +}); diff --git a/test/test-lib.js b/test/test-lib.js new file mode 100644 index 0000000..9886ef6 --- /dev/null +++ b/test/test-lib.js @@ -0,0 +1,16 @@ +var getNamedInputValues = function ($scope) { + return filter($scope.inputVal(), function (val, key) { + return key !== 'undefined'; + }); +}; + +var generateNameMappedInputValues = function (group, index, defaultValue, override) { + var defaultObject = {}; + defaultObject['group-' + group + '[' + index + '][text-input]'] = defaultValue; + defaultObject['group-' + group + '[' + index + '][textarea-input]'] = defaultValue; + defaultObject['group-' + group + '[' + index + '][select-input]'] = defaultValue || null; + defaultObject['group-' + group + '[' + index + '][radio-input]'] = defaultValue || null; + defaultObject['group-' + group + '[' + index + '][checkbox-input][]'] = defaultValue ? [defaultValue] : []; + defaultObject['group-' + group + '[' + index + '][multiple-select-input][]'] = defaultValue ? [defaultValue] : []; + return $.extend(defaultObject, override || {}); +}; diff --git a/test/test.js b/test/test.js index f819521..8cfa729 100644 --- a/test/test.js +++ b/test/test.js @@ -14,23 +14,6 @@ QUnit.module('repeater', { } }); -var getNamedInputValues = function ($scope) { - return filter($scope.inputVal(), function (val, key) { - return key !== 'undefined'; - }); -}; - -var generateNameMappedInputValues = function (group, index, defaultValue, override) { - var defaultObject = {}; - defaultObject['group-' + group + '[' + index + '][text-input]'] = defaultValue; - defaultObject['group-' + group + '[' + index + '][textarea-input]'] = defaultValue; - defaultObject['group-' + group + '[' + index + '][select-input]'] = defaultValue || null; - defaultObject['group-' + group + '[' + index + '][radio-input]'] = defaultValue || null; - defaultObject['group-' + group + '[' + index + '][checkbox-input][]'] = defaultValue ? [defaultValue] : []; - defaultObject['group-' + group + '[' + index + '][multiple-select-input][]'] = defaultValue ? [defaultValue] : []; - return $.extend(defaultObject, override || {}); -}; - QUnit.test('add item', function (assert) { this.$repeater.repeater(); this.$secondRepeater.repeater();