The data comes from this spreadsheet.
Created
August 10, 2012 01:31
-
-
Save jlord/3310111 to your computer and use it in GitHub Desktop.
Counting items in a Google Spreadsheet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* accounting.js v0.3.2 | |
* Copyright 2011, Joss Crowcroft | |
* | |
* Freely distributable under the MIT license. | |
* Portions of accounting.js are inspired or borrowed from underscore.js | |
* | |
* Full details and documentation: | |
* http://josscrowcroft.github.com/accounting.js/ | |
*/ | |
(function(root, undefined) { | |
/* --- Setup --- */ | |
// Create the local library object, to be exported or referenced globally later | |
var lib = {}; | |
// Current version | |
lib.version = '0.3.2'; | |
/* --- Exposed settings --- */ | |
// The library's settings configuration object. Contains default parameters for | |
// currency and number formatting | |
lib.settings = { | |
currency: { | |
symbol : "$", // default currency symbol is '$' | |
format : "%s%v", // controls output: %s = symbol, %v = value (can be object, see docs) | |
decimal : ".", // decimal point separator | |
thousand : ",", // thousands separator | |
precision : 2, // decimal places | |
grouping : 3 // digit grouping (not implemented yet) | |
}, | |
number: { | |
precision : 0, // default precision on numbers is 0 | |
grouping : 3, // digit grouping (not implemented yet) | |
thousand : ",", | |
decimal : "." | |
} | |
}; | |
/* --- Internal Helper Methods --- */ | |
// Store reference to possibly-available ECMAScript 5 methods for later | |
var nativeMap = Array.prototype.map, | |
nativeIsArray = Array.isArray, | |
toString = Object.prototype.toString; | |
/** | |
* Tests whether supplied parameter is a string | |
* from underscore.js | |
*/ | |
function isString(obj) { | |
return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); | |
} | |
/** | |
* Tests whether supplied parameter is a string | |
* from underscore.js, delegates to ECMA5's native Array.isArray | |
*/ | |
function isArray(obj) { | |
return nativeIsArray ? nativeIsArray(obj) : toString.call(obj) === '[object Array]'; | |
} | |
/** | |
* Tests whether supplied parameter is a true object | |
*/ | |
function isObject(obj) { | |
return obj && toString.call(obj) === '[object Object]'; | |
} | |
/** | |
* Extends an object with a defaults object, similar to underscore's _.defaults | |
* | |
* Used for abstracting parameter handling from API methods | |
*/ | |
function defaults(object, defs) { | |
var key; | |
object = object || {}; | |
defs = defs || {}; | |
// Iterate over object non-prototype properties: | |
for (key in defs) { | |
if (defs.hasOwnProperty(key)) { | |
// Replace values with defaults only if undefined (allow empty/zero values): | |
if (object[key] == null) object[key] = defs[key]; | |
} | |
} | |
return object; | |
} | |
/** | |
* Implementation of `Array.map()` for iteration loops | |
* | |
* Returns a new Array as a result of calling `iterator` on each array value. | |
* Defers to native Array.map if available | |
*/ | |
function map(obj, iterator, context) { | |
var results = [], i, j; | |
if (!obj) return results; | |
// Use native .map method if it exists: | |
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); | |
// Fallback for native .map: | |
for (i = 0, j = obj.length; i < j; i++ ) { | |
results[i] = iterator.call(context, obj[i], i, obj); | |
} | |
return results; | |
} | |
/** | |
* Check and normalise the value of precision (must be positive integer) | |
*/ | |
function checkPrecision(val, base) { | |
val = Math.round(Math.abs(val)); | |
return isNaN(val)? base : val; | |
} | |
/** | |
* Parses a format string or object and returns format obj for use in rendering | |
* | |
* `format` is either a string with the default (positive) format, or object | |
* containing `pos` (required), `neg` and `zero` values (or a function returning | |
* either a string or object) | |
* | |
* Either string or format.pos must contain "%v" (value) to be valid | |
*/ | |
function checkCurrencyFormat(format) { | |
var defaults = lib.settings.currency.format; | |
// Allow function as format parameter (should return string or object): | |
if ( typeof format === "function" ) format = format(); | |
// Format can be a string, in which case `value` ("%v") must be present: | |
if ( isString( format ) && format.match("%v") ) { | |
// Create and return positive, negative and zero formats: | |
return { | |
pos : format, | |
neg : format.replace("-", "").replace("%v", "-%v"), | |
zero : format | |
}; | |
// If no format, or object is missing valid positive value, use defaults: | |
} else if ( !format || !format.pos || !format.pos.match("%v") ) { | |
// If defaults is a string, casts it to an object for faster checking next time: | |
return ( !isString( defaults ) ) ? defaults : lib.settings.currency.format = { | |
pos : defaults, | |
neg : defaults.replace("%v", "-%v"), | |
zero : defaults | |
}; | |
} | |
// Otherwise, assume format was fine: | |
return format; | |
} | |
/* --- API Methods --- */ | |
/** | |
* Takes a string/array of strings, removes all formatting/cruft and returns the raw float value | |
* alias: accounting.`parse(string)` | |
* | |
* Decimal must be included in the regular expression to match floats (defaults to | |
* accounting.settings.number.decimal), so if the number uses a non-standard decimal | |
* separator, provide it as the second argument. | |
* | |
* Also matches bracketed negatives (eg. "$ (1.99)" => -1.99) | |
* | |
* Doesn't throw any errors (`NaN`s become 0) but this may change in future | |
*/ | |
var unformat = lib.unformat = lib.parse = function(value, decimal) { | |
// Recursively unformat arrays: | |
if (isArray(value)) { | |
return map(value, function(val) { | |
return unformat(val, decimal); | |
}); | |
} | |
// Fails silently (need decent errors): | |
value = value || 0; | |
// Return the value as-is if it's already a number: | |
if (typeof value === "number") return value; | |
// Default decimal point comes from settings, but could be set to eg. "," in opts: | |
decimal = decimal || lib.settings.number.decimal; | |
// Build regex to strip out everything except digits, decimal point and minus sign: | |
var regex = new RegExp("[^0-9-" + decimal + "]", ["g"]), | |
unformatted = parseFloat( | |
("" + value) | |
.replace(/\((.*)\)/, "-$1") // replace bracketed values with negatives | |
.replace(regex, '') // strip out any cruft | |
.replace(decimal, '.') // make sure decimal point is standard | |
); | |
// This will fail silently which may cause trouble, let's wait and see: | |
return !isNaN(unformatted) ? unformatted : 0; | |
}; | |
/** | |
* Implementation of toFixed() that treats floats more like decimals | |
* | |
* Fixes binary rounding issues (eg. (0.615).toFixed(2) === "0.61") that present | |
* problems for accounting- and finance-related software. | |
*/ | |
var toFixed = lib.toFixed = function(value, precision) { | |
precision = checkPrecision(precision, lib.settings.number.precision); | |
var power = Math.pow(10, precision); | |
// Multiply up by precision, round accurately, then divide and use native toFixed(): | |
return (Math.round(lib.unformat(value) * power) / power).toFixed(precision); | |
}; | |
/** | |
* Format a number, with comma-separated thousands and custom precision/decimal places | |
* | |
* Localise by overriding the precision and thousand / decimal separators | |
* 2nd parameter `precision` can be an object matching `settings.number` | |
*/ | |
var formatNumber = lib.formatNumber = function(number, precision, thousand, decimal) { | |
// Resursively format arrays: | |
if (isArray(number)) { | |
return map(number, function(val) { | |
return formatNumber(val, precision, thousand, decimal); | |
}); | |
} | |
// Clean up number: | |
number = unformat(number); | |
// Build options object from second param (if object) or all params, extending defaults: | |
var opts = defaults( | |
(isObject(precision) ? precision : { | |
precision : precision, | |
thousand : thousand, | |
decimal : decimal | |
}), | |
lib.settings.number | |
), | |
// Clean up precision | |
usePrecision = checkPrecision(opts.precision), | |
// Do some calc: | |
negative = number < 0 ? "-" : "", | |
base = parseInt(toFixed(Math.abs(number || 0), usePrecision), 10) + "", | |
mod = base.length > 3 ? base.length % 3 : 0; | |
// Format the number: | |
return negative + (mod ? base.substr(0, mod) + opts.thousand : "") + base.substr(mod).replace(/(\d{3})(?=\d)/g, "$1" + opts.thousand) + (usePrecision ? opts.decimal + toFixed(Math.abs(number), usePrecision).split('.')[1] : ""); | |
}; | |
/** | |
* Format a number into currency | |
* | |
* Usage: accounting.formatMoney(number, symbol, precision, thousandsSep, decimalSep, format) | |
* defaults: (0, "$", 2, ",", ".", "%s%v") | |
* | |
* Localise by overriding the symbol, precision, thousand / decimal separators and format | |
* Second param can be an object matching `settings.currency` which is the easiest way. | |
* | |
* To do: tidy up the parameters | |
*/ | |
var formatMoney = lib.formatMoney = function(number, symbol, precision, thousand, decimal, format) { | |
// Resursively format arrays: | |
if (isArray(number)) { | |
return map(number, function(val){ | |
return formatMoney(val, symbol, precision, thousand, decimal, format); | |
}); | |
} | |
// Clean up number: | |
number = unformat(number); | |
// Build options object from second param (if object) or all params, extending defaults: | |
var opts = defaults( | |
(isObject(symbol) ? symbol : { | |
symbol : symbol, | |
precision : precision, | |
thousand : thousand, | |
decimal : decimal, | |
format : format | |
}), | |
lib.settings.currency | |
), | |
// Check format (returns object with pos, neg and zero): | |
formats = checkCurrencyFormat(opts.format), | |
// Choose which format to use for this value: | |
useFormat = number > 0 ? formats.pos : number < 0 ? formats.neg : formats.zero; | |
// Return with currency symbol added: | |
return useFormat.replace('%s', opts.symbol).replace('%v', formatNumber(Math.abs(number), checkPrecision(opts.precision), opts.thousand, opts.decimal)); | |
}; | |
/** | |
* Format a list of numbers into an accounting column, padding with whitespace | |
* to line up currency symbols, thousand separators and decimals places | |
* | |
* List should be an array of numbers | |
* Second parameter can be an object containing keys that match the params | |
* | |
* Returns array of accouting-formatted number strings of same length | |
* | |
* NB: `white-space:pre` CSS rule is required on the list container to prevent | |
* browsers from collapsing the whitespace in the output strings. | |
*/ | |
lib.formatColumn = function(list, symbol, precision, thousand, decimal, format) { | |
if (!list) return []; | |
// Build options object from second param (if object) or all params, extending defaults: | |
var opts = defaults( | |
(isObject(symbol) ? symbol : { | |
symbol : symbol, | |
precision : precision, | |
thousand : thousand, | |
decimal : decimal, | |
format : format | |
}), | |
lib.settings.currency | |
), | |
// Check format (returns object with pos, neg and zero), only need pos for now: | |
formats = checkCurrencyFormat(opts.format), | |
// Whether to pad at start of string or after currency symbol: | |
padAfterSymbol = formats.pos.indexOf("%s") < formats.pos.indexOf("%v") ? true : false, | |
// Store value for the length of the longest string in the column: | |
maxLength = 0, | |
// Format the list according to options, store the length of the longest string: | |
formatted = map(list, function(val, i) { | |
if (isArray(val)) { | |
// Recursively format columns if list is a multi-dimensional array: | |
return lib.formatColumn(val, opts); | |
} else { | |
// Clean up the value | |
val = unformat(val); | |
// Choose which format to use for this value (pos, neg or zero): | |
var useFormat = val > 0 ? formats.pos : val < 0 ? formats.neg : formats.zero, | |
// Format this value, push into formatted list and save the length: | |
fVal = useFormat.replace('%s', opts.symbol).replace('%v', formatNumber(Math.abs(val), checkPrecision(opts.precision), opts.thousand, opts.decimal)); | |
if (fVal.length > maxLength) maxLength = fVal.length; | |
return fVal; | |
} | |
}); | |
// Pad each number in the list and send back the column of numbers: | |
return map(formatted, function(val, i) { | |
// Only if this is a string (not a nested array, which would have already been padded): | |
if (isString(val) && val.length < maxLength) { | |
// Depending on symbol position, pad after symbol or at index 0: | |
return padAfterSymbol ? val.replace(opts.symbol, opts.symbol+(new Array(maxLength - val.length + 1).join(" "))) : (new Array(maxLength - val.length + 1).join(" ")) + val; | |
} | |
return val; | |
}); | |
}; | |
/* --- Module Definition --- */ | |
// Export accounting for CommonJS. If being loaded as an AMD module, define it as such. | |
// Otherwise, just add `accounting` to the global object | |
if (typeof exports !== 'undefined') { | |
if (typeof module !== 'undefined' && module.exports) { | |
exports = module.exports = lib; | |
} | |
exports.accounting = lib; | |
} else if (typeof define === 'function' && define.amd) { | |
// Return the library as an AMD module: | |
define([], function() { | |
return lib; | |
}); | |
} else { | |
// Use accounting.noConflict to restore `accounting` back to its original value. | |
// Returns a reference to the library's `accounting` object; | |
// e.g. `var numbers = accounting.noConflict();` | |
lib.noConflict = (function(oldAccounting) { | |
return function() { | |
// Reset the value of the root's `accounting` variable: | |
root.accounting = oldAccounting; | |
// Delete the noConflict method: | |
lib.noConflict = undefined; | |
// Return reference to the library to re-assign it: | |
return lib; | |
}; | |
})(root.accounting); | |
// Declare `fx` on the root (global/window) object: | |
root['accounting'] = lib; | |
} | |
// Root will be `window` in browser or `global` on the server: | |
}(this)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
ICanHaz.js version 0.10 -- by @HenrikJoreteg | |
More info at: http://icanhazjs.com | |
*/ | |
(function () { | |
/* | |
mustache.js — Logic-less templates in JavaScript | |
See http://mustache.github.com/ for more info. | |
*/ | |
var Mustache = function () { | |
var _toString = Object.prototype.toString; | |
Array.isArray = Array.isArray || function (obj) { | |
return _toString.call(obj) == "[object Array]"; | |
} | |
var _trim = String.prototype.trim, trim; | |
if (_trim) { | |
trim = function (text) { | |
return text == null ? "" : _trim.call(text); | |
} | |
} else { | |
var trimLeft, trimRight; | |
// IE doesn't match non-breaking spaces with \s. | |
if ((/\S/).test("\xA0")) { | |
trimLeft = /^[\s\xA0]+/; | |
trimRight = /[\s\xA0]+$/; | |
} else { | |
trimLeft = /^\s+/; | |
trimRight = /\s+$/; | |
} | |
trim = function (text) { | |
return text == null ? "" : | |
text.toString().replace(trimLeft, "").replace(trimRight, ""); | |
} | |
} | |
var escapeMap = { | |
"&": "&", | |
"<": "<", | |
">": ">", | |
'"': '"', | |
"'": ''' | |
}; | |
function escapeHTML(string) { | |
return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) { | |
return escapeMap[s] || s; | |
}); | |
} | |
var regexCache = {}; | |
var Renderer = function () {}; | |
Renderer.prototype = { | |
otag: "{{", | |
ctag: "}}", | |
pragmas: {}, | |
buffer: [], | |
pragmas_implemented: { | |
"IMPLICIT-ITERATOR": true | |
}, | |
context: {}, | |
render: function (template, context, partials, in_recursion) { | |
// reset buffer & set context | |
if (!in_recursion) { | |
this.context = context; | |
this.buffer = []; // TODO: make this non-lazy | |
} | |
// fail fast | |
if (!this.includes("", template)) { | |
if (in_recursion) { | |
return template; | |
} else { | |
this.send(template); | |
return; | |
} | |
} | |
// get the pragmas together | |
template = this.render_pragmas(template); | |
// render the template | |
var html = this.render_section(template, context, partials); | |
// render_section did not find any sections, we still need to render the tags | |
if (html === false) { | |
html = this.render_tags(template, context, partials, in_recursion); | |
} | |
if (in_recursion) { | |
return html; | |
} else { | |
this.sendLines(html); | |
} | |
}, | |
/* | |
Sends parsed lines | |
*/ | |
send: function (line) { | |
if (line !== "") { | |
this.buffer.push(line); | |
} | |
}, | |
sendLines: function (text) { | |
if (text) { | |
var lines = text.split("\n"); | |
for (var i = 0; i < lines.length; i++) { | |
this.send(lines[i]); | |
} | |
} | |
}, | |
/* | |
Looks for %PRAGMAS | |
*/ | |
render_pragmas: function (template) { | |
// no pragmas | |
if (!this.includes("%", template)) { | |
return template; | |
} | |
var that = this; | |
var regex = this.getCachedRegex("render_pragmas", function (otag, ctag) { | |
return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g"); | |
}); | |
return template.replace(regex, function (match, pragma, options) { | |
if (!that.pragmas_implemented[pragma]) { | |
throw({message: | |
"This implementation of mustache doesn't understand the '" + | |
pragma + "' pragma"}); | |
} | |
that.pragmas[pragma] = {}; | |
if (options) { | |
var opts = options.split("="); | |
that.pragmas[pragma][opts[0]] = opts[1]; | |
} | |
return ""; | |
// ignore unknown pragmas silently | |
}); | |
}, | |
/* | |
Tries to find a partial in the curent scope and render it | |
*/ | |
render_partial: function (name, context, partials) { | |
name = trim(name); | |
if (!partials || partials[name] === undefined) { | |
throw({message: "unknown_partial '" + name + "'"}); | |
} | |
if (!context || typeof context[name] != "object") { | |
return this.render(partials[name], context, partials, true); | |
} | |
return this.render(partials[name], context[name], partials, true); | |
}, | |
/* | |
Renders inverted (^) and normal (#) sections | |
*/ | |
render_section: function (template, context, partials) { | |
if (!this.includes("#", template) && !this.includes("^", template)) { | |
// did not render anything, there were no sections | |
return false; | |
} | |
var that = this; | |
var regex = this.getCachedRegex("render_section", function (otag, ctag) { | |
// This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder | |
return new RegExp( | |
"^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1) | |
otag + // {{ | |
"(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3) | |
ctag + // }} | |
"\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped | |
otag + // {{ | |
"\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag). | |
ctag + // }} | |
"\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped. | |
"g"); | |
}); | |
// for each {{#foo}}{{/foo}} section do... | |
return template.replace(regex, function (match, before, type, name, content, after) { | |
// before contains only tags, no sections | |
var renderedBefore = before ? that.render_tags(before, context, partials, true) : "", | |
// after may contain both sections and tags, so use full rendering function | |
renderedAfter = after ? that.render(after, context, partials, true) : "", | |
// will be computed below | |
renderedContent, | |
value = that.find(name, context); | |
if (type === "^") { // inverted section | |
if (!value || Array.isArray(value) && value.length === 0) { | |
// false or empty list, render it | |
renderedContent = that.render(content, context, partials, true); | |
} else { | |
renderedContent = ""; | |
} | |
} else if (type === "#") { // normal section | |
if (Array.isArray(value)) { // Enumerable, Let's loop! | |
renderedContent = that.map(value, function (row) { | |
return that.render(content, that.create_context(row), partials, true); | |
}).join(""); | |
} else if (that.is_object(value)) { // Object, Use it as subcontext! | |
renderedContent = that.render(content, that.create_context(value), | |
partials, true); | |
} else if (typeof value == "function") { | |
// higher order section | |
renderedContent = value.call(context, content, function (text) { | |
return that.render(text, context, partials, true); | |
}); | |
} else if (value) { // boolean section | |
renderedContent = that.render(content, context, partials, true); | |
} else { | |
renderedContent = ""; | |
} | |
} | |
return renderedBefore + renderedContent + renderedAfter; | |
}); | |
}, | |
/* | |
Replace {{foo}} and friends with values from our view | |
*/ | |
render_tags: function (template, context, partials, in_recursion) { | |
// tit for tat | |
var that = this; | |
var new_regex = function () { | |
return that.getCachedRegex("render_tags", function (otag, ctag) { | |
return new RegExp(otag + "(=|!|>|&|\\{|%)?([^#\\^]+?)\\1?" + ctag + "+", "g"); | |
}); | |
}; | |
var regex = new_regex(); | |
var tag_replace_callback = function (match, operator, name) { | |
switch(operator) { | |
case "!": // ignore comments | |
return ""; | |
case "=": // set new delimiters, rebuild the replace regexp | |
that.set_delimiters(name); | |
regex = new_regex(); | |
return ""; | |
case ">": // render partial | |
return that.render_partial(name, context, partials); | |
case "{": // the triple mustache is unescaped | |
case "&": // & operator is an alternative unescape method | |
return that.find(name, context); | |
default: // escape the value | |
return escapeHTML(that.find(name, context)); | |
} | |
}; | |
var lines = template.split("\n"); | |
for(var i = 0; i < lines.length; i++) { | |
lines[i] = lines[i].replace(regex, tag_replace_callback, this); | |
if (!in_recursion) { | |
this.send(lines[i]); | |
} | |
} | |
if (in_recursion) { | |
return lines.join("\n"); | |
} | |
}, | |
set_delimiters: function (delimiters) { | |
var dels = delimiters.split(" "); | |
this.otag = this.escape_regex(dels[0]); | |
this.ctag = this.escape_regex(dels[1]); | |
}, | |
escape_regex: function (text) { | |
// thank you Simon Willison | |
if (!arguments.callee.sRE) { | |
var specials = [ | |
'/', '.', '*', '+', '?', '|', | |
'(', ')', '[', ']', '{', '}', '\\' | |
]; | |
arguments.callee.sRE = new RegExp( | |
'(\\' + specials.join('|\\') + ')', 'g' | |
); | |
} | |
return text.replace(arguments.callee.sRE, '\\$1'); | |
}, | |
/* | |
find `name` in current `context`. That is find me a value | |
from the view object | |
*/ | |
find: function (name, context) { | |
name = trim(name); | |
// Checks whether a value is thruthy or false or 0 | |
function is_kinda_truthy(bool) { | |
return bool === false || bool === 0 || bool; | |
} | |
var value; | |
// check for dot notation eg. foo.bar | |
if (name.match(/([a-z_]+)\./ig)) { | |
var childValue = this.walk_context(name, context); | |
if (is_kinda_truthy(childValue)) { | |
value = childValue; | |
} | |
} else { | |
if (is_kinda_truthy(context[name])) { | |
value = context[name]; | |
} else if (is_kinda_truthy(this.context[name])) { | |
value = this.context[name]; | |
} | |
} | |
if (typeof value == "function") { | |
return value.apply(context); | |
} | |
if (value !== undefined) { | |
return value; | |
} | |
// silently ignore unkown variables | |
return ""; | |
}, | |
walk_context: function (name, context) { | |
var path = name.split('.'); | |
// if the var doesn't exist in current context, check the top level context | |
var value_context = (context[path[0]] != undefined) ? context : this.context; | |
var value = value_context[path.shift()]; | |
while (value != undefined && path.length > 0) { | |
value_context = value; | |
value = value[path.shift()]; | |
} | |
// if the value is a function, call it, binding the correct context | |
if (typeof value == "function") { | |
return value.apply(value_context); | |
} | |
return value; | |
}, | |
// Utility methods | |
/* includes tag */ | |
includes: function (needle, haystack) { | |
return haystack.indexOf(this.otag + needle) != -1; | |
}, | |
// by @langalex, support for arrays of strings | |
create_context: function (_context) { | |
if (this.is_object(_context)) { | |
return _context; | |
} else { | |
var iterator = "."; | |
if (this.pragmas["IMPLICIT-ITERATOR"]) { | |
iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; | |
} | |
var ctx = {}; | |
ctx[iterator] = _context; | |
return ctx; | |
} | |
}, | |
is_object: function (a) { | |
return a && typeof a == "object"; | |
}, | |
/* | |
Why, why, why? Because IE. Cry, cry cry. | |
*/ | |
map: function (array, fn) { | |
if (typeof array.map == "function") { | |
return array.map(fn); | |
} else { | |
var r = []; | |
var l = array.length; | |
for(var i = 0; i < l; i++) { | |
r.push(fn(array[i])); | |
} | |
return r; | |
} | |
}, | |
getCachedRegex: function (name, generator) { | |
var byOtag = regexCache[this.otag]; | |
if (!byOtag) { | |
byOtag = regexCache[this.otag] = {}; | |
} | |
var byCtag = byOtag[this.ctag]; | |
if (!byCtag) { | |
byCtag = byOtag[this.ctag] = {}; | |
} | |
var regex = byCtag[name]; | |
if (!regex) { | |
regex = byCtag[name] = generator(this.otag, this.ctag); | |
} | |
return regex; | |
} | |
}; | |
return({ | |
name: "mustache.js", | |
version: "0.4.0", | |
/* | |
Turns a template and view into HTML | |
*/ | |
to_html: function (template, view, partials, send_fun) { | |
var renderer = new Renderer(); | |
if (send_fun) { | |
renderer.send = send_fun; | |
} | |
renderer.render(template, view || {}, partials); | |
if (!send_fun) { | |
return renderer.buffer.join("\n"); | |
} | |
} | |
}); | |
}(); | |
/*! | |
ICanHaz.js -- by @HenrikJoreteg | |
*/ | |
/*global */ | |
(function () { | |
function trim(stuff) { | |
if (''.trim) return stuff.trim(); | |
else return stuff.replace(/^\s+/, '').replace(/\s+$/, ''); | |
} | |
var ich = { | |
VERSION: "0.10", | |
templates: {}, | |
// grab jquery or zepto if it's there | |
$: (typeof window !== 'undefined') ? window.jQuery || window.Zepto || null : null, | |
// public function for adding templates | |
// can take a name and template string arguments | |
// or can take an object with name/template pairs | |
// We're enforcing uniqueness to avoid accidental template overwrites. | |
// If you want a different template, it should have a different name. | |
addTemplate: function (name, templateString) { | |
if (typeof name === 'object') { | |
for (var template in name) { | |
this.addTemplate(template, name[template]); | |
} | |
return; | |
} | |
if (ich[name]) { | |
console.error("Invalid name: " + name + "."); | |
} else if (ich.templates[name]) { | |
console.error("Template \"" + name + " \" exists"); | |
} else { | |
ich.templates[name] = templateString; | |
ich[name] = function (data, raw) { | |
data = data || {}; | |
var result = Mustache.to_html(ich.templates[name], data, ich.templates); | |
return (ich.$ && !raw) ? ich.$(result) : result; | |
}; | |
} | |
}, | |
// clears all retrieval functions and empties cache | |
clearAll: function () { | |
for (var key in ich.templates) { | |
delete ich[key]; | |
} | |
ich.templates = {}; | |
}, | |
// clears/grabs | |
refresh: function () { | |
ich.clearAll(); | |
ich.grabTemplates(); | |
}, | |
// grabs templates from the DOM and caches them. | |
// Loop through and add templates. | |
// Whitespace at beginning and end of all templates inside <script> tags will | |
// be trimmed. If you want whitespace around a partial, add it in the parent, | |
// not the partial. Or do it explicitly using <br/> or | |
grabTemplates: function () { | |
var i, | |
scripts = document.getElementsByTagName('script'), | |
script, | |
trash = []; | |
for (i = 0, l = scripts.length; i < l; i++) { | |
script = scripts[i]; | |
if (script && script.innerHTML && script.id && (script.type === "text/html" || script.type === "text/x-icanhaz")) { | |
ich.addTemplate(script.id, trim(script.innerHTML)); | |
trash.unshift(script); | |
} | |
} | |
for (i = 0, l = trash.length; i < l; i++) { | |
trash[i].parentNode.removeChild(trash[i]); | |
} | |
} | |
}; | |
// Use CommonJS if applicable | |
if (typeof require !== 'undefined') { | |
module.exports = ich; | |
} else { | |
// else attach it to the window | |
window.ich = ich; | |
} | |
if (typeof document !== 'undefined') { | |
if (ich.$) { | |
ich.$(function () { | |
ich.grabTemplates(); | |
}); | |
} else { | |
document.addEventListener('DOMContentLoaded', function () { | |
ich.grabTemplates(); | |
}, true); | |
} | |
} | |
})(); | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]--> | |
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]--> | |
<!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]--> | |
<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]--> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | |
<title></title> | |
<meta name="description" content=""> | |
<meta name="viewport" content="width=device-width"> | |
<link rel="stylesheet" href="style.css"> | |
<script src="tabletop.js" type="text/javascript"></script> | |
<script src="ICanHaz.js" type="text/javascript"></script> | |
<script src="accounting.js" type="text/javascript"></script> | |
</head> | |
<body> | |
<!--[if lt IE 7]><p class=chromeframe>Your browser is <em>ancient!</em> <a href="http://browsehappy.com/">Upgrade to a different browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">install Google Chrome Frame</a> to experience this site.</p><![endif]--> | |
<div id="table"></div> | |
<div id="stats"></div> | |
<script id="stats" type="text/html"> | |
<h4>{{numberActive}} of {{numberTotalProjects}} projects are active</h4> | |
<h4>{{numberCompletedProjects}} projects are completed</h4> | |
<h4>{{totalSpent}} has been spent as of {{currentDate}} </h4> | |
</script> | |
<script id="test" type="text/html"> | |
<table> | |
{{#rows}} | |
<tr><td>{{project}}</td><td>{{total}}</td></tr> | |
{{/rows}} | |
</table> | |
</script> | |
<script type="text/javascript"> | |
document.addEventListener('DOMContentLoaded', runWebsite) | |
function runWebsite() { | |
var public_spreadsheet_url = 'https://docs.google.com/spreadsheet/pub?key=0Aj3c4mZCQQaMdGE2TVphOWlXMUMyclRXa2Z1c0g5MGc&single=true&gid=1&output=html'; | |
Tabletop.init( { key: public_spreadsheet_url, | |
callback: showInfo, | |
simpleSheet: true } ) | |
function showInfo(data, tabletop) { | |
var numberActive = getActiveProjects(data).length | |
var numberTotalProjects = 14 | |
var numberCompletedProjects = completedProjects(data) | |
var totalSpent = amountSpent(data) | |
var test = ich.test({ | |
"rows": data | |
}) | |
var stats = ich.stats({ | |
"numberActive": numberActive, | |
"numberTotalProjects": numberTotalProjects, | |
"numberCompletedProjects": numberCompletedProjects, | |
"totalSpent": accounting.formatMoney(totalSpent), | |
"currentDate": getCurrentYear() | |
}) | |
document.getElementById('table').innerHTML = test; | |
document.getElementById('stats').innerHTML = stats; | |
} | |
} | |
function getCurrentYear() { | |
return new Date().getFullYear() | |
} | |
function completedProjects(projects) { | |
var completed = 0 | |
projects.forEach(function (project) { | |
if (!hasActiveFuture(project)) completed = completed + 1 | |
}) | |
return completed | |
} | |
function isActive(element) { | |
var currentYear = "year" + getCurrentYear() | |
var dollars = element[currentYear] | |
if (dollars > 0) return true | |
return false | |
} | |
function amountSpent(projects) { | |
var spent = 0 | |
projects.forEach(function (project) { | |
var currentYear = "year" + getCurrentYear() | |
var funds = parseInt(project[currentYear]) | |
if (funds > 0) spent = spent + funds | |
getPreviousYears().forEach(function (year) { | |
var funds = parseInt(project[year]) | |
if (funds > 0) spent = spent + funds | |
}) | |
}) | |
return spent | |
} | |
function isComplete(element) { | |
var currentYear = "year" + getCurrentYear() | |
var dollars = element[currentYear] | |
if (dollars > 0) return true | |
return false | |
} | |
function getPreviousYears() { | |
var currentYear = "year" + getCurrentYear() | |
var years = ["year2012", "year2013", "year2014", "year2015", "year2016", "year2017", "year2018", "year2019"] | |
return years.slice(0, years.indexOf(currentYear)) | |
} | |
function getFutureYears() { | |
var currentYear = "year" + getCurrentYear() | |
var years = ["year2012", "year2013", "year2014", "year2015", "year2016", "year2017", "year2018", "year2019"] | |
return years.slice(years.indexOf(currentYear)) | |
} | |
function hasActiveFuture(element) { | |
var activeFuture = false | |
getFutureYears().forEach(function (year){ | |
if (element[year] > 0) activeFuture = true | |
}) | |
return activeFuture | |
} | |
function getActiveProjects(projects) { | |
var activeProjects = [] | |
projects.forEach(function getActive(element) { | |
if (isActive(element)) activeProjects.push(element) | |
}) | |
return activeProjects | |
} | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* HTML5 Boilerplate */ | |
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } | |
audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } | |
audio:not([controls]) { display: none; } | |
[hidden] { display: none; } | |
html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } | |
html, button, input, select, textarea { font-family: sans-serif; color: #222; } | |
body { margin: 0; font-size: 1em; line-height: 1.4; } | |
::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; } | |
::selection { background: #fe57a1; color: #fff; text-shadow: none; } | |
a { color: #00e; } | |
a:visited { color: #551a8b; } | |
a:hover { color: #06e; } | |
a:focus { outline: thin dotted; } | |
a:hover, a:active { outline: 0; } | |
abbr[title] { border-bottom: 1px dotted; } | |
b, strong { font-weight: bold; } | |
blockquote { margin: 1em 40px; } | |
dfn { font-style: italic; } | |
hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } | |
ins { background: #ff9; color: #000; text-decoration: none; } | |
mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } | |
pre, code, kbd, samp { font-family: monospace, serif; _font-family: 'courier new', monospace; font-size: 1em; } | |
pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } | |
q { quotes: none; } | |
q:before, q:after { content: ""; content: none; } | |
small { font-size: 85%; } | |
sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } | |
sup { top: -0.5em; } | |
sub { bottom: -0.25em; } | |
ul, ol { margin: 1em 0; padding: 0 0 0 40px; } | |
dd { margin: 0 0 0 40px; } | |
nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; } | |
img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; } | |
svg:not(:root) { overflow: hidden; } | |
figure { margin: 0; } | |
form { margin: 0; } | |
fieldset { border: 0; margin: 0; padding: 0; } | |
label { cursor: pointer; } | |
legend { border: 0; *margin-left: -7px; padding: 0; white-space: normal; } | |
button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } | |
button, input { line-height: normal; } | |
button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; } | |
button[disabled], input[disabled] { cursor: default; } | |
input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; *width: 13px; *height: 13px; } | |
input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } | |
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; } | |
button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } | |
textarea { overflow: auto; vertical-align: top; resize: vertical; } | |
input:valid, textarea:valid { } | |
input:invalid, textarea:invalid { background-color: #f0dddd; } | |
table { border-collapse: collapse; border-spacing: 0; } | |
td { vertical-align: top; } | |
.chromeframe { margin: 0.2em 0; background: #ccc; color: black; padding: 0.2em 0; } | |
@media only screen and (min-width: 35em) { | |
} | |
.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; *line-height: 0; } | |
.ir br { display: none; } | |
.hidden { display: none !important; visibility: hidden; } | |
.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } | |
.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } | |
.invisible { visibility: hidden; } | |
.clearfix:before, .clearfix:after { content: ""; display: table; } | |
.clearfix:after { clear: both; } | |
.clearfix { *zoom: 1; } | |
@media print { | |
* { background: transparent !important; color: black !important; box-shadow:none !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } | |
a, a:visited { text-decoration: underline; } | |
a[href]:after { content: " (" attr(href) ")"; } | |
abbr[title]:after { content: " (" attr(title) ")"; } | |
.ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } | |
pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } | |
thead { display: table-header-group; } | |
tr, img { page-break-inside: avoid; } | |
img { max-width: 100% !important; } | |
@page { margin: 0.5cm; } | |
p, h2, h3 { orphans: 3; widows: 3; } | |
h2, h3 { page-break-after: avoid; } | |
} | |
#holder { | |
width: 700px; | |
height: 700px; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(global) { | |
if (!Array.prototype.indexOf) { | |
Array.prototype.indexOf = function (obj, fromIndex) { | |
if (fromIndex == null) { | |
fromIndex = 0; | |
} else if (fromIndex < 0) { | |
fromIndex = Math.max(0, this.length + fromIndex); | |
} | |
for (var i = fromIndex, j = this.length; i < j; i++) { | |
if (this[i] === obj) | |
return i; | |
} | |
return -1; | |
}; | |
} | |
/* | |
Initialize with Tabletop.init( { key: '0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc' } ) | |
OR! | |
Initialize with Tabletop.init( { key: 'https://docs.google.com/spreadsheet/pub?hl=en_US&hl=en_US&key=0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc&output=html&widget=true' } ) | |
OR! | |
Initialize with Tabletop.init('0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc') | |
*/ | |
"use strict"; | |
var Tabletop = global.Tabletop = function(options) { | |
// Make sure Tabletop is being used as a constructor no matter what. | |
if(!this || this === global) { | |
return new Tabletop(options); | |
} | |
if(typeof(options) == 'string') { | |
options = { key : options }; | |
} | |
this.callback = options.callback; | |
this.wanted = options.wanted || []; | |
this.key = options.key; | |
this.simpleSheet = !!options.simpleSheet; | |
this.parseNumbers = !!options.parseNumbers; | |
this.wait = !!options.wait; | |
this.postProcess = options.postProcess; | |
this.debug = !!options.debug; | |
/* Be friendly about what you accept */ | |
if(/key=/.test(this.key)) { | |
this.log("You passed a key as a URL! Attempting to parse."); | |
this.key = this.key.match("key=(.*?)&")[1]; | |
} | |
if(!this.key) { | |
alert("You need to pass Tabletop a key!"); | |
return; | |
} | |
this.log("Initializing with key %s", this.key); | |
this.models = {}; | |
this.model_names = []; | |
this.base_json_url = "https://spreadsheets.google.com/feeds/worksheets/" + this.key + "/public/basic?alt=json-in-script"; | |
if(!this.wait) { | |
this.fetch(); | |
} | |
}; | |
// A global storage for callbacks. | |
Tabletop.callbacks = {}; | |
// Backwards compatibility. | |
Tabletop.init = function(options) { | |
return new Tabletop(options); | |
}; | |
Tabletop.sheets = function() { | |
alert("Times have changed! You'll want to use var tabletop = Tabletop.init(...); tabletop.sheets(...); instead of Tabletop.sheets(...)"); | |
}; | |
Tabletop.prototype = { | |
fetch: function(callback) { | |
if(typeof(callback) !== "undefined") { | |
this.callback = callback; | |
} | |
this.injectScript(this.base_json_url, this.loadSheets); | |
}, | |
/* | |
Insert the URL into the page as a script tag. Once it's loaded the spreadsheet data | |
it triggers the callback. This helps you avoid cross-domain errors | |
http://code.google.com/apis/gdata/samples/spreadsheet_sample.html | |
Let's be plain-Jane and not use jQuery or anything. | |
*/ | |
injectScript: function(url, callback) { | |
var script = document.createElement('script'), | |
self = this, | |
callbackName = 'tt' + (+new Date()) + (Math.floor(Math.random()*100000)); | |
// Create a temp callback which will get removed once it has executed, | |
// this allows multiple instances of Tabletop to coexist. | |
Tabletop.callbacks[ callbackName ] = function () { | |
var args = Array.prototype.slice.call( arguments, 0 ); | |
callback.apply(self, args); | |
script.parentNode.removeChild(script); | |
delete Tabletop.callbacks[callbackName]; | |
}; | |
url = url + "&callback=" + 'Tabletop.callbacks.' + callbackName; | |
script.src = url; | |
document.getElementsByTagName('script')[0].parentNode.appendChild(script); | |
}, | |
/* | |
Is this a sheet you want to pull? | |
If { wanted: ["Sheet1"] } has been specified, only Sheet1 is imported | |
Pulls all sheets if none are specified | |
*/ | |
isWanted: function(sheetName) { | |
if(this.wanted.length === 0) { | |
return true; | |
} else { | |
return this.wanted.indexOf(sheetName) != -1; | |
} | |
}, | |
/* | |
What gets send to the callback | |
if simpleSheet == true, then don't return an array of Tabletop.this.models, | |
only return the first one's elements | |
*/ | |
data: function() { | |
// If the instance is being queried before the data's been fetched | |
// then return undefined. | |
if(this.model_names.length === 0) { | |
return undefined; | |
} | |
if(this.simpleSheet) { | |
if(this.model_names.length > 1 && this.debug) | |
console.debug("WARNING You have more than one sheet but are using simple sheet mode! Don't blame me when something goes wrong."); | |
return this.models[ this.model_names[0] ].all(); | |
} else { | |
return this.models; | |
} | |
}, | |
/* | |
Add another sheet to the wanted list | |
*/ | |
addWanted: function(sheet) { | |
if(this.wanted.indexOf(sheet) == -1) { | |
this.wanted.push(sheet) | |
} | |
}, | |
/* | |
Load all worksheets of the spreadsheet, turning each into a Tabletop Model. | |
Need to use injectScript because the worksheet view that you're working from | |
doesn't actually include the data. The list-based feed (/feeds/list/key..) does, though. | |
Calls back to loadSheet in order to get the real work done. | |
Used as a callback for the worksheet-based JSON | |
*/ | |
loadSheets: function(data) { | |
var i, ilen; | |
var toInject = []; | |
for(i = 0, ilen = data.feed.entry.length; i < ilen ; i++) { | |
// Only pull in desired sheets to reduce loading | |
if( this.isWanted(data.feed.entry[i].content.$t) ) { | |
var sheet_id = data.feed.entry[i].link[3].href.substr( data.feed.entry[i].link[3].href.length - 3, 3); | |
var json_url = "https://spreadsheets.google.com/feeds/list/" + this.key + "/" + sheet_id + "/public/values?alt=json-in-script"; | |
toInject.push(json_url); | |
} | |
} | |
this.sheetsToLoad = toInject.length; | |
for(i = 0, ilen = toInject.length; i < ilen; i++) { | |
this.injectScript(toInject[i], this.loadSheet); | |
} | |
}, | |
/* | |
Access layer for the this.models | |
.sheets() gets you all of the sheets | |
.sheets('Sheet1') gets you the sheet named Sheet1 | |
*/ | |
sheets: function(sheetName) { | |
if(typeof sheetName === "undefined") | |
return this.models; | |
else | |
if(typeof(this.models[ sheetName ]) === "undefined") { | |
// alert( "Can't find " + sheetName ); | |
return; | |
} else { | |
return this.models[ sheetName ]; | |
} | |
}, | |
/* | |
Parse a single list-based worksheet, turning it into a Tabletop Model | |
Used as a callback for the list-based JSON | |
*/ | |
loadSheet: function(data) { | |
var model = new Tabletop.Model( { data: data, | |
parseNumbers: this.parseNumbers, | |
postProcess: this.postProcess } ); | |
this.models[ model.name ] = model; | |
if(this.model_names.indexOf(model.name) == -1) { | |
this.model_names.push(model.name); | |
} | |
this.sheetsToLoad--; | |
if(this.sheetsToLoad === 0) | |
this.doCallback(); | |
}, | |
/* | |
Execute the callback upon loading! Rely on this.data() because you might | |
only request certain pieces of data (i.e. simpleSheet mode) | |
Tests this.sheetsToLoad just in case a race condition happens to show up | |
*/ | |
doCallback: function() { | |
if(this.sheetsToLoad === 0) | |
this.callback(this.data(), this); | |
}, | |
log: function(msg) { | |
if(this.debug) { | |
if(typeof console !== "undefined" && typeof console.log !== "undefined") { | |
console.log(msg) | |
} | |
} | |
} | |
}; | |
/* | |
Tabletop.Model stores the attribute names and parses the worksheet data | |
to turn it into something worthwhile | |
Options should be in the format { data: XXX }, with XXX being the list-based worksheet | |
*/ | |
Tabletop.Model = function(options) { | |
var i, j, ilen, jlen; | |
this.column_names = []; | |
this.name = options.data.feed.title.$t; | |
this.elements = []; | |
this.raw = options.data; // A copy of the sheet's raw data, for accessing minutiae | |
for(var key in options.data.feed.entry[0]){ | |
if(/^gsx/.test(key)) | |
this.column_names.push( key.replace("gsx$","") ); | |
} | |
for(i = 0, ilen = options.data.feed.entry.length ; i < ilen; i++) { | |
var source = options.data.feed.entry[i]; | |
var element = {}; | |
for(var j = 0, jlen = this.column_names.length; j < jlen ; j++) { | |
var cell = source[ "gsx$" + this.column_names[j] ]; | |
if(options.parseNumbers && cell.$t !== '' && !isNaN(cell.$t)) | |
element[ this.column_names[j] ] = +cell.$t; | |
else | |
element[ this.column_names[j] ] = cell.$t; | |
} | |
if(element.rowNumber === undefined) | |
element.rowNumber = i + 1; | |
if( options.postProcess ) | |
options.postProcess(element); | |
this.elements.push(element); | |
} | |
}; | |
Tabletop.Model.prototype = { | |
/* | |
Returns all of the elements (rows) of the worksheet as objects | |
*/ | |
all: function() { | |
return this.elements; | |
}, | |
/* | |
Return the elements as an array of arrays, instead of an array of objects | |
*/ | |
toArray: function() { | |
var array = [], | |
i, j, ilen, jlen; | |
for(i = 0, ilen = this.elements.length; i < ilen; i++) { | |
var row = []; | |
for(j = 0, jlen = this.column_names.length; j < jlen ; j++) { | |
row.push( this.elements[i][ this.column_names[j] ] ); | |
} | |
array.push(row); | |
} | |
return array; | |
} | |
}; | |
})(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment