From 14d4d2f12d8c5ac68eeaa4fad8f5f5b591f75f62 Mon Sep 17 00:00:00 2001 From: Julius Date: Wed, 18 Sep 2013 23:15:21 +0200 Subject: [PATCH] Add list.js for filtering articles (another try for foodcoops#143) --- app/assets/javascripts/application.js | 2 + app/assets/javascripts/list.unlist.js | 150 +++++ app/assets/stylesheets/application.css | 1 + app/assets/stylesheets/list.unlist.css | 3 + app/views/group_orders/_form.html.haml | 10 +- config/locales/de.yml | 1 + config/locales/en.yml | 1 + vendor/assets/javascripts/list.js | 775 +++++++++++++++++++++++++ 8 files changed, 941 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/list.unlist.js create mode 100644 app/assets/stylesheets/list.unlist.css create mode 100644 vendor/assets/javascripts/list.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index c6d6e36a..09771539 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -8,6 +8,8 @@ //= require bootstrap-datepicker/locales/bootstrap-datepicker.de //= require bootstrap-datepicker/locales/bootstrap-datepicker.nl //= require jquery.observe_field +//= require list +//= require list.unlist //= require rails.validations //= require_self //= require ordering diff --git a/app/assets/javascripts/list.unlist.js b/app/assets/javascripts/list.unlist.js new file mode 100644 index 00000000..f649369f --- /dev/null +++ b/app/assets/javascripts/list.unlist.js @@ -0,0 +1,150 @@ +/******************************************************************************* +******************************************************************************** + +The following code is a modification of list.js. It was created by copy-pasting +the original code with the copyright notice below. + +******************************************************************************** +*******************************************************************************/ + + + +/******************************************************************************* +Begin copyright notice of the original code +*******************************************************************************/ + +/* +ListJS Beta 0.2.0 +By Jonny Strömberg (www.jonnystromberg.com, www.listjs.com) + +OBS. The API is not frozen. It MAY change! + +License (MIT) + +Copyright (c) 2011 Jonny Strömberg http://jonnystromberg.com + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +*/ + +/******************************************************************************* +End copyright notice of the original code +*******************************************************************************/ + + + +/******************************************************************************* +Begin copy-pasted and modified code +*******************************************************************************/ + +// * template engine which adds class 'unlisted' instead of removing from DOM +// * especially useful in case of formulars +// * uses jQuery's $ +List.prototype.templateEngines.unlist = function(list, settings) { + var listSource = ListJsHelpers.getByClass(settings.listClass, list.listContainer, true), + itemSource = getItemSource(settings.item), + templater = this; + + function getItemSource(item) { + if (item === undefined) { + var nodes = listSource.childNodes, + items = []; + + for (var i = 0, il = nodes.length; i < il; i++) { + // Only textnodes have a data attribute + if (nodes[i].data === undefined) { + return nodes[i]; + } + } + return null; + } else if (item.indexOf("<") !== -1) { // Try create html element of list, do not work for tables!! + var div = document.createElement('div'); + div.innerHTML = item; + return div.firstChild; + } else { + return document.getElementById(settings.item); + } + } + + var ensure = { + created: function(item) { + if (item.elm === undefined) { + templater.create(item); + } + } + }; + + /* Get values from element */ + this.get = function(item, valueNames) { + ensure.created(item); + var values = {}; + for(var i = 0, il = valueNames.length; i < il; i++) { + var elm = ListJsHelpers.getByClass(valueNames[i], item.elm, true); + values[valueNames[i]] = elm ? elm.innerHTML : ""; + } + return values; + }; + + /* Sets values at element */ + this.set = function(item, values) { + ensure.created(item); + for(var v in values) { + if (values.hasOwnProperty(v)) { + // TODO speed up if possible + var elm = ListJsHelpers.getByClass(v, item.elm, true); + if (elm) { + elm.innerHTML = values[v]; + } + } + } + }; + + this.create = function(item) { + if (item.elm !== undefined) { + return; + } + /* If item source does not exists, use the first item in list as + source for new items */ + var newItem = itemSource.cloneNode(true); + newItem.id = ""; + item.elm = newItem; + templater.set(item, item.values()); + }; + this.remove = function(item) { + listSource.removeChild(item.elm); + }; + this.show = function(item) { + ensure.created(item); + listSource.appendChild(item.elm); // append item (or move it to the end) + $(item.elm).removeClass('unlisted'); + }; + this.hide = function(item) { + if (item.elm !== undefined) { + $(item.elm).addClass('unlisted'); + } + }; + this.clear = function() { + $(listSource.childNodes).addClass('unlisted'); + }; +}; + +/******************************************************************************* +End copy-pasted and modified code +*******************************************************************************/ diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 24a5b6c4..674a635c 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -3,4 +3,5 @@ *= require select2 *= require token-input-bootstrappy *= require bootstrap-datepicker +*= require list.unlist */ \ No newline at end of file diff --git a/app/assets/stylesheets/list.unlist.css b/app/assets/stylesheets/list.unlist.css new file mode 100644 index 00000000..b0ed4600 --- /dev/null +++ b/app/assets/stylesheets/list.unlist.css @@ -0,0 +1,3 @@ +.list .unlisted { + display: none; +} \ No newline at end of file diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index 2bd2f30b..78bccce5 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -7,6 +7,8 @@ setMinimumBalance(#{FoodsoftConfig[:minimum_balance] or 0}); setToleranceBehaviour(#{FoodsoftConfig[:tolerance_is_costly]}); setStockit(#{@order.stockit?}); + // create List for search-feature (using list.js, http://listjs.com) + new List(document.body, { valueNames: ['name'], engine: 'unlist' }); }); - title t('.title'), false @@ -37,6 +39,10 @@ .well.pull-right = render 'switch_order', current_order: @order +.row-fluid + .well.clear + = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query input-large search' + = form_for @group_order do |f| = f.hidden_field :lock_version = f.hidden_field :order_id @@ -59,10 +65,10 @@ %th(style="width:20px")= t '.available' %th#col_required= t '.amount' %th{style: "width:15px;"}= t '.sum' - %tbody + %tbody.list - @order.articles_grouped_by_category.each do |category, order_articles| %tr.article-category - %td + %td.name = category %i.icon-tag %td{colspan: "9"} diff --git a/config/locales/de.yml b/config/locales/de.yml index 07a7f2f9..ab08d6dc 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -787,6 +787,7 @@ de: new_funds: Neuer Kontostand note: Notiz price: Preis + search_article: Artikel suchen... sum: Summe sum_amount: ! 'Gesamtbestellmenge bisher:' supplier: Lieferant diff --git a/config/locales/en.yml b/config/locales/en.yml index d21665ec..18356d73 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -791,6 +791,7 @@ en: new_funds: New account balance note: Note price: Price + search_article: Search for article... sum: Sum sum_amount: Current amount supplier: Supplier diff --git a/vendor/assets/javascripts/list.js b/vendor/assets/javascripts/list.js new file mode 100644 index 00000000..44ce6879 --- /dev/null +++ b/vendor/assets/javascripts/list.js @@ -0,0 +1,775 @@ +/* +ListJS Beta 0.2.0 +By Jonny Strömberg (www.jonnystromberg.com, www.listjs.com) + +OBS. The API is not frozen. It MAY change! + +License (MIT) + +Copyright (c) 2011 Jonny Strömberg http://jonnystromberg.com + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +*/ +(function( window, undefined ) { +"use strict"; +var document = window.document, + h; + +var List = function(id, options, values) { + var self = this, + templater, + init, + initialItems, + Item, + Templater, + sortButtons, + events = { + 'updated': [] + }; + this.listContainer = (typeof(id) == 'string') ? document.getElementById(id) : id; + // Check if the container exists. If not return instead of breaking the javascript + if (!this.listContainer) + return; + + this.items = []; + this.visibleItems = []; // These are the items currently visible + this.matchingItems = []; // These are the items currently matching filters and search, regadlessof visible count + this.searched = false; + this.filtered = false; + + this.list = null; + this.templateEngines = {}; + + this.page = options.page || 200; + this.i = options.i || 1; + + init = { + start: function(values, options) { + options.plugins = options.plugins || {}; + this.classes(options); + templater = new Templater(self, options); + this.callbacks(options); + this.items.start(values, options); + self.update(); + this.plugins(options.plugins); + }, + classes: function(options) { + options.listClass = options.listClass || 'list'; + options.searchClass = options.searchClass || 'search'; + options.sortClass = options.sortClass || 'sort'; + }, + callbacks: function(options) { + self.list = h.getByClass(options.listClass, self.listContainer, true); + h.addEvent(h.getByClass(options.searchClass, self.listContainer), 'keyup', self.search); + sortButtons = h.getByClass(options.sortClass, self.listContainer); + h.addEvent(sortButtons, 'click', self.sort); + }, + items: { + start: function(values, options) { + if (options.valueNames) { + var itemsToIndex = this.get(), + valueNames = options.valueNames; + if (options.indexAsync) { + this.indexAsync(itemsToIndex, valueNames); + } else { + this.index(itemsToIndex, valueNames); + } + } + if (values !== undefined) { + self.add(values); + } + }, + get: function() { + // return h.getByClass('item', self.list); + var nodes = self.list.childNodes, + items = []; + for (var i = 0, il = nodes.length; i < il; i++) { + // Only textnodes have a data attribute + if (nodes[i].data === undefined) { + items.push(nodes[i]); + } + } + return items; + }, + index: function(itemElements, valueNames) { + for (var i = 0, il = itemElements.length; i < il; i++) { + self.items.push(new Item(valueNames, itemElements[i])); + } + }, + indexAsync: function(itemElements, valueNames) { + var itemsToIndex = itemElements.splice(0, 100); // TODO: If < 100 items, what happens in IE etc? + this.index(itemsToIndex, valueNames); + if (itemElements.length > 0) { + setTimeout(function() { + init.items.indexAsync(itemElements, valueNames); + }, + 10); + } else { + self.update(); + // TODO: Add indexed callback + } + } + }, + plugins: function(plugins) { + var locals = { + templater: templater, + init: init, + initialItems: initialItems, + Item: Item, + Templater: Templater, + sortButtons: sortButtons, + events: events, + reset: reset + }; + for (var i = 0; i < plugins.length; i++) { + plugins[i][1] = plugins[i][1] || {}; + var pluginName = plugins[i][1].name || plugins[i][0]; + self[pluginName] = self.plugins[plugins[i][0]].call(self, locals, plugins[i][1]); + } + } + }; + + + /* + * Add object to list + */ + this.add = function(values, callback) { + if (callback) { + addAsync(values, callback); + } + var added = [], + notCreate = false; + if (values[0] === undefined){ + values = [values]; + } + for (var i = 0, il = values.length; i < il; i++) { + var item = null; + if (values[i] instanceof Item) { + item = values[i]; + item.reload(); + } else { + notCreate = (self.items.length > self.page) ? true : false; + item = new Item(values[i], undefined, notCreate); + } + self.items.push(item); + added.push(item); + } + self.update(); + return added; + }; + + /* + * Adds items asynchronous to the list, good for adding huge amount of + * data. Defaults to add 100 items a time + */ + var addAsync = function(values, callback, items) { + var valuesToAdd = values.splice(0, 100); + items = items || []; + items = items.concat(self.add(valuesToAdd)); + if (values.length > 0) { + setTimeout(function() { + addAsync(values, callback, items); + }, 10); + } else { + self.update(); + callback(items); + } + }; + + this.show = function(i, page) { + this.i = i; + this.page = page; + self.update(); + }; + + /* Removes object from list. + * Loops through the list and removes objects where + * property "valuename" === value + */ + this.remove = function(valueName, value, options) { + var found = 0; + for (var i = 0, il = self.items.length; i < il; i++) { + if (self.items[i].values()[valueName] == value) { + templater.remove(self.items[i], options); + self.items.splice(i,1); + il--; + found++; + } + } + self.update(); + return found; + }; + + /* Gets the objects in the list which + * property "valueName" === value + */ + this.get = function(valueName, value) { + var matchedItems = []; + for (var i = 0, il = self.items.length; i < il; i++) { + var item = self.items[i]; + if (item.values()[valueName] == value) { + matchedItems.push(item); + } + } + if (matchedItems.length == 0) { + return null; + } else if (matchedItems.length == 1) { + return matchedItems[0]; + } else { + return matchedItems; + } + }; + + /* Sorts the list. + * @valueOrEvent Either a JavaScript event object or a valueName + * @sortFunction (optional) Define if natural sorting does not fullfill your needs + */ + this.sort = function(valueName, options) { + var length = self.items.length, + value = null, + target = valueName.target || valueName.srcElement, /* IE have srcElement */ + sorting = '', + isAsc = false, + asc = 'asc', + desc = 'desc', + options = options || {}; + + if (target === undefined) { + value = valueName; + isAsc = options.asc || false; + } else { + value = h.getAttribute(target, 'data-sort'); + isAsc = h.hasClass(target, asc) ? false : true; + } + for (var i = 0, il = sortButtons.length; i < il; i++) { + h.removeClass(sortButtons[i], asc); + h.removeClass(sortButtons[i], desc); + } + if (isAsc) { + if (target !== undefined) { + h.addClass(target, asc); + } + isAsc = true; + } else { + if (target !== undefined) { + h.addClass(target, desc); + } + isAsc = false; + } + + if (options.sortFunction) { + options.sortFunction = options.sortFunction; + } else { + options.sortFunction = function(a, b) { + return h.sorter.alphanum(a.values()[value].toLowerCase(), b.values()[value].toLowerCase(), isAsc); + }; + } + self.items.sort(options.sortFunction); + self.update(); + }; + + /* + * Searches the list after values with content "searchStringOrEvent". + * The columns parameter defines if all values should be included in the search, + * defaults to undefined which means "all". + */ + this.search = function(searchString, columns) { + self.i = 1; // Reset paging + var matching = [], + found, + item, + text, + values, + is, + columns = (columns === undefined) ? self.items[0].values() : columns, + searchString = (searchString === undefined) ? "" : searchString, + target = searchString.target || searchString.srcElement; /* IE have srcElement */ + + searchString = (target === undefined) ? (""+searchString).toLowerCase() : ""+target.value.toLowerCase(); + is = self.items; + // Escape regular expression characters + searchString = searchString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + + templater.clear(); + if (searchString === "" ) { + reset.search(); + self.searched = false; + self.update(); + } else { + self.searched = true; + + for (var k = 0, kl = is.length; k < kl; k++) { + found = false; + item = is[k]; + values = item.values(); + + for(var j in columns) { + if(values.hasOwnProperty(j) && columns[j] !== null) { + text = (values[j] != null) ? values[j].toString().toLowerCase() : ""; + if ((searchString !== "") && (text.search(searchString) > -1)) { + found = true; + } + } + } + if (found) { + item.found = true; + matching.push(item); + } else { + item.found = false; + } + } + self.update(); + } + return self.visibleItems; + }; + + /* + * Filters the list. If filterFunction() returns False hides the Item. + * if filterFunction == false are the filter removed + */ + this.filter = function(filterFunction) { + self.i = 1; // Reset paging + reset.filter(); + if (filterFunction === undefined) { + self.filtered = false; + } else { + self.filtered = true; + var is = self.items; + for (var i = 0, il = is.length; i < il; i++) { + var item = is[i]; + if (filterFunction(item)) { + item.filtered = true; + } else { + item.filtered = false; + } + } + } + self.update(); + return self.visibleItems; + }; + + /* + * Get size of the list + */ + this.size = function() { + return self.items.length; + }; + + /* + * Removes all items from the list + */ + this.clear = function() { + templater.clear(); + self.items = []; + }; + + this.on = function(event, callback) { + events[event].push(callback); + }; + + var trigger = function(event) { + var i = events[event].length; + while(i--) { + events[event][i](); + } + }; + + var reset = { + filter: function() { + var is = self.items, + il = is.length; + while (il--) { + is[il].filtered = false; + } + }, + search: function() { + var is = self.items, + il = is.length; + while (il--) { + is[il].found = false; + } + } + }; + + + this.update = function() { + var is = self.items, + il = is.length; + + self.visibleItems = []; + self.matchingItems = []; + templater.clear(); + for (var i = 0; i < il; i++) { + if (is[i].matching() && ((self.matchingItems.length+1) >= self.i && self.visibleItems.length < self.page)) { + is[i].show(); + self.visibleItems.push(is[i]); + self.matchingItems.push(is[i]); + } else if (is[i].matching()) { + self.matchingItems.push(is[i]); + is[i].hide(); + } else { + is[i].hide(); + } + } + trigger('updated'); + }; + + Item = function(initValues, element, notCreate) { + var item = this, + values = {}; + + this.found = false; // Show if list.searched == true and this.found == true + this.filtered = false;// Show if list.filtered == true and this.filtered == true + + var init = function(initValues, element, notCreate) { + if (element === undefined) { + if (notCreate) { + item.values(initValues, notCreate); + } else { + item.values(initValues); + } + } else { + item.elm = element; + var values = templater.get(item, initValues); + item.values(values); + } + }; + this.values = function(newValues, notCreate) { + if (newValues !== undefined) { + for(var name in newValues) { + values[name] = newValues[name]; + } + if (notCreate !== true) { + templater.set(item, item.values()); + } + } else { + return values; + } + }; + this.show = function() { + templater.show(item); + }; + this.hide = function() { + templater.hide(item); + }; + this.matching = function() { + return ( + (self.filtered && self.searched && item.found && item.filtered) || + (self.filtered && !self.searched && item.filtered) || + (!self.filtered && self.searched && item.found) || + (!self.filtered && !self.searched) + ); + }; + this.visible = function() { + return (item.elm.parentNode) ? true : false; + }; + init(initValues, element, notCreate); + }; + + /* Templater with different kinds of template engines. + * All engines have these methods + * - reload(item) + * - remove(item) + */ + Templater = function(list, settings) { + if (settings.engine === undefined) { + settings.engine = "standard"; + } else { + settings.engine = settings.engine.toLowerCase(); + } + return new self.constructor.prototype.templateEngines[settings.engine](list, settings); + }; + + init.start(values, options); +}; + +List.prototype.templateEngines = {}; +List.prototype.plugins = {}; + +List.prototype.templateEngines.standard = function(list, settings) { + var listSource = h.getByClass(settings.listClass, list.listContainer, true), + itemSource = getItemSource(settings.item), + templater = this; + + function getItemSource(item) { + if (item === undefined) { + var nodes = listSource.childNodes, + items = []; + + for (var i = 0, il = nodes.length; i < il; i++) { + // Only textnodes have a data attribute + if (nodes[i].data === undefined) { + return nodes[i]; + } + } + return null; + } else if (item.indexOf("<") !== -1) { // Try create html element of list, do not work for tables!! + var div = document.createElement('div'); + div.innerHTML = item; + return div.firstChild; + } else { + return document.getElementById(settings.item); + } + } + + var ensure = { + created: function(item) { + if (item.elm === undefined) { + templater.create(item); + } + } + }; + + /* Get values from element */ + this.get = function(item, valueNames) { + ensure.created(item); + var values = {}; + for(var i = 0, il = valueNames.length; i < il; i++) { + var elm = h.getByClass(valueNames[i], item.elm, true); + values[valueNames[i]] = elm ? elm.innerHTML : ""; + } + return values; + }; + + /* Sets values at element */ + this.set = function(item, values) { + ensure.created(item); + for(var v in values) { + if (values.hasOwnProperty(v)) { + // TODO speed up if possible + var elm = h.getByClass(v, item.elm, true); + if (elm) { + elm.innerHTML = values[v]; + } + } + } + }; + + this.create = function(item) { + if (item.elm !== undefined) { + return; + } + /* If item source does not exists, use the first item in list as + source for new items */ + var newItem = itemSource.cloneNode(true); + newItem.id = ""; + item.elm = newItem; + templater.set(item, item.values()); + }; + this.remove = function(item) { + listSource.removeChild(item.elm); + }; + this.show = function(item) { + ensure.created(item); + listSource.appendChild(item.elm); + }; + this.hide = function(item) { + if (item.elm !== undefined && item.elm.parentNode === listSource) { + listSource.removeChild(item.elm); + } + }; + this.clear = function() { + /* .innerHTML = ''; fucks up IE */ + if (listSource.hasChildNodes()) { + while (listSource.childNodes.length >= 1) + { + listSource.removeChild(listSource.firstChild); + } + } + }; +}; + + +/* +* These helper functions are not written by List.js author Jonny (they may have been +* adjusted, thought). +*/ +h = { + /* + * Cross browser getElementsByClassName, which uses native + * if it exists. Modified version of Dustin Diaz function: + * http://www.dustindiaz.com/getelementsbyclass + */ + getByClass: (function() { + if (document.getElementsByClassName) { + return function(searchClass,node,single) { + if (single) { + return node.getElementsByClassName(searchClass)[0]; + } else { + return node.getElementsByClassName(searchClass); + } + }; + } else { + return function(searchClass,node,single) { + var classElements = [], + tag = '*'; + if (node == null) { + node = document; + } + var els = node.getElementsByTagName(tag); + var elsLen = els.length; + var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)"); + for (var i = 0, j = 0; i < elsLen; i++) { + if ( pattern.test(els[i].className) ) { + if (single) { + return els[i]; + } else { + classElements[j] = els[i]; + j++; + } + } + } + return classElements; + }; + } + })(), + /* (elm, 'event' callback) Source: http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/ */ + addEvent: (function( window, document ) { + if ( document.addEventListener ) { + return function( elem, type, cb ) { + if ((elem && !(elem instanceof Array) && !elem.length && !h.isNodeList(elem) && (elem.length !== 0)) || elem === window ) { + elem.addEventListener(type, cb, false ); + } else if ( elem && elem[0] !== undefined ) { + var len = elem.length; + for ( var i = 0; i < len; i++ ) { + h.addEvent(elem[i], type, cb); + } + } + }; + } + else if ( document.attachEvent ) { + return function ( elem, type, cb ) { + if ((elem && !(elem instanceof Array) && !elem.length && !h.isNodeList(elem) && (elem.length !== 0)) || elem === window ) { + elem.attachEvent( 'on' + type, function() { return cb.call(elem, window.event); } ); + } else if ( elem && elem[0] !== undefined ) { + var len = elem.length; + for ( var i = 0; i < len; i++ ) { + h.addEvent( elem[i], type, cb ); + } + } + }; + } + })(this, document), + /* (elm, attribute) Source: http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method */ + getAttribute: function(ele, attr) { + var result = (ele.getAttribute && ele.getAttribute(attr)) || null; + if( !result ) { + var attrs = ele.attributes; + var length = attrs.length; + for(var i = 0; i < length; i++) { + if (attr[i] !== undefined) { + if(attr[i].nodeName === attr) { + result = attr[i].nodeValue; + } + } + } + } + return result; + }, + /* http://stackoverflow.com/questions/7238177/detect-htmlcollection-nodelist-in-javascript */ + isNodeList: function(nodes) { + var result = Object.prototype.toString.call(nodes); + if (typeof nodes === 'object' && /^\[object (HTMLCollection|NodeList|Object)\]$/.test(result) && (nodes.length == 0 || (typeof nodes[0] === "object" && nodes[0].nodeType > 0))) { + return true; + } + return false; + }, + hasClass: function(ele, classN) { + var classes = this.getAttribute(ele, 'class') || this.getAttribute(ele, 'className') || ""; + return (classes.search(classN) > -1); + }, + addClass: function(ele, classN) { + if (!this.hasClass(ele, classN)) { + var classes = this.getAttribute(ele, 'class') || this.getAttribute(ele, 'className') || ""; + classes = classes + ' ' + classN + ' '; + classes = classes.replace(/\s{2,}/g, ' '); + ele.setAttribute('class', classes); + } + }, + removeClass: function(ele, classN) { + if (this.hasClass(ele, classN)) { + var classes = this.getAttribute(ele, 'class') || this.getAttribute(ele, 'className') || ""; + classes = classes.replace(classN, ''); + ele.setAttribute('class', classes); + } + }, + /* + * The sort function. From http://my.opera.com/GreyWyvern/blog/show.dml/1671288 + */ + sorter: { + alphanum: function(a,b,asc) { + if (a === undefined || a === null) { + a = ""; + } + if (b === undefined || b === null) { + b = ""; + } + a = a.toString().replace(/&(lt|gt);/g, function (strMatch, p1){ + return (p1 == "lt")? "<" : ">"; + }); + a = a.replace(/<\/?[^>]+(>|$)/g, ""); + + b = b.toString().replace(/&(lt|gt);/g, function (strMatch, p1){ + return (p1 == "lt")? "<" : ">"; + }); + b = b.replace(/<\/?[^>]+(>|$)/g, ""); + var aa = this.chunkify(a); + var bb = this.chunkify(b); + + for (var x = 0; aa[x] && bb[x]; x++) { + if (aa[x] !== bb[x]) { + var c = Number(aa[x]), d = Number(bb[x]); + if (asc) { + if (c == aa[x] && d == bb[x]) { + return c - d; + } else { + return (aa[x] > bb[x]) ? 1 : -1; + } + } else { + if (c == aa[x] && d == bb[x]) { + return d-c;//c - d; + } else { + return (aa[x] > bb[x]) ? -1 : 1; //(aa[x] > bb[x]) ? 1 : -1; + } + } + } + } + return aa.length - bb.length; + }, + chunkify: function(t) { + var tz = [], x = 0, y = -1, n = 0, i, j; + + while (i = (j = t.charAt(x++)).charCodeAt(0)) { + var m = (i == 45 || i == 46 || (i >=48 && i <= 57)); + if (m !== n) { + tz[++y] = ""; + n = m; + } + tz[y] += j; + } + return tz; + } + } +}; + +window.List = List; +window.ListJsHelpers = h; +})(window);