diff --git a/.gitignore b/.gitignore index 58b805fe..646ac519 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ .DS_Store -node_modules/ \ No newline at end of file +node_modules/ diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 83ba5f8b..00000000 --- a/.npmignore +++ /dev/null @@ -1,5 +0,0 @@ -css/ -img/ -dist/ -demo.html -index.html \ No newline at end of file diff --git a/bower.json b/bower.json new file mode 100644 index 00000000..26d9145a --- /dev/null +++ b/bower.json @@ -0,0 +1,26 @@ +{ + "name": "sheetsee", + "version": "1.0.0", + "main": [ + "js/sheetsee.js", + "css/sss.css" + ], + "ignore": [ + "contributing.md", + "docs", + "demos", + "img", + "site" + ], + "homepage": "http://jlord.github.io/sheetsee.js", + "description": "Sheetsee.js is a library for easily creating tables, charts and maps from spreadsheet data.", + "keywords": [ + "spreadsheet", + "tables", + "maps" + ], + "dependencies": { + "jquery": ">= 1.9.0", + "tabletop": ">= 1.3.5" + } +} diff --git a/buildpage.js b/buildpage.js index 4e171ff2..c6642dde 100644 --- a/buildpage.js +++ b/buildpage.js @@ -42,9 +42,11 @@ function applyTemplate(html, name) { if (name === "index") { content.rootstyle = "." content.rootdoc = "docs" + content.rootdemo = "." } else { content.rootstyle = ".." content.rootdoc = "." + content.rootdemo = ".." } var file = "template.hbs" var rawTemplate = fs.readFileSync(file).toString() diff --git a/contributing.md b/contributing.md new file mode 100644 index 00000000..3296973c --- /dev/null +++ b/contributing.md @@ -0,0 +1,17 @@ +# Contributing to Sheetsee.js + +This repository (github/jlord/sheetsee.js) is primarily for documentation and the documentation website. It also contains a copy of a full sheetsee.js (one that includes the map, charts and table portions). + +### Use this repository to file issues and pull requests on documentation/documentation site. + +The documentation is contained in the `/docs` and `/demos` folders in the root. I build the site (on the `gh-pages` branch) from these files so **submit pull requests against the files in `/docs` and `/demos`** and not the `/site` folder or the `gh-pages` branch. + +### For issues and pull requests on the JavaScript, use the repository for the affected portion: + +- [sheetsee](http://www.github.com/jlord/sheetsee/issues/new) +- [sheetsee-core](http://www.github.com/jlord/sheetsee-core/issues/new) +- [sheetsee-tables](http://www.github.com/jlord/sheetsee-tables/issues/new) +- [sheetsee-maps](http://www.github.com/jlord/sheetsee-maps/issues/new) +- [sheetsee-charts](http://www.github.com/jlord/sheetsee-charts/issues/new) + +Thank you much! :heart: :octocat: diff --git a/demos/demo-chart.html b/demos/demo-chart.html index cc94c234..93fcd577 100644 --- a/demos/demo-chart.html +++ b/demos/demo-chart.html @@ -5,7 +5,8 @@ Sheetsee Chart Demo - + + @@ -46,9 +47,9 @@

Ideas

Demos

Use

-

Home

+

Home Page

diff --git a/demos/demo-map.html b/demos/demo-map.html index b5489c66..0be42924 100644 --- a/demos/demo-map.html +++ b/demos/demo-map.html @@ -5,13 +5,14 @@ Sheetsee Maps Demo - + + +#Pagination {background: #eee;} +.pagination-next, .pagination-pre {cursor: hand;} +.no-pag {color: #acacac;} ``` ## Table Filter/Search @@ -124,9 +84,77 @@ Sheetsee.initiateTableFilter(tableOptions) It will connect that input to your data as well as inject this HTML for a button, which you can style yourself in your CSS: + ```HTML Clear no matches ``` +## Example + +_HTML_ + +```HTML +
+ +``` + +_Template_ + +```JavaScript + +``` + +_JavaScript_ + +```javascript + +``` + +To create another table, simply repeat the steps above (abreviated here below). + +_HTML_ +```HTML +
+ +``` +_Template_ + +```JavaScript + +``` + +_JavaScript_ + +```JavaScript + +``` + +Learn more about the things you can do with [ICanHaz.js](http://icanhazjs.com). + _[View Demo](/demos/demo-table.html)_ diff --git a/js/sheetsee.js b/js/sheetsee.js index 72f45e90..359e15cb 100644 --- a/js/sheetsee.js +++ b/js/sheetsee.js @@ -1,772 +1,560 @@ ;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ -var baseCreateCallback = require('lodash._basecreatecallback'), - keys = require('lodash.keys'), - objectTypes = require('lodash._objecttypes'); - -/** - * Assigns own enumerable properties of source object(s) to the destination - * object. Subsequent sources will overwrite property assignments of previous - * sources. If a callback is provided it will be executed to produce the - * assigned values. The callback is bound to `thisArg` and invoked with two - * arguments; (objectValue, sourceValue). - * - * @static - * @memberOf _ - * @type Function - * @alias extend - * @category Objects - * @param {Object} object The destination object. - * @param {...Object} [source] The source objects. - * @param {Function} [callback] The function to customize assigning values. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns the destination object. - * @example - * - * _.assign({ 'name': 'moe' }, { 'age': 40 }); - * // => { 'name': 'moe', 'age': 40 } - * - * var defaults = _.partialRight(_.assign, function(a, b) { - * return typeof a == 'undefined' ? b : a; - * }); - * - * var food = { 'name': 'apple' }; - * defaults(food, { 'name': 'banana', 'type': 'fruit' }); - * // => { 'name': 'apple', 'type': 'fruit' } - */ -var assign = function(object, source, guard) { - var index, iterable = object, result = iterable; - if (!iterable) return result; - var args = arguments, - argsIndex = 0, - argsLength = typeof guard == 'number' ? 2 : args.length; - if (argsLength > 3 && typeof args[argsLength - 2] == 'function') { - var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2); - } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') { - callback = args[--argsLength]; - } - while (++argsIndex < argsLength) { - iterable = args[argsIndex]; - if (iterable && objectTypes[typeof iterable]) { - var ownIndex = -1, - ownProps = objectTypes[typeof iterable] && keys(iterable), - length = ownProps ? ownProps.length : 0; - - while (++ownIndex < length) { - index = ownProps[ownIndex]; - result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]; - } - } - } - return result -}; +if (typeof window.Sheetsee === 'undefined') window.Sheetsee = {}; window.Sheetsee = require('sheetsee-core'); var extend = require('lodash.assign'); extend(window.Sheetsee, require('sheetsee-maps'), require('sheetsee-charts'), require('sheetsee-tables')); module.exports = Sheetsee; +},{"lodash.assign":3,"sheetsee-charts":28,"sheetsee-core":31,"sheetsee-maps":32,"sheetsee-tables":59}],2:[function(require,module,exports){ +/*! +ICanHaz.js version 0.10.2 -- by @HenrikJoreteg +More info at: http://icanhazjs.com +*/ +(function () { +/* + mustache.js — Logic-less templates in JavaScript -module.exports = assign; + See http://mustache.github.com/ for more info. +*/ -},{"lodash._basecreatecallback":3,"lodash._objecttypes":22,"lodash.keys":23}],3:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ -var bind = require('lodash.bind'), - identity = require('lodash.identity'), - setBindData = require('lodash._setbinddata'), - support = require('lodash.support'); +var Mustache = function () { + var _toString = Object.prototype.toString; -/** Used to detected named functions */ -var reFuncName = /^function[ \n\r\t]+\w/; + Array.isArray = Array.isArray || function (obj) { + return _toString.call(obj) == "[object Array]"; + } -/** Used to detect functions containing a `this` reference */ -var reThis = /\bthis\b/; + var _trim = String.prototype.trim, trim; -/** Native method shortcuts */ -var fnToString = Function.prototype.toString; + if (_trim) { + trim = function (text) { + return text == null ? "" : _trim.call(text); + } + } else { + var trimLeft, trimRight; -/** - * The base implementation of `_.createCallback` without support for creating - * "_.pluck" or "_.where" style callbacks. - * - * @private - * @param {*} [func=identity] The value to convert to a callback. - * @param {*} [thisArg] The `this` binding of the created callback. - * @param {number} [argCount] The number of arguments the callback accepts. - * @returns {Function} Returns a callback function. - */ -function baseCreateCallback(func, thisArg, argCount) { - if (typeof func != 'function') { - return identity; - } - // exit early if there is no `thisArg` - if (typeof thisArg == 'undefined') { - return func; - } - var bindData = func.__bindData__ || (support.funcNames && !func.name); - if (typeof bindData == 'undefined') { - var source = reThis && fnToString.call(func); - if (!support.funcNames && source && !reFuncName.test(source)) { - bindData = true; + // IE doesn't match non-breaking spaces with \s. + if ((/\S/).test("\xA0")) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; + } else { + trimLeft = /^\s+/; + trimRight = /\s+$/; } - if (support.funcNames || !bindData) { - // checks if `func` references the `this` keyword and stores the result - bindData = !support.funcDecomp || reThis.test(source); - setBindData(func, bindData); + + trim = function (text) { + return text == null ? "" : + text.toString().replace(trimLeft, "").replace(trimRight, ""); } } - // exit early if there are no `this` references or `func` is bound - if (bindData !== true && (bindData && bindData[1] & 1)) { - return func; - } - switch (argCount) { - case 1: return function(value) { - return func.call(thisArg, value); - }; - case 2: return function(a, b) { - return func.call(thisArg, a, b); - }; - case 3: return function(value, index, collection) { - return func.call(thisArg, value, index, collection); - }; - case 4: return function(accumulator, value, index, collection) { - return func.call(thisArg, accumulator, value, index, collection); - }; - } - return bind(func, thisArg); -} -module.exports = baseCreateCallback; + var escapeMap = { + "&": "&", + "<": "<", + ">": ">", + '"': '"', + "'": ''' + }; -},{"lodash._setbinddata":4,"lodash.bind":12,"lodash.identity":19,"lodash.support":20}],4:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ -var getObject = require('lodash._getobject'), - noop = require('lodash._noop'), - reNative = require('lodash._renative'), - releaseObject = require('lodash._releaseobject'); + function escapeHTML(string) { + return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) { + return escapeMap[s] || s; + }); + } -/** Used for native method references */ -var objectProto = Object.prototype; + var regexCache = {}; + var Renderer = function () {}; -var defineProperty = (function() { - try { - var o = {}, - func = reNative.test(func = Object.defineProperty) && func, - result = func(o, o, o) && func; - } catch(e) { } - return result; -}()); + Renderer.prototype = { + otag: "{{", + ctag: "}}", + pragmas: {}, + buffer: [], + pragmas_implemented: { + "IMPLICIT-ITERATOR": true + }, + context: {}, -/** - * Sets `this` binding data on a given function. - * - * @private - * @param {Function} func The function to set data on. - * @param {*} value The value to set. - */ -var setBindData = !defineProperty ? noop : function(func, value) { - var descriptor = getObject(); - descriptor.value = value; - defineProperty(func, '__bindData__', descriptor); - releaseObject(descriptor); -}; + render: function (template, context, partials, in_recursion) { + // reset buffer & set context + if (!in_recursion) { + this.context = context; + this.buffer = []; // TODO: make this non-lazy + } -module.exports = setBindData; + // fail fast + if (!this.includes("", template)) { + if (in_recursion) { + return template; + } else { + this.send(template); + return; + } + } -},{"lodash._getobject":5,"lodash._noop":7,"lodash._releaseobject":8,"lodash._renative":11}],5:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ -var objectPool = require('lodash._objectpool'); + // get the pragmas together + template = this.render_pragmas(template); -/** - * Gets an object from the object pool or creates a new one if the pool is empty. - * - * @private - * @returns {Object} The object from the pool. - */ -function getObject() { - return objectPool.pop() || { - 'array': null, - 'cache': null, - 'configurable': false, - 'criteria': null, - 'enumerable': false, - 'false': false, - 'index': 0, - 'leading': false, - 'maxWait': 0, - 'null': false, - 'number': null, - 'object': null, - 'push': null, - 'string': null, - 'trailing': false, - 'true': false, - 'undefined': false, - 'value': null, - 'writable': false - }; -} + // render the template + var html = this.render_section(template, context, partials); -module.exports = getObject; + // 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); + } -},{"lodash._objectpool":6}],6:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ + if (in_recursion) { + return html; + } else { + this.sendLines(html); + } + }, -/** Used to pool arrays and objects used internally */ -var objectPool = []; + /* + Sends parsed lines + */ + send: function (line) { + if (line !== "") { + this.buffer.push(line); + } + }, -module.exports = objectPool; + sendLines: function (text) { + if (text) { + var lines = text.split("\n"); + for (var i = 0; i < lines.length; i++) { + this.send(lines[i]); + } + } + }, -},{}],7:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ + /* + Looks for %PRAGMAS + */ + render_pragmas: function (template) { + // no pragmas + if (!this.includes("%", template)) { + return template; + } -/** - * A no-operation function. - * - * @private - */ -function noop() { - // no operation performed -} + var that = this; + var regex = this.getCachedRegex("render_pragmas", function (otag, ctag) { + return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g"); + }); -module.exports = noop; + 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 + }); + }, -},{}],8:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ -var maxPoolSize = require('lodash._maxpoolsize'), - objectPool = require('lodash._objectpool'); + /* + 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); + }, -/** - * Releases the given object back to the object pool. - * - * @private - * @param {Object} [object] The object to release. - */ -function releaseObject(object) { - var cache = object.cache; - if (cache) { - releaseObject(cache); - } - object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null; - if (objectPool.length < maxPoolSize) { - objectPool.push(object); - } -} + /* + 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; + } -module.exports = releaseObject; + var that = this; -},{"lodash._maxpoolsize":9,"lodash._objectpool":10}],9:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ + 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) -/** Used as the max size of the `arrayPool` and `objectPool` */ -var maxPoolSize = 40; + otag + // {{ + "(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3) + ctag + // }} -module.exports = maxPoolSize; + "\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped -},{}],10:[function(require,module,exports){ -module.exports=require(6) -},{}],11:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ + otag + // {{ + "\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag). + ctag + // }} -/** Used for native method references */ -var objectProto = Object.prototype; + "\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped. -/** Used to detect if a method is native */ -var reNative = RegExp('^' + - String(objectProto.valueOf) - .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - .replace(/valueOf|for [^\]]+/g, '.+?') + '$' -); + "g"); + }); -module.exports = reNative; -},{}],12:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ -var createBound = require('lodash._createbound'), - reNative = require('lodash._renative'); + // 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) : "", -/** - * Used for `Array` method references. - * - * Normally `Array.prototype` would suffice, however, using an array literal - * avoids issues in Narwhal. - */ -var arrayRef = []; + // after may contain both sections and tags, so use full rendering function + renderedAfter = after ? that.render(after, context, partials, true) : "", -/* Native method shortcuts for methods with the same name as other `lodash` methods */ -var nativeSlice = arrayRef.slice; + // will be computed below + renderedContent, -/** - * Creates a function that, when called, invokes `func` with the `this` - * binding of `thisArg` and prepends any additional `bind` arguments to those - * provided to the bound function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to bind. - * @param {*} [thisArg] The `this` binding of `func`. - * @param {...*} [arg] Arguments to be partially applied. - * @returns {Function} Returns the new bound function. - * @example - * - * var func = function(greeting) { - * return greeting + ' ' + this.name; - * }; - * - * func = _.bind(func, { 'name': 'moe' }, 'hi'); - * func(); - * // => 'hi moe' - */ -function bind(func, thisArg) { - return arguments.length > 2 - ? createBound(func, 17, nativeSlice.call(arguments, 2), null, thisArg) - : createBound(func, 1, null, null, thisArg); -} - -module.exports = bind; - -},{"lodash._createbound":13,"lodash._renative":18}],13:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ -var createObject = require('lodash._createobject'), - isFunction = require('lodash.isfunction'), - isObject = require('lodash.isobject'), - reNative = require('lodash._renative'), - setBindData = require('lodash._setbinddata'), - support = require('lodash.support'); - -/** - * Used for `Array` method references. - * - * Normally `Array.prototype` would suffice, however, using an array literal - * avoids issues in Narwhal. - */ -var arrayRef = []; - -/** Used for native method references */ -var objectProto = Object.prototype; + value = that.find(name, context); -/** Native method shortcuts */ -var push = arrayRef.push, - toString = objectProto.toString, - unshift = arrayRef.unshift; + 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 = ""; + } + } -/* Native method shortcuts for methods with the same name as other `lodash` methods */ -var nativeBind = reNative.test(nativeBind = toString.bind) && nativeBind, - nativeSlice = arrayRef.slice; + return renderedBefore + renderedContent + renderedAfter; + }); + }, -/** - * Creates a function that, when called, either curries or invokes `func` - * with an optional `this` binding and partially applied arguments. - * - * @private - * @param {Function|string} func The function or method name to reference. - * @param {number} bitmask The bitmask of method flags to compose. - * The bitmask may be composed of the following flags: - * 1 - `_.bind` - * 2 - `_.bindKey` - * 4 - `_.curry` - * 8 - `_.curry` (bound) - * 16 - `_.partial` - * 32 - `_.partialRight` - * @param {Array} [partialArgs] An array of arguments to prepend to those - * provided to the new function. - * @param {Array} [partialRightArgs] An array of arguments to append to those - * provided to the new function. - * @param {*} [thisArg] The `this` binding of `func`. - * @param {number} [arity] The arity of `func`. - * @returns {Function} Returns the new bound function. - */ -function createBound(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) { - var isBind = bitmask & 1, - isBindKey = bitmask & 2, - isCurry = bitmask & 4, - isCurryBound = bitmask & 8, - isPartial = bitmask & 16, - isPartialRight = bitmask & 32, - key = func; + /* + Replace {{foo}} and friends with values from our view + */ + render_tags: function (template, context, partials, in_recursion) { + // tit for tat + var that = this; - if (!isBindKey && !isFunction(func)) { - throw new TypeError; - } - if (isPartial && !partialArgs.length) { - bitmask &= ~16; - isPartial = partialArgs = false; - } - if (isPartialRight && !partialRightArgs.length) { - bitmask &= ~32; - isPartialRight = partialRightArgs = false; - } - var bindData = func && func.__bindData__; - if (bindData) { - if (isBind && !(bindData[1] & 1)) { - bindData[4] = thisArg; - } - if (!isBind && bindData[1] & 1) { - bitmask |= 8; - } - if (isCurry && !(bindData[1] & 4)) { - bindData[5] = arity; - } - if (isPartial) { - push.apply(bindData[2] || (bindData[2] = []), partialArgs); - } - if (isPartialRight) { - push.apply(bindData[3] || (bindData[3] = []), partialRightArgs); - } - bindData[1] |= bitmask; - return createBound.apply(null, bindData); - } - // use `Function#bind` if it exists and is fast - // (in V8 `Function#bind` is slower except when partially applied) - if (isBind && !(isBindKey || isCurry || isPartialRight) && - (support.fastBind || (nativeBind && isPartial))) { - if (isPartial) { - var args = [thisArg]; - push.apply(args, partialArgs); - } - var bound = isPartial - ? nativeBind.apply(func, args) - : nativeBind.call(func, thisArg); - } - else { - bound = function() { - // `Function#bind` spec - // http://es5.github.io/#x15.3.4.5 - var args = arguments, - thisBinding = isBind ? thisArg : this; + var new_regex = function () { + return that.getCachedRegex("render_tags", function (otag, ctag) { + return new RegExp(otag + "(=|!|>|&|\\{|%)?([^#\\^]+?)\\1?" + ctag + "+", "g"); + }); + }; - if (isCurry || isPartial || isPartialRight) { - args = nativeSlice.call(args); - if (isPartial) { - unshift.apply(args, partialArgs); - } - if (isPartialRight) { - push.apply(args, partialRightArgs); + 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)); } - if (isCurry && args.length < arity) { - bitmask |= 16 & ~32; - return createBound(func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity); + }; + 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 (isBindKey) { - func = thisBinding[key]; - } - if (this instanceof bound) { - // ensure `new bound` is an instance of `func` - thisBinding = createObject(func.prototype); - // mimic the constructor's `return` behavior - // http://es5.github.io/#x13.2.2 - var result = func.apply(thisBinding, args); - return isObject(result) ? result : thisBinding; + if (in_recursion) { + return lines.join("\n"); } - return func.apply(thisBinding, args); - }; - } - setBindData(bound, nativeSlice.call(arguments)); - return bound; -} + }, -module.exports = createBound; + set_delimiters: function (delimiters) { + var dels = delimiters.split(" "); + this.otag = this.escape_regex(dels[0]); + this.ctag = this.escape_regex(dels[1]); + }, -},{"lodash._createobject":14,"lodash._renative":18,"lodash._setbinddata":4,"lodash.isfunction":16,"lodash.isobject":17,"lodash.support":20}],14:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ -var isObject = require('lodash.isobject'), - noop = require('lodash._noop'), - reNative = require('lodash._renative'); + 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'); + }, -/** Used for native method references */ -var objectProto = Object.prototype; + /* + find `name` in current `context`. That is find me a value + from the view object + */ + find: function (name, context) { + name = trim(name); -/* Native method shortcuts for methods with the same name as other `lodash` methods */ -var nativeCreate = reNative.test(nativeCreate = Object.create) && nativeCreate; + // Checks whether a value is thruthy or false or 0 + function is_kinda_truthy(bool) { + return bool === false || bool === 0 || bool; + } -/** - * Creates a new object with the specified `prototype`. - * - * @private - * @param {Object} prototype The prototype object. - * @returns {Object} Returns the new object. - */ -function createObject(prototype) { - return isObject(prototype) ? nativeCreate(prototype) : {}; -} + var value; -module.exports = createObject; + // 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]; + } + } -},{"lodash._noop":15,"lodash._renative":18,"lodash.isobject":17}],15:[function(require,module,exports){ -module.exports=require(7) -},{}],16:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ + if (typeof value == "function") { + return value.apply(context); + } + if (value !== undefined) { + return value; + } + // silently ignore unkown variables + return ""; + }, -/** - * Checks if `value` is a function. - * - * @static - * @memberOf _ - * @category Objects - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is a function, else `false`. - * @example - * - * _.isFunction(_); - * // => true - */ -function isFunction(value) { - return typeof value == 'function'; -} + 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; + }, -module.exports = isFunction; + // Utility methods -},{}],17:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ -var objectTypes = require('lodash._objecttypes'); + /* includes tag */ + includes: function (needle, haystack) { + return haystack.indexOf(this.otag + needle) != -1; + }, -/** - * Checks if `value` is the language type of Object. - * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @category Objects - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(1); - * // => false - */ -function isObject(value) { - // check if the value is the ECMAScript language type of Object - // http://es5.github.io/#x8 - // and avoid a V8 bug - // http://code.google.com/p/v8/issues/detail?id=2291 - return !!(value && objectTypes[typeof value]); -} + // 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; + } + }, -module.exports = isObject; + is_object: function (a) { + return a && typeof a == "object"; + }, -},{"lodash._objecttypes":22}],18:[function(require,module,exports){ -module.exports=require(11) -},{}],19:[function(require,module,exports){ -/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ + /* + 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; + } + }, -/** - * This method returns the first argument provided to it. - * - * @static - * @memberOf _ - * @category Utilities - * @param {*} value Any value. - * @returns {*} Returns `value`. - * @example - * - * var moe = { 'name': 'moe' }; - * moe === _.identity(moe); - * // => true - */ -function identity(value) { - return value; -} + getCachedRegex: function (name, generator) { + var byOtag = regexCache[this.otag]; + if (!byOtag) { + byOtag = regexCache[this.otag] = {}; + } -module.exports = identity; + var byCtag = byOtag[this.ctag]; + if (!byCtag) { + byCtag = byOtag[this.ctag] = {}; + } -},{}],20:[function(require,module,exports){ -var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/** - * Lo-Dash 2.1.0 (Custom Build) - * Build: `lodash modularize modern exports="npm" -o ./npm` - * Copyright 2012-2013 The Dojo Foundation - * Based on Underscore.js 1.5.2 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ -var reNative = require('lodash._renative'); + var regex = byCtag[name]; + if (!regex) { + regex = byCtag[name] = generator(this.otag, this.ctag); + } -/** Used to detect functions containing a `this` reference */ -var reThis = /\bthis\b/; + return regex; + } + }; -/** Used for native method references */ -var objectProto = Object.prototype; + return({ + name: "mustache.js", + version: "0.4.0", -/** Native method shortcuts */ -var toString = objectProto.toString; + /* + 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+$/, ''); + } -/* Native method shortcuts for methods with the same name as other `lodash` methods */ -var nativeBind = reNative.test(nativeBind = toString.bind) && nativeBind; + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; -/** Detect various environments */ -var isIeOpera = reNative.test(global.attachEvent), - isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera); + var ich = { + VERSION: "0.10.2", + templates: {}, -/** - * An object used to flag environments features. - * - * @static - * @memberOf _ - * @type Object - */ -var support = {}; + // grab jquery or zepto if it's there + $: (typeof window !== 'undefined') ? window.jQuery || window.Zepto || null : null, -/** - * Detect if `Function#bind` exists and is inferred to be fast (all but V8). - * - * @memberOf _.support - * @type boolean - */ -support.fastBind = nativeBind && !isV8; + // 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.$(trim(result)) : result; + }; + } + }, -/** - * Detect if functions can be decompiled by `Function#toString` - * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps). - * - * @memberOf _.support - * @type boolean - */ -support.funcDecomp = !reNative.test(global.WinRTError) && reThis.test(function() { return this; }); + // clears all retrieval functions and empties cache + clearAll: function () { + for (var key in ich.templates) { + delete ich[key]; + } + ich.templates = {}; + }, -/** - * Detect if `Function#name` is supported (all but IE). - * - * @memberOf _.support - * @type boolean - */ -support.funcNames = typeof Function.name == 'string'; + // clears/grabs + refresh: function () { + ich.clearAll(); + ich.grabTemplates(); + }, -module.exports = support; + // grabs templates from the DOM and caches them. + // Loop through and add templates. + // Whitespace at beginning and end of all templates inside + + + + + + + + + +
+

Pennies by State

+

spreadsheet

+
+

View Source // View Documentation

+ + +
+ + + + + diff --git a/site/demos/demo-map.html b/site/demos/demo-map.html new file mode 100644 index 00000000..0be42924 --- /dev/null +++ b/site/demos/demo-map.html @@ -0,0 +1,95 @@ + + + + + Sheetsee Maps Demo + + + + + + + + + + +
+

All Pennies Map

+

spreadsheet

+

+

View Source // View Documentation

+

Using linestrings, polygons and multipolygons

+
+

View Source // View Spreadsheet

+ + + + + + diff --git a/site/demos/demo-table.html b/site/demos/demo-table.html new file mode 100644 index 00000000..92c717df --- /dev/null +++ b/site/demos/demo-table.html @@ -0,0 +1,124 @@ + + + + + Sheetsee Table Demo + + + + + + + + + +
+

All Pennies

+

spreadsheet

+ +

+

California Pennies

+ +
+

Pretty Pennies

+
+

View Source // View Documentation

+ + +
+ + + + + + + + + + + diff --git a/site/docs/about.html b/site/docs/about.html new file mode 100644 index 00000000..09d3a4fc --- /dev/null +++ b/site/docs/about.html @@ -0,0 +1,88 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

About

+

Sheetsee.js began as a part of my Code for America 2012 Fellowship project, See Penny Work. The idea and original code was to enable cities to easily publish and maintain themselves their budget data. The original sheetsee.js was built into Wordpress templates so that with the See Penny Work template, you could create pages that you only had to name and they would be populated with maps, charts and tables based on the page name corelating with a project in the spreadsheet.

+

In early 2013, after the CfA Fellowship, I recieved a grant from Mozilla Open News to pull out the sheetsee.js bits and make it a standalone open source library. That brought us to version 2.

+

The present version makes the project modular, customizable and with more maping and table features.

+

Built on top of Tabletop.js

+

Sheetsee would not exist were it not for tabletop.js a library that handles the messy interactions with the Google Spreadsheets API for you and returns a lovely array of your data. Every instance of Sheetsee begins with running tabletop.js.

+

Sheetsee.js + Mapbox.js + d3.js

+

Once you've got the data, the meat of Sheetsee comes into play. You can now decide if you want to map, chart or display your data in a table. Sheetsee's table module, sheetsee-tables, comes with sorting, filtering and pagination. Sheetsee-maps is built ontop of Leaflet.js and Mapbox.js and allows you to customize colors and popups of points, lines, polygons or multipolygons. Finally, Sheetee-charts comes with three basic d3.js charts: bar, circle and line. It is difficult to make a chart that can suit many types of data, but it is easy to choose your own d3 chart and plug it in to sheetsee. Documentation for creating a d3 module is here.

+

Hacked on Openly

+ +

Contact

+ +

Big Time Thanks

+

Thanks to Code for America for providing the platform me to build the first version of sheetsee.js for Macon, Georgia.

+

Thanks to Dan Sinker at Open News for having faith and getting things together to make this Code Sprint happen and thanks to Matt Green at WBEZ for being a willing partner.

+

Thanks to Max Ogden for emotional support, teaching me JavaScript and answering lots of questions.

+

Thanks to all the authors and contributors to Tabletop.js, Mapbox.js, Leaflet.js, jQuery, ICanHas.js and d3.js. Thanks to Google and the Internet for existing and to all those who've written tutorials or asked or answered a question on StackOverflow.

+

Thanks to Mom and Dad for getting a computer in 1996 and the mIRC scripts I started writing that I suppose would eventually lead me here.

+ + + +
+ + + diff --git a/site/docs/basics.html b/site/docs/basics.html new file mode 100644 index 00000000..5d4e461e --- /dev/null +++ b/site/docs/basics.html @@ -0,0 +1,125 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

Spreadsheets as Databases

+

Spreadsheets are a great lightweight databases. Google Spreadsheets in particular are easy to work with and share, making this unlike most traditional database setups. That being said, traditional databases are great for bigger, more secure jobs. If you're storing lots and lots and lots of information, or storing sensitive or complex information -- the spreadsheet is not for you. But if you're working on small to medium sized personal or community projects, try a spreadsheet!

+

The Short & Sweet

+
    +
  1. Link to Sheetsee.js, tabletop.js and jQuery in your HTML head.
  2. +
  3. Create a place holder <div> in your HTML for any chart, map or table you want to have.
  4. +
  5. Create templates for tables in <script> tags.
  6. +
  7. Inside of a <script> tag initialize Tabletop.js. It waits for the document to load and then initializes tabletop and calls back a function when it has returned with the spreadsheet data.
    document.addEventListener('DOMContentLoaded', function() {
    + var gData
    + var URL = "YOURSPREADSHEETSKEYHERE"
    + Tabletop.init( { key: URL, callback: callback, simpleSheet: true } )
    +})
    +
    +
  8. +
  9. Define the function that Tabletop.js calls when it returns with the data. This function will contain all the Sheetsee.js functions that you use for the maps, charts and tables you desire. Style it up with some CSS.
    function callback(data) {
    + // All the sheetsee things you want to do!
    +}
    +
    +
  10. +
  11. Set it and forget. Now all you need to do is edit the spreadsheet and visitors will get the latest information every time they load the page.
  12. +
+

Bare Minimum Setup

+

Ignoring some HTML things to conserve space, you get the point. This is a basic setup.

+
<html>
+  <head>
+    <meta charset="utf-8">
+    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
+    <script type="text/javascript" src="js/tabletop.js"></script>
+    <script type="text/javascript" src='js/sheetsee.js'></script>
+    <link rel="stylesheet" type="text/css" href="css/sss.css">
+  </head>
+  <body>
+  <div id="placeholder"></div>
+
+  <script id="placeholder" type="text/html">
+    // template if you so desire!
+  </script>
+
+  <script type="text/javascript">
+    document.addEventListener('DOMContentLoaded', function() {
+        var URL = "YOURSPREADSHEETSKEYHERE"
+        Tabletop.init( { key: URL, callback: myData, simpleSheet: true } )
+    })
+    function myData(data) {
+        All the sheetsee things you want to do!
+    }
+  </script>
+  </body>
+</html>
+
+

Your Data

+

sheetsee

+

Your Google Spreadsheet should be set up with row one as your column headers. Row two and beyond should be your data. Each header and row becomes an oject in the final array that Tabletop.js delivers of your data.

+

sheetsee

+

There shouldn't be any breaks or horizontal organization in the spreadsheet. But, feel free to format the style of your spreadsheet as you wish; borders, fonts and colors and such do not transfer or affect your data exporting.

+
[{"name":"Coco","breed":"Teacup Maltese","kind":"Dog","cuddlability":"5","lat":"37.74832","long":"-122.402158","picurl":"http://distilleryimage8.s3.amazonaws.com/98580826813011e2bbe622000a9f1270_7.jpg","hexcolor":"#ECECEC","rowNumber":1}...]
+

Hexcolor

+

sheetsee

+

You must add a column to your spreadsheet with the heading hexcolor (case insensitive). The maps, charts and such use colors and this is the easiest way to standardize that. The color scheme is up to you, all you need to do is fill the column with hexidecimal color values. This color picker by Devin Hunt is really nice. #Funtip: Coloring the background of the cell it's hexcolor brings delight!

+

Geocoding

+

If you intend to map your data and only have addresses you'll need to geocode the addresses into lat/long coordinates. Mapbox built a plugin + that does this for you in Google Docs. You can also use websites like latlong.net to get the coordinates and paste them into rows with column headers lat and long.

+

Publishing Your Spreadsheet

+

sheetsee

+

You need to do this in order to generate a unique key for your spreadsheet, which Tabletop.js will use to get your spreadsheet data. In your Google Spreadsheet, click File > Publish to the Web. Then in the next window click Start Publishing; it will then turn into a Stop Publishing button.

+

sheetsee

+

You should have an address in a box at the bottom, your key is the portion between the = and the &. You'll retrieve this later when you hook up your site to the spreadsheet. Actually, you technically can use the whole URL, but it's so long...

+

CSS

+

Sheetsee.js comes with a bare minimum stylesheet, sss.css, which contains elements you'll want to style when using the feature they correspond to.

+ + + +
+ + + diff --git a/site/docs/building.html b/site/docs/building.html new file mode 100644 index 00000000..4bd15b12 --- /dev/null +++ b/site/docs/building.html @@ -0,0 +1,82 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

Right-sizing

+

You can customize your sheetsee.js build with just the parts you want to use. If you want to just use the full version, you can grab it here at github.com/jlord/sheetsee.js.

+

All bundle comes with mapbox.js and handlebars.js (since both are available on NPM). Additionally you'll need to also include tabletop.js and jQuery in your HTML head like so:

+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/tabletop.js/1.1.0/tabletop.min.js"></script>
+
+

To build your Sheetsee you'll need Node.js and NPM (the latter comes with the former in most installs) on your computer and a command line.

+

Get Node/NPM

+

Download Node.js from nodejs.org/download. For most users you can just download the Mac .pkg or Windows .msi. Follow the install instructions, both include NPM. Once they're installed, proceed:

+

Install sheetsee from NPM

+

The sheetsee (with no '.js') module is the tool for building custom Sheetsee.js builds. Install sheetsee globally and then run it within the folder of your soon-to-be sheetsee.js project.

+

Install globally

+
npm install -g sheetsee
+
+

Run from within a project folder

+
sheetsee [options]
+
+

Here are the options for the different modules. If you want save the generated file as sheetsee.js then add the --save option.

+
    +
  • -m or -maps for maps
  • +
  • -t or -tables for tables
  • +
  • -c or -charts for charts
  • +
  • --save to write out the file*
  • +
+

* otherwise, defaults to standardout on your console which you can | pbcopy

+

So for instance, sheetsee -m -t --save will build you a Sheetsee.js with the basic data functions, the map and tables sections built in and save it as a file named sheetsee.js. Running sheetsee -m -t | pbcopy will save the output to your clipboard.

+ + + +
+ + + diff --git a/site/docs/changelog.html b/site/docs/changelog.html new file mode 100644 index 00000000..bf21db57 --- /dev/null +++ b/site/docs/changelog.html @@ -0,0 +1,89 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

Sheetsee v3

+

May 10, 2014

+

Better Table Template Options

+

Updated sheetsee-tables to allow you to re-use a template (rather than duplicating it for each different table you wanted to create). Previously it assumed your HTML table div id matched your script template id. This means that you can pass in an extra key/value pair in your table options into Sheetsee.maketable(). The new pair it takes is: "templateID" : "yourtemplateid". Example below, full sheetsee-tables documentation here.

+
var tableOptions = {
+                    "data": gData,
+                    "pagination": 10,
+                    "tableDiv": "#fullTable",
+                    "filterDiv": "#fullTableFilter",
+                    "templateID": "tableTemplte"
+                    }
+Sheetsee.makeTable(tableOptions)
+
+

May 2014

+

Important Google Spreadsheets & Tabletop News

+

Google recently updated their Google Spreadsheets and the API. For a bit this was breaking things using the old API, including Tabletop. This has been fixed and the latest version of tabletop.js works on both old and new spreadsheets. Be sure to include at least version 1.3.4 in your project.

+

August 13, 2013

+

Charting Intake

+

D3 charts need an array of objects, and something to chart: the thing itself (aka labels) and the corresponding value (aka units). Your data usually contains more than D3 needs to make the chart, so you have to tell it what to chart from your data to chart.

+

Previously Sheetsee required you pass your data through a function, addUnitsAndLabels() which took in your data and the things you wanted to chart, reformatted your data for you so that you could pass it into one of the d3 charts. This is one more step than actually needs to happen.

+

Now Sheetsee just asks for what you want your labels and units to be in the options you give it when calling the chart function. It then sorts the data correctly on the inside of the chart function. Yay, easier!

+
var options = {
+  labels: "name",
+  units: "cuddleability",
+  m: [60, 60, 30, 150],
+  w: 600, h: 400,
+  div: "#barChart",
+  xaxis: "no. of pennies",
+  hiColor: "#FF317D"
+}
+
+

Thanks @maxogden for the help with this.

+

Started doing this changelong pretty late in the game.

+ + + +
+ + + diff --git a/site/docs/custom-charts.html b/site/docs/custom-charts.html new file mode 100644 index 00000000..363f42d7 --- /dev/null +++ b/site/docs/custom-charts.html @@ -0,0 +1,110 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

Custom Charts

+

It's easy to take a D3.js chart of your own and use it with Sheetsee.js. If you make it into a module, anyone can use your chart, too!

+

Sheetsee charts currently work by taking in some options, like so:

+
var pieOptions = {labels: "name", units: "units", m: [80, 80, 80, 80], w: 600, h: 400, div: "#pieChart", hiColor: "#14ECC8"}
+
+

The labels represent the actual thing you're charting and units are how many of those things. Margin, width and height are m, w, h and the <div> to build your chart in is div. Finally, you can supply a highlight color if you want.

+

So, your chart could take the same options, but map them into your D3 code with the correct variables. An example from maxogden/sheetsee-d3bubble:

+

Append the d3.js code with a map of your sheetsee options

+
Sheetsee.d3BubbleChart = function(data, options) {
+    var tree = {name: "data", children: []}
+    var groups = {}
+
+    // data needs to look like this:
+    // var data = { name: "wahtever", children: [
+    //   { name: "group1", children: [
+    //     { name: 'bob', size: 3},
+    //     { name: 'judy', size: 5}
+    //   ]},
+    //   { name: "group2", children: [
+    //     { name: 'jim', size: 10},
+    //     { name: 'bill', size: 5}
+    //   ]}
+    // ]}
+
+    data.map(function(r) {
+        var groupName = r[options.group]
+        groups[groupName] = true
+    })
+
+    Object.keys(groups).map(function(groupName) {
+        var groupMembers = []
+        data.map(function(r) {
+            if (r[options.group] !== groupName) return
+            groupMembers.push({name: r[options.name], size: r[options.size]})
+        })
+        tree.children.push({name: groupName, children: groupMembers})
+    })
+
+  // the rest of the code
+
+

In your HTML call it like so

+
<script type="text/javascript">
+  document.addEventListener('DOMContentLoaded', function() {
+    var URL = "0AvFUWxii39gXdFhqZzdTeU5DTWtOdENkQ1Y5bHdqT0E"
+    Tabletop.init( { key: URL, callback: showInfo, simpleSheet: true } )
+  })
+
+  function showInfo(data) {
+    Sheetsee.d3BubbleChart(data, { name: 'name', size: 'cuddlability', group: 'kind', div: '#stuff'})
+  }
+</script>
+
+

There are lots of charts to get excited about in the D3 gallery.

+

View the entire source

+ + + +
+ + + diff --git a/site/docs/fork-n-go.html b/site/docs/fork-n-go.html new file mode 100644 index 00000000..41063615 --- /dev/null +++ b/site/docs/fork-n-go.html @@ -0,0 +1,79 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

Fork-n-Go

+

A Fork-n-Go project is a project on GitHub that in a few clicks and starting with a fork, gives another user a live website that they control with an easy to swap-for-your-own Google Spreadsheet database.

+

To awesome things that make this possible: Forking and GitHub Pages.

+

On GitHub, a fork is a full copy of a repository, on your account, that you can manage and edit. It's done with the click of a button.

+

GitHub Pages is the hosting service that GitHub provides free to all users, organizations and repositories. This means everyone of these entities or project can have it's own website at a predictable domain:

+
    +
  • organizations: orgname.github.io
  • +
  • users: username.github.io
  • +
  • repositories: username.github.io/repositoryname
  • +
+

To have a website for a repository all you need is a branch named gh-pages. GitHub will then look in that branch for web files and serve them up at the address.

+

What all of this means is that Sheetsee.js projects, hosted on gh-pages branches on GitHub, can easily be forked and connected to another spreadsheet giving another user a live website of their own really easily.

+

A Fork-n-Go example from my blog post on the topic:

+

Hack Spots Fork-n-Go

+

I made this website to collect hack spots all over the world from friends and friends of friends (the spreadsheet is wide open, so you can add some, too!). It’s using sheetsee to power the table, map and other elements of the page. Its source is in this repo, with just a gh-pages branch. To create an instance of this site for yourself all you need to do:

+
    +
  • Create a Google spreadsheet with the same headers (just copy and paste header row from the original). Click File > Publish to Web, then Start Publishing.
  • +
  • Fork the original repository.
  • +
  • Edit the HTML file directly on GitHub.com to replace the original spreadsheet’s unique key with your spreadsheet’s key (found in your spreadsheet’s URL). +Commit your change.
  • +
+

Now you have the same site connected to a spreadsheet that you manage — it’s live and can be found at yourGitHubName.github.io/theReposName.

+

forkcommit

+ + + +
+ + + diff --git a/site/docs/sheetsee-charts.html b/site/docs/sheetsee-charts.html new file mode 100644 index 00000000..899b2ef7 --- /dev/null +++ b/site/docs/sheetsee-charts.html @@ -0,0 +1,151 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

Sheetsee-charts

+

View Demo

+

Sheetsee.js provides three D3.js chart options to use with your spreadsheet data: a bar chart, line graph and pie chart. You can also use a custom D3 chart with Sheetsee, read about that here.

+

Make a Chart

+

Each chart requires your data be an array of objects, with objects containing label and units key/value pairs.

+

Experiment with the charts to find the correct size your <div> will need to be to hold the chart with your data in it nicely.

+

You can also make your own D3 chart in a separate .js file, link to that in your HTML head and pass your data on to it after Tabletop.js returns. Information here on using your own chart.

+

Bar Chart

+

To create a bar chart you'll need to add a placeholder <div> in your HTML with an id.

+
<div id="barChart"></div>
+
+

In your CSS, give it dimensions.

+
#barChart {height: 400px; max-width: 600px; background: #F8CDCD;}
+
+

You'll also have these CSS elements to style however you'd like:

+
.labels text {text-align: right;}
+.bar .labels text {fill: #333;}
+.bar rect {fill: #e6e6e6;}
+.axis {shape-rendering: crispEdges;}
+.x.axis line {stroke: #fff; fill: none;}
+.x.axis path {fill: none;}
+.x.axis text {fill: #333;}
+.xLabel {font-family: sans-serif; font-size: 9px;}
+
+

In a <script> tag set up your options.

+
var barOptions = {labels: "name", units: "cuddleability", m: [60, 60, 30, 150], w: 600, h: 400, div: "#barChart", xaxis: "no. of pennies", hiColor: "#FF317D"}
+
+
    +
  • labels is a string, usually a column header, it's what you call what you're charting
  • +
  • units is a string, usually a column header, it's the value you're charting
  • +
  • m is margins: top, right, bottom, left
  • +
  • w and h are width and height, this should match your CSS specs
  • +
  • div is the id for the <div> in your HTML
  • +
  • xaxis is optional text label for your x axis
  • +
  • hiColor is the highlight color of your choosing!
  • +
+

Then call the d3BarChart() function with your data and options.

+
Sheetsee.d3BarChart(data, barOptions)
+
+

Line Chart

+

To create a line chart you'll need to add a placeholder <div> in your html with an id.

+
<div id="lineChart"></div>
+
+

In your CSS, give it dimensions.

+
#lineChart {height: 400px; max-width: 600px; background: #F8CDCD;}
+
+

And these chart elements to style:

+
.axis {shape-rendering: crispEdges;}
+.x.axis .minor, .y.axis .minor {stroke-opacity: .5;}
+.x.axis {stroke-opacity: 1;}
+.y.axis line, .y.axis path {fill: none; stroke: #acacac; stroke-width: 1;}
+.bigg {-webkit-transition: all .2s ease-in-out; -webkit-transform: scale(2);}
+path.chartLine {stroke: #333; stroke-width: 3; fill: none;}
+div.tooltip {position: absolute; text-align: left; padding: 4px 8px; width: auto; font-size: 10px; height: auto; background: #fff; border: 0px; pointer-events: none;}
+circle {fill: #e6e6e6;}
+
+

In a <script> tag set up your options.

+
var lineOptions = {labels: "name", units: "cuddleability", m: [80, 100, 120, 100], w: 600, h: 400, div: "#lineChart", yaxis: "no. of pennies", hiColor: "#14ECC8"}
+
+
    +
  • labels is a string, usually a column header, it's what you call what you're charting
  • +
  • units is a string, usually a column header, it's the value you're charting
  • +
  • m is your margins: top, right, bottom, left
  • +
  • w and h are width and height, this should match your CSS specs
  • +
  • div is the id for the <div> in your HTML
  • +
  • yaxis is optional text label for your y axis
  • +
  • hiColor is the highlight color of your choosing!
  • +
+

Then call the d3LineChart() function with your data and options.

+
Sheetsee.d3LineChart(data, lineOptions)
+
+

Pie Chart

+

To create a bar chart you'll need to add a placeholder <div> in your html with an id.

+
<div id="pieChart"></div>
+
+

In your CSS, give it dimensions.

+
#pieChart {height: 400px; max-width: 600px; background: #F8CDCD;}
+
+

Style chart elements:

+
.arc path { stroke: #fff;}
+
+

In a <script> tag set up your options.

+
var pieOptions = {labels: "name", units: "units", m: [80, 80, 80, 80], w: 600, h: 400, div: "#pieChart", hiColor: "#14ECC8"}
+
+
    +
  • labels is a string, usually a column header, it's what you call what you're charting
  • +
  • units is a string, usually a column header, it's the value you're charting
  • +
  • m is your margins: top, right, bottom, left
  • +
  • w and h are width and height, this should match your CSS specs
  • +
  • div is the id for the <div> in your HTML
  • +
  • hiColor is the highlight color of your choosing!
  • +
+

Then call the d3PieChart() function with your data and options.

+
Sheetsee.d3PieChart(data, pieOptions)
+
+ + + +
+ + + diff --git a/site/docs/sheetsee-core.html b/site/docs/sheetsee-core.html new file mode 100644 index 00000000..2affba30 --- /dev/null +++ b/site/docs/sheetsee-core.html @@ -0,0 +1,129 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

Sheetsee-core

+

This is the core module in sheetsee and is included in all builds. It contains the functions for building your custom file as well as the basic data manipulation functions.

+

Working With Your Data

+

Tabletop.js will fetch the data from your spreadsheet and return it as an array of objects. Sheetsee.js has functions built in to help you filter or reorganize the data if you'd like.

+

Sheetsee.getGroupCount(data, groupTerm)

+

This takes in your data, an array of objects, and searches for a string, groupTerm, in each piece of your data (formerly the cells of your spreadsheet). It returns the number of times it found the groupTerm.

+
getGroupCount(data, "cat")
+// returns a number
+
+

Sheetsee.getColumnTotal(data, column)

+

Given your data, an array of objects, and a string column header, this functions sums each cell in that column(so this collumn you mention best have numbers).

+
getColumnTotal(data, "cuddlability")
+// returns number
+
+

Sheetsee.getAveragefromColumn(data, column)

+

A really simple function that builds on getColumnTotal() by returning the average number in a column of numbers.

+
getColumnAverage(data, "cuddlability")
+// returns number
+
+

Sheetsee.getMin(data, column)

+

This will return an array of object or objects (if there is a tie) of the element with the lowest number value in the column you specify from your data.

+
getMin(data, "cuddlability")
+// returns array
+
+

Sheetsee.getMax(data, column)

+

This will return an array of object or objects (if there is a tie) of the element with the highest number value in the column you specify from your data.

+
getMin(data, "cuddlability")
+// returns array
+
+

Don't Forget JavaScript Math

+

Create variables that are the sums, differences, multiples and so forth of others. Lots of info on that here on MDN.

+
var profit09 = Sheetsee.getColumnTotal(data, "2009")
+var profit10 = Sheetsee.getColumnTotal(data, "2010")
+var difference = profit09 - profit10
+
+

What These Little Bits are Good For

+

You don't have to just create tables of your data. You can have other portions of your page that show things like, "The difference taco consumption between last week and this week is..." These are easy to create with JavaScript math functions and knowing a little bit more about icanhaz.js.

+

Sheetsee.getMatches(data, filter, category)

+

Takes data as an array of objects, a string you'd like to filter and a string of the category you want it to look in (a column header from your spreadsheet).

+
getMatches(data, "dog", "kind")
+
+

Returns an array of objects matching the category's filter.

+
[{"name": "coco", "kind": "dog"...}, {"name": "wolfgang", "kind": "dog"...},{"name": "cooc", "kind": "dog"...} ]
+
+

Sheetsee.getOccurance(data, category)

+

Takes data as an array of objects and a string for category (a column header from your spreadsheet) you want tally how often an element occured.

+
getOccurance(data, "kind")
+
+

Returns an object with keys and values for each variation of the category and its occurance.

+
{"dog": 3, "cat": 3}
+
+

Sheetsee.makeColorArrayOfObject(data, colors)

+

If you use getOccurance() and want to then chart that data with d3.js, you'll need to make it into an array (instead of an object) and add colors back in (since the hexcolor column applies to the datapoints in your original dataset and not this new dataset).

+

This function takes in your data, as an object, and an array of hexidecimal color strings which you define.

+
var kinds = getOccurance(data, "kind")
+var kindColors = ["#ff00ff", "#DCF13C"]
+
+var kindData = makeColorArrayOfObjects(mostPopBreeds, breedColors)
+
+

It will return an array of objects formatted to go directly into a d3 chart with the appropriate units and label keys, like so:

+
[{"label": "dog", "units": 2, "hexcolor": "#ff00ff"}, {"label": "cat", "units": 3, "hexcolor": "#DCF13C"}]
+
+

If you pass in an array of just one color it will repeat that color for all items. If you pass fewer colors than data elements it will repeat the sequences of colors for the remainder elements.

+

Sheetsee.addUnitsLabels(arrayObj, oldLabel, oldUnits)

+

If you're using data, the data directly from Tabletop, you'll need to format it before you use the d3 charts. You'll need to determine what part of your data you want to chart - what will be your label, what your charting, and what will be your units, how many of them are there (this should be a number).

+
var data =  [{"name": "coco", "kind": "dog", "cuddablity": 5}, {"name": "unagi", "kind": "cat", "cuddlability": 0}]
+
+

For istance, if from our original data above we want to chart the age of each cat, we'll use:

+
Sheetsee.addUnitsLabels(data, "name", "cuddlability")
+
+

Which will return an array, ready for the d3 charts:

+
[{"label": "coco", "kind": "dog", "units": 5}, {"label": "unagi", "kind": "cat", "units": 0}]
+
+ + + +
+ + + diff --git a/site/docs/sheetsee-maps.html b/site/docs/sheetsee-maps.html new file mode 100644 index 00000000..97cf7843 --- /dev/null +++ b/site/docs/sheetsee-maps.html @@ -0,0 +1,136 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

Sheetsee-maps

+

View Demo

+

Sheetsee.js uses Mapbox.js and Leaflet.js to make maps of your points, polygons, lines or multipolygons (all coordinate based). Details on what that actually looks like here.

+

Maps: Polygons and Lines

+

Sheetsee-maps now supports polygons and lines. So long as you have the correct coordinate structure in your cells, Sheetsee will add them to the geoJSON it creates for your maps. More details for coordinates of lines and polygons in geoJSON are here, but briefly:

+

A linestring:

+
[-122.41722106933594, 37.7663045891584], [-122.40477561950684, 37.77695634643178]
+

A polygon:

+
[-122.41790771484375, 37.740381166384914], [-122.41790771484375, 37.74520008134973], [-122.40966796874999, 37.74520008134973],[-122.40966796874999, 37.740381166384914], [-122.41790771484375, 37.740381166384914]
+

A Multipolygon:

+
[[-122.431640625, 37.79106586542567], [-122.431640625, 37.797441398913286], [-122.42666244506835, 37.797441398913286],[-122.42666244506835, 37.79106586542567], [-122.431640625, 37.79106586542567]],
+[[-122.43352890014648, 37.78197638783258], [-122.43352890014648, 37.789031004883654], [-122.42443084716797, 37.789031004883654], [-122.42443084716797, 37.78197638783258], [-122.43352890014648, 37.78197638783258]]
+
+### The Parts
+
+You'll create a placeholder `<div>` in your HTML, CSS giving it a size and fire up a map from within `<script>` tags. You can also customize your popup content.
+
+## Your HTML Placeholder `<div>`
+
+Create an empty `<div>` in your HTML, with an id (name). Add CSS to give it dimensions
+
+```HTML
+<div id="map"></div>
+

CSS

+
#map {width: 500px; height: 500px;}
+
+

Your <script> Functions

+

Next you'll need to create geoJSON out of your data so that it can be mapped.

+

Sheetsee.createGeoJSON(data, optionsJSON)

+

This takes in your data and the parts of your data, optionsJSON, that you plan on including in your map's popups. These will be column headers in your spreadsheet. If you're not going to have popups on your markers, don't worry about it then and just pass in your data (by default it will use all the row's information).

+
var optionsJSON = ["name", "breed", "cuddlability"]
+var geoJSON = Sheetsee.createGeoJSON(gData, optionsJSON)
+
+

It will return an array in the special geoJSON format that map making things love.

+
[{
+  "geometry": {"type": "Point", "coordinates": [long, lat]},
+  "properties": {
+    "marker-size": "small",
+    "marker-color": lineItem.hexcolor
+  },
+  "opts": {},
+}}
+
+

Sheetsee.loadMap(mapDiv)

+

To create a simple map, with no data, you simply call .loadMap() and pass in a string of the mapDiv (with no '#') from your HTML.

+
var map = Sheetsee.loadMap("map")
+
+

Sheetsee.addTileLayer(map, tileLayer)

+

To add a tile layer (aka a custom map scheme/design/background) you'll use this function which takes in your map and the source of the tileLayer. This source can be a Mapbox id, a URL to a TileJSON or your own generated TileJSON. See Mapbox's Documentation for more information.

+
Sheetsee.addTileLayer(map, 'examples.map-20v6611k')
+
+

You can add tiles from awesome mapmakers like Stamen or create your own in Mapbox's Tilemill or online.

+

Sheetsee.addMarkerLayer(geoJSON, map)

+

To add makers, lines or shapes to your map, use this function and pass in your geoJSON so that it can get the coordinates and your map so that it places the markers there. You can customize what the content in your marker's popup looks like with a popupTemplate, which is an ICanHaz.js template in HTML and can reference the column headers you included in your optionsJSON.

+
var markerLayer = Sheetsee.addMarkerLayer(geoJSON, map, popupTemplate)
+
+

Example template:

+
var popupTemplate = "<h4>Hello {{name}}</h4>"
+
+

Source from the map demo:

+
<script type="text/javascript">
+  document.addEventListener('DOMContentLoaded', function() {
+    var gData
+    var URL = "0Ao5u1U6KYND7dGN5QngweVJUWE16bTRob0d2a3dCbnc"
+    Tabletop.init( { key: URL, callback: showInfo, simpleSheet: true } )
+  })
+
+  function showInfo(data) {
+    gData = data
+    var optionsJSON = ["placename", "photo-url"]
+    var template = "<ul><li><a href='{{photo-url}}' target='_blank'>"
+                 + "<img src='{{photo-url}}'></a></li>"
+                 + "<li><h4>{{placename}}</h4></li></ul>"
+    var geoJSON = Sheetsee.createGeoJSON(gData, optionsJSON)
+    var map = Sheetsee.loadMap("map")
+    Sheetsee.addTileLayer(map, 'examples.map-20v6611k')
+    var markerLayer = Sheetsee.addMarkerLayer(geoJSON, map, template)
+  }
+</script>
+
+ + + +
+ + + diff --git a/site/docs/sheetsee-tables.html b/site/docs/sheetsee-tables.html new file mode 100644 index 00000000..de194bd2 --- /dev/null +++ b/site/docs/sheetsee-tables.html @@ -0,0 +1,161 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

Sheetsee-tables

+

With this module you can create tables of your data that are sortable, searchable and paginate-able.

+

You'll need a placeholder <div> in your html, a <script> mustache template and a <script> that initiates the table.

+

Your HTML Placeholder

+

This is as simple as an empty <div> with an id.

+

Your Template

+

Your template is the mockup of what you'd like your table to look like and what content it should show. The style is up to you! It is an HTML template inside of <script> tags.

+

Sorting

+

If you want users to be able to click on headers and sort that column, your template must include table headers with the class tHeader.

+

You can then style .tHeader in your CSS to make them look how you want.

+

Your Script

+

You'll want to set your table options and pass them into Sheetsee.makeTable(). If you want to add a search/filter, pass your options into Sheetsee.initiateTableFilter()

+

Funtions

+

Sheetsee.makeTable(tableOptions)

+

You pass in an object containing:

+
    +
  • data your data array
  • +
  • pagination how many rows displayed at one time, defaults to all
  • +
  • tableDiv the
    placeholder in your HTML
  • +
  • filterDiv the <div> containing your <input> filter if using search
  • +
  • templateID if you are reusing a template or your templateID is different than your tableDiv (if you don't include this, it will assume it matches tableDiv)
  • +
+
var tableOptions = {
+                    "data": gData,
+                    "pagination": 10,
+                    "tableDiv": "#fullTable",
+                    "filterDiv": "#fullTableFilter",
+                    "templateID": "fullTable"
+                    }
+Sheetsee.makeTable(tableOptions)
+
+

Pagination

+

If you do not put in a number for pagination, by default it will show all of the data at once. With pagination, HTML will be added at the bottom of your table for naviagtion, which you can style in your CSS:

+

HTML

+
<div id='Pagination' currentPage class='table-pagination'>
+  Showing page {{currentPage}} of {{totalPages}}
+  <a class='pagination-pre'>Previous</a><a class='pagination-next'>Next</a>
+</div>
+
+

CSS

+
#Pagination {background: #eee;}
+.pagination-next, .pagination-pre {cursor: hand;}
+.no-pag {color: #acacac;}
+
+ +

If you want to have an input to allow users to search/filter the data in the table, you'll add an input to your HTML. Give it an id and if you want, placeholder text:

+
<input id="tableFilter" type="text" placeholder="filter by.."></input>
+
+

Sheetsee.initiateTableFilter(tableOptions)

+

You will then call this function with your tableOptions to make that input live and connected to your table:

+
Sheetsee.initiateTableFilter(tableOptions)
+
+

It will connect that input to your data as well as inject this HTML for a button, which you can style yourself in your CSS:

+
<span class="clear button">Clear</span>
+<span class="noMatches">no matches</span>
+
+

Example

+

HTML

+
<div id="siteTable"></div>
+<input id="siteTableFilter" type="text"></input>
+
+

Template

+
<script id="tableTemplate" type="text/html">
+    <table>
+    <tr><th class="tHeader">City</th><th class="tHeader">Place Name</th><th class="tHeader">Year</th><th class="tHeader">Image</th></tr>
+      {{#rows}}
+        <tr><td>{{city}}</td><td>{{placename}}</td><td>{{year}}</td><td>{{image}}</td></tr>
+      {{/rows}}
+  </table>
+</script>
+
+

JavaScript

+
<script type="text/javascript">
+    document.addEventListener('DOMContentLoaded', function() {
+      var tableOptions = {
+                          "data": gData,
+                          "pagination": 10,
+                          "tableDiv": "#siteTable",
+                          "filterDiv": "#siteTableFilter",
+                          "templateID": "tableTemplate"
+                          }
+      Sheetsee.makeTable(tableOptions)
+      Sheetsee.initiateTableFilter(tableOptions)
+    })
+</script>
+
+

To create another table, simply repeat the steps above (abreviated here below).

+

HTML

+
<div id="secondTable"></div>
+<input id="secondFilter" type="text"></input>
+
+

Template

+
<script text="text/javascript" id="secondTable">
+  // Template here
+</script>
+
+

JavaScript

+
<script>
+  var secondTableOpts = {} // the options
+  Sheetsee.makeTable(secondTableOpts)
+  Sheetsee.initiateTableFilter(secondTableOpts)
+</script>
+
+

Learn more about the things you can do with ICanHaz.js.

+

View Demo

+ + + +
+ + + diff --git a/site/docs/tips.html b/site/docs/tips.html new file mode 100644 index 00000000..5ff27e20 --- /dev/null +++ b/site/docs/tips.html @@ -0,0 +1,110 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

Tips

+

A few things to think about beyond charts, maps and tables.

+

ICanHaz.js

+

You can use templates for more than just tables. Use them to create lists ol, ul; array of images... You'll need a placeholder <div> in your HTML, a <script> for your template and a script to call ICanHaz from your Tabletop.js callback. For a live example, see the bottom photo grid of the sheetsee-table demo.

+

HTML

+
<div id="divID"></div>
+
+

Template

+
<script id="divID" type="text/html">
+  {{#rows}}
+    <div><img class="photo" src="{{some-variable}}"></div>
+  {{/rows}}
+</script>
+
+

Script

+
<script type="text/html">
+  // your other Sheetsee.js, Tabletop code above
+  var html = Sheetsee.ich.divID({'rows': data})
+  $('#divID').html(html)
+</script>
+
+

non-table example output

+

lib

+

Query Strings

+

If your spreadsheet contains address information, using templates (Sheetsee.js uses a form of Mustache), you can embed those elements into a query string (aka a search URL) like Google Maps URL or Yelp. If you search for a location in Google Maps, you'll notice it creates a URL for that search.

+

So, if you have information in your spreadsheet that would go inside a query string, make a template for inserting them into a link on your page.

+

The basic elements are: a spreadsheet with address info + HTML template to create the query string.

+

The Sheetsee Hack-Spots is an does such a thing. Here is the spreadsheet, with address information

+

img

+

In the HTML template for the table on the Hack-Spots page, the button’s links look like this:

+
<a class="button" href="https://maps.google.com/maps?q={{address}},{{city}},{{state}}" target="_blank">View in Google Maps</a>
+<a class="button" href="http://www.yelp.com/search?find_desc={{name}}&find_loc={{city}},{{state}}" target="_blank">Find on Yelp</a>
+
+

Here is the exact line of code on GitHub.

+

We’re inserting the address, city, and state details from the spreadsheet into the structure of a query string for Google maps and Yelp. You can figure out the query string of a service by just using it (type in an address in Google Maps) and looking at the resulting URL.

+

With a some CSS and such, the resulting website has a table with the hack spots and a button for viewing in Google Maps or Yelp:

+

img

+

When the page builds, it creates the correct link for each row. When someone clicks on the buttons it takes them directly to the Google Map search result for that address. BAM!

+

IFTTT

+

Ifttt.com offers lots of options sending data from your actions (Twitter, Instagram, GitHub, Pocket...) to Google Spreadsheets.

+

Row Numbers

+

When Tabletop.js returns your spreadsheet data, it adds a key/value of rownumber. This is great to use when you need to uniquely match or find a row in your data.

+

Images

+

Your spreadsheet can contain URLs to images which you can use to display the images on the page you build. Your template would look something like this:

+
<img src='{{imgurl}}'/>
+
+

Data as Classes

+

You can use your data as classes to style with CSS. For instance, if you had data about recipes and a column called 'Taste' that contained either 'savory' or 'sweet'. In a table of the recipes you could do something like:

+
<tr><td class="{{taste}}"></tr>
+
+

Then in your CSS:

+
td .savory {}
+td .sweet {}
+
+ + + +
+ + + diff --git a/site/img/fbi_spinner.gif b/site/img/fbi_spinner.gif new file mode 100644 index 00000000..872ede51 Binary files /dev/null and b/site/img/fbi_spinner.gif differ diff --git a/site/img/hexcolors.png b/site/img/hexcolors.png new file mode 100644 index 00000000..83c98d93 Binary files /dev/null and b/site/img/hexcolors.png differ diff --git a/site/img/key.png b/site/img/key.png new file mode 100644 index 00000000..94ef13b1 Binary files /dev/null and b/site/img/key.png differ diff --git a/site/img/latlong.png b/site/img/latlong.png new file mode 100644 index 00000000..343e393e Binary files /dev/null and b/site/img/latlong.png differ diff --git a/site/img/nonos.png b/site/img/nonos.png new file mode 100644 index 00000000..15ab0a7f Binary files /dev/null and b/site/img/nonos.png differ diff --git a/site/img/publish.png b/site/img/publish.png new file mode 100644 index 00000000..c0ff7101 Binary files /dev/null and b/site/img/publish.png differ diff --git a/site/img/sheetsee-03.png b/site/img/sheetsee-03.png new file mode 100644 index 00000000..a9a4154d Binary files /dev/null and b/site/img/sheetsee-03.png differ diff --git a/site/img/spreadsheettodata.png b/site/img/spreadsheettodata.png new file mode 100644 index 00000000..d787d9f2 Binary files /dev/null and b/site/img/spreadsheettodata.png differ diff --git a/site/img/webconsole.png b/site/img/webconsole.png new file mode 100644 index 00000000..610ad9be Binary files /dev/null and b/site/img/webconsole.png differ diff --git a/site/index.html b/site/index.html new file mode 100644 index 00000000..9c3b3163 --- /dev/null +++ b/site/index.html @@ -0,0 +1,123 @@ + + + + + Sheetsee.js + + + + + + + + + + + +
+

sheetseeimg

+

Sheetsee.js

+

Sheetsee.js is a client-side library for connecting Google Spreadsheets to a website and visualizing the information in tables, maps and charts.

+

Google Spreadsheets can be used as simple and collaborative databases, they make getting a data driven site going much easier than traditional databases. Read more about using spreadsheets for databases here.

+

Modules

+

Each of sheetsee.js's features are divided into modules. Use just the parts you need; see docs on building. If you don't want to build your own, you can just use the full library which includes all modules, it's here on GitHub.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModuleContainsDocs
sheetsee-coreIncluded in any build. Gets you started and has the working-with-your-data functions.Doc
sheetsee-tablesContains everything you'll need to create a table including sortable columns, pagination and search.Doc
sheetsee-mapsFor making maps with your point, line or polygon spreadsheet data. Built on Mapbox.js.Doc
sheetsee-chartsIncludes 3 basic d3 charts: bar, line and pie. You can also use your own.Doc
+

In the Wild

+

What can you make with Sheetsee.js? Lots of things, here are some examples:

+ +

List your sheetsee project here: file an issue or pull request.

+

Resources & Documentation

+

More resources on using Sheetsee.js:

+ + + + + + + + + + + + + + + + + +
Getting StartedIdeasUseDemos
About Sheetsee.js
Building Sheetsee
Basics
Fork-n-Go
Tips!
Custom charts
Sheetsee-core
Sheetsee-tables
Sheetsee-maps
Sheetsee-charts
Table Demo
Map Demo
Chart Demo
+

Note on New Google Spreadsheets

+

Google recently updated their Google Spreadsheets and the API. For a bit this was breaking things using the old API, including Tabletop. This has been fixed and the latest version of tabletop.js works on both old and new spreadsheets. Be sure to include at least version 1.3.4 in your project.

+ + + +
+ + + diff --git a/site/js/sheetsee.js b/site/js/sheetsee.js new file mode 100644 index 00000000..359e15cb --- /dev/null +++ b/site/js/sheetsee.js @@ -0,0 +1,25375 @@ +;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o": ">", + '"': '"', + "'": ''' + }; + + 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+$/, ''); + } + + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; + + var ich = { + VERSION: "0.10.2", + 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.$(trim(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