There are 6 previous versions of this script.
Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)
// Digikey Sort By Price: Uses AJAX to fetch pages from digikey, sorts by price,
// and filters the table
// version 020, 2010-08-22
// Copyright (c) 2010, Alex Leone (acleone ~at~ gmail.com)
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script. To install it, you need
// Greasemonkey 0.3 or later: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools/Manage User Scripts,
// select "Digikey Sort By Price", and click Uninstall.
//
// This script should also work in Chrome.
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name Digikey Sort By Price
// @namespace http://students.washington.edu/acleone/
// @version 2.1
// @description sorts by price after using AJAX to fetch all pages
// @include http://search.digikey.com/scripts/DkSearch/dksus.dll*
// ==/UserScript==
/*
How to update the verison #:
1. Change the version number above.
2. Set the VERSION constant to the same number.
*/
function _log(s) {
GM_log("[sort_by_p] " + s);
}
function _error(s) {
s = "Error: " + s;
_log(s);
if (ui !== null) {
ui.add_error(s);
}
}
var CONTENT_DIV_ID = 'content',
CONTENT_DIV_START_RE = /<div id="content">/i,
CONTENT_DIV_END_RE = /<\/div>\s*<div id="noprint">/i,
PARTS_PAGE_HELP_HREF_RE = /help04\.html/i,
PAGE_NUMS_RE = /Page ([\d,.]+)\s*\/\s*([\d,.]+)/i,
NEXTPAGE_FORM_NAME = 'srform',
NEXTPAGE_PAGE_FIELD = 'page',
THOUSANDS_SEP_RE = /,/g,
CHECK_FOR_UPDATES_EVERY = 1000*60*60*6, // check for updates every 6 hours
UPDATE_URL = 'http://userscripts.org/scripts/source/50519.meta.js',
HOME1_URL = 'http://students.washington.edu/acleone/codes' +
'/greasemonkey/digikey_sort_by_price/',
HOME2_URL = 'http://userscripts.org/scripts/show/50519',
HELP_URL = 'http://students.washington.edu/acleone/codes' +
'/greasemonkey/digikey_sort_by_price/help_v020.html',
VERSION = '2.1',
VERSION_RE = /\/\/\s*@version\s+([0-9.]+)/i;
var table_data = null, // an instance of TableData.
ui = null, // an instance of SbpUI.
requestor = null, // an instance of Requestor.
pref_storage = null, // an instance of PrefStorage.
update_checker = null, // an instance of UpdateChecker.
_update_avail_shown = false;
function main() {
pref_storage = new PrefStorage();
pref_storage.init();
if (pref_storage.get("version", 'null') !== VERSION) {
pref_storage.clear();
pref_storage.set("version", VERSION);
}
update_checker = new UpdateChecker();
update_checker.init();
var parser = new HtmlTextParser(null);
parser._content_div = document.getElementById(CONTENT_DIV_ID);
if (!parser._content_div) {
_error('No <div id="content">!');
return false;
}
if (!parser.parse()) {
_error("Parsing Unsuccessful!");
return false;
}
if (!parser.is_parts_page()) {
_error("Not a parts page!");
return false;
}
table_data = new TableData();
ui = new SbpUI(table_data, parser._content_div);
ui.init();
if (!parser.get_table_data(table_data)) {
_error("Couldn't get table data!");
return false;
}
ui.table_data_ui.build_table_start_html(parser._parts_table);
ui.loading_ui.update_nparts();
var page_nums = parser.get_page_nums();
return (page_nums === null)? main_single_page(parser) :
main_multi_page(parser, page_nums);
}
function main_single_page(parser) {
_log("Single parts page.");
on_ajax_done();
return true;
}
function main_multi_page(parser, page_nums) {
_log("On page " + page_nums.cur_page + " of " + page_nums.out_of);
ui.loading_ui.set_out_of(page_nums.out_of);
var nextpage_form = parser.get_nextpage_form();
if (nextpage_form === null) {
_error("Couldn't find the nextpage form!");
return false;
}
var page_input = parser.get_page_input();
if (page_input === null) {
_error('nextpage form has no <input name="page">!');
return false;
}
var orig_page_val = parseInt(page_input.value, 10);
if (orig_page_val !== page_nums.cur_page) {
_error("nextpage form page doesn't match Page n/N text!");
return false;
}
if (page_nums.out_of >= 50 && !confirm("[Digikey Sort By Price] Fetch "
+ page_nums.out_of + " pages?")) {
_error("User cancelled large fetch!");
return false;
}
var i, requests = [];
for (i = 1; i <= page_nums.out_of; i += 1) {
if (i === page_nums.cur_page) {
continue;
}
page_input.value = i.toString();
requests.push({
req_details: {
method: "POST",
url: window.location.href,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: _get_form_post_data(nextpage_form)
},
page_nums: {
cur_page: i,
out_of: page_nums.out_of
}
});
}
// set this back to what it was originally.
page_input.value = orig_page_val.toString();
// start making requests
requestor = new Requestor({
requests: requests,
retries: 3,
concurrent: 4,
onload: on_ajax_page_load,
onerror: on_ajax_error,
ondone: on_ajax_done
});
return true;
}
function _ajax_error(res, msg) {
_error("[ajax] " + msg);
_error("[ajax] res.responseText: \r\n" + res.responseText);
requestor.stop();
}
function on_ajax_page_load(res, req) {
if (res.status !== 200) {
_ajax_error(res, "res.status !== 200: " + res.status);
return;
}
var parser = new HtmlTextParser(res.responseText);
if (!parser.parse()) {
_ajax_error(res, "Parsing Unsuccessful!");
return false;
}
if (!parser.is_parts_page()) {
_ajax_error(res, "Not a parts page!");
return false;
}
if (!parser.get_table_data(table_data)) {
_ajax_error(res, "Couldn't get table data!");
return false;
}
var pn = parser.get_page_nums();
if (pn === null) {
_ajax_error(res, "No page numbers found!");
return false;
}
if (pn.cur_page !== req.page_nums.cur_page
|| pn.out_of !== req.page_nums.out_of) {
_ajax_error(res, "Page numbers don't match expected values: "
+ pn.cur_page + '/' + pn.out_of + " != "
+ req.page_nums.cur_page + '/' + req.page_nums.out_of);
return false;
}
ui.loading_ui.inc_npages_loaded();
ui.loading_ui.update_nparts();
}
function on_ajax_error() {
_error("Page fetching failed!");
}
function on_ajax_done() {
_log("Page fetching done!");
ui.loading_ui.done();
ui.build_new_content_div();
ui.sort_ui.update();
setTimeout(function () {
ui.tdata.filter();
ui.tdata.sort();
ui.table_data_ui.update_table();
}, 10);
}
//=============================================================================
// Extracting data from the page
//=============================================================================
function HtmlTextParser(html) {
this._html = html;
this._content_div_html = null;
this._content_div = null;
this._parts_table = null;
this._parts_table_tbody = null;
this._nextpage_form = null;
}
HtmlTextParser.prototype = {
_build_content_div: function () {
var cstart = CONTENT_DIV_START_RE.exec(this._html);
if (cstart === null) {
_error("No match for CONTENT_DIV_START_RE: '" +
CONTENT_DIV_START_RE.toString() + "'!");
return false;
}
var cstop = CONTENT_DIV_END_RE.exec(
this._html.substring(cstart.index));
if (cstop === null) {
_error("No match for CONTENT_DIV_END_RE: '" +
CONTENT_DIV_END_RE.toString() + "'!");
return false;
}
this._content_div_html = this._html.substring(
cstart.index + cstart[0].length, cstop.index + cstart.index);
this._content_div = document.createElement('div');
this._content_div.innerHTML = this._content_div_html;
return true;
},
parse: function () {
/**
* @returns true if the page was successfully parsed, false otherwise.
*/
if (this._content_div === null && !this._build_content_div()) {
return false;
}
var tables = this._content_div.getElementsByTagName('table');
if (!tables.length) {
_error("No tables in content div!");
return false;
}
this._parts_table = tables[tables.length - 1];
var tHead = this._parts_table.tHead;
if (!tHead) {
_error("Parts table has no thead!");
return false;
}
if (tHead.rows.length !== 2) {
_error("Parts table thead has the wrong number of rows!");
return false;
}
var tBodies = this._parts_table.tBodies;
if (!tBodies.length) {
_error("Parts table has no tbody!");
return false;
}
this._parts_table_tbody = tBodies[tBodies.length - 1];
return true;
},
is_parts_page: function () {
/**
* @returns true if this is a parts page, false otherwise.
*/
var i, as = this._content_div.getElementsByTagName('a');
for (i = as.length - 1; i >= 0; i -= 1) {
if (PARTS_PAGE_HELP_HREF_RE.test(as[i].href)) {
return true;
}
}
return false;
},
get_page_nums: function () {
/**
* @returns an object {cur_page: 2, out_of: 12}, or null.
*/
var html = ((this._content_div_html)? this._content_div_html
: this._content_div.innerHTML),
match = PAGE_NUMS_RE.exec(html);
if (match !== null) {
return {
cur_page: parseInt(match[1]
.replace(THOUSANDS_SEP_RE, ''), 10),
out_of: parseInt(match[2]
.replace(THOUSANDS_SEP_RE, ''), 10)
};
}
return null;
},
get_nextpage_form: function () {
/**
* @returns the next-page form dom element or null.
*/
var i, forms = this._content_div.getElementsByTagName('form');
for (i = forms.length - 1; i >= 0; i -= 1) {
if (forms[i].name === NEXTPAGE_FORM_NAME) {
this._nextpage_form = forms[i];
return forms[i];
}
}
return null;
},
get_page_input: function () {
if (this._nextpage_form === null) {
this.get_nextpage_form();
if (this._nextpage_form === null) {
return null;
}
}
var i, inputs = this._nextpage_form.getElementsByTagName('input');
for (i = 0; i < inputs.length; i += 1) {
if (inputs[i].name === NEXTPAGE_PAGE_FIELD) {
return inputs[i];
}
}
return null;
},
get_table_data: function (add_to) {
/**
* Adds table data to `add_to`.
* @param add_to -- instance of TableData
* @returns true
*/
var i, rows = this._parts_table_tbody.rows;
for (i = 0; i < rows.length; i += 1) {
add_to.add(rows[i]);
}
return true;
}
};
function _get_form_post_data(form) {
/**
* @param form a <form> dom element
* @returns a uri string suitable for POST-ing from all the inputs with
* `name` and `value` attributes.
*/
var inputs = form.getElementsByTagName('input'),
i, input, encoded = [];
for (i = 0; i < inputs.length; i += 1) {
input = inputs[i];
if (input.hasAttribute('name') && input.hasAttribute('value')) {
encoded.push(encodeURIComponent(input.name) + "=" +
encodeURIComponent(input.value));
}
}
return encoded.join('&');
}
//=============================================================================
// Data Structure
//=============================================================================
function TableData() {
this._data = [];
/**
* _data format:
* _data[0] -- the outerHTML of the <tr> from digikey
* _data[1:] -- {t: td.textContent, f: parseFloat Number} for each td.
* if parseFloat returns NaN, then f will be Infinity.
*/
this._filtered_data = [];
this._col_names = null;
this.cols = null; // instance of Cols.
}
TableData.prototype = {
add: function (tr) {
/**
* tr {Dom Element} -- a <tr> to extract data from.
*/
var i, td_text, f, row = [_get_outer_html(tr)], cells = tr.cells;
for (i = 0; i < cells.length; i += 1) {
td_text = cells[i].textContent;
f = parseFloat(td_text.replace(THOUSANDS_SEP_RE, ''));
if (isNaN(f)) {
f = Infinity;
}
row.push({
t: td_text,
f: f
});
}
this._data.push(row);
},
set_col_names: function (tr) {
/**
* Sets the column names from a <tr> node.
* @param tr {Dom Element} -- the <tr> with column names.
*/
var i;
this._col_names = [];
for (i = 0; i < tr.cells.length; i += 1) {
this._col_names.push(tr.cells[i].textContent);
}
this.cols = new Cols(this);
},
get_col_names: function () {
return this._col_names;
},
get_col_name: function (c) {
return this._col_names[c];
},
nrows: function () {
/**
* @returns {Number} -- number of rows.
*/
return this._data.length;
},
nfiltered_rows: function () {
/**
* @returns {Number} -- number of rows in the filtered data set.
*/
return this._filtered_data.length;
},
ncols: function () {
/**
* @returns {Number} -- number of columns, or -1 if there's no data.
*/
return this._col_names.length;
},
filtered_rows_html: function (join_arr, start_i, len) {
/**
* Adds <tr> html strings to join_arr.
* @param start_i is optional.
* @param len is optional.
*/
var fd = this._filtered_data;
if (start_i === undefined) {
start_i = 0;
}
if (len === undefined) {
len = fd.length;
}
var i, end = start_i + len;
if (end > fd.length) {
end = fd.length;
}
for (i = start_i; i < end; i += 1) {
join_arr.push(fd[i][0]);
}
},
filter: function () {
this._filtered_data = ui.filters_ui.filter(this._data);
ui.filters_ui.update_count();
},
sort: function () {
this._filtered_data.sort(ui.sort_ui.get_sort_func());
}
};
function Cols(table_data) {
/**
* these are the indicies in the table data structure.
*/
this.PRICE = table_data.ncols() - 1;
this.MIN_QTY = this.PRICE - 1;
this.QTY_AVAILABLE = this.PRICE - 2;
this.DESC = 4;
}
//=============================================================================
// User Interface
//=============================================================================
function SbpUI(table_data, content_div) {
this.tdata = table_data;
this._orig_content = content_div; //< the original <div id="content">
this._new_content_div = null; //< <div> for the new content to go.
this._div = null; //< the <div> that surrounds our ui.
this._errors = null; // div to display error messages.
this.table_data_ui = null; // instance of TableDataUI
this.loading_ui = null; // instance of LoadingUI
this.hidden_cols_ui = null; // instance of HiddenColsUI
this.sort_ui = null; // instance of SortUI
this.filters_ui = null; // instance of FiltersUI
}
SbpUI.prototype = {
init: function () {
this._div = document.createElement('div');
this._div.style.cssText = '\
font-family: Helvetica, Arial, sans-serif;\
margin: 20px;\
padding: 5px 15px;\
-moz-border-radius: 15px;\
border-radius: 15px;\
border: 1px solid black;\
background-color: #eee';
this._div.style.marginBottom = '5px';
this._div.innerHTML = '\
<h2 style="margin-bottom: 2px">Digikey Sort By Price Userscript v</h2>\
<div style="margin-bottom: 6px">\
<a style="font-size:85%" href="#" target="_blank">Homepage 1</a> \
<a style="font-size:85%" href="#" target="_blank">Homepage 2</a> \
<a style="font-size:85%" href="#" target="_blank">Help</a> \
<a style="font-size:85%" href="#clearprefs" \
title="Clear all script preferences">Clear Prefs</a> \
<a style="font-size:85%" href="#checknow">Check For Updates</a>\
</div>\
<div></div>\
<div style="color: red"></div>';
this._div.firstChild.innerHTML += VERSION;
this._orig_content.parentNode
.insertBefore(this._div, this._orig_content);
var divs = this._div.getElementsByTagName('div'),
links = this._div.getElementsByTagName('a');
links[0].href = HOME1_URL;
links[1].href = HOME2_URL;
links[2].href = HELP_URL;
links[3].addEventListener('click', onclick_clear_prefs, true);
links[4].addEventListener('click', onclick_update_check_now, true);
this.table_data_ui = new TableDataUI();
this._errors = divs[2];
this.loading_ui = new LoadingUI();
this.loading_ui.build_into(divs[1]);
if (pref_storage.get("new_version_available", false)) {
_show_update_avail();
}
},
build_new_content_div: function () {
/**
* Hides the original content.
*/
// cloneNode(false) -- only copy attributes.
this._new_content_div = this._orig_content.cloneNode(false);
this._orig_content.parentNode
.insertBefore(this._new_content_div, this._orig_content);
this._orig_content.style.display = 'none';
this._new_content_div.innerHTML = '<div></div><div></div><div></div>';
this._new_content_div.style.marginTop = '0px';
this._new_content_div.style.paddingTop = '0px';
// getElementsByTagName returns live node list, so save divs.
var divs = this._new_content_div.getElementsByTagName('div'),
filters_div = divs[0],
hidden_cols_div = divs[1],
tdata_div = divs[2];
this.filters_ui = new FiltersUI();
this.filters_ui.build_into(filters_div);
this.sort_ui = new SortUI();
this.sort_ui.build_into(this.filters_ui.get_sort_span());
this.hidden_cols_ui = new HiddenColsUI();
this.hidden_cols_ui.build_into(hidden_cols_div);
hidden_cols_div.style.marginTop = '8px';
tdata_div.style.marginTop = '15px';
this.table_data_ui.build_into(tdata_div);
},
add_error: function (s) {
/**
* Displays an error in our ui.
*/
if (this._errors !== null) {
this._div.style.border = "1px solid red";
this._div.style.backgroundColor = "#ffcccc";
var ndiv = document.createElement('div');
ndiv.style.cssText = 'max-height: 150px; overflow: auto;';
var n = document.createElement('pre');
n.innerHTML = s.replace(/&/g, '&').replace(/</g, '<')
.replace(/>/g, '>');
ndiv.appendChild(n);
this._errors.appendChild(ndiv);
}
}
};
function onclick_clear_prefs(event) {
pref_storage.clear();
_log("Preferences Cleared!");
alert("Preferences Cleared!");
return _stop_event(event);
}
function onclick_update_check_now(event) {
var a = this;
_disable_link(a, true);
update_checker.check_now(function (alert_msg) {
_disable_link(a, false);
if (alert_msg) {
alert(alert_msg);
}
});
return _stop_event(event);
}
function TableDataUI() {
this._table_start_html = null; // html thead and sort columns.
this._table = null; // the <table> element that we create.
this._table_div = null; // where we build the table
this._paginator = null; // instance of Paginator
this._pag_top = null; // instance of PaginatorUI above table
this._pag_bot = null; // instance of PaginatorUI below table
}
TableDataUI.prototype = {
build_into: function (parent) {
parent.innerHTML = '<div></div><div></div><div></div>';
this._table_div = parent.childNodes[1];
this._paginator = new Paginator();
this._pag_top = new PaginatorUI(this._paginator);
this._pag_top.build_into(parent.childNodes[0]);
this._pag_bot = new PaginatorUI(this._paginator);
this._pag_bot.build_into(parent.childNodes[2]);
},
build_table_start_html: function (orig_table) {
/**
* @param orig_table {Dom Element} -- the original parts table.
*/
var start_arr = ['<table '];
var i, oattrs = orig_table.attributes;
for (i = 0; i < oattrs.length; i += 1) {
start_arr.push(oattrs[i].nodeName + '="'
+ oattrs[i].nodeValue + '"');
}
start_arr.push('><thead><tr>');
var first_tr = orig_table.rows[0];
ui.tdata.set_col_names(first_tr);
start_arr.push(first_tr.innerHTML);
start_arr.push('</tr><tr align="center">');
for (i = 0; i < ui.tdata.ncols() - 1; i += 1) {
start_arr.push('<td style="font-weight: bolder">\
<a href="#' + i + '_a" title="Smallest First (shift + click to add as \
secondary sort column)" \
style="float: left; text-decoration:none;">^</a>\
<a href="#' + i + '_h" title="Hide Column" \
style="color: red; text-decoration:none;">x</a>\
<a href="#' + i + '_d" title="Largest First (shift + click to add as \
secondary sort column)" \
style="float: right; text-decoration:none;">v</a></td>');
}
start_arr.push('<td> </td></tr></thead><tbody>');
this._table_start_html = start_arr.join('');
},
get_table: function () {
return this._table;
},
update_table: function () {
this._paginator.update();
this._pag_top.update();
this._pag_bot.update();
var html_arr = [this._table_start_html];
ui.tdata.filtered_rows_html(html_arr,
this._paginator.start_i(), this._paginator.length());
html_arr.push('</tbody></table>');
this._table_div.innerHTML = html_arr.join('');
this._table = this._table_div.firstChild;
var second_tr = this._table.firstChild.firstChild.nextSibling;
second_tr.addEventListener('click',
_create_link_handler(onclick_sort_tr, this), true);
ui.hidden_cols_ui.update_table(this._table);
}
};
function onclick_sort_tr(event) {
var col_action = event.after_pound.split('_'),
td_ui = event.data,
col = parseInt(col_action[0], 10), action = col_action[1];
if (action === 'h') {
var hc_ui = ui.hidden_cols_ui;
hc_ui.hide_col(col);
hc_ui.update_links();
hc_ui.update_table(td_ui.get_table());
} else if (action === 'a' || action === 'd') {
ui.sort_ui.add_key(col, action, event.shiftKey,
(event.ctrlKey)? 'l' : 'n');
ui.sort_ui.update();
ui.tdata.sort();
td_ui._paginator.cur_page = 0;
td_ui.update_table();
}
return false;
}
function LoadingUI() {
this._npages_loaded = null; // <span>
this._out_of = null; // <span>
this._nparts = null; // <span>
this._done = null; // <span>
}
LoadingUI.prototype = {
build_into: function (parent) {
parent.innerHTML = '\
Loaded <span>1</span> of <span>1</span> pages, <span>N</span> parts ... \
<span><a href="#stop" title="Stop Fetching">Stop</a></span>';
var spans = parent.getElementsByTagName('span');
this._npages_loaded = spans[0];
this._out_of = spans[1];
this._nparts = spans[2];
this._done = spans[3];
this._done.firstChild
.addEventListener('click', onclick_stop_fetching, true);
},
set_out_of: function (out_of) {
this._out_of.firstChild.nodeValue = out_of.toString();
},
inc_npages_loaded: function () {
var nl_node = this._npages_loaded.firstChild;
nl_node.nodeValue = (parseInt(nl_node.nodeValue, 10) + 1).toString();
},
update_nparts: function () {
this._nparts.firstChild.nodeValue = ui.tdata.nrows().toString();
},
done: function () {
this._done.innerHTML = "Done.";
}
};
function onclick_stop_fetching(event) {
_error("User stopped fetching.");
requestor.stop();
}
//=============================================================================
// Update Checks
//=============================================================================
function UpdateChecker() {
}
UpdateChecker.prototype = {
init: function () {
var last_check_ms = pref_storage.get("last_update_check", 0);
if (Date.now() - last_check_ms > CHECK_FOR_UPDATES_EVERY) {
this.check_now();
}
},
check_now: function (cbk_func) {
/** Calls cbk_func(msg) if we need to display something. */
cbk_func = cbk_func || function () {};
_log("Checking now for updates.");
pref_storage.set("last_update_check", Date.now());
GM_xmlhttpRequest({
method: 'GET',
url: UPDATE_URL,
onerror: function (details) {
if (details.status === 0 && details.statusText.length === 0
&& details.finalUrl === UPDATE_URL) {
// chrome blocks cross-domain xhr.
_log("Browser blocked cross-domain xhr.");
cbk_func("Please check for updates manually at\n\n" +
HOME1_URL + "\n\nor\n\n" + HOME2_URL);
} else {
_error("Could not check for updates at " + UPDATE_URL +
"\nfinalUrl: " + details.finalUrl +
"\nresponseHeaders: " + details.responseHeaders +
"\nresponseText: " + details.responseText +
"\nstatus: " + details.status +
"\nstatusText: " + details.statusText);
cbk_func();
}
},
onload: function (details) {
var vmatch = VERSION_RE.exec(details.responseText);
if (!vmatch) {
_error("No version string in " + details.responseText);
cbk_func();
return;
}
var v = vmatch[1];
_log("Update check returned version " + v);
if (parseFloat(v) > parseFloat(VERSION)) {
pref_storage.set("new_version_available", true);
_show_update_avail();
cbk_func();
alert("A new version of Digikey Sort By Price is " +
"available. See\n\n" + HOME1_URL + "\n\nor\n\n" +
HOME2_URL);
} else {
cbk_func("You have the latest version of " +
"Digikey Sort By Price.");
}
}
});
}
};
function _show_update_avail() {
if (!_update_avail_shown) {
_update_avail_shown = true;
_error("A new version of Digikey Sort By Price is " +
"available. See\n\n" + HOME1_URL + "\n\nor\n\n" + HOME2_URL);
}
}
//=============================================================================
// Pagination
//=============================================================================
function Paginator() {
this.cur_page = 0;
this.per_page = 100;
this.show_all = false;
this.npages = 1;
}
Paginator.prototype = {
is_first: function () {
return this.cur_page === 0;
},
is_last: function () {
return this.cur_page === this.npages - 1;
},
is_page: function (i) {
return this.cur_page === i;
},
update: function () {
if (this.show_all) {
this.npages = 1;
} else {
this.npages = Math.ceil(ui.tdata.nfiltered_rows()
/ this.per_page);
}
},
start_i: function () {
if (this.show_all) {
return 0;
}
return this.cur_page * this.per_page;
},
length: function () {
if (this.show_all) {
return ui.tdata.nfiltered_rows();
}
return this.per_page;
}
};
function PaginatorUI(paginator) {
this._paginator = paginator;
this._page_display_span = null;
this._prev_page = null;
this._next_page = null;
this._pages_span = null;
this._per_page_links = null;
}
PaginatorUI.prototype = {
build_into: function (parent) {
parent.innerHTML = '\
<span>\
<a href="#p_p" title="Previous Page">prev</a> \
<a href="#p_n" title="Next Page">next</a> \
<span></span>\
</span>\
( per page: \
<a href="#s_25" title="Show 25 per page">25</a> \
<a href="#s_50" title="Show 50 per page">50</a> \
<a href="#s_100" title="Show 100 per page">100</a> \
<a href="#s_200" title="Show 200 per page">200</a> \
<a href="#s_all" title="Show all parts">all</a> )';
var links = parent.getElementsByTagName('a'),
spans = parent.getElementsByTagName('span');
this._page_display_span = spans[0];
this._prev_page = links[0];
this._next_page = links[1];
this._pages_span = spans[1];
this._per_page_links = [];
var i;
for (i = 2; i < links.length; i += 1) {
this._per_page_links.push(links[i]);
}
parent.addEventListener('click',
_create_link_handler(onclick_paginator_div), true);
},
update: function () {
var p = this._paginator, i, a, page_links;
if (p.show_all || p.npages < 2) {
this._page_display_span.style.display = 'none';
} else {
this._page_display_span.style.display = 'inline';
_disable_link(this._prev_page, p.is_first());
_disable_link(this._next_page, p.is_last());
page_links = [];
this._pages_span.innerHTML = '';
for (i = 0; i < p.npages; i += 1) {
page_links.push('<a href="#p_' + i + '">' + (i + 1) + '</a>');
}
this._pages_span.innerHTML = page_links.join(' ');
_disable_link(this._pages_span
.getElementsByTagName('a')[p.cur_page]);
}
for (i = 0; i < this._per_page_links.length; i += 1) {
a = this._per_page_links[i];
_disable_link(a, _get_href_after_pound(a) === 's_' + p.per_page);
}
}
};
function onclick_paginator_div (event) {
var action_p = event.after_pound.split('_'),
action = action_p[0], p = action_p[1],
td_ui = ui.table_data_ui, paginator = td_ui._paginator;
if (action === 'p') {
if (p === 'n') {
paginator.cur_page += (paginator.is_last())? 0 : 1;
} else if (p === 'p') {
paginator.cur_page -= (paginator.is_first())? 0 : 1;
} else {
paginator.cur_page = parseInt(p, 10);
}
} else if (action === 's') {
if (p === 'all') {
paginator.show_all = true;
paginator.per_page = 'all';
} else {
paginator.cur_page = 0;
paginator.per_page = parseInt(p, 10);
paginator.show_all = false;
}
}
setTimeout(function () {
td_ui.update_table();
}, 10);
return false;
}
//=============================================================================
// Hiding Columns
//=============================================================================
function HiddenColsUI() {
this._hidden = []; // array of 0-indexed columns.
this._just_restored = [];
this._parent = null;
this._restore_span = null; // <span> with restore links.
this.from_json_obj(pref_storage.get("HiddenColsUI", []));
}
HiddenColsUI.prototype = {
build_into: function (parent) {
this._parent = parent;
parent.style.display = 'none';
parent.innerHTML = '\
<fieldset style="display:inline-block">\
<legend style="font-weight:bold">Hidden Columns</legend>\
Restore Hidden Columns: <span></span>\
</fieldset>';
this._restore_span = parent.getElementsByTagName('span')[0];
this._restore_span.addEventListener('click',
_create_link_handler(onclick_restore, this), true);
this.update_links();
},
to_json_obj: function () {
var i, result = [];
for (i = 0; i < this._hidden.length; i += 1) {
result.push(ui.tdata.get_col_name(this._hidden[i]));
}
return result;
},
from_json_obj: function (obj) {
var col_names = ui.tdata.get_col_names(), i, j;
this._hidden = [];
this._just_restored = [];
for (i = 0; i < obj.length; i += 1) {
j = col_names.indexOf(obj[i]);
if (j !== -1) {
this._hidden.push(j);
}
}
},
hide_col: function (col) {
this._hidden.push(col);
this._hidden.sort(function (a, b) {
return a - b;
});
},
restore_col: function (col) {
/**
* update_table must once after this.
*/
var i = this._hidden.indexOf(col);
if (i === -1) {
return;
}
this._just_restored.push(this._hidden.splice(i, 1));
},
update_links: function () {
var i, c, links = [];
for (i = 0; i < this._hidden.length; i += 1) {
c = this._hidden[i];
links.push('<a href="#' + c + '" title="Show this column">'
+ ui.tdata.get_col_name(c) + '</a>');
}
this._restore_span.innerHTML = links.join(', ');
if (links.length) {
this._parent.style.removeProperty('display');
} else {
this._parent.style.display = 'none';
}
pref_storage.set("HiddenColsUI", this.to_json_obj());
},
update_table: function (table) {
/**
* clears just_restored.
*/
var h = this._hidden, hlen = h.length,
r = this._just_restored, rlen = r.length;
if (!hlen && !rlen) {
return;
}
var rows = table.rows, cells, i, j;
for (i = 0; i < rows.length; i += 1) {
cells = rows[i].cells;
for (j = 0; j < hlen; j += 1) {
cells[h[j]].style.setProperty('display', 'none', '');
}
for (j = 0; j < rlen; j += 1) {
cells[r[j]].style.removeProperty('display');
}
}
this._just_restored = [];
}
};
function onclick_restore(event) {
var col = parseInt(event.after_pound, 10),
hc_ui = event.data;
hc_ui.restore_col(col);
hc_ui.update_links();
hc_ui.update_table(ui.table_data_ui.get_table());
return false;
}
//=============================================================================
// Filtering
//=============================================================================
function FiltersUI() {
this._filters = [];
this._fdiv = null;
this._ndisp = null;
this._out_of = null;
this._sort_span = null;
this.from_json_obj(pref_storage.get("FiltersUI", []));
}
FiltersUI.prototype = {
build_into: function (parent) {
parent.innerHTML = '\
<form action="#"><fieldset style="display: inline-block">\
<legend style="font-weight:bold">Filter By</legend>\
<div style="margin-bottom: 8px"></div>\
<div style="margin-bottom: 8px">\
<a href="#addadv" title="Add Another Filter" style="font-size:85%">\
Add Another Filter</a>\
</div>\
<input type="submit" value="Filter" /> \
<span>Displaying <span></span> of <span></span> parts, <span></span></span>\
</fieldset></form>';
parent.getElementsByTagName('form')[0].addEventListener('submit',
onsubmit_filters, true);
this._fdiv = parent.getElementsByTagName('div')[0];
var spans = parent.getElementsByTagName('span');
this._ndisp = spans[1];
this._out_of = spans[2];
this._sort_span = spans[3];
parent.getElementsByTagName('a')[0]
.addEventListener('click', onclick_add_adv, true);
var i;
for (i = 0; i < this._filters.length; i += 1) {
this.add_filter(this._filters[i]);
}
this.update_removes();
},
to_json_obj: function () {
var i, filter, result = [];
for (i = 0; i < this._filters.length; i += 1) {
filter = this._filters[i];
result.push({
enabled: this.is_enabled(i),
type: _to_filter_type_str(filter),
obj: (filter.to_json_obj)? filter.to_json_obj() : null
});
}
return result;
},
from_json_obj: function (obj) {
this._filters = [];
var i, o, f, fcls;
for (i = 0; i < obj.length; i += 1) {
o = obj[i];
fcls = _from_filter_type_str(o.type);
if (!fcls) {
continue;
}
f = new fcls(ui.tdata.cols);
if (f.from_json_obj) {
f.from_json_obj(o.obj);
}
f.enabled = o.enabled;
this._filters.push(f);
}
if (this._filters.length < 4) {
var cols = ui.tdata.cols;
this._filters = [
new AvailableFilter(cols),
new StockedFilter(cols),
new CallFilter(cols),
new MaxPriceFilter(cols),
new AdvancedFilter(cols)
];
}
},
is_enabled: function (i) {
return this._fdiv.childNodes[i].firstChild.checked;
},
get_sort_span: function () {
return this._sort_span;
},
filter: function (data) {
var result = data, i, filter;
for (i = 0; i < this._filters.length; i += 1) {
filter = this._filters[i];
if (this.is_enabled(i)) {
if (filter.reset) {
filter.reset();
}
result = result.filter(filter.filter);
}
}
return result;
},
update_count: function () {
this._ndisp.innerHTML = ui.tdata.nfiltered_rows();
this._out_of.innerHTML = ui.tdata.nrows();
},
add_filter: function (filter) {
var self = this;
var div = document.createElement('div');
this._fdiv.appendChild(div);
var check = document.createElement('input');
check.type = "checkbox";
check.checked = filter.enabled;
div.appendChild(check);
var span = document.createElement('span');
div.appendChild(span);
span.addEventListener('change', function (event) {
check.checked = true;
}, true);
filter.parent = span;
if (filter.build_into) {
filter.build_into(span);
} else {
span.innerHTML = filter.html;
}
var rlink = document.createElement('a');
rlink.style.cssText = 'display: none; margin-left: 10px;'
+ ' font-size:85%';
rlink.title = 'Remove This Filter';
rlink.innerHTML = 'remove';
div.appendChild(rlink);
rlink.addEventListener('click',
_create_link_handler(onclick_remove_adv, this), true);
},
remove_filter: function (i) {
this._fdiv.removeChild(this._fdiv.childNodes[i]);
this._filters.splice(i, 1);
},
update_removes: function () {
var i, rlink;
for (i = 0; i < this._fdiv.childNodes.length; i += 1) {
rlink = this._fdiv.childNodes[i].lastChild;
rlink.href = '#rmfilter_' + i;
if (i > 2 && this._filters.length > 4) {
rlink.style.display = 'inline';
} else {
rlink.style.display = 'none';
}
}
//pref_storage.set("FiltersUI", this.to_json_obj());
}
};
function onsubmit_filters(event) {
pref_storage.set("FiltersUI", ui.filters_ui.to_json_obj());
ui.tdata.filter();
ui.tdata.sort();
ui.table_data_ui._paginator.cur_page = 0;
ui.table_data_ui.update_table();
return _stop_event(event);
}
function onclick_add_adv(event) {
var new_f = new AdvancedFilter(ui.tdata.cols);
ui.filters_ui._filters.push(new_f);
ui.filters_ui.add_filter(new_f);
ui.filters_ui.update_removes();
return _stop_event(event);
}
function onclick_remove_adv(event) {
var filters_ui = event.data,
i = parseInt(event.after_pound.split('_')[1], 10);
filters_ui.remove_filter(i);
filters_ui.update_removes();
}
function _to_filter_type_str(filter) {
if (filter instanceof AvailableFilter) {
return "AvailableFilter";
} else if (filter instanceof StockedFilter) {
return "StockedFilter";
} else if (filter instanceof CallFilter) {
return "CallFilter";
} else if (filter instanceof MaxPriceFilter) {
return "MaxPriceFilter";
} else if (filter instanceof AdvancedFilter) {
return "AdvancedFilter";
}
return null;
}
function _from_filter_type_str(s) {
var classes = {
'AvailableFilter': AvailableFilter,
'StockedFilter': StockedFilter,
'CallFilter': CallFilter,
'MaxPriceFilter': MaxPriceFilter,
'AdvancedFilter': AdvancedFilter
};
if (classes.hasOwnProperty(s)) {
return classes[s];
}
}
function AvailableFilter(cols) {
this.html = "Available (Quantity Available > 0 or 'Available')";
this.filter = function (row) {
var o = row[cols.QTY_AVAILABLE];
return o.f !== Infinity && o.f > 0 || o.t === 'Available';
};
}
function StockedFilter(cols) {
this.html = "Stocked (Minimum Quantity doesn't contain 'Non-Stock')";
this.filter = function (row) {
return row[cols.MIN_QTY].t.indexOf('Non-Stock') === -1;
};
}
function CallFilter(cols) {
this.html = "No 'Call' Prices";
this.filter = function (row) {
var o = row[cols.PRICE];
return o.t !== 'Call';
};
}
function MaxPriceFilter(cols) {
var max_price = 100.0;
this.build_into = function (parent) {
parent.innerHTML = 'Maximum Price (Min Qty * Price):'
+ ' <input type="text" size=6'
+ ' value="' + max_price + '" />';
};
this.reset = function () {
max_price = parseFloat(this.parent.getElementsByTagName('input')[0]
.value.replace(THOUSANDS_SEP_RE, ''));
};
this.filter = function (row) {
var min_qty = row[cols.MIN_QTY], price = row[cols.PRICE];
return min_qty.f * price.f < max_price;
};
this.to_json_obj = function () {
this.reset(); // saves values from input
return max_price;
};
this.from_json_obj = function (obj) {
max_price = obj;
};
}
function AdvancedFilter(cols) {
var OPS = ['contains', "doesn't contain",
'matches regex', "doesn't match regex",
'> (num)', '< (num)', '= (num)'];
var BOOLS = ['and', 'or'];
var conds = [{
col: cols.DESC,
op: 0,
val: "",
join: null
}];
var col_names = ui.tdata.get_col_names().slice(0, -1);
col_names.unshift('* (any col)');
var _parent = null;
this.build_into = function (parent) {
_parent = parent;
var i, cond, html_arr = [];
for (i = 0; i < conds.length; i += 1) {
cond = conds[i];
html_arr.push('<span>');
html_arr.push(_build_select_html(col_names, cond.col,
'width:120px'));
html_arr.push(_build_select_html(OPS, cond.op, 'width:100px'));
html_arr.push('<input type="text" size="'
+ cond.val.toString().length + '" />');
html_arr.push('<span> [<a href="#rmcond_' + i + '" '
+ 'style="color:red;" '
+ 'title="Remove This Condition">x</a>]'
+ '</span>');
html_arr.push(_build_select_html(BOOLS, cond.join,
'margin: 0px 20px'));
html_arr.push('</span>');
}
html_arr.push(' [<a href="#newcond" '
+ 'title="Add Another Condition">+</a>]');
parent.innerHTML = html_arr.join('');
this.update_hidden();
var inputs = parent.getElementsByTagName('input');
for (i = 0; i < inputs.length; i += 1) {
inputs[i].value = conds[i].val;
}
parent.addEventListener('keydown', advfilter_resize_inputs, true);
parent.addEventListener('change', advfilter_resize_inputs, true);
parent.addEventListener('click',
_create_link_handler(onclick_advfilter, this), true);
};
this.to_json_obj = function () {
this.reset(); // updates conds
var i, cond, result = [];
for (i = 0; i < conds.length; i += 1) {
cond = conds[i];
result.push({
col: (cond.col === 0)? 0 : col_names[cond.col],
op: cond.op,
val: cond.val,
join: cond.join
});
}
return result;
};
this.from_json_obj = function (obj) {
var i, col_i, cond, o;
conds = [];
for (i = 0; i < obj.length; i += 1) {
o = obj[i];
if (o.col === 0) {
conds.push(o);
} else {
col_i = col_names.indexOf(o.col);
if (col_i !== -1) {
o.col = col_i;
conds.push(o);
}
}
}
if (conds.length === 0) {
conds = [{
col: cols.DESC,
op: 0,
val: "",
join: null
}];
} else {
conds[conds.length - 1].join = null;
}
};
this.add_new_cond = function (new_link) {
var cond_span = new_link.previousSibling.previousSibling,
new_cond_span = cond_span.cloneNode(true);
_parent.insertBefore(new_cond_span, cond_span.nextSibling);
this.update_hidden();
};
this.rm_cond = function (rm_link, cond_i) {
var span = rm_link.parentNode.parentNode;
span.parentNode.removeChild(span);
this.update_hidden();
};
this.update_hidden = function () {
var inputs = _parent.getElementsByTagName('input'),
i, span = inputs[0].parentNode;
for (i = 0; i < inputs.length; i += 1) {
span = inputs[i].parentNode;
span.getElementsByTagName('a')[0].href = "#rmcond_" + i;
span.lastChild // join select
.style.display = (i < inputs.length - 1)? 'inline' : 'none';
span.lastChild.previousSibling // remove link span
.style.display = (inputs.length === 1)? 'none' : 'inline';
}
};
this.reset = function () {
/**
* Updates conds.
*/
conds = [];
var inputs = _parent.getElementsByTagName('input'),
i, input, cond;
for (i = 0; i < inputs.length; i += 1) {
input = inputs[i];
cond = {
col: parseInt(input.previousSibling.previousSibling.value, 10),
op: parseInt(input.previousSibling.value, 10),
val: input.value,
join: parseInt(input.parentNode.lastChild.value, 10)
};
if (cond.op > 3) {
cond.val = parseFloat(cond.val.replace(THOUSANDS_SEP_RE, ''));
if (isNaN(cond.val)) {
cond.val = input.value;
}
}
cond.f = _create_advfilter_func(cond.col, cond.op, cond.val);
conds.push(cond);
}
conds[conds.length - 1].join = null;
};
this.filter = function (row) {
/**
* f && t = f
* f && f || t = (f && f) || t = t
*/
var i, cond, join, last_was = true;
for (i = 0; i < conds.length; i += 1) {
cond = conds[i];
join = cond.join;
if (last_was === false) {
if (join === 1) { // ... || -- don't skip the next
last_was = true;
}
// f && ... until || x
continue;
}
if (cond.f(row)) {
if (join === 1 || join === null) {
// ... || -- short-circuit to true
return true;
}
} else {
if (join === 0) {
// 'and' -- skip to next ||
last_was = false;
}
}
}
return false;
};
}
function _create_advfilter_func(col, op, val) {
if (op === 0 || op === 1) {
val = val.toLowerCase();
if (op === 0 && col !== 0) {
return function (row) {
return row[col].t.toLowerCase().indexOf(val) !== -1;
};
} else if (op === 0 && col === 0) {
return function (row) {
var i;
for (i = 1; i < row.length; i += 1) {
if (row[i].t.toLowerCase().indexOf(val) !== -1) {
return true;
}
}
return false;
};
} else if (op === 1 && col !== 0) {
return function (row) {
return row[col].t.toLowerCase().indexOf(val) === -1;
};
} else if (op === 1 && col === 0) {
return function (row) {
var i;
for (i = 1; i < row.length; i += 1) {
if (row[i].t.toLowerCase().indexOf(val) === -1) {
return true;
}
}
return false;
};
}
} else if (op === 2 || op === 3) {
var re = RegExp(val, "i");
if (op === 2 && col !== 0) {
return function (row) {
return re.test(row[col].t);
};
} else if (op === 2 && col === 0) {
return function (row) {
var i;
for (i = 1; i < row.length; i += 1) {
if (re.test(row[i].t)) {
return true;
}
}
return false;
};
} else if (op === 3 && col !== 0) {
return function (row) {
return !re.test(row[col].t);
};
} else if (op === 3 && col === 0) {
return function (row) {
var i;
for (i = 1; i < row.length; i += 1) {
if (!re.test(row[i].t)) {
return true;
}
}
return false;
};
}
} else if (op === 4 && col !== 0) {
return function (row) {
return row[col].f > val;
};
} else if (op === 4 && col === 0) {
return function (row) {
var i;
for (i = 1; i < row.length; i += 1) {
if (row[i].f > val) {
return true;
}
}
return false;
};
} else if (op === 5 && col !== 0) {
return function (row) {
return row[col].f < val;
};
} else if (op === 5 && col === 0) {
return function (row) {
var i;
for (i = 1; i < row.length; i += 1) {
if (row[i].f < val) {
return true;
}
}
return false;
};
} else if (op === 6 && col !== 0) {
return function (row) {
return row[col].f === val;
};
} else if (op === 6 && col === 0) {
return function (row) {
var i;
for (i = 1; i < row.length; i += 1) {
if (row[i].f === val) {
return true;
}
}
return false;
};
}
}
function onclick_advfilter(event) {
if (event.after_pound === 'newcond') {
event.data.add_new_cond(event.target);
} else if (event.after_pound.indexOf('rmcond') === 0) {
var cond_i = parseInt(event.after_pound.split('_')[1], 10);
event.data.rm_cond(event.target, cond_i);
}
return false;
}
function advfilter_resize_inputs(event) {
if (event.target.nodeName.toUpperCase() === 'INPUT') {
setTimeout(advfilter_resize_inputs_t, 10, event.target);
}
}
function advfilter_resize_inputs_t(node) {
node.size = node.value.length;
}
//=============================================================================
// Sorting
//=============================================================================
function SortUI() {
this._span = null;
this._keys = [];
this.from_json_obj(pref_storage.get("SortUI", []));
}
SortUI.prototype = {
build_into: function (parent) {
parent.innerHTML = 'Sorted by <span></span>';
this._span = parent.getElementsByTagName('span')[0];
this._span.addEventListener('click',
_create_link_handler(onclick_sort_del, this), true);
this.update();
},
to_json_obj: function () {
var i, k, result = [];
for (i = 0; i < this._keys.length; i += 1) {
k = this._keys[i];
result.push({
col: ui.tdata.get_col_name(k.col),
a_or_d: k.a_or_d,
ctype: k.ctype
});
}
return result;
},
from_json_obj: function (obj) {
var i, o, col_i, col_names = ui.tdata.get_col_names();
this._keys = [];
for (i = 0; i < obj.length; i += 1) {
o = obj[i];
col_i = col_names.indexOf(o.col);
if (col_i !== -1) {
this.add_key(col_i, o.a_or_d, true, o.ctype);
}
}
if (this._keys.length === 0) {
this.add_key(ui.tdata.cols.PRICE - 1, 'a', true, 'n');
}
},
add_key: function (col, a_or_d, add_key, ctype) {
var o = {
col: col,
a_or_d: a_or_d,
ctype: ctype,
f: _create_sort_func(col, a_or_d, ctype)
};
if (add_key) {
var i, k;
// can't add duplicate columns.
for (i = 0; i < this._keys.length; i += 1) {
k = this._keys[i];
if (o.col === k.col) {
this._keys[i] = o;
return;
}
}
this._keys.push(o);
} else {
this._keys = [o];
}
},
del_key: function (key_i) {
this._keys.splice(key_i, 1);
},
update: function () {
var i, sk, texts = [];
for (i = 0; i < this._keys.length; i += 1) {
sk = this._keys[i];
texts.push("'" + ui.tdata.get_col_name(sk.col) + "' "
+ ((sk.a_or_d === 'a')? 'Asc ' : 'Desc ')
+ '(' + ((sk.ctype === 'l')? 'lexicographic' : 'numeric')
+ ')' + ((this._keys.length === 1)? '' :
' [<a href="#' + i + '"'
+ ' title="Remove this sort key">x</a>]'));
}
this._span.innerHTML = texts.join(', then ');
pref_storage.set("SortUI", this.to_json_obj());
},
get_sort_func: function () {
return _create_recursive_sort(this._keys);
}
};
function onclick_sort_del(event) {
var key_i = parseInt(event.after_pound, 10),
sort_ui = event.data;
sort_ui.del_key(key_i);
sort_ui.update();
ui.tdata.sort();
ui.table_data_ui._paginator.cur_page = 0;
ui.table_data_ui.update_table();
return false;
}
function _create_recursive_sort(sort_keys, i) {
if (i === undefined) {
i = 0;
}
if (i >= sort_keys.length) {
return function (a, b) {
return 0;
};
}
var f = sort_keys[i].f;
var next = _create_recursive_sort(sort_keys, i + 1);
return function (a, b) {
var c = f(a, b);
if (c !== 0) {
return c;
}
return next(a, b);
};
}
function _create_sort_func(col, a_or_d, ctype) {
var f = _get_sort_func(a_or_d, ctype),
i = col + 1;
return function (a, b) {
return f(a[i], b[i]);
};
}
function _get_sort_func(a_or_d, ctype) {
if (a_or_d === 'a') {
return (ctype === 'l')? _lexicographic_a : _numeric_a;
} else {
return (ctype === 'l')? _lexicographic_d : _numeric_d;
}
}
function _numeric_a(a, b) {
if (a.f === Infinity && b.f === Infinity) {
return _lexicographic_a(a, b);
}
return a.f - b.f;
}
function _numeric_d(a, b) {
if (a.f === Infinity && b.f === Infinity) {
return _lexicographic_d(a, b);
}
return b.f - a.f;
}
function _lexicographic_a(a, b) {
return a.t.localeCompare(b.t);
}
function _lexicographic_d(a, b) {
return b.t.localeCompare(a.t);
}
//=============================================================================
// Preference Storage
//=============================================================================
function PrefStorage() {
this._JSON = null; // the browser's JSON object (or nsIJSON)
this._set = null;
this._get = null;
}
PrefStorage.prototype = {
init: function () {
try {
this._JSON = JSON;
} catch (x) {
_log("[storage] No native JSON");
try {
var Ci = Components.interfaces;
var Cc = Components.classes;
var nativeJSON = Cc["@mozilla.org/dom/json;1"]
.createInstance(Ci.nsIJSON);
this._JSON = {
stringify: function (obj) {
return nativeJSON.encode(obj);
},
parse: function (str) {
return nativeJSON.decode(str);
}
};
} catch (x) {
this._JSON = null;
_log("[storage] Browser doesn't support JSON, " +
"storage disabled.");
return false;
}
}
try {
GM_setValue("dsbp_test", "3");
if (GM_getValue("dsbp_test") !== "3") {
throw "No GM_setValue!";
}
this._set = function (name, value) {
GM_setValue(name, value);
};
this._get = function (name) {
return GM_getValue(name, null);
};
this.clear = function () {
var i, names = GM_listValues();
for (i = 0; i < names.length; i += 1) {
GM_deleteValue(names[i]);
}
};
} catch (x) {
_log("[storage] No GM_setValue: " + x);
try {
if (!localStorage.setItem) {
throw "No localStorage!";
}
this._set = function (name, value) {
return localStorage.setItem("dsbp_" + name, value);
};
this._get = function (name) {
return localStorage.getItem("dsbp_" + name);
};
this.clear = function () {
var i, name, to_remove = [];
for (i = 0; i < localStorage.length; i += 1) {
name = localStorage.key(i);
if (name.indexOf("dsbp_") === 0) {
to_remove.push(name);
}
}
for (i = 0; i < to_remove.length; i += 1) {
localStorage.removeItem(to_remove[i]);
}
};
} catch (x) {
this._set = null;
this._get = null;
_log("[storage] Browser doesn't support storage.");
return false;
}
}
},
set: function (name, value) {
if (this._set !== null && this._JSON !== null) {
this._set(name, this._JSON.stringify(value));
}
},
get: function (name, default_) {
/**
* Returns the parsed JSON object.
*/
var o;
if (this._get === null || this._JSON === null
|| (o = this._get(name)) === null) {
return default_;
}
try {
return this._JSON.parse(o);
} catch (x) {
_log("[storage] Warning: json parsing reported: " + x);
return default_;
}
}
};
//=============================================================================
// Ajax Request Queuer
//=============================================================================
function Requestor(opts) {
/**
* Concurrently makes a set of ajax requests.
* @param opts an options object:
* 'requests' -- a list of objects: {
* 'req_details' -- passed to GM_xmlhttpRequest(req_details).
* onload and onerror will be overwritten.
* 'onload' -- (optional) callback function (res, req, requestor)
* Called when a request succeeds.
* res -- a response obj with status, responseText, etc, as
* defined by greasemonkey.
* req -- this object.
* requestor -- this Requestor.
*
* 'onload' -- (optional) callback function (res, req, requestor)
* Called when a request succeeds. This is called after a
* request-specific onload callback. Same res, req, etc as above.
*
* 'ondone' -- (optional) callback function (requestor)
* Called when all the requests are finished.
*
* 'retries' -- (optional, default: 3) number of times to retry failed
* requests.
*
* 'onerror' -- (optional) callback function (requestor)
* Called when a request has failed more than `retries` times.
*
* 'concurrent' -- (optional, default: 5) number of concurrent fetches.
*/
if (opts.retries === undefined) { opts.retries = 3; }
if (opts.concurrent === undefined) { opts.concurrent = 5; }
var _req_queue = [];
var _call_queue = [];
var _currently_fetching = 0;
var _stopped = false;
var self = this;
function _build_req_queue() {
var i, req, creq_details, o;
for (i = 0; i < opts.requests.length; i += 1) {
req = opts.requests[i];
creq_details = _copy_obj(req.req_details);
o = {
details: creq_details,
orig_req: req,
times_tried: 0
};
creq_details.onload = _req_curry(_onload, o);
creq_details.onerror = _req_curry(_onerror, o);
_req_queue.push(o);
}
}
function _service_call_queue() {
var o;
while (_call_queue.length) {
o = _call_queue.shift();
//console.time('cbk_' + o.func.name);
o.func.apply(self, o.params);
//console.timeEnd('cbk_' + o.func.name);
}
}
function _spawn_requests() {
if (_req_queue.length === 0 && _currently_fetching === 0) {
_alldone();
return;
}
if (_stopped) {
return;
}
var req;
while (_currently_fetching < opts.concurrent && _req_queue.length) {
req = _req_queue.shift();
_currently_fetching += 1;
GM_xmlhttpRequest(req.details);
}
}
function _req_curry(f, req) {
/**
* @returns a function that will call f with second parameter req.
*/
return function (res) {
f(res, req);
};
}
function _onload(res, req) {
/**
* Called when a request successfully returns.
* @param res -- a Response object, see
* http://wiki.greasespot.net/GM_xmlhttpRequest
* @param req -- the augmented request object - see _build_req_queue.
*/
_currently_fetching -= 1;
if (req.orig_req.onload) {
_call_queue.push({
func: req.orig_req.onload,
params: [res, req.orig_req, self]
});
}
if (opts.onload) {
_call_queue.push({
func: opts.onload,
params: [res, req.orig_req, self]
});
}
_spawn_requests();
setTimeout(_service_call_queue, 1);
}
function _onerror(res, req) {
/**
* Called when a request does not successfully return.
* @param res -- a Response object, see
* http://wiki.greasespot.net/GM_xmlhttpRequest
* @param req -- the augmented request object - see _build_req_queue.
*/
_currently_fetching -= 1;
req.times_tried += 1;
if (req.times_tried > opts.retries) {
_stopped = true;
if (opts.onerror) {
_call_queue.push({
func: opts.onerror,
params: [self]
});
}
} else {
_req_queue.push(req);
_spawn_requests();
}
setTimeout(_service_call_queue, 1);
}
function _alldone() {
/**
* Called when all requests are finished.
*/
if (opts.ondone) {
_call_queue.push({
func: opts.ondone,
params: [self]
});
}
}
this.stop = function () {
_stopped = true;
};
_build_req_queue();
_spawn_requests();
}
//=============================================================================
// Misc Functions
//=============================================================================
function _copy_obj(o) {
/**
* Shallow-copy.
*/
var k, r = {};
for (k in o) {
if (o.hasOwnProperty(k)) {
r[k] = o[k];
}
}
return r;
}
function _stop_event(event) {
if (event.preventDefault) {
event.preventDefault();
}
if (event.stopPropogation) {
event.stopPropogation();
}
return false;
}
function _disable_link(a, disabled) {
/**
* @param a {Dom Element}
* @param disabled {boolean}
*/
if (disabled || disabled === undefined) {
a.style.color = 'black';
a.style.textDecoration = 'none';
} else {
a.style.removeProperty('color');
a.style.removeProperty('text-decoration');
}
}
function _get_outer_html(node) {
/**
* Gets the outerHTML of node, using XMLSerializer if there is no outerHTML
* property.
*/
var r = node.outerHTML;
if (r === undefined) {
return (new XMLSerializer()).serializeToString(node);
}
return r;
}
function _get_href_after_pound(a) {
var i = a.href.lastIndexOf('#');
return a.href.substring(i + 1);
}
function _create_link_handler(f, data) {
return function (event) {
var a = event.target;
if (a.nodeName.toUpperCase() === 'A') {
event.data = data;
event.after_pound = _get_href_after_pound(a);
if (!f(event)) {
return _stop_event(event);
}
}
};
}
function _build_select_html(vals, sel_i, style) {
var s;
if (style === undefined) {
s = ['<select>'];
} else {
s = ['<select style="' + style + '">'];
}
var i;
for (i = 0; i < vals.length; i += 1) {
s.push('<option value="' + i + '"'
+ ((i === sel_i)? ' selected="selected"' : '')
+ '>' + vals[i] + '</option>');
}
s.push('</select>');
return s.join('');
}
main();