nf.UI = {
Pager: function(owner) { this.owner = owner; this.current = 1; this.total = 1; }, Actions: function(owner) { this.owner = owner || window; this.items = {}; this.rootElement = null; this.searchField = null; }, Labels: { create: 'Create', createAll: 'Create All', search: 'Search', accept: 'Accept', unpair: 'Unpair', cancel: 'Cancel', acceptAll: 'Accept all suggestions', goToFirst: String.fromCharCode(0xAB), // looks like << goToLast: String.fromCharCode(0xBB), // looks like >> goToPrevious: String.fromCharCode(0x2039), // looks like < goToNext: String.fromCharCode(0x203A), // looks like > of: 'of' }, Tests: { email: function(a, b) { // direct comparison (case sensitive) if(a == b) { return 1; } // go case-insensitive var x = (a || '').toLowerCase(), y = (b || '').toLowerCase(); // direct comparision if(x == y) { return .95; } // compare without domain suffixes if(x.replace(arguments.callee.variation, '') == y.replace(arguments.callee.variation, '')) { return .85; } // use deprioritised string comparison return nf.UI.Tests.string(a, b) / 3; }, fullName: function(a, b) { a = (a || '').trim(); b = (b || '').trim(); // direct comparison if(a == b) { return 1; } else if(a.toLowerCase() == b.toLowerCase()) { return .9; } // go to word by word var p = [a, b].map(function(x) { return x .trim() .split(/[^a-z]+/i) .filter(function(y) { return y && nf.UI.Tests.fullName.salutations.indexOf(y.toLowerCase()) == -1; }); }); var score = 0, i, j, k = p[0].length, l = p[1].length, x, y; for(i = 0; i < k; i++) { for(j = 0; j < l; j++) { x = p[0][i]; // word from a y = p[1][j]; // word from b // whole word match (with case): if(x == y) { score += 1; } // go case insensitive else { x = x.toLowerCase(); y = y.toLowerCase(); // whole word match: if(x == y) { score += .8; } // shortened version (eg Rob for Robert or J for John): else if(x.indexOf(y) == 0 || y.indexOf(x) == 0) { score += .5; } // contained (eg Liz for Elizabeth): else if(x.indexOf(y) >= 0 || y.indexOf(x) >= 0) { score += .3; } // first letter (eg Jim for James): else if(x.substr(0, 1) == y.substr(0, 1)) { score += .1; } } } } // max score will be 0.8. normalise to highest word count. return .8 * score / Math.max(p[0].length, p[1].length); }, string: function(a, b) { if(a == b) { return 1; } a = a || ''; b = b || ''; var i, x, y; if(a.length > b.length) { x = a.toLowerCase().trim(); y = b.toLowerCase().trim(); } else { x = b.toLowerCase().trim(); y = a.toLowerCase().trim(); } if(x === '' || y === '') { return 0; } if(x.indexOf(y) != -1) { return .95 * y.length / x.length; } return 0; }, amount: function(a, b) { if(a == b) { return 1; } var x = parseFloat((a || 0).toString().replace(/[^-.\d]+/g, '')), y = parseFloat((b || 0).toString().replace(/[^-.\d]+/g, '')); if(isNaN(x) || isNaN(y)) { return 0; } if(x == y) { return 1; } if(x == 0 || y == 0) { return 0; } return (x > y ? y / x : x / y) * 1.8 - 1; } }
};
nf.UI.Pager.prototype = {
goTo: function(dest) { this.current = Math.max(Math.min(this.total, dest), 1); this.update(); this.owner.update(); }, update: function(totalItems, itemsPerPage) { if(typeof totalItems == 'number' && typeof itemsPerPage == 'number') { this.total = Math.max(1, Math.ceil(totalItems / itemsPerPage)); this.current = Math.max(Math.min(this.current, this.total), 1); } this.currentElement.innerHTML = this.current.toString(); this.totalElement.innerHTML = this.total.toString(); nf.className(this.rootElement, 'bof', this.current == 1 ? 1 : -1); nf.className(this.rootElement, 'eof', this.current == this.total ? 1 : -1); }, init: function() { var l = nf.UI.Labels, pager = this; nf('<a>', this.rootElement, { className: 'first', href: 'javascript:', innerHTML: l.goToFirst.toHTML(), onclick: function() { pager.goTo(1) } }); nf('<', this.rootElement, ' '); nf('<a>', this.rootElement, { className: 'previous', href: 'javascript:', innerHTML: l.goToPrevious.toHTML(), onclick: function() { pager.goTo(pager.current - 1) } }); nf('<', this.rootElement, ' '); this.currentElement = nf('<span>', this.rootElement, {className: 'current'}); nf('<', this.rootElement, ' '); nf('<span>', this.rootElement, {className: 'of', innerHTML: l.of.toHTML()}); nf('<', this.rootElement, ' '); this.totalElement = nf('<span>', this.rootElement, {className: 'total'}); nf('<', this.rootElement, ' '); nf('<a>', this.rootElement, { className: 'next', href: 'javascript:', innerHTML: l.goToNext.toHTML(), onclick: function() { pager.goTo(pager.current + 1) } }); nf('<', this.rootElement, ' '); nf('<a>', this.rootElement, { className: 'last', href: 'javascript:', innerHTML: l.goToLast.toHTML(), onclick: function() { pager.goTo(pager.total) } }); this.update(); }
};
nf.UI.Actions.prototype = {
add: function(id, label, callback) { if(!(id in this.items)) { this.items[id] = [label, callback]; this.redraw(); } }, remove: function(id) { if(id in this.items) { delete this.items[id]; this.redraw(); } }, redraw: function() { if(!this.rootElement) { return; } this.rootElement.innerHTML = ''; var i = null; for(i in this.items) { nf('<a>', nf('<li>', this.rootElement, {className: i}), { href: 'javascript:', innerHTML: this.items[i][0].toHTML(), onclick: (function(c, that) { return function() { c.call(that) } })(this.items[i][1], this.owner) }); } if(i) { nf.className(this.rootElement.firstChild, 'first', 1); nf.className(this.rootElement.childNodes[this.rootElement.childNodes.length - 1], 'last', 1); } this.redrawSearch(); }, redrawSearch: function() { var searchValue = this.searchField ? this.searchField.value : '', id, li, l, o; if(this.owner.searchable) { l = nf.UI.Labels.search; li = nf('<li>', this.rootElement, {className: 'last search'}); if(this.rootElement.childNodes.length == 1) { nf.className(li, 'first', 1); } else { nf.className(li.previousSibling, 'last', -1); } id = this.owner.appId + 'Search'; nf('<label>', li, { innerHTML: l.toHTML(), htmlFor: id }); o = this.owner; this.searchField = nf('<input>', li, { placeholder: l, title: l, type: 'text', id: id, value: searchValue, onkeyup: function() { o.update() } }); } }
};
// list view class nf.ListView = function(rootElement) {
this.rootElement = rootElement; this.clear();
};
// list view class nf.ListView.prototype = {
addItem: function() { var ret = nf('<li>'); this.rootElement.appendChild(ret); this.refreshClasses(); return ret; }, activateItem: function(item) { if(typeof item == 'number') { item = this.rootElement.childNodes[item]; } var i = this.rootElement.childNodes.length; while(i--) { nf.className( this.rootElement.childNodes[i], 'active', this.rootElement.childNodes[i] === item ? 1 : -1 ); } }, removeItem: function(item) { if(typeof item == 'number') { item = this.rootElement.childNodes[item]; } this.rootElement.removeChild(item); this.refreshClasses(); }, refreshClasses: function() { var c = this.rootElement.childNodes, i = c.length; while(i--) { nf.className(c[i], 'first', !i ? 1 : -1); nf.className(c[i], 'last', i == c.length - 1 ? 1 : -1); } }, clear: function() { if(this.rootElement) { this.rootElement.innerHTML = ''; } }
};
nf.UI.Tests.fullName.salutations = “mr mrs ms dr prof”.split(' ');
nf.UI.Tests.email.variation = /.(com|net|org|gov|edu|{1,2})b.*|.+$/i;