if(!RegExp.escape) { RegExp.escape = function(text) { return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); }; } /* input-many */ (function($) { var methods = { init: function (annotations) { // disable all elements inside our template, and mark them so we can find them later. this.find(".input-many-template :input:not([disabled])").each(function() { this.disabled = true; $(this).addClass("input_many_template_input"); }); // bind event handlers this.find(".remove-item:not([disabled])").not(this.find(".input-many .add-item")).click(methods.removeOne); this.find(".add-item:not([disabled])").not(this.find(".input-many .add-item")).click(methods.addOne); }, addOne: function () { var me = $(this).parent().parent(); var top = me.parent(); var template = top.children("li.input-many-template"); var clone = template.clone(true).removeClass("input-many-template"); var attrs = top.data('rapid')['input-many']; // length-2 because ignore the template li and the empty li var name_updater = methods.getNameUpdater.call(top, top.children().length-2, attrs['prefix']); // enable previously marked elements clone.find(".input_many_template_input").each(function() { this.disabled = false; $(this).removeClass("input_many_template_input"); }); clone.find(".remove-item:not([disabled])").not(clone.find(".input-many .add-item")).click(methods.removeOne); clone.find(".add-item:not([disabled])").not(clone.find(".input-many .add-item")).click(methods.addOne); // update id & name clone.find("*").each(function() { name_updater.call(this); }); name_updater.call(clone.get(0)); // do the add with anim clone.css("display", "none").insertAfter(me).hjq('show', attrs['show']); // initialize subelements me.next().hjq(); // visibility if(me.hasClass("empty")) { me.addClass("hidden"); me.find("input.empty-input").attr("disabled", true); } else { // now that we've added an element after us, we should only have a '-' button me.children("div.buttons").children("button.remove-item").removeClass("hidden"); me.children("div.buttons").children("button.add-item").addClass("hidden"); } me.hjq('createFunction', attrs.add_hook).call(me.get(0)); clone.trigger('rapid:add'); clone.trigger('rapid:change'); return false; // prevent bubbling }, removeOne: function() { var me = $(this).parent().parent(); var top = me.parent(); var attrs = top.data('rapid')['input-many']; if(attrs.remove_hook) { if(!me.hjq('createFunction', attrs.remove_hook).call(me.get(0))) { return false; }; } var remove_evt=$.Event("rapid:remove"); me.trigger(remove_evt, [me]); if(remove_evt.isPropagationStopped()) { return false; } // rename everybody from me onwards var i=methods.getIndex.call(me.get(0)) var n=me.next(); for(; n.length>0; i+=1, n=n.next()) { var name_updater = methods.getNameUpdater.call(top, i, attrs['prefix']); n.find("*").each(function() { name_updater.call(this); }); name_updater.call(n.get(0)); } // adjust +/- buttons on the button element as appropriate var last=top.children("li:last"); if(last.get(0)==me.get(0)) { last = last.prev(); } if(last.hasClass("empty")) { last.removeClass("hidden"); last.find("input.empty-input").removeAttr("disabled"); } else { // if we've reached the minimum, we don't want to add the '-' button if(top.children().length-3 <= (attrs['minimum']||0)) { last.children("div.buttons").children("button.remove-item").addClass("hidden"); } else { last.children("div.buttons").children("button.remove-item").removeClass("hidden"); } last.children("div.buttons").children("button.add-item").removeClass("hidden"); } // remove with animation me.hjq('hideAndRemove', attrs['hide']); top.trigger('rapid:change'); return false; //prevent bubbling }, // given this==the input-many, returns a lambda that updates the name & id for an element getNameUpdater: function(new_index, name_prefix) { var id_prefix = name_prefix.replace(/\[/g, "_").replace(/\]/g, ""); var name_re = RegExp("^" + RegExp.escape(name_prefix)+ "\[\-?[0-9]+\]"); var name_sub = name_prefix + '[' + new_index.toString() + ']'; var id_re = RegExp("^" + RegExp.escape(id_prefix)+ "_\-?[0-9]+"); var id_sub = id_prefix + '_' + new_index.toString(); var class_re = RegExp(RegExp.escape(name_prefix)+ "\[\-?[0-9]+\]"); var class_sub = name_sub; return function() { if(this.name) { this.name = this.name.replace(name_re, name_sub); } if (id_prefix==this.id.slice(0, id_prefix.length)) { this.id = this.id.replace(id_re, id_sub); } else { // silly rails. text_area_tag and text_field_tag use different conventions for the id. if(name_prefix==this.id.slice(0, name_prefix.length)) { this.id = this.id.replace(name_re, name_sub); } /* else { hjq.util.log("hjq.input_many.update_id: id_prefix "+id_prefix+" didn't match input "+this.id); } */ } if (class_re.test(this.className)) { this.className = this.className.replace(class_re, class_sub); } if(($(this).data('rapid') || {})['input-many']) { var annos=$(this).data('rapid'); annos['input-many']['prefix'] = annos['input-many']['prefix'].replace(name_re, name_sub); $(this).data('rapid', annos); } return this; }; }, // given this==an input-many item, get the submit index getIndex: function() { return Number(this.id.match(/_([-0-9]+)$/)[1]); } }; $.fn.hjq_input_many = function( method ) { if ( methods[method] ) { return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { return methods.init.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist on hjq_input_many' ); } }; })( jQuery );