- search(searchString, columns)
+
search(searchString, columns, searchFunction)
Searches the list
itemsInList = [
-{ id: 1, name: "Jonny" }
-, { id: 2, name "Gustaf" }
-, { id: 3, name "Jonas" }
+{ id: 1, name: "Jonny Stromberg", born: 1986 }
+, { id: 2, name "Jonas Arnklint", born: 1985 }
+, { id: 3, name "Martina Elm", born: 1986 }
+, { id: 4, name "Gustaf Lindqvist", born: 1983 }
+, { id: 5, name "Jonny Strandberg", born: 1990 }
];
-listObj.search('Jonny'); // Only item with name Jonny is shown (also returns this item)
+listObj.search('Jonny'); // Only items with name Jonny are shown (also returns these items)
listObj.search(); // Show all items in list
listObj.search('Jonny', ['name']); // Only search in the 'name' column
+Space-separated words match in any order using logical AND. Surround a phrase in quotes for exact matches:
+
+listObj.search('Jon 198'); // Items that match Jon AND 198
+
+listObj.search('"Jonny S" 1990'); // Items that match "Jonny S" AND 1990
+
+Optionally your own search function can be used:
+
+listObj.search('Jonny', searchFunction); // Custom search for Jonny
+
+listObj.search('Jonny', ['name'], searchFunction); // Custom search in the 'name' column
+
+function searchFunction(searchString, columns) {
+ for (var k = 0, kl = listObj.items.length; k < kl; k++) {
+ listObj.items[k].found = false;
+ // Insert your custom search logic here, set found = true
+
+ }
+};
+
- on(event, callback)
+
on(event, callback)
Execute callback
when list have been updated (triggered by update()
, which is used by a lot of methods). Use updated
as the event.
Avaliable events
diff --git a/docs/docs/index.html b/docs/docs/index.html
old mode 100644
new mode 100755
index b2f8d1ec..45632742
--- a/docs/docs/index.html
+++ b/docs/docs/index.html
@@ -4,7 +4,7 @@
---
Really simple examples
-You can use List.js on either exising HTML or create new with super simple templating.
+You can use List.js on either existing HTML or create new with super simple templating.
Example 1: Using existing list
@@ -127,8 +127,7 @@ JavaScript (nothing special)
var hackerList = new List('hacker-list', options);
-
-
+Read more here
Example 6: Using data attributes and other custom attributes (introduced in v1.2.0)
diff --git a/docs/docs/search-sort.html b/docs/docs/search-sort.html
new file mode 100755
index 00000000..7dbac087
--- /dev/null
+++ b/docs/docs/search-sort.html
@@ -0,0 +1,91 @@
+---
+layout: default
+title: Automagical Searching + Sorting
+---
+
+
+Automagical Searching + Sorting
+
+It is easy to add search input and sort buttons with just a few classes and attributes in your HTML. ‘Automagical’ because List.js registers the event handlers, searches/sorts and updates the list for you:
+
+
+Searching
+
+
+ -
+
+ class String. *required
+ The default class search
is how List.js finds your writable search field. If you change it also set options.searchClass.
+
+ Alternatively, using fuzzy-search
here will switch to the Fuzzy Search function.
+
+
+ -
+
+ type String. *required
+ The default input type search
is similar to using text
, but web browsers may render it slightly differently: see https://developer.mozilla.org/.../input/search. Either type will work with List.js.
+
+
+
+
+
+<input type="search" class="search" placeholder="normal search"> or
+<input type="search" class="fuzzy-search" placeholder="fuzzy search!">
+
+
+Sorting
+
+
+ -
+
class String. *required
+ The default class sort
is how List.js finds clickable sort buttons. If you change it also set options.sortClass.
+
+
+ -
+
+ data-sort String. *required
+ This attribute on a clickable sort button should match the column name passed to List.js in options.valueNames.
+
+
+ -
+
+ data-order String
+ Set to asc
or desc
to enforce that sorting order for a column. The user won't be able to change the order, and any data-default-order
attribute is ignored.
+
+
+ -
+
+ data-default-order String, default: "asc"
+ Set to desc
to change the initial sorting order for a column. Subsequent clicks will toggle the sorting order between ascending/descending, as usual.
+
+
+ -
+
+ data-insensitive Boolean, default: true
+ Set to false
for case-sensitive sorting of that column.
+
+
+
+
+
+Sort by:
+<span class='sort' data-sort='name'>Name</span> or
+<span class='sort' data-sort='born' data-default-order='desc'>Born in Year</span> or
+<span class='sort' data-sort='city'>City</span>
+
+
+The CSS classes asc
and desc
are added when a sort button is clicked on, so List.js can show which column is currently sorted. For example, using this CSS sets a yellow background with ⬆ or ⬇ added after the button text:
+
+
+.sort.asc, .sort.desc {
+ background-color: yellow;
+ }
+.sort.asc::after {
+ content: "\002B06";
+ padding-left: 3px;
+ }
+.sort.desc::after {
+ content: "\002B07";
+ padding-left: 3px
+ }
+
diff --git a/package-lock.json b/package-lock.json
index fe1950dc..75cda77e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6529,9 +6529,9 @@
}
},
"string-natural-compare": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-2.0.3.tgz",
- "integrity": "sha512-4Kcl12rNjc+6EKhY8QyDVuQTAlMWwRiNbsxnVwBUKFr7dYPQuXVrtNU4sEkjF9LHY0AY6uVbB3ktbkIH4LC+BQ=="
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz",
+ "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw=="
},
"strip-bom": {
"version": "4.0.0",
diff --git a/package.json b/package.json
index ddf8dadb..29edf624 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"url": "https://github.com/javve/list.js/issues"
},
"dependencies": {
- "string-natural-compare": "^2.0.2"
+ "string-natural-compare": "^3.0.1"
},
"devDependencies": {
"@babel/core": "^7.12.7",
diff --git a/src/fuzzy-search.js b/src/fuzzy-search.js
old mode 100644
new mode 100755
index 0c44d189..91a07c59
--- a/src/fuzzy-search.js
+++ b/src/fuzzy-search.js
@@ -55,10 +55,14 @@ module.exports = function (list, options) {
},
}
- events.bind(getByClass(list.listContainer, options.searchClass), 'keyup', function (e) {
- var target = e.target || e.srcElement // IE have srcElement
- list.search(target.value, fuzzySearch.search)
- })
+ events.bind(
+ getByClass(list.listContainer, options.searchClass),
+ 'keyup',
+ list.utils.events.debounce(function (e) {
+ var target = e.target || e.srcElement // IE have srcElement
+ list.search(target.value, fuzzySearch.search)
+ }, list.searchDelay)
+ )
return function (str, columns) {
list.search(str, columns, fuzzySearch.search)
diff --git a/src/index.js b/src/index.js
old mode 100644
new mode 100755
index 39ffcbac..a06091fa
--- a/src/index.js
+++ b/src/index.js
@@ -6,12 +6,15 @@ var naturalSort = require('string-natural-compare'),
toString = require('./utils/to-string'),
classes = require('./utils/classes'),
getAttribute = require('./utils/get-attribute'),
- toArray = require('./utils/to-array')
+ toArray = require('./utils/to-array'),
+ templater = require('./templater'),
+ Item = require('./item'),
+ sort = require('./sort'),
+ { addSortListeners, clearSortOrder, setSortOrder } = require('./sort-buttons')
module.exports = function (id, options, values) {
var self = this,
init,
- Item = require('./item')(self),
addAsync = require('./add-async')(self),
initPagination = require('./pagination')(self)
@@ -28,6 +31,7 @@ module.exports = function (id, options, values) {
self.searched = false
self.filtered = false
self.searchColumns = undefined
+ self.searchDelay = 0
self.handlers = { updated: [] }
self.valueNames = []
self.utils = {
@@ -51,15 +55,20 @@ module.exports = function (id, options, values) {
self.list = getByClass(self.listContainer, self.listClass, true)
self.parse = require('./parse')(self)
- self.templater = require('./templater')(self)
+ self.templater = templater
+ self.template = self.templater.getTemplate({
+ parentEl: self.list,
+ valueNames: self.valueNames,
+ template: self.item,
+ })
self.search = require('./search')(self)
self.filter = require('./filter')(self)
- self.sort = require('./sort')(self)
self.fuzzySearch = require('./fuzzy-search')(self, options.fuzzySearch)
this.handlers()
this.items()
this.pagination()
+ this.sort()
self.update()
},
@@ -89,6 +98,43 @@ module.exports = function (id, options, values) {
}
}
},
+ sort: function () {
+ const sortButtons = self.utils.getByClass(self.listContainer, self.sortClass)
+ const { items, sortFunction, alphabet } = self
+ const before = function () {
+ self.trigger('sortStart')
+ }
+ const after = function () {
+ self.update()
+ self.trigger('sortComplete')
+ }
+ addSortListeners(sortButtons, {
+ items,
+ sortFunction,
+ alphabet,
+ before,
+ after,
+ })
+
+ self.handlers.sortStart = self.handlers.sortStart || []
+ self.handlers.sortComplete = self.handlers.sortComplete || []
+ self.on('searchStart', function () {
+ clearSortOrder(sortButtons)
+ })
+ self.on('filterStart', function () {
+ clearSortOrder(sortButtons)
+ })
+ self.sort = function (valueName, options = {}) {
+ before()
+ setSortOrder(sortButtons, valueName, options.order)
+ options.alphabet = options.alphabet || self.alphabet
+ options.sortFunction = options.sortFunction || self.sortFunction
+ options.valueName = valueName
+ sort(items, valueName, options)
+ after()
+ return items
+ }
+ },
}
/*
@@ -122,15 +168,12 @@ module.exports = function (id, options, values) {
addAsync(values.slice(0), callback)
return
}
- var added = [],
- notCreate = false
+ var added = []
if (values[0] === undefined) {
values = [values]
}
for (var i = 0, il = values.length; i < il; i++) {
- var item = null
- notCreate = self.items.length > self.page ? true : false
- item = new Item(values[i], undefined, notCreate)
+ var item = new Item(values[i], { template: self.template })
self.items.push(item)
added.push(item)
}
@@ -153,7 +196,7 @@ module.exports = function (id, options, values) {
var found = 0
for (var i = 0, il = self.items.length; i < il; i++) {
if (self.items[i].values()[valueName] == value) {
- self.templater.remove(self.items[i], options)
+ self.templater.remove(self.items[i].elm, self.list)
self.items.splice(i, 1)
il--
i--
@@ -189,7 +232,7 @@ module.exports = function (id, options, values) {
* Removes all items from the list
*/
this.clear = function () {
- self.templater.clear()
+ self.templater.clear(self.list)
self.items = []
return self
}
@@ -241,17 +284,20 @@ module.exports = function (id, options, values) {
self.visibleItems = []
self.matchingItems = []
- self.templater.clear()
+ self.templater.clear(self.list)
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()
+ if (is[i].matching(self) && self.matchingItems.length + 1 >= self.i && self.visibleItems.length < self.page) {
+ if (!is[i].elm) {
+ is[i].elm = templater.create(is[i].values(), self.template)
+ }
+ templater.show(is[i].elm, self.list)
self.visibleItems.push(is[i])
self.matchingItems.push(is[i])
- } else if (is[i].matching()) {
+ } else if (is[i].matching(self)) {
self.matchingItems.push(is[i])
- is[i].hide()
+ templater.remove(is[i].elm, self.list)
} else {
- is[i].hide()
+ templater.remove(is[i].elm, self.list)
}
}
self.trigger('updated')
diff --git a/src/item.js b/src/item.js
index 41410625..9d7628af 100644
--- a/src/item.js
+++ b/src/item.js
@@ -1,60 +1,41 @@
-module.exports = function (list) {
- return function (initValues, element, notCreate) {
- var item = this
+const templater = require('./templater')
- this._values = {}
+module.exports = function (initValues, { element, template } = {}) {
+ var item = this
- this.found = false // Show if list.searched == true and this.found == true
- this.filtered = false // Show if list.filtered == true and this.filtered == true
+ this._values = {}
- 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 = list.templater.get(item, initValues)
- item.values(values)
- }
- }
-
- this.values = function (newValues, notCreate) {
- if (newValues !== undefined) {
- for (var name in newValues) {
- item._values[name] = newValues[name]
- }
- if (notCreate !== true) {
- list.templater.set(item, item.values())
- }
- } else {
- return item._values
- }
- }
-
- this.show = function () {
- list.templater.show(item)
- }
+ this.found = false
+ this.filtered = false
- this.hide = function () {
- list.templater.hide(item)
- }
-
- this.matching = function () {
- return (
- (list.filtered && list.searched && item.found && item.filtered) ||
- (list.filtered && !list.searched && item.filtered) ||
- (!list.filtered && list.searched && item.found) ||
- (!list.filtered && !list.searched)
- )
- }
+ var init = function (values, { element, template } = {}) {
+ if (element) item.elm = element
+ if (!template) throw new Error('missing_item_template')
+ item.template = template
+ item.values(values)
+ }
- this.visible = function () {
- return item.elm && item.elm.parentNode == list.list ? true : false
+ this.values = function (newValues) {
+ if (newValues !== undefined) {
+ for (var name in newValues) {
+ item._values[name] = newValues[name]
+ }
+ if (item.elm) {
+ templater.set(item.elm, item.values(), item.template.valueNames)
+ }
+ } else {
+ return item._values
}
+ }
- init(initValues, element, notCreate)
+ this.matching = function ({ searched, filtered }) {
+ return (
+ (filtered && searched && item.found && item.filtered) ||
+ (filtered && !searched && item.filtered) ||
+ (!filtered && searched && item.found) ||
+ (!filtered && !searched)
+ )
}
+
+ init(initValues, { element, template })
}
diff --git a/src/parse.js b/src/parse.js
index 0abe5900..ab666589 100644
--- a/src/parse.js
+++ b/src/parse.js
@@ -1,6 +1,6 @@
-module.exports = function (list) {
- var Item = require('./item')(list)
+var Item = require('./item')
+module.exports = function (list) {
var getChildren = function (parent) {
var nodes = parent.childNodes,
items = []
@@ -15,7 +15,8 @@ module.exports = function (list) {
var parse = function (itemElements, valueNames) {
for (var i = 0, il = itemElements.length; i < il; i++) {
- list.items.push(new Item(valueNames, itemElements[i]))
+ var values = list.templater.get(itemElements[i], list.valueNames)
+ list.items.push(new Item(values, { element: itemElements[i], template: list.template }))
}
}
var parseAsync = function (itemElements, valueNames) {
diff --git a/src/search.js b/src/search.js
old mode 100644
new mode 100755
index afdfaef9..f64ae4cc
--- a/src/search.js
+++ b/src/search.js
@@ -4,7 +4,7 @@ module.exports = function (list) {
var prepare = {
resetList: function () {
list.i = 1
- list.templater.clear()
+ list.templater.clear(list.list)
customSearch = undefined
},
setOptions: function (args) {
@@ -41,28 +41,43 @@ module.exports = function (list) {
}
var search = {
list: function () {
- for (var k = 0, kl = list.items.length; k < kl; k++) {
- search.item(list.items[k])
+ // Extract quoted phrases "word1 word2" from original searchString
+ // searchString is converted to lowercase by List.js
+ var words = [],
+ phrase,
+ ss = searchString
+ while ((phrase = ss.match(/"([^"]+)"/)) !== null) {
+ words.push(phrase[1])
+ ss = ss.substring(0, phrase.index) + ss.substring(phrase.index + phrase[0].length)
}
- },
- item: function (item) {
- item.found = false
- for (var j = 0, jl = columns.length; j < jl; j++) {
- if (search.values(item.values(), columns[j])) {
- item.found = true
- return
- }
- }
- },
- values: function (values, column) {
- if (values.hasOwnProperty(column)) {
- text = list.utils.toString(values[column]).toLowerCase()
- if (searchString !== '' && text.search(searchString) > -1) {
- return true
+ // Get remaining space-separated words (if any)
+ ss = ss.trim()
+ if (ss.length) words = words.concat(ss.split(/\s+/))
+ for (var k = 0, kl = list.items.length; k < kl; k++) {
+ var item = list.items[k]
+ item.found = false
+ if (!words.length) continue
+ for (var i = 0, il = words.length; i < il; i++) {
+ var word_found = false
+ for (var j = 0, jl = columns.length; j < jl; j++) {
+ var values = item.values(),
+ column = columns[j]
+ if (values.hasOwnProperty(column) && values[column] !== undefined && values[column] !== null) {
+ var text = typeof values[column] !== 'string' ? values[column].toString() : values[column]
+ if (text.toLowerCase().indexOf(words[i]) !== -1) {
+ // word found, so no need to check it against any other columns
+ word_found = true
+ break
+ }
+ }
+ }
+ // this word not found? no need to check any other words, the item cannot match
+ if (!word_found) break
}
+ item.found = word_found
}
- return false
},
+ // Removed search.item() and search.values()
reset: function () {
list.reset.search()
list.searched = false
@@ -96,14 +111,18 @@ module.exports = function (list) {
list.handlers.searchStart = list.handlers.searchStart || []
list.handlers.searchComplete = list.handlers.searchComplete || []
- list.utils.events.bind(list.utils.getByClass(list.listContainer, list.searchClass), 'keyup', function (e) {
- var target = e.target || e.srcElement, // IE have srcElement
- alreadyCleared = target.value === '' && !list.searched
- if (!alreadyCleared) {
- // If oninput already have resetted the list, do nothing
- searchMethod(target.value)
- }
- })
+ list.utils.events.bind(
+ list.utils.getByClass(list.listContainer, list.searchClass),
+ 'keyup',
+ list.utils.events.debounce(function (e) {
+ var target = e.target || e.srcElement, // IE have srcElement
+ alreadyCleared = target.value === '' && !list.searched
+ if (!alreadyCleared) {
+ // If oninput already have resetted the list, do nothing
+ searchMethod(target.value)
+ }
+ }, list.searchDelay)
+ )
// Used to detect click on HTML5 clear button
list.utils.events.bind(list.utils.getByClass(list.listContainer, list.searchClass), 'input', function (e) {
diff --git a/src/sort-buttons.js b/src/sort-buttons.js
new file mode 100644
index 00000000..a41a13a5
--- /dev/null
+++ b/src/sort-buttons.js
@@ -0,0 +1,75 @@
+var getAttribute = require('./utils/get-attribute')
+var classes = require('./utils/classes')
+var events = require('./utils/events')
+var sorter = require('./sort')
+
+var getInSensitive = function (btn) {
+ var insensitive = getAttribute(btn, 'data-insensitive')
+ if (insensitive === 'false') {
+ return false
+ } else {
+ return true
+ }
+}
+var getNextSortOrder = function (btn) {
+ var predefinedOrder = getAttribute(btn, 'data-order')
+ if (predefinedOrder == 'asc' || predefinedOrder == 'desc') {
+ return predefinedOrder
+ } else if (classes(btn).has('desc')) {
+ return 'asc'
+ } else if (classes(btn).has('asc')) {
+ return 'desc'
+ } else {
+ return 'asc'
+ }
+}
+var clearSortOrder = function (els) {
+ for (var i = 0, il = els.length; i < il; i++) {
+ classes(els[i]).remove('asc')
+ classes(els[i]).remove('desc')
+ }
+}
+var setSortOrder = function (els, valueName, order) {
+ for (var i = 0, il = els.length; i < il; i++) {
+ var btn = els[i]
+ if (getAttribute(btn, 'data-sort') !== valueName) {
+ classes(btn).remove('asc')
+ classes(btn).remove('desc')
+ continue
+ }
+ var predefinedOrder = getAttribute(btn, 'data-order')
+ if (predefinedOrder == 'asc' || predefinedOrder == 'desc') {
+ if (predefinedOrder == order) {
+ classes(btn).add(order)
+ classes(btn).remove(order === 'asc' ? 'desc' : 'asc')
+ } else {
+ classes(btn).remove('asc')
+ classes(btn).remove('desc')
+ }
+ } else {
+ classes(btn).add(order)
+ classes(btn).remove(order === 'asc' ? 'desc' : 'asc')
+ }
+ }
+}
+
+var addSortListeners = function (elements, { items, sortFunction, alphabet, before, after } = {}) {
+ events.bind(elements, 'click', function () {
+ if (before) before()
+ var target = arguments[0].currentTarget || arguments[0].srcElement || undefined
+ var valueName = getAttribute(target, 'data-sort')
+ var order = getNextSortOrder(target)
+ var insensitive = getInSensitive(target)
+ var options = {
+ sortFunction,
+ insensitive,
+ alphabet,
+ order,
+ }
+ setSortOrder(elements, valueName, order)
+ sorter(items, valueName, options)
+ if (after) after()
+ })
+}
+
+module.exports = { addSortListeners, getInSensitive, getNextSortOrder, setSortOrder, clearSortOrder }
diff --git a/src/sort.js b/src/sort.js
index 4e4b415f..b792aa12 100644
--- a/src/sort.js
+++ b/src/sort.js
@@ -1,104 +1,29 @@
-module.exports = function (list) {
- var buttons = {
- els: undefined,
- clear: function () {
- for (var i = 0, il = buttons.els.length; i < il; i++) {
- list.utils.classes(buttons.els[i]).remove('asc')
- list.utils.classes(buttons.els[i]).remove('desc')
- }
- },
- getOrder: function (btn) {
- var predefinedOrder = list.utils.getAttribute(btn, 'data-order')
- if (predefinedOrder == 'asc' || predefinedOrder == 'desc') {
- return predefinedOrder
- } else if (list.utils.classes(btn).has('desc')) {
- return 'asc'
- } else if (list.utils.classes(btn).has('asc')) {
- return 'desc'
- } else {
- return 'asc'
- }
- },
- getInSensitive: function (btn, options) {
- var insensitive = list.utils.getAttribute(btn, 'data-insensitive')
- if (insensitive === 'false') {
- options.insensitive = false
- } else {
- options.insensitive = true
- }
- },
- setOrder: function (options) {
- for (var i = 0, il = buttons.els.length; i < il; i++) {
- var btn = buttons.els[i]
- if (list.utils.getAttribute(btn, 'data-sort') !== options.valueName) {
- continue
- }
- var predefinedOrder = list.utils.getAttribute(btn, 'data-order')
- if (predefinedOrder == 'asc' || predefinedOrder == 'desc') {
- if (predefinedOrder == options.order) {
- list.utils.classes(btn).add(options.order)
- }
- } else {
- list.utils.classes(btn).add(options.order)
- }
- }
- },
- }
-
- var sort = function () {
- list.trigger('sortStart')
- var options = {}
-
- var target = arguments[0].currentTarget || arguments[0].srcElement || undefined
-
- if (target) {
- options.valueName = list.utils.getAttribute(target, 'data-sort')
- buttons.getInSensitive(target, options)
- options.order = buttons.getOrder(target)
- } else {
- options = arguments[1] || options
- options.valueName = arguments[0]
- options.order = options.order || 'asc'
- options.insensitive = typeof options.insensitive == 'undefined' ? true : options.insensitive
- }
-
- buttons.clear()
- buttons.setOrder(options)
-
- // caseInsensitive
- // alphabet
- var customSortFunction = options.sortFunction || list.sortFunction || null,
- multi = options.order === 'desc' ? -1 : 1,
- sortFunction
-
- if (customSortFunction) {
- sortFunction = function (itemA, itemB) {
- return customSortFunction(itemA, itemB, options) * multi
- }
- } else {
- sortFunction = function (itemA, itemB) {
- var sort = list.utils.naturalSort
- sort.alphabet = list.alphabet || options.alphabet || undefined
- if (!sort.alphabet && options.insensitive) {
- sort = list.utils.naturalSort.caseInsensitive
+var naturalSort = require('string-natural-compare')
+
+module.exports = (items, column, options = {}) => {
+ const { sortFunction, order, alphabet, insensitive } = options
+ const caseSensitive = insensitive !== false
+ const multi = order === 'desc' ? -1 : 1
+
+ if (sortFunction) {
+ return items.sort(function (itemA, itemB) {
+ return (
+ sortFunction(itemA, itemB, {
+ valueName: column,
+ alphabet,
+ caseSensitive,
+ }) * multi
+ )
+ })
+ } else {
+ return items.sort(function (itemA, itemB) {
+ const options = { alphabet }
+ if (!alphabet) {
+ if (caseSensitive) {
+ options.caseInsensitive = true
}
- return sort(itemA.values()[options.valueName], itemB.values()[options.valueName]) * multi
}
- }
-
- list.items.sort(sortFunction)
- list.update()
- list.trigger('sortComplete')
+ return naturalSort('' + itemA.values()[column], '' + itemB.values()[column], options) * multi
+ })
}
-
- // Add handlers
- list.handlers.sortStart = list.handlers.sortStart || []
- list.handlers.sortComplete = list.handlers.sortComplete || []
-
- buttons.els = list.utils.getByClass(list.listContainer, list.sortClass)
- list.utils.events.bind(buttons.els, 'click', sort)
- list.on('searchStart', buttons.clear)
- list.on('filterStart', buttons.clear)
-
- return sort
}
diff --git a/src/templater.js b/src/templater.js
index 5d09973d..a0e40bed 100644
--- a/src/templater.js
+++ b/src/templater.js
@@ -1,195 +1,137 @@
-var Templater = function (list) {
- var createItem,
- templater = this
-
- var init = function () {
- var itemSource
-
- if (typeof list.item === 'function') {
- createItem = function (values) {
- var item = list.item(values)
- return getItemSource(item)
+var getByClass = require('./utils/get-by-class')
+var getAttribute = require('./utils/get-attribute')
+var valueNamesUtils = require('./utils/value-names')
+
+var createCleanTemplateItem = function (templateNode, valueNames) {
+ var el = templateNode.cloneNode(true)
+ el.removeAttribute('id')
+ for (var i = 0, il = valueNames.length; i < il; i++) {
+ var elm = undefined,
+ valueName = valueNames[i]
+ if (valueName.data) {
+ for (var j = 0, jl = valueName.data.length; j < jl; j++) {
+ el.setAttribute('data-' + valueName.data[j], '')
}
- return
- }
-
- if (typeof list.item === 'string') {
- if (list.item.indexOf('<') === -1) {
- itemSource = document.getElementById(list.item)
- } else {
- itemSource = getItemSource(list.item)
+ } else if (valueName.attr && valueName.name) {
+ elm = getByClass(el, valueName.name, true)
+ if (elm) {
+ elm.setAttribute(valueName.attr, '')
}
} else {
- /* If item source does not exists, use the first item in list as
- source for new items */
- itemSource = getFirstListItem()
- }
-
- if (!itemSource) {
- throw new Error("The list needs to have at least one item on init otherwise you'll have to add a template.")
- }
-
- itemSource = createCleanTemplateItem(itemSource, list.valueNames)
-
- createItem = function () {
- return itemSource.cloneNode(true)
+ elm = getByClass(el, valueName, true)
+ if (elm) {
+ elm.innerHTML = ''
+ }
}
}
+ return el
+}
- var createCleanTemplateItem = function (templateNode, valueNames) {
- var el = templateNode.cloneNode(true)
- el.removeAttribute('id')
+var stringToDOMElement = function (itemHTML) {
+ if (typeof itemHTML !== 'string') return undefined
+ if (/]/g.exec(itemHTML)) {
+ var tbody = document.createElement('tbody')
+ tbody.innerHTML = itemHTML
+ return tbody.firstElementChild
+ } else if (itemHTML.indexOf('<') !== -1) {
+ var div = document.createElement('div')
+ div.innerHTML = itemHTML
+ return div.firstElementChild
+ }
+ return undefined
+}
- for (var i = 0, il = valueNames.length; i < il; i++) {
- var elm = undefined,
- valueName = valueNames[i]
- if (valueName.data) {
- for (var j = 0, jl = valueName.data.length; j < jl; j++) {
- el.setAttribute('data-' + valueName.data[j], '')
- }
- } else if (valueName.attr && valueName.name) {
- elm = list.utils.getByClass(el, valueName.name, true)
- if (elm) {
- elm.setAttribute(valueName.attr, '')
- }
- } else {
- elm = list.utils.getByClass(el, valueName, true)
- if (elm) {
- elm.innerHTML = ''
- }
- }
+var templater = {}
+
+templater.getTemplate = function ({ valueNames, parentEl, template }) {
+ if (typeof template === 'function') {
+ return {
+ valueNames,
+ type: 'dynamic',
+ render: function (values) {
+ var item = template(values)
+ return stringToDOMElement(item)
+ },
}
- return el
}
- var getFirstListItem = function () {
- var nodes = list.list.childNodes
-
+ var itemSource
+ if (typeof template === 'string') {
+ if (template.indexOf('<') === -1) {
+ itemSource = document.getElementById(template)
+ } else {
+ itemSource = stringToDOMElement(template)
+ }
+ } else {
+ var nodes = parentEl.childNodes
for (var i = 0, il = nodes.length; i < il; i++) {
// Only textnodes have a data attribute
if (nodes[i].data === undefined) {
- return nodes[i].cloneNode(true)
+ itemSource = nodes[i].cloneNode(true)
+ break
}
}
- return undefined
- }
-
- var getItemSource = function (itemHTML) {
- if (typeof itemHTML !== 'string') return undefined
- if (/
]/g.exec(itemHTML)) {
- var tbody = document.createElement('tbody')
- tbody.innerHTML = itemHTML
- return tbody.firstElementChild
- } else if (itemHTML.indexOf('<') !== -1) {
- var div = document.createElement('div')
- div.innerHTML = itemHTML
- return div.firstElementChild
- }
- return undefined
}
-
- var getValueName = function (name) {
- for (var i = 0, il = list.valueNames.length; i < il; i++) {
- var valueName = list.valueNames[i]
- if (valueName.data) {
- var data = valueName.data
- for (var j = 0, jl = data.length; j < jl; j++) {
- if (data[j] === name) {
- return { data: name }
- }
- }
- } else if (valueName.attr && valueName.name && valueName.name == name) {
- return valueName
- } else if (valueName === name) {
- return name
- }
- }
+ if (!itemSource)
+ throw new Error("The list needs to have at least one item on init otherwise you'll have to add a template.")
+
+ itemSource = createCleanTemplateItem(itemSource, valueNames)
+
+ return {
+ valueNames,
+ render: function (values) {
+ var el = itemSource.cloneNode(true)
+ templater.set(el, values, valueNames)
+ return el
+ },
}
+}
- var setValue = function (item, name, value) {
- var elm = undefined,
- valueName = getValueName(name)
- if (!valueName) return
+templater.get = function (el, valueNames) {
+ var values = {}
+ for (var i = 0, il = valueNames.length; i < il; i++) {
+ var valueName = valueNames[i]
if (valueName.data) {
- item.elm.setAttribute('data-' + valueName.data, value)
- } else if (valueName.attr && valueName.name) {
- elm = list.utils.getByClass(item.elm, valueName.name, true)
- if (elm) {
- elm.setAttribute(valueName.attr, value)
+ for (var j = 0, jl = valueName.data.length; j < jl; j++) {
+ values[valueName.data[j]] = getAttribute(el, 'data-' + valueName.data[j])
}
+ } else if (valueName.attr && valueName.name) {
+ var elm = getByClass(el, valueName.name, true)
+ values[valueName.name] = elm ? getAttribute(elm, valueName.attr) : ''
} else {
- elm = list.utils.getByClass(item.elm, valueName, true)
- if (elm) {
- elm.innerHTML = value
- }
+ var elm = getByClass(el, valueName, true)
+ values[valueName] = elm ? elm.innerHTML : ''
}
}
+ return values
+}
- this.get = function (item, valueNames) {
- templater.create(item)
- var values = {}
- for (var i = 0, il = valueNames.length; i < il; i++) {
- var elm = undefined,
- valueName = valueNames[i]
- if (valueName.data) {
- for (var j = 0, jl = valueName.data.length; j < jl; j++) {
- values[valueName.data[j]] = list.utils.getAttribute(item.elm, 'data-' + valueName.data[j])
- }
- } else if (valueName.attr && valueName.name) {
- elm = list.utils.getByClass(item.elm, valueName.name, true)
- values[valueName.name] = elm ? list.utils.getAttribute(elm, valueName.attr) : ''
- } else {
- elm = list.utils.getByClass(item.elm, valueName, true)
- values[valueName] = elm ? elm.innerHTML : ''
- }
- }
- return values
- }
-
- this.set = function (item, values) {
- if (!templater.create(item)) {
- for (var v in values) {
- if (values.hasOwnProperty(v)) {
- setValue(item, v, values[v])
- }
- }
+templater.set = function (el, values, valueNames) {
+ for (var v in values) {
+ if (values.hasOwnProperty(v)) {
+ valueNamesUtils.set(el, v, values[v], valueNames)
}
}
+}
- this.create = function (item) {
- if (item.elm !== undefined) {
- return false
- }
- item.elm = createItem(item.values())
- templater.set(item, item.values())
- return true
- }
- this.remove = function (item) {
- if (item.elm.parentNode === list.list) {
- list.list.removeChild(item.elm)
- }
- }
- this.show = function (item) {
- templater.create(item)
- list.list.appendChild(item.elm)
- }
- this.hide = function (item) {
- if (item.elm !== undefined && item.elm.parentNode === list.list) {
- list.list.removeChild(item.elm)
- }
+templater.create = function (values, template) {
+ return template.render(values)
+}
+templater.remove = function (el, parentEl) {
+ if (el !== undefined && el.parentNode === parentEl) {
+ parentEl.removeChild(el)
}
- this.clear = function () {
- /* .innerHTML = ''; fucks up IE */
- if (list.list.hasChildNodes()) {
- while (list.list.childNodes.length >= 1) {
- list.list.removeChild(list.list.firstChild)
- }
+}
+templater.show = function (el, parentEl) {
+ parentEl.appendChild(el)
+}
+templater.clear = function (parentEl) {
+ /* .innerHTML = ''; fucks up IE */
+ if (parentEl.hasChildNodes()) {
+ while (parentEl.childNodes.length >= 1) {
+ parentEl.removeChild(parentEl.firstChild)
}
}
-
- init()
}
-module.exports = function (list) {
- return new Templater(list)
-}
+module.exports = templater
diff --git a/src/utils/events.js b/src/utils/events.js
old mode 100644
new mode 100755
index eeebcf61..03f0b490
--- a/src/utils/events.js
+++ b/src/utils/events.js
@@ -13,10 +13,10 @@ var bind = window.addEventListener ? 'addEventListener' : 'attachEvent',
* @api public
*/
-exports.bind = function(el, type, fn, capture){
- el = toArray(el);
- for ( var i = 0, il = el.length; i < il; i++ ) {
- el[i][bind](prefix + type, fn, capture || false);
+exports.bind = function (el, type, fn, capture) {
+ el = toArray(el)
+ for (var i = 0, il = el.length; i < il; i++) {
+ el[i][bind](prefix + type, fn, capture || false)
}
}
@@ -30,9 +30,39 @@ exports.bind = function(el, type, fn, capture){
* @api public
*/
-exports.unbind = function(el, type, fn, capture){
- el = toArray(el);
- for ( var i = 0, il = el.length; i < il; i++ ) {
- el[i][unbind](prefix + type, fn, capture || false);
+exports.unbind = function (el, type, fn, capture) {
+ el = toArray(el)
+ for (var i = 0, il = el.length; i < il; i++) {
+ el[i][unbind](prefix + type, fn, capture || false)
}
}
+
+/**
+ * Returns a function, that, as long as it continues to be invoked, will not
+ * be triggered. The function will be called after it stops being called for
+ * `wait` milliseconds. If `immediate` is true, trigger the function on the
+ * leading edge, instead of the trailing.
+ *
+ * @param {Function} fn
+ * @param {Integer} wait
+ * @param {Boolean} immediate
+ * @api public
+ */
+
+exports.debounce = function (fn, wait, immediate) {
+ var timeout
+ return wait
+ ? function () {
+ var context = this,
+ args = arguments
+ var later = function () {
+ timeout = null
+ if (!immediate) fn.apply(context, args)
+ }
+ var callNow = immediate && !timeout
+ clearTimeout(timeout)
+ timeout = setTimeout(later, wait)
+ if (callNow) fn.apply(context, args)
+ }
+ : fn
+}
diff --git a/src/utils/value-names.js b/src/utils/value-names.js
new file mode 100644
index 00000000..158d8595
--- /dev/null
+++ b/src/utils/value-names.js
@@ -0,0 +1,39 @@
+var getByClass = require('./get-by-class')
+
+const getDefinitionFromName = (name, valueNames) => {
+ for (var i = 0, il = valueNames.length; i < il; i++) {
+ var valueName = valueNames[i]
+ if (valueName.data) {
+ var data = valueName.data
+ for (var j = 0, jl = data.length; j < jl; j++) {
+ if (data[j] === name) {
+ return { data: name }
+ }
+ }
+ } else if (valueName.attr && valueName.name && valueName.name == name) {
+ return valueName
+ } else if (valueName === name) {
+ return name
+ }
+ }
+}
+const set = (el, name, value, valueNames) => {
+ var elm = undefined,
+ valueName = getDefinitionFromName(name, valueNames)
+ if (!valueName) return
+ if (valueName.data) {
+ el.setAttribute('data-' + valueName.data, value)
+ } else if (valueName.attr && valueName.name) {
+ elm = getByClass(el, valueName.name, true)
+ if (elm) {
+ elm.setAttribute(valueName.attr, value)
+ }
+ } else {
+ elm = getByClass(el, valueName, true)
+ if (elm) {
+ elm.innerHTML = value
+ }
+ }
+}
+
+module.exports = { getDefinitionFromName, set }