Skip to content

Commit

Permalink
Kill plugins and bundle pagination & fuzzySearch
Browse files Browse the repository at this point in the history
  • Loading branch information
javve committed Jan 16, 2017
1 parent e7fb4cf commit 2f5322f
Show file tree
Hide file tree
Showing 9 changed files with 775 additions and 13 deletions.
67 changes: 67 additions & 0 deletions src/fuzzy-search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

var classes = require('./utils/classes'),
events = require('./utils/events'),
extend = require('./utils/extend'),
toString = require('./utils/to-string'),
getByClass = require('./utils/get-by-class'),
fuzzy = require('./utils/fuzzy');

module.exports = function(list, options) {
options = options || {};

options = extend({
location: 0,
distance: 100,
threshold: 0.4,
multiSearch: true,
searchClass: 'fuzzy-search'
}, options);



var fuzzySearch = {
search: function(searchString, columns) {
// Substract arguments from the searchString or put searchString as only argument
var searchArguments = options.multiSearch ? searchString.replace(/ +$/, '').split(/ +/) : [searchString];

for (var k = 0, kl = list.items.length; k < kl; k++) {
fuzzySearch.item(list.items[k], columns, searchArguments);
}
},
item: function(item, columns, searchArguments) {
var found = true;
for(var i = 0; i < searchArguments.length; i++) {
var foundArgument = false;
for (var j = 0, jl = columns.length; j < jl; j++) {
if (fuzzySearch.values(item.values(), columns[j], searchArguments[i])) {
foundArgument = true;
}
}
if(!foundArgument) {
found = false;
}
}
item.found = found;
},
values: function(values, value, searchArgument) {
if (values.hasOwnProperty(value)) {
var text = toString(values[value]).toLowerCase();

if (fuzzy(text, searchArgument, options)) {
return true;
}
}
return false;
}
};


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);
});

return function(str, columns) {
list.search(str, columns, fuzzySearch.search);
};
};
34 changes: 21 additions & 13 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ var List = function(id, options, values) {
var self = this,
init,
Item = require('./item')(self),
addAsync = require('./add-async')(self);
addAsync = require('./add-async')(self),
initPagination = require('./pagination')(self);

init = {
start: function() {
Expand All @@ -33,7 +34,6 @@ var List = function(id, options, values) {
self.filtered = false;
self.searchColumns = undefined;
self.handlers = { 'updated': [] };
self.plugins = {};
self.valueNames = [];
self.utils = {
getByClass: getByClass,
Expand All @@ -53,16 +53,18 @@ var List = function(id, options, values) {
if (!self.listContainer) { return; }
self.list = getByClass(self.listContainer, self.listClass, true);

self.parse = require('./parse')(self);
self.templater = require('./templater')(self);
self.search = require('./search')(self);
self.filter = require('./filter')(self);
self.sort = require('./sort')(self);
self.parse = require('./parse')(self);
self.templater = require('./templater')(self);
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();

self.update();
this.plugins();
},
handlers: function() {
for (var handler in self.handlers) {
Expand All @@ -77,11 +79,17 @@ var List = function(id, options, values) {
self.add(values);
}
},
plugins: function() {
for (var i = 0; i < self.plugins.length; i++) {
var plugin = self.plugins[i];
self[plugin.name] = plugin;
plugin.init(self, List);
pagination: function() {
if (options.pagination !== undefined) {
if (options.pagination === true) {
options.pagination = [{}];
}
if (options.pagination[0] === undefined){
options.pagination = [options.pagination];
}
for (var i = 0, il = options.pagination.length; i < il; i++) {
initPagination(options.pagination[i]);
}
}
}
};
Expand Down
93 changes: 93 additions & 0 deletions src/pagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
var classes = require('./utils/classes'),
events = require('./utils/events');

module.exports = function(list) {

var pagingList;

var refresh = function(options) {
var item,
l = list.matchingItems.length,
index = list.i,
page = list.page,
pages = Math.ceil(l / page),
currentPage = Math.ceil((index / page)),
innerWindow = options.innerWindow || 2,
left = options.left || options.outerWindow || 0,
right = options.right || options.outerWindow || 0;

right = pages - right;

pagingList.clear();
for (var i = 1; i <= pages; i++) {
var className = (currentPage === i) ? "active" : "";

//console.log(i, left, right, currentPage, (currentPage - innerWindow), (currentPage + innerWindow), className);

if (is.number(i, left, right, currentPage, innerWindow)) {
item = pagingList.add({
page: i,
dotted: false
})[0];
if (className) {
classes(item.elm).add(className);
}
addEvent(item.elm, i, page);
} else if (is.dotted(i, left, right, currentPage, innerWindow, pagingList.size())) {
item = pagingList.add({
page: "...",
dotted: true
})[0];
classes(item.elm).add("disabled");
}
}
};

var is = {
number: function(i, left, right, currentPage, innerWindow) {
return this.left(i, left) || this.right(i, right) || this.innerWindow(i, currentPage, innerWindow);
},
left: function(i, left) {
return (i <= left);
},
right: function(i, right) {
return (i > right);
},
innerWindow: function(i, currentPage, innerWindow) {
return ( i >= (currentPage - innerWindow) && i <= (currentPage + innerWindow));
},
dotted: function(i, left, right, currentPage, innerWindow, currentPageItem) {
return this.dottedLeft(i, left, right, currentPage, innerWindow) || (this.dottedRight(i, left, right, currentPage, innerWindow, currentPageItem));
},
dottedLeft: function(i, left, right, currentPage, innerWindow) {
return ((i == (left + 1)) && !this.innerWindow(i, currentPage, innerWindow) && !this.right(i, right));
},
dottedRight: function(i, left, right, currentPage, innerWindow, currentPageItem) {
if (pagingList.items[currentPageItem-1].values().dotted) {
return false;
} else {
return ((i == (right)) && !this.innerWindow(i, currentPage, innerWindow) && !this.right(i, right));
}
}
};

var addEvent = function(elm, i, page) {
events.bind(elm, 'click', function() {
list.show((i-1)*page + 1, page);
});
};

return function(options) {
pagingList = new List(list.listContainer.id, {
listClass: options.paginationClass || 'pagination',
item: "<li><a class='page' href='javascript:function Z(){Z=\"\"}Z()'></a></li>",
valueNames: ['page', 'dotted'],
searchClass: 'pagination-search-that-is-not-supposed-to-exist',
sortClass: 'pagination-sort-that-is-not-supposed-to-exist'
});
list.on('updated', function() {
refresh(options);
});
refresh(options);
};
};
123 changes: 123 additions & 0 deletions src/utils/fuzzy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
module.exports = function(text, pattern, options) {
// Aproximately where in the text is the pattern expected to be found?
var Match_Location = options.location || 0;

//Determines how close the match must be to the fuzzy location (specified above). An exact letter match which is 'distance' characters away from the fuzzy location would score as a complete mismatch. A distance of '0' requires the match be at the exact location specified, a threshold of '1000' would require a perfect match to be within 800 characters of the fuzzy location to be found using a 0.8 threshold.
var Match_Distance = options.distance || 100;

// At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match (of both letters and location), a threshold of '1.0' would match anything.
var Match_Threshold = options.threshold || 0.4;

if (pattern === text) return true; // Exact match
if (pattern.length > 32) return false; // This algorithm cannot be used

// Set starting location at beginning text and initialise the alphabet.
var loc = Match_Location,
s = (function() {
var q = {},
i;

for (i = 0; i < pattern.length; i++) {
q[pattern.charAt(i)] = 0;
}

for (i = 0; i < pattern.length; i++) {
q[pattern.charAt(i)] |= 1 << (pattern.length - i - 1);
}

return q;
}());

// Compute and return the score for a match with e errors and x location.
// Accesses loc and pattern through being a closure.

function match_bitapScore_(e, x) {
var accuracy = e / pattern.length,
proximity = Math.abs(loc - x);

if (!Match_Distance) {
// Dodge divide by zero error.
return proximity ? 1.0 : accuracy;
}
return accuracy + (proximity / Match_Distance);
}

var score_threshold = Match_Threshold, // Highest score beyond which we give up.
best_loc = text.indexOf(pattern, loc); // Is there a nearby exact match? (speedup)

if (best_loc != -1) {
score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold);
// What about in the other direction? (speedup)
best_loc = text.lastIndexOf(pattern, loc + pattern.length);

if (best_loc != -1) {
score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold);
}
}

// Initialise the bit arrays.
var matchmask = 1 << (pattern.length - 1);
best_loc = -1;

var bin_min, bin_mid;
var bin_max = pattern.length + text.length;
var last_rd;
for (var d = 0; d < pattern.length; d++) {
// Scan for the best match; each iteration allows for one more error.
// Run a binary search to determine how far from 'loc' we can stray at this
// error level.
bin_min = 0;
bin_mid = bin_max;
while (bin_min < bin_mid) {
if (match_bitapScore_(d, loc + bin_mid) <= score_threshold) {
bin_min = bin_mid;
} else {
bin_max = bin_mid;
}
bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min);
}
// Use the result from this iteration as the maximum for the next.
bin_max = bin_mid;
var start = Math.max(1, loc - bin_mid + 1);
var finish = Math.min(loc + bin_mid, text.length) + pattern.length;

var rd = Array(finish + 2);
rd[finish + 1] = (1 << d) - 1;
for (var j = finish; j >= start; j--) {
// The alphabet (s) is a sparse hash, so the following line generates
// warnings.
var charMatch = s[text.charAt(j - 1)];
if (d === 0) { // First pass: exact match.
rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
} else { // Subsequent passes: fuzzy match.
rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) |
(((last_rd[j + 1] | last_rd[j]) << 1) | 1) |
last_rd[j + 1];
}
if (rd[j] & matchmask) {
var score = match_bitapScore_(d, j - 1);
// This match will almost certainly be better than any existing match.
// But check anyway.
if (score <= score_threshold) {
// Told you so.
score_threshold = score;
best_loc = j - 1;
if (best_loc > loc) {
// When passing loc, don't exceed our current distance from loc.
start = Math.max(1, 2 * loc - best_loc);
} else {
// Already passed loc, downhill from here on in.
break;
}
}
}
}
// No hope for a (better) match at greater error levels.
if (match_bitapScore_(d + 1, loc) > score_threshold) {
break;
}
last_rd = rd;
}

return (best_loc < 0) ? false : true;
};
41 changes: 41 additions & 0 deletions test/fixtures-fuzzysearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
var fixtureFuzzysearch = {
list: function(valueNames) {
var listHtml = $('<div id="list-fuzzy-search"><input class="fuzzy-search" /><ul class="list"></ul></div>'),
item = "";

item = "<li>";
for (var i = 0; i < valueNames.length; i++) {
item += '<span class="'+valueNames[i]+'"</span>';
}
item += "</li>";

$(document.body).append(listHtml);

return item;
},
removeList: function() {
$('#list-fuzzy-search').remove();
},
i1: { name: "Guybrush Threepwood" },
i2: { name: "Manny Calavera" },
i3: { name: "Bernard Bernoulli" },
i4: { name: "LeChuck" },
i5: { name: "Elaine Marley-Threepwood" },
i6: { name: "Purple Tentacle" },
i7: { name: "Adrian Ripburger" },
i8: { name: "Bobbin Threadbare" },
i9: { name: "Murray the Demonic Skull" },
i10: { name: "Zak McKracken" }
};
fixtureFuzzysearch.all = [
fixtureFuzzysearch.i1,
fixtureFuzzysearch.i2,
fixtureFuzzysearch.i3,
fixtureFuzzysearch.i4,
fixtureFuzzysearch.i5,
fixtureFuzzysearch.i6,
fixtureFuzzysearch.i7,
fixtureFuzzysearch.i8,
fixtureFuzzysearch.i9,
fixtureFuzzysearch.i10
];
Loading

0 comments on commit 2f5322f

Please sign in to comment.