/*! * jQuery List v0.2.0 * https://github.com/nathggns/jq-list * Copyright (c) 2013 Nathaniel Higgins; Licensed MIT * Built on 2013-04-06 */ (function (window, document, $, swig, undefined) { 'use strict'; var global_information = {}; var default_row = {}; var defaults = { rows: [], element: null, template: function () {}, template_html: '', children: [], events: {}, count: 1, templates: {}, original: { html: '', attrs: {}, data: {} } }; var compiled_templates = {}; /** * This function generates a random number, if this number has been generated before * it will generate another. * @param {int} length The 'length' is the number of digits in the number, defaults to 10 * @return {int} Random number */ var getID = (function (length) { var IDs = [undefined]; return function () { for ( var id; IDs.indexOf(id) > -1; id = Math.floor( Math.random() * Math.pow(10, length || 10) ) ) {} return id; }; })(); /** * Get on object to run methods from * @param {object} element Element to run the methods on * @return {object} Allows you to run methods */ var getMethods = function (element) { var roles = { /** * Handle clicking the add button, add a list item. * @param {event} e the event variable from the event. * @param {int} index The index 1-based index of the list item that was clicked */ 'add': function (e, index) { this.add(index - 1); this.render(); }, /** * Handle clicking the delelete button, deletes a list item * @param {event} e the event variable from the event. * @param {int} index The index 1-based index of the list item that was clicked */ 'delete': function (e, index) { this['delete'](index - 1); this.render(); } }; var methods = { /** * Initiate the plugin. * @param {string|object} selector The element to run the plugin on * @param {object} options Options for the plugin. Defaults to {} */ 'init': function (selector, options) { if (!options) { options = {}; } if (!element.data('ran')) { var original = { html: element.html(), attrs: $.extend(true, {}, this.attrs(element.get(0))), data: $.extend(true, {}, element.data()) }; if (options.events) { $.each(options.events, function (index, events) { if (typeof events === 'function') { options.events[index] = [events]; } }); } var info = $.extend(true, {}, defaults, { element: element, original: original }, options || {}); this.saveInformation(info); // These are the sublists if (info.children) { var children = []; $.each(info.children, function (name, child) { if (typeof child === 'string') { child = { selector: child }; } if (!child.name) { child.name = name; } if (!child.options) { child.options = {}; } child.element = $(child.selector); children.push(child); }); info.children = children; } $.each(info.templates, function (name, template) { compiled_templates[name] = run.compileTemplate(template); }); this.setTemplate($(selector)); for (var i = 0, l = info.count; i < info.count; i++) { this.add(i); } } this.render(); element.data('ran', true); this.events('init'); }, /** * Generate a function to replace template placeholders * @param {string|object} template The subtemplate * @return {function} */ 'compileTemplate': function (template) { template = this.giveAttrs(template); /** * Replace the $element with the subtemplate * @param {object} $element Subtemplate placeholder */ return function ($element) { template = run.runOnString(template, function ($ele) { var $content = $ele.find('[data-content]'); if ($content.length > 0) { var html = $element.html(); $content.after(html); $content.remove(); } }); $element.after(template); $element.remove(); }; }, /** * Run a callback on a string as if it were an element * @param {string|object} string The string to run the callback on * @param {function} callback the callback to run * @return {string]} The result of the callback */ 'runOnString': function (string, callback) { if (typeof string !== 'string') { string = string.html(); } var $ele = $('
').html(string); callback($ele); return $ele.html(); }, /** * Give the attributes to the elements we need to hook into * @param {object|string} $element The element to find children in * @return {object|string} The element after the attributes have been added */ 'giveAttrs': function ($element) { var return_text = false; var key = this.getKey(); if (typeof $element === 'string') { $element = $('
').html($element); return_text = true; } var $specials = $element.find('[value], input, button, textarea, select').each(function (i) { $(this).attr('data-identifier', '{{ item.identifier }}-' + i); }); $specials = $specials.add($element.find('[data-role]')).each(function () { $(this).attr('data-parent', key); }); $specials.add($element.find('[data-sublist]')).each(function () { $(this).attr('data-index', '{{ loop.index }}'); $(this).attr('data-parent-identifier', '{{ item.identifier }}'); }); if (return_text) { $element = $element.html(); } return $element; }, /** * Add another item to list * @param {int} index The index to add the item after */ 'add': function (index) { this.events('before_add'); var info = this.getInformation(); var row = $.extend(true, {}, default_row); row.identifier = this.getKey() + '-' + getID(); info.rows.splice(index + 1, 0, row); this.saveInformation(info); this.events('add', index); }, /** * Remove an item from the list * @param {int} index The index of the item to remove */ 'delete': function (index) { this.events('before_delete', index); var info = this.getInformation(); info.rows.splice(index, 1); this.saveInformation(info); this.events('delete', index); }, /** * Render the list * @param {object} vars List of variables to pass to the swig template */ 'render': function (vars) { this.events('before_render'); var info = this.getInformation(); var children = info.children; var rows = {}; var data = {}; var real_rows = info.rows; var identifiers = $.map(real_rows, function(row) { return row.identifier; }); $.each(children, function (index, child) { var $sublists = element.find('[data-sublist="' + child.name + '"]'); $sublists.each(function () { var $sublist = $(this); var new_list = false; if (!$sublist.data('ran')) { $sublist.list(child.element, child.options); } var row_id = identifiers.indexOf($sublist.data('parent-identifier')); if (row_id === -1) { return; } real_rows[row_id].child = { rows: $sublist.list('getInformation').rows, string: '[data-sublist="' + child.name + '"][data-parent-identifier="' + $sublist.data('parent-identifier') + '"]', info: child }; }); }); element.find('[data-identifier]').each(function () { data[$(this).data('identifier')] = this; }); element.html(info.template($.extend(true, {}, { list: real_rows, length: real_rows.length, showdelete: real_rows.length > 1 }, vars || {}, info.render_options || {}))); this.events('render_before_fix'); element.find('[data-role]').on('click', function (e) { var $this = $(this); var role = $this.data('role'); if ($this.data('has-assigned')) { return; } $this.data('has-assigned', true); if (roles[role]) { e.preventDefault(); roles[role].call(run, e, $this.data('index')); } }); $.each(children, function (index, child) { var $sublists = element.find('[data-sublist="' + child.name + '"]').filter(function() { return !$(this).data('ran'); }); $sublists.each(function() { var $sublist = $(this); $sublist.list(child.element, child.options); var info = $sublist.list('getInformation'); info.render_options = { owner: { index: $sublist.data('index') } }; $sublist.list('saveInformation', info).list('render'); }); }); $.each(real_rows, function (index, row) { if (row.child) { var $sublist = element.find(row.child.string); if ($sublist.data('ran')) { var info = $sublist.list('getInformation'); info.rows = row.child.rows; $sublist.list('saveInformation', info).list('render'); } } }); $.each(data, function (identifier, ele) { var $new = element.find('[data-identifier="' + identifier + '"]'); var $ele = $(ele); if ($new.length > 0) { $.each(run.attrs($new[0]), function (name, attr) { if (name !== 'type') { $ele.attr(name, attr); } }); $ele.insertAfter($new); $new.remove(); } }); info.rows = real_rows; this.saveInformation(info); this.events('render'); }, /** * Get the stored information about the current plugin instance. * @return {object} The info */ 'getInformation': function () { return global_information[this.getKey()]; }, /** * Get a key to identify the current instance * @return {int} The instance */ 'getKey': function () { if (!element.data('info-key')) { element.data('info-key', getID()); } return element.data('info-key'); }, /** * Save information about the current instance * @param {object} info Information to save */ 'saveInformation': function (info) { this.events('save_info', info); global_information[this.getKey()] = info; }, /** * Get key-value object of the attributes of a DOMElement * @param {DOMElement} ele The element to fetch attributes from * @return {object} key-value object of attributes */ 'attrs': function (ele) { ele = ele || element; var attrs = {}; $.each(ele.attributes, function () { attrs[this.nodeName] = this.nodeValue; }); return attrs; }, /** * Run event callbacks * @param {string} event_name The name of the events to run */ 'events': function () { if (arguments.length > 0) { var event_name = arguments[0]; var info = this.getInformation(); var args = Array.prototype.slice.call(arguments, 1); if (info && info.events[event_name]) { $.each(info.events[event_name], function () { this.apply(element, args); }); } if (info) { $.each(info.events[event_name] || [], function() { this.apply(element, args); }); element.trigger('list-' + event_name, args); } } }, /** * Assign an event handler * @param {string} event the event type * @param {function} handler the handler for the event */ 'on': function (event, handler) { var info = this.getInformation(); if (!info.events[event]) { info.events[event] = []; } info.events[event].push(handler); this.saveInformation(info); }, /** * Remove an event handler * @param {string} event the event type * @param {function} handler the handler for the event */ 'off': function (event, handler) { var info = this.getInformation(); if (info.events[event]) { var index = info.events[event].indexOf(handler); if (index > -1) { info.events[event].splice(index, 1); } } this.saveInformation(info); }, /** * Grab the method runner * @return {object} Method runner. */ 'methodRunner': function() { return run; }, /** * Completely reset this instance of the list plugin */ 'reset': function() { // Get our info and key, as we can't get it later var key = this.getKey(); var info = this.getInformation(); // Remove the entry in our registry delete global_information[key]; // Remove all data first $.each(element.data(), function(name) { element.removeData(name); }); // Re-assign original data $.each(info.original.data, function(name, val) { element.data(name, val); }); // Remove all attributes first $.each(this.attrs(element[0]), function(name) { if (name !== 'type') { element.removeAttr(name); } }); // Re-assign original attributes $.each(info.original.attrs, function(name, val) { if (name !== 'type') { element.attr(name, val); } }); // Reset the html element.html(info.original.html); }, /** * Get the template * * @param {bool} return_func Return the template function. * @return {string|function} template string or function, depending on return_func */ 'getTemplate': function(return_func) { var info = this.getInformation(); return typeof return_func === 'undefined' ? info.template_html : info.template; }, /** * Set a template, pass either an element or a string... * * @param {string|object} Template to set */ 'setTemplate': function(string) { if (typeof string !== 'string') { string = string.html(); } string = this.giveAttrs(string); var $copy = $('
').html(string); var $elements; var handler = function($elements) { $elements.each(function () { var $this = $(this); var name = $this.data('template'); var template = compiled_templates[name]; template($this); }); }; while (($elements = $copy.find('[data-template]')).length > 0) { handler($elements); } string = $copy.html(); var info = this.getInformation(); info.template_html = string; info.template = swig.compile(string); this.saveInformation(info); } }; var run = function () { var name = Array.prototype.shift.call(arguments); return methods[name].apply(run, arguments); }; $.each(methods, function (name) { run[name] = function () { Array.prototype.unshift.call(arguments, name); var args = arguments; return run.apply(run, args); }; }); return run; }; /** * Get the method from the arguments variable * * Usage: getMethod.apply(this, arguments); */ var getMethod = function() { if (arguments.length < 1) { Array.prototype.push.call(arguments, 'init'); } var method = Array.prototype.splice.call(arguments, 0, 1)[0]; return [method, arguments]; }; $.list = getMethods(false); $.fn.list = function () { var arr = getMethod.apply(this, arguments); var method = arr[0]; var args = arr[1]; if (!this.data('ran')) { var selector; if (method !== 'init') { Array.prototype.unshift.call(args, method); method = 'init'; } if (args.length < 1) { selector = '[type="text/swig"]'; } else { selector = args[0]; Array.prototype.splice.call(args, 0, 1); } Array.prototype.unshift.call(args, selector); } var result = getMethods(this)[method].apply(this, args); if (result) { return result; } else { return this; } }; })(window, document, jQuery, swig);