From 14d4d2f12d8c5ac68eeaa4fad8f5f5b591f75f62 Mon Sep 17 00:00:00 2001 From: Julius Date: Wed, 18 Sep 2013 23:15:21 +0200 Subject: [PATCH 01/17] 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); From 891293af8495ddd2a467aecdcc6958579c7f0f67 Mon Sep 17 00:00:00 2001 From: Julius Date: Sun, 22 Sep 2013 21:50:59 +0200 Subject: [PATCH 02/17] Add delay to list.js search --- app/assets/javascripts/application.js | 1 + app/assets/javascripts/list.customized.js | 43 +++++++++++++++++++++++ app/views/group_orders/_form.html.haml | 4 +-- 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/list.customized.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 09771539..c684cf31 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -10,6 +10,7 @@ //= require jquery.observe_field //= require list //= require list.unlist +//= require list.customized //= require rails.validations //= require_self //= require ordering diff --git a/app/assets/javascripts/list.customized.js b/app/assets/javascripts/list.customized.js new file mode 100644 index 00000000..f95a0e0c --- /dev/null +++ b/app/assets/javascripts/list.customized.js @@ -0,0 +1,43 @@ +(function(window, undefined) { + +var CustomizedList = function(id, options, values) { + var self = this; + var h = window.ListJsHelpers; + + this.searchTimeout = undefined; + + var init = { + start: function(id, options, values) { + this.defaults(options); + this.list(id, options, values); + this.callbacks(options); + }, + defaults: function(options) { + options.delayedSearchClass = options.delayedSearchClass || 'delayed-search'; + options.delayedSearchTime = options.delayedSearchTime || 500; + }, + list: function(id, options, values) { + self.list = new window.List(id, options, values); + }, + callbacks: function(options) { + h.addEvent(h.getByClass(options.delayedSearchClass, self.list.listContainer), 'keyup', self.searchDelayStart); + } + }; + + this.searchDelayStart = function(searchString, columns) { + // TODO: if keycode corresponds to 'ENTER' ? skip delay + // TODO: if search is about to be cleared (empty searchString) ? skip delay + clearTimeout(self.searchTimeout); + self.searchTimeout = window.setTimeout(function() {self.searchDelayEnd(searchString, columns)}, options.delayedSearchTime); + } + + this.searchDelayEnd = function(searchString, columns) { + self.list.search(searchString, columns); + } + + init.start(id, options, values); +} + +window.CustomizedList = CustomizedList; + +})(window); diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index 78bccce5..dfac473f 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -8,7 +8,7 @@ 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' }); + new CustomizedList(document.body, { valueNames: ['name'], engine: 'unlist' }); }); - title t('.title'), false @@ -41,7 +41,7 @@ .row-fluid .well.clear - = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query input-large search' + = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query input-large delayed-search' = form_for @group_order do |f| = f.hidden_field :lock_version From 6ec81ace18ba8d72350599cb81bd38850eaa49cf Mon Sep 17 00:00:00 2001 From: Julius Date: Sun, 22 Sep 2013 22:04:29 +0200 Subject: [PATCH 03/17] Always show article category headers (do not unlist them on list.js search) --- app/assets/javascripts/list.unlist.js | 6 +++--- app/assets/stylesheets/list.unlist.css | 2 +- app/views/group_orders/_form.html.haml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/list.unlist.js b/app/assets/javascripts/list.unlist.js index f649369f..dc7101ed 100644 --- a/app/assets/javascripts/list.unlist.js +++ b/app/assets/javascripts/list.unlist.js @@ -136,9 +136,9 @@ List.prototype.templateEngines.unlist = function(list, settings) { $(item.elm).removeClass('unlisted'); }; this.hide = function(item) { - if (item.elm !== undefined) { - $(item.elm).addClass('unlisted'); - } + ensure.created(item); + $(item.elm).addClass('unlisted'); + listSource.appendChild(item.elm); }; this.clear = function() { $(listSource.childNodes).addClass('unlisted'); diff --git a/app/assets/stylesheets/list.unlist.css b/app/assets/stylesheets/list.unlist.css index b0ed4600..9fad9603 100644 --- a/app/assets/stylesheets/list.unlist.css +++ b/app/assets/stylesheets/list.unlist.css @@ -1,3 +1,3 @@ -.list .unlisted { +.list .unlisted:not(.no-unlist) { 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 dfac473f..64e88453 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -67,8 +67,8 @@ %th{style: "width:15px;"}= t '.sum' %tbody.list - @order.articles_grouped_by_category.each do |category, order_articles| - %tr.article-category - %td.name + %tr.no-unlist.article-category + %td = category %i.icon-tag %td{colspan: "9"} From 3c8f56b24f8c8176c2bc0d0bd509177a69e4bde3 Mon Sep 17 00:00:00 2001 From: Julius Date: Sun, 22 Sep 2013 22:07:34 +0200 Subject: [PATCH 04/17] Add dismiss button to sidebars in group_order form (idea stolen from foodcoops#143) --- app/views/group_orders/_form.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index 64e88453..9d087687 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -15,6 +15,7 @@ .row-fluid .well.pull-left + %button{type: "button", class: "close", data: {dismiss: 'alert'}}= '×'.html_safe %h2= @order.name %dl.dl-horizontal - unless @order.note.blank? @@ -37,6 +38,7 @@ %dd= number_to_currency(@ordering_data[:available_funds]) .well.pull-right + %button{type: "button", class: "close", data: {dismiss: 'alert'}}= '×'.html_safe = render 'switch_order', current_order: @order .row-fluid From 9f083dd4d065ec12d38d7ade793a97514a0f92de Mon Sep 17 00:00:00 2001 From: Julius Date: Mon, 23 Sep 2013 21:31:54 +0200 Subject: [PATCH 05/17] Add button to clear article search (list.js customization) --- app/assets/javascripts/list.customized.js | 30 +++++++++++++++++-- .../bootstrap_and_overrides.css.less | 5 ++++ app/views/group_orders/_form.html.haml | 6 +++- config/locales/de.yml | 1 + config/locales/en.yml | 1 + 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/list.customized.js b/app/assets/javascripts/list.customized.js index f95a0e0c..96ca9793 100644 --- a/app/assets/javascripts/list.customized.js +++ b/app/assets/javascripts/list.customized.js @@ -11,30 +11,56 @@ var CustomizedList = function(id, options, values) { this.defaults(options); this.list(id, options, values); this.callbacks(options); + this.onload(options); }, defaults: function(options) { options.delayedSearchClass = options.delayedSearchClass || 'delayed-search'; options.delayedSearchTime = options.delayedSearchTime || 500; + options.highlightClass = options.highlightClass || 'btn-primary'; + options.resetSearchClass = options.resetSearchClass || 'reset-search'; }, list: function(id, options, values) { self.list = new window.List(id, options, values); }, callbacks: function(options) { - h.addEvent(h.getByClass(options.delayedSearchClass, self.list.listContainer), 'keyup', self.searchDelayStart); + var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); + $(resetSearchButton).click(self.resetSearch); + self.list.on('updated', self.highlightResetButton); + + var delayedSearchInput = h.getByClass(options.delayedSearchClass, self.list.listContainer); + $(delayedSearchInput).keyup(self.searchDelayStart); + }, + onload: function(options) { + var initialSearchTerm = $('.' + options.delayedSearchClass + ', .' + options.searchClass, self.list.listContainer).val(); + if('' != initialSearchTerm) { + self.list.search(initialSearchTerm); + } } }; this.searchDelayStart = function(searchString, columns) { // TODO: if keycode corresponds to 'ENTER' ? skip delay - // TODO: if search is about to be cleared (empty searchString) ? skip delay clearTimeout(self.searchTimeout); self.searchTimeout = window.setTimeout(function() {self.searchDelayEnd(searchString, columns)}, options.delayedSearchTime); + + var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); + $(resetSearchButton).removeClass(options.highlightClass); } this.searchDelayEnd = function(searchString, columns) { self.list.search(searchString, columns); } + this.highlightResetButton = function() { + var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); + $(resetSearchButton).toggleClass(options.highlightClass, self.list.searched); + } + + this.resetSearch = function() { + $('.' + options.delayedSearchClass + ', .' + options.searchClass, self.list.listContainer).val(''); + self.list.search(''); + } + init.start(id, options, values); } diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 5d595bc2..cce03029 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -238,3 +238,8 @@ tr.unavailable { margin-bottom: 15px } } + +// allow buttons as input add-on (with proper height) +.input-append button.add-on { + height: inherit; +} diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index 9d087687..bab7cad7 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -43,7 +43,11 @@ .row-fluid .well.clear - = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query input-large delayed-search' + .form-search + .input-append + = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query delayed-search' + %button.add-on.btn.reset-search{:type => :button, :title => t('.reset_article_search')} + %i.icon.icon-remove = form_for @group_order do |f| = f.hidden_field :lock_version diff --git a/config/locales/de.yml b/config/locales/de.yml index ab08d6dc..7e8f67bc 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -787,6 +787,7 @@ de: new_funds: Neuer Kontostand note: Notiz price: Preis + reset_article_search: Suche zurücksetzen search_article: Artikel suchen... sum: Summe sum_amount: ! 'Gesamtbestellmenge bisher:' diff --git a/config/locales/en.yml b/config/locales/en.yml index 18356d73..63d33cfd 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 + reset_article_search: Reset search search_article: Search for article... sum: Sum sum_amount: Current amount From a87ced2646d2fba206cd1b6b5d5ffdd3c6235fbf Mon Sep 17 00:00:00 2001 From: Julius Date: Tue, 24 Sep 2013 21:21:08 +0200 Subject: [PATCH 06/17] Hide article category if no article matches the search --- app/assets/javascripts/list.customized.js | 8 ++++---- app/assets/javascripts/list.unlist.js | 25 +++++++++++++++++++++++ app/views/group_orders/_form.html.haml | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/list.customized.js b/app/assets/javascripts/list.customized.js index 96ca9793..da79cbe1 100644 --- a/app/assets/javascripts/list.customized.js +++ b/app/assets/javascripts/list.customized.js @@ -45,21 +45,21 @@ var CustomizedList = function(id, options, values) { var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); $(resetSearchButton).removeClass(options.highlightClass); - } + }; this.searchDelayEnd = function(searchString, columns) { self.list.search(searchString, columns); - } + }; this.highlightResetButton = function() { var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); $(resetSearchButton).toggleClass(options.highlightClass, self.list.searched); - } + }; this.resetSearch = function() { $('.' + options.delayedSearchClass + ', .' + options.searchClass, self.list.listContainer).val(''); self.list.search(''); - } + }; init.start(id, options, values); } diff --git a/app/assets/javascripts/list.unlist.js b/app/assets/javascripts/list.unlist.js index dc7101ed..16600077 100644 --- a/app/assets/javascripts/list.unlist.js +++ b/app/assets/javascripts/list.unlist.js @@ -62,6 +62,19 @@ List.prototype.templateEngines.unlist = function(list, settings) { itemSource = getItemSource(settings.item), templater = this; + var init = { + start: function(options) { + this.defaults(options); + this.callbacks(options); + }, + defaults: function(options) { + options.listHeadingsClass = options.listHeadingsClass || 'list-heading'; + }, + callbacks: function(options) { + list.on('updated', templater.updateListHeadings); + } + }; + function getItemSource(item) { if (item === undefined) { var nodes = listSource.childNodes, @@ -143,6 +156,18 @@ List.prototype.templateEngines.unlist = function(list, settings) { this.clear = function() { $(listSource.childNodes).addClass('unlisted'); }; + + this.updateListHeadings = function() { + var headSel = '.' + settings.listHeadingsClass; + + $(headSel, listSource).each(function() { + if( $(this).nextUntil(headSel, ':not(.unlisted)').length ) { + $(this).removeClass('unlisted'); + } + }); + }; + + init.start(settings); }; /******************************************************************************* diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index bab7cad7..fbb4da00 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -73,7 +73,7 @@ %th{style: "width:15px;"}= t '.sum' %tbody.list - @order.articles_grouped_by_category.each do |category, order_articles| - %tr.no-unlist.article-category + %tr.list-heading.article-category %td = category %i.icon-tag From c1ac95e4400b7ba2ef4eac7c75d9f770a3d21310 Mon Sep 17 00:00:00 2001 From: Julius Date: Wed, 25 Sep 2013 20:58:53 +0200 Subject: [PATCH 07/17] list.unlist.js: Use jQuery's toggleClass instead of native if-else (just for briefness) --- app/assets/javascripts/list.unlist.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/list.unlist.js b/app/assets/javascripts/list.unlist.js index 16600077..1bbc12a7 100644 --- a/app/assets/javascripts/list.unlist.js +++ b/app/assets/javascripts/list.unlist.js @@ -161,9 +161,8 @@ List.prototype.templateEngines.unlist = function(list, settings) { var headSel = '.' + settings.listHeadingsClass; $(headSel, listSource).each(function() { - if( $(this).nextUntil(headSel, ':not(.unlisted)').length ) { - $(this).removeClass('unlisted'); - } + var listedCount = $(this).nextUntil(headSel, ':not(.unlisted)').length; + $(this).toggleClass('unlisted', 0==listedCount); }); }; From 53f24cd885169d4bb0adf2253257fdaabc3dd054 Mon Sep 17 00:00:00 2001 From: Julius Date: Wed, 25 Sep 2013 21:22:34 +0200 Subject: [PATCH 08/17] Fix migrate problem caused by output formating(?) --- db/migrate/20130702113610_update_group_order_totals.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20130702113610_update_group_order_totals.rb b/db/migrate/20130702113610_update_group_order_totals.rb index ca9aab67..f0052ee5 100644 --- a/db/migrate/20130702113610_update_group_order_totals.rb +++ b/db/migrate/20130702113610_update_group_order_totals.rb @@ -7,7 +7,7 @@ class UpdateGroupOrderTotals < ActiveRecord::Migration say "If you do want to update the ordergroup totals, open the rails console " + "(by running `rails c`), and enter:" - say "GroupOrder.all.each { |go| go.order.closed? and go.update_price! }", subitem: true + say "GroupOrder.all.each { |go| go.order.closed? and go.update_price! }" say "You may want to check first that no undesired accounting issues are introduced. " + "It may be wise to discuss this with those responsible for the ordering finances." From 26e207aeb106f35465025cbcfcbf5754793ce0a8 Mon Sep 17 00:00:00 2001 From: Julius Date: Thu, 3 Oct 2013 16:10:18 +0200 Subject: [PATCH 09/17] Reuse some list.js standard template functions (thanks to wvengen https://gist.github.com/wvengen/6784056) --- app/assets/javascripts/list.unlist.js | 122 ++++++++------------------ 1 file changed, 36 insertions(+), 86 deletions(-) diff --git a/app/assets/javascripts/list.unlist.js b/app/assets/javascripts/list.unlist.js index 1bbc12a7..b40cb98f 100644 --- a/app/assets/javascripts/list.unlist.js +++ b/app/assets/javascripts/list.unlist.js @@ -1,3 +1,6 @@ +// for use with listjs 0.2.0 +// https://github.com/javve/list.js + /******************************************************************************* ******************************************************************************** @@ -50,6 +53,7 @@ End copyright notice of the original code +(function(w, undefined) { /******************************************************************************* Begin copy-pasted and modified code *******************************************************************************/ @@ -57,10 +61,23 @@ 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; +w.List.prototype.templateEngines.unlist = function(list, settings) { + var h = w.ListJsHelpers; + + // start with standard engine, override specific methods afterwards + this.superClass = w.List.prototype.templateEngines.standard; + this.superClass(list, settings); + + // todo refer to listjs code instead of copy-pasting + var listSource = h.getByClass(settings.listClass, list.listContainer, true); + var templater = this; + var ensure = { + created: function(item) { + if(item.elm === undefined) { + templater.create(item); + } + } + }; var init = { start: function(options) { @@ -74,88 +91,20 @@ List.prototype.templateEngines.unlist = function(list, settings) { list.on('updated', templater.updateListHeadings); } }; - - 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) { - ensure.created(item); - $(item.elm).addClass('unlisted'); - listSource.appendChild(item.elm); - }; - this.clear = function() { - $(listSource.childNodes).addClass('unlisted'); - }; + + 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) { + ensure.created(item); + $(item.elm).addClass('unlisted'); + listSource.appendChild(item.elm); + }; + this.clear = function() { + $(listSource.childNodes).addClass('unlisted'); + }; this.updateListHeadings = function() { var headSel = '.' + settings.listHeadingsClass; @@ -172,3 +121,4 @@ List.prototype.templateEngines.unlist = function(list, settings) { /******************************************************************************* End copy-pasted and modified code *******************************************************************************/ +})(window); From 3f822dc396ef6a2157d791058591116451c7608c Mon Sep 17 00:00:00 2001 From: Julius Date: Thu, 3 Oct 2013 22:03:14 +0200 Subject: [PATCH 10/17] Revert "Fix migrate problem caused by output formating(?)" This reverts commit 53f24cd885169d4bb0adf2253257fdaabc3dd054. --- db/migrate/20130702113610_update_group_order_totals.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20130702113610_update_group_order_totals.rb b/db/migrate/20130702113610_update_group_order_totals.rb index f0052ee5..ca9aab67 100644 --- a/db/migrate/20130702113610_update_group_order_totals.rb +++ b/db/migrate/20130702113610_update_group_order_totals.rb @@ -7,7 +7,7 @@ class UpdateGroupOrderTotals < ActiveRecord::Migration say "If you do want to update the ordergroup totals, open the rails console " + "(by running `rails c`), and enter:" - say "GroupOrder.all.each { |go| go.order.closed? and go.update_price! }" + say "GroupOrder.all.each { |go| go.order.closed? and go.update_price! }", subitem: true say "You may want to check first that no undesired accounting issues are introduced. " + "It may be wise to discuss this with those responsible for the ordering finances." From a3e82f5304739f109d40883d442c087405180e90 Mon Sep 17 00:00:00 2001 From: Julius Date: Thu, 3 Oct 2013 22:14:41 +0200 Subject: [PATCH 11/17] Add article price tooltip to StockTaking form --- app/views/stock_takings/_stock_change.html.haml | 5 +++-- app/views/stock_takings/new.html.haml | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/views/stock_takings/_stock_change.html.haml b/app/views/stock_takings/_stock_change.html.haml index 616be369..3d27c1fa 100644 --- a/app/views/stock_takings/_stock_change.html.haml +++ b/app/views/stock_takings/_stock_change.html.haml @@ -3,5 +3,6 @@ = form.hidden_field :stock_article_id = "Menge (#{stock_change.stock_article.quantity_available})" = form.text_field :quantity, :size => 5, :autocomplete => 'off' - %b= stock_change.stock_article.name - = "(#{number_to_currency(stock_change.stock_article.price)} / #{stock_change.stock_article.unit})" + %span{:data => {:toggle => :tooltip, :title => render(:partial => 'shared/article_price_info', :locals => {:article => stock_change.stock_article})}} + %b= stock_change.stock_article.name + = "(#{number_to_currency(stock_change.stock_article.price)} / #{stock_change.stock_article.unit})" diff --git a/app/views/stock_takings/new.html.haml b/app/views/stock_takings/new.html.haml index 51239ba4..c46406f3 100644 --- a/app/views/stock_takings/new.html.haml +++ b/app/views/stock_takings/new.html.haml @@ -1,5 +1,19 @@ - title t('.title') +- content_for :javascript do + :javascript + $(function() { + enablePriceTooltips(); + }); + + function enablePriceTooltips(context) { + $('[data-toggle~="tooltip"]', context).tooltip({ + animation: false, + html: true, + placement: 'left' + }); + } + - content_for :sidebar do %p %i= t('.text_deviations', inv_link: link_to(t('.temp_inventory'), stock_articles_path)).html_safe From fef8c6d99f7f457c77363cd44b54d160677beb78 Mon Sep 17 00:00:00 2001 From: Julius Date: Thu, 3 Oct 2013 22:24:21 +0200 Subject: [PATCH 12/17] Move StockArticle#history to StockArticle#show --- app/controllers/stockit_controller.rb | 10 +++++----- app/views/stockit/index.html.haml | 3 +-- .../stockit/{history.haml => show.html.haml} | 0 config/locales/de.yml | 19 +++++++++---------- config/locales/en.yml | 19 +++++++++---------- config/locales/fr.yml | 19 +++++++++---------- config/locales/nl.yml | 19 +++++++++---------- config/routes.rb | 2 -- 8 files changed, 42 insertions(+), 49 deletions(-) rename app/views/stockit/{history.haml => show.html.haml} (100%) diff --git a/app/controllers/stockit_controller.rb b/app/controllers/stockit_controller.rb index 3e3b43b2..475ac3a3 100644 --- a/app/controllers/stockit_controller.rb +++ b/app/controllers/stockit_controller.rb @@ -31,6 +31,11 @@ class StockitController < ApplicationController end end + def show + @stock_article = StockArticle.find(params[:id]) + @stock_changes = @stock_article.stock_changes.order('stock_changes.created_at DESC') + end + def destroy @article = StockArticle.find(params[:id]) @article.mark_as_deleted @@ -55,9 +60,4 @@ class StockitController < ApplicationController render :partial => 'form', :locals => {:stock_article => stock_article} end - - def history - @stock_article = StockArticle.undeleted.find(params[:stock_article_id]) - @stock_changes = @stock_article.stock_changes.order('stock_changes.created_at DESC').each {|s| s.readonly!} - end end diff --git a/app/views/stockit/index.html.haml b/app/views/stockit/index.html.haml index 477e5816..c2899d9e 100644 --- a/app/views/stockit/index.html.haml +++ b/app/views/stockit/index.html.haml @@ -45,7 +45,7 @@ %tbody - for article in @stock_articles %tr{:class => stock_article_classes(article), :id => "stockArticle-#{article.id}"} - %td=h article.name + %td= link_to article.name, article %td= article.quantity %td= article.quantity - article.quantity_available %th= article.quantity_available @@ -56,7 +56,6 @@ %td= article.article_category.name %td = link_to t('ui.edit'), edit_stock_article_path(article), class: 'btn btn-mini' - = link_to t('ui.history'), stock_article_history_path(article), class: 'btn btn-mini' = link_to t('ui.delete'), article, :method => :delete, :confirm => t('.confirm_delete'), class: 'btn btn-mini btn-danger', :remote => true %p diff --git a/app/views/stockit/history.haml b/app/views/stockit/show.html.haml similarity index 100% rename from app/views/stockit/history.haml rename to app/views/stockit/show.html.haml diff --git a/config/locales/de.yml b/config/locales/de.yml index a0a0e3a7..8971e53d 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1731,15 +1731,6 @@ de: title: Lagerartikel bearbeiten form: price_hint: Um Chaos zu vermeiden können bis auf weiteres die Preise von angelegten Lagerartikeln nicht mehr verändert werden. - history: - change_quantity: Veränderung - datetime: Zeitpunkt - delivery: Lieferung - new_quantity: Neuer Bestand - order: Bestellung - reason: Ereignis - stock_changes: Verlauf anzeigen für »%{article_name}« - stock_taking: Inventur index: article: article: Artikel @@ -1765,6 +1756,15 @@ de: new: search_text: ! 'Suche nache Artikeln aus allen Katalogen:' title: Neuen Lagerartikel anlegen + show: + change_quantity: Veränderung + datetime: Zeitpunkt + delivery: Lieferung + new_quantity: Neuer Bestand + order: Bestellung + reason: Ereignis + stock_changes: Verlauf anzeigen für »%{article_name}« + stock_taking: Inventur stock_create: notice: Lagerartikel wurde gespeichert. stock_update: @@ -1889,7 +1889,6 @@ de: close: Schließen delete: Löschen edit: Bearbeiten - history: Verlauf anzeigen marks: close: ! '×' success: diff --git a/config/locales/en.yml b/config/locales/en.yml index b13f4707..50121ec5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1735,15 +1735,6 @@ en: title: Edit stock articles form: price_hint: To avoid choas, it is not possible to edit the prices of already added stock articles until further notice. - history: - change_quantity: Change - datetime: Time - delivery: Delivery - new_quantity: New quantity - order: Order - reason: Reason - stock_changes: Stock quantity changes of ‘%{article_name}’ - stock_taking: Inventory index: article: article: Article @@ -1769,6 +1760,15 @@ en: new: search_text: ! 'Search for articles in all catalogues:' title: Add new stock article + show: + change_quantity: Change + datetime: Time + delivery: Delivery + new_quantity: New quantity + order: Order + reason: Reason + stock_changes: Stock quantity changes of ‘%{article_name}’ + stock_taking: Inventory stock_create: notice: Stock article was created. stock_update: @@ -1893,7 +1893,6 @@ en: close: Close delete: Delete edit: Edit - history: Show history marks: close: ! '×' success: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 18e2cfea..30f446c2 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1737,15 +1737,6 @@ fr: title: Modifier l'article form: price_hint: Pour éviter que ça soit le bazar, les prix des articles en stock ne peuvent plus être modifiés. - history: - change_quantity: Modification - datetime: Temps - delivery: Réapprovisionnement - new_quantity: Nouveau stock - order: Commande - reason: Raison - stock_changes: Afficher l'historique pour "%{article_name}" - stock_taking: Inventaire index: article: article: Article @@ -1771,6 +1762,15 @@ fr: new: search_text: ! 'Rechercher des articles dans tous les catalogues:' title: Ajouter un article au stock + show: + change_quantity: Modification + datetime: Temps + delivery: Réapprovisionnement + new_quantity: Nouveau stock + order: Commande + reason: Raison + stock_changes: Afficher l'historique pour "%{article_name}" + stock_taking: Inventaire stock_create: notice: L'article a été sauvegardé. stock_update: @@ -1903,7 +1903,6 @@ fr: close: Fermer delete: Supprimer edit: Modifier - history: Afficher l'historique marks: close: ! '×' success: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 221d9373..50fdf702 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1663,15 +1663,6 @@ nl: title: form: price_hint: - history: - change_quantity: - datetime: - delivery: - new_quantity: - order: - reason: - stock_changes: - stock_taking: index: article: article: @@ -1697,6 +1688,15 @@ nl: new: search_text: title: + show: + change_quantity: + datetime: + delivery: + new_quantity: + order: + reason: + stock_changes: + stock_taking: stock_create: notice: Voorraadsartikel is opgeslagen. stock_update: @@ -1821,7 +1821,6 @@ nl: close: Sluiten delete: Verwijder edit: Bewerk - history: marks: close: ! '×' success: diff --git a/config/routes.rb b/config/routes.rb index 0f96d7fa..533d2d97 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -97,8 +97,6 @@ Foodsoft::Application.routes.draw do get :articles_search get :fill_new_stock_article_form end - - get :history end resources :suppliers do From 9a9a782bc40266d881129c37d8b01a6b971a2247 Mon Sep 17 00:00:00 2001 From: Julius Date: Thu, 3 Oct 2013 23:21:53 +0200 Subject: [PATCH 13/17] Complete StockArticle#show view --- app/views/stockit/show.html.haml | 62 +++++++++++++++++++++++--------- config/locales/de.yml | 2 +- config/locales/en.yml | 2 +- config/locales/fr.yml | 2 +- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/app/views/stockit/show.html.haml b/app/views/stockit/show.html.haml index f4fe2b07..5f416bf9 100644 --- a/app/views/stockit/show.html.haml +++ b/app/views/stockit/show.html.haml @@ -1,17 +1,47 @@ -- title t('.stock_changes', :article_name => @stock_article.name) +- title @stock_article.name -%table.table.table-hover#stock_changes - %thead - %tr - %th= t '.datetime' - %th= t '.reason' - %th= t '.change_quantity' - %th= t '.new_quantity' - %tbody - - reversed_history = @stock_article.quantity_history.reverse - - @stock_changes.each_with_index do |stock_change, index| - %tr - %td= l stock_change.created_at - %td= link_to_stock_change_reason(stock_change) - %td= stock_change.quantity - %td= reversed_history[index] +.row-fluid + .span6 + %dl.dl-horizontal + %dt= StockArticle.human_attribute_name 'supplier' + %dd= link_to @stock_article.supplier.name, @stock_article.supplier + %dt= StockArticle.human_attribute_name 'name' + %dd= @stock_article.name + %dt= StockArticle.human_attribute_name 'unit' + %dd= @stock_article.unit + %dt= StockArticle.human_attribute_name 'price' + %dd= number_to_currency @stock_article.price + %dt= StockArticle.human_attribute_name 'tax' + %dd= number_to_percentage @stock_article.tax + %dt= StockArticle.human_attribute_name 'deposit' + %dd= number_to_currency @stock_article.deposit + %dt= StockArticle.human_attribute_name 'fc_price' + %dd= number_to_currency @stock_article.fc_price + %dt= StockArticle.human_attribute_name 'article_category' + %dd= @stock_article.article_category.name + %dt= StockArticle.human_attribute_name 'note' + %dd= @stock_article.note + %dt= StockArticle.human_attribute_name 'quantity' + %dd= @stock_article.quantity + %dt= StockArticle.human_attribute_name 'quantity_available' + %dd= @stock_article.quantity_available + .form-actions + = link_to t('ui.edit'), edit_stock_article_path(@stock_article), class: 'btn' + + .span6 + %h2= t('.stock_changes') + %table.table.table-hover#stock_changes + %thead + %tr + %th= t '.datetime' + %th= t '.reason' + %th= t '.change_quantity' + %th= t '.new_quantity' + %tbody + - reversed_history = @stock_article.quantity_history.reverse + - @stock_changes.each_with_index do |stock_change, index| + %tr + %td= l stock_change.created_at + %td= link_to_stock_change_reason(stock_change) + %td= stock_change.quantity + %td= reversed_history[index] diff --git a/config/locales/de.yml b/config/locales/de.yml index 8971e53d..ba339ee8 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1763,7 +1763,7 @@ de: new_quantity: Neuer Bestand order: Bestellung reason: Ereignis - stock_changes: Verlauf anzeigen für »%{article_name}« + stock_changes: Verlauf des Lagerbestands stock_taking: Inventur stock_create: notice: Lagerartikel wurde gespeichert. diff --git a/config/locales/en.yml b/config/locales/en.yml index 50121ec5..6c3fbea8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1767,7 +1767,7 @@ en: new_quantity: New quantity order: Order reason: Reason - stock_changes: Stock quantity changes of ‘%{article_name}’ + stock_changes: Stock quantity changes stock_taking: Inventory stock_create: notice: Stock article was created. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 30f446c2..8d16c076 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1769,7 +1769,7 @@ fr: new_quantity: Nouveau stock order: Commande reason: Raison - stock_changes: Afficher l'historique pour "%{article_name}" + stock_changes: Afficher l'historique stock_taking: Inventaire stock_create: notice: L'article a été sauvegardé. From 3a948e9b7396738981dd37efbb5bbe6371636952 Mon Sep 17 00:00:00 2001 From: Julius Date: Fri, 4 Oct 2013 18:28:45 +0200 Subject: [PATCH 14/17] Move list.js extensions to plugins --- Gemfile | 2 +- Gemfile.lock | 3 + app/assets/javascripts/application.js | 3 +- app/assets/javascripts/list.customized.js | 69 -- app/assets/javascripts/list.delay.js | 50 ++ app/assets/javascripts/list.reset.js | 42 ++ app/views/group_orders/_form.html.haml | 6 +- vendor/assets/javascripts/list.js | 775 ---------------------- 8 files changed, 102 insertions(+), 848 deletions(-) delete mode 100644 app/assets/javascripts/list.customized.js create mode 100644 app/assets/javascripts/list.delay.js create mode 100644 app/assets/javascripts/list.reset.js delete mode 100644 vendor/assets/javascripts/list.js diff --git a/Gemfile b/Gemfile index e80314ed..f2dbd228 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,7 @@ end gem 'jquery-rails' gem 'select2-rails' gem 'bootstrap-datepicker-rails' - +gem 'rails-assets-listjs', '0.2.0.beta.4' # remember to maintain list.*.js plugins and template engines on update gem 'mysql2' gem 'prawn' diff --git a/Gemfile.lock b/Gemfile.lock index c816b8c4..61e2fc6c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -192,6 +192,8 @@ GEM activesupport (= 3.2.13) bundler (~> 1.0) railties (= 3.2.13) + rails-assets-listjs (0.2.0.beta.4) + railties (>= 3.1) rails-settings-cached (0.2.4) rails (>= 3.0.0) railties (3.2.13) @@ -332,6 +334,7 @@ DEPENDENCIES prawn quiet_assets rails (~> 3.2.9) + rails-assets-listjs (= 0.2.0.beta.4) rails-settings-cached (= 0.2.4) resque rspec-core diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index c684cf31..b8dc87e8 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -10,7 +10,8 @@ //= require jquery.observe_field //= require list //= require list.unlist -//= require list.customized +//= require list.delay +//= require list.reset //= require rails.validations //= require_self //= require ordering diff --git a/app/assets/javascripts/list.customized.js b/app/assets/javascripts/list.customized.js deleted file mode 100644 index da79cbe1..00000000 --- a/app/assets/javascripts/list.customized.js +++ /dev/null @@ -1,69 +0,0 @@ -(function(window, undefined) { - -var CustomizedList = function(id, options, values) { - var self = this; - var h = window.ListJsHelpers; - - this.searchTimeout = undefined; - - var init = { - start: function(id, options, values) { - this.defaults(options); - this.list(id, options, values); - this.callbacks(options); - this.onload(options); - }, - defaults: function(options) { - options.delayedSearchClass = options.delayedSearchClass || 'delayed-search'; - options.delayedSearchTime = options.delayedSearchTime || 500; - options.highlightClass = options.highlightClass || 'btn-primary'; - options.resetSearchClass = options.resetSearchClass || 'reset-search'; - }, - list: function(id, options, values) { - self.list = new window.List(id, options, values); - }, - callbacks: function(options) { - var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); - $(resetSearchButton).click(self.resetSearch); - self.list.on('updated', self.highlightResetButton); - - var delayedSearchInput = h.getByClass(options.delayedSearchClass, self.list.listContainer); - $(delayedSearchInput).keyup(self.searchDelayStart); - }, - onload: function(options) { - var initialSearchTerm = $('.' + options.delayedSearchClass + ', .' + options.searchClass, self.list.listContainer).val(); - if('' != initialSearchTerm) { - self.list.search(initialSearchTerm); - } - } - }; - - this.searchDelayStart = function(searchString, columns) { - // TODO: if keycode corresponds to 'ENTER' ? skip delay - clearTimeout(self.searchTimeout); - self.searchTimeout = window.setTimeout(function() {self.searchDelayEnd(searchString, columns)}, options.delayedSearchTime); - - var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); - $(resetSearchButton).removeClass(options.highlightClass); - }; - - this.searchDelayEnd = function(searchString, columns) { - self.list.search(searchString, columns); - }; - - this.highlightResetButton = function() { - var resetSearchButton = h.getByClass(options.resetSearchClass, self.list.listContainer); - $(resetSearchButton).toggleClass(options.highlightClass, self.list.searched); - }; - - this.resetSearch = function() { - $('.' + options.delayedSearchClass + ', .' + options.searchClass, self.list.listContainer).val(''); - self.list.search(''); - }; - - init.start(id, options, values); -} - -window.CustomizedList = CustomizedList; - -})(window); diff --git a/app/assets/javascripts/list.delay.js b/app/assets/javascripts/list.delay.js new file mode 100644 index 00000000..72ac425c --- /dev/null +++ b/app/assets/javascripts/list.delay.js @@ -0,0 +1,50 @@ +// for use with listjs 0.2.0 +// https://github.com/javve/list.js + +(function(window, undefined) { + +window.List.prototype.plugins.delay = function(locals, options) { + var list = this; + + this.searchTimeout = undefined; + + var init = { + start: function(options) { + this.defaults(options); + this.callbacks(options); + this.onload(options); + }, + defaults: function(options) { + options.delayedSearchClass = options.delayedSearchClass || 'delayed-search'; + options.delayedSearchTime = options.delayedSearchTime || 500; + }, + callbacks: function(options) { + $('.' + options.delayedSearchClass, list.listContainer).keyup(list.searchDelayStart); + }, + onload: function(options) { + var initialSearchTerm = $('.' + options.delayedSearchClass, list.listContainer).val(); + if('' != initialSearchTerm) { + list.search(initialSearchTerm); + } + } + }; + + this.searchDelayStart = function(searchString, columns) { + // TODO: if keycode corresponds to 'ENTER' ? skip delay + clearTimeout(list.searchTimeout); + list.searchTimeout = window.setTimeout( + function() {list.searchDelayEnd(searchString, columns)}, + options.delayedSearchTime + ); + + $(list.listContainer).trigger('updateComing'); + }; + + this.searchDelayEnd = function(searchString, columns) { + list.search(searchString, columns); + }; + + init.start(options); +} + +})(window); diff --git a/app/assets/javascripts/list.reset.js b/app/assets/javascripts/list.reset.js new file mode 100644 index 00000000..9482f32c --- /dev/null +++ b/app/assets/javascripts/list.reset.js @@ -0,0 +1,42 @@ +// for use with listjs 0.2.0 +// https://github.com/javve/list.js + +(function(window, undefined) { + +window.List.prototype.plugins.reset = function(locals, options) { + var list = this; + + var init = { + start: function(options) { + this.defaults(options); + this.callbacks(options); + }, + defaults: function(options) { + options.highlightClass = options.highlightClass || 'btn-primary'; + options.resetSearchClass = options.resetSearchClass || 'reset-search'; + options.resettableClass = options.resettableClass || 'resettable'; + }, + callbacks: function(options) { + $('.' + options.resetSearchClass, list.listContainer).click(list.resetSearch); + list.on('updated', list.highlightResetButton); + + $(list.listContainer).on('updateComing', function() { + list.highlightResetButton(false); + }); + } + }; + + this.highlightResetButton = function(highlightEnabled) { + highlightEnabled = (undefined === highlightEnabled) ? (list.searched) : (highlightEnabled); + $('.' + options.resetSearchClass, list.listContainer).toggleClass(options.highlightClass, highlightEnabled); + }; + + this.resetSearch = function() { + $('.' + options.resettableClass, list.listContainer).val(''); + list.search(''); + }; + + init.start(options); +} + +})(window); diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index fbb4da00..3c6048c0 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -8,7 +8,9 @@ setToleranceBehaviour(#{FoodsoftConfig[:tolerance_is_costly]}); setStockit(#{@order.stockit?}); // create List for search-feature (using list.js, http://listjs.com) - new CustomizedList(document.body, { valueNames: ['name'], engine: 'unlist' }); + var listjsResetPlugin = ['reset', {highlightClass: 'btn-primary'}]; + var listjsDelayPlugin = ['delay', {delayedSearchTime: 500}]; + new List(document.body, { valueNames: ['name'], engine: 'unlist', plugins: [listjsResetPlugin, listjsDelayPlugin] }); }); - title t('.title'), false @@ -45,7 +47,7 @@ .well.clear .form-search .input-append - = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query delayed-search' + = text_field_tag :article, params[:article], placeholder: t('.search_article'), class: 'search-query delayed-search resettable' %button.add-on.btn.reset-search{:type => :button, :title => t('.reset_article_search')} %i.icon.icon-remove diff --git a/vendor/assets/javascripts/list.js b/vendor/assets/javascripts/list.js deleted file mode 100644 index 44ce6879..00000000 --- a/vendor/assets/javascripts/list.js +++ /dev/null @@ -1,775 +0,0 @@ -/* -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); From 10d1615cd878f33ea4175bfab76e7cca83bd1785 Mon Sep 17 00:00:00 2001 From: Julius Date: Sat, 5 Oct 2013 22:28:04 +0200 Subject: [PATCH 15/17] Prepare tooltip for next bootstrap version (as in 1fed9834b35dbee8b329305b2a62ac1ddf8e0eb7) --- app/views/stock_takings/new.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/stock_takings/new.html.haml b/app/views/stock_takings/new.html.haml index c46406f3..6e4526f9 100644 --- a/app/views/stock_takings/new.html.haml +++ b/app/views/stock_takings/new.html.haml @@ -10,7 +10,8 @@ $('[data-toggle~="tooltip"]', context).tooltip({ animation: false, html: true, - placement: 'left' + placement: 'left', + container: 'body' }); } From 9041a350d5b15279974241b58515ec3c663ed00f Mon Sep 17 00:00:00 2001 From: Julius Date: Sat, 5 Oct 2013 22:37:17 +0200 Subject: [PATCH 16/17] Add some locales for article attributes --- config/locales/de.yml | 5 +++++ config/locales/en.yml | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index bed1f8b9..ea3d9bf8 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -42,7 +42,10 @@ de: fc_price: Endpreis fc_share: FC-Aufschlag gross_price: Bruttopreis + name: Name + note: Notiz price: Nettopreis + supplier: Lieferantin tax: MwSt unit: Einheit unit_quantity: Gebindegröße @@ -51,6 +54,8 @@ de: note: Notiz stock_article: price: Nettopreis + quantity: Lagerbestand + quantity_available: Verfügbarer Bestand user: first_name: Vorname password: Passwort diff --git a/config/locales/en.yml b/config/locales/en.yml index cebb1c8e..d54afc8a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -42,7 +42,10 @@ en: fc_price: FC price fc_share: FC share gross_price: gross price + name: name + note: note price: price + supplier: supplier tax: VAT unit: unit unit_quantity: unit quantity @@ -50,7 +53,9 @@ en: amount: amount note: note stock_article: - price: Price + price: price + quantity: quantity + quantity_available: available quantity user: first_name: First name password: Password From 1b49a08c5e4084ee4c77f3594bb2a533b32b9876 Mon Sep 17 00:00:00 2001 From: wvengen Date: Tue, 8 Oct 2013 11:42:23 +0200 Subject: [PATCH 17/17] localeapp roundtrip --- config/locales/de.yml | 2 +- config/locales/en.yml | 2 +- config/locales/fr.yml | 7 +++++++ config/locales/nl.yml | 7 +++++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index 8b2c4bb2..ab2c694b 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -802,7 +802,7 @@ de: note: Notiz price: Preis reset_article_search: Suche zurücksetzen - search_article: Artikel suchen... + 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 9b514e31..f7d04466 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -806,7 +806,7 @@ en: note: Note price: Price reset_article_search: Reset search - search_article: Search for article... + search_article: Search for article... sum: Sum sum_amount: Current amount supplier: Supplier diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 835bbdfe..f9b71327 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -42,7 +42,10 @@ fr: fc_price: prix final fc_share: supplément boufcoop gross_price: prix brut + name: + note: price: prix net + supplier: tax: TVA unit: unité unit_quantity: unités par lot @@ -51,6 +54,8 @@ fr: note: note stock_article: price: Prix net + quantity: + quantity_available: user: first_name: Prénom password: Mot de passe @@ -811,6 +816,8 @@ fr: new_funds: Nouveau solde note: Note price: Prix + reset_article_search: + search_article: sum: Prix total sum_amount: ! 'Quantité déjà commandée:' supplier: Fourni par diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 5a3d4f99..2bef7df0 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -42,7 +42,10 @@ nl: fc_price: prijs foodcoop fc_share: marge foodcoop gross_price: bruto prijs + name: + note: price: netto prijs + supplier: tax: BTW unit: eenheid unit_quantity: groothandelseenheid @@ -51,6 +54,8 @@ nl: note: notitie stock_article: price: prijs + quantity: + quantity_available: user: first_name: Voornaam password: Wachtwoord @@ -797,6 +802,8 @@ nl: new_funds: Nieuw tegoed note: Notitie price: Prijs + reset_article_search: + search_article: sum: Som sum_amount: ! 'Huidig totaalbedrag:' supplier: Leverancier