diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..227a3756 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "0.10" +script: phantomjs test/qunit/runner.js test/index.html diff --git a/LICENSE.txt b/LICENSE.txt index ac88e62c..a0993c64 100755 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2011 Max Ogden & Contributors +Copyright (c) 2011-2014 Max Ogden, Rufus Pollock and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 3757ee81..aea8464b 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/okfn/recline.png)](https://travis-ci.org/okfn/recline) + A simple but powerful library for building data applications in pure Javascript and HTML.

Recline Website - including Overview, Documentation, Demos etc

@@ -35,12 +37,15 @@ See CONTRIBUTING.md. Possible breaking changes +* Many backends moved to their own repositories #314 * Updated Leaflet to latest version 0.4.4 #220 * Added marker clustering in map view to handle a large number of markers * Dataset.restore method removed (not used internally except from Multiview.restore) * Views no longer call render in initialize but must be called client code * Backend.Memory.Store attribute for holding 'records' renamed to `records` from `data` * Require new underscore.deferred vendor library for all use (jQuery no longer required if just using recline.dataset.js) +* View.el is now the raw DOM element. If you want a jQuery-wrapped version, use view.$el. #350 +* Upgraded timelinejs lib - #316 ### v0.5 - July 5th 2012 (first public release) diff --git a/_includes/backend-list.html b/_includes/backend-list.html index 88e5f38d..315f856f 100644 --- a/_includes/backend-list.html +++ b/_includes/backend-list.html @@ -2,7 +2,7 @@
  • gdocs: Google Docs (Spreadsheet)
  • csv: CSV files
  • solr: SOLR (partial)
  • -
  • elasticsearch: ElasticSearch
  • +
  • elasticsearch: ElasticSearch
  • dataproxy: DataProxy (CSV and XLS on the Web)
  • ckan: CKAN – support for CKAN datastore
  • couchdb: CouchDB
  • diff --git a/_includes/example-backends-elasticsearch.js b/_includes/example-backends-elasticsearch.js deleted file mode 100644 index 51d41c0c..00000000 --- a/_includes/example-backends-elasticsearch.js +++ /dev/null @@ -1,13 +0,0 @@ -var dataset = new recline.Model.Dataset({ - url: 'http://datahub.io/dataset/rendition-on-record/ac5a28ea-eb52-4b0a-a399-5dcc1becf9d9/api', - backend: 'elasticsearch' -}); - -dataset.fetch(); - -// For demonstrations purposes display the data in a grid -var grid = new recline.View.SlickGrid({ - model: dataset -}); -$('#my-elasticsearch').append(grid.el); - diff --git a/_includes/recline-deps.html b/_includes/recline-deps.html index f4d09e5f..2f4b241a 100644 --- a/_includes/recline-deps.html +++ b/_includes/recline-deps.html @@ -8,7 +8,7 @@ - + @@ -21,21 +21,22 @@ - - + + + - - + + @@ -52,8 +53,7 @@ - - + diff --git a/demos/multiview/app.js b/demos/multiview/app.js index 06e3dc61..2aec5896 100755 --- a/demos/multiview/app.js +++ b/demos/multiview/app.js @@ -73,7 +73,18 @@ var createExplorer = function(dataset, state) { id: 'grid', label: 'Grid', view: new recline.View.SlickGrid({ - model: dataset + model: dataset, + state: { + gridOptions: { + editable: true, + enabledAddRow: true, + enableCellNavigation: true + }, + columnsEditor: [ + { column: 'date', editor: Slick.Editors.Date }, + { column: 'title', editor: Slick.Editors.Text } + ] + } }) }, { @@ -81,6 +92,7 @@ var createExplorer = function(dataset, state) { label: 'Graph', view: new recline.View.Graph({ model: dataset + }) }, { diff --git a/dist/recline.dataset.js b/dist/recline.dataset.js index 3ca6fd41..fd97e552 100644 --- a/dist/recline.dataset.js +++ b/dist/recline.dataset.js @@ -3,9 +3,10 @@ this.recline = this.recline || {}; this.recline.Model = this.recline.Model || {}; (function(my) { + "use strict"; // use either jQuery or Underscore Deferred depending on what is available -var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; +var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; // ## Dataset my.Dataset = Backbone.Model.extend({ @@ -15,6 +16,7 @@ my.Dataset = Backbone.Model.extend({ // ### initialize initialize: function() { + var self = this; _.bindAll(this, 'query'); this.backend = null; if (this.get('backend')) { @@ -34,8 +36,9 @@ my.Dataset = Backbone.Model.extend({ this.facets = new my.FacetList(); this.recordCount = null; this.queryState = new my.Query(); - this.queryState.bind('change', this.query); - this.queryState.bind('facet:add', this.query); + this.queryState.bind('change facet:add', function () { + self.query(); // We want to call query() without any arguments. + }); // store is what we query and save against // store will either be the backend or be a memory store if Backend fetch // tells us to use memory store @@ -68,7 +71,13 @@ my.Dataset = Backbone.Model.extend({ } function handleResults(results) { - var out = self._normalizeRecordsAndFields(results.records, results.fields); + // if explicitly given the fields + // (e.g. var dataset = new Dataset({fields: fields, ...}) + // use that field info over anything we get back by parsing the data + // (results.fields) + var fields = self.get('fields') || results.fields; + + var out = self._normalizeRecordsAndFields(results.records, fields); if (results.useMemoryStore) { self._store = new recline.Backend.Memory.Store(out.records, out.fields); } @@ -298,7 +307,7 @@ my.Record = Backbone.Model.extend({ // // NB: if field is undefined a default '' value will be returned getFieldValue: function(field) { - val = this.getFieldValueUnrendered(field); + var val = this.getFieldValueUnrendered(field); if (field && !_.isUndefined(field.renderer)) { val = field.renderer(val, field, this.toJSON()); } @@ -588,10 +597,11 @@ this.recline.Backend = this.recline.Backend || {}; this.recline.Backend.Memory = this.recline.Backend.Memory || {}; (function(my) { + "use strict"; my.__type__ = 'memory'; // private data - use either jQuery or Underscore Deferred depending on what is available - var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; // ## Data Wrapper // @@ -692,9 +702,9 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; integer: function (e) { return parseFloat(e, 10); }, 'float': function (e) { return parseFloat(e, 10); }, number: function (e) { return parseFloat(e, 10); }, - string : function (e) { return e.toString() }, - date : function (e) { return new Date(e).valueOf() }, - datetime : function (e) { return new Date(e).valueOf() } + string : function (e) { return e.toString(); }, + date : function (e) { return moment(e).valueOf(); }, + datetime : function (e) { return new Date(e).valueOf(); } }; var keyedFields = {}; _.each(self.fields, function(field) { @@ -725,8 +735,8 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; } function range(record, filter) { - var startnull = (filter.start == null || filter.start === ''); - var stopnull = (filter.stop == null || filter.stop === ''); + var startnull = (filter.start === null || filter.start === ''); + var stopnull = (filter.stop === null || filter.stop === ''); var parse = getDataParser(filter); var value = parse(record[filter.field]); var start = parse(filter.start); @@ -750,8 +760,8 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; if (queryObj.q) { var terms = queryObj.q.split(' '); var patterns=_.map(terms, function(term) { - return new RegExp(term.toLowerCase());; - }); + return new RegExp(term.toLowerCase()); + }); results = _.filter(results, function(rawdoc) { var matches = true; _.each(patterns, function(pattern) { diff --git a/dist/recline.js b/dist/recline.js index 9df9cacc..d95cbb53 100644 --- a/dist/recline.js +++ b/dist/recline.js @@ -4,10 +4,11 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; // Note that provision of jQuery is optional (it is **only** needed if you use fetch on a remote file) (function(my) { + "use strict"; my.__type__ = 'csv'; // use either jQuery or Underscore Deferred depending on what is available - var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; // ## fetch // @@ -32,37 +33,46 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; var reader = new FileReader(); var encoding = dataset.encoding || 'UTF-8'; reader.onload = function(e) { - var rows = my.parseCSV(e.target.result, dataset); - dfd.resolve({ - records: rows, - metadata: { - filename: dataset.file.name - }, - useMemoryStore: true - }); + var out = my.extractFields(my.parseCSV(e.target.result, dataset), dataset); + out.useMemoryStore = true; + out.metadata = { + filename: dataset.file.name + } + dfd.resolve(out); }; reader.onerror = function (e) { alert('Failed to load file. Code: ' + e.target.error.code); }; reader.readAsText(dataset.file, encoding); } else if (dataset.data) { - var rows = my.parseCSV(dataset.data, dataset); - dfd.resolve({ - records: rows, - useMemoryStore: true - }); + var out = my.extractFields(my.parseCSV(dataset.data, dataset), dataset); + out.useMemoryStore = true; + dfd.resolve(out); } else if (dataset.url) { jQuery.get(dataset.url).done(function(data) { - var rows = my.parseCSV(data, dataset); - dfd.resolve({ - records: rows, - useMemoryStore: true - }); + var out = my.extractFields(my.parseCSV(data, dataset), dataset); + out.useMemoryStore = true; + dfd.resolve(out); }); } return dfd.promise(); }; + // Convert array of rows in { records: [ ...] , fields: [ ... ] } + // @param {Boolean} noHeaderRow If true assume that first row is not a header (i.e. list of fields but is data. + my.extractFields = function(rows, noFields) { + if (noFields.noHeaderRow !== true && rows.length > 0) { + return { + fields: rows[0], + records: rows.slice(1) + } + } else { + return { + records: rows + } + } + }; + // ## parseCSV // // Converts a Comma Separated Values string into an array of arrays. @@ -83,6 +93,8 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; // fields containing special characters, such as the delimiter or // quotechar, or which contain new-line characters. It defaults to '"' // + // @param {Integer} skipInitialRows A integer number of rows to skip (default 0) + // // Heavily based on uselesscode's JS CSV parser (MIT Licensed): // http://www.uselesscode.org/javascript/csv/ my.parseCSV= function(s, options) { @@ -128,7 +140,7 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; // If we are at a EOF or EOR if (inQuote === false && (cur === delimiter || cur === "\n")) { - field = processField(field); + field = processField(field); // Add the current field to the current row row.push(field); // If this is EOR append row to output and flush row @@ -168,6 +180,9 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; row.push(field); out.push(row); + // Expose the ability to discard initial rows + if (options.skipInitialRows) out = out.slice(options.skipInitialRows); + return out; }; @@ -295,6 +310,7 @@ this.recline.Backend = this.recline.Backend || {}; this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; (function(my) { + "use strict"; my.__type__ = 'dataproxy'; // URL for the dataproxy my.dataproxy_url = '//jsonpdataproxy.appspot.com'; @@ -304,7 +320,7 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; // use either jQuery or Underscore Deferred depending on what is available - var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; // ## load // @@ -367,465 +383,14 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; }(this.recline.Backend.DataProxy)); this.recline = this.recline || {}; this.recline.Backend = this.recline.Backend || {}; -this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; - -(function($, my) { - my.__type__ = 'elasticsearch'; - - // use either jQuery or Underscore Deferred depending on what is available - var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; - - // ## ElasticSearch Wrapper - // - // A simple JS wrapper around an [ElasticSearch](http://www.elasticsearch.org/) endpoints. - // - // @param {String} endpoint: url for ElasticSearch type/table, e.g. for ES running - // on http://localhost:9200 with index twitter and type tweet it would be: - // - //
    http://localhost:9200/twitter/tweet
    - // - // @param {Object} options: set of options such as: - // - // * headers - {dict of headers to add to each request} - // * dataType: dataType for AJAx requests e.g. set to jsonp to make jsonp requests (default is json requests) - my.Wrapper = function(endpoint, options) { - var self = this; - this.endpoint = endpoint; - this.options = _.extend({ - dataType: 'json' - }, - options); - - // ### mapping - // - // Get ES mapping for this type/table - // - // @return promise compatible deferred object. - this.mapping = function() { - var schemaUrl = self.endpoint + '/_mapping'; - var jqxhr = makeRequest({ - url: schemaUrl, - dataType: this.options.dataType - }); - return jqxhr; - }; - - // ### get - // - // Get record corresponding to specified id - // - // @return promise compatible deferred object. - this.get = function(id) { - var base = this.endpoint + '/' + id; - return makeRequest({ - url: base, - dataType: 'json' - }); - }; - - // ### upsert - // - // create / update a record to ElasticSearch backend - // - // @param {Object} doc an object to insert to the index. - // @return deferred supporting promise API - this.upsert = function(doc) { - var data = JSON.stringify(doc); - url = this.endpoint; - if (doc.id) { - url += '/' + doc.id; - } - return makeRequest({ - url: url, - type: 'POST', - data: data, - dataType: 'json' - }); - }; - - // ### delete - // - // Delete a record from the ElasticSearch backend. - // - // @param {Object} id id of object to delete - // @return deferred supporting promise API - this.remove = function(id) { - url = this.endpoint; - url += '/' + id; - return makeRequest({ - url: url, - type: 'DELETE', - dataType: 'json' - }); - }; - - this._normalizeQuery = function(queryObj) { - var self = this; - var queryInfo = (queryObj && queryObj.toJSON) ? queryObj.toJSON() : _.extend({}, queryObj); - var out = { - constant_score: { - query: {} - } - }; - if (!queryInfo.q) { - out.constant_score.query = { - match_all: {} - }; - } else { - out.constant_score.query = { - query_string: { - query: queryInfo.q - } - }; - } - if (queryInfo.filters && queryInfo.filters.length) { - out.constant_score.filter = { - and: [] - }; - _.each(queryInfo.filters, function(filter) { - out.constant_score.filter.and.push(self._convertFilter(filter)); - }); - } - return out; - }, - - // convert from Recline sort structure to ES form - // http://www.elasticsearch.org/guide/reference/api/search/sort.html - this._normalizeSort = function(sort) { - var out = _.map(sort, function(sortObj) { - var _tmp = {}; - var _tmp2 = _.clone(sortObj); - delete _tmp2['field']; - _tmp[sortObj.field] = _tmp2; - return _tmp; - }); - return out; - }, - - this._convertFilter = function(filter) { - var out = {}; - out[filter.type] = {} - if (filter.type === 'term') { - out.term[filter.field] = filter.term.toLowerCase(); - } else if (filter.type === 'geo_distance') { - out.geo_distance[filter.field] = filter.point; - out.geo_distance.distance = filter.distance; - out.geo_distance.unit = filter.unit; - } - return out; - }, - - // ### query - // - // @return deferred supporting promise API - this.query = function(queryObj) { - var esQuery = (queryObj && queryObj.toJSON) ? queryObj.toJSON() : _.extend({}, queryObj); - esQuery.query = this._normalizeQuery(queryObj); - delete esQuery.q; - delete esQuery.filters; - if (esQuery.sort && esQuery.sort.length > 0) { - esQuery.sort = this._normalizeSort(esQuery.sort); - } - var data = {source: JSON.stringify(esQuery)}; - var url = this.endpoint + '/_search'; - var jqxhr = makeRequest({ - url: url, - data: data, - dataType: this.options.dataType - }); - return jqxhr; - } - }; - - - // ## Recline Connectors - // - // Requires URL of ElasticSearch endpoint to be specified on the dataset - // via the url attribute. - - // ES options which are passed through to `options` on Wrapper (see Wrapper for details) - my.esOptions = {}; - - // ### fetch - my.fetch = function(dataset) { - var es = new my.Wrapper(dataset.url, my.esOptions); - var dfd = new Deferred(); - es.mapping().done(function(schema) { - - if (!schema){ - dfd.reject({'message':'Elastic Search did not return a mapping'}); - return; - } - - // only one top level key in ES = the type so we can ignore it - var key = _.keys(schema)[0]; - var fieldData = _.map(schema[key].properties, function(dict, fieldName) { - dict.id = fieldName; - return dict; - }); - dfd.resolve({ - fields: fieldData - }); - }) - .fail(function(arguments) { - dfd.reject(arguments); - }); - return dfd.promise(); - }; - - // ### save - my.save = function(changes, dataset) { - var es = new my.Wrapper(dataset.url, my.esOptions); - if (changes.creates.length + changes.updates.length + changes.deletes.length > 1) { - var dfd = new Deferred(); - msg = 'Saving more than one item at a time not yet supported'; - alert(msg); - dfd.reject(msg); - return dfd.promise(); - } - if (changes.creates.length > 0) { - return es.upsert(changes.creates[0]); - } - else if (changes.updates.length >0) { - return es.upsert(changes.updates[0]); - } else if (changes.deletes.length > 0) { - return es.remove(changes.deletes[0].id); - } - }; - - // ### query - my.query = function(queryObj, dataset) { - var dfd = new Deferred(); - var es = new my.Wrapper(dataset.url, my.esOptions); - var jqxhr = es.query(queryObj); - jqxhr.done(function(results) { - var out = { - total: results.hits.total - }; - out.hits = _.map(results.hits.hits, function(hit) { - if (!('id' in hit._source) && hit._id) { - hit._source.id = hit._id; - } - return hit._source; - }); - if (results.facets) { - out.facets = results.facets; - } - dfd.resolve(out); - }).fail(function(errorObj) { - var out = { - title: 'Failed: ' + errorObj.status + ' code', - message: errorObj.responseText - }; - dfd.reject(out); - }); - return dfd.promise(); - }; - - -// ### makeRequest -// -// Just $.ajax but in any headers in the 'headers' attribute of this -// Backend instance. Example: -// -//
    -// var jqxhr = this._makeRequest({
    -//   url: the-url
    -// });
    -// 
    -var makeRequest = function(data, headers) { - var extras = {}; - if (headers) { - extras = { - beforeSend: function(req) { - _.each(headers, function(value, key) { - req.setRequestHeader(key, value); - }); - } - }; - } - var data = _.extend(extras, data); - return $.ajax(data); -}; - -}(jQuery, this.recline.Backend.ElasticSearch)); - -this.recline = this.recline || {}; -this.recline.Backend = this.recline.Backend || {}; -this.recline.Backend.GDocs = this.recline.Backend.GDocs || {}; - -(function(my) { - my.__type__ = 'gdocs'; - - // use either jQuery or Underscore Deferred depending on what is available - var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; - - // ## Google spreadsheet backend - // - // Fetch data from a Google Docs spreadsheet. - // - // Dataset must have a url attribute pointing to the Gdocs or its JSON feed e.g. - //
    -  // var dataset = new recline.Model.Dataset({
    -  //     url: 'https://docs.google.com/spreadsheet/ccc?key=0Aon3JiuouxLUdGlQVDJnbjZRSU1tUUJWOUZXRG53VkE#gid=0'
    -  //   },
    -  //   'gdocs'
    -  // );
    -  //
    -  // var dataset = new recline.Model.Dataset({
    -  //     url: 'https://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values?alt=json'
    -  //   },
    -  //   'gdocs'
    -  // );
    -  // 
    - // - // @return object with two attributes - // - // * fields: array of Field objects - // * records: array of objects for each row - my.fetch = function(dataset) { - var dfd = new Deferred(); - var urls = my.getGDocsAPIUrls(dataset.url); - - // TODO cover it with tests - // get the spreadsheet title - (function () { - var titleDfd = new Deferred(); - - jQuery.getJSON(urls.spreadsheet, function (d) { - titleDfd.resolve({ - spreadsheetTitle: d.feed.title.$t - }); - }); - - return titleDfd.promise(); - }()).then(function (response) { - - // get the actual worksheet data - jQuery.getJSON(urls.worksheet, function(d) { - var result = my.parseData(d); - var fields = _.map(result.fields, function(fieldId) { - return {id: fieldId}; - }); - - dfd.resolve({ - metadata: { - title: response.spreadsheetTitle +" :: "+ result.worksheetTitle, - spreadsheetTitle: response.spreadsheetTitle, - worksheetTitle : result.worksheetTitle - }, - records : result.records, - fields : fields, - useMemoryStore: true - }); - }); - }); - - return dfd.promise(); - }; - - // ## parseData - // - // Parse data from Google Docs API into a reasonable form - // - // :options: (optional) optional argument dictionary: - // columnsToUse: list of columns to use (specified by field names) - // colTypes: dictionary (with column names as keys) specifying types (e.g. range, percent for use in conversion). - // :return: tabular data object (hash with keys: field and data). - // - // Issues: seems google docs return columns in rows in random order and not even sure whether consistent across rows. - my.parseData = function(gdocsSpreadsheet, options) { - var options = options || {}; - var colTypes = options.colTypes || {}; - var results = { - fields : [], - records: [] - }; - var entries = gdocsSpreadsheet.feed.entry || []; - var key; - var colName; - // percentage values (e.g. 23.3%) - var rep = /^([\d\.\-]+)\%$/; - - for(key in entries[0]) { - // it's barely possible it has inherited keys starting with 'gsx$' - if(/^gsx/.test(key)) { - colName = key.substr(4); - results.fields.push(colName); - } - } - - // converts non numberical values that should be numerical (22.3%[string] -> 0.223[float]) - results.records = _.map(entries, function(entry) { - var row = {}; - - _.each(results.fields, function(col) { - var _keyname = 'gsx$' + col; - var value = entry[_keyname].$t; - var num; - - // TODO cover this part of code with test - // TODO use the regexp only once - // if labelled as % and value contains %, convert - if(colTypes[col] === 'percent' && rep.test(value)) { - num = rep.exec(value)[1]; - value = parseFloat(num) / 100; - } - - row[col] = value; - }); - - return row; - }); - - results.worksheetTitle = gdocsSpreadsheet.feed.title.$t; - return results; - }; - - // Convenience function to get GDocs JSON API Url from standard URL - my.getGDocsAPIUrls = function(url) { - // https://docs.google.com/spreadsheet/ccc?key=XXXX#gid=YYY - var regex = /.*spreadsheet\/ccc?.*key=([^#?&+]+)[^#]*(#gid=([\d]+).*)?/; - var matches = url.match(regex); - var key; - var worksheet; - var urls; - - if(!!matches) { - key = matches[1]; - // the gid in url is 0-based and feed url is 1-based - worksheet = parseInt(matches[3]) + 1; - if (isNaN(worksheet)) { - worksheet = 1; - } - urls = { - worksheet : 'https://spreadsheets.google.com/feeds/list/'+ key +'/'+ worksheet +'/public/values?alt=json', - spreadsheet: 'https://spreadsheets.google.com/feeds/worksheets/'+ key +'/public/basic?alt=json' - } - } - else { - // we assume that it's one of the feeds urls - key = url.split('/')[5]; - // by default then, take first worksheet - worksheet = 1; - urls = { - worksheet : 'https://spreadsheets.google.com/feeds/list/'+ key +'/'+ worksheet +'/public/values?alt=json', - spreadsheet: 'https://spreadsheets.google.com/feeds/worksheets/'+ key +'/public/basic?alt=json' - } - } - - return urls; - }; -}(this.recline.Backend.GDocs)); -this.recline = this.recline || {}; -this.recline.Backend = this.recline.Backend || {}; this.recline.Backend.Memory = this.recline.Backend.Memory || {}; (function(my) { + "use strict"; my.__type__ = 'memory'; // private data - use either jQuery or Underscore Deferred depending on what is available - var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; // ## Data Wrapper // @@ -926,9 +491,9 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; integer: function (e) { return parseFloat(e, 10); }, 'float': function (e) { return parseFloat(e, 10); }, number: function (e) { return parseFloat(e, 10); }, - string : function (e) { return e.toString() }, - date : function (e) { return new Date(e).valueOf() }, - datetime : function (e) { return new Date(e).valueOf() } + string : function (e) { return e.toString(); }, + date : function (e) { return moment(e).valueOf(); }, + datetime : function (e) { return new Date(e).valueOf(); } }; var keyedFields = {}; _.each(self.fields, function(field) { @@ -959,12 +524,14 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; } function range(record, filter) { - var startnull = (filter.start == null || filter.start === ''); - var stopnull = (filter.stop == null || filter.stop === ''); + var filterStart = filter.start || filter.from; + var filterStop = filter.stop || filter.to; + var startnull = (_.isUndefined(filterStart) || filterStart === null || filterStart === ''); + var stopnull = (_.isUndefined(filterStop) || filterStop === null || filterStop === ''); var parse = getDataParser(filter); var value = parse(record[filter.field]); - var start = parse(filter.start); - var stop = parse(filter.stop); + var start = parse(startnull ? '' : filterStart); + var stop = parse(stopnull ? '' : filterStop); // if at least one end of range is set do not allow '' to get through // note that for strings '' <= {any-character} e.g. '' <= 'a' @@ -984,8 +551,8 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; if (queryObj.q) { var terms = queryObj.q.split(' '); var patterns=_.map(terms, function(term) { - return new RegExp(term.toLowerCase());; - }); + return new RegExp(term.toLowerCase()); + }); results = _.filter(results, function(rawdoc) { var matches = true; _.each(patterns, function(pattern) { @@ -1123,9 +690,10 @@ this.recline = this.recline || {}; this.recline.Model = this.recline.Model || {}; (function(my) { + "use strict"; // use either jQuery or Underscore Deferred depending on what is available -var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; +var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; // ## Dataset my.Dataset = Backbone.Model.extend({ @@ -1135,6 +703,7 @@ my.Dataset = Backbone.Model.extend({ // ### initialize initialize: function() { + var self = this; _.bindAll(this, 'query'); this.backend = null; if (this.get('backend')) { @@ -1154,8 +723,9 @@ my.Dataset = Backbone.Model.extend({ this.facets = new my.FacetList(); this.recordCount = null; this.queryState = new my.Query(); - this.queryState.bind('change', this.query); - this.queryState.bind('facet:add', this.query); + this.queryState.bind('change facet:add', function () { + self.query(); // We want to call query() without any arguments. + }); // store is what we query and save against // store will either be the backend or be a memory store if Backend fetch // tells us to use memory store @@ -1188,7 +758,13 @@ my.Dataset = Backbone.Model.extend({ } function handleResults(results) { - var out = self._normalizeRecordsAndFields(results.records, results.fields); + // if explicitly given the fields + // (e.g. var dataset = new Dataset({fields: fields, ...}) + // use that field info over anything we get back by parsing the data + // (results.fields) + var fields = self.get('fields') || results.fields; + + var out = self._normalizeRecordsAndFields(results.records, fields); if (results.useMemoryStore) { self._store = new recline.Backend.Memory.Store(out.records, out.fields); } @@ -1418,7 +994,7 @@ my.Record = Backbone.Model.extend({ // // NB: if field is undefined a default '' value will be returned getFieldValue: function(field) { - val = this.getFieldValueUnrendered(field); + var val = this.getFieldValueUnrendered(field); if (field && !_.isUndefined(field.renderer)) { val = field.renderer(val, field, this.toJSON()); } @@ -1709,7 +1285,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { - + "use strict"; // ## Graph view for a Dataset using Flot graphing library. // // Initialization arguments (in a hash in first parameter): @@ -1720,7 +1296,8 @@ this.recline.View = this.recline.View || {}; // { // group: {column name for x-axis}, // series: [{column name for series A}, {column name series B}, ... ], -// graphType: 'line', +// // options are: lines, points, lines-and-points, bars, columns +// graphType: 'lines', // graphOptions: {custom [flot options]} // } // @@ -1743,14 +1320,11 @@ my.Flot = Backbone.View.extend({ var self = this; this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"]; - this.el = $(this.el); _.bindAll(this, 'render', 'redraw', '_toolTip', '_xaxisLabel'); this.needToRedraw = false; - this.model.bind('change', this.render); - this.model.fields.bind('reset', this.render); - this.model.fields.bind('add', this.render); - this.model.records.bind('add', this.redraw); - this.model.records.bind('reset', this.redraw); + this.listenTo(this.model, 'change', this.render); + this.listenTo(this.model.fields, 'reset add', this.render); + this.listenTo(this.model.records, 'reset add', this.redraw); var stateData = _.extend({ group: null, // so that at least one series chooser box shows up @@ -1765,29 +1339,34 @@ my.Flot = Backbone.View.extend({ model: this.model, state: this.state.toJSON() }); - this.editor.state.bind('change', function() { + this.listenTo(this.editor.state, 'change', function() { self.state.set(self.editor.state.toJSON()); self.redraw(); }); - this.elSidebar = this.editor.el; + this.elSidebar = this.editor.$el; }, render: function() { var self = this; var tmplData = this.model.toTemplateJSON(); var htmls = Mustache.render(this.template, tmplData); - $(this.el).html(htmls); - this.$graph = this.el.find('.panel.graph'); + this.$el.html(htmls); + this.$graph = this.$el.find('.panel.graph'); this.$graph.on("plothover", this._toolTip); return this; }, + remove: function () { + this.editor.remove(); + Backbone.View.prototype.remove.apply(this, arguments); + }, + redraw: function() { // There are issues generating a Flot graph if either: // * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with // Uncaught Invalid dimensions for plot, width = 0, height = 0 // * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value' - var areWeVisible = !jQuery.expr.filters.hidden(this.el[0]); + var areWeVisible = !jQuery.expr.filters.hidden(this.el); if ((!areWeVisible || this.model.records.length === 0)) { this.needToRedraw = true; return; @@ -1858,25 +1437,17 @@ my.Flot = Backbone.View.extend({ }, _xaxisLabel: function (x) { - var xfield = this.model.fields.get(this.state.attributes.group); - - // time series - var xtype = xfield.get('type'); - var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time'); - - if (this.xvaluesAreIndex) { + if (this._groupFieldIsDateTime()) { + // oddly x comes through as milliseconds *string* (rather than int + // or float) so we have to reparse + x = new Date(parseFloat(x)).toLocaleDateString(); + } else if (this.xvaluesAreIndex) { x = parseInt(x, 10); // HACK: deal with bar graph style cases where x-axis items were strings // In this case x at this point is the index of the item in the list of // records not its actual x-axis value x = this.model.records.models[x].get(this.state.attributes.group); } - if (isDateTime) { - x = new Date(x).toLocaleDateString(); - } - // } else if (isDateTime) { - // x = new Date(parseInt(x, 10)).toLocaleDateString(); - // } return x; }, @@ -1891,25 +1462,26 @@ my.Flot = Backbone.View.extend({ // @param numPoints the number of points that will be plotted getGraphOptions: function(typeId, numPoints) { var self = this; - - var tickFormatter = function (x) { - // convert x to a string and make sure that it is not too long or the - // tick labels will overlap - // TODO: find a more accurate way of calculating the size of tick labels - var label = self._xaxisLabel(x) || ""; - - if (typeof label !== 'string') { - label = label.toString(); - } - if (self.state.attributes.graphType !== 'bars' && label.length > 10) { - label = label.slice(0, 10) + "..."; - } - - return label; - }; - + var groupFieldIsDateTime = self._groupFieldIsDateTime(); var xaxis = {}; - xaxis.tickFormatter = tickFormatter; + + if (!groupFieldIsDateTime) { + xaxis.tickFormatter = function (x) { + // convert x to a string and make sure that it is not too long or the + // tick labels will overlap + // TODO: find a more accurate way of calculating the size of tick labels + var label = self._xaxisLabel(x) || ""; + + if (typeof label !== 'string') { + label = label.toString(); + } + if (self.state.attributes.graphType !== 'bars' && label.length > 10) { + label = label.slice(0, 10) + "..."; + } + + return label; + }; + } // for labels case we only want ticks at the label intervals // HACK: however we also get this case with Date fields. In that case we @@ -1918,10 +1490,12 @@ my.Flot = Backbone.View.extend({ var numTicks = Math.min(this.model.records.length, 15); var increment = this.model.records.length / numTicks; var ticks = []; - for (i=0; i option'); + var options = this.$el.find(id + ' select > option'); if (options) { options.each(function(opt){ if (this.value == value) { @@ -2156,16 +1736,16 @@ my.FlotControls = Backbone.View.extend({ }, onEditorSubmit: function(e) { - var select = this.el.find('.editor-group select'); + var select = this.$el.find('.editor-group select'); var $editor = this; - var $series = this.el.find('.editor-series select'); + var $series = this.$el.find('.editor-series select'); var series = $series.map(function () { return $(this).val(); }); var updatedState = { series: $.makeArray(series), - group: this.el.find('.editor-group select').val(), - graphType: this.el.find('.editor-type select').val() + group: this.$el.find('.editor-group select').val(), + graphType: this.$el.find('.editor-type select').val() }; this.state.set(updatedState); }, @@ -2182,7 +1762,7 @@ my.FlotControls = Backbone.View.extend({ }, this.model.toTemplateJSON()); var htmls = Mustache.render(this.templateSeriesEditor, data); - this.el.find('.editor-series-group').append(htmls); + this.$el.find('.editor-series-group').append(htmls); return this; }, @@ -2213,6 +1793,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; // ## (Data) Grid Dataset View // // Provides a tabular view on a Dataset. @@ -2224,11 +1805,8 @@ my.Grid = Backbone.View.extend({ initialize: function(modelEtc) { var self = this; - this.el = $(this.el); _.bindAll(this, 'render', 'onHorizontalScroll'); - this.model.records.bind('add', this.render); - this.model.records.bind('reset', this.render); - this.model.records.bind('remove', this.render); + this.listenTo(this.model.records, 'add reset remove', this.render); this.tempState = {}; var state = _.extend({ hiddenFields: [] @@ -2268,7 +1846,7 @@ my.Grid = Backbone.View.extend({ onHorizontalScroll: function(e) { var currentScroll = $(e.target).scrollLeft(); - this.el.find('.recline-grid thead tr').scrollLeft(currentScroll); + this.$el.find('.recline-grid thead tr').scrollLeft(currentScroll); }, // ====================================================== @@ -2296,7 +1874,7 @@ my.Grid = Backbone.View.extend({ var modelData = this.model.toJSON(); modelData.notEmpty = ( this.fields.length > 0 ); // TODO: move this sort of thing into a toTemplateJSON method on Dataset? - modelData.fields = _.map(this.fields, function(field) { + modelData.fields = this.fields.map(function(field) { return field.toJSON(); }); // last header width = scroll bar - border (2px) */ @@ -2305,17 +1883,18 @@ my.Grid = Backbone.View.extend({ }, render: function() { var self = this; - this.fields = this.model.fields.filter(function(field) { + this.fields = new recline.Model.FieldList(this.model.fields.filter(function(field) { return _.indexOf(self.state.get('hiddenFields'), field.id) == -1; - }); + })); + this.scrollbarDimensions = this.scrollbarDimensions || this._scrollbarSize(); // skip measurement if already have dimensions var numFields = this.fields.length; // compute field widths (-20 for first menu col + 10px for padding on each col and finally 16px for the scrollbar) - var fullWidth = self.el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width; + var fullWidth = self.$el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width; var width = parseInt(Math.max(50, fullWidth / numFields), 10); // if columns extend outside viewport then remainder is 0 var remainder = Math.max(fullWidth - numFields * width,0); - _.each(this.fields, function(field, idx) { + this.fields.each(function(field, idx) { // add the remainder to the first field width so we make up full col if (idx === 0) { field.set({width: width+remainder}); @@ -2324,10 +1903,10 @@ my.Grid = Backbone.View.extend({ } }); var htmls = Mustache.render(this.template, this.toTemplateJSON()); - this.el.html(htmls); + this.$el.html(htmls); this.model.records.forEach(function(doc) { var tr = $(''); - self.el.find('tbody').append(tr); + self.$el.find('tbody').append(tr); var newView = new my.GridRow({ model: doc, el: tr, @@ -2336,12 +1915,12 @@ my.Grid = Backbone.View.extend({ newView.render(); }); // hide extra header col if no scrollbar to avoid unsightly overhang - var $tbody = this.el.find('tbody')[0]; + var $tbody = this.$el.find('tbody')[0]; if ($tbody.scrollHeight <= $tbody.offsetHeight) { - this.el.find('th.last-header').hide(); + this.$el.find('th.last-header').hide(); } - this.el.find('.recline-grid').toggleClass('no-hidden', (self.state.get('hiddenFields').length === 0)); - this.el.find('.recline-grid tbody').scroll(this.onHorizontalScroll); + this.$el.find('.recline-grid').toggleClass('no-hidden', (self.state.get('hiddenFields').length === 0)); + this.$el.find('.recline-grid tbody').scroll(this.onHorizontalScroll); return this; }, @@ -2377,8 +1956,7 @@ my.GridRow = Backbone.View.extend({ initialize: function(initData) { _.bindAll(this, 'render'); this._fields = initData.fields; - this.el = $(this.el); - this.model.bind('change', this.render); + this.listenTo(this.model, 'change', this.render); }, template: ' \ @@ -2411,9 +1989,9 @@ my.GridRow = Backbone.View.extend({ }, render: function() { - this.el.attr('data-id', this.model.id); + this.$el.attr('data-id', this.model.id); var html = Mustache.render(this.template, this.toTemplateJSON()); - $(this.el).html(html); + this.$el.html(html); return this; }, @@ -2433,7 +2011,7 @@ my.GridRow = Backbone.View.extend({ ', onEditClick: function(e) { - var editing = this.el.find('.data-table-cell-editor-editor'); + var editing = this.$el.find('.data-table-cell-editor-editor'); if (editing.length > 0) { editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden"); } @@ -2479,7 +2057,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { - + "use strict"; // ## Map view for a Dataset using Leaflet mapping library. // // This view allows to plot gereferenced records on a map. The location @@ -2503,6 +2081,8 @@ this.recline.View = this.recline.View || {}; // latField: {id of field containing latitude in the dataset} // autoZoom: true, // // use cluster support +// // cluster: true = always on +// // cluster: false = always off // cluster: false // } // @@ -2526,7 +2106,6 @@ my.Map = Backbone.View.extend({ initialize: function(options) { var self = this; - this.el = $(this.el); this.visible = true; this.mapReady = false; // this will be the Leaflet L.Map object (setup below) @@ -2553,32 +2132,32 @@ my.Map = Backbone.View.extend({ }; // Listen to changes in the fields - this.model.fields.bind('change', function() { + this.listenTo(this.model.fields, 'change', function() { self._setupGeometryField(); self.render(); }); // Listen to changes in the records - this.model.records.bind('add', function(doc){self.redraw('add',doc);}); - this.model.records.bind('change', function(doc){ + this.listenTo(this.model.records, 'add', function(doc){self.redraw('add',doc);}); + this.listenTo(this.model.records, 'change', function(doc){ self.redraw('remove',doc); self.redraw('add',doc); }); - this.model.records.bind('remove', function(doc){self.redraw('remove',doc);}); - this.model.records.bind('reset', function(){self.redraw('reset');}); + this.listenTo(this.model.records, 'remove', function(doc){self.redraw('remove',doc);}); + this.listenTo(this.model.records, 'reset', function(){self.redraw('reset');}); this.menu = new my.MapMenu({ model: this.model, state: this.state.toJSON() }); - this.menu.state.bind('change', function() { + this.listenTo(this.menu.state, 'change', function() { self.state.set(self.menu.state.toJSON()); self.redraw(); }); - this.state.bind('change', function() { + this.listenTo(this.state, 'change', function() { self.redraw(); }); - this.elSidebar = this.menu.el; + this.elSidebar = this.menu.$el; }, // ## Customization Functions @@ -2598,7 +2177,7 @@ my.Map = Backbone.View.extend({ // } infobox: function(record) { var html = ''; - for (key in record.attributes){ + for (var key in record.attributes){ if (!(this.state.get('geomField') && key == this.state.get('geomField'))){ html += '
    ' + key + ': '+ record.attributes[key] + '
    '; } @@ -2647,10 +2226,9 @@ my.Map = Backbone.View.extend({ // Also sets up the editor fields and the map if necessary. render: function() { var self = this; - - htmls = Mustache.render(this.template, this.model.toTemplateJSON()); - $(this.el).html(htmls); - this.$map = this.el.find('.panel.map'); + var htmls = Mustache.render(this.template, this.model.toTemplateJSON()); + this.$el.html(htmls); + this.$map = this.$el.find('.panel.map'); this.redraw(); return this; }, @@ -2694,15 +2272,6 @@ my.Map = Backbone.View.extend({ this._remove(doc); } - // enable clustering if there is a large number of markers - var countAfter = 0; - this.features.eachLayer(function(){countAfter++;}); - var sizeIncreased = countAfter - countBefore > 0; - if (!this.state.get('cluster') && countAfter > 64 && sizeIncreased) { - this.state.set({cluster: true}); - return; - } - // this must come before zooming! // if not: errors when using e.g. circle markers like // "Cannot call method 'project' of undefined" @@ -2801,7 +2370,7 @@ my.Map = Backbone.View.extend({ if (!(docs instanceof Array)) docs = [docs]; _.each(docs,function(doc){ - for (key in self.features._layers){ + for (var key in self.features._layers){ if (self.features._layers[key].feature.properties.cid == doc.cid){ self.features.removeLayer(self.features._layers[key]); } @@ -3008,7 +2577,6 @@ my.MapMenu = Backbone.View.extend({ Cluster markers \ \ \ - \ \ ', @@ -3022,11 +2590,10 @@ my.MapMenu = Backbone.View.extend({ initialize: function(options) { var self = this; - this.el = $(this.el); _.bindAll(this, 'render'); - this.model.fields.bind('change', this.render); + this.listenTo(this.model.fields, 'change', this.render); this.state = new recline.Model.ObjectState(options.state); - this.state.bind('change', this.render); + this.listenTo(this.state, 'change', this.render); this.render(); }, @@ -3035,28 +2602,28 @@ my.MapMenu = Backbone.View.extend({ // Also sets up the editor fields and the map if necessary. render: function() { var self = this; - htmls = Mustache.render(this.template, this.model.toTemplateJSON()); - $(this.el).html(htmls); + var htmls = Mustache.render(this.template, this.model.toTemplateJSON()); + this.$el.html(htmls); if (this._geomReady() && this.model.fields.length){ if (this.state.get('geomField')){ this._selectOption('editor-geom-field',this.state.get('geomField')); - this.el.find('#editor-field-type-geom').attr('checked','checked').change(); + this.$el.find('#editor-field-type-geom').attr('checked','checked').change(); } else{ this._selectOption('editor-lon-field',this.state.get('lonField')); this._selectOption('editor-lat-field',this.state.get('latField')); - this.el.find('#editor-field-type-latlon').attr('checked','checked').change(); + this.$el.find('#editor-field-type-latlon').attr('checked','checked').change(); } } if (this.state.get('autoZoom')) { - this.el.find('#editor-auto-zoom').attr('checked', 'checked'); + this.$el.find('#editor-auto-zoom').attr('checked', 'checked'); } else { - this.el.find('#editor-auto-zoom').removeAttr('checked'); + this.$el.find('#editor-auto-zoom').removeAttr('checked'); } if (this.state.get('cluster')) { - this.el.find('#editor-cluster').attr('checked', 'checked'); + this.$el.find('#editor-cluster').attr('checked', 'checked'); } else { - this.el.find('#editor-cluster').removeAttr('checked'); + this.$el.find('#editor-cluster').removeAttr('checked'); } return this; }, @@ -3075,17 +2642,17 @@ my.MapMenu = Backbone.View.extend({ // onEditorSubmit: function(e){ e.preventDefault(); - if (this.el.find('#editor-field-type-geom').attr('checked')){ + if (this.$el.find('#editor-field-type-geom').attr('checked')){ this.state.set({ - geomField: this.el.find('.editor-geom-field > select > option:selected').val(), + geomField: this.$el.find('.editor-geom-field > select > option:selected').val(), lonField: null, latField: null }); } else { this.state.set({ geomField: null, - lonField: this.el.find('.editor-lon-field > select > option:selected').val(), - latField: this.el.find('.editor-lat-field > select > option:selected').val() + lonField: this.$el.find('.editor-lon-field > select > option:selected').val(), + latField: this.$el.find('.editor-lat-field > select > option:selected').val() }); } return false; @@ -3096,11 +2663,11 @@ my.MapMenu = Backbone.View.extend({ // onFieldTypeChange: function(e){ if (e.target.value == 'geom'){ - this.el.find('.editor-field-type-geom').show(); - this.el.find('.editor-field-type-latlon').hide(); + this.$el.find('.editor-field-type-geom').show(); + this.$el.find('.editor-field-type-latlon').hide(); } else { - this.el.find('.editor-field-type-geom').hide(); - this.el.find('.editor-field-type-latlon').show(); + this.$el.find('.editor-field-type-geom').hide(); + this.$el.find('.editor-field-type-latlon').show(); } }, @@ -3115,7 +2682,7 @@ my.MapMenu = Backbone.View.extend({ // Private: Helper function to select an option from a select list // _selectOption: function(id,value){ - var options = this.el.find('.' + id + ' > select > option'); + var options = this.$el.find('.' + id + ' > select > option'); if (options){ options.each(function(opt){ if (this.value == value) { @@ -3136,6 +2703,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; // ## MultiView // // Manage multiple views together along with query editor etc. Usage: @@ -3260,7 +2828,6 @@ my.MultiView = Backbone.View.extend({ initialize: function(options) { var self = this; - this.el = $(this.el); this._setupState(options.state); // Hash of 'page' views (i.e. those for whole page) keyed by page name @@ -3330,43 +2897,39 @@ my.MultiView = Backbone.View.extend({ } this._showHideSidebar(); - this.model.bind('query:start', function() { - self.notify({loader: true, persist: true}); - }); - this.model.bind('query:done', function() { - self.clearNotifications(); - self.el.find('.doc-count').text(self.model.recordCount || 'Unknown'); - }); - this.model.bind('query:fail', function(error) { - self.clearNotifications(); - var msg = ''; - if (typeof(error) == 'string') { - msg = error; - } else if (typeof(error) == 'object') { - if (error.title) { - msg = error.title + ': '; - } - if (error.message) { - msg += error.message; - } - } else { - msg = 'There was an error querying the backend'; + this.listenTo(this.model, 'query:start', function() { + self.notify({loader: true, persist: true}); + }); + this.listenTo(this.model, 'query:done', function() { + self.clearNotifications(); + self.$el.find('.doc-count').text(self.model.recordCount || 'Unknown'); + }); + this.listenTo(this.model, 'query:fail', function(error) { + self.clearNotifications(); + var msg = ''; + if (typeof(error) == 'string') { + msg = error; + } else if (typeof(error) == 'object') { + if (error.title) { + msg = error.title + ': '; } - self.notify({message: msg, category: 'error', persist: true}); - }); + if (error.message) { + msg += error.message; + } + } else { + msg = 'There was an error querying the backend'; + } + self.notify({message: msg, category: 'error', persist: true}); + }); // retrieve basic data like fields etc // note this.model and dataset returned are the same // TODO: set query state ...? this.model.queryState.set(self.state.get('query'), {silent: true}); - this.model.fetch() - .fail(function(error) { - self.notify({message: error.message, category: 'error', persist: true}); - }); }, setReadOnly: function() { - this.el.addClass('recline-read-only'); + this.$el.addClass('recline-read-only'); }, render: function() { @@ -3374,11 +2937,11 @@ my.MultiView = Backbone.View.extend({ tmplData.views = this.pageViews; tmplData.sidebarViews = this.sidebarViews; var template = Mustache.render(this.template, tmplData); - $(this.el).html(template); + this.$el.html(template); // now create and append other views - var $dataViewContainer = this.el.find('.data-view-container'); - var $dataSidebar = this.el.find('.data-view-sidebar'); + var $dataViewContainer = this.$el.find('.data-view-container'); + var $dataSidebar = this.$el.find('.data-view-sidebar'); // the main views _.each(this.pageViews, function(view, pageName) { @@ -3390,25 +2953,37 @@ my.MultiView = Backbone.View.extend({ }); _.each(this.sidebarViews, function(view) { - this['$'+view.id] = view.view.el; + this['$'+view.id] = view.view.$el; $dataSidebar.append(view.view.el); }, this); - var pager = new recline.View.Pager({ + this.pager = new recline.View.Pager({ model: this.model.queryState }); - this.el.find('.recline-results-info').after(pager.el); + this.$el.find('.recline-results-info').after(this.pager.el); - var queryEditor = new recline.View.QueryEditor({ + this.queryEditor = new recline.View.QueryEditor({ model: this.model.queryState }); - this.el.find('.query-editor-here').append(queryEditor.el); + this.$el.find('.query-editor-here').append(this.queryEditor.el); }, + remove: function () { + _.each(this.pageViews, function (view) { + view.view.remove(); + }); + _.each(this.sidebarViews, function (view) { + view.view.remove(); + }); + this.pager.remove(); + this.queryEditor.remove(); + Backbone.View.prototype.remove.apply(this, arguments); + }, + // hide the sidebar if empty _showHideSidebar: function() { - var $dataSidebar = this.el.find('.data-view-sidebar'); + var $dataSidebar = this.$el.find('.data-view-sidebar'); var visibleChildren = $dataSidebar.children().filter(function() { return $(this).css("display") != "none"; }).length; @@ -3421,19 +2996,19 @@ my.MultiView = Backbone.View.extend({ }, updateNav: function(pageName) { - this.el.find('.navigation a').removeClass('active'); - var $el = this.el.find('.navigation a[data-view="' + pageName + '"]'); + this.$el.find('.navigation a').removeClass('active'); + var $el = this.$el.find('.navigation a[data-view="' + pageName + '"]'); $el.addClass('active'); // add/remove sidebars and hide inactive views _.each(this.pageViews, function(view, idx) { if (view.id === pageName) { - view.view.el.show(); + view.view.$el.show(); if (view.view.elSidebar) { view.view.elSidebar.show(); } } else { - view.view.el.hide(); + view.view.$el.hide(); if (view.view.elSidebar) { view.view.elSidebar.hide(); } @@ -3502,7 +3077,7 @@ my.MultiView = Backbone.View.extend({ _bindStateChanges: function() { var self = this; // finally ensure we update our state object when state of sub-object changes so that state is always up to date - this.model.queryState.bind('change', function() { + this.listenTo(this.model.queryState, 'change', function() { self.state.set({query: self.model.queryState.toJSON()}); }); _.each(this.pageViews, function(pageView) { @@ -3510,7 +3085,7 @@ my.MultiView = Backbone.View.extend({ var update = {}; update['view-' + pageView.id] = pageView.view.state.toJSON(); self.state.set(update); - pageView.view.state.bind('change', function() { + self.listenTo(pageView.view.state, 'change', function() { var update = {}; update['view-' + pageView.id] = pageView.view.state.toJSON(); // had problems where change not being triggered for e.g. grid view so let's do it explicitly @@ -3524,7 +3099,7 @@ my.MultiView = Backbone.View.extend({ _bindFlashNotifications: function() { var self = this; _.each(this.pageViews, function(pageView) { - pageView.view.bind('recline:flash', function(flash) { + self.listenTo(pageView.view, 'recline:flash', function(flash) { self.notify(flash); }); }); @@ -3650,7 +3225,7 @@ my.parseQueryString = function(q) { // Parse the query string out of the URL hash my.parseHashQueryString = function() { - q = my.parseHashUrl(window.location.hash).query; + var q = my.parseHashUrl(window.location.hash).query; return my.parseQueryString(q); }; @@ -3690,6 +3265,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; // ## SlickGrid Dataset View // // Provides a tabular view on a Dataset, based on SlickGrid. @@ -3719,13 +3295,10 @@ this.recline.View = this.recline.View || {}; my.SlickGrid = Backbone.View.extend({ initialize: function(modelEtc) { var self = this; - this.el = $(this.el); - this.el.addClass('recline-slickgrid'); - _.bindAll(this, 'render'); - this.model.records.bind('add', this.render); - this.model.records.bind('reset', this.render); - this.model.records.bind('remove', this.render); - this.model.records.bind('change', this.onRecordChanged, this); + this.$el.addClass('recline-slickgrid'); + _.bindAll(this, 'render', 'onRecordChanged'); + this.listenTo(this.model.records, 'add remove reset', this.render); + this.listenTo(this.model.records, 'change', this.onRecordChanged); var state = _.extend({ hiddenColumns: [], @@ -3739,6 +3312,8 @@ my.SlickGrid = Backbone.View.extend({ ); this.state = new recline.Model.ObjectState(state); + + this._slickHandler = new Slick.EventHandler(); }, events: { @@ -3804,7 +3379,7 @@ my.SlickGrid = Backbone.View.extend({ }); // Restrict the visible columns - var visibleColumns = columns.filter(function(column) { + var visibleColumns = _.filter(columns, function(column) { return _.indexOf(self.state.get('hiddenColumns'), column.id) === -1; }); @@ -3850,7 +3425,7 @@ my.SlickGrid = Backbone.View.extend({ this.getItem = function(index) {return rows[index];}; this.getItemMetadata = function(index) {return {};}; this.getModel = function(index) {return models[index];}; - this.getModelRow = function(m) {return models.indexOf(m);}; + this.getModelRow = function(m) {return _.indexOf(models, m);}; this.updateItem = function(m,i) { rows[i] = toRow(m); models[i] = m; @@ -3873,7 +3448,7 @@ my.SlickGrid = Backbone.View.extend({ this.grid.setSortColumn(column, sortAsc); } - this.grid.onSort.subscribe(function(e, args){ + this._slickHandler.subscribe(this.grid.onSort, function(e, args){ var order = (args.sortAsc) ? 'asc':'desc'; var sort = [{ field: args.sortCol.field, @@ -3882,7 +3457,7 @@ my.SlickGrid = Backbone.View.extend({ self.model.query({sort: sort}); }); - this.grid.onColumnsReordered.subscribe(function(e, args){ + this._slickHandler.subscribe(this.grid.onColumnsReordered, function(e, args){ self.state.set({columnsOrder: _.pluck(self.grid.getColumns(),'id')}); }); @@ -3898,7 +3473,7 @@ my.SlickGrid = Backbone.View.extend({ self.state.set({columnsWidth:columnsWidth}); }); - this.grid.onCellChange.subscribe(function (e, args) { + this._slickHandler.subscribe(this.grid.onCellChange, function (e, args) { // We need to change the model associated value // var grid = args.grid; @@ -3921,7 +3496,12 @@ my.SlickGrid = Backbone.View.extend({ } return this; - }, + }, + + remove: function () { + this._slickHandler.unsubscribeAll(); + Backbone.View.prototype.remove.apply(this, arguments); + }, show: function() { // If the div is hidden, SlickGrid will calculate wrongly some @@ -4067,6 +3647,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; // turn off unnecessary logging from VMM Timeline if (typeof VMM !== 'undefined') { VMM.debug = false; @@ -4090,18 +3671,20 @@ my.Timeline = Backbone.View.extend({ initialize: function(options) { var self = this; - this.el = $(this.el); - this.timeline = new VMM.Timeline(); + this.timeline = new VMM.Timeline(this.elementId); this._timelineIsInitialized = false; - this.model.fields.bind('reset', function() { + this.listenTo(this.model.fields, 'reset', function() { self._setupTemporalField(); }); - this.model.records.bind('all', function() { + this.listenTo(this.model.records, 'all', function() { self.reloadData(); }); var stateData = _.extend({ startField: null, endField: null, + // by default timelinejs (and browsers) will parse ambiguous dates in US format (mm/dd/yyyy) + // set to true to interpret dd/dd/dddd as dd/mm/yyyy + nonUSDates: false, timelineJSOptions: {} }, options.state @@ -4113,7 +3696,7 @@ my.Timeline = Backbone.View.extend({ render: function() { var tmplData = {}; var htmls = Mustache.render(this.template, tmplData); - this.el.html(htmls); + this.$el.html(htmls); // can only call _initTimeline once view in DOM as Timeline uses $ // internally to look up element if ($(this.elementId).length > 0) { @@ -4129,9 +3712,10 @@ my.Timeline = Backbone.View.extend({ }, _initTimeline: function() { - var $timeline = this.el.find(this.elementId); var data = this._timelineJSON(); - this.timeline.init(data, this.elementId, this.state.get("timelineJSOptions")); + var config = this.state.get("timelineJSOptions"); + config.id = this.elementId; + this.timeline.init(config, data); this._timelineIsInitialized = true }, @@ -4193,19 +3777,33 @@ my.Timeline = Backbone.View.extend({ return out; }, + // convert dates into a format TimelineJS will handle + // TimelineJS does not document this at all so combo of read the code + + // trial and error + // Summary (AFAICt): + // Preferred: [-]yyyy[,mm,dd,hh,mm,ss] + // Supported: mm/dd/yyyy _parseDate: function(date) { if (!date) { return null; } - var out = date.trim(); + var out = $.trim(date); out = out.replace(/(\d)th/g, '$1'); out = out.replace(/(\d)st/g, '$1'); - out = out.trim() ? moment(out) : null; - if (out.toDate() == 'Invalid Date') { - return null; - } else { - return out.toDate(); + out = $.trim(out); + if (out.match(/\d\d\d\d-\d\d-\d\d(T.*)?/)) { + out = out.replace(/-/g, ',').replace('T', ',').replace(':',','); } + if (out.match(/\d\d-\d\d-\d\d.*/)) { + out = out.replace(/-/g, '/'); + } + if (this.state.get('nonUSDates')) { + var parts = out.match(/(\d\d)\/(\d\d)\/(\d\d.*)/); + if (parts) { + out = [parts[2], parts[1], parts[3]].join('/'); + } + } + return out; }, _setupTemporalField: function() { @@ -4234,6 +3832,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; // ## FacetViewer // @@ -4271,9 +3870,8 @@ my.FacetViewer = Backbone.View.extend({ }, initialize: function(model) { _.bindAll(this, 'render'); - this.el = $(this.el); - this.model.facets.bind('all', this.render); - this.model.fields.bind('all', this.render); + this.listenTo(this.model.facets, 'all', this.render); + this.listenTo(this.model.fields, 'all', this.render); this.render(); }, render: function() { @@ -4290,17 +3888,17 @@ my.FacetViewer = Backbone.View.extend({ return facet; }); var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); + this.$el.html(templated); // are there actually any facets to show? if (this.model.facets.length > 0) { - this.el.show(); + this.$el.show(); } else { - this.el.hide(); + this.$el.hide(); } }, onHide: function(e) { e.preventDefault(); - this.el.hide(); + this.$el.hide(); }, onFacetFilter: function(e) { e.preventDefault(); @@ -4338,7 +3936,8 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { - + "use strict"; + my.Fields = Backbone.View.extend({ className: 'recline-fields-view', template: ' \ @@ -4377,13 +3976,12 @@ my.Fields = Backbone.View.extend({ initialize: function(model) { var self = this; - this.el = $(this.el); _.bindAll(this, 'render'); // TODO: this is quite restrictive in terms of when it is re-run // e.g. a change in type will not trigger a re-run atm. // being more liberal (e.g. binding to all) can lead to being called a lot (e.g. for change:width) - this.model.fields.bind('reset', function(action) { + this.listenTo(this.model.fields, 'reset', function(action) { self.model.fields.each(function(field) { field.facets.unbind('all', self.render); field.facets.bind('all', self.render); @@ -4392,7 +3990,7 @@ my.Fields = Backbone.View.extend({ self.model.getFieldsSummary(); self.render(); }); - this.el.find('.collapse').collapse(); + this.$el.find('.collapse').collapse(); this.render(); }, render: function() { @@ -4406,7 +4004,7 @@ my.Fields = Backbone.View.extend({ tmplData.fields.push(out); }); var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); + this.$el.html(templated); } }); @@ -4417,6 +4015,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; my.FilterEditor = Backbone.View.extend({ className: 'recline-filter-editor well', @@ -4501,11 +4100,9 @@ my.FilterEditor = Backbone.View.extend({ 'submit form.js-add': 'onAddFilter' }, initialize: function() { - this.el = $(this.el); _.bindAll(this, 'render'); - this.model.fields.bind('all', this.render); - this.model.queryState.bind('change', this.render); - this.model.queryState.bind('change:filters:new-blank', this.render); + this.listenTo(this.model.fields, 'all', this.render); + this.listenTo(this.model.queryState, 'change change:filters:new-blank', this.render); this.render(); }, render: function() { @@ -4521,13 +4118,13 @@ my.FilterEditor = Backbone.View.extend({ return Mustache.render(self.filterTemplates[this.type], this); }; var out = Mustache.render(this.template, tmplData); - this.el.html(out); + this.$el.html(out); }, onAddFilterShow: function(e) { e.preventDefault(); var $target = $(e.target); $target.hide(); - this.el.find('form.js-add').show(); + this.$el.find('form.js-add').show(); }, onAddFilter: function(e) { e.preventDefault(); @@ -4587,6 +4184,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; my.Pager = Backbone.View.extend({ className: 'recline-pager', @@ -4607,14 +4205,13 @@ my.Pager = Backbone.View.extend({ initialize: function() { _.bindAll(this, 'render'); - this.el = $(this.el); - this.model.bind('change', this.render); + this.listenTo(this.model, 'change', this.render); this.render(); }, onFormSubmit: function(e) { e.preventDefault(); - var newFrom = parseInt(this.el.find('input[name="from"]').val()); - var newSize = parseInt(this.el.find('input[name="to"]').val()) - newFrom; + var newFrom = parseInt(this.$el.find('input[name="from"]').val()); + var newSize = parseInt(this.$el.find('input[name="to"]').val()) - newFrom; newFrom = Math.max(newFrom, 0); newSize = Math.max(newSize, 1); this.model.set({size: newSize, from: newFrom}); @@ -4635,7 +4232,7 @@ my.Pager = Backbone.View.extend({ var tmplData = this.model.toJSON(); tmplData.to = this.model.get('from') + this.model.get('size'); var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); + this.$el.html(templated); } }); @@ -4647,6 +4244,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; my.QueryEditor = Backbone.View.extend({ className: 'recline-query-editor', @@ -4666,19 +4264,18 @@ my.QueryEditor = Backbone.View.extend({ initialize: function() { _.bindAll(this, 'render'); - this.el = $(this.el); - this.model.bind('change', this.render); + this.listenTo(this.model, 'change', this.render); this.render(); }, onFormSubmit: function(e) { e.preventDefault(); - var query = this.el.find('.text-query input').val(); + var query = this.$el.find('.text-query input').val(); this.model.set({q: query}); }, render: function() { var tmplData = this.model.toJSON(); var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); + this.$el.html(templated); } }); @@ -4690,6 +4287,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; my.ValueFilter = Backbone.View.extend({ className: 'recline-filter-editor well', @@ -4736,11 +4334,9 @@ my.ValueFilter = Backbone.View.extend({ 'submit form.js-add': 'onAddFilter' }, initialize: function() { - this.el = $(this.el); _.bindAll(this, 'render'); - this.model.fields.bind('all', this.render); - this.model.queryState.bind('change', this.render); - this.model.queryState.bind('change:filters:new-blank', this.render); + this.listenTo(this.model.fields, 'all', this.render); + this.listenTo(this.model.queryState, 'change change:filters:new-blank', this.render); this.render(); }, render: function() { @@ -4756,7 +4352,7 @@ my.ValueFilter = Backbone.View.extend({ return Mustache.render(self.filterTemplates.term, this); }; var out = Mustache.render(this.template, tmplData); - this.el.html(out); + this.$el.html(out); }, updateFilter: function(input) { var self = this; @@ -4770,7 +4366,7 @@ my.ValueFilter = Backbone.View.extend({ e.preventDefault(); var $target = $(e.target); $target.hide(); - this.el.find('form.js-add').show(); + this.$el.find('form.js-add').show(); }, onAddFilter: function(e) { e.preventDefault(); diff --git a/docs/tutorial-backends.markdown b/docs/tutorial-backends.markdown index 2f8c3594..1b27fb0a 100644 --- a/docs/tutorial-backends.markdown +++ b/docs/tutorial-backends.markdown @@ -76,8 +76,8 @@ much more limited if you are just using a Backend. Specifically: - - + + @@ -99,6 +99,8 @@ a bespoke chooser and a Kartograph (svg-only) map. {% highlight javascript %} +// include the Recline backend for Google Docs + {% include example-backends-gdocs.js %} {% endhighlight %} @@ -106,26 +108,19 @@ a bespoke chooser and a Kartograph (svg-only) map.
     
    + + -## Loading Data from ElasticSearch and the DataHub +## Loading Data from ElasticSearch -Recline supports ElasticSearch as a full read/write/query backend. It also means that Recline can load data from the [DataHub's](http://datahub.io/) data API as that is ElasticSearch compatible. Here's an example, using [this dataset about Rendition flights](http://datahub.io/dataset/rendition-on-record/ac5a28ea-eb52-4b0a-a399-5dcc1becf9d9') on the DataHub: +Recline supports ElasticSearch as a full read/write/query backend via the +[ElasticSearch.js library][esjs]. See the library for examples. -{% highlight javascript %} -{% include example-backends-elasticsearch.js %} -{% endhighlight %} - -### Result - -
     
    - - +[esjs]: https://github.com/okfn/elasticsearch.js ## Loading data from CSV files diff --git a/docs/tutorial-maps.markdown b/docs/tutorial-maps.markdown index 914d7d52..60ab0a1d 100644 --- a/docs/tutorial-maps.markdown +++ b/docs/tutorial-maps.markdown @@ -102,3 +102,30 @@ view.featureparse = function (e) { }; {% endhighlight %} + +### Turning on clustering + +You can turn on clustering of markers by setting the cluster option: + + var map = new recline.View.Map({ + model: dataset + state: { + cluster: true; + } + }); + +You could also enable marker clustering only if you have more than a +certain number of markers. Here's an example: + + // var map is recline.View.Map instance + // marker cluster threshold + var threshold = 65; + + // enable clustering if there is a large number of markers + var countAfter = 0; + map.features.eachLayer(function(){countAfter++;}); + if (countAfter > threshold) { + // note the map will auto-redraw when you change state! + map.state.set({cluster: true}); + } + diff --git a/docs/tutorial-views.markdown b/docs/tutorial-views.markdown index 48f460f9..f8d4a916 100644 --- a/docs/tutorial-views.markdown +++ b/docs/tutorial-views.markdown @@ -130,6 +130,7 @@ library and the Recline Flot Graph view: + {% endhighlight %} @@ -247,11 +248,11 @@ First, add the additional dependencies for the timeline view. The timeline is bu {% highlight html %} - + - - + + {% endhighlight %} Now, create a new div for the map (must have an explicit height for the timeline to render): diff --git a/src/backend.csv.js b/src/backend.csv.js index aacfc5e9..6d805827 100644 --- a/src/backend.csv.js +++ b/src/backend.csv.js @@ -4,10 +4,11 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; // Note that provision of jQuery is optional (it is **only** needed if you use fetch on a remote file) (function(my) { + "use strict"; my.__type__ = 'csv'; // use either jQuery or Underscore Deferred depending on what is available - var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; // ## fetch // @@ -32,37 +33,46 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; var reader = new FileReader(); var encoding = dataset.encoding || 'UTF-8'; reader.onload = function(e) { - var rows = my.parseCSV(e.target.result, dataset); - dfd.resolve({ - records: rows, - metadata: { - filename: dataset.file.name - }, - useMemoryStore: true - }); + var out = my.extractFields(my.parseCSV(e.target.result, dataset), dataset); + out.useMemoryStore = true; + out.metadata = { + filename: dataset.file.name + } + dfd.resolve(out); }; reader.onerror = function (e) { alert('Failed to load file. Code: ' + e.target.error.code); }; reader.readAsText(dataset.file, encoding); } else if (dataset.data) { - var rows = my.parseCSV(dataset.data, dataset); - dfd.resolve({ - records: rows, - useMemoryStore: true - }); + var out = my.extractFields(my.parseCSV(dataset.data, dataset), dataset); + out.useMemoryStore = true; + dfd.resolve(out); } else if (dataset.url) { jQuery.get(dataset.url).done(function(data) { - var rows = my.parseCSV(data, dataset); - dfd.resolve({ - records: rows, - useMemoryStore: true - }); + var out = my.extractFields(my.parseCSV(data, dataset), dataset); + out.useMemoryStore = true; + dfd.resolve(out); }); } return dfd.promise(); }; + // Convert array of rows in { records: [ ...] , fields: [ ... ] } + // @param {Boolean} noHeaderRow If true assume that first row is not a header (i.e. list of fields but is data. + my.extractFields = function(rows, noFields) { + if (noFields.noHeaderRow !== true && rows.length > 0) { + return { + fields: rows[0], + records: rows.slice(1) + } + } else { + return { + records: rows + } + } + }; + // ## parseCSV // // Converts a Comma Separated Values string into an array of arrays. @@ -83,6 +93,8 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; // fields containing special characters, such as the delimiter or // quotechar, or which contain new-line characters. It defaults to '"' // + // @param {Integer} skipInitialRows A integer number of rows to skip (default 0) + // // Heavily based on uselesscode's JS CSV parser (MIT Licensed): // http://www.uselesscode.org/javascript/csv/ my.parseCSV= function(s, options) { @@ -128,7 +140,7 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; // If we are at a EOF or EOR if (inQuote === false && (cur === delimiter || cur === "\n")) { - field = processField(field); + field = processField(field); // Add the current field to the current row row.push(field); // If this is EOR append row to output and flush row @@ -168,6 +180,9 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {}; row.push(field); out.push(row); + // Expose the ability to discard initial rows + if (options.skipInitialRows) out = out.slice(options.skipInitialRows); + return out; }; diff --git a/src/backend.dataproxy.js b/src/backend.dataproxy.js index ffe36792..92b5ae7c 100644 --- a/src/backend.dataproxy.js +++ b/src/backend.dataproxy.js @@ -3,6 +3,7 @@ this.recline.Backend = this.recline.Backend || {}; this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; (function(my) { + "use strict"; my.__type__ = 'dataproxy'; // URL for the dataproxy my.dataproxy_url = '//jsonpdataproxy.appspot.com'; @@ -12,7 +13,7 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {}; // use either jQuery or Underscore Deferred depending on what is available - var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; // ## load // diff --git a/src/backend.elasticsearch.js b/src/backend.elasticsearch.js deleted file mode 100644 index 82ba52de..00000000 --- a/src/backend.elasticsearch.js +++ /dev/null @@ -1,285 +0,0 @@ -this.recline = this.recline || {}; -this.recline.Backend = this.recline.Backend || {}; -this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {}; - -(function($, my) { - my.__type__ = 'elasticsearch'; - - // use either jQuery or Underscore Deferred depending on what is available - var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; - - // ## ElasticSearch Wrapper - // - // A simple JS wrapper around an [ElasticSearch](http://www.elasticsearch.org/) endpoints. - // - // @param {String} endpoint: url for ElasticSearch type/table, e.g. for ES running - // on http://localhost:9200 with index twitter and type tweet it would be: - // - //
    http://localhost:9200/twitter/tweet
    - // - // @param {Object} options: set of options such as: - // - // * headers - {dict of headers to add to each request} - // * dataType: dataType for AJAx requests e.g. set to jsonp to make jsonp requests (default is json requests) - my.Wrapper = function(endpoint, options) { - var self = this; - this.endpoint = endpoint; - this.options = _.extend({ - dataType: 'json' - }, - options); - - // ### mapping - // - // Get ES mapping for this type/table - // - // @return promise compatible deferred object. - this.mapping = function() { - var schemaUrl = self.endpoint + '/_mapping'; - var jqxhr = makeRequest({ - url: schemaUrl, - dataType: this.options.dataType - }); - return jqxhr; - }; - - // ### get - // - // Get record corresponding to specified id - // - // @return promise compatible deferred object. - this.get = function(id) { - var base = this.endpoint + '/' + id; - return makeRequest({ - url: base, - dataType: 'json' - }); - }; - - // ### upsert - // - // create / update a record to ElasticSearch backend - // - // @param {Object} doc an object to insert to the index. - // @return deferred supporting promise API - this.upsert = function(doc) { - var data = JSON.stringify(doc); - url = this.endpoint; - if (doc.id) { - url += '/' + doc.id; - } - return makeRequest({ - url: url, - type: 'POST', - data: data, - dataType: 'json' - }); - }; - - // ### delete - // - // Delete a record from the ElasticSearch backend. - // - // @param {Object} id id of object to delete - // @return deferred supporting promise API - this.remove = function(id) { - url = this.endpoint; - url += '/' + id; - return makeRequest({ - url: url, - type: 'DELETE', - dataType: 'json' - }); - }; - - this._normalizeQuery = function(queryObj) { - var self = this; - var queryInfo = (queryObj && queryObj.toJSON) ? queryObj.toJSON() : _.extend({}, queryObj); - var out = { - constant_score: { - query: {} - } - }; - if (!queryInfo.q) { - out.constant_score.query = { - match_all: {} - }; - } else { - out.constant_score.query = { - query_string: { - query: queryInfo.q - } - }; - } - if (queryInfo.filters && queryInfo.filters.length) { - out.constant_score.filter = { - and: [] - }; - _.each(queryInfo.filters, function(filter) { - out.constant_score.filter.and.push(self._convertFilter(filter)); - }); - } - return out; - }, - - // convert from Recline sort structure to ES form - // http://www.elasticsearch.org/guide/reference/api/search/sort.html - this._normalizeSort = function(sort) { - var out = _.map(sort, function(sortObj) { - var _tmp = {}; - var _tmp2 = _.clone(sortObj); - delete _tmp2['field']; - _tmp[sortObj.field] = _tmp2; - return _tmp; - }); - return out; - }, - - this._convertFilter = function(filter) { - var out = {}; - out[filter.type] = {} - if (filter.type === 'term') { - out.term[filter.field] = filter.term.toLowerCase(); - } else if (filter.type === 'geo_distance') { - out.geo_distance[filter.field] = filter.point; - out.geo_distance.distance = filter.distance; - out.geo_distance.unit = filter.unit; - } - return out; - }, - - // ### query - // - // @return deferred supporting promise API - this.query = function(queryObj) { - var esQuery = (queryObj && queryObj.toJSON) ? queryObj.toJSON() : _.extend({}, queryObj); - esQuery.query = this._normalizeQuery(queryObj); - delete esQuery.q; - delete esQuery.filters; - if (esQuery.sort && esQuery.sort.length > 0) { - esQuery.sort = this._normalizeSort(esQuery.sort); - } - var data = {source: JSON.stringify(esQuery)}; - var url = this.endpoint + '/_search'; - var jqxhr = makeRequest({ - url: url, - data: data, - dataType: this.options.dataType - }); - return jqxhr; - } - }; - - - // ## Recline Connectors - // - // Requires URL of ElasticSearch endpoint to be specified on the dataset - // via the url attribute. - - // ES options which are passed through to `options` on Wrapper (see Wrapper for details) - my.esOptions = {}; - - // ### fetch - my.fetch = function(dataset) { - var es = new my.Wrapper(dataset.url, my.esOptions); - var dfd = new Deferred(); - es.mapping().done(function(schema) { - - if (!schema){ - dfd.reject({'message':'Elastic Search did not return a mapping'}); - return; - } - - // only one top level key in ES = the type so we can ignore it - var key = _.keys(schema)[0]; - var fieldData = _.map(schema[key].properties, function(dict, fieldName) { - dict.id = fieldName; - return dict; - }); - dfd.resolve({ - fields: fieldData - }); - }) - .fail(function(arguments) { - dfd.reject(arguments); - }); - return dfd.promise(); - }; - - // ### save - my.save = function(changes, dataset) { - var es = new my.Wrapper(dataset.url, my.esOptions); - if (changes.creates.length + changes.updates.length + changes.deletes.length > 1) { - var dfd = new Deferred(); - msg = 'Saving more than one item at a time not yet supported'; - alert(msg); - dfd.reject(msg); - return dfd.promise(); - } - if (changes.creates.length > 0) { - return es.upsert(changes.creates[0]); - } - else if (changes.updates.length >0) { - return es.upsert(changes.updates[0]); - } else if (changes.deletes.length > 0) { - return es.remove(changes.deletes[0].id); - } - }; - - // ### query - my.query = function(queryObj, dataset) { - var dfd = new Deferred(); - var es = new my.Wrapper(dataset.url, my.esOptions); - var jqxhr = es.query(queryObj); - jqxhr.done(function(results) { - var out = { - total: results.hits.total - }; - out.hits = _.map(results.hits.hits, function(hit) { - if (!('id' in hit._source) && hit._id) { - hit._source.id = hit._id; - } - return hit._source; - }); - if (results.facets) { - out.facets = results.facets; - } - dfd.resolve(out); - }).fail(function(errorObj) { - var out = { - title: 'Failed: ' + errorObj.status + ' code', - message: errorObj.responseText - }; - dfd.reject(out); - }); - return dfd.promise(); - }; - - -// ### makeRequest -// -// Just $.ajax but in any headers in the 'headers' attribute of this -// Backend instance. Example: -// -//
    -// var jqxhr = this._makeRequest({
    -//   url: the-url
    -// });
    -// 
    -var makeRequest = function(data, headers) { - var extras = {}; - if (headers) { - extras = { - beforeSend: function(req) { - _.each(headers, function(value, key) { - req.setRequestHeader(key, value); - }); - } - }; - } - var data = _.extend(extras, data); - return $.ajax(data); -}; - -}(jQuery, this.recline.Backend.ElasticSearch)); - diff --git a/src/backend.gdocs.js b/src/backend.gdocs.js deleted file mode 100644 index 4d18aa9b..00000000 --- a/src/backend.gdocs.js +++ /dev/null @@ -1,167 +0,0 @@ -this.recline = this.recline || {}; -this.recline.Backend = this.recline.Backend || {}; -this.recline.Backend.GDocs = this.recline.Backend.GDocs || {}; - -(function(my) { - my.__type__ = 'gdocs'; - - // use either jQuery or Underscore Deferred depending on what is available - var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; - - // ## Google spreadsheet backend - // - // Fetch data from a Google Docs spreadsheet. - // - // Dataset must have a url attribute pointing to the Gdocs or its JSON feed e.g. - //
    -  // var dataset = new recline.Model.Dataset({
    -  //     url: 'https://docs.google.com/spreadsheet/ccc?key=0Aon3JiuouxLUdGlQVDJnbjZRSU1tUUJWOUZXRG53VkE#gid=0'
    -  //   },
    -  //   'gdocs'
    -  // );
    -  //
    -  // var dataset = new recline.Model.Dataset({
    -  //     url: 'https://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values?alt=json'
    -  //   },
    -  //   'gdocs'
    -  // );
    -  // 
    - // - // @return object with two attributes - // - // * fields: array of Field objects - // * records: array of objects for each row - my.fetch = function(dataset) { - var dfd = new Deferred(); - var urls = my.getGDocsAPIUrls(dataset.url); - - // TODO cover it with tests - // get the spreadsheet title - (function () { - var titleDfd = new Deferred(); - - jQuery.getJSON(urls.spreadsheet, function (d) { - titleDfd.resolve({ - spreadsheetTitle: d.feed.title.$t - }); - }); - - return titleDfd.promise(); - }()).then(function (response) { - - // get the actual worksheet data - jQuery.getJSON(urls.worksheet, function(d) { - var result = my.parseData(d); - var fields = _.map(result.fields, function(fieldId) { - return {id: fieldId}; - }); - - dfd.resolve({ - metadata: { - title: response.spreadsheetTitle +" :: "+ result.worksheetTitle, - spreadsheetTitle: response.spreadsheetTitle, - worksheetTitle : result.worksheetTitle - }, - records : result.records, - fields : fields, - useMemoryStore: true - }); - }); - }); - - return dfd.promise(); - }; - - // ## parseData - // - // Parse data from Google Docs API into a reasonable form - // - // :options: (optional) optional argument dictionary: - // columnsToUse: list of columns to use (specified by field names) - // colTypes: dictionary (with column names as keys) specifying types (e.g. range, percent for use in conversion). - // :return: tabular data object (hash with keys: field and data). - // - // Issues: seems google docs return columns in rows in random order and not even sure whether consistent across rows. - my.parseData = function(gdocsSpreadsheet, options) { - var options = options || {}; - var colTypes = options.colTypes || {}; - var results = { - fields : [], - records: [] - }; - var entries = gdocsSpreadsheet.feed.entry || []; - var key; - var colName; - // percentage values (e.g. 23.3%) - var rep = /^([\d\.\-]+)\%$/; - - for(key in entries[0]) { - // it's barely possible it has inherited keys starting with 'gsx$' - if(/^gsx/.test(key)) { - colName = key.substr(4); - results.fields.push(colName); - } - } - - // converts non numberical values that should be numerical (22.3%[string] -> 0.223[float]) - results.records = _.map(entries, function(entry) { - var row = {}; - - _.each(results.fields, function(col) { - var _keyname = 'gsx$' + col; - var value = entry[_keyname].$t; - var num; - - // TODO cover this part of code with test - // TODO use the regexp only once - // if labelled as % and value contains %, convert - if(colTypes[col] === 'percent' && rep.test(value)) { - num = rep.exec(value)[1]; - value = parseFloat(num) / 100; - } - - row[col] = value; - }); - - return row; - }); - - results.worksheetTitle = gdocsSpreadsheet.feed.title.$t; - return results; - }; - - // Convenience function to get GDocs JSON API Url from standard URL - my.getGDocsAPIUrls = function(url) { - // https://docs.google.com/spreadsheet/ccc?key=XXXX#gid=YYY - var regex = /.*spreadsheet\/ccc?.*key=([^#?&+]+)[^#]*(#gid=([\d]+).*)?/; - var matches = url.match(regex); - var key; - var worksheet; - var urls; - - if(!!matches) { - key = matches[1]; - // the gid in url is 0-based and feed url is 1-based - worksheet = parseInt(matches[3]) + 1; - if (isNaN(worksheet)) { - worksheet = 1; - } - urls = { - worksheet : 'https://spreadsheets.google.com/feeds/list/'+ key +'/'+ worksheet +'/public/values?alt=json', - spreadsheet: 'https://spreadsheets.google.com/feeds/worksheets/'+ key +'/public/basic?alt=json' - } - } - else { - // we assume that it's one of the feeds urls - key = url.split('/')[5]; - // by default then, take first worksheet - worksheet = 1; - urls = { - worksheet : 'https://spreadsheets.google.com/feeds/list/'+ key +'/'+ worksheet +'/public/values?alt=json', - spreadsheet: 'https://spreadsheets.google.com/feeds/worksheets/'+ key +'/public/basic?alt=json' - } - } - - return urls; - }; -}(this.recline.Backend.GDocs)); diff --git a/src/backend.memory.js b/src/backend.memory.js index 07e14fd6..0c83a7d1 100644 --- a/src/backend.memory.js +++ b/src/backend.memory.js @@ -3,10 +3,11 @@ this.recline.Backend = this.recline.Backend || {}; this.recline.Backend.Memory = this.recline.Backend.Memory || {}; (function(my) { + "use strict"; my.__type__ = 'memory'; // private data - use either jQuery or Underscore Deferred depending on what is available - var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; + var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; // ## Data Wrapper // @@ -107,9 +108,9 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; integer: function (e) { return parseFloat(e, 10); }, 'float': function (e) { return parseFloat(e, 10); }, number: function (e) { return parseFloat(e, 10); }, - string : function (e) { return e.toString() }, - date : function (e) { return new Date(e).valueOf() }, - datetime : function (e) { return new Date(e).valueOf() } + string : function (e) { return e.toString(); }, + date : function (e) { return moment(e).valueOf(); }, + datetime : function (e) { return new Date(e).valueOf(); } }; var keyedFields = {}; _.each(self.fields, function(field) { @@ -140,19 +141,19 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; } function range(record, filter) { - var startnull = (filter.start == null || filter.start === ''); - var stopnull = (filter.stop == null || filter.stop === ''); + var fromnull = (_.isUndefined(filter.from) || filter.from === null || filter.from === ''); + var tonull = (_.isUndefined(filter.to) || filter.to === null || filter.to === ''); var parse = getDataParser(filter); var value = parse(record[filter.field]); - var start = parse(filter.start); - var stop = parse(filter.stop); + var from = parse(fromnull ? '' : filter.from); + var to = parse(tonull ? '' : filter.to); // if at least one end of range is set do not allow '' to get through // note that for strings '' <= {any-character} e.g. '' <= 'a' - if ((!startnull || !stopnull) && value === '') { + if ((!fromnull || !tonull) && value === '') { return false; } - return ((startnull || value >= start) && (stopnull || value <= stop)); + return ((fromnull || value >= from) && (tonull || value <= to)); } function geo_distance() { @@ -165,8 +166,8 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {}; if (queryObj.q) { var terms = queryObj.q.split(' '); var patterns=_.map(terms, function(term) { - return new RegExp(term.toLowerCase());; - }); + return new RegExp(term.toLowerCase()); + }); results = _.filter(results, function(rawdoc) { var matches = true; _.each(patterns, function(pattern) { diff --git a/src/model.js b/src/model.js index 1e6e6441..ee6b84b3 100644 --- a/src/model.js +++ b/src/model.js @@ -3,9 +3,10 @@ this.recline = this.recline || {}; this.recline.Model = this.recline.Model || {}; (function(my) { + "use strict"; // use either jQuery or Underscore Deferred depending on what is available -var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred; +var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred; // ## Dataset my.Dataset = Backbone.Model.extend({ @@ -15,6 +16,7 @@ my.Dataset = Backbone.Model.extend({ // ### initialize initialize: function() { + var self = this; _.bindAll(this, 'query'); this.backend = null; if (this.get('backend')) { @@ -34,17 +36,26 @@ my.Dataset = Backbone.Model.extend({ this.facets = new my.FacetList(); this.recordCount = null; this.queryState = new my.Query(); - this.queryState.bind('change', this.query); - this.queryState.bind('facet:add', this.query); + this.queryState.bind('change facet:add', function () { + self.query(); // We want to call query() without any arguments. + }); // store is what we query and save against // store will either be the backend or be a memory store if Backend fetch // tells us to use memory store this._store = this.backend; + + // if backend has a handleQueryResultFunction, use that + this._handleResult = (this.backend != null && _.has(this.backend, 'handleQueryResult')) ? + this.backend.handleQueryResult : this._handleQueryResult; if (this.backend == recline.Backend.Memory) { this.fetch(); } }, + sync: function(method, model, options) { + return this.backend.sync(method, model, options); + }, + // ### fetch // // Retrieve dataset and (some) records from the backend. @@ -68,7 +79,13 @@ my.Dataset = Backbone.Model.extend({ } function handleResults(results) { - var out = self._normalizeRecordsAndFields(results.records, results.fields); + // if explicitly given the fields + // (e.g. var dataset = new Dataset({fields: fields, ...}) + // use that field info over anything we get back by parsing the data + // (results.fields) + var fields = self.get('fields') || results.fields; + + var out = self._normalizeRecordsAndFields(results.records, fields); if (results.useMemoryStore) { self._store = new recline.Backend.Memory.Store(out.records, out.fields); } @@ -180,7 +197,7 @@ my.Dataset = Backbone.Model.extend({ this._store.query(actualQuery, this.toJSON()) .done(function(queryResult) { - self._handleQueryResult(queryResult); + self._handleResult(queryResult); self.trigger('query:done'); dfd.resolve(self.records); }) @@ -298,7 +315,7 @@ my.Record = Backbone.Model.extend({ // // NB: if field is undefined a default '' value will be returned getFieldValue: function(field) { - val = this.getFieldValueUnrendered(field); + var val = this.getFieldValueUnrendered(field); if (field && !_.isUndefined(field.renderer)) { val = field.renderer(val, field, this.toJSON()); } @@ -472,8 +489,8 @@ my.Query = Backbone.Model.extend({ }, range: { type: 'range', - start: '', - stop: '' + from: '', + to: '' }, geo_distance: { type: 'geo_distance', @@ -501,6 +518,23 @@ my.Query = Backbone.Model.extend({ filters.push(ourfilter); this.trigger('change:filters:new-blank'); }, + replaceFilter: function(filter) { + // delete filter on the same field, then add + var filters = this.get('filters'); + var idx = -1; + _.each(this.get('filters'), function(f, key, list) { + if (filter.field == f.field) { + idx = key; + } + }); + // trigger just one event (change:filters:new-blank) instead of one for remove and + // one for add + if (idx >= 0) { + filters.splice(idx, 1); + this.set({filters: filters}); + } + this.addFilter(filter); + }, updateFilter: function(index, value) { }, // ### removeFilter @@ -517,7 +551,7 @@ my.Query = Backbone.Model.extend({ // Add a Facet to this query // // See - addFacet: function(fieldId) { + addFacet: function(fieldId, size, silent) { var facets = this.get('facets'); // Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) if (_.contains(_.keys(facets), fieldId)) { @@ -526,8 +560,13 @@ my.Query = Backbone.Model.extend({ facets[fieldId] = { terms: { field: fieldId } }; + if (!_.isUndefined(size)) { + facets[fieldId].terms.size = size; + } this.set({facets: facets}, {silent: true}); - this.trigger('facet:add', this); + if (!silent) { + this.trigger('facet:add', this); + } }, addHistogramFacet: function(fieldId) { var facets = this.get('facets'); @@ -539,7 +578,30 @@ my.Query = Backbone.Model.extend({ }; this.set({facets: facets}, {silent: true}); this.trigger('facet:add', this); + }, + removeFacet: function(fieldId) { + var facets = this.get('facets'); + // Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) + if (!_.contains(_.keys(facets), fieldId)) { + return; + } + delete facets[fieldId]; + this.set({facets: facets}, {silent: true}); + this.trigger('facet:remove', this); + }, + clearFacets: function() { + var facets = this.get('facets'); + _.each(_.keys(facets), function(fieldId) { + delete facets[fieldId]; + }); + this.trigger('facet:remove', this); + }, + // trigger a facet add; use this to trigger a single event after adding + // multiple facets + refreshFacets: function() { + this.trigger('facet:add', this); } + }); @@ -577,9 +639,9 @@ my.ObjectState = Backbone.Model.extend({ // ## Backbone.sync // // Override Backbone.sync to hand off to sync function in relevant backend -Backbone.sync = function(method, model, options) { - return model.backend.sync(method, model, options); -}; +// Backbone.sync = function(method, model, options) { +// return model.backend.sync(method, model, options); +// }; }(this.recline.Model)); diff --git a/src/view.flot.js b/src/view.flot.js index b50c6000..62605eb3 100644 --- a/src/view.flot.js +++ b/src/view.flot.js @@ -4,7 +4,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { - + "use strict"; // ## Graph view for a Dataset using Flot graphing library. // // Initialization arguments (in a hash in first parameter): @@ -15,7 +15,8 @@ this.recline.View = this.recline.View || {}; // { // group: {column name for x-axis}, // series: [{column name for series A}, {column name series B}, ... ], -// graphType: 'line', +// // options are: lines, points, lines-and-points, bars, columns +// graphType: 'lines', // graphOptions: {custom [flot options]} // } // @@ -38,14 +39,11 @@ my.Flot = Backbone.View.extend({ var self = this; this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"]; - this.el = $(this.el); _.bindAll(this, 'render', 'redraw', '_toolTip', '_xaxisLabel'); this.needToRedraw = false; - this.model.bind('change', this.render); - this.model.fields.bind('reset', this.render); - this.model.fields.bind('add', this.render); - this.model.records.bind('add', this.redraw); - this.model.records.bind('reset', this.redraw); + this.listenTo(this.model, 'change', this.render); + this.listenTo(this.model.fields, 'reset add', this.render); + this.listenTo(this.model.records, 'reset add', this.redraw); var stateData = _.extend({ group: null, // so that at least one series chooser box shows up @@ -60,29 +58,34 @@ my.Flot = Backbone.View.extend({ model: this.model, state: this.state.toJSON() }); - this.editor.state.bind('change', function() { + this.listenTo(this.editor.state, 'change', function() { self.state.set(self.editor.state.toJSON()); self.redraw(); }); - this.elSidebar = this.editor.el; + this.elSidebar = this.editor.$el; }, render: function() { var self = this; var tmplData = this.model.toTemplateJSON(); var htmls = Mustache.render(this.template, tmplData); - $(this.el).html(htmls); - this.$graph = this.el.find('.panel.graph'); + this.$el.html(htmls); + this.$graph = this.$el.find('.panel.graph'); this.$graph.on("plothover", this._toolTip); return this; }, + remove: function () { + this.editor.remove(); + Backbone.View.prototype.remove.apply(this, arguments); + }, + redraw: function() { // There are issues generating a Flot graph if either: // * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with // Uncaught Invalid dimensions for plot, width = 0, height = 0 // * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value' - var areWeVisible = !jQuery.expr.filters.hidden(this.el[0]); + var areWeVisible = !jQuery.expr.filters.hidden(this.el); if ((!areWeVisible || this.model.records.length === 0)) { this.needToRedraw = true; return; @@ -153,25 +156,17 @@ my.Flot = Backbone.View.extend({ }, _xaxisLabel: function (x) { - var xfield = this.model.fields.get(this.state.attributes.group); - - // time series - var xtype = xfield.get('type'); - var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time'); - - if (this.xvaluesAreIndex) { + if (this._groupFieldIsDateTime()) { + // oddly x comes through as milliseconds *string* (rather than int + // or float) so we have to reparse + x = new Date(parseFloat(x)).toLocaleDateString(); + } else if (this.xvaluesAreIndex) { x = parseInt(x, 10); // HACK: deal with bar graph style cases where x-axis items were strings // In this case x at this point is the index of the item in the list of // records not its actual x-axis value x = this.model.records.models[x].get(this.state.attributes.group); } - if (isDateTime) { - x = new Date(x).toLocaleDateString(); - } - // } else if (isDateTime) { - // x = new Date(parseInt(x, 10)).toLocaleDateString(); - // } return x; }, @@ -186,25 +181,26 @@ my.Flot = Backbone.View.extend({ // @param numPoints the number of points that will be plotted getGraphOptions: function(typeId, numPoints) { var self = this; - - var tickFormatter = function (x) { - // convert x to a string and make sure that it is not too long or the - // tick labels will overlap - // TODO: find a more accurate way of calculating the size of tick labels - var label = self._xaxisLabel(x) || ""; - - if (typeof label !== 'string') { - label = label.toString(); - } - if (self.state.attributes.graphType !== 'bars' && label.length > 10) { - label = label.slice(0, 10) + "..."; - } - - return label; - }; - + var groupFieldIsDateTime = self._groupFieldIsDateTime(); var xaxis = {}; - xaxis.tickFormatter = tickFormatter; + + if (!groupFieldIsDateTime) { + xaxis.tickFormatter = function (x) { + // convert x to a string and make sure that it is not too long or the + // tick labels will overlap + // TODO: find a more accurate way of calculating the size of tick labels + var label = self._xaxisLabel(x) || ""; + + if (typeof label !== 'string') { + label = label.toString(); + } + if (self.state.attributes.graphType !== 'bars' && label.length > 10) { + label = label.slice(0, 10) + "..."; + } + + return label; + }; + } // for labels case we only want ticks at the label intervals // HACK: however we also get this case with Date fields. In that case we @@ -213,10 +209,12 @@ my.Flot = Backbone.View.extend({ var numTicks = Math.min(this.model.records.length, 15); var increment = this.model.records.length / numTicks; var ticks = []; - for (i=0; i option'); + var options = this.$el.find(id + ' select > option'); if (options) { options.each(function(opt){ if (this.value == value) { @@ -451,16 +467,16 @@ my.FlotControls = Backbone.View.extend({ }, onEditorSubmit: function(e) { - var select = this.el.find('.editor-group select'); + var select = this.$el.find('.editor-group select'); var $editor = this; - var $series = this.el.find('.editor-series select'); + var $series = this.$el.find('.editor-series select'); var series = $series.map(function () { return $(this).val(); }); var updatedState = { series: $.makeArray(series), - group: this.el.find('.editor-group select').val(), - graphType: this.el.find('.editor-type select').val() + group: this.$el.find('.editor-group select').val(), + graphType: this.$el.find('.editor-type select').val() }; this.state.set(updatedState); }, @@ -477,7 +493,7 @@ my.FlotControls = Backbone.View.extend({ }, this.model.toTemplateJSON()); var htmls = Mustache.render(this.templateSeriesEditor, data); - this.el.find('.editor-series-group').append(htmls); + this.$el.find('.editor-series-group').append(htmls); return this; }, diff --git a/src/view.grid.js b/src/view.grid.js index 2a6a2b55..af3733b0 100644 --- a/src/view.grid.js +++ b/src/view.grid.js @@ -4,6 +4,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; // ## (Data) Grid Dataset View // // Provides a tabular view on a Dataset. @@ -15,11 +16,8 @@ my.Grid = Backbone.View.extend({ initialize: function(modelEtc) { var self = this; - this.el = $(this.el); _.bindAll(this, 'render', 'onHorizontalScroll'); - this.model.records.bind('add', this.render); - this.model.records.bind('reset', this.render); - this.model.records.bind('remove', this.render); + this.listenTo(this.model.records, 'add reset remove', this.render); this.tempState = {}; var state = _.extend({ hiddenFields: [] @@ -59,7 +57,7 @@ my.Grid = Backbone.View.extend({ onHorizontalScroll: function(e) { var currentScroll = $(e.target).scrollLeft(); - this.el.find('.recline-grid thead tr').scrollLeft(currentScroll); + this.$el.find('.recline-grid thead tr').scrollLeft(currentScroll); }, // ====================================================== @@ -87,7 +85,7 @@ my.Grid = Backbone.View.extend({ var modelData = this.model.toJSON(); modelData.notEmpty = ( this.fields.length > 0 ); // TODO: move this sort of thing into a toTemplateJSON method on Dataset? - modelData.fields = _.map(this.fields, function(field) { + modelData.fields = this.fields.map(function(field) { return field.toJSON(); }); // last header width = scroll bar - border (2px) */ @@ -96,17 +94,18 @@ my.Grid = Backbone.View.extend({ }, render: function() { var self = this; - this.fields = this.model.fields.filter(function(field) { + this.fields = new recline.Model.FieldList(this.model.fields.filter(function(field) { return _.indexOf(self.state.get('hiddenFields'), field.id) == -1; - }); + })); + this.scrollbarDimensions = this.scrollbarDimensions || this._scrollbarSize(); // skip measurement if already have dimensions var numFields = this.fields.length; // compute field widths (-20 for first menu col + 10px for padding on each col and finally 16px for the scrollbar) - var fullWidth = self.el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width; + var fullWidth = self.$el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width; var width = parseInt(Math.max(50, fullWidth / numFields), 10); // if columns extend outside viewport then remainder is 0 var remainder = Math.max(fullWidth - numFields * width,0); - _.each(this.fields, function(field, idx) { + this.fields.each(function(field, idx) { // add the remainder to the first field width so we make up full col if (idx === 0) { field.set({width: width+remainder}); @@ -115,10 +114,10 @@ my.Grid = Backbone.View.extend({ } }); var htmls = Mustache.render(this.template, this.toTemplateJSON()); - this.el.html(htmls); + this.$el.html(htmls); this.model.records.forEach(function(doc) { var tr = $(''); - self.el.find('tbody').append(tr); + self.$el.find('tbody').append(tr); var newView = new my.GridRow({ model: doc, el: tr, @@ -127,12 +126,12 @@ my.Grid = Backbone.View.extend({ newView.render(); }); // hide extra header col if no scrollbar to avoid unsightly overhang - var $tbody = this.el.find('tbody')[0]; + var $tbody = this.$el.find('tbody')[0]; if ($tbody.scrollHeight <= $tbody.offsetHeight) { - this.el.find('th.last-header').hide(); + this.$el.find('th.last-header').hide(); } - this.el.find('.recline-grid').toggleClass('no-hidden', (self.state.get('hiddenFields').length === 0)); - this.el.find('.recline-grid tbody').scroll(this.onHorizontalScroll); + this.$el.find('.recline-grid').toggleClass('no-hidden', (self.state.get('hiddenFields').length === 0)); + this.$el.find('.recline-grid tbody').scroll(this.onHorizontalScroll); return this; }, @@ -168,8 +167,7 @@ my.GridRow = Backbone.View.extend({ initialize: function(initData) { _.bindAll(this, 'render'); this._fields = initData.fields; - this.el = $(this.el); - this.model.bind('change', this.render); + this.listenTo(this.model, 'change', this.render); }, template: ' \ @@ -202,9 +200,9 @@ my.GridRow = Backbone.View.extend({ }, render: function() { - this.el.attr('data-id', this.model.id); + this.$el.attr('data-id', this.model.id); var html = Mustache.render(this.template, this.toTemplateJSON()); - $(this.el).html(html); + this.$el.html(html); return this; }, @@ -224,7 +222,7 @@ my.GridRow = Backbone.View.extend({ ', onEditClick: function(e) { - var editing = this.el.find('.data-table-cell-editor-editor'); + var editing = this.$el.find('.data-table-cell-editor-editor'); if (editing.length > 0) { editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden"); } diff --git a/src/view.map.js b/src/view.map.js index d6de52fc..a90270ce 100644 --- a/src/view.map.js +++ b/src/view.map.js @@ -4,13 +4,13 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { - + "use strict"; // ## Map view for a Dataset using Leaflet mapping library. // // This view allows to plot gereferenced records on a map. The location // information can be provided in 2 ways: // -// 1. Via a single field. This field must be either a geo_point or +// 1. Via a single field. This field must be either a geo_point or // [GeoJSON](http://geojson.org) object // 2. Via two fields with latitude and longitude coordinates. // @@ -28,6 +28,8 @@ this.recline.View = this.recline.View || {}; // latField: {id of field containing latitude in the dataset} // autoZoom: true, // // use cluster support +// // cluster: true = always on +// // cluster: false = always off // cluster: false // } // @@ -51,7 +53,6 @@ my.Map = Backbone.View.extend({ initialize: function(options) { var self = this; - this.el = $(this.el); this.visible = true; this.mapReady = false; // this will be the Leaflet L.Map object (setup below) @@ -78,32 +79,32 @@ my.Map = Backbone.View.extend({ }; // Listen to changes in the fields - this.model.fields.bind('change', function() { + this.listenTo(this.model.fields, 'change', function() { self._setupGeometryField(); self.render(); }); // Listen to changes in the records - this.model.records.bind('add', function(doc){self.redraw('add',doc);}); - this.model.records.bind('change', function(doc){ + this.listenTo(this.model.records, 'add', function(doc){self.redraw('add',doc);}); + this.listenTo(this.model.records, 'change', function(doc){ self.redraw('remove',doc); self.redraw('add',doc); }); - this.model.records.bind('remove', function(doc){self.redraw('remove',doc);}); - this.model.records.bind('reset', function(){self.redraw('reset');}); + this.listenTo(this.model.records, 'remove', function(doc){self.redraw('remove',doc);}); + this.listenTo(this.model.records, 'reset', function(){self.redraw('reset');}); this.menu = new my.MapMenu({ model: this.model, state: this.state.toJSON() }); - this.menu.state.bind('change', function() { + this.listenTo(this.menu.state, 'change', function() { self.state.set(self.menu.state.toJSON()); self.redraw(); }); - this.state.bind('change', function() { + this.listenTo(this.state, 'change', function() { self.redraw(); }); - this.elSidebar = this.menu.el; + this.elSidebar = this.menu.$el; }, // ## Customization Functions @@ -123,7 +124,7 @@ my.Map = Backbone.View.extend({ // } infobox: function(record) { var html = ''; - for (key in record.attributes){ + for (var key in record.attributes){ if (!(this.state.get('geomField') && key == this.state.get('geomField'))){ html += '
    ' + key + ': '+ record.attributes[key] + '
    '; } @@ -172,10 +173,9 @@ my.Map = Backbone.View.extend({ // Also sets up the editor fields and the map if necessary. render: function() { var self = this; - - htmls = Mustache.render(this.template, this.model.toTemplateJSON()); - $(this.el).html(htmls); - this.$map = this.el.find('.panel.map'); + var htmls = Mustache.render(this.template, this.model.toTemplateJSON()); + this.$el.html(htmls); + this.$map = this.$el.find('.panel.map'); this.redraw(); return this; }, @@ -219,15 +219,6 @@ my.Map = Backbone.View.extend({ this._remove(doc); } - // enable clustering if there is a large number of markers - var countAfter = 0; - this.features.eachLayer(function(){countAfter++;}); - var sizeIncreased = countAfter - countBefore > 0; - if (!this.state.get('cluster') && countAfter > 64 && sizeIncreased) { - this.state.set({cluster: true}); - return; - } - // this must come before zooming! // if not: errors when using e.g. circle markers like // "Cannot call method 'project' of undefined" @@ -326,7 +317,7 @@ my.Map = Backbone.View.extend({ if (!(docs instanceof Array)) docs = [docs]; _.each(docs,function(doc){ - for (key in self.features._layers){ + for (var key in self.features._layers){ if (self.features._layers[key].feature.properties.cid == doc.cid){ self.features.removeLayer(self.features._layers[key]); } @@ -335,6 +326,31 @@ my.Map = Backbone.View.extend({ }, + // Private: convert DMS coordinates to decimal + // + // north and east are positive, south and west are negative + // + _parseCoordinateString: function(coord){ + if (typeof(coord) != 'string') { + return(parseFloat(coord)); + } + var dms = coord.split(/[^\.\d\w]+/); + var deg = 0; var m = 0; + var toDeg = [1, 60, 3600]; // conversion factors for Deg, min, sec + var i; + for (i = 0; i < dms.length; ++i) { + if (isNaN(parseFloat(dms[i]))) { + continue; + } + deg += parseFloat(dms[i]) / toDeg[m]; + m += 1; + } + if (coord.match(/[SW]/)) { + deg = -1*deg; + } + return(deg); + }, + // Private: Return a GeoJSON geomtry extracted from the record fields // _getGeometryFromRecord: function(doc){ @@ -346,12 +362,12 @@ my.Map = Backbone.View.extend({ value = $.parseJSON(value); } catch(e) {} } - if (typeof(value) === 'string') { value = value.replace('(', '').replace(')', ''); var parts = value.split(','); - var lat = parseFloat(parts[0]); - var lon = parseFloat(parts[1]); + var lat = this._parseCoordinateString(parts[0]); + var lon = this._parseCoordinateString(parts[1]); + if (!isNaN(lon) && !isNaN(parseFloat(lat))) { return { "type": "Point", @@ -379,6 +395,9 @@ my.Map = Backbone.View.extend({ // We'll create a GeoJSON like point object from the two lat/lon fields var lon = doc.get(this.state.get('lonField')); var lat = doc.get(this.state.get('latField')); + lon = this._parseCoordinateString(lon); + lat = this._parseCoordinateString(lat); + if (!isNaN(parseFloat(lon)) && !isNaN(parseFloat(lat))) { return { type: 'Point', @@ -442,8 +461,8 @@ my.Map = Backbone.View.extend({ var self = this; this.map = new L.Map(this.$map.get(0)); - var mapUrl = "http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png"; - var osmAttribution = 'Map data © 2011 OpenStreetMap contributors, Tiles Courtesy of MapQuest '; + var mapUrl = "//otile{s}-s.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png"; + var osmAttribution = 'Map data © 2011 OpenStreetMap contributors, Tiles Courtesy of MapQuest '; var bg = new L.TileLayer(mapUrl, {maxZoom: 18, attribution: osmAttribution ,subdomains: '1234'}); this.map.addLayer(bg); @@ -533,7 +552,6 @@ my.MapMenu = Backbone.View.extend({ Cluster markers \ \ \ - \ \ ', @@ -547,11 +565,10 @@ my.MapMenu = Backbone.View.extend({ initialize: function(options) { var self = this; - this.el = $(this.el); _.bindAll(this, 'render'); - this.model.fields.bind('change', this.render); + this.listenTo(this.model.fields, 'change', this.render); this.state = new recline.Model.ObjectState(options.state); - this.state.bind('change', this.render); + this.listenTo(this.state, 'change', this.render); this.render(); }, @@ -560,28 +577,28 @@ my.MapMenu = Backbone.View.extend({ // Also sets up the editor fields and the map if necessary. render: function() { var self = this; - htmls = Mustache.render(this.template, this.model.toTemplateJSON()); - $(this.el).html(htmls); + var htmls = Mustache.render(this.template, this.model.toTemplateJSON()); + this.$el.html(htmls); if (this._geomReady() && this.model.fields.length){ if (this.state.get('geomField')){ this._selectOption('editor-geom-field',this.state.get('geomField')); - this.el.find('#editor-field-type-geom').attr('checked','checked').change(); + this.$el.find('#editor-field-type-geom').attr('checked','checked').change(); } else{ this._selectOption('editor-lon-field',this.state.get('lonField')); this._selectOption('editor-lat-field',this.state.get('latField')); - this.el.find('#editor-field-type-latlon').attr('checked','checked').change(); + this.$el.find('#editor-field-type-latlon').attr('checked','checked').change(); } } if (this.state.get('autoZoom')) { - this.el.find('#editor-auto-zoom').attr('checked', 'checked'); + this.$el.find('#editor-auto-zoom').attr('checked', 'checked'); } else { - this.el.find('#editor-auto-zoom').removeAttr('checked'); + this.$el.find('#editor-auto-zoom').removeAttr('checked'); } if (this.state.get('cluster')) { - this.el.find('#editor-cluster').attr('checked', 'checked'); + this.$el.find('#editor-cluster').attr('checked', 'checked'); } else { - this.el.find('#editor-cluster').removeAttr('checked'); + this.$el.find('#editor-cluster').removeAttr('checked'); } return this; }, @@ -600,17 +617,17 @@ my.MapMenu = Backbone.View.extend({ // onEditorSubmit: function(e){ e.preventDefault(); - if (this.el.find('#editor-field-type-geom').attr('checked')){ + if (this.$el.find('#editor-field-type-geom').attr('checked')){ this.state.set({ - geomField: this.el.find('.editor-geom-field > select > option:selected').val(), + geomField: this.$el.find('.editor-geom-field > select > option:selected').val(), lonField: null, latField: null }); } else { this.state.set({ geomField: null, - lonField: this.el.find('.editor-lon-field > select > option:selected').val(), - latField: this.el.find('.editor-lat-field > select > option:selected').val() + lonField: this.$el.find('.editor-lon-field > select > option:selected').val(), + latField: this.$el.find('.editor-lat-field > select > option:selected').val() }); } return false; @@ -621,11 +638,11 @@ my.MapMenu = Backbone.View.extend({ // onFieldTypeChange: function(e){ if (e.target.value == 'geom'){ - this.el.find('.editor-field-type-geom').show(); - this.el.find('.editor-field-type-latlon').hide(); + this.$el.find('.editor-field-type-geom').show(); + this.$el.find('.editor-field-type-latlon').hide(); } else { - this.el.find('.editor-field-type-geom').hide(); - this.el.find('.editor-field-type-latlon').show(); + this.$el.find('.editor-field-type-geom').hide(); + this.$el.find('.editor-field-type-latlon').show(); } }, @@ -640,7 +657,7 @@ my.MapMenu = Backbone.View.extend({ // Private: Helper function to select an option from a select list // _selectOption: function(id,value){ - var options = this.el.find('.' + id + ' > select > option'); + var options = this.$el.find('.' + id + ' > select > option'); if (options){ options.each(function(opt){ if (this.value == value) { diff --git a/src/view.multiview.js b/src/view.multiview.js index 8542488b..ccd35171 100644 --- a/src/view.multiview.js +++ b/src/view.multiview.js @@ -5,6 +5,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; // ## MultiView // // Manage multiple views together along with query editor etc. Usage: @@ -129,7 +130,6 @@ my.MultiView = Backbone.View.extend({ initialize: function(options) { var self = this; - this.el = $(this.el); this._setupState(options.state); // Hash of 'page' views (i.e. those for whole page) keyed by page name @@ -199,43 +199,39 @@ my.MultiView = Backbone.View.extend({ } this._showHideSidebar(); - this.model.bind('query:start', function() { - self.notify({loader: true, persist: true}); - }); - this.model.bind('query:done', function() { - self.clearNotifications(); - self.el.find('.doc-count').text(self.model.recordCount || 'Unknown'); - }); - this.model.bind('query:fail', function(error) { - self.clearNotifications(); - var msg = ''; - if (typeof(error) == 'string') { - msg = error; - } else if (typeof(error) == 'object') { - if (error.title) { - msg = error.title + ': '; - } - if (error.message) { - msg += error.message; - } - } else { - msg = 'There was an error querying the backend'; + this.listenTo(this.model, 'query:start', function() { + self.notify({loader: true, persist: true}); + }); + this.listenTo(this.model, 'query:done', function() { + self.clearNotifications(); + self.$el.find('.doc-count').text(self.model.recordCount || 'Unknown'); + }); + this.listenTo(this.model, 'query:fail', function(error) { + self.clearNotifications(); + var msg = ''; + if (typeof(error) == 'string') { + msg = error; + } else if (typeof(error) == 'object') { + if (error.title) { + msg = error.title + ': '; } - self.notify({message: msg, category: 'error', persist: true}); - }); + if (error.message) { + msg += error.message; + } + } else { + msg = 'There was an error querying the backend'; + } + self.notify({message: msg, category: 'error', persist: true}); + }); // retrieve basic data like fields etc // note this.model and dataset returned are the same // TODO: set query state ...? this.model.queryState.set(self.state.get('query'), {silent: true}); - this.model.fetch() - .fail(function(error) { - self.notify({message: error.message, category: 'error', persist: true}); - }); }, setReadOnly: function() { - this.el.addClass('recline-read-only'); + this.$el.addClass('recline-read-only'); }, render: function() { @@ -243,11 +239,11 @@ my.MultiView = Backbone.View.extend({ tmplData.views = this.pageViews; tmplData.sidebarViews = this.sidebarViews; var template = Mustache.render(this.template, tmplData); - $(this.el).html(template); + this.$el.html(template); // now create and append other views - var $dataViewContainer = this.el.find('.data-view-container'); - var $dataSidebar = this.el.find('.data-view-sidebar'); + var $dataViewContainer = this.$el.find('.data-view-container'); + var $dataSidebar = this.$el.find('.data-view-sidebar'); // the main views _.each(this.pageViews, function(view, pageName) { @@ -259,25 +255,37 @@ my.MultiView = Backbone.View.extend({ }); _.each(this.sidebarViews, function(view) { - this['$'+view.id] = view.view.el; + this['$'+view.id] = view.view.$el; $dataSidebar.append(view.view.el); }, this); - var pager = new recline.View.Pager({ + this.pager = new recline.View.Pager({ model: this.model.queryState }); - this.el.find('.recline-results-info').after(pager.el); + this.$el.find('.recline-results-info').after(this.pager.el); - var queryEditor = new recline.View.QueryEditor({ + this.queryEditor = new recline.View.QueryEditor({ model: this.model.queryState }); - this.el.find('.query-editor-here').append(queryEditor.el); + this.$el.find('.query-editor-here').append(this.queryEditor.el); }, + remove: function () { + _.each(this.pageViews, function (view) { + view.view.remove(); + }); + _.each(this.sidebarViews, function (view) { + view.view.remove(); + }); + this.pager.remove(); + this.queryEditor.remove(); + Backbone.View.prototype.remove.apply(this, arguments); + }, + // hide the sidebar if empty _showHideSidebar: function() { - var $dataSidebar = this.el.find('.data-view-sidebar'); + var $dataSidebar = this.$el.find('.data-view-sidebar'); var visibleChildren = $dataSidebar.children().filter(function() { return $(this).css("display") != "none"; }).length; @@ -290,19 +298,19 @@ my.MultiView = Backbone.View.extend({ }, updateNav: function(pageName) { - this.el.find('.navigation a').removeClass('active'); - var $el = this.el.find('.navigation a[data-view="' + pageName + '"]'); + this.$el.find('.navigation a').removeClass('active'); + var $el = this.$el.find('.navigation a[data-view="' + pageName + '"]'); $el.addClass('active'); // add/remove sidebars and hide inactive views _.each(this.pageViews, function(view, idx) { if (view.id === pageName) { - view.view.el.show(); + view.view.$el.show(); if (view.view.elSidebar) { view.view.elSidebar.show(); } } else { - view.view.el.hide(); + view.view.$el.hide(); if (view.view.elSidebar) { view.view.elSidebar.hide(); } @@ -371,7 +379,7 @@ my.MultiView = Backbone.View.extend({ _bindStateChanges: function() { var self = this; // finally ensure we update our state object when state of sub-object changes so that state is always up to date - this.model.queryState.bind('change', function() { + this.listenTo(this.model.queryState, 'change', function() { self.state.set({query: self.model.queryState.toJSON()}); }); _.each(this.pageViews, function(pageView) { @@ -379,7 +387,7 @@ my.MultiView = Backbone.View.extend({ var update = {}; update['view-' + pageView.id] = pageView.view.state.toJSON(); self.state.set(update); - pageView.view.state.bind('change', function() { + self.listenTo(pageView.view.state, 'change', function() { var update = {}; update['view-' + pageView.id] = pageView.view.state.toJSON(); // had problems where change not being triggered for e.g. grid view so let's do it explicitly @@ -393,7 +401,7 @@ my.MultiView = Backbone.View.extend({ _bindFlashNotifications: function() { var self = this; _.each(this.pageViews, function(pageView) { - pageView.view.bind('recline:flash', function(flash) { + self.listenTo(pageView.view, 'recline:flash', function(flash) { self.notify(flash); }); }); @@ -519,7 +527,7 @@ my.parseQueryString = function(q) { // Parse the query string out of the URL hash my.parseHashQueryString = function() { - q = my.parseHashUrl(window.location.hash).query; + var q = my.parseHashUrl(window.location.hash).query; return my.parseQueryString(q); }; diff --git a/src/view.slickgrid.js b/src/view.slickgrid.js index e4780fd8..f7512f8d 100644 --- a/src/view.slickgrid.js +++ b/src/view.slickgrid.js @@ -4,6 +4,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; // ## SlickGrid Dataset View // // Provides a tabular view on a Dataset, based on SlickGrid. @@ -22,7 +23,11 @@ this.recline.View = this.recline.View || {}; // model: dataset, // el: $el, // state: { -// gridOptions: {editable: true}, +// gridOptions: { +// editable: true, +// enableAddRows: true +// ... +// }, // columnsEditor: [ // {column: 'date', editor: Slick.Editors.Date }, // {column: 'title', editor: Slick.Editors.Text} @@ -33,13 +38,10 @@ this.recline.View = this.recline.View || {}; my.SlickGrid = Backbone.View.extend({ initialize: function(modelEtc) { var self = this; - this.el = $(this.el); - this.el.addClass('recline-slickgrid'); - _.bindAll(this, 'render'); - this.model.records.bind('add', this.render); - this.model.records.bind('reset', this.render); - this.model.records.bind('remove', this.render); - this.model.records.bind('change', this.onRecordChanged, this); + this.$el.addClass('recline-slickgrid'); + _.bindAll(this, 'render', 'onRecordChanged'); + this.listenTo(this.model.records, 'add remove reset', this.render); + this.listenTo(this.model.records, 'change', this.onRecordChanged); var state = _.extend({ hiddenColumns: [], @@ -53,6 +55,8 @@ my.SlickGrid = Backbone.View.extend({ ); this.state = new recline.Model.ObjectState(state); + + this._slickHandler = new Slick.EventHandler(); }, events: { @@ -113,12 +117,30 @@ my.SlickGrid = Backbone.View.extend({ var editInfo = _.find(self.state.get('columnsEditor'),function(c){return c.column === field.id;}); if (editInfo){ column.editor = editInfo.editor; + } else { + // guess editor type + var typeToEditorMap = { + 'string': Slick.Editors.LongText, + 'integer': Slick.Editors.IntegerEditor, + 'number': Slick.Editors.Text, + // TODO: need a way to ensure we format date in the right way + // Plus what if dates are in distant past or future ... (?) + // 'date': Slick.Editors.DateEditor, + 'date': Slick.Editors.Text, + 'boolean': Slick.Editors.YesNoSelectEditor + // TODO: (?) percent ... + }; + if (field.type in typeToEditorMap) { + column.editor = typeToEditorMap[field.type] + } else { + column.editor = Slick.Editors.LongText; + } } columns.push(column); }); // Restrict the visible columns - var visibleColumns = columns.filter(function(column) { + var visibleColumns = _.filter(columns, function(column) { return _.indexOf(self.state.get('hiddenColumns'), column.id) === -1; }); @@ -164,7 +186,7 @@ my.SlickGrid = Backbone.View.extend({ this.getItem = function(index) {return rows[index];}; this.getItemMetadata = function(index) {return {};}; this.getModel = function(index) {return models[index];}; - this.getModelRow = function(m) {return models.indexOf(m);}; + this.getModelRow = function(m) {return _.indexOf(models, m);}; this.updateItem = function(m,i) { rows[i] = toRow(m); models[i] = m; @@ -187,7 +209,7 @@ my.SlickGrid = Backbone.View.extend({ this.grid.setSortColumn(column, sortAsc); } - this.grid.onSort.subscribe(function(e, args){ + this._slickHandler.subscribe(this.grid.onSort, function(e, args){ var order = (args.sortAsc) ? 'asc':'desc'; var sort = [{ field: args.sortCol.field, @@ -196,7 +218,7 @@ my.SlickGrid = Backbone.View.extend({ self.model.query({sort: sort}); }); - this.grid.onColumnsReordered.subscribe(function(e, args){ + this._slickHandler.subscribe(this.grid.onColumnsReordered, function(e, args){ self.state.set({columnsOrder: _.pluck(self.grid.getColumns(),'id')}); }); @@ -212,7 +234,7 @@ my.SlickGrid = Backbone.View.extend({ self.state.set({columnsWidth:columnsWidth}); }); - this.grid.onCellChange.subscribe(function (e, args) { + this._slickHandler.subscribe(this.grid.onCellChange, function (e, args) { // We need to change the model associated value // var grid = args.grid; @@ -235,7 +257,12 @@ my.SlickGrid = Backbone.View.extend({ } return this; - }, + }, + + remove: function () { + this._slickHandler.unsubscribeAll(); + Backbone.View.prototype.remove.apply(this, arguments); + }, show: function() { // If the div is hidden, SlickGrid will calculate wrongly some diff --git a/src/view.timeline.js b/src/view.timeline.js index 87cdabe4..cd4a1084 100644 --- a/src/view.timeline.js +++ b/src/view.timeline.js @@ -4,6 +4,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; // turn off unnecessary logging from VMM Timeline if (typeof VMM !== 'undefined') { VMM.debug = false; @@ -27,18 +28,20 @@ my.Timeline = Backbone.View.extend({ initialize: function(options) { var self = this; - this.el = $(this.el); - this.timeline = new VMM.Timeline(); + this.timeline = new VMM.Timeline(this.elementId); this._timelineIsInitialized = false; - this.model.fields.bind('reset', function() { + this.listenTo(this.model.fields, 'reset', function() { self._setupTemporalField(); }); - this.model.records.bind('all', function() { + this.listenTo(this.model.records, 'all', function() { self.reloadData(); }); var stateData = _.extend({ startField: null, endField: null, + // by default timelinejs (and browsers) will parse ambiguous dates in US format (mm/dd/yyyy) + // set to true to interpret dd/dd/dddd as dd/mm/yyyy + nonUSDates: false, timelineJSOptions: {} }, options.state @@ -50,7 +53,7 @@ my.Timeline = Backbone.View.extend({ render: function() { var tmplData = {}; var htmls = Mustache.render(this.template, tmplData); - this.el.html(htmls); + this.$el.html(htmls); // can only call _initTimeline once view in DOM as Timeline uses $ // internally to look up element if ($(this.elementId).length > 0) { @@ -66,9 +69,10 @@ my.Timeline = Backbone.View.extend({ }, _initTimeline: function() { - var $timeline = this.el.find(this.elementId); var data = this._timelineJSON(); - this.timeline.init(data, this.elementId, this.state.get("timelineJSOptions")); + var config = this.state.get("timelineJSOptions"); + config.id = this.elementId; + this.timeline.init(config, data); this._timelineIsInitialized = true }, @@ -130,19 +134,33 @@ my.Timeline = Backbone.View.extend({ return out; }, + // convert dates into a format TimelineJS will handle + // TimelineJS does not document this at all so combo of read the code + + // trial and error + // Summary (AFAICt): + // Preferred: [-]yyyy[,mm,dd,hh,mm,ss] + // Supported: mm/dd/yyyy _parseDate: function(date) { if (!date) { return null; } - var out = date.trim(); + var out = $.trim(date); out = out.replace(/(\d)th/g, '$1'); out = out.replace(/(\d)st/g, '$1'); - out = out.trim() ? moment(out) : null; - if (out.toDate() == 'Invalid Date') { - return null; - } else { - return out.toDate(); + out = $.trim(out); + if (out.match(/\d\d\d\d-\d\d-\d\d(T.*)?/)) { + out = out.replace(/-/g, ',').replace('T', ',').replace(':',','); } + if (out.match(/\d\d-\d\d-\d\d.*/)) { + out = out.replace(/-/g, '/'); + } + if (this.state.get('nonUSDates')) { + var parts = out.match(/(\d\d)\/(\d\d)\/(\d\d.*)/); + if (parts) { + out = [parts[2], parts[1], parts[3]].join('/'); + } + } + return out; }, _setupTemporalField: function() { diff --git a/src/widget.facetviewer.js b/src/widget.facetviewer.js index 396bc8eb..4e140af3 100644 --- a/src/widget.facetviewer.js +++ b/src/widget.facetviewer.js @@ -4,6 +4,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; // ## FacetViewer // @@ -41,9 +42,8 @@ my.FacetViewer = Backbone.View.extend({ }, initialize: function(model) { _.bindAll(this, 'render'); - this.el = $(this.el); - this.model.facets.bind('all', this.render); - this.model.fields.bind('all', this.render); + this.listenTo(this.model.facets, 'all', this.render); + this.listenTo(this.model.fields, 'all', this.render); this.render(); }, render: function() { @@ -60,17 +60,17 @@ my.FacetViewer = Backbone.View.extend({ return facet; }); var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); + this.$el.html(templated); // are there actually any facets to show? if (this.model.facets.length > 0) { - this.el.show(); + this.$el.show(); } else { - this.el.hide(); + this.$el.hide(); } }, onHide: function(e) { e.preventDefault(); - this.el.hide(); + this.$el.hide(); }, onFacetFilter: function(e) { e.preventDefault(); diff --git a/src/widget.fields.js b/src/widget.fields.js index 13078d2f..aba67557 100644 --- a/src/widget.fields.js +++ b/src/widget.fields.js @@ -20,7 +20,8 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { - + "use strict"; + my.Fields = Backbone.View.extend({ className: 'recline-fields-view', template: ' \ @@ -59,13 +60,12 @@ my.Fields = Backbone.View.extend({ initialize: function(model) { var self = this; - this.el = $(this.el); _.bindAll(this, 'render'); // TODO: this is quite restrictive in terms of when it is re-run // e.g. a change in type will not trigger a re-run atm. // being more liberal (e.g. binding to all) can lead to being called a lot (e.g. for change:width) - this.model.fields.bind('reset', function(action) { + this.listenTo(this.model.fields, 'reset', function(action) { self.model.fields.each(function(field) { field.facets.unbind('all', self.render); field.facets.bind('all', self.render); @@ -74,7 +74,7 @@ my.Fields = Backbone.View.extend({ self.model.getFieldsSummary(); self.render(); }); - this.el.find('.collapse').collapse(); + this.$el.find('.collapse').collapse(); this.render(); }, render: function() { @@ -88,7 +88,7 @@ my.Fields = Backbone.View.extend({ tmplData.fields.push(out); }); var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); + this.$el.html(templated); } }); diff --git a/src/widget.filtereditor.js b/src/widget.filtereditor.js index 665c851b..3473b9d5 100644 --- a/src/widget.filtereditor.js +++ b/src/widget.filtereditor.js @@ -4,6 +4,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; my.FilterEditor = Backbone.View.extend({ className: 'recline-filter-editor well', @@ -58,9 +59,9 @@ my.FilterEditor = Backbone.View.extend({ × \ \ \ - \ + \ \ - \ + \ \ \ ', @@ -88,11 +89,9 @@ my.FilterEditor = Backbone.View.extend({ 'submit form.js-add': 'onAddFilter' }, initialize: function() { - this.el = $(this.el); _.bindAll(this, 'render'); - this.model.fields.bind('all', this.render); - this.model.queryState.bind('change', this.render); - this.model.queryState.bind('change:filters:new-blank', this.render); + this.listenTo(this.model.fields, 'all', this.render); + this.listenTo(this.model.queryState, 'change change:filters:new-blank', this.render); this.render(); }, render: function() { @@ -108,13 +107,13 @@ my.FilterEditor = Backbone.View.extend({ return Mustache.render(self.filterTemplates[this.type], this); }; var out = Mustache.render(this.template, tmplData); - this.el.html(out); + this.$el.html(out); }, onAddFilterShow: function(e) { e.preventDefault(); var $target = $(e.target); $target.hide(); - this.el.find('form.js-add').show(); + this.$el.find('form.js-add').show(); }, onAddFilter: function(e) { e.preventDefault(); diff --git a/src/widget.pager.js b/src/widget.pager.js index 7d2e1e47..b2b5d03d 100644 --- a/src/widget.pager.js +++ b/src/widget.pager.js @@ -4,6 +4,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; my.Pager = Backbone.View.extend({ className: 'recline-pager', @@ -24,14 +25,13 @@ my.Pager = Backbone.View.extend({ initialize: function() { _.bindAll(this, 'render'); - this.el = $(this.el); - this.model.bind('change', this.render); + this.listenTo(this.model, 'change', this.render); this.render(); }, onFormSubmit: function(e) { e.preventDefault(); - var newFrom = parseInt(this.el.find('input[name="from"]').val()); - var newSize = parseInt(this.el.find('input[name="to"]').val()) - newFrom; + var newFrom = parseInt(this.$el.find('input[name="from"]').val()); + var newSize = parseInt(this.$el.find('input[name="to"]').val()) - newFrom; newFrom = Math.max(newFrom, 0); newSize = Math.max(newSize, 1); this.model.set({size: newSize, from: newFrom}); @@ -52,7 +52,7 @@ my.Pager = Backbone.View.extend({ var tmplData = this.model.toJSON(); tmplData.to = this.model.get('from') + this.model.get('size'); var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); + this.$el.html(templated); } }); diff --git a/src/widget.queryeditor.js b/src/widget.queryeditor.js index 891983de..87defe0d 100644 --- a/src/widget.queryeditor.js +++ b/src/widget.queryeditor.js @@ -4,6 +4,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; my.QueryEditor = Backbone.View.extend({ className: 'recline-query-editor', @@ -23,19 +24,18 @@ my.QueryEditor = Backbone.View.extend({ initialize: function() { _.bindAll(this, 'render'); - this.el = $(this.el); - this.model.bind('change', this.render); + this.listenTo(this.model, 'change', this.render); this.render(); }, onFormSubmit: function(e) { e.preventDefault(); - var query = this.el.find('.text-query input').val(); + var query = this.$el.find('.text-query input').val(); this.model.set({q: query}); }, render: function() { var tmplData = this.model.toJSON(); var templated = Mustache.render(this.template, tmplData); - this.el.html(templated); + this.$el.html(templated); } }); diff --git a/src/widget.valuefilter.js b/src/widget.valuefilter.js index 60c08e65..29de9bab 100644 --- a/src/widget.valuefilter.js +++ b/src/widget.valuefilter.js @@ -4,6 +4,7 @@ this.recline = this.recline || {}; this.recline.View = this.recline.View || {}; (function($, my) { + "use strict"; my.ValueFilter = Backbone.View.extend({ className: 'recline-filter-editor well', @@ -50,11 +51,9 @@ my.ValueFilter = Backbone.View.extend({ 'submit form.js-add': 'onAddFilter' }, initialize: function() { - this.el = $(this.el); _.bindAll(this, 'render'); - this.model.fields.bind('all', this.render); - this.model.queryState.bind('change', this.render); - this.model.queryState.bind('change:filters:new-blank', this.render); + this.listenTo(this.model.fields, 'all', this.render); + this.listenTo(this.model.queryState, 'change change:filters:new-blank', this.render); this.render(); }, render: function() { @@ -70,7 +69,7 @@ my.ValueFilter = Backbone.View.extend({ return Mustache.render(self.filterTemplates.term, this); }; var out = Mustache.render(this.template, tmplData); - this.el.html(out); + this.$el.html(out); }, updateFilter: function(input) { var self = this; @@ -84,7 +83,7 @@ my.ValueFilter = Backbone.View.extend({ e.preventDefault(); var $target = $(e.target); $target.hide(); - this.el.find('form.js-add').show(); + this.$el.find('form.js-add').show(); }, onAddFilter: function(e) { e.preventDefault(); diff --git a/test/backend.csv.test.js b/test/backend.csv.test.js index c027f097..e7b09b72 100644 --- a/test/backend.csv.test.js +++ b/test/backend.csv.test.js @@ -64,6 +64,19 @@ test("parseCSV - quotechar", function() { }); +test("parseCSV skipInitialRows", function() { + var csv = '"Jones, Jay",10\n' + + '"Xyz ""ABC"" O\'Brien",11:35\n' + + '"Other, AN",12:35\n'; + + var array = recline.Backend.CSV.parseCSV(csv, {skipInitialRows: 1}); + var exp = [ + ['Xyz "ABC" O\'Brien', '11:35' ], + ['Other, AN', '12:35' ] + ]; + deepEqual(exp, array); +}); + test("serializeCSV - Array", function() { var csv = [ ['Jones, Jay', 10], diff --git a/test/backend.elasticsearch.test.js b/test/backend.elasticsearch.test.js deleted file mode 100644 index 18a05998..00000000 --- a/test/backend.elasticsearch.test.js +++ /dev/null @@ -1,353 +0,0 @@ -(function ($) { -module("Backend ElasticSearch - Wrapper"); - -test("queryNormalize", function() { - var backend = new recline.Backend.ElasticSearch.Wrapper(); - - var in_ = new recline.Model.Query(); - var out = backend._normalizeQuery(in_); - var exp = { - constant_score: { - query: { - match_all: {} - } - } - }; - deepEqual(out, exp); - - var in_ = new recline.Model.Query(); - in_.set({q: ''}); - var out = backend._normalizeQuery(in_); - deepEqual(out, exp); - - var in_ = new recline.Model.Query(); - in_.attributes.q = 'abc'; - var out = backend._normalizeQuery(in_); - equal(out.constant_score.query.query_string.query, 'abc'); - - var in_ = new recline.Model.Query(); - in_.addFilter({ - type: 'term', - field: 'xyz', - term: 'XXX' - }); - var out = backend._normalizeQuery(in_); - var exp = { - constant_score: { - query: { - match_all: {} - }, - filter: { - and: [ - { - term: { - xyz: 'xxx' - } - } - ] - } - } - }; - deepEqual(out, exp); - - var in_ = new recline.Model.Query(); - in_.addFilter({ - type: 'geo_distance', - field: 'xyz' - }); - var out = backend._normalizeQuery(in_); - var exp = { - constant_score: { - query: { - match_all: {} - }, - filter: { - and: [ - { - geo_distance: { - distance: 10, - unit: 'km', - 'xyz': { lon: 0, lat: 0 } - } - } - ] - } - } - }; - deepEqual(out, exp); -}); - -var mapping_data = { - "note": { - "properties": { - "_created": { - "format": "dateOptionalTime", - "type": "date" - }, - "_last_modified": { - "format": "dateOptionalTime", - "type": "date" - }, - "end": { - "type": "string" - }, - "owner": { - "type": "string" - }, - "start": { - "type": "string" - }, - "title": { - "type": "string" - } - } - } -}; - -var sample_data = { - "_shards": { - "failed": 0, - "successful": 5, - "total": 5 - }, - "hits": { - "hits": [ - { - "_id": "u3rpLyuFS3yLNXrtxWkMwg", - "_index": "hypernotes", - "_score": 1.0, - "_source": { - "_created": "2012-02-24T17:53:57.286Z", - "_last_modified": "2012-02-24T17:53:57.286Z", - "owner": "tester", - "title": "Note 1" - }, - "_type": "note" - }, - { - "_id": "n7JMkFOHSASJCVTXgcpqkA", - "_index": "hypernotes", - "_score": 1.0, - "_source": { - "_created": "2012-02-24T17:53:57.290Z", - "_last_modified": "2012-02-24T17:53:57.290Z", - "owner": "tester", - "title": "Note 3" - }, - "_type": "note" - }, - { - "_id": "g7UMA55gTJijvsB3dFitzw", - "_index": "hypernotes", - "_score": 1.0, - "_source": { - "_created": "2012-02-24T17:53:57.289Z", - "_last_modified": "2012-02-24T17:53:57.289Z", - "owner": "tester", - "title": "Note 2" - }, - "_type": "note" - } - ], - "max_score": 1.0, - "total": 3 - }, - "timed_out": false, - "took": 2 -}; - -test("query", function() { - var backend = new recline.Backend.ElasticSearch.Wrapper('https://localhost:9200/my-es-db/my-es-type'); - - var stub = sinon.stub($, 'ajax', function(options) { - if (options.url.indexOf('_mapping') != -1) { - return { - done: function(callback) { - callback(mapping_data); - return this; - }, - fail: function() { - return this; - } - } - } else { - return { - done: function(callback) { - callback(sample_data); - return this; - }, - fail: function() { - } - } - } - }); - - backend.mapping().done(function(data) { - var fields = _.keys(data.note.properties); - deepEqual(['_created', '_last_modified', 'end', 'owner', 'start', 'title'], fields); - }); - - backend.query().done(function(queryResult) { - equal(3, queryResult.hits.total); - equal(3, queryResult.hits.hits.length); - equal('Note 1', queryResult.hits.hits[0]._source['title']); - start(); - }); - $.ajax.restore(); -}); - -// DISABLED - this test requires ElasticSearch to be running locally -// test("write", function() { -// var url = 'http://localhost:9200/recline-test/es-write'; -// var backend = new recline.Backend.ElasticSearch.Wrapper(url); -// stop(); -// -// var id = parseInt(Math.random()*100000000).toString(); -// var rec = { -// id: id, -// title: 'my title' -// }; -// var jqxhr = backend.upsert(rec); -// jqxhr.done(function(data) { -// ok(data.ok); -// equal(data._id, id); -// equal(data._type, 'es-write'); -// equal(data._version, 1); -// -// // update -// rec.title = 'new title'; -// var jqxhr = backend.upsert(rec); -// jqxhr.done(function(data) { -// equal(data._version, 2); -// -// // delete -// var jqxhr = backend.remove(rec.id); -// jqxhr.done(function(data) { -// ok(data.ok); -// rec = null; -// -// // try to get ... -// var jqxhr = backend.get(id); -// jqxhr.done(function(data) { -// // should not be here -// ok(false, 'Should have got 404'); -// }).error(function(error) { -// equal(error.status, 404); -// start(); -// }); -// }); -// }); -// }).fail(function(error) { -// console.log(error); -// ok(false, 'Basic request failed - is ElasticSearch running locally on port 9200 (required for this test!)'); -// start(); -// }); -// }); - - -// ================================================== - -module("Backend ElasticSearch - Recline"); - -test("query", function() { - var dataset = new recline.Model.Dataset({ - url: 'https://localhost:9200/my-es-db/my-es-type', - backend: 'elasticsearch' - }); - - var stub = sinon.stub($, 'ajax', function(options) { - if (options.url.indexOf('_mapping') != -1) { - return { - done: function(callback) { - callback(mapping_data); - return this; - }, - fail: function() { - return this; - } - } - } else { - return { - done: function(callback) { - callback(sample_data); - return this; - }, - fail: function() { - } - }; - } - }); - - dataset.fetch().done(function(dataset) { - deepEqual(['_created', '_last_modified', 'end', 'owner', 'start', 'title'], _.pluck(dataset.fields.toJSON(), 'id')); - dataset.query().then(function(recList) { - equal(3, dataset.recordCount); - equal(3, recList.length); - equal('Note 1', recList.models[0].get('title')); - start(); - }); - }); - $.ajax.restore(); -}); - -// DISABLED - this test requires ElasticSearch to be running locally -// test("write", function() { -// var dataset = new recline.Model.Dataset({ -// url: 'http://localhost:9200/recline-test/es-write', -// backend: 'elasticsearch' -// }); -// -// stop(); -// -// var id = parseInt(Math.random()*100000000).toString(); -// var rec = new recline.Model.Record({ -// id: id, -// title: 'my title' -// }); -// dataset.records.add(rec); -// // have to do this explicitly as we not really supporting adding new items atm -// dataset._changes.creates.push(rec.toJSON()); -// var jqxhr = dataset.save(); -// jqxhr.done(function(data) { -// ok(data.ok); -// equal(data._id, id); -// equal(data._type, 'es-write'); -// equal(data._version, 1); -// -// // update -// rec.set({title: 'new title'}); -// // again set up by hand ... -// dataset._changes.creates = []; -// dataset._changes.updates.push(rec.toJSON()); -// var jqxhr = dataset.save(); -// jqxhr.done(function(data) { -// equal(data._version, 2); -// -// // delete -// dataset._changes.updates = 0; -// dataset._changes.deletes.push(rec.toJSON()); -// var jqxhr = dataset.save(); -// jqxhr.done(function(data) { -// ok(data.ok); -// rec = null; -// -// // try to get ... -// var es = new recline.Backend.ElasticSearch.Wrapper(dataset.get('url')); -// var jqxhr = es.get(id); -// jqxhr.done(function(data) { -// // should not be here -// ok(false, 'Should have got 404'); -// }).error(function(error) { -// equal(error.status, 404); -// start(); -// }); -// }); -// }); -// }).fail(function(error) { -// console.log(error); -// ok(false, 'Basic request failed - is ElasticSearch running locally on port 9200 (required for this test!)'); -// start(); -// }); -// }); - -})(this.jQuery); diff --git a/test/backend.gdocs.test.js b/test/backend.gdocs.test.js deleted file mode 100644 index 0e860b9c..00000000 --- a/test/backend.gdocs.test.js +++ /dev/null @@ -1,320 +0,0 @@ -(function ($) { -module("Backend GDocs"); - -var sampleGDocsSpreadsheetMetadata = { - feed: { - category: [ - { - term: "http://schemas.google.com/spreadsheets/2006#worksheet", - scheme: "http://schemas.google.com/spreadsheets/2006" - } - ], - updated: { - $t: "2010-07-13T09:57:28.408Z" - }, - xmlns: "http://www.w3.org/2005/Atom", - title: { - $t: "javascript-test", - type: "text" - }, - author: [ - { - name: { - $t: "okfn.rufus.pollock" - }, - email: { - $t: "okfn.rufus.pollock@gmail.com" - } - } - ], - openSearch$startIndex: { - $t: "1" - }, - xmlns$gs: "http://schemas.google.com/spreadsheets/2006", - xmlns$openSearch: "http://a9.com/-/spec/opensearchrss/1.0/", - entry: [ - { - category: [ - { - term: "http://schemas.google.com/spreadsheets/2006#worksheet", - scheme: "http://schemas.google.com/spreadsheets/2006" - } - ], - updated: { - $t: "2010-07-13T09:57:28.408Z" - }, - title: { - $t: "Sheet1", - type: "text" - }, - content: { - $t: "Sheet1", - type: "text" - }, - link: [ - { - href: "https://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/basic", - type: "application/atom+xml", - rel: "http://schemas.google.com/spreadsheets/2006#listfeed" - }, - { - href: "https://spreadsheets.google.com/feeds/cells/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/basic", - type: "application/atom+xml", - rel: "http://schemas.google.com/spreadsheets/2006#cellsfeed" - }, - { - href: "https://spreadsheets.google.com/tq?key=0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc&sheet=od6&pub=1", - type: "application/atom+xml", - rel: "http://schemas.google.com/visualization/2008#visualizationApi" - }, - { - href: "https://spreadsheets.google.com/feeds/worksheets/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/public/basic/od6", - type: "application/atom+xml", - rel: "self" - } - ], - id: { - $t: "https://spreadsheets.google.com/feeds/worksheets/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/public/basic/od6" - } - } - ], - link: [ - { - href: "https://spreadsheets.google.com/pub?key=0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc", - type: "text/html", - rel: "alternate" - }, - { - href: "https://spreadsheets.google.com/feeds/worksheets/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/public/basic", - type: "application/atom+xml", - rel: "http://schemas.google.com/g/2005#feed" - }, - { - href: "https://spreadsheets.google.com/feeds/worksheets/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/public/basic?alt=json", - type: "application/atom+xml", - rel: "self" - } - ], - openSearch$totalResults: { - $t: "1" - }, - id: { - $t: "https://spreadsheets.google.com/feeds/worksheets/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/public/basic" - } - }, - version: "1.0", - encoding: "UTF-8" -} - -var sampleGDocsSpreadsheetData = { - feed: { - category: [ - { - term: "http://schemas.google.com/spreadsheets/2006#list", - scheme: "http://schemas.google.com/spreadsheets/2006" - } - ], - updated: { - $t: "2010-07-12T18:32:16.200Z" - }, - xmlns: "http://www.w3.org/2005/Atom", - xmlns$gsx: "http://schemas.google.com/spreadsheets/2006/extended", - title: { - $t: "Sheet1", - type: "text" - }, - author: [ - { - name: { - $t: "okfn.rufus.pollock" - }, - email: { - $t: "okfn.rufus.pollock@gmail.com" - } - } - ], - openSearch$startIndex: { - $t: "1" - }, - link: [ - { - href: "http://spreadsheets.google.com/pub?key=0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc", - type: "text/html", - rel: "alternate" - }, - { - href: "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values", - type: "application/atom+xml", - rel: "http://schemas.google.com/g/2005#feed" - }, - { - href: "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values?alt=json-in-script", - type: "application/atom+xml", - rel: "self" - } - ], - xmlns$openSearch: "http://a9.com/-/spec/opensearchrss/1.0/", - entry: [ - { - category: [ - { - term: "http://schemas.google.com/spreadsheets/2006#list", - scheme: "http://schemas.google.com/spreadsheets/2006" - } - ], - updated: { - $t: "2010-07-12T18:32:16.200Z" - }, - 'gsx$column-2': { - $t: "1" - }, - 'gsx$column-1': { - $t: "A" - }, - title: { - $t: "A", - type: "text" - }, - content: { - $t: "column-2: 1", - type: "text" - }, - link: [ - { - href: "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cokwr", - type: "application/atom+xml", - rel: "self" - } - ], - id: { - $t: "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cokwr" - } - }, - { - category: [ - { - term: "http://schemas.google.com/spreadsheets/2006#list", - scheme: "http://schemas.google.com/spreadsheets/2006" - } - ], - updated: { - $t: "2010-07-12T18:32:16.200Z" - }, - 'gsx$column-2': { - $t: "2" - }, - 'gsx$column-1': { - $t: "b" - }, - title: { - $t: "b", - type: "text" - }, - content: { - $t: "column-2: 2", - type: "text" - }, - link: [ - { - href: "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cpzh4", - type: "application/atom+xml", - rel: "self" - } - ], - id: { - $t: "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cpzh4" - } - }, - { - category: [ - { - term: "http://schemas.google.com/spreadsheets/2006#list", - scheme: "http://schemas.google.com/spreadsheets/2006" - } - ], - updated: { - $t: "2010-07-12T18:32:16.200Z" - }, - 'gsx$column-2': { - $t: "3" - }, - 'gsx$column-1': { - $t: "c" - }, - title: { - $t: "c", - type: "text" - }, - content: { - $t: "column-2: 3", - type: "text" - }, - link: [ - { - href: "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cre1l", - type: "application/atom+xml", - rel: "self" - } - ], - id: { - $t: "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cre1l" - } - } - ], - openSearch$totalResults: { - $t: "3" - }, - id: { - $t: "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values" - } - }, - version: "1.0", - encoding: "UTF-8" -} - -test("GDocs Backend", function() { - var dataset = new recline.Model.Dataset({ - url: 'https://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values?alt=json', - backend: 'gdocs' - }); - - var stub = sinon.stub($, 'getJSON', function(options, cb) { - var spreadsheetUrl = 'spreadsheets.google.com/feeds/worksheets/'; - var worksheetUrl = 'spreadsheets.google.com/feeds/list/'; - - if (options.indexOf(spreadsheetUrl) !== -1) { - cb(sampleGDocsSpreadsheetMetadata) - } - else if(options.indexOf(worksheetUrl) !== -1) { - cb(sampleGDocsSpreadsheetData) - } - }); - - dataset.fetch().then(function() { - var docList = dataset.records; - deepEqual(['column-2', 'column-1'], _.pluck(dataset.fields.toJSON(), 'id')); - equal(3, docList.length); - equal("A", docList.models[0].get('column-1')); - equal('javascript-test :: Sheet1', dataset.get('title')); - }); - $.getJSON.restore(); -}); - -test("GDocs Backend.getUrl", function() { - var key = 'Abc_dajkdkjdafkj'; - var gid = 0; - var worksheet = 1; - var url = 'https://docs.google.com/spreadsheet/ccc?key=' + key + '#gid=' + gid - var out = recline.Backend.GDocs.getGDocsAPIUrls(url); - var exp1 = 'https://spreadsheets.google.com/feeds/list/' + key + '/' + worksheet + '/public/values?alt=json' - var exp2 = 'https://spreadsheets.google.com/feeds/worksheets/' + key + '/public/basic?alt=json' - equal(exp1, out.worksheet); - equal(exp2, out.spreadsheet); - - var url = 'https://docs.google.com/spreadsheet/ccc?key=' + key; - var out = recline.Backend.GDocs.getGDocsAPIUrls(url); - equal(out.worksheet, exp1); -}); - -})(this.jQuery); - diff --git a/test/backend.memory.test.js b/test/backend.memory.test.js index e4636884..440e7d8d 100644 --- a/test/backend.memory.test.js +++ b/test/backend.memory.test.js @@ -100,18 +100,19 @@ test('filters', function () { }); query = new recline.Model.Query(); - query.addFilter({type: 'range', field: 'date', start: '2011-01-01', stop: '2011-05-01'}); + query.addFilter({type: 'range', field: 'date', from: '2011-01-01', to: '2011-05-01'}); data.query(query.toJSON()).then(function(out) { equal(out.total, 3); deepEqual(_.pluck(out.hits, 'date'), ['2011-01-01','2011-02-03','2011-04-05']); }); query = new recline.Model.Query(); - query.addFilter({type: 'range', field: 'z', start: '0', stop: '10'}); + query.addFilter({type: 'range', field: 'z', from: '0', to: '10'}); data.query(query.toJSON()).then(function(out) { equal(out.total, 3); deepEqual(_.pluck(out.hits, 'z'), [3,6,9]); }); + }); @@ -119,35 +120,37 @@ test('filters with nulls', function () { var data = _wrapData(); query = new recline.Model.Query(); - query.addFilter({type: 'range', field: 'z', start: '', stop: null}); + query.addFilter({type: 'range', field: 'z', from: '', to: null}); data.query(query.toJSON()).then(function(out) { equal(out.total, 6); }); query = new recline.Model.Query(); - query.addFilter({type: 'range', field: 'x', start: '', stop: '3'}); + query.addFilter({type: 'range', field: 'x', from: '', to: '3'}); data.query(query.toJSON()).then(function(out) { equal(out.total, 3); }); query = new recline.Model.Query(); - query.addFilter({type: 'range', field: 'x', start: '3', stop: ''}); + query.addFilter({type: 'range', field: 'x', from: '3', to: ''}); data.query(query.toJSON()).then(function(out) { equal(out.total, 4); }); data.records[5].country = ''; + query = new recline.Model.Query(); - query.addFilter({type: 'range', field: 'country', start: '', stop: 'Z'}); + query.addFilter({type: 'range', field: 'country', from: '', to: 'Z'}); data.query(query.toJSON()).then(function(out) { equal(out.total, 5); }); query = new recline.Model.Query(); - query.addFilter({type: 'range', field: 'x', start: '', stop: ''}); + query.addFilter({type: 'range', field: 'x', from: '', to: ''}); data.query(query.toJSON()).then(function(out) { equal(out.total, 6); }); + }); test('facet', function () { @@ -305,14 +308,14 @@ test('filters', function () { }); dataset = makeBackendDataset(); - dataset.queryState.addFilter({type: 'range', field: 'date', start: '2011-01-01', stop: '2011-05-01'}); + dataset.queryState.addFilter({type: 'range', field: 'date', from: '2011-01-01', to: '2011-05-01'}); dataset.query().then(function() { equal(dataset.records.length, 3); deepEqual(dataset.records.pluck('date'), ['2011-01-01','2011-02-03','2011-04-05']); }); dataset = makeBackendDataset(); - dataset.queryState.addFilter({type: 'range', field: 'z', start: '0', stop: '10'}); + dataset.queryState.addFilter({type: 'range', field: 'z', from: '0', to: '10'}); dataset.query().then(function() { equal(dataset.records.length, 3); deepEqual(dataset.records.pluck('z'), [3,6,9]); diff --git a/test/built.html b/test/built.html index 9caf8822..3e88af2c 100644 --- a/test/built.html +++ b/test/built.html @@ -9,8 +9,8 @@ - - + + @@ -18,7 +18,8 @@ - + + diff --git a/test/index.html b/test/index.html index 5027e979..1dc22ed8 100644 --- a/test/index.html +++ b/test/index.html @@ -4,28 +4,36 @@ Qunit Tests - + + + + + + - - - + + + + + - + - + + @@ -34,15 +42,11 @@ - - - - diff --git a/test/model.test.js b/test/model.test.js index e4ba3617..3103dccd 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -177,6 +177,26 @@ test('Dataset getFieldsSummary', function () { }); }); +test('fetch without and with explicit fields', function () { + var dataset = new recline.Model.Dataset({ + backend: 'csv', + data: 'A,B\n1,2\n3,4' + }); + dataset.fetch(); + equal(dataset.fields.at(0).id, 'A'); + equal(dataset.fields.at(0).get('type'), 'string'); + + var dataset = new recline.Model.Dataset({ + fields: [{id: 'X', type: 'number'}, {id: 'Y'}], + backend: 'csv', + data: 'A,B\n1,2\n3,4' + }); + dataset.fetch(); + equal(dataset.fields.at(0).id, 'X'); + equal(dataset.fields.at(0).get('type'), 'number'); + equal(dataset.records.at(0).get('X'), 1); +}); + test('_normalizeRecordsAndFields', function () { var data = [ // fields but no records @@ -279,7 +299,7 @@ test('_normalizeRecordsAndFields', function () { fields: [{id: 'col1'}, {id: 'col2'}], records: [ {col1: 1, col2: 2}, - {col1: 3, col2: 4}, + {col1: 3, col2: 4} ] }, exp: { @@ -327,6 +347,30 @@ test('Query', function () { deepEqual({terms: {field: 'xyz'}}, query.get('facets')['xyz']); }); +test('Query.addFacet', function () { + var query = new recline.Model.Query(); + query.addFacet('xyz', 25); + deepEqual({terms: {field: 'xyz', "size": 25}}, query.get('facets')['xyz']); +}); + +test('Query.removeFacet', function () { + var query = new recline.Model.Query(); + query.addFacet('xyz'); + deepEqual({terms: {field: 'xyz'}}, query.get('facets')['xyz']); + query.removeFacet('xyz'); + equal(undefined, query.get('facets')['xyz']); +}); + +test('Query.clearFacets', function () { + var query = new recline.Model.Query(); + query.addFacet('abc'); + query.addFacet('xyz'); + deepEqual({terms: {field: 'xyz'}}, query.get('facets')['xyz']); + deepEqual({terms: {field: 'abc'}}, query.get('facets')['abc']); + query.clearFacets(); + deepEqual({}, query.get('facets')); +}); + test('Query.addFilter', function () { var query = new recline.Model.Query(); query.addFilter({type: 'term', field: 'xyz'}); @@ -354,4 +398,56 @@ test('Query.addFilter', function () { deepEqual(exp, query.get('filters')[2]); }); +test('Query.replaceFilter', function () { + var query = new recline.Model.Query(); + query.addFilter({type: 'term', field: 'xyz', term: 'one'}); + var exp = { + field: 'xyz', + type: 'term', + term: 'one' + }; + deepEqual(query.get('filters')[0], exp); + + query.replaceFilter({type: 'term', field: 'xyz', term: 'two'}); + exp = { + field: 'xyz', + type: 'term', + term: 'two' + }; + deepEqual(query.get('filters')[0], exp); + +}); + +test('Query.replaceFilter first filter', function () { + // replaceFilter changes filter order + var query = new recline.Model.Query(); + query.addFilter({type: 'term', field: 'abc', term: 'one'}); + query.addFilter({type: 'term', field: 'xyz', term: 'two'}); + var exp0 = { + field: 'abc', + type: 'term', + term: 'one' + }; + deepEqual(query.get('filters')[0], exp0); + var exp1 = { + field: 'xyz', + type: 'term', + term: 'two' + }; + deepEqual(query.get('filters')[1], exp1); + + var numFilters = query.get('filters').length; + query.replaceFilter({type: 'term', field: 'abc', term: 'three'}); + equal(query.get('filters').length, numFilters); + exp0 = { + field: 'abc', + type: 'term', + term: 'three' + }; + // deletes original filter and adds replacement to end + deepEqual(query.get('filters')[1], exp0); + deepEqual(query.get('filters')[0], exp1); + +}); + })(this.jQuery); diff --git a/test/qunit/qunit-assert-html.js b/test/qunit/qunit-assert-html.js new file mode 100644 index 00000000..223a0fec --- /dev/null +++ b/test/qunit/qunit-assert-html.js @@ -0,0 +1,377 @@ +/*global QUnit:false */ +(function( QUnit, window, undefined ) { + "use strict"; + + var trim = function( s ) { + if ( !s ) { + return ""; + } + return typeof s.trim === "function" ? s.trim() : s.replace( /^\s+|\s+$/g, "" ); + }; + + var normalizeWhitespace = function( s ) { + if ( !s ) { + return ""; + } + return trim( s.replace( /\s+/g, " " ) ); + }; + + var dedupeFlatDict = function( dictToDedupe, parentDict ) { + var key, val; + if ( parentDict ) { + for ( key in dictToDedupe ) { + val = dictToDedupe[key]; + if ( val && ( val === parentDict[key] ) ) { + delete dictToDedupe[key]; + } + } + } + return dictToDedupe; + }; + + var objectKeys = Object.keys || (function() { + var hasOwn = function( obj, propName ) { + return Object.prototype.hasOwnProperty.call( obj, propName ); + }; + return function( obj ) { + var keys = [], + key; + for ( key in obj ) { + if ( hasOwn( obj, key ) ) { + keys.push( key ); + } + } + return keys; + }; + })(); + + /** + * Calculate based on `currentStyle`/`getComputedStyle` styles instead + */ + var getElementStyles = (function() { + + // Memoized + var camelCase = (function() { + var camelCaseFn = (function() { + // Matches dashed string for camelizing + var rmsPrefix = /^-ms-/, + msPrefixFix = "ms-", + rdashAlpha = /-([\da-z])/gi, + camelCaseReplacerFn = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }; + + return function( s ) { + return s.replace(rmsPrefix, msPrefixFix).replace(rdashAlpha, camelCaseReplacerFn); + }; + })(); + + var camelCaseMemoizer = {}; + + return function( s ) { + var temp = camelCaseMemoizer[s]; + if ( temp ) { + return temp; + } + + temp = camelCaseFn( s ); + camelCaseMemoizer[s] = temp; + return temp; + }; + })(); + + var styleKeySortingFn = function( a, b ) { + return camelCase( a ) < camelCase( b ); + }; + + return function( elem ) { + var styleCount, i, key, + styles = {}, + styleKeys = [], + style = elem.ownerDocument.defaultView ? + elem.ownerDocument.defaultView.getComputedStyle( elem, null ) : + elem.currentStyle; + + // `getComputedStyle` + if ( style && style.length && style[0] && style[style[0]] ) { + styleCount = style.length; + while ( styleCount-- ) { + styleKeys.push( style[styleCount] ); + } + styleKeys.sort( styleKeySortingFn ); + + for ( i = 0, styleCount = styleKeys.length ; i < styleCount ; i++ ) { + key = styleKeys[i]; + if ( key !== "cssText" && typeof style[key] === "string" && style[key] ) { + styles[camelCase( key )] = style[key]; + } + } + } + // `currentStyle` support: IE < 9.0, Opera < 10.6 + else { + for ( key in style ) { + styleKeys.push( key ); + } + styleKeys.sort(); + + for ( i = 0, styleCount = styleKeys.length ; i < styleCount ; i++ ) { + key = styleKeys[i]; + if ( key !== "cssText" && typeof style[key] === "string" && style[key] ) { + styles[key] = style[key]; + } + } + } + + return styles; + + }; + })(); + + var serializeElementNode = function( elementNode, rootNodeStyles ) { + var subNodes, i, len, styles, attrName, + serializedNode = { + NodeType: elementNode.nodeType, + NodeName: elementNode.nodeName.toLowerCase(), + Attributes: {}, + ChildNodes: [] + }; + + subNodes = elementNode.attributes; + for ( i = 0, len = subNodes.length ; i < len ; i++ ) { + attrName = subNodes[i].name.toLowerCase(); + if ( attrName === "class" ) { + serializedNode.Attributes[attrName] = normalizeWhitespace( subNodes[i].value ); + } + else if ( attrName !== "style" ) { + serializedNode.Attributes[attrName] = subNodes[i].value; + } + // Ignore the "style" attribute completely + } + + // Only add the style attribute if there is 1+ pertinent rules + styles = dedupeFlatDict( getElementStyles( elementNode ), rootNodeStyles ); + if ( styles && objectKeys( styles ).length ) { + serializedNode.Attributes["style"] = styles; + } + + subNodes = elementNode.childNodes; + for ( i = 0, len = subNodes.length; i < len; i++ ) { + serializedNode.ChildNodes.push( serializeNode( subNodes[i], rootNodeStyles ) ); + } + + return serializedNode; + }; + + var serializeNode = function( node, rootNodeStyles ) { + var serializedNode; + + switch (node.nodeType) { + case 1: // Node.ELEMENT_NODE + serializedNode = serializeElementNode( node, rootNodeStyles ); + break; + case 3: // Node.TEXT_NODE + serializedNode = { + NodeType: node.nodeType, + NodeName: node.nodeName.toLowerCase(), + NodeValue: node.nodeValue + }; + break; + case 4: // Node.CDATA_SECTION_NODE + case 7: // Node.PROCESSING_INSTRUCTION_NODE + case 8: // Node.COMMENT_NODE + serializedNode = { + NodeType: node.nodeType, + NodeName: node.nodeName.toLowerCase(), + NodeValue: trim( node.nodeValue ) + }; + break; + case 5: // Node.ENTITY_REFERENCE_NODE + case 6: // Node.ENTITY_NODE + case 9: // Node.DOCUMENT_NODE + case 10: // Node.DOCUMENT_TYPE_NODE + case 11: // Node.DOCUMENT_FRAGMENT_NODE + case 12: // Node.NOTATION_NODE + serializedNode = { + NodeType: node.nodeType, + NodeName: node.nodeName + }; + break; + case 2: // Node.ATTRIBUTE_NODE + throw new Error( "`node.nodeType` was `Node.ATTRIBUTE_NODE` (2), which is not supported by this method" ); + default: + throw new Error( "`node.nodeType` was not recognized: " + node.nodeType ); + } + + return serializedNode; + }; + + var serializeHtml = function( html ) { + var scratch = getCleanSlate(), + rootNode = scratch.container(), + rootNodeStyles = getElementStyles( rootNode ), + serializedHtml = [], + kids, i, len; + rootNode.innerHTML = trim( html ); + + kids = rootNode.childNodes; + for ( i = 0, len = kids.length; i < len; i++ ) { + serializedHtml.push( serializeNode( kids[i], rootNodeStyles ) ); + } + + scratch.reset(); + + return serializedHtml; + }; + + var getCleanSlate = (function() { + var containerElId = "qunit-html-addon-container", + iframeReady = false, + iframeLoaded = function() { + iframeReady = true; + }, + iframeReadied = function() { + if (iframe.readyState === "complete" || iframe.readyState === 4) { + iframeReady = true; + } + }, + iframeApi, + iframe, + iframeWin, + iframeDoc; + + if ( !iframeApi ) { + + QUnit.begin(function() { + // Initialize the background iframe! + if ( !iframe || !iframeWin || !iframeDoc ) { + iframe = window.document.createElement( "iframe" ); + QUnit.addEvent( iframe, "load", iframeLoaded ); + QUnit.addEvent( iframe, "readystatechange", iframeReadied ); + iframe.style.position = "absolute"; + iframe.style.top = iframe.style.left = "-1000px"; + iframe.height = iframe.width = 0; + + // `getComputedStyle` behaves inconsistently cross-browser when not attached to a live DOM + window.document.body.appendChild( iframe ); + + iframeWin = iframe.contentWindow || + iframe.window || + iframe.contentDocument && iframe.contentDocument.defaultView || + iframe.document && ( iframe.document.defaultView || iframe.document.window ) || + window.frames[( iframe.name || iframe.id )]; + + iframeDoc = iframeWin && iframeWin.document || + iframe.contentDocument || + iframe.document; + + var iframeContents = [ + "", + "", + "", + " QUnit HTML addon iframe", + "", + "", + "
    ", + " ", + "", + "" + ].join( "\n" ); + + iframeDoc.open(); + iframeDoc.write( iframeContents ); + iframeDoc.close(); + + // Is ready? + iframeReady = iframeReady || iframeWin.isReady; + } + }); + + QUnit.done(function() { + if ( iframe && iframe.ownerDocument ) { + iframe.parentNode.removeChild( iframe ); + } + iframe = iframeWin = iframeDoc = null; + iframeReady = false; + }); + + var waitForIframeReady = function( maxTimeout ) { + if ( !iframeReady ) { + if ( !maxTimeout ) { + maxTimeout = 2000; // 2 seconds MAX + } + var startTime = new Date(); + while ( !iframeReady && ( ( new Date() - startTime ) < maxTimeout ) ) { + iframeReady = iframeReady || iframeWin.isReady; + } + } + }; + + iframeApi = { + container: function() { + waitForIframeReady(); + if ( iframeReady && iframeDoc ) { + return iframeDoc.getElementById( containerElId ); + } + return undefined; + }, + reset: function() { + var containerEl = iframeApi.container(); + if ( containerEl ) { + containerEl.innerHTML = ""; + } + } + }; + } + + // Actual function signature for `getCleanState` + return function() { return iframeApi; }; + })(); + + QUnit.extend( QUnit.assert, { + + /** + * Compare two snippets of HTML for equality after normalization. + * + * @example assert.htmlEqual("Hello, QUnit! ", "Hello, QUnit!", "HTML should be equal"); + * @param {String} actual The actual HTML before normalization. + * @param {String} expected The excepted HTML before normalization. + * @param {String} [message] Optional message to display in the results. + */ + htmlEqual: function( actual, expected, message ) { + if ( !message ) { + message = "HTML should be equal"; + } + + this.deepEqual( serializeHtml( actual ), serializeHtml( expected ), message ); + }, + + /** + * Compare two snippets of HTML for inequality after normalization. + * + * @example assert.notHtmlEqual("Hello, QUnit!", "Hello, QUnit!", "HTML should not be equal"); + * @param {String} actual The actual HTML before normalization. + * @param {String} expected The excepted HTML before normalization. + * @param {String} [message] Optional message to display in the results. + */ + notHtmlEqual: function( actual, expected, message ) { + if ( !message ) { + message = "HTML should not be equal"; + } + + this.notDeepEqual( serializeHtml( actual ), serializeHtml( expected ), message ); + }, + + /** + * @private + * Normalize and serialize an HTML snippet. Primarily only exposed for unit testing purposes. + * + * @example assert._serializeHtml('Test'); + * @param {String} html The HTML snippet to normalize and serialize. + * @returns {Object[]} The normalized and serialized form of the HTML snippet. + */ + _serializeHtml: serializeHtml + + }); +})( QUnit, this ); diff --git a/test/qunit/qunit.css b/test/qunit/qunit.css index 23235ec8..d7fc0c8e 100644 --- a/test/qunit/qunit.css +++ b/test/qunit/qunit.css @@ -1,11 +1,11 @@ /** - * QUnit v1.6.0 - A JavaScript Unit Testing Framework + * QUnit v1.11.0 - A JavaScript Unit Testing Framework * - * http://docs.jquery.com/QUnit + * http://qunitjs.com * - * Copyright (c) 2012 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license */ /** Font Family and Sizes */ @@ -20,7 +20,7 @@ /** Resets */ -#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { +#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { margin: 0; padding: 0; } @@ -38,10 +38,10 @@ line-height: 1em; font-weight: normal; - border-radius: 15px 15px 0 0; - -moz-border-radius: 15px 15px 0 0; - -webkit-border-top-right-radius: 15px; - -webkit-border-top-left-radius: 15px; + border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + -webkit-border-top-right-radius: 5px; + -webkit-border-top-left-radius: 5px; } #qunit-header a { @@ -54,9 +54,9 @@ color: #fff; } -#qunit-header label { +#qunit-testrunner-toolbar label { display: inline-block; - padding-left: 0.5em; + padding: 0 .5em 0 .1em; } #qunit-banner { @@ -67,6 +67,7 @@ padding: 0.5em 0 0.5em 2em; color: #5E740B; background-color: #eee; + overflow: hidden; } #qunit-userAgent { @@ -76,6 +77,9 @@ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; } +#qunit-modulefilter-container { + float: right; +} /** Tests: Pass/Fail */ @@ -107,19 +111,24 @@ color: #000; } -#qunit-tests ol { +#qunit-tests li .runtime { + float: right; + font-size: smaller; +} + +.qunit-assert-list { margin-top: 0.5em; padding: 0.5em; background-color: #fff; - border-radius: 15px; - -moz-border-radius: 15px; - -webkit-border-radius: 15px; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; +} - box-shadow: inset 0px 2px 13px #999; - -moz-box-shadow: inset 0px 2px 13px #999; - -webkit-box-shadow: inset 0px 2px 13px #999; +.qunit-collapsed { + display: none; } #qunit-tests table { @@ -162,8 +171,7 @@ #qunit-tests b.failed { color: #710909; } #qunit-tests li li { - margin: 0.5em; - padding: 0.4em 0.5em 0.4em 0.5em; + padding: 5px; background-color: #fff; border-bottom: none; list-style-position: inside; @@ -172,9 +180,9 @@ /*** Passing Styles */ #qunit-tests li li.pass { - color: #5E740B; + color: #3c510c; background-color: #fff; - border-left: 26px solid #C6E746; + border-left: 10px solid #C6E746; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } @@ -190,15 +198,15 @@ #qunit-tests li li.fail { color: #710909; background-color: #fff; - border-left: 26px solid #EE5757; + border-left: 10px solid #EE5757; white-space: pre; } #qunit-tests > li:last-child { - border-radius: 0 0 15px 15px; - -moz-border-radius: 0 0 15px 15px; - -webkit-border-bottom-right-radius: 15px; - -webkit-border-bottom-left-radius: 15px; + border-radius: 0 0 5px 5px; + -moz-border-radius: 0 0 5px 5px; + -webkit-border-bottom-right-radius: 5px; + -webkit-border-bottom-left-radius: 5px; } #qunit-tests .fail { color: #000000; background-color: #EE5757; } diff --git a/test/qunit/qunit.js b/test/qunit/qunit.js index 2c277fab..302545f4 100644 --- a/test/qunit/qunit.js +++ b/test/qunit/qunit.js @@ -1,54 +1,113 @@ /** - * QUnit v1.6.0 - A JavaScript Unit Testing Framework + * QUnit v1.11.0 - A JavaScript Unit Testing Framework * - * http://docs.jquery.com/QUnit + * http://qunitjs.com * - * Copyright (c) 2012 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license */ (function( window ) { var QUnit, + assert, config, + onErrorFnPrev, testId = 0, + fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, + // Keep a local reference to Date (GH-283) + Date = window.Date, defined = { - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch( e ) { - return false; + setTimeout: typeof window.setTimeout !== "undefined", + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch( e ) { + return false; + } + }()) + }, + /** + * Provides a normalized error string, correcting an issue + * with IE 7 (and prior) where Error.prototype.toString is + * not properly implemented + * + * Based on http://es5.github.com/#x15.11.4.4 + * + * @param {String|Error} error + * @return {String} error message + */ + errorString = function( error ) { + var name, message, + errorString = error.toString(); + if ( errorString.substring( 0, 7 ) === "[object" ) { + name = error.name ? error.name.toString() : "Error"; + message = error.message ? error.message.toString() : ""; + if ( name && message ) { + return name + ": " + message; + } else if ( name ) { + return name; + } else if ( message ) { + return message; + } else { + return "Error"; + } + } else { + return errorString; } - }()) -}; + }, + /** + * Makes a clone of an object using only Array or Object as base, + * and copies over the own enumerable properties. + * + * @param {Object} obj + * @return {Object} New object with only the own properties (recursively). + */ + objectValues = function( obj ) { + // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. + /*jshint newcap: false */ + var key, val, + vals = QUnit.is( "array", obj ) ? [] : {}; + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + val = obj[key]; + vals[key] = val === Object(val) ? objectValues(val) : val; + } + } + return vals; + }; -function Test( name, testName, expected, async, callback ) { - this.name = name; - this.testName = testName; - this.expected = expected; - this.async = async; - this.callback = callback; +function Test( settings ) { + extend( this, settings ); this.assertions = []; + this.testNumber = ++Test.count; } +Test.count = 0; + Test.prototype = { init: function() { - var b, li, - tests = id( "qunit-tests" ); + var a, b, li, + tests = id( "qunit-tests" ); if ( tests ) { b = document.createElement( "strong" ); - b.innerHTML = "Running " + this.name; + b.innerHTML = this.nameHtml; + + // `a` initialized at top of scope + a = document.createElement( "a" ); + a.innerHTML = "Rerun"; + a.href = QUnit.url({ testNumber: this.testNumber }); li = document.createElement( "li" ); li.appendChild( b ); + li.appendChild( a ); li.className = "running"; li.id = this.id = "qunit-test-output" + testId++; @@ -83,6 +142,7 @@ Test.prototype = { teardown: function() {} }, this.moduleTestEnvironment ); + this.started = +new Date(); runLoggingCallbacks( "testStart", QUnit, { name: this.testName, module: this.module @@ -102,7 +162,7 @@ Test.prototype = { try { this.testEnvironment.setup.call( this.testEnvironment ); } catch( e ) { - QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); + QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); } }, run: function() { @@ -111,22 +171,28 @@ Test.prototype = { var running = id( "qunit-testresult" ); if ( running ) { - running.innerHTML = "Running:
    " + this.name; + running.innerHTML = "Running:
    " + this.nameHtml; } if ( this.async ) { QUnit.stop(); } + this.callbackStarted = +new Date(); + if ( config.notrycatch ) { - this.callback.call( this.testEnvironment ); + this.callback.call( this.testEnvironment, QUnit.assert ); + this.callbackRuntime = +new Date() - this.callbackStarted; return; } try { - this.callback.call( this.testEnvironment ); + this.callback.call( this.testEnvironment, QUnit.assert ); + this.callbackRuntime = +new Date() - this.callbackStarted; } catch( e ) { - QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + ": " + e.message, extractStacktrace( e, 1 ) ); + this.callbackRuntime = +new Date() - this.callbackStarted; + + QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); // else next test will carry the responsibility saveGlobal(); @@ -139,35 +205,43 @@ Test.prototype = { teardown: function() { config.current = this; if ( config.notrycatch ) { + if ( typeof this.callbackRuntime === "undefined" ) { + this.callbackRuntime = +new Date() - this.callbackStarted; + } this.testEnvironment.teardown.call( this.testEnvironment ); return; } else { try { this.testEnvironment.teardown.call( this.testEnvironment ); } catch( e ) { - QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); + QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); } } checkPollution(); }, finish: function() { config.current = this; - if ( this.expected != null && this.expected != this.assertions.length ) { + if ( config.requireExpects && this.expected === null ) { + QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); + } else if ( this.expected !== null && this.expected !== this.assertions.length ) { QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); - } else if ( this.expected == null && !this.assertions.length ) { + } else if ( this.expected === null && !this.assertions.length ) { QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); } - var assertion, a, b, i, li, ol, + var i, assertion, a, b, time, li, ol, + test = this, good = 0, bad = 0, tests = id( "qunit-tests" ); + this.runtime = +new Date() - this.started; config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; if ( tests ) { ol = document.createElement( "ol" ); + ol.className = "qunit-assert-list"; for ( i = 0; i < this.assertions.length; i++ ) { assertion = this.assertions[i]; @@ -196,42 +270,42 @@ Test.prototype = { } if ( bad === 0 ) { - ol.style.display = "none"; + addClass( ol, "qunit-collapsed" ); } // `b` initialized at top of scope b = document.createElement( "strong" ); - b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - // `a` initialized at top of scope - a = document.createElement( "a" ); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ filter: getText([b]).replace( /\([^)]+\)$/, "" ).replace( /(^\s*|\s*$)/g, "" ) }); + b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; addEvent(b, "click", function() { - var next = b.nextSibling.nextSibling, - display = next.style.display; - next.style.display = display === "none" ? "block" : "none"; + var next = b.parentNode.lastChild, + collapsed = hasClass( next, "qunit-collapsed" ); + ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); }); addEvent(b, "dblclick", function( e ) { var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { + if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { target = target.parentNode; } if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ - filter: getText([target]).replace( /\([^)]+\)$/, "" ).replace( /(^\s*|\s*$)/g, "" ) - }); + window.location = QUnit.url({ testNumber: test.testNumber }); } }); + // `time` initialized at top of scope + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = this.runtime + " ms"; + // `li` initialized at top of scope li = id( this.id ); li.className = bad ? "fail" : "pass"; li.removeChild( li.firstChild ); + a = li.firstChild; li.appendChild( b ); li.appendChild( a ); + li.appendChild( time ); li.appendChild( ol ); } else { @@ -249,10 +323,13 @@ Test.prototype = { module: this.module, failed: bad, passed: this.assertions.length - bad, - total: this.assertions.length + total: this.assertions.length, + duration: this.runtime }); QUnit.reset(); + + config.current = undefined; }, queue: function() { @@ -291,13 +368,15 @@ Test.prototype = { } }; +// Root QUnit object. // `QUnit` initialized at top of scope QUnit = { // call on start of module test to prepend name to all tests module: function( name, testEnvironment ) { config.currentModule = name; - config.currentModuleTestEnviroment = testEnvironment; + config.currentModuleTestEnvironment = testEnvironment; + config.modules[name] = true; }, asyncTest: function( testName, expected, callback ) { @@ -311,7 +390,7 @@ QUnit = { test: function( testName, expected, callback, async ) { var test, - name = "" + escapeInnerText( testName ) + ""; + nameHtml = "" + escapeText( testName ) + ""; if ( arguments.length === 2 ) { callback = expected; @@ -319,117 +398,49 @@ QUnit = { } if ( config.currentModule ) { - name = "" + config.currentModule + ": " + name; + nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; } - if ( !validTest(config.currentModule + ": " + testName) ) { + test = new Test({ + nameHtml: nameHtml, + testName: testName, + expected: expected, + async: async, + callback: callback, + module: config.currentModule, + moduleTestEnvironment: config.currentModuleTestEnvironment, + stack: sourceFromStacktrace( 2 ) + }); + + if ( !validTest( test ) ) { return; } - test = new Test( name, testName, expected, async, callback ); - test.module = config.currentModule; - test.moduleTestEnvironment = config.currentModuleTestEnviroment; - test.stack = sourceFromStacktrace( 2 ); test.queue(); }, // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. expect: function( asserts ) { - config.current.expected = asserts; - }, - - // Asserts true. - // @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - ok: function( result, msg ) { - if ( !config.current ) { - throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); + if (arguments.length === 1) { + config.current.expected = asserts; + } else { + return config.current.expected; } - result = !!result; - - var source, - details = { - result: result, - message: msg - }; - - msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); - msg = "" + msg + ""; - - if ( !result ) { - source = sourceFromStacktrace( 2 ); - if ( source ) { - details.source = source; - msg += "
    Source:
    " + escapeInnerText( source ) + "
    "; - } - } - runLoggingCallbacks( "log", QUnit, details ); - config.current.assertions.push({ - result: result, - message: msg - }); - }, - - // Checks that the first two arguments are equal, with an optional message. Prints out both actual and expected values. - // @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes." ); - equal: function( actual, expected, message ) { - QUnit.push( expected == actual, actual, expected, message ); - }, - - notEqual: function( actual, expected, message ) { - QUnit.push( expected != actual, actual, expected, message ); - }, - - deepEqual: function( actual, expected, message ) { - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); - }, - - notDeepEqual: function( actual, expected, message ) { - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); - }, - - strictEqual: function( actual, expected, message ) { - QUnit.push( expected === actual, actual, expected, message ); - }, - - notStrictEqual: function( actual, expected, message ) { - QUnit.push( expected !== actual, actual, expected, message ); - }, - - raises: function( block, expected, message ) { - var actual, - ok = false; - - if ( typeof expected === "string" ) { - message = expected; - expected = null; - } - - try { - block.call( config.current.testEnvironment ); - } catch (e) { - actual = e; - } - - if ( actual ) { - // we don't want to validate thrown error - if ( !expected ) { - ok = true; - // expected is a regexp - } else if ( QUnit.objectType( expected ) === "regexp" ) { - ok = expected.test( actual ); - // expected is a constructor - } else if ( actual instanceof expected ) { - ok = true; - // expected is a validation function which returns true is validation passed - } else if ( expected.call( {}, actual ) === true ) { - ok = true; - } - } - - QUnit.ok( ok, message ); }, start: function( count ) { + // QUnit hasn't been initialized yet. + // Note: RequireJS (et al) may delay onLoad + if ( config.semaphore === undefined ) { + QUnit.begin(function() { + // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first + setTimeout(function() { + QUnit.start( count ); + }); + }); + return; + } + config.semaphore -= count || 1; // don't start until equal number of stop-calls if ( config.semaphore > 0 ) { @@ -438,6 +449,8 @@ QUnit = { // ignore if start is called more often then stop if ( config.semaphore < 0 ) { config.semaphore = 0; + QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); + return; } // A slight delay, to avoid any current callbacks if ( defined.setTimeout ) { @@ -473,6 +486,191 @@ QUnit = { } }; +// `assert` initialized at top of scope +// Asssert helpers +// All of these must either call QUnit.push() or manually do: +// - runLoggingCallbacks( "log", .. ); +// - config.current.assertions.push({ .. }); +// We attach it to the QUnit object *after* we expose the public API, +// otherwise `assert` will become a global variable in browsers (#341). +assert = { + /** + * Asserts rough true-ish result. + * @name ok + * @function + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function( result, msg ) { + if ( !config.current ) { + throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); + } + result = !!result; + + var source, + details = { + module: config.current.module, + name: config.current.testName, + result: result, + message: msg + }; + + msg = escapeText( msg || (result ? "okay" : "failed" ) ); + msg = "" + msg + ""; + + if ( !result ) { + source = sourceFromStacktrace( 2 ); + if ( source ) { + details.source = source; + msg += "
    Source:
    " + escapeText( source ) + "
    "; + } + } + runLoggingCallbacks( "log", QUnit, details ); + config.current.assertions.push({ + result: result, + message: msg + }); + }, + + /** + * Assert that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * @name equal + * @function + * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); + */ + equal: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + QUnit.push( expected == actual, actual, expected, message ); + }, + + /** + * @name notEqual + * @function + */ + notEqual: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + QUnit.push( expected != actual, actual, expected, message ); + }, + + /** + * @name propEqual + * @function + */ + propEqual: function( actual, expected, message ) { + actual = objectValues(actual); + expected = objectValues(expected); + QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name notPropEqual + * @function + */ + notPropEqual: function( actual, expected, message ) { + actual = objectValues(actual); + expected = objectValues(expected); + QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name deepEqual + * @function + */ + deepEqual: function( actual, expected, message ) { + QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name notDeepEqual + * @function + */ + notDeepEqual: function( actual, expected, message ) { + QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name strictEqual + * @function + */ + strictEqual: function( actual, expected, message ) { + QUnit.push( expected === actual, actual, expected, message ); + }, + + /** + * @name notStrictEqual + * @function + */ + notStrictEqual: function( actual, expected, message ) { + QUnit.push( expected !== actual, actual, expected, message ); + }, + + "throws": function( block, expected, message ) { + var actual, + expectedOutput = expected, + ok = false; + + // 'expected' is optional + if ( typeof expected === "string" ) { + message = expected; + expected = null; + } + + config.current.ignoreGlobalErrors = true; + try { + block.call( config.current.testEnvironment ); + } catch (e) { + actual = e; + } + config.current.ignoreGlobalErrors = false; + + if ( actual ) { + // we don't want to validate thrown error + if ( !expected ) { + ok = true; + expectedOutput = null; + // expected is a regexp + } else if ( QUnit.objectType( expected ) === "regexp" ) { + ok = expected.test( errorString( actual ) ); + // expected is a constructor + } else if ( actual instanceof expected ) { + ok = true; + // expected is a validation function which returns true is validation passed + } else if ( expected.call( {}, actual ) === true ) { + expectedOutput = null; + ok = true; + } + + QUnit.push( ok, actual, expectedOutput, message ); + } else { + QUnit.pushFailure( message, null, 'No exception was thrown.' ); + } + } +}; + +/** + * @deprecate since 1.8.0 + * Kept assertion helpers in root for backwards compatibility. + */ +extend( QUnit, assert ); + +/** + * @deprecated since 1.9.0 + * Kept root "raises()" for backwards compatibility. + * (Note that we don't introduce assert.raises). + */ +QUnit.raises = assert[ "throws" ]; + +/** + * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 + * Kept to avoid TypeErrors for undefined methods. + */ +QUnit.equals = function() { + QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); +}; +QUnit.same = function() { + QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); +}; + // We want access to the constructor's prototype (function() { function F() {} @@ -482,17 +680,11 @@ QUnit = { QUnit.constructor = F; }()); -// deprecated; still export them to window to provide clear error messages -// next step: remove entirely -QUnit.equals = function() { - QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); -}; -QUnit.same = function() { - QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); -}; - -// Maintain internal state -// `config` initialized at top of scope +/** + * Config object: Maintain internal state + * Later exposed as QUnit.config + * `config` initialized at top of scope + */ config = { // The queue of tests to run queue: [], @@ -511,7 +703,26 @@ config = { // by default, modify document.title when suite is done altertitle: true, - urlConfig: [ "noglobals", "notrycatch" ], + // when enabled, all tests must call expect() + requireExpects: false, + + // add checkboxes that are persisted in the query-string + // when enabled, the id is set to `true` as a `QUnit.config` property + urlConfig: [ + { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." + }, + { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." + } + ], + + // Set of all modules. + modules: {}, // logging callback queues begin: [], @@ -523,7 +734,16 @@ config = { moduleDone: [] }; -// Load paramaters +// Export global variables, unless an 'exports' object exists, +// in that case we assume we're in CommonJS (dealt with on the bottom of the script) +if ( typeof exports === "undefined" ) { + extend( window, QUnit ); + + // Expose QUnit object + window.QUnit = QUnit; +} + +// Initialize more QUnit.config and QUnit.urlParams (function() { var i, location = window.location || { search: "", protocol: "file:" }, @@ -543,21 +763,24 @@ config = { } QUnit.urlParams = urlParams; + + // String search anywhere in moduleName+testName config.filter = urlParams.filter; + // Exact match of the module name + config.module = urlParams.module; + + config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; + // Figure out if we're running the tests from a server or not QUnit.isLocal = location.protocol === "file:"; }()); -// Expose the API as global variables, unless an 'exports' object exists, -// in that case we assume we're in CommonJS - export everything at the end -if ( typeof exports === "undefined" ) { - extend( window, QUnit ); - window.QUnit = QUnit; -} - -// define these after exposing globals to keep them in these QUnit namespace only +// Extend QUnit object, +// these after set here because they should not be exposed as global functions extend( QUnit, { + assert: assert, + config: config, // Initialize the configuration options @@ -572,7 +795,7 @@ extend( QUnit, { autorun: false, filter: "", queue: [], - semaphore: 0 + semaphore: 1 }); var tests, banner, result, @@ -580,7 +803,7 @@ extend( QUnit, { if ( qunit ) { qunit.innerHTML = - "

    " + escapeInnerText( document.title ) + "

    " + + "

    " + escapeText( document.title ) + "

    " + "

    " + "
    " + "

    " + @@ -613,17 +836,10 @@ extend( QUnit, { }, // Resets the test setup. Useful for tests that modify the DOM. - // If jQuery is available, uses jQuery's html(), otherwise just innerHTML. reset: function() { - var fixture; - - if ( window.jQuery ) { - jQuery( "#qunit-fixture" ).html( config.fixture ); - } else { - fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + fixture.innerHTML = config.fixture; } }, @@ -643,7 +859,7 @@ extend( QUnit, { // Safe object type checking is: function( type, obj ) { - return QUnit.objectType( obj ) == type; + return QUnit.objectType( obj ) === type; }, objectType: function( obj ) { @@ -655,7 +871,8 @@ extend( QUnit, { return "null"; } - var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; + var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), + type = match && match[1] || ""; switch ( type ) { case "Number": @@ -684,22 +901,24 @@ extend( QUnit, { var output, source, details = { + module: config.current.module, + name: config.current.testName, result: result, message: message, actual: actual, expected: expected }; - message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); + message = escapeText( message ) || ( result ? "okay" : "failed" ); message = "" + message + ""; output = message; if ( !result ) { - expected = escapeInnerText( QUnit.jsDump.parse(expected) ); - actual = escapeInnerText( QUnit.jsDump.parse(actual) ); + expected = escapeText( QUnit.jsDump.parse(expected) ); + actual = escapeText( QUnit.jsDump.parse(actual) ); output += ""; - if ( actual != expected ) { + if ( actual !== expected ) { output += ""; output += ""; } @@ -708,7 +927,7 @@ extend( QUnit, { if ( source ) { details.source = source; - output += ""; + output += ""; } output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    Source:
    " + escapeText( source ) + "
    "; @@ -722,22 +941,36 @@ extend( QUnit, { }); }, - pushFailure: function( message, source ) { + pushFailure: function( message, source, actual ) { + if ( !config.current ) { + throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); + } + var output, details = { + module: config.current.module, + name: config.current.testName, result: false, message: message }; - message = escapeInnerText(message ) || "error"; + message = escapeText( message ) || "error"; message = "" + message + ""; output = message; + output += ""; + + if ( actual ) { + output += ""; + } + if ( source ) { details.source = source; - output += "
    Result:
    " + escapeText( actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    "; + output += "Source:
    " + escapeText( source ) + "
    "; } + output += ""; + runLoggingCallbacks( "log", QUnit, details ); config.current.assertions.push({ @@ -758,31 +991,44 @@ extend( QUnit, { querystring += encodeURIComponent( key ) + "=" + encodeURIComponent( params[ key ] ) + "&"; } - return window.location.pathname + querystring.slice( 0, -1 ); + return window.location.protocol + "//" + window.location.host + + window.location.pathname + querystring.slice( 0, -1 ); }, extend: extend, id: id, addEvent: addEvent + // load, equiv, jsDump, diff: Attached later }); -// QUnit.constructor is set to the empty F() above so that we can add to it's prototype later -// Doing this allows us to tell if the following methods have been overwritten on the actual -// QUnit object, which is a deprecated way of using the callbacks. +/** + * @deprecated: Created for backwards compatibility with test runner that set the hook function + * into QUnit.{hook}, instead of invoking it and passing the hook function. + * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. + * Doing this allows us to tell if the following methods have been overwritten on the actual + * QUnit object. + */ extend( QUnit.constructor.prototype, { + // Logging callbacks; all receive a single argument with the listed properties // run test/logs.html for any related changes begin: registerLoggingCallback( "begin" ), + // done: { failed, passed, total, runtime } done: registerLoggingCallback( "done" ), + // log: { result, actual, expected, message } log: registerLoggingCallback( "log" ), + // testStart: { name } testStart: registerLoggingCallback( "testStart" ), - // testDone: { name, failed, passed, total } + + // testDone: { name, failed, passed, total, duration } testDone: registerLoggingCallback( "testDone" ), + // moduleStart: { name } moduleStart: registerLoggingCallback( "moduleStart" ), + // moduleDone: { name, failed, passed, total } moduleDone: registerLoggingCallback( "moduleDone" ) }); @@ -796,6 +1042,9 @@ QUnit.load = function() { // Initialize the config, saving the execution queue var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, + urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter, + numModules = 0, + moduleFilterHtml = "", urlConfigHtml = "", oldconfig = extend( {}, config ); @@ -808,10 +1057,36 @@ QUnit.load = function() { for ( i = 0; i < len; i++ ) { val = config.urlConfig[i]; - config[val] = QUnit.urlParams[val]; - urlConfigHtml += ""; + if ( typeof val === "string" ) { + val = { + id: val, + label: val, + tooltip: "[no tooltip available]" + }; + } + config[ val.id ] = QUnit.urlParams[ val.id ]; + urlConfigHtml += ""; } + moduleFilterHtml += ""; + // `userAgent` initialized at top of scope userAgent = id( "qunit-userAgent" ); if ( userAgent ) { @@ -821,12 +1096,7 @@ QUnit.load = function() { // `banner` initialized at top of scope banner = id( "qunit-header" ); if ( banner ) { - banner.innerHTML = "" + banner.innerHTML + " " + urlConfigHtml; - addEvent( banner, "change", function( event ) { - var params = {}; - params[ event.target.name ] = event.target.checked ? true : undefined; - window.location = QUnit.url( params ); - }); + banner.innerHTML = "" + banner.innerHTML + " "; } // `toolbar` initialized at top of scope @@ -867,8 +1137,37 @@ QUnit.load = function() { // `label` initialized at top of scope label = document.createElement( "label" ); label.setAttribute( "for", "qunit-filter-pass" ); + label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." ); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); + + urlConfigCheckboxesContainer = document.createElement("span"); + urlConfigCheckboxesContainer.innerHTML = urlConfigHtml; + urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input"); + // For oldIE support: + // * Add handlers to the individual elements instead of the container + // * Use "click" instead of "change" + // * Fallback from event.target to event.srcElement + addEvents( urlConfigCheckboxes, "click", function( event ) { + var params = {}, + target = event.target || event.srcElement; + params[ target.name ] = target.checked ? true : undefined; + window.location = QUnit.url( params ); + }); + toolbar.appendChild( urlConfigCheckboxesContainer ); + + if (numModules > 1) { + moduleFilter = document.createElement( 'span' ); + moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' ); + moduleFilter.innerHTML = moduleFilterHtml; + addEvent( moduleFilter.lastChild, "change", function() { + var selectBox = moduleFilter.getElementsByTagName("select")[0], + selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); + + window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } ); + }); + toolbar.appendChild(moduleFilter); + } } // `main` initialized at top of scope @@ -884,15 +1183,36 @@ QUnit.load = function() { addEvent( window, "load", QUnit.load ); -// addEvent(window, "error" ) gives us a useless event object -window.onerror = function( message, file, line ) { - if ( QUnit.config.current ) { - QUnit.pushFailure( message, file + ":" + line ); - } else { - QUnit.test( "global failure", function() { - QUnit.pushFailure( message, file + ":" + line ); - }); +// `onErrorFnPrev` initialized at top of scope +// Preserve other handlers +onErrorFnPrev = window.onerror; + +// Cover uncaught exceptions +// Returning true will surpress the default browser handler, +// returning false will let it run. +window.onerror = function ( error, filePath, linerNr ) { + var ret = false; + if ( onErrorFnPrev ) { + ret = onErrorFnPrev( error, filePath, linerNr ); } + + // Treat return value as window.onerror itself does, + // Only do our handling if not surpressed. + if ( ret !== true ) { + if ( QUnit.config.current ) { + if ( QUnit.config.current.ignoreGlobalErrors ) { + return true; + } + QUnit.pushFailure( error, filePath + ":" + linerNr ); + } else { + QUnit.test( "global failure", extend( function() { + QUnit.pushFailure( error, filePath + ":" + linerNr ); + }, { validTest: validTest } ) ); + } + return false; + } + + return ret; }; function done() { @@ -919,7 +1239,7 @@ function done() { " milliseconds.
    ", "", passed, - " tests of ", + " assertions of ", config.stats.all, " passed, ", config.stats.bad, @@ -954,6 +1274,11 @@ function done() { } } + // scroll back to top to show results + if ( window.scrollTo ) { + window.scrollTo(0, 0); + } + runLoggingCallbacks( "done", QUnit, { failed: config.stats.bad, passed: passed, @@ -962,39 +1287,52 @@ function done() { }); } -function validTest( name ) { - var not, - filter = config.filter, - run = false; +/** @return Boolean: true if this test should be ran */ +function validTest( test ) { + var include, + filter = config.filter && config.filter.toLowerCase(), + module = config.module && config.module.toLowerCase(), + fullName = (test.module + ": " + test.testName).toLowerCase(); + + // Internally-generated tests are always valid + if ( test.callback && test.callback.validTest === validTest ) { + delete test.callback.validTest; + return true; + } + + if ( config.testNumber ) { + return test.testNumber === config.testNumber; + } + + if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { + return false; + } if ( !filter ) { return true; } - not = filter.charAt( 0 ) === "!"; - - if ( not ) { + include = filter.charAt( 0 ) !== "!"; + if ( !include ) { filter = filter.slice( 1 ); } - if ( name.indexOf( filter ) !== -1 ) { - return !not; + // If the filter matches, we need to honour include + if ( fullName.indexOf( filter ) !== -1 ) { + return include; } - if ( not ) { - run = true; - } - - return run; + // Otherwise, do the opposite + return !include; } // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) // Later Safari and IE10 are supposed to support error.stack as well // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack function extractStacktrace( e, offset ) { - offset = offset || 3; + offset = offset === undefined ? 3 : offset; - var stack; + var stack, include, i; if ( e.stacktrace ) { // Opera @@ -1005,6 +1343,18 @@ function extractStacktrace( e, offset ) { if (/^error$/i.test( stack[0] ) ) { stack.shift(); } + if ( fileName ) { + include = []; + for ( i = offset; i < stack.length; i++ ) { + if ( stack[ i ].indexOf( fileName ) !== -1 ) { + break; + } + include.push( stack[ i ] ); + } + if ( include.length ) { + return include.join( "\n" ); + } + } return stack[ offset ]; } else if ( e.sourceURL ) { // Safari, PhantomJS @@ -1025,17 +1375,27 @@ function sourceFromStacktrace( offset ) { } } -function escapeInnerText( s ) { +/** + * Escape text for attribute or text content. + */ +function escapeText( s ) { if ( !s ) { return ""; } s = s + ""; - return s.replace( /[\&<>]/g, function( s ) { + // Both single quotes and double quotes (for attributes) + return s.replace( /['"<>&]/g, function( s ) { switch( s ) { - case "&": return "&"; - case "<": return "<"; - case ">": return ">"; - default: return s; + case '\'': + return '''; + case '"': + return '"'; + case '<': + return '<'; + case '>': + return '>'; + case '&': + return '&'; } }); } @@ -1083,7 +1443,7 @@ function saveGlobal() { } } -function checkPollution( name ) { +function checkPollution() { var newGlobals, deletedGlobals, old = config.pollution; @@ -1132,16 +1492,53 @@ function extend( a, b ) { return a; } +/** + * @param {HTMLElement} elem + * @param {string} type + * @param {Function} fn + */ function addEvent( elem, type, fn ) { + // Standards-based browsers if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, fn ); + // IE } else { - fn(); + elem.attachEvent( "on" + type, fn ); } } +/** + * @param {Array|NodeList} elems + * @param {string} type + * @param {Function} fn + */ +function addEvents( elems, type, fn ) { + var i = elems.length; + while ( i-- ) { + addEvent( elems[i], type, fn ); + } +} + +function hasClass( elem, name ) { + return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; +} + +function addClass( elem, name ) { + if ( !hasClass( elem, name ) ) { + elem.className += (elem.className ? " " : "") + name; + } +} + +function removeClass( elem, name ) { + var set = " " + elem.className + " "; + // Class name may appear multiple times + while ( set.indexOf(" " + name + " ") > -1 ) { + set = set.replace(" " + name + " " , " "); + } + // If possible, trim it for prettiness, but not neccecarily + elem.className = window.jQuery ? jQuery.trim( set ) : ( set.trim ? set.trim() : set ); +} + function id( name ) { return !!( typeof document !== "undefined" && document && document.getElementById ) && document.getElementById( name ); @@ -1155,7 +1552,6 @@ function registerLoggingCallback( key ) { // Supports deprecated method of completely overwriting logging callbacks function runLoggingCallbacks( key, scope, args ) { - //debugger; var i, callbacks; if ( QUnit.hasOwnProperty( key ) ) { QUnit[ key ].call(scope, args ); @@ -1197,6 +1593,7 @@ QUnit.equiv = (function() { // for string, boolean, number and null function useStrictEquality( b, a ) { + /*jshint eqeqeq:false */ if ( b instanceof a.constructor || a instanceof b.constructor ) { // to catch short annotaion VS 'new' annotation of a // declaration @@ -1231,7 +1628,8 @@ QUnit.equiv = (function() { a.global === b.global && // (gmi) ... a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; + a.multiline === b.multiline && + a.sticky === b.sticky; }, // - skip when the property is a method of an instance (OOP) @@ -1392,7 +1790,8 @@ QUnit.jsDump = (function() { var reName = /^function (\w+)/, jsDump = { - parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance + // type is used mostly internally, you can fix a (custom)type in advance + parse: function( obj, type, stack ) { stack = stack || [ ]; var inStack, res, parser = this.parsers[ type || this.typeOf(obj) ]; @@ -1400,18 +1799,16 @@ QUnit.jsDump = (function() { type = typeof parser; inStack = inArray( obj, stack ); - if ( inStack != -1 ) { + if ( inStack !== -1 ) { return "recursion(" + (inStack - stack.length) + ")"; } - //else - if ( type == "function" ) { + if ( type === "function" ) { stack.push( obj ); res = parser.call( this, obj, stack ); stack.pop(); return res; } - // else - return ( type == "string" ) ? parser : this.parsers.error; + return ( type === "string" ) ? parser : this.parsers.error; }, typeOf: function( obj ) { var type; @@ -1419,11 +1816,11 @@ QUnit.jsDump = (function() { type = "null"; } else if ( typeof obj === "undefined" ) { type = "undefined"; - } else if ( QUnit.is( "RegExp", obj) ) { + } else if ( QUnit.is( "regexp", obj) ) { type = "regexp"; - } else if ( QUnit.is( "Date", obj) ) { + } else if ( QUnit.is( "date", obj) ) { type = "date"; - } else if ( QUnit.is( "Function", obj) ) { + } else if ( QUnit.is( "function", obj) ) { type = "function"; } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { type = "window"; @@ -1438,6 +1835,8 @@ QUnit.jsDump = (function() { ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) ) { type = "array"; + } else if ( obj.constructor === Error.prototype.constructor ) { + type = "error"; } else { type = typeof obj; } @@ -1446,7 +1845,8 @@ QUnit.jsDump = (function() { separator: function() { return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; }, - indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + // extra can be a number, shortcut for increasing-calling-decreasing + indent: function( extra ) { if ( !this.multiline ) { return ""; } @@ -1475,13 +1875,16 @@ QUnit.jsDump = (function() { parsers: { window: "[Window]", document: "[Document]", - error: "[ERROR]", //when no parser is found, shouldn"t happen + error: function(error) { + return "Error(\"" + error.message + "\")"; + }, unknown: "[Unknown]", "null": "null", "undefined": "undefined", "function": function( fn ) { var ret = "function", - name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE + // functions never have name in IE + name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; if ( name ) { ret += " " + name; @@ -1497,13 +1900,9 @@ QUnit.jsDump = (function() { object: function( map, stack ) { var ret = [ ], keys, key, val, i; QUnit.jsDump.up(); - if ( Object.keys ) { - keys = Object.keys( map ); - } else { - keys = []; - for ( key in map ) { - keys.push( key ); - } + keys = []; + for ( key in map ) { + keys.push( key ); } keys.sort(); for ( i = 0; i < keys.length; i++ ) { @@ -1515,21 +1914,34 @@ QUnit.jsDump = (function() { return join( "{", ret, "}" ); }, node: function( node ) { - var a, val, + var len, i, val, open = QUnit.jsDump.HTML ? "<" : "<", close = QUnit.jsDump.HTML ? ">" : ">", tag = node.nodeName.toLowerCase(), - ret = open + tag; + ret = open + tag, + attrs = node.attributes; - for ( a in QUnit.jsDump.DOMAttrs ) { - val = node[ QUnit.jsDump.DOMAttrs[a] ]; - if ( val ) { - ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); + if ( attrs ) { + for ( i = 0, len = attrs.length; i < len; i++ ) { + val = attrs[i].nodeValue; + // IE6 includes all attributes in .attributes, even ones not explicitly set. + // Those have values like undefined, null, 0, false, "" or "inherit". + if ( val && val !== "inherit" ) { + ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); + } } } - return ret + close + open + "/" + tag + close; + ret += close; + + // Show content of TextNode or CDATASection + if ( node.nodeType === 3 || node.nodeType === 4 ) { + ret += node.nodeValue; + } + + return ret + open + "/" + tag + close; }, - functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function + // function calls it internally, it's the arguments part of the function + functionArgs: function( fn ) { var args, l = fn.length; @@ -1539,54 +1951,34 @@ QUnit.jsDump = (function() { args = new Array(l); while ( l-- ) { - args[l] = String.fromCharCode(97+l);//97 is 'a' + // 97 is 'a' + args[l] = String.fromCharCode(97+l); } return " " + args.join( ", " ) + " "; }, - key: quote, //object calls it internally, the key part of an item in a map - functionCode: "[code]", //function calls it internally, it's the content of the function - attribute: quote, //node calls it internally, it's an html attribute value + // object calls it internally, the key part of an item in a map + key: quote, + // function calls it internally, it's the content of the function + functionCode: "[code]", + // node calls it internally, it's an html attribute value + attribute: quote, string: quote, date: quote, - regexp: literal, //regex + regexp: literal, number: literal, "boolean": literal }, - DOMAttrs: { - //attributes to dump from nodes, name=>realName - id: "id", - name: "name", - "class": "className" - }, - HTML: false,//if true, entities are escaped ( <, >, \t, space and \n ) - indentChar: " ",//indentation unit - multiline: true //if true, items in a collection, are separated by a \n, else just a space. + // if true, entities are escaped ( <, >, \t, space and \n ) + HTML: false, + // indentation unit + indentChar: " ", + // if true, items in a collection, are separated by a \n, else just a space. + multiline: true }; return jsDump; }()); -// from Sizzle.js -function getText( elems ) { - var i, elem, - ret = ""; - - for ( i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += getText( elem.childNodes ); - } - } - - return ret; -} - // from jquery.js function inArray( elem, array ) { if ( array.indexOf ) { @@ -1617,13 +2009,14 @@ function inArray( elem, array ) { * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" */ QUnit.diff = (function() { + /*jshint eqeqeq:false, eqnull:true */ function diff( o, n ) { var i, ns = {}, os = {}; for ( i = 0; i < n.length; i++ ) { - if ( ns[ n[i] ] == null ) { + if ( !hasOwn.call( ns, n[i] ) ) { ns[ n[i] ] = { rows: [], o: null @@ -1633,7 +2026,7 @@ QUnit.diff = (function() { } for ( i = 0; i < o.length; i++ ) { - if ( os[ o[i] ] == null ) { + if ( !hasOwn.call( os, o[i] ) ) { os[ o[i] ] = { rows: [], n: null @@ -1646,7 +2039,7 @@ QUnit.diff = (function() { if ( !hasOwn.call( ns, i ) ) { continue; } - if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { + if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] @@ -1752,7 +2145,7 @@ QUnit.diff = (function() { // for CommonJS enviroments, export everything if ( typeof exports !== "undefined" ) { - extend(exports, QUnit); + extend( exports, QUnit ); } // get at whatever the global object is, like window in browsers diff --git a/test/qunit/runner.js b/test/qunit/runner.js new file mode 100644 index 00000000..92e7b939 --- /dev/null +++ b/test/qunit/runner.js @@ -0,0 +1,139 @@ +/* + * QtWebKit-powered headless test runner using PhantomJS + * + * PhantomJS binaries: http://phantomjs.org/download.html + * Requires PhantomJS 1.6+ (1.7+ recommended) + * + * Run with: + * phantomjs runner.js [url-of-your-qunit-testsuite] + * + * e.g. + * phantomjs runner.js http://localhost/qunit/test/index.html + */ + +/*global phantom:false, require:false, console:false, window:false, QUnit:false */ + +(function() { + 'use strict'; + + var url, page, timeout, + args = require('system').args; + + // arg[0]: scriptName, args[1...]: arguments + if (args.length < 2 || args.length > 3) { + console.error('Usage:\n phantomjs runner.js [url-of-your-qunit-testsuite] [timeout-in-seconds]'); + phantom.exit(1); + } + + url = args[1]; + page = require('webpage').create(); + if (args[2] !== undefined) { + timeout = parseInt(args[2], 10); + } + + // Route `console.log()` calls from within the Page context to the main Phantom context (i.e. current `this`) + page.onConsoleMessage = function(msg) { + console.log(msg); + }; + + page.onInitialized = function() { + page.evaluate(addLogging); + }; + + page.onCallback = function(message) { + var result, + failed; + + if (message) { + if (message.name === 'QUnit.done') { + result = message.data; + failed = !result || result.failed; + + phantom.exit(failed ? 1 : 0); + } + } + }; + + page.open(url, function(status) { + if (status !== 'success') { + console.error('Unable to access network: ' + status); + phantom.exit(1); + } else { + // Cannot do this verification with the 'DOMContentLoaded' handler because it + // will be too late to attach it if a page does not have any script tags. + var qunitMissing = page.evaluate(function() { return (typeof QUnit === 'undefined' || !QUnit); }); + if (qunitMissing) { + console.error('The `QUnit` object is not present on this page.'); + phantom.exit(1); + } + + // Set a timeout on the test running, otherwise tests with async problems will hang forever + if (typeof timeout === 'number') { + setTimeout(function() { + console.error('The specified timeout of ' + timeout + ' seconds has expired. Aborting...'); + phantom.exit(1); + }, timeout * 1000); + } + + // Do nothing... the callback mechanism will handle everything! + } + }); + + function addLogging() { + window.document.addEventListener('DOMContentLoaded', function() { + var currentTestAssertions = []; + + QUnit.log(function(details) { + var response; + + // Ignore passing assertions + if (details.result) { + return; + } + + response = details.message || ''; + + if (typeof details.expected !== 'undefined') { + if (response) { + response += ', '; + } + + response += 'expected: ' + details.expected + ', but was: ' + details.actual; + } + + if (details.source) { + response += "\n" + details.source; + } + + currentTestAssertions.push('Failed assertion: ' + response); + }); + + QUnit.testDone(function(result) { + var i, + len, + name = result.module + ': ' + result.name; + + if (result.failed) { + console.log('Test failed: ' + name); + + for (i = 0, len = currentTestAssertions.length; i < len; i++) { + console.log(' ' + currentTestAssertions[i]); + } + } + + currentTestAssertions.length = 0; + }); + + QUnit.done(function(result) { + console.log('Took ' + result.runtime + 'ms to run ' + result.total + ' tests. ' + result.passed + ' passed, ' + result.failed + ' failed.'); + + if (typeof window.callPhantom === 'function') { + window.callPhantom({ + 'name': 'QUnit.done', + 'data': result + }); + } + }); + }, false); + } +})(); diff --git a/test/sinon/1.1.1/sinon.js b/test/sinon/1.1.1/sinon.js deleted file mode 100644 index 2de58701..00000000 --- a/test/sinon/1.1.1/sinon.js +++ /dev/null @@ -1,2820 +0,0 @@ -/** - * Sinon.JS 1.1.1, 2011/05/17 - * - * @author Christian Johansen (christian@cjohansen.no) - * - * (The BSD License) - * - * Copyright (c) 2010-2011, Christian Johansen, christian@cjohansen.no - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * * Neither the name of Christian Johansen nor the names of his contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -"use strict"; -/*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/ -/*global module, require, __dirname, document*/ -/** - * Sinon core utilities. For internal use only. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -var sinon = (function () { - var div = typeof document != "undefined" && document.createElement("div"); - - function isNode(obj) { - var success = false; - - try { - obj.appendChild(div); - success = div.parentNode == obj; - } catch (e) { - return false; - } finally { - try { - obj.removeChild(div); - } catch (e) {} - } - - return success; - } - - function isElement(obj) { - return div && obj && obj.nodeType === 1 && isNode(obj); - } - - return { - wrapMethod: function wrapMethod(object, property, method) { - if (!object) { - throw new TypeError("Should wrap property of object"); - } - - if (typeof method != "function") { - throw new TypeError("Method wrapper should be function"); - } - - var wrappedMethod = object[property]; - var type = typeof wrappedMethod; - - if (type != "function") { - throw new TypeError("Attempted to wrap " + type + " property " + property + - " as function"); - } - - if (wrappedMethod.restore && wrappedMethod.restore.sinon) { - throw new TypeError("Attempted to wrap " + property + " which is already wrapped"); - } - - if (wrappedMethod.calledBefore) { - var verb = !!wrappedMethod.returns ? "stubbed" : "spied on"; - throw new TypeError("Attempted to wrap " + property + " which is already " + verb); - } - - object[property] = method; - method.displayName = property; - - method.restore = function () { - object[property] = wrappedMethod; - }; - - method.restore.sinon = true; - - return method; - }, - - extend: function extend(target) { - for (var i = 1, l = arguments.length; i < l; i += 1) { - for (var prop in arguments[i]) { - if (arguments[i].hasOwnProperty(prop)) { - target[prop] = arguments[i][prop]; - } - - // DONT ENUM bug, only care about toString - if (arguments[i].hasOwnProperty("toString") && - arguments[i].toString != target.toString) { - target.toString = arguments[i].toString; - } - } - } - - return target; - }, - - create: function create(proto) { - var F = function () {}; - F.prototype = proto; - return new F(); - }, - - deepEqual: function deepEqual(a, b) { - if (typeof a != "object" || typeof b != "object") { - return a === b; - } - - if (isElement(a) || isElement(b)) { - return a === b; - } - - if (a === b) { - return true; - } - - if (Object.prototype.toString.call(a) == "[object Array]") { - if (a.length !== b.length) { - return false; - } - - for (var i = 0, l = a.length; i < l; i += 1) { - if (!deepEqual(a[i], b[i])) { - return false; - } - } - - return true; - } - - var prop, aLength = 0, bLength = 0; - - for (prop in a) { - aLength += 1; - - if (!deepEqual(a[prop], b[prop])) { - return false; - } - } - - for (prop in b) { - bLength += 1; - } - - if (aLength != bLength) { - return false; - } - - return true; - }, - - keys: function keys(object) { - var objectKeys = []; - - for (var prop in object) { - if (object.hasOwnProperty(prop)) { - objectKeys.push(prop); - } - } - - return objectKeys.sort(); - }, - - functionName: function functionName(func) { - var name = func.displayName || func.name; - - // Use function decomposition as a last resort to get function - // name. Does not rely on function decomposition to work - if it - // doesn't debugging will be slightly less informative - // (i.e. toString will say 'spy' rather than 'myFunc'). - if (!name) { - var matches = func.toString().match(/function ([^\s\(]+)/); - name = matches && matches[1]; - } - - return name; - }, - - functionToString: function toString() { - if (this.getCall && this.callCount) { - var thisValue, prop, i = this.callCount; - - while (i--) { - thisValue = this.getCall(i).thisValue; - - for (prop in thisValue) { - if (thisValue[prop] === this) { - return prop; - } - } - } - } - - return this.displayName || "sinon fake"; - }, - - getConfig: function (custom) { - var config = {}; - custom = custom || {}; - var defaults = sinon.defaultConfig; - - for (var prop in defaults) { - if (defaults.hasOwnProperty(prop)) { - config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop]; - } - } - - return config; - }, - - format: function (val) { - return "" + val; - }, - - defaultConfig: { - injectIntoThis: true, - injectInto: null, - properties: ["spy", "stub", "mock", "clock", "server", "requests"], - useFakeTimers: true, - useFakeServer: true - } - }; -}()); - -if (typeof module == "object" && typeof require == "function") { - module.exports = sinon; - module.exports.spy = require("./sinon/spy"); - module.exports.stub = require("./sinon/stub"); - module.exports.mock = require("./sinon/mock"); - module.exports.collection = require("./sinon/collection"); - module.exports.assert = require("./sinon/assert"); - module.exports.sandbox = require("./sinon/sandbox"); - module.exports.test = require("./sinon/test"); - module.exports.testCase = require("./sinon/test_case"); - module.exports.assert = require("./sinon/assert"); -} - -/* @depend ../sinon.js */ -/*jslint eqeqeq: false, onevar: false, plusplus: false*/ -/*global module, require, sinon*/ -/** - * Spy functions - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -(function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; - var spyCall; - var callId = 0; - - if (!sinon && commonJSModule) { - sinon = require("sinon"); - } - - if (!sinon) { - return; - } - - function spy(object, property) { - if (!property && typeof object == "function") { - return spy.create(object); - } - - if (!object || !property) { - return spy.create(function () {}); - } - - var method = object[property]; - return sinon.wrapMethod(object, property, spy.create(method)); - } - - sinon.extend(spy, (function () { - var slice = Array.prototype.slice; - - function delegateToCalls(api, method, matchAny, actual) { - api[method] = function () { - if (!this.called) { - return false; - } - - var currentCall; - var matches = 0; - - for (var i = 0, l = this.callCount; i < l; i += 1) { - currentCall = this.getCall(i); - - if (currentCall[actual || method].apply(currentCall, arguments)) { - matches += 1; - - if (matchAny) { - return true; - } - } - } - - return matches === this.callCount; - }; - } - - function matchingFake(fakes, args, strict) { - if (!fakes) { - return; - } - - var alen = args.length; - - for (var i = 0, l = fakes.length; i < l; i++) { - if (fakes[i].matches(args, strict)) { - return fakes[i]; - } - } - } - - var uuid = 0; - - // Public API - var spyApi = { - reset: function () { - this.called = false; - this.calledOnce = false; - this.calledTwice = false; - this.calledThrice = false; - this.callCount = 0; - this.args = []; - this.returnValues = []; - this.thisValues = []; - this.exceptions = []; - this.callIds = []; - }, - - create: function create(func) { - var name; - - if (typeof func != "function") { - func = function () {}; - } else { - name = sinon.functionName(func); - } - - function proxy() { - return proxy.invoke(func, this, slice.call(arguments)); - } - - sinon.extend(proxy, spy); - delete proxy.create; - sinon.extend(proxy, func); - - proxy.reset(); - proxy.prototype = func.prototype; - proxy.displayName = name || "spy"; - proxy.toString = sinon.functionToString; - proxy._create = sinon.spy.create; - proxy.id = "spy#" + uuid++; - - return proxy; - }, - - invoke: function invoke(func, thisValue, args) { - var matching = matchingFake(this.fakes, args); - var exception, returnValue; - this.called = true; - this.callCount += 1; - this.calledOnce = this.callCount == 1; - this.calledTwice = this.callCount == 2; - this.calledThrice = this.callCount == 3; - this.thisValues.push(thisValue); - this.args.push(args); - this.callIds.push(callId++); - - try { - if (matching) { - returnValue = matching.invoke(func, thisValue, args); - } else { - returnValue = (this.func || func).apply(thisValue, args); - } - } catch (e) { - this.returnValues.push(undefined); - exception = e; - throw e; - } finally { - this.exceptions.push(exception); - } - - this.returnValues.push(returnValue); - - return returnValue; - }, - - getCall: function getCall(i) { - if (i < 0 || i >= this.callCount) { - return null; - } - - return spyCall.create(this, this.thisValues[i], this.args[i], - this.returnValues[i], this.exceptions[i], - this.callIds[i]); - }, - - calledBefore: function calledBefore(spyFn) { - if (!this.called) { - return false; - } - - if (!spyFn.called) { - return true; - } - - return this.callIds[0] < spyFn.callIds[0]; - }, - - calledAfter: function calledAfter(spyFn) { - if (!this.called || !spyFn.called) { - return false; - } - - return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1]; - }, - - withArgs: function () { - var args = slice.call(arguments); - - if (this.fakes) { - var match = matchingFake(this.fakes, args, true); - - if (match) { - return match; - } - } else { - this.fakes = []; - } - - var original = this; - var fake = this._create(); - fake.matchingAguments = args; - this.fakes.push(fake); - - fake.withArgs = function () { - return original.withArgs.apply(original, arguments); - }; - - return fake; - }, - - matches: function (args, strict) { - var margs = this.matchingAguments; - - if (margs.length <= args.length && - sinon.deepEqual(margs, args.slice(0, margs.length))) { - return !strict || margs.length == args.length; - } - } - }; - - delegateToCalls(spyApi, "calledOn", true); - delegateToCalls(spyApi, "alwaysCalledOn", false, "calledOn"); - delegateToCalls(spyApi, "calledWith", true); - delegateToCalls(spyApi, "alwaysCalledWith", false, "calledWith"); - delegateToCalls(spyApi, "calledWithExactly", true); - delegateToCalls(spyApi, "alwaysCalledWithExactly", false, "calledWithExactly"); - delegateToCalls(spyApi, "threw", true); - delegateToCalls(spyApi, "alwaysThrew", false, "threw"); - delegateToCalls(spyApi, "returned", true); - delegateToCalls(spyApi, "alwaysReturned", false, "returned"); - - return spyApi; - }())); - - spyCall = (function () { - return { - create: function create(spy, thisValue, args, returnValue, exception, id) { - var proxyCall = sinon.create(spyCall); - delete proxyCall.create; - proxyCall.proxy = spy; - proxyCall.thisValue = thisValue; - proxyCall.args = args; - proxyCall.returnValue = returnValue; - proxyCall.exception = exception; - proxyCall.callId = typeof id == "number" && id || callId++; - - return proxyCall; - }, - - calledOn: function calledOn(thisValue) { - return this.thisValue === thisValue; - }, - - calledWith: function calledWith() { - for (var i = 0, l = arguments.length; i < l; i += 1) { - if (!sinon.deepEqual(arguments[i], this.args[i])) { - return false; - } - } - - return true; - }, - - calledWithExactly: function calledWithExactly() { - return arguments.length == this.args.length && - this.calledWith.apply(this, arguments); - }, - - returned: function returned(value) { - return this.returnValue === value; - }, - - threw: function threw(error) { - if (typeof error == "undefined" || !this.exception) { - return !!this.exception; - } - - if (typeof error == "string") { - return this.exception.name == error; - } - - return this.exception === error; - }, - - calledBefore: function (other) { - return this.callId < other.callId; - }, - - calledAfter: function (other) { - return this.callId > other.callId; - }, - - toString: function () { - var callStr = this.proxy.toString() + "("; - var args = []; - - for (var i = 0, l = this.args.length; i < l; ++i) { - args.push(sinon.format(this.args[i])); - } - - callStr = callStr + args.join(", ") + ")"; - - if (typeof this.returnValue != "undefined") { - callStr += " => " + sinon.format(this.returnValue); - } - - if (this.exception) { - callStr += " !" + this.exception.name; - - if (this.exception.message) { - callStr += "(" + this.exception.message + ")"; - } - } - - return callStr; - } - }; - }()); - - if (commonJSModule) { - module.exports = spy; - } else { - sinon.spy = spy; - } - - sinon.spyCall = spyCall; -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend ../sinon.js - * @depend spy.js - */ -/*jslint eqeqeq: false, onevar: false*/ -/*global module, require, sinon*/ -/** - * Stub functions - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -(function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; - - if (!sinon && commonJSModule) { - sinon = require("sinon"); - } - - if (!sinon) { - return; - } - - function stub(object, property, func) { - if (!!func && typeof func != "function") { - throw new TypeError("Custom stub should be function"); - } - - var wrapper; - - if (func) { - wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func; - } else { - wrapper = stub.create(); - } - - if (!object && !property) { - return sinon.stub.create(); - } - - if (!property && !!object && typeof object == "object") { - for (var prop in object) { - if (object.hasOwnProperty(prop) && typeof object[prop] == "function") { - stub(object, prop); - } - } - - return object; - } - - return sinon.wrapMethod(object, property, wrapper); - } - - function getCallback(stub, args) { - if (stub.callArgAt < 0) { - for (var i = 0, l = args.length; i < l; ++i) { - if (!stub.callArgProp && typeof args[i] == "function") { - return args[i]; - } - - if (stub.callArgProp && args[i] && - typeof args[i][stub.callArgProp] == "function") { - return args[i][stub.callArgProp]; - } - } - - return null; - } - - return args[stub.callArgAt]; - } - - var join = Array.prototype.join; - - function getCallbackError(stub, func, args) { - if (stub.callArgAt < 0) { - var msg; - - if (stub.callArgProp) { - msg = sinon.functionName(stub) + - " expected to yield to '" + stub.callArgProp + - "', but no object with such a property was passed." - } else { - msg = sinon.functionName(stub) + - " expected to yield, but no callback was passed." - } - - if (args.length > 0) { - msg += " Received [" + join.call(args, ", ") + "]"; - } - - return msg; - } - - return "argument at index " + stub.callArgAt + " is not a function: " + func; - } - - function callCallback(stub, args) { - if (typeof stub.callArgAt == "number") { - var func = getCallback(stub, args); - - if (typeof func != "function") { - throw new TypeError(getCallbackError(stub, func, args)); - } - - func.apply(null, stub.callbackArguments); - } - } - - var uuid = 0; - - sinon.extend(stub, (function () { - var slice = Array.prototype.slice; - - function throwsException(error, message) { - if (typeof error == "string") { - this.exception = new Error(message || ""); - this.exception.name = error; - } else if (!error) { - this.exception = new Error("Error"); - } else { - this.exception = error; - } - - return this; - } - - return { - create: function create() { - var functionStub = function () { - if (functionStub.exception) { - throw functionStub.exception; - } - - callCallback(functionStub, arguments); - - return functionStub.returnValue; - }; - - functionStub.id = "stub#" + uuid++; - var orig = functionStub; - functionStub = sinon.spy.create(functionStub); - functionStub.func = orig; - - sinon.extend(functionStub, stub); - functionStub._create = sinon.stub.create; - functionStub.displayName = "stub"; - functionStub.toString = sinon.functionToString; - - return functionStub; - }, - - returns: function returns(value) { - this.returnValue = value; - - return this; - }, - - "throws": throwsException, - throwsException: throwsException, - - callsArg: function callsArg(pos) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); - } - - this.callArgAt = pos; - this.callbackArguments = []; - - return this; - }, - - callsArgWith: function callsArgWith(pos) { - if (typeof pos != "number") { - throw new TypeError("argument index is not number"); - } - - this.callArgAt = pos; - this.callbackArguments = slice.call(arguments, 1); - - return this; - }, - - yields: function () { - this.callArgAt = -1; - this.callbackArguments = slice.call(arguments, 0); - - return this; - }, - - yieldsTo: function (prop) { - this.callArgAt = -1; - this.callArgProp = prop; - this.callbackArguments = slice.call(arguments, 1); - - return this; - } - }; - }())); - - if (commonJSModule) { - module.exports = stub; - } else { - sinon.stub = stub; - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend ../sinon.js - * @depend stub.js - */ -/*jslint eqeqeq: false, onevar: false, nomen: false*/ -/*global module, require, sinon*/ -/** - * Mock functions. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -(function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; - - if (!sinon && commonJSModule) { - sinon = require("sinon"); - } - - if (!sinon) { - return; - } - - function mock(object) { - if (!object) { - return sinon.expectation.create("Anonymous mock"); - } - - return mock.create(object); - } - - sinon.mock = mock; - - sinon.extend(mock, (function () { - function each(collection, callback) { - if (!collection) { - return; - } - - for (var i = 0, l = collection.length; i < l; i += 1) { - callback(collection[i]); - } - } - - return { - create: function create(object) { - if (!object) { - throw new TypeError("object is null"); - } - - var mockObject = sinon.extend({}, mock); - mockObject.object = object; - delete mockObject.create; - - return mockObject; - }, - - expects: function expects(method) { - if (!method) { - throw new TypeError("method is falsy"); - } - - if (!this.expectations) { - this.expectations = {}; - this.proxies = []; - } - - if (!this.expectations[method]) { - this.expectations[method] = []; - var mockObject = this; - - sinon.wrapMethod(this.object, method, function () { - return mockObject.invokeMethod(method, this, arguments); - }); - - this.proxies.push(method); - } - - var expectation = sinon.expectation.create(method); - this.expectations[method].push(expectation); - - return expectation; - }, - - restore: function restore() { - var object = this.object; - - each(this.proxies, function (proxy) { - if (typeof object[proxy].restore == "function") { - object[proxy].restore(); - } - }); - }, - - verify: function verify() { - var expectations = this.expectations || {}; - var messages = [], met = []; - - each(this.proxies, function (proxy) { - each(expectations[proxy], function (expectation) { - if (!expectation.met()) { - messages.push(expectation.toString()); - } else { - met.push(expectation.toString()); - } - }); - }); - - this.restore(); - - if (messages.length > 0) { - err(messages.concat(met).join("\n")); - } - - return true; - }, - - invokeMethod: function invokeMethod(method, thisValue, args) { - var expectations = this.expectations && this.expectations[method]; - var length = expectations && expectations.length || 0; - - for (var i = 0; i < length; i += 1) { - if (!expectations[i].met() && - expectations[i].allowsCall(thisValue, args)) { - return expectations[i].apply(thisValue, args); - } - } - - var messages = []; - - for (i = 0; i < length; i += 1) { - messages.push(" " + expectations[i].toString()); - } - - messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({ - proxy: method, - args: args - })); - - err(messages.join("\n")); - } - }; - }())); - - function err(message) { - var exception = new Error(message); - exception.name = "ExpectationError"; - - throw exception; - } - - sinon.expectation = (function () { - var slice = Array.prototype.slice; - var _invoke = sinon.spy.invoke; - - function timesInWords(times) { - if (times == 0) { - return "never"; - } else if (times == 1) { - return "once"; - } else if (times == 2) { - return "twice"; - } else if (times == 3) { - return "thrice"; - } - - return times + " times"; - } - - function callCountInWords(callCount) { - if (callCount == 0) { - return "never called"; - } else { - return "called " + timesInWords(callCount); - } - } - - function expectedCallCountInWords(expectation) { - var min = expectation.minCalls; - var max = expectation.maxCalls; - - if (typeof min == "number" && typeof max == "number") { - var str = timesInWords(min); - - if (min != max) { - str = "at least " + str + " and at most " + timesInWords(max); - } - - return str; - } - - if (typeof min == "number") { - return "at least " + timesInWords(min); - } - - return "at most " + timesInWords(max); - } - - function receivedMinCalls(expectation) { - var hasMinLimit = typeof expectation.minCalls == "number"; - return !hasMinLimit || expectation.callCount >= expectation.minCalls; - } - - function receivedMaxCalls(expectation) { - if (typeof expectation.maxCalls != "number") { - return false; - } - - return expectation.callCount == expectation.maxCalls; - } - - return { - minCalls: 1, - maxCalls: 1, - - create: function create(methodName) { - var expectation = sinon.extend(sinon.stub.create(), sinon.expectation); - delete expectation.create; - expectation.method = methodName; - - return expectation; - }, - - invoke: function invoke(func, thisValue, args) { - this.verifyCallAllowed(thisValue, args); - - return _invoke.apply(this, arguments); - }, - - atLeast: function atLeast(num) { - if (typeof num != "number") { - throw new TypeError("'" + num + "' is not number"); - } - - if (!this.limitsSet) { - this.maxCalls = null; - this.limitsSet = true; - } - - this.minCalls = num; - - return this; - }, - - atMost: function atMost(num) { - if (typeof num != "number") { - throw new TypeError("'" + num + "' is not number"); - } - - if (!this.limitsSet) { - this.minCalls = null; - this.limitsSet = true; - } - - this.maxCalls = num; - - return this; - }, - - never: function never() { - return this.exactly(0); - }, - - once: function once() { - return this.exactly(1); - }, - - twice: function twice() { - return this.exactly(2); - }, - - thrice: function thrice() { - return this.exactly(3); - }, - - exactly: function exactly(num) { - if (typeof num != "number") { - throw new TypeError("'" + num + "' is not a number"); - } - - this.atLeast(num); - return this.atMost(num); - }, - - met: function met() { - return !this.failed && receivedMinCalls(this); - }, - - verifyCallAllowed: function verifyCallAllowed(thisValue, args) { - if (receivedMaxCalls(this)) { - this.failed = true; - err(this.method + " already called " + timesInWords(this.maxCalls)); - } - - if ("expectedThis" in this && this.expectedThis !== thisValue) { - err(this.method + " called with " + thisValue + " as thisValue, expected " + - this.expectedThis); - } - - if (!("expectedArguments" in this)) { - return; - } - - if (!args || args.length === 0) { - err(this.method + " received no arguments, expected " + - this.expectedArguments.join()); - } - - if (args.length < this.expectedArguments.length) { - err(this.method + " received too few arguments (" + args.join() + - "), expected " + this.expectedArguments.join()); - } - - if (this.expectsExactArgCount && - args.length != this.expectedArguments.length) { - err(this.method + " received too many arguments (" + args.join() + - "), expected " + this.expectedArguments.join()); - } - - for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { - if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { - err(this.method + " received wrong arguments (" + args.join() + - "), expected " + this.expectedArguments.join()); - } - } - }, - - allowsCall: function allowsCall(thisValue, args) { - if (this.met()) { - return false; - } - - if ("expectedThis" in this && this.expectedThis !== thisValue) { - return false; - } - - if (!("expectedArguments" in this)) { - return true; - } - - args = args || []; - - if (args.length < this.expectedArguments.length) { - return false; - } - - if (this.expectsExactArgCount && - args.length != this.expectedArguments.length) { - return false; - } - - for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { - if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { - return false; - } - } - - return true; - }, - - withArgs: function withArgs() { - this.expectedArguments = slice.call(arguments); - return this; - }, - - withExactArgs: function withExactArgs() { - this.withArgs.apply(this, arguments); - this.expectsExactArgCount = true; - return this; - }, - - on: function on(thisValue) { - this.expectedThis = thisValue; - return this; - }, - - toString: function () { - var args = (this.expectedArguments || []).slice(); - - if (!this.expectsExactArgCount) { - args.push("[...]"); - } - - var callStr = sinon.spyCall.toString.call({ - proxy: this.method, args: args - }); - - var message = callStr.replace(", [...", "[, ...") + " " + - expectedCallCountInWords(this); - - if (this.met()) { - return "Expectation met: " + message; - } - - return "Expected " + message + " (" + - callCountInWords(this.callCount) + ")"; - }, - - verify: function verify() { - if (!this.met()) { - err(this.toString()); - } - - return true; - } - }; - }()); - - if (commonJSModule) { - module.exports = mock; - } else { - sinon.mock = mock; - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend ../sinon.js - * @depend stub.js - * @depend mock.js - */ -/*jslint eqeqeq: false, onevar: false, forin: true*/ -/*global module, require, sinon*/ -/** - * Collections of stubs, spies and mocks. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -(function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; - - if (!sinon && commonJSModule) { - sinon = require("sinon"); - } - - if (!sinon) { - return; - } - - function getFakes(fakeCollection) { - if (!fakeCollection.fakes) { - fakeCollection.fakes = []; - } - - return fakeCollection.fakes; - } - - function each(fakeCollection, method) { - var fakes = getFakes(fakeCollection); - - for (var i = 0, l = fakes.length; i < l; i += 1) { - if (typeof fakes[i][method] == "function") { - fakes[i][method](); - } - } - } - - var collection = { - verify: function resolve() { - each(this, "verify"); - }, - - restore: function restore() { - each(this, "restore"); - }, - - verifyAndRestore: function verifyAndRestore() { - var exception; - - try { - this.verify(); - } catch (e) { - exception = e; - } - - this.restore(); - - if (exception) { - throw exception; - } - }, - - add: function add(fake) { - getFakes(this).push(fake); - - return fake; - }, - - spy: function spy() { - return this.add(sinon.spy.apply(sinon, arguments)); - }, - - stub: function stub(object, property, value) { - if (property) { - var original = object[property]; - - if (typeof original != "function") { - if (!object.hasOwnProperty(property)) { - throw new TypeError("Cannot stub non-existent own property " + property); - } - - object[property] = value; - - return this.add({ - restore: function () { - object[property] = original; - } - }); - } - } - - return this.add(sinon.stub.apply(sinon, arguments)); - }, - - mock: function mock() { - return this.add(sinon.mock.apply(sinon, arguments)); - }, - - inject: function inject(obj) { - var col = this; - - obj.spy = function () { - return col.spy.apply(col, arguments); - }; - - obj.stub = function () { - return col.stub.apply(col, arguments); - }; - - obj.mock = function () { - return col.mock.apply(col, arguments); - }; - - return obj; - } - }; - - if (commonJSModule) { - module.exports = collection; - } else { - sinon.collection = collection; - } -}(typeof sinon == "object" && sinon || null)); - -/*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/ -/*global module, require, window*/ -/** - * Fake timer API - * setTimeout - * setInterval - * clearTimeout - * clearInterval - * tick - * reset - * Date - * - * Inspired by jsUnitMockTimeOut from JsUnit - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -if (typeof sinon == "undefined") { - var sinon = {}; -} - -sinon.clock = (function () { - var id = 0; - - function addTimer(args, recurring) { - if (args.length === 0) { - throw new Error("Function requires at least 1 parameter"); - } - - var toId = id++; - var delay = args[1] || 0; - - if (!this.timeouts) { - this.timeouts = {}; - } - - this.timeouts[toId] = { - id: toId, - func: args[0], - callAt: this.now + delay - }; - - if (recurring === true) { - this.timeouts[toId].interval = delay; - } - - return toId; - } - - function parseTime(str) { - if (!str) { - return 0; - } - - var strings = str.split(":"); - var l = strings.length, i = l; - var ms = 0, parsed; - - if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) { - throw new Error("tick only understands numbers and 'h:m:s'"); - } - - while (i--) { - parsed = parseInt(strings[i], 10); - - if (parsed >= 60) { - throw new Error("Invalid time " + str); - } - - ms += parsed * Math.pow(60, (l - i - 1)); - } - - return ms * 1000; - } - - function createObject(object) { - var newObject; - - if (Object.create) { - newObject = Object.create(object); - } else { - var F = function () {}; - F.prototype = object; - newObject = new F(); - } - - newObject.Date.clock = newObject; - return newObject; - } - - return { - now: 0, - - create: function create(now) { - var clock = createObject(this); - - if (typeof now == "number") { - this.now = now; - } - - return clock; - }, - - setTimeout: function setTimeout(callback, timeout) { - return addTimer.call(this, arguments, false); - }, - - clearTimeout: function clearTimeout(timerId) { - if (!this.timeouts) { - this.timeouts = []; - } - - delete this.timeouts[timerId]; - }, - - setInterval: function setInterval(callback, timeout) { - return addTimer.call(this, arguments, true); - }, - - clearInterval: function clearInterval(timerId) { - this.clearTimeout(timerId); - }, - - tick: function tick(ms) { - ms = typeof ms == "number" ? ms : parseTime(ms); - var tickFrom = this.now, tickTo = this.now + ms, previous = this.now; - var timer = this.firstTimerInRange(tickFrom, tickTo); - - while (timer && tickFrom <= tickTo) { - if (this.timeouts[timer.id]) { - tickFrom = this.now = timer.callAt; - this.callTimer(timer); - } - - timer = this.firstTimerInRange(previous, tickTo); - previous = tickFrom; - } - - this.now = tickTo; - }, - - firstTimerInRange: function (from, to) { - var timer, smallest, originalTimer; - - for (var id in this.timeouts) { - if (this.timeouts.hasOwnProperty(id)) { - if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) { - continue; - } - - if (!smallest || this.timeouts[id].callAt < smallest) { - originalTimer = this.timeouts[id]; - smallest = this.timeouts[id].callAt; - - timer = { - func: this.timeouts[id].func, - callAt: this.timeouts[id].callAt, - interval: this.timeouts[id].interval, - id: this.timeouts[id].id - }; - } - } - } - - return timer || null; - }, - - callTimer: function (timer) { - try { - if (typeof timer.func == "function") { - timer.func.call(null); - } else { - eval(timer.func); - } - } catch (e) {} - - if (!this.timeouts[timer.id]) { - return; - } - - if (typeof timer.interval == "number") { - this.timeouts[timer.id].callAt += timer.interval; - } else { - delete this.timeouts[timer.id]; - } - }, - - reset: function reset() { - this.timeouts = {}; - }, - - Date: (function () { - var NativeDate = Date; - - function ClockDate(year, month, date, hour, minute, second, ms) { - // Defensive and verbose to avoid potential harm in passing - // explicit undefined when user does not pass argument - switch (arguments.length) { - case 0: - return new NativeDate(ClockDate.clock.now); - case 1: - return new NativeDate(year); - case 2: - return new NativeDate(year, month); - case 3: - return new NativeDate(year, month, date); - case 4: - return new NativeDate(year, month, date, hour); - case 5: - return new NativeDate(year, month, date, hour, minute); - case 6: - return new NativeDate(year, month, date, hour, minute, second); - default: - return new NativeDate(year, month, date, hour, minute, second, ms); - } - } - - if (NativeDate.now) { - ClockDate.now = function now() { - return ClockDate.clock.now; - }; - } - - if (NativeDate.toSource) { - ClockDate.toSource = function toSource() { - return NativeDate.toSource(); - }; - } - - ClockDate.toString = function toString() { - return NativeDate.toString(); - }; - - ClockDate.prototype = NativeDate.prototype; - ClockDate.parse = NativeDate.parse; - ClockDate.UTC = NativeDate.UTC; - - return ClockDate; - }()) - }; -}()); - -sinon.timers = { - setTimeout: setTimeout, - clearTimeout: clearTimeout, - setInterval: setInterval, - clearInterval: clearInterval, - Date: Date -}; - -sinon.useFakeTimers = (function (global) { - var methods = ["Date", "setTimeout", "setInterval", "clearTimeout", "clearInterval"]; - - function restore() { - var method; - - for (var i = 0, l = this.methods.length; i < l; i++) { - method = this.methods[i]; - global[method] = this["_" + method]; - } - } - - function stubGlobal(method, clock) { - clock["_" + method] = global[method]; - - global[method] = function () { - return clock[method].apply(clock, arguments); - }; - - for (var prop in clock[method]) { - if (clock[method].hasOwnProperty(prop)) { - global[method][prop] = clock[method][prop]; - } - } - - global[method].clock = clock; - } - - return function useFakeTimers(now) { - var clock = sinon.clock.create(now); - clock.restore = restore; - clock.methods = Array.prototype.slice.call(arguments, - typeof now == "number" ? 1 : 0); - - if (clock.methods.length === 0) { - clock.methods = methods; - } - - for (var i = 0, l = clock.methods.length; i < l; i++) { - stubGlobal(clock.methods[i], clock); - } - - return clock; - }; -}(typeof global != "undefined" ? global : this)); - -if (typeof module == "object" && typeof require == "function") { - module.exports = sinon; -} - -/*jslint eqeqeq: false, onevar: false*/ -/*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ -/** - * Fake XMLHttpRequest object - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -if (typeof sinon == "undefined") { - this.sinon = {}; -} - -sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; - -sinon.FakeXMLHttpRequest = (function () { - /*jsl:ignore*/ - var unsafeHeaders = { - "Accept-Charset": true, - "Accept-Encoding": true, - "Connection": true, - "Content-Length": true, - "Cookie": true, - "Cookie2": true, - "Content-Transfer-Encoding": true, - "Date": true, - "Expect": true, - "Host": true, - "Keep-Alive": true, - "Referer": true, - "TE": true, - "Trailer": true, - "Transfer-Encoding": true, - "Upgrade": true, - "User-Agent": true, - "Via": true - }; - /*jsl:end*/ - - function FakeXMLHttpRequest() { - this.readyState = FakeXMLHttpRequest.UNSENT; - this.requestHeaders = {}; - this.requestBody = null; - this.status = 0; - this.statusText = ""; - - if (typeof FakeXMLHttpRequest.onCreate == "function") { - FakeXMLHttpRequest.onCreate(this); - } - } - - function verifyState(xhr) { - if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { - throw new Error("INVALID_STATE_ERR"); - } - - if (xhr.sendFlag) { - throw new Error("INVALID_STATE_ERR"); - } - } - - sinon.extend(FakeXMLHttpRequest.prototype, { - async: true, - - open: function open(method, url, async, username, password) { - this.method = method; - this.url = url; - this.async = typeof async == "boolean" ? async : true; - this.username = username; - this.password = password; - this.responseText = null; - this.responseXML = null; - this.requestHeaders = {}; - this.sendFlag = false; - this.readyStateChange(FakeXMLHttpRequest.OPENED); - }, - - readyStateChange: function readyStateChange(state) { - this.readyState = state; - - if (typeof this.onreadystatechange == "function") { - this.onreadystatechange(); - } - }, - - setRequestHeader: function setRequestHeader(header, value) { - verifyState(this); - - if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { - throw new Error("Refused to set unsafe header \"" + header + "\""); - } - - if (this.requestHeaders[header]) { - this.requestHeaders[header] += "," + value; - } else { - this.requestHeaders[header] = value; - } - }, - - // Helps testing - setResponseHeaders: function setResponseHeaders(headers) { - this.responseHeaders = {}; - - for (var header in headers) { - if (headers.hasOwnProperty(header)) { - this.responseHeaders[header] = headers[header]; - } - } - - if (this.async) { - this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); - } - }, - - // Currently treats ALL data as a DOMString (i.e. no Document) - send: function send(data) { - verifyState(this); - - if (!/^(get|head)$/i.test(this.method)) { - if (this.requestHeaders["Content-Type"]) { - var value = this.requestHeaders["Content-Type"].split(";"); - this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8"; - } else { - this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; - } - - this.requestBody = data; - } - - this.errorFlag = false; - this.sendFlag = this.async; - this.readyStateChange(FakeXMLHttpRequest.OPENED); - - if (typeof this.onSend == "function") { - this.onSend(this); - } - }, - - abort: function abort() { - this.aborted = true; - this.responseText = null; - this.errorFlag = true; - this.requestHeaders = {}; - - if (this.readyState > sinon.FakeXMLHttpRequest.OPENED) { - this.readyStateChange(sinon.FakeXMLHttpRequest.DONE); - this.sendFlag = false; - } - - this.readyState = sinon.FakeXMLHttpRequest.UNSENT; - }, - - getResponseHeader: function getResponseHeader(header) { - if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { - return null; - } - - if (/^Set-Cookie2?$/i.test(header)) { - return null; - } - - header = header.toLowerCase(); - - for (var h in this.responseHeaders) { - if (h.toLowerCase() == header) { - return this.responseHeaders[h]; - } - } - - return null; - }, - - getAllResponseHeaders: function getAllResponseHeaders() { - if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { - return ""; - } - - var headers = ""; - - for (var header in this.responseHeaders) { - if (this.responseHeaders.hasOwnProperty(header) && - !/^Set-Cookie2?$/i.test(header)) { - headers += header + ": " + this.responseHeaders[header] + "\r\n"; - } - } - - return headers; - }, - - setResponseBody: function setResponseBody(body) { - if (this.readyState == FakeXMLHttpRequest.DONE) { - throw new Error("Request done"); - } - - if (this.async && this.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { - throw new Error("No headers received"); - } - - var chunkSize = this.chunkSize || 10; - var index = 0; - this.responseText = ""; - - do { - if (this.async) { - this.readyStateChange(FakeXMLHttpRequest.LOADING); - } - - this.responseText += body.substring(index, index + chunkSize); - index += chunkSize; - } while (index < body.length); - - var type = this.getResponseHeader("Content-Type"); - - if (this.responseText && - (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { - try { - this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText); - } catch (e) {} - } - - if (this.async) { - this.readyStateChange(FakeXMLHttpRequest.DONE); - } else { - this.readyState = FakeXMLHttpRequest.DONE; - } - }, - - respond: function respond(status, headers, body) { - this.setResponseHeaders(headers || {}); - this.status = typeof status == "number" ? status : 200; - this.statusText = FakeXMLHttpRequest.statusCodes[this.status]; - this.setResponseBody(body || ""); - } - }); - - sinon.extend(FakeXMLHttpRequest, { - UNSENT: 0, - OPENED: 1, - HEADERS_RECEIVED: 2, - LOADING: 3, - DONE: 4 - }); - - // Borrowed from JSpec - FakeXMLHttpRequest.parseXML = function parseXML(text) { - var xmlDoc; - - if (typeof DOMParser != "undefined") { - var parser = new DOMParser(); - xmlDoc = parser.parseFromString(text, "text/xml"); - } else { - xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); - xmlDoc.async = "false"; - xmlDoc.loadXML(text); - } - - return xmlDoc; - }; - - FakeXMLHttpRequest.statusCodes = { - 100: "Continue", - 101: "Switching Protocols", - 200: "OK", - 201: "Created", - 202: "Accepted", - 203: "Non-Authoritative Information", - 204: "No Content", - 205: "Reset Content", - 206: "Partial Content", - 300: "Multiple Choice", - 301: "Moved Permanently", - 302: "Found", - 303: "See Other", - 304: "Not Modified", - 305: "Use Proxy", - 307: "Temporary Redirect", - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Request Entity Too Large", - 414: "Request-URI Too Long", - 415: "Unsupported Media Type", - 416: "Requested Range Not Satisfiable", - 417: "Expectation Failed", - 422: "Unprocessable Entity", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported" - }; - - return FakeXMLHttpRequest; -}()); - -(function (global) { - var GlobalXMLHttpRequest = global.XMLHttpRequest; - var GlobalActiveXObject = global.ActiveXObject; - var supportsActiveX = typeof ActiveXObject != "undefined"; - var supportsXHR = typeof XMLHttpRequest != "undefined"; - - sinon.useFakeXMLHttpRequest = function () { - sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) { - if (supportsXHR) { - global.XMLHttpRequest = GlobalXMLHttpRequest; - } - - if (supportsActiveX) { - global.ActiveXObject = GlobalActiveXObject; - } - - delete sinon.FakeXMLHttpRequest.restore; - - if (keepOnCreate !== true) { - delete sinon.FakeXMLHttpRequest.onCreate; - } - }; - - if (supportsXHR) { - global.XMLHttpRequest = sinon.FakeXMLHttpRequest; - } - - if (supportsActiveX) { - global.ActiveXObject = function ActiveXObject(objId) { - if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/.test(objId)) { - return new sinon.FakeXMLHttpRequest(); - } - - return new GlobalActiveXObject(objId); - }; - } - - return sinon.FakeXMLHttpRequest; - }; -}(this)); - -if (typeof module == "object" && typeof require == "function") { - module.exports = sinon; -} - -/** - * @depend fake_xml_http_request.js - */ -/*jslint eqeqeq: false, onevar: false, regexp: false, plusplus: false*/ -/*global module, require, window*/ -/** - * The Sinon "server" mimics a web server that receives requests from - * sinon.FakeXMLHttpRequest and provides an API to respond to those requests, - * both synchronously and asynchronously. To respond synchronuously, canned - * answers have to be provided upfront. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -if (typeof sinon == "undefined") { - var sinon = {}; -} - -sinon.fakeServer = (function () { - function F() {} - - function create(proto) { - F.prototype = proto; - return new F(); - } - - function responseArray(handler) { - var response = handler; - - if (Object.prototype.toString.call(handler) != "[object Array]") { - response = [200, {}, handler]; - } - - if (typeof response[2] != "string") { - throw new TypeError("Fake server response body should be string, but was " + - typeof response[2]); - } - - return response; - } - - var wloc = window.location; - var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host); - - function matchOne(response, reqMethod, reqUrl) { - var rmeth = response.method; - var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase(); - var url = response.url; - var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl)); - - return matchMethod && matchUrl; - } - - function match(response, request) { - var requestMethod = this.getHTTPMethod(request); - var requestUrl = request.url; - - if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) { - requestUrl = requestUrl.replace(rCurrLoc, ""); - } - - if (matchOne(response, this.getHTTPMethod(request), requestUrl)) { - if (typeof response.response == "function") { - var args = [request].concat(requestUrl.match(response.url).slice(1)); - return response.response.apply(response, args); - } - - return true; - } - - return false; - } - - return { - create: function () { - var server = create(this); - this.xhr = sinon.useFakeXMLHttpRequest(); - server.requests = []; - - this.xhr.onCreate = function (xhrObj) { - server.addRequest(xhrObj); - }; - - return server; - }, - - addRequest: function addRequest(xhrObj) { - var server = this; - this.requests.push(xhrObj); - - xhrObj.onSend = function () { - server.handleRequest(this); - }; - - if (this.autoRespond && !this.responding) { - setTimeout(function () { - server.responding = false; - server.respond(); - }, this.autoRespondAfter || 10); - - this.responding = true; - } - }, - - getHTTPMethod: function getHTTPMethod(request) { - if (this.fakeHTTPMethods && /post/i.test(request.method)) { - var matches = request.requestBody.match(/_method=([^\b;]+)/); - return !!matches ? matches[1] : request.method; - } - - return request.method; - }, - - handleRequest: function handleRequest(xhr) { - if (xhr.async) { - if (!this.queue) { - this.queue = []; - } - - this.queue.push(xhr); - } else { - this.processRequest(xhr); - } - }, - - respondWith: function respondWith(method, url, body) { - if (arguments.length == 1) { - this.response = responseArray(method); - } else { - if (!this.responses) { - this.responses = []; - } - - if (arguments.length == 2) { - body = url; - url = method; - method = null; - } - - this.responses.push({ - method: method, - url: url, - response: typeof body == "function" ? body : responseArray(body) - }); - } - }, - - respond: function respond() { - var queue = this.queue || []; - var request; - - while(request = queue.shift()) { - this.processRequest(request); - } - }, - - processRequest: function processRequest(request) { - try { - if (request.aborted) { - return; - } - - var response = this.response || [404, {}, ""]; - - if (this.responses) { - for (var i = 0, l = this.responses.length; i < l; i++) { - if (match.call(this, this.responses[i], request)) { - response = this.responses[i].response; - break; - } - } - } - - if (request.readyState != 4) { - request.respond(response[0], response[1], response[2]); - } - } catch (e) {} - }, - - restore: function restore() { - return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments); - } - }; -}()); - -if (typeof module == "object" && typeof require == "function") { - module.exports = sinon; -} - -/** - * @depend fake_server.js - * @depend fake_timers.js - */ -/*jslint browser: true, eqeqeq: false, onevar: false*/ -/*global sinon*/ -/** - * Add-on for sinon.fakeServer that automatically handles a fake timer along with - * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery - * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead, - * it polls the object for completion with setInterval. Dispite the direct - * motivation, there is nothing jQuery-specific in this file, so it can be used - * in any environment where the ajax implementation depends on setInterval or - * setTimeout. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -(function () { - function Server() {} - Server.prototype = sinon.fakeServer; - - sinon.fakeServerWithClock = new Server(); - - sinon.fakeServerWithClock.addRequest = function addRequest(xhr) { - if (xhr.async) { - if (typeof setTimeout.clock == "object") { - this.clock = setTimeout.clock; - } else { - this.clock = sinon.useFakeTimers(); - this.resetClock = true; - } - - if (!this.longestTimeout) { - var clockSetTimeout = this.clock.setTimeout; - var clockSetInterval = this.clock.setInterval; - var server = this; - - this.clock.setTimeout = function (fn, timeout) { - server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); - - return clockSetTimeout.apply(this, arguments); - }; - - this.clock.setInterval = function (fn, timeout) { - server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); - - return clockSetInterval.apply(this, arguments); - }; - } - } - - return sinon.fakeServer.addRequest.call(this, xhr); - }; - - sinon.fakeServerWithClock.respond = function respond() { - var returnVal = sinon.fakeServer.respond.apply(this, arguments); - - if (this.clock) { - this.clock.tick(this.longestTimeout || 0); - this.longestTimeout = 0; - - if (this.resetClock) { - this.clock.restore(); - this.resetClock = false; - } - } - - return returnVal; - }; - - sinon.fakeServerWithClock.restore = function restore() { - if (this.clock) { - this.clock.restore(); - } - - return sinon.fakeServer.restore.apply(this, arguments); - }; -}()); - -/** - * @depend ../sinon.js - * @depend collection.js - * @depend util/fake_timers.js - * @depend util/fake_server_with_clock.js - */ -/*jslint eqeqeq: false, onevar: false, plusplus: false*/ -/*global require, module*/ -/** - * Manages fake collections as well as fake utilities such as Sinon's - * timers and fake XHR implementation in one convenient object. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -if (typeof module == "object" && typeof require == "function") { - var sinon = require("sinon"); - sinon.extend(sinon, require("./util/fake_timers")); -} - -(function () { - function exposeValue(sandbox, config, key, value) { - if (!value) { - return; - } - - if (config.injectInto) { - config.injectInto[key] = value; - } else { - sandbox.args.push(value); - } - } - - function prepareSandboxFromConfig(config) { - var sandbox = sinon.create(sinon.sandbox); - - if (config.useFakeServer) { - if (typeof config.useFakeServer == "object") { - sandbox.serverPrototype = config.useFakeServer; - } - - sandbox.useFakeServer(); - } - - if (config.useFakeTimers) { - if (typeof config.useFakeTimers == "object") { - sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers); - } else { - sandbox.useFakeTimers(); - } - } - - return sandbox; - } - - sinon.sandbox = sinon.extend(sinon.create(sinon.collection), { - useFakeTimers: function useFakeTimers() { - this.clock = sinon.useFakeTimers.apply(sinon, arguments); - - return this.add(this.clock); - }, - - serverPrototype: sinon.fakeServer, - - useFakeServer: function useFakeServer() { - var proto = this.serverPrototype || sinon.fakeServer; - - if (!proto || !proto.create) { - return null; - } - - this.server = proto.create(); - return this.add(this.server); - }, - - inject: function (obj) { - sinon.collection.inject.call(this, obj); - - if (this.clock) { - obj.clock = this.clock; - } - - if (this.server) { - obj.server = this.server; - obj.requests = this.server.requests; - } - - return obj; - }, - - create: function (config) { - if (!config) { - return sinon.create(sinon.sandbox); - } - - var sandbox = prepareSandboxFromConfig(config); - sandbox.args = sandbox.args || []; - var prop, value, exposed = sandbox.inject({}); - - if (config.properties) { - for (var i = 0, l = config.properties.length; i < l; i++) { - prop = config.properties[i]; - value = exposed[prop] || prop == "sandbox" && sandbox; - exposeValue(sandbox, config, prop, value); - } - } else { - exposeValue(sandbox, config, "sandbox", value); - } - - return sandbox; - } - }); - - sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer; - - if (typeof module != "undefined") { - module.exports = sinon.sandbox; - } -}()); - -/** - * @depend ../sinon.js - * @depend stub.js - * @depend mock.js - * @depend sandbox.js - */ -/*jslint eqeqeq: false, onevar: false, forin: true, plusplus: false*/ -/*global module, require, sinon*/ -/** - * Test function, sandboxes fakes - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -(function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; - - if (!sinon && commonJSModule) { - sinon = require("sinon"); - } - - if (!sinon) { - return; - } - - function test(callback) { - var type = typeof callback; - - if (type != "function") { - throw new TypeError("sinon.test needs to wrap a test function, got " + type); - } - - return function () { - var config = sinon.getConfig(sinon.config); - config.injectInto = config.injectIntoThis && this || config.injectInto; - var sandbox = sinon.sandbox.create(config); - var exception, result; - var args = Array.prototype.slice.call(arguments).concat(sandbox.args); - - try { - result = callback.apply(this, args); - } catch (e) { - exception = e; - } - - sandbox.verifyAndRestore(); - - if (exception) { - throw exception; - } - - return result; - }; - } - - test.config = { - injectIntoThis: true, - injectInto: null, - properties: ["spy", "stub", "mock", "clock", "server", "requests"], - useFakeTimers: true, - useFakeServer: true - }; - - if (commonJSModule) { - module.exports = test; - } else { - sinon.test = test; - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend ../sinon.js - * @depend test.js - */ -/*jslint eqeqeq: false, onevar: false, eqeqeq: false*/ -/*global module, require, sinon*/ -/** - * Test case, sandboxes all test functions - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -(function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; - - if (!sinon && commonJSModule) { - sinon = require("sinon"); - } - - if (!sinon || !Object.prototype.hasOwnProperty) { - return; - } - - function createTest(property, setUp, tearDown) { - return function () { - if (setUp) { - setUp.apply(this, arguments); - } - - var exception, result; - - try { - result = property.apply(this, arguments); - } catch (e) { - exception = e; - } - - if (tearDown) { - tearDown.apply(this, arguments); - } - - if (exception) { - throw exception; - } - - return result; - }; - } - - function testCase(tests, prefix) { - /*jsl:ignore*/ - if (!tests || typeof tests != "object") { - throw new TypeError("sinon.testCase needs an object with test functions"); - } - /*jsl:end*/ - - prefix = prefix || "test"; - var rPrefix = new RegExp("^" + prefix); - var methods = {}, testName, property, method; - var setUp = tests.setUp; - var tearDown = tests.tearDown; - - for (testName in tests) { - if (tests.hasOwnProperty(testName)) { - property = tests[testName]; - - if (/^(setUp|tearDown)$/.test(testName)) { - continue; - } - - if (typeof property == "function" && rPrefix.test(testName)) { - method = property; - - if (setUp || tearDown) { - method = createTest(property, setUp, tearDown); - } - - methods[testName] = sinon.test(method); - } else { - methods[testName] = tests[testName]; - } - } - } - - return methods; - } - - if (commonJSModule) { - module.exports = testCase; - } else { - sinon.testCase = testCase; - } -}(typeof sinon == "object" && sinon || null)); - -/** - * @depend ../sinon.js - * @depend stub.js - */ -/*jslint eqeqeq: false, onevar: false, nomen: false, plusplus: false*/ -/*global module, require, sinon*/ -/** - * Assertions matching the test spy retrieval interface. - * - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2010-2011 Christian Johansen - */ - -(function (sinon) { - var commonJSModule = typeof module == "object" && typeof require == "function"; - var slice = Array.prototype.slice; - var assert; - - if (!sinon && commonJSModule) { - sinon = require("sinon"); - } - - if (!sinon) { - return; - } - - function times(count) { - return count == 1 && "once" || - count == 2 && "twice" || - count == 3 && "thrice" || - (count || 0) + " times"; - } - - function verifyIsStub(method) { - if (!method) { - assert.fail("fake is not a spy"); - } - - if (typeof method != "function") { - assert.fail(method + " is not a function"); - } - - if (typeof method.getCall != "function") { - assert.fail(method + " is not stubbed"); - } - } - - function failAssertion(object, msg) { - var failMethod = object.fail || assert.fail; - failMethod.call(object, msg); - } - - function mirrorAssertion(method, message) { - assert[method] = function (fake) { - verifyIsStub(fake); - - var failed = typeof fake[method] == "function" ? - !fake[method].apply(fake, slice.call(arguments, 1)) : !fake[method]; - - if (failed) { - var msg = message.replace("%c", times(fake.callCount)); - msg = msg.replace("%n", fake + ""); - - msg = msg.replace("%C", function (m) { - return formatSpyCalls(fake); - }); - - msg = msg.replace("%t", function (m) { - return formatThisValues(fake); - }); - - msg = msg.replace("%*", [].slice.call(arguments, 1).join(", ")); - - for (var i = 0, l = arguments.length; i < l; i++) { - msg = msg.replace("%" + i, arguments[i]); - } - - failAssertion(this, msg); - } else { - assert.pass(method); - } - }; - } - - function formatSpyCalls(spy) { - var calls = []; - - for (var i = 0, l = spy.callCount; i < l; ++i) { - calls.push(" " + spy.getCall(i).toString()); - } - - return calls.length > 0 ? "\n" + calls.join("\n") : ""; - } - - function formatThisValues(spy) { - var objects = []; - - for (var i = 0, l = spy.callCount; i < l; ++i) { - objects.push(sinon.format(spy.thisValues[i])); - } - - return objects.join(", "); - } - - assert = { - failException: "AssertError", - - fail: function fail(message) { - var error = new Error(message); - error.name = this.failException || assert.failException; - - throw error; - }, - - pass: function pass(assertion) {}, - - called: function assertCalled(method) { - verifyIsStub(method); - - if (!method.called) { - failAssertion(this, "expected " + method + - " to have been called at least once but was never called"); - } else { - assert.pass("called"); - } - }, - - notCalled: function assertNotCalled(method) { - verifyIsStub(method); - - if (method.called) { - failAssertion( - this, "expected " + method + " to not have been called but was " + - "called " + times(method.callCount) + formatSpyCalls(method)); - } else { - assert.pass("notCalled"); - } - }, - - callOrder: function assertCallOrder() { - verifyIsStub(arguments[0]); - var expected = []; - var actual = []; - var failed = false; - expected.push(arguments[0]); - - for (var i = 1, l = arguments.length; i < l; i++) { - verifyIsStub(arguments[i]); - expected.push(arguments[i]); - - if (!arguments[i - 1].calledBefore(arguments[i])) { - failed = true; - } - } - - if (failed) { - actual = [].concat(expected).sort(function (a, b) { - var aId = a.getCall(0).callId; - var bId = b.getCall(0).callId; - - // uuid, won't ever be equal - return aId < bId ? -1 : 1; - }); - - var expectedStr, actualStr; - - try { - expectedStr = expected.join(", "); - actualStr = actual.join(", "); - } catch (e) {} - - failAssertion(this, "expected " + (expectedStr || "") + " to be " + - "called in order but were called as " + actualStr); - } else { - assert.pass("callOrder"); - } - }, - - callCount: function assertCallCount(method, count) { - verifyIsStub(method); - - if (method.callCount != count) { - failAssertion(this, "expected " + method + " to be called " + - times(count) + " but was called " + - times(method.callCount) + formatSpyCalls(method)); - } else { - assert.pass("callCount"); - } - }, - - expose: function expose(target, options) { - if (!target) { - throw new TypeError("target is null or undefined"); - } - - options = options || {}; - var prefix = typeof options.prefix == "undefined" && "assert" || options.prefix; - - var name = function (prop) { - if (!prefix) { - return prop; - } - - return prefix + prop.substring(0, 1).toUpperCase() + prop.substring(1); - }; - - for (var assertion in this) { - if (!/^(fail|expose)/.test(assertion)) { - target[name(assertion)] = this[assertion]; - } - } - - if (typeof options.includeFail == "undefined" || !!options.includeFail) { - target.fail = this.fail; - target.failException = this.failException; - } - - return target; - } - }; - - mirrorAssertion("calledOnce", "expected %n to be called once but was called %c%C"); - mirrorAssertion("calledTwice", "expected %n to be called twice but was called %c%C"); - mirrorAssertion("calledThrice", "expected %n to be called thrice but was called %c%C"); - mirrorAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t"); - mirrorAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t"); - mirrorAssertion("calledWith", "expected %n to be called with arguments %*%C"); - mirrorAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C"); - mirrorAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C"); - mirrorAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C"); - mirrorAssertion("threw", "%n did not throw exception%C"); - mirrorAssertion("alwaysThrew", "%n did not always throw exception%C"); - - if (commonJSModule) { - module.exports = assert; - } else { - sinon.assert = assert; - } -}(typeof sinon == "object" && sinon || null)); diff --git a/test/sinon/1.7.1/sinon.js b/test/sinon/1.7.1/sinon.js new file mode 100644 index 00000000..589c0f57 --- /dev/null +++ b/test/sinon/1.7.1/sinon.js @@ -0,0 +1,4299 @@ +/** + * Sinon.JS 1.7.1, 2013/05/07 + * + * @author Christian Johansen (christian@cjohansen.no) + * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS + * + * (The BSD License) + * + * Copyright (c) 2010-2013, Christian Johansen, christian@cjohansen.no + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Christian Johansen nor the names of his contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +this.sinon = (function () { +var buster = (function (setTimeout, B) { + var isNode = typeof require == "function" && typeof module == "object"; + var div = typeof document != "undefined" && document.createElement("div"); + var F = function () {}; + + var buster = { + bind: function bind(obj, methOrProp) { + var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp; + var args = Array.prototype.slice.call(arguments, 2); + return function () { + var allArgs = args.concat(Array.prototype.slice.call(arguments)); + return method.apply(obj, allArgs); + }; + }, + + partial: function partial(fn) { + var args = [].slice.call(arguments, 1); + return function () { + return fn.apply(this, args.concat([].slice.call(arguments))); + }; + }, + + create: function create(object) { + F.prototype = object; + return new F(); + }, + + extend: function extend(target) { + if (!target) { return; } + for (var i = 1, l = arguments.length, prop; i < l; ++i) { + for (prop in arguments[i]) { + target[prop] = arguments[i][prop]; + } + } + return target; + }, + + nextTick: function nextTick(callback) { + if (typeof process != "undefined" && process.nextTick) { + return process.nextTick(callback); + } + setTimeout(callback, 0); + }, + + functionName: function functionName(func) { + if (!func) return ""; + if (func.displayName) return func.displayName; + if (func.name) return func.name; + var matches = func.toString().match(/function\s+([^\(]+)/m); + return matches && matches[1] || ""; + }, + + isNode: function isNode(obj) { + if (!div) return false; + try { + obj.appendChild(div); + obj.removeChild(div); + } catch (e) { + return false; + } + return true; + }, + + isElement: function isElement(obj) { + return obj && obj.nodeType === 1 && buster.isNode(obj); + }, + + isArray: function isArray(arr) { + return Object.prototype.toString.call(arr) == "[object Array]"; + }, + + flatten: function flatten(arr) { + var result = [], arr = arr || []; + for (var i = 0, l = arr.length; i < l; ++i) { + result = result.concat(buster.isArray(arr[i]) ? flatten(arr[i]) : arr[i]); + } + return result; + }, + + each: function each(arr, callback) { + for (var i = 0, l = arr.length; i < l; ++i) { + callback(arr[i]); + } + }, + + map: function map(arr, callback) { + var results = []; + for (var i = 0, l = arr.length; i < l; ++i) { + results.push(callback(arr[i])); + } + return results; + }, + + parallel: function parallel(fns, callback) { + function cb(err, res) { + if (typeof callback == "function") { + callback(err, res); + callback = null; + } + } + if (fns.length == 0) { return cb(null, []); } + var remaining = fns.length, results = []; + function makeDone(num) { + return function done(err, result) { + if (err) { return cb(err); } + results[num] = result; + if (--remaining == 0) { cb(null, results); } + }; + } + for (var i = 0, l = fns.length; i < l; ++i) { + fns[i](makeDone(i)); + } + }, + + series: function series(fns, callback) { + function cb(err, res) { + if (typeof callback == "function") { + callback(err, res); + } + } + var remaining = fns.slice(); + var results = []; + function callNext() { + if (remaining.length == 0) return cb(null, results); + var promise = remaining.shift()(next); + if (promise && typeof promise.then == "function") { + promise.then(buster.partial(next, null), next); + } + } + function next(err, result) { + if (err) return cb(err); + results.push(result); + callNext(); + } + callNext(); + }, + + countdown: function countdown(num, done) { + return function () { + if (--num == 0) done(); + }; + } + }; + + if (typeof process === "object" && + typeof require === "function" && typeof module === "object") { + var crypto = require("crypto"); + var path = require("path"); + + buster.tmpFile = function (fileName) { + var hashed = crypto.createHash("sha1"); + hashed.update(fileName); + var tmpfileName = hashed.digest("hex"); + + if (process.platform == "win32") { + return path.join(process.env["TEMP"], tmpfileName); + } else { + return path.join("/tmp", tmpfileName); + } + }; + } + + if (Array.prototype.some) { + buster.some = function (arr, fn, thisp) { + return arr.some(fn, thisp); + }; + } else { + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some + buster.some = function (arr, fun, thisp) { + if (arr == null) { throw new TypeError(); } + arr = Object(arr); + var len = arr.length >>> 0; + if (typeof fun !== "function") { throw new TypeError(); } + + for (var i = 0; i < len; i++) { + if (arr.hasOwnProperty(i) && fun.call(thisp, arr[i], i, arr)) { + return true; + } + } + + return false; + }; + } + + if (Array.prototype.filter) { + buster.filter = function (arr, fn, thisp) { + return arr.filter(fn, thisp); + }; + } else { + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter + buster.filter = function (fn, thisp) { + if (this == null) { throw new TypeError(); } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof fn != "function") { throw new TypeError(); } + + var res = []; + for (var i = 0; i < len; i++) { + if (i in t) { + var val = t[i]; // in case fun mutates this + if (fn.call(thisp, val, i, t)) { res.push(val); } + } + } + + return res; + }; + } + + if (isNode) { + module.exports = buster; + buster.eventEmitter = require("./buster-event-emitter"); + Object.defineProperty(buster, "defineVersionGetter", { + get: function () { + return require("./define-version-getter"); + } + }); + } + + return buster.extend(B || {}, buster); +}(setTimeout, buster)); +if (typeof buster === "undefined") { + var buster = {}; +} + +if (typeof module === "object" && typeof require === "function") { + buster = require("buster-core"); +} + +buster.format = buster.format || {}; +buster.format.excludeConstructors = ["Object", /^.$/]; +buster.format.quoteStrings = true; + +buster.format.ascii = (function () { + + var hasOwn = Object.prototype.hasOwnProperty; + + var specialObjects = []; + if (typeof global != "undefined") { + specialObjects.push({ obj: global, value: "[object global]" }); + } + if (typeof document != "undefined") { + specialObjects.push({ obj: document, value: "[object HTMLDocument]" }); + } + if (typeof window != "undefined") { + specialObjects.push({ obj: window, value: "[object Window]" }); + } + + function keys(object) { + var k = Object.keys && Object.keys(object) || []; + + if (k.length == 0) { + for (var prop in object) { + if (hasOwn.call(object, prop)) { + k.push(prop); + } + } + } + + return k.sort(); + } + + function isCircular(object, objects) { + if (typeof object != "object") { + return false; + } + + for (var i = 0, l = objects.length; i < l; ++i) { + if (objects[i] === object) { + return true; + } + } + + return false; + } + + function ascii(object, processed, indent) { + if (typeof object == "string") { + var quote = typeof this.quoteStrings != "boolean" || this.quoteStrings; + return processed || quote ? '"' + object + '"' : object; + } + + if (typeof object == "function" && !(object instanceof RegExp)) { + return ascii.func(object); + } + + processed = processed || []; + + if (isCircular(object, processed)) { + return "[Circular]"; + } + + if (Object.prototype.toString.call(object) == "[object Array]") { + return ascii.array.call(this, object, processed); + } + + if (!object) { + return "" + object; + } + + if (buster.isElement(object)) { + return ascii.element(object); + } + + if (typeof object.toString == "function" && + object.toString !== Object.prototype.toString) { + return object.toString(); + } + + for (var i = 0, l = specialObjects.length; i < l; i++) { + if (object === specialObjects[i].obj) { + return specialObjects[i].value; + } + } + + return ascii.object.call(this, object, processed, indent); + } + + ascii.func = function (func) { + return "function " + buster.functionName(func) + "() {}"; + }; + + ascii.array = function (array, processed) { + processed = processed || []; + processed.push(array); + var pieces = []; + + for (var i = 0, l = array.length; i < l; ++i) { + pieces.push(ascii.call(this, array[i], processed)); + } + + return "[" + pieces.join(", ") + "]"; + }; + + ascii.object = function (object, processed, indent) { + processed = processed || []; + processed.push(object); + indent = indent || 0; + var pieces = [], properties = keys(object), prop, str, obj; + var is = ""; + var length = 3; + + for (var i = 0, l = indent; i < l; ++i) { + is += " "; + } + + for (i = 0, l = properties.length; i < l; ++i) { + prop = properties[i]; + obj = object[prop]; + + if (isCircular(obj, processed)) { + str = "[Circular]"; + } else { + str = ascii.call(this, obj, processed, indent + 2); + } + + str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str; + length += str.length; + pieces.push(str); + } + + var cons = ascii.constructorName.call(this, object); + var prefix = cons ? "[" + cons + "] " : "" + + return (length + indent) > 80 ? + prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" : + prefix + "{ " + pieces.join(", ") + " }"; + }; + + ascii.element = function (element) { + var tagName = element.tagName.toLowerCase(); + var attrs = element.attributes, attribute, pairs = [], attrName; + + for (var i = 0, l = attrs.length; i < l; ++i) { + attribute = attrs.item(i); + attrName = attribute.nodeName.toLowerCase().replace("html:", ""); + + if (attrName == "contenteditable" && attribute.nodeValue == "inherit") { + continue; + } + + if (!!attribute.nodeValue) { + pairs.push(attrName + "=\"" + attribute.nodeValue + "\""); + } + } + + var formatted = "<" + tagName + (pairs.length > 0 ? " " : ""); + var content = element.innerHTML; + + if (content.length > 20) { + content = content.substr(0, 20) + "[...]"; + } + + var res = formatted + pairs.join(" ") + ">" + content + ""; + + return res.replace(/ contentEditable="inherit"/, ""); + }; + + ascii.constructorName = function (object) { + var name = buster.functionName(object && object.constructor); + var excludes = this.excludeConstructors || buster.format.excludeConstructors || []; + + for (var i = 0, l = excludes.length; i < l; ++i) { + if (typeof excludes[i] == "string" && excludes[i] == name) { + return ""; + } else if (excludes[i].test && excludes[i].test(name)) { + return ""; + } + } + + return name; + }; + + return ascii; +}()); + +if (typeof module != "undefined") { + module.exports = buster.format; +} +/*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/ +/*global module, require, __dirname, document*/ +/** + * Sinon core utilities. For internal use only. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +var sinon = (function (buster) { + var div = typeof document != "undefined" && document.createElement("div"); + var hasOwn = Object.prototype.hasOwnProperty; + + function isDOMNode(obj) { + var success = false; + + try { + obj.appendChild(div); + success = div.parentNode == obj; + } catch (e) { + return false; + } finally { + try { + obj.removeChild(div); + } catch (e) { + // Remove failed, not much we can do about that + } + } + + return success; + } + + function isElement(obj) { + return div && obj && obj.nodeType === 1 && isDOMNode(obj); + } + + function isFunction(obj) { + return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply); + } + + function mirrorProperties(target, source) { + for (var prop in source) { + if (!hasOwn.call(target, prop)) { + target[prop] = source[prop]; + } + } + } + + function isRestorable (obj) { + return typeof obj === "function" && typeof obj.restore === "function" && obj.restore.sinon; + } + + var sinon = { + wrapMethod: function wrapMethod(object, property, method) { + if (!object) { + throw new TypeError("Should wrap property of object"); + } + + if (typeof method != "function") { + throw new TypeError("Method wrapper should be function"); + } + + var wrappedMethod = object[property]; + + if (!isFunction(wrappedMethod)) { + throw new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + + property + " as function"); + } + + if (wrappedMethod.restore && wrappedMethod.restore.sinon) { + throw new TypeError("Attempted to wrap " + property + " which is already wrapped"); + } + + if (wrappedMethod.calledBefore) { + var verb = !!wrappedMethod.returns ? "stubbed" : "spied on"; + throw new TypeError("Attempted to wrap " + property + " which is already " + verb); + } + + // IE 8 does not support hasOwnProperty on the window object. + var owned = hasOwn.call(object, property); + object[property] = method; + method.displayName = property; + + method.restore = function () { + // For prototype properties try to reset by delete first. + // If this fails (ex: localStorage on mobile safari) then force a reset + // via direct assignment. + if (!owned) { + delete object[property]; + } + if (object[property] === method) { + object[property] = wrappedMethod; + } + }; + + method.restore.sinon = true; + mirrorProperties(method, wrappedMethod); + + return method; + }, + + extend: function extend(target) { + for (var i = 1, l = arguments.length; i < l; i += 1) { + for (var prop in arguments[i]) { + if (arguments[i].hasOwnProperty(prop)) { + target[prop] = arguments[i][prop]; + } + + // DONT ENUM bug, only care about toString + if (arguments[i].hasOwnProperty("toString") && + arguments[i].toString != target.toString) { + target.toString = arguments[i].toString; + } + } + } + + return target; + }, + + create: function create(proto) { + var F = function () {}; + F.prototype = proto; + return new F(); + }, + + deepEqual: function deepEqual(a, b) { + if (sinon.match && sinon.match.isMatcher(a)) { + return a.test(b); + } + if (typeof a != "object" || typeof b != "object") { + return a === b; + } + + if (isElement(a) || isElement(b)) { + return a === b; + } + + if (a === b) { + return true; + } + + if ((a === null && b !== null) || (a !== null && b === null)) { + return false; + } + + var aString = Object.prototype.toString.call(a); + if (aString != Object.prototype.toString.call(b)) { + return false; + } + + if (aString == "[object Array]") { + if (a.length !== b.length) { + return false; + } + + for (var i = 0, l = a.length; i < l; i += 1) { + if (!deepEqual(a[i], b[i])) { + return false; + } + } + + return true; + } + + var prop, aLength = 0, bLength = 0; + + for (prop in a) { + aLength += 1; + + if (!deepEqual(a[prop], b[prop])) { + return false; + } + } + + for (prop in b) { + bLength += 1; + } + + return aLength == bLength; + }, + + functionName: function functionName(func) { + var name = func.displayName || func.name; + + // Use function decomposition as a last resort to get function + // name. Does not rely on function decomposition to work - if it + // doesn't debugging will be slightly less informative + // (i.e. toString will say 'spy' rather than 'myFunc'). + if (!name) { + var matches = func.toString().match(/function ([^\s\(]+)/); + name = matches && matches[1]; + } + + return name; + }, + + functionToString: function toString() { + if (this.getCall && this.callCount) { + var thisValue, prop, i = this.callCount; + + while (i--) { + thisValue = this.getCall(i).thisValue; + + for (prop in thisValue) { + if (thisValue[prop] === this) { + return prop; + } + } + } + } + + return this.displayName || "sinon fake"; + }, + + getConfig: function (custom) { + var config = {}; + custom = custom || {}; + var defaults = sinon.defaultConfig; + + for (var prop in defaults) { + if (defaults.hasOwnProperty(prop)) { + config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop]; + } + } + + return config; + }, + + format: function (val) { + return "" + val; + }, + + defaultConfig: { + injectIntoThis: true, + injectInto: null, + properties: ["spy", "stub", "mock", "clock", "server", "requests"], + useFakeTimers: true, + useFakeServer: true + }, + + timesInWords: function timesInWords(count) { + return count == 1 && "once" || + count == 2 && "twice" || + count == 3 && "thrice" || + (count || 0) + " times"; + }, + + calledInOrder: function (spies) { + for (var i = 1, l = spies.length; i < l; i++) { + if (!spies[i - 1].calledBefore(spies[i]) || !spies[i].called) { + return false; + } + } + + return true; + }, + + orderByFirstCall: function (spies) { + return spies.sort(function (a, b) { + // uuid, won't ever be equal + var aCall = a.getCall(0); + var bCall = b.getCall(0); + var aId = aCall && aCall.callId || -1; + var bId = bCall && bCall.callId || -1; + + return aId < bId ? -1 : 1; + }); + }, + + log: function () {}, + + logError: function (label, err) { + var msg = label + " threw exception: " + sinon.log(msg + "[" + err.name + "] " + err.message); + if (err.stack) { sinon.log(err.stack); } + + setTimeout(function () { + err.message = msg + err.message; + throw err; + }, 0); + }, + + typeOf: function (value) { + if (value === null) { + return "null"; + } + else if (value === undefined) { + return "undefined"; + } + var string = Object.prototype.toString.call(value); + return string.substring(8, string.length - 1).toLowerCase(); + }, + + createStubInstance: function (constructor) { + if (typeof constructor !== "function") { + throw new TypeError("The constructor should be a function."); + } + return sinon.stub(sinon.create(constructor.prototype)); + }, + + restore: function (object) { + if (object !== null && typeof object === "object") { + for (var prop in object) { + if (isRestorable(object[prop])) { + object[prop].restore(); + } + } + } + else if (isRestorable(object)) { + object.restore(); + } + } + }; + + var isNode = typeof module == "object" && typeof require == "function"; + + if (isNode) { + try { + buster = { format: require("buster-format") }; + } catch (e) {} + module.exports = sinon; + module.exports.spy = require("./sinon/spy"); + module.exports.spyCall = require("./sinon/call"); + module.exports.stub = require("./sinon/stub"); + module.exports.mock = require("./sinon/mock"); + module.exports.collection = require("./sinon/collection"); + module.exports.assert = require("./sinon/assert"); + module.exports.sandbox = require("./sinon/sandbox"); + module.exports.test = require("./sinon/test"); + module.exports.testCase = require("./sinon/test_case"); + module.exports.assert = require("./sinon/assert"); + module.exports.match = require("./sinon/match"); + } + + if (buster) { + var formatter = sinon.create(buster.format); + formatter.quoteStrings = false; + sinon.format = function () { + return formatter.ascii.apply(formatter, arguments); + }; + } else if (isNode) { + try { + var util = require("util"); + sinon.format = function (value) { + return typeof value == "object" && value.toString === Object.prototype.toString ? util.inspect(value) : value; + }; + } catch (e) { + /* Node, but no util module - would be very old, but better safe than + sorry */ + } + } + + return sinon; +}(typeof buster == "object" && buster)); + +/* @depend ../sinon.js */ +/*jslint eqeqeq: false, onevar: false, plusplus: false*/ +/*global module, require, sinon*/ +/** + * Match functions + * + * @author Maximilian Antoni (mail@maxantoni.de) + * @license BSD + * + * Copyright (c) 2012 Maximilian Antoni + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function assertType(value, type, name) { + var actual = sinon.typeOf(value); + if (actual !== type) { + throw new TypeError("Expected type of " + name + " to be " + + type + ", but was " + actual); + } + } + + var matcher = { + toString: function () { + return this.message; + } + }; + + function isMatcher(object) { + return matcher.isPrototypeOf(object); + } + + function matchObject(expectation, actual) { + if (actual === null || actual === undefined) { + return false; + } + for (var key in expectation) { + if (expectation.hasOwnProperty(key)) { + var exp = expectation[key]; + var act = actual[key]; + if (match.isMatcher(exp)) { + if (!exp.test(act)) { + return false; + } + } else if (sinon.typeOf(exp) === "object") { + if (!matchObject(exp, act)) { + return false; + } + } else if (!sinon.deepEqual(exp, act)) { + return false; + } + } + } + return true; + } + + matcher.or = function (m2) { + if (!isMatcher(m2)) { + throw new TypeError("Matcher expected"); + } + var m1 = this; + var or = sinon.create(matcher); + or.test = function (actual) { + return m1.test(actual) || m2.test(actual); + }; + or.message = m1.message + ".or(" + m2.message + ")"; + return or; + }; + + matcher.and = function (m2) { + if (!isMatcher(m2)) { + throw new TypeError("Matcher expected"); + } + var m1 = this; + var and = sinon.create(matcher); + and.test = function (actual) { + return m1.test(actual) && m2.test(actual); + }; + and.message = m1.message + ".and(" + m2.message + ")"; + return and; + }; + + var match = function (expectation, message) { + var m = sinon.create(matcher); + var type = sinon.typeOf(expectation); + switch (type) { + case "object": + if (typeof expectation.test === "function") { + m.test = function (actual) { + return expectation.test(actual) === true; + }; + m.message = "match(" + sinon.functionName(expectation.test) + ")"; + return m; + } + var str = []; + for (var key in expectation) { + if (expectation.hasOwnProperty(key)) { + str.push(key + ": " + expectation[key]); + } + } + m.test = function (actual) { + return matchObject(expectation, actual); + }; + m.message = "match(" + str.join(", ") + ")"; + break; + case "number": + m.test = function (actual) { + return expectation == actual; + }; + break; + case "string": + m.test = function (actual) { + if (typeof actual !== "string") { + return false; + } + return actual.indexOf(expectation) !== -1; + }; + m.message = "match(\"" + expectation + "\")"; + break; + case "regexp": + m.test = function (actual) { + if (typeof actual !== "string") { + return false; + } + return expectation.test(actual); + }; + break; + case "function": + m.test = expectation; + if (message) { + m.message = message; + } else { + m.message = "match(" + sinon.functionName(expectation) + ")"; + } + break; + default: + m.test = function (actual) { + return sinon.deepEqual(expectation, actual); + }; + } + if (!m.message) { + m.message = "match(" + expectation + ")"; + } + return m; + }; + + match.isMatcher = isMatcher; + + match.any = match(function () { + return true; + }, "any"); + + match.defined = match(function (actual) { + return actual !== null && actual !== undefined; + }, "defined"); + + match.truthy = match(function (actual) { + return !!actual; + }, "truthy"); + + match.falsy = match(function (actual) { + return !actual; + }, "falsy"); + + match.same = function (expectation) { + return match(function (actual) { + return expectation === actual; + }, "same(" + expectation + ")"); + }; + + match.typeOf = function (type) { + assertType(type, "string", "type"); + return match(function (actual) { + return sinon.typeOf(actual) === type; + }, "typeOf(\"" + type + "\")"); + }; + + match.instanceOf = function (type) { + assertType(type, "function", "type"); + return match(function (actual) { + return actual instanceof type; + }, "instanceOf(" + sinon.functionName(type) + ")"); + }; + + function createPropertyMatcher(propertyTest, messagePrefix) { + return function (property, value) { + assertType(property, "string", "property"); + var onlyProperty = arguments.length === 1; + var message = messagePrefix + "(\"" + property + "\""; + if (!onlyProperty) { + message += ", " + value; + } + message += ")"; + return match(function (actual) { + if (actual === undefined || actual === null || + !propertyTest(actual, property)) { + return false; + } + return onlyProperty || sinon.deepEqual(value, actual[property]); + }, message); + }; + } + + match.has = createPropertyMatcher(function (actual, property) { + if (typeof actual === "object") { + return property in actual; + } + return actual[property] !== undefined; + }, "has"); + + match.hasOwn = createPropertyMatcher(function (actual, property) { + return actual.hasOwnProperty(property); + }, "hasOwn"); + + match.bool = match.typeOf("boolean"); + match.number = match.typeOf("number"); + match.string = match.typeOf("string"); + match.object = match.typeOf("object"); + match.func = match.typeOf("function"); + match.array = match.typeOf("array"); + match.regexp = match.typeOf("regexp"); + match.date = match.typeOf("date"); + + if (commonJSModule) { + module.exports = match; + } else { + sinon.match = match; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend match.js + */ +/*jslint eqeqeq: false, onevar: false, plusplus: false*/ +/*global module, require, sinon*/ +/** + * Spy calls + * + * @author Christian Johansen (christian@cjohansen.no) + * @author Maximilian Antoni (mail@maxantoni.de) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + * Copyright (c) 2013 Maximilian Antoni + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function throwYieldError(proxy, text, args) { + var msg = sinon.functionName(proxy) + text; + if (args.length) { + msg += " Received [" + slice.call(args).join(", ") + "]"; + } + throw new Error(msg); + } + + var slice = Array.prototype.slice; + + var callProto = { + calledOn: function calledOn(thisValue) { + if (sinon.match && sinon.match.isMatcher(thisValue)) { + return thisValue.test(this.thisValue); + } + return this.thisValue === thisValue; + }, + + calledWith: function calledWith() { + for (var i = 0, l = arguments.length; i < l; i += 1) { + if (!sinon.deepEqual(arguments[i], this.args[i])) { + return false; + } + } + + return true; + }, + + calledWithMatch: function calledWithMatch() { + for (var i = 0, l = arguments.length; i < l; i += 1) { + var actual = this.args[i]; + var expectation = arguments[i]; + if (!sinon.match || !sinon.match(expectation).test(actual)) { + return false; + } + } + return true; + }, + + calledWithExactly: function calledWithExactly() { + return arguments.length == this.args.length && + this.calledWith.apply(this, arguments); + }, + + notCalledWith: function notCalledWith() { + return !this.calledWith.apply(this, arguments); + }, + + notCalledWithMatch: function notCalledWithMatch() { + return !this.calledWithMatch.apply(this, arguments); + }, + + returned: function returned(value) { + return sinon.deepEqual(value, this.returnValue); + }, + + threw: function threw(error) { + if (typeof error === "undefined" || !this.exception) { + return !!this.exception; + } + + return this.exception === error || this.exception.name === error; + }, + + calledWithNew: function calledWithNew(thisValue) { + return this.thisValue instanceof this.proxy; + }, + + calledBefore: function (other) { + return this.callId < other.callId; + }, + + calledAfter: function (other) { + return this.callId > other.callId; + }, + + callArg: function (pos) { + this.args[pos](); + }, + + callArgOn: function (pos, thisValue) { + this.args[pos].apply(thisValue); + }, + + callArgWith: function (pos) { + this.callArgOnWith.apply(this, [pos, null].concat(slice.call(arguments, 1))); + }, + + callArgOnWith: function (pos, thisValue) { + var args = slice.call(arguments, 2); + this.args[pos].apply(thisValue, args); + }, + + "yield": function () { + this.yieldOn.apply(this, [null].concat(slice.call(arguments, 0))); + }, + + yieldOn: function (thisValue) { + var args = this.args; + for (var i = 0, l = args.length; i < l; ++i) { + if (typeof args[i] === "function") { + args[i].apply(thisValue, slice.call(arguments, 1)); + return; + } + } + throwYieldError(this.proxy, " cannot yield since no callback was passed.", args); + }, + + yieldTo: function (prop) { + this.yieldToOn.apply(this, [prop, null].concat(slice.call(arguments, 1))); + }, + + yieldToOn: function (prop, thisValue) { + var args = this.args; + for (var i = 0, l = args.length; i < l; ++i) { + if (args[i] && typeof args[i][prop] === "function") { + args[i][prop].apply(thisValue, slice.call(arguments, 2)); + return; + } + } + throwYieldError(this.proxy, " cannot yield to '" + prop + + "' since no callback was passed.", args); + }, + + toString: function () { + var callStr = this.proxy.toString() + "("; + var args = []; + + for (var i = 0, l = this.args.length; i < l; ++i) { + args.push(sinon.format(this.args[i])); + } + + callStr = callStr + args.join(", ") + ")"; + + if (typeof this.returnValue != "undefined") { + callStr += " => " + sinon.format(this.returnValue); + } + + if (this.exception) { + callStr += " !" + this.exception.name; + + if (this.exception.message) { + callStr += "(" + this.exception.message + ")"; + } + } + + return callStr; + } + }; + + callProto.invokeCallback = callProto.yield; + + function createSpyCall(spy, thisValue, args, returnValue, exception, id) { + if (typeof id !== "number") { + throw new TypeError("Call id is not a number"); + } + var proxyCall = sinon.create(callProto); + proxyCall.proxy = spy; + proxyCall.thisValue = thisValue; + proxyCall.args = args; + proxyCall.returnValue = returnValue; + proxyCall.exception = exception; + proxyCall.callId = id; + + return proxyCall; + }; + createSpyCall.toString = callProto.toString; // used by mocks + + if (commonJSModule) { + module.exports = createSpyCall; + } else { + sinon.spyCall = createSpyCall; + } +}(typeof sinon == "object" && sinon || null)); + + +/** + * @depend ../sinon.js + * @depend call.js + */ +/*jslint eqeqeq: false, onevar: false, plusplus: false*/ +/*global module, require, sinon*/ +/** + * Spy functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var push = Array.prototype.push; + var slice = Array.prototype.slice; + var callId = 0; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function spy(object, property) { + if (!property && typeof object == "function") { + return spy.create(object); + } + + if (!object && !property) { + return spy.create(function () { }); + } + + var method = object[property]; + return sinon.wrapMethod(object, property, spy.create(method)); + } + + function matchingFake(fakes, args, strict) { + if (!fakes) { + return; + } + + var alen = args.length; + + for (var i = 0, l = fakes.length; i < l; i++) { + if (fakes[i].matches(args, strict)) { + return fakes[i]; + } + } + } + + function incrementCallCount() { + this.called = true; + this.callCount += 1; + this.notCalled = false; + this.calledOnce = this.callCount == 1; + this.calledTwice = this.callCount == 2; + this.calledThrice = this.callCount == 3; + } + + function createCallProperties() { + this.firstCall = this.getCall(0); + this.secondCall = this.getCall(1); + this.thirdCall = this.getCall(2); + this.lastCall = this.getCall(this.callCount - 1); + } + + var vars = "a,b,c,d,e,f,g,h,i,j,k,l"; + function createProxy(func) { + // Retain the function length: + var p; + if (func.length) { + eval("p = (function proxy(" + vars.substring(0, func.length * 2 - 1) + + ") { return p.invoke(func, this, slice.call(arguments)); });"); + } + else { + p = function proxy() { + return p.invoke(func, this, slice.call(arguments)); + }; + } + return p; + } + + var uuid = 0; + + // Public API + var spyApi = { + reset: function () { + this.called = false; + this.notCalled = true; + this.calledOnce = false; + this.calledTwice = false; + this.calledThrice = false; + this.callCount = 0; + this.firstCall = null; + this.secondCall = null; + this.thirdCall = null; + this.lastCall = null; + this.args = []; + this.returnValues = []; + this.thisValues = []; + this.exceptions = []; + this.callIds = []; + if (this.fakes) { + for (var i = 0; i < this.fakes.length; i++) { + this.fakes[i].reset(); + } + } + }, + + create: function create(func) { + var name; + + if (typeof func != "function") { + func = function () { }; + } else { + name = sinon.functionName(func); + } + + var proxy = createProxy(func); + + sinon.extend(proxy, spy); + delete proxy.create; + sinon.extend(proxy, func); + + proxy.reset(); + proxy.prototype = func.prototype; + proxy.displayName = name || "spy"; + proxy.toString = sinon.functionToString; + proxy._create = sinon.spy.create; + proxy.id = "spy#" + uuid++; + + return proxy; + }, + + invoke: function invoke(func, thisValue, args) { + var matching = matchingFake(this.fakes, args); + var exception, returnValue; + + incrementCallCount.call(this); + push.call(this.thisValues, thisValue); + push.call(this.args, args); + push.call(this.callIds, callId++); + + try { + if (matching) { + returnValue = matching.invoke(func, thisValue, args); + } else { + returnValue = (this.func || func).apply(thisValue, args); + } + } catch (e) { + push.call(this.returnValues, undefined); + exception = e; + throw e; + } finally { + push.call(this.exceptions, exception); + } + + push.call(this.returnValues, returnValue); + + createCallProperties.call(this); + + return returnValue; + }, + + getCall: function getCall(i) { + if (i < 0 || i >= this.callCount) { + return null; + } + + return sinon.spyCall(this, this.thisValues[i], this.args[i], + this.returnValues[i], this.exceptions[i], + this.callIds[i]); + }, + + calledBefore: function calledBefore(spyFn) { + if (!this.called) { + return false; + } + + if (!spyFn.called) { + return true; + } + + return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1]; + }, + + calledAfter: function calledAfter(spyFn) { + if (!this.called || !spyFn.called) { + return false; + } + + return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1]; + }, + + withArgs: function () { + var args = slice.call(arguments); + + if (this.fakes) { + var match = matchingFake(this.fakes, args, true); + + if (match) { + return match; + } + } else { + this.fakes = []; + } + + var original = this; + var fake = this._create(); + fake.matchingAguments = args; + push.call(this.fakes, fake); + + fake.withArgs = function () { + return original.withArgs.apply(original, arguments); + }; + + for (var i = 0; i < this.args.length; i++) { + if (fake.matches(this.args[i])) { + incrementCallCount.call(fake); + push.call(fake.thisValues, this.thisValues[i]); + push.call(fake.args, this.args[i]); + push.call(fake.returnValues, this.returnValues[i]); + push.call(fake.exceptions, this.exceptions[i]); + push.call(fake.callIds, this.callIds[i]); + } + } + createCallProperties.call(fake); + + return fake; + }, + + matches: function (args, strict) { + var margs = this.matchingAguments; + + if (margs.length <= args.length && + sinon.deepEqual(margs, args.slice(0, margs.length))) { + return !strict || margs.length == args.length; + } + }, + + printf: function (format) { + var spy = this; + var args = slice.call(arguments, 1); + var formatter; + + return (format || "").replace(/%(.)/g, function (match, specifyer) { + formatter = spyApi.formatters[specifyer]; + + if (typeof formatter == "function") { + return formatter.call(null, spy, args); + } else if (!isNaN(parseInt(specifyer), 10)) { + return sinon.format(args[specifyer - 1]); + } + + return "%" + specifyer; + }); + } + }; + + function delegateToCalls(method, matchAny, actual, notCalled) { + spyApi[method] = function () { + if (!this.called) { + if (notCalled) { + return notCalled.apply(this, arguments); + } + return false; + } + + var currentCall; + var matches = 0; + + for (var i = 0, l = this.callCount; i < l; i += 1) { + currentCall = this.getCall(i); + + if (currentCall[actual || method].apply(currentCall, arguments)) { + matches += 1; + + if (matchAny) { + return true; + } + } + } + + return matches === this.callCount; + }; + } + + delegateToCalls("calledOn", true); + delegateToCalls("alwaysCalledOn", false, "calledOn"); + delegateToCalls("calledWith", true); + delegateToCalls("calledWithMatch", true); + delegateToCalls("alwaysCalledWith", false, "calledWith"); + delegateToCalls("alwaysCalledWithMatch", false, "calledWithMatch"); + delegateToCalls("calledWithExactly", true); + delegateToCalls("alwaysCalledWithExactly", false, "calledWithExactly"); + delegateToCalls("neverCalledWith", false, "notCalledWith", + function () { return true; }); + delegateToCalls("neverCalledWithMatch", false, "notCalledWithMatch", + function () { return true; }); + delegateToCalls("threw", true); + delegateToCalls("alwaysThrew", false, "threw"); + delegateToCalls("returned", true); + delegateToCalls("alwaysReturned", false, "returned"); + delegateToCalls("calledWithNew", true); + delegateToCalls("alwaysCalledWithNew", false, "calledWithNew"); + delegateToCalls("callArg", false, "callArgWith", function () { + throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); + }); + spyApi.callArgWith = spyApi.callArg; + delegateToCalls("callArgOn", false, "callArgOnWith", function () { + throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); + }); + spyApi.callArgOnWith = spyApi.callArgOn; + delegateToCalls("yield", false, "yield", function () { + throw new Error(this.toString() + " cannot yield since it was not yet invoked."); + }); + // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode. + spyApi.invokeCallback = spyApi.yield; + delegateToCalls("yieldOn", false, "yieldOn", function () { + throw new Error(this.toString() + " cannot yield since it was not yet invoked."); + }); + delegateToCalls("yieldTo", false, "yieldTo", function (property) { + throw new Error(this.toString() + " cannot yield to '" + property + + "' since it was not yet invoked."); + }); + delegateToCalls("yieldToOn", false, "yieldToOn", function (property) { + throw new Error(this.toString() + " cannot yield to '" + property + + "' since it was not yet invoked."); + }); + + spyApi.formatters = { + "c": function (spy) { + return sinon.timesInWords(spy.callCount); + }, + + "n": function (spy) { + return spy.toString(); + }, + + "C": function (spy) { + var calls = []; + + for (var i = 0, l = spy.callCount; i < l; ++i) { + var stringifiedCall = " " + spy.getCall(i).toString(); + if (/\n/.test(calls[i - 1])) { + stringifiedCall = "\n" + stringifiedCall; + } + push.call(calls, stringifiedCall); + } + + return calls.length > 0 ? "\n" + calls.join("\n") : ""; + }, + + "t": function (spy) { + var objects = []; + + for (var i = 0, l = spy.callCount; i < l; ++i) { + push.call(objects, sinon.format(spy.thisValues[i])); + } + + return objects.join(", "); + }, + + "*": function (spy, args) { + var formatted = []; + + for (var i = 0, l = args.length; i < l; ++i) { + push.call(formatted, sinon.format(args[i])); + } + + return formatted.join(", "); + } + }; + + sinon.extend(spy, spyApi); + + spy.spyCall = sinon.spyCall; + + if (commonJSModule) { + module.exports = spy; + } else { + sinon.spy = spy; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend spy.js + */ +/*jslint eqeqeq: false, onevar: false*/ +/*global module, require, sinon*/ +/** + * Stub functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function stub(object, property, func) { + if (!!func && typeof func != "function") { + throw new TypeError("Custom stub should be function"); + } + + var wrapper; + + if (func) { + wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func; + } else { + wrapper = stub.create(); + } + + if (!object && !property) { + return sinon.stub.create(); + } + + if (!property && !!object && typeof object == "object") { + for (var prop in object) { + if (typeof object[prop] === "function") { + stub(object, prop); + } + } + + return object; + } + + return sinon.wrapMethod(object, property, wrapper); + } + + function getChangingValue(stub, property) { + var index = stub.callCount - 1; + var values = stub[property]; + var prop = index in values ? values[index] : values[values.length - 1]; + stub[property + "Last"] = prop; + + return prop; + } + + function getCallback(stub, args) { + var callArgAt = getChangingValue(stub, "callArgAts"); + + if (callArgAt < 0) { + var callArgProp = getChangingValue(stub, "callArgProps"); + + for (var i = 0, l = args.length; i < l; ++i) { + if (!callArgProp && typeof args[i] == "function") { + return args[i]; + } + + if (callArgProp && args[i] && + typeof args[i][callArgProp] == "function") { + return args[i][callArgProp]; + } + } + + return null; + } + + return args[callArgAt]; + } + + var join = Array.prototype.join; + + function getCallbackError(stub, func, args) { + if (stub.callArgAtsLast < 0) { + var msg; + + if (stub.callArgPropsLast) { + msg = sinon.functionName(stub) + + " expected to yield to '" + stub.callArgPropsLast + + "', but no object with such a property was passed." + } else { + msg = sinon.functionName(stub) + + " expected to yield, but no callback was passed." + } + + if (args.length > 0) { + msg += " Received [" + join.call(args, ", ") + "]"; + } + + return msg; + } + + return "argument at index " + stub.callArgAtsLast + " is not a function: " + func; + } + + var nextTick = (function () { + if (typeof process === "object" && typeof process.nextTick === "function") { + return process.nextTick; + } else if (typeof setImmediate === "function") { + return setImmediate; + } else { + return function (callback) { + setTimeout(callback, 0); + }; + } + })(); + + function callCallback(stub, args) { + if (stub.callArgAts.length > 0) { + var func = getCallback(stub, args); + + if (typeof func != "function") { + throw new TypeError(getCallbackError(stub, func, args)); + } + + var callbackArguments = getChangingValue(stub, "callbackArguments"); + var callbackContext = getChangingValue(stub, "callbackContexts"); + + if (stub.callbackAsync) { + nextTick(function() { + func.apply(callbackContext, callbackArguments); + }); + } else { + func.apply(callbackContext, callbackArguments); + } + } + } + + var uuid = 0; + + sinon.extend(stub, (function () { + var slice = Array.prototype.slice, proto; + + function throwsException(error, message) { + if (typeof error == "string") { + this.exception = new Error(message || ""); + this.exception.name = error; + } else if (!error) { + this.exception = new Error("Error"); + } else { + this.exception = error; + } + + return this; + } + + proto = { + create: function create() { + var functionStub = function () { + + callCallback(functionStub, arguments); + + if (functionStub.exception) { + throw functionStub.exception; + } else if (typeof functionStub.returnArgAt == 'number') { + return arguments[functionStub.returnArgAt]; + } else if (functionStub.returnThis) { + return this; + } + return functionStub.returnValue; + }; + + functionStub.id = "stub#" + uuid++; + var orig = functionStub; + functionStub = sinon.spy.create(functionStub); + functionStub.func = orig; + + functionStub.callArgAts = []; + functionStub.callbackArguments = []; + functionStub.callbackContexts = []; + functionStub.callArgProps = []; + + sinon.extend(functionStub, stub); + functionStub._create = sinon.stub.create; + functionStub.displayName = "stub"; + functionStub.toString = sinon.functionToString; + + return functionStub; + }, + + resetBehavior: function () { + var i; + + this.callArgAts = []; + this.callbackArguments = []; + this.callbackContexts = []; + this.callArgProps = []; + + delete this.returnValue; + delete this.returnArgAt; + this.returnThis = false; + + if (this.fakes) { + for (i = 0; i < this.fakes.length; i++) { + this.fakes[i].resetBehavior(); + } + } + }, + + returns: function returns(value) { + this.returnValue = value; + + return this; + }, + + returnsArg: function returnsArg(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.returnArgAt = pos; + + return this; + }, + + returnsThis: function returnsThis() { + this.returnThis = true; + + return this; + }, + + "throws": throwsException, + throwsException: throwsException, + + callsArg: function callsArg(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push([]); + this.callbackContexts.push(undefined); + this.callArgProps.push(undefined); + + return this; + }, + + callsArgOn: function callsArgOn(pos, context) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push([]); + this.callbackContexts.push(context); + this.callArgProps.push(undefined); + + return this; + }, + + callsArgWith: function callsArgWith(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push(slice.call(arguments, 1)); + this.callbackContexts.push(undefined); + this.callArgProps.push(undefined); + + return this; + }, + + callsArgOnWith: function callsArgWith(pos, context) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push(slice.call(arguments, 2)); + this.callbackContexts.push(context); + this.callArgProps.push(undefined); + + return this; + }, + + yields: function () { + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 0)); + this.callbackContexts.push(undefined); + this.callArgProps.push(undefined); + + return this; + }, + + yieldsOn: function (context) { + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 1)); + this.callbackContexts.push(context); + this.callArgProps.push(undefined); + + return this; + }, + + yieldsTo: function (prop) { + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 1)); + this.callbackContexts.push(undefined); + this.callArgProps.push(prop); + + return this; + }, + + yieldsToOn: function (prop, context) { + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 2)); + this.callbackContexts.push(context); + this.callArgProps.push(prop); + + return this; + } + }; + + // create asynchronous versions of callsArg* and yields* methods + for (var method in proto) { + // need to avoid creating anotherasync versions of the newly added async methods + if (proto.hasOwnProperty(method) && + method.match(/^(callsArg|yields|thenYields$)/) && + !method.match(/Async/)) { + proto[method + 'Async'] = (function (syncFnName) { + return function () { + this.callbackAsync = true; + return this[syncFnName].apply(this, arguments); + }; + })(method); + } + } + + return proto; + + }())); + + if (commonJSModule) { + module.exports = stub; + } else { + sinon.stub = stub; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend stub.js + */ +/*jslint eqeqeq: false, onevar: false, nomen: false*/ +/*global module, require, sinon*/ +/** + * Mock functions. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var push = [].push; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function mock(object) { + if (!object) { + return sinon.expectation.create("Anonymous mock"); + } + + return mock.create(object); + } + + sinon.mock = mock; + + sinon.extend(mock, (function () { + function each(collection, callback) { + if (!collection) { + return; + } + + for (var i = 0, l = collection.length; i < l; i += 1) { + callback(collection[i]); + } + } + + return { + create: function create(object) { + if (!object) { + throw new TypeError("object is null"); + } + + var mockObject = sinon.extend({}, mock); + mockObject.object = object; + delete mockObject.create; + + return mockObject; + }, + + expects: function expects(method) { + if (!method) { + throw new TypeError("method is falsy"); + } + + if (!this.expectations) { + this.expectations = {}; + this.proxies = []; + } + + if (!this.expectations[method]) { + this.expectations[method] = []; + var mockObject = this; + + sinon.wrapMethod(this.object, method, function () { + return mockObject.invokeMethod(method, this, arguments); + }); + + push.call(this.proxies, method); + } + + var expectation = sinon.expectation.create(method); + push.call(this.expectations[method], expectation); + + return expectation; + }, + + restore: function restore() { + var object = this.object; + + each(this.proxies, function (proxy) { + if (typeof object[proxy].restore == "function") { + object[proxy].restore(); + } + }); + }, + + verify: function verify() { + var expectations = this.expectations || {}; + var messages = [], met = []; + + each(this.proxies, function (proxy) { + each(expectations[proxy], function (expectation) { + if (!expectation.met()) { + push.call(messages, expectation.toString()); + } else { + push.call(met, expectation.toString()); + } + }); + }); + + this.restore(); + + if (messages.length > 0) { + sinon.expectation.fail(messages.concat(met).join("\n")); + } else { + sinon.expectation.pass(messages.concat(met).join("\n")); + } + + return true; + }, + + invokeMethod: function invokeMethod(method, thisValue, args) { + var expectations = this.expectations && this.expectations[method]; + var length = expectations && expectations.length || 0, i; + + for (i = 0; i < length; i += 1) { + if (!expectations[i].met() && + expectations[i].allowsCall(thisValue, args)) { + return expectations[i].apply(thisValue, args); + } + } + + var messages = [], available, exhausted = 0; + + for (i = 0; i < length; i += 1) { + if (expectations[i].allowsCall(thisValue, args)) { + available = available || expectations[i]; + } else { + exhausted += 1; + } + push.call(messages, " " + expectations[i].toString()); + } + + if (exhausted === 0) { + return available.apply(thisValue, args); + } + + messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({ + proxy: method, + args: args + })); + + sinon.expectation.fail(messages.join("\n")); + } + }; + }())); + + var times = sinon.timesInWords; + + sinon.expectation = (function () { + var slice = Array.prototype.slice; + var _invoke = sinon.spy.invoke; + + function callCountInWords(callCount) { + if (callCount == 0) { + return "never called"; + } else { + return "called " + times(callCount); + } + } + + function expectedCallCountInWords(expectation) { + var min = expectation.minCalls; + var max = expectation.maxCalls; + + if (typeof min == "number" && typeof max == "number") { + var str = times(min); + + if (min != max) { + str = "at least " + str + " and at most " + times(max); + } + + return str; + } + + if (typeof min == "number") { + return "at least " + times(min); + } + + return "at most " + times(max); + } + + function receivedMinCalls(expectation) { + var hasMinLimit = typeof expectation.minCalls == "number"; + return !hasMinLimit || expectation.callCount >= expectation.minCalls; + } + + function receivedMaxCalls(expectation) { + if (typeof expectation.maxCalls != "number") { + return false; + } + + return expectation.callCount == expectation.maxCalls; + } + + return { + minCalls: 1, + maxCalls: 1, + + create: function create(methodName) { + var expectation = sinon.extend(sinon.stub.create(), sinon.expectation); + delete expectation.create; + expectation.method = methodName; + + return expectation; + }, + + invoke: function invoke(func, thisValue, args) { + this.verifyCallAllowed(thisValue, args); + + return _invoke.apply(this, arguments); + }, + + atLeast: function atLeast(num) { + if (typeof num != "number") { + throw new TypeError("'" + num + "' is not number"); + } + + if (!this.limitsSet) { + this.maxCalls = null; + this.limitsSet = true; + } + + this.minCalls = num; + + return this; + }, + + atMost: function atMost(num) { + if (typeof num != "number") { + throw new TypeError("'" + num + "' is not number"); + } + + if (!this.limitsSet) { + this.minCalls = null; + this.limitsSet = true; + } + + this.maxCalls = num; + + return this; + }, + + never: function never() { + return this.exactly(0); + }, + + once: function once() { + return this.exactly(1); + }, + + twice: function twice() { + return this.exactly(2); + }, + + thrice: function thrice() { + return this.exactly(3); + }, + + exactly: function exactly(num) { + if (typeof num != "number") { + throw new TypeError("'" + num + "' is not a number"); + } + + this.atLeast(num); + return this.atMost(num); + }, + + met: function met() { + return !this.failed && receivedMinCalls(this); + }, + + verifyCallAllowed: function verifyCallAllowed(thisValue, args) { + if (receivedMaxCalls(this)) { + this.failed = true; + sinon.expectation.fail(this.method + " already called " + times(this.maxCalls)); + } + + if ("expectedThis" in this && this.expectedThis !== thisValue) { + sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " + + this.expectedThis); + } + + if (!("expectedArguments" in this)) { + return; + } + + if (!args) { + sinon.expectation.fail(this.method + " received no arguments, expected " + + sinon.format(this.expectedArguments)); + } + + if (args.length < this.expectedArguments.length) { + sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) + + "), expected " + sinon.format(this.expectedArguments)); + } + + if (this.expectsExactArgCount && + args.length != this.expectedArguments.length) { + sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) + + "), expected " + sinon.format(this.expectedArguments)); + } + + for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { + sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) + + ", expected " + sinon.format(this.expectedArguments)); + } + } + }, + + allowsCall: function allowsCall(thisValue, args) { + if (this.met() && receivedMaxCalls(this)) { + return false; + } + + if ("expectedThis" in this && this.expectedThis !== thisValue) { + return false; + } + + if (!("expectedArguments" in this)) { + return true; + } + + args = args || []; + + if (args.length < this.expectedArguments.length) { + return false; + } + + if (this.expectsExactArgCount && + args.length != this.expectedArguments.length) { + return false; + } + + for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { + return false; + } + } + + return true; + }, + + withArgs: function withArgs() { + this.expectedArguments = slice.call(arguments); + return this; + }, + + withExactArgs: function withExactArgs() { + this.withArgs.apply(this, arguments); + this.expectsExactArgCount = true; + return this; + }, + + on: function on(thisValue) { + this.expectedThis = thisValue; + return this; + }, + + toString: function () { + var args = (this.expectedArguments || []).slice(); + + if (!this.expectsExactArgCount) { + push.call(args, "[...]"); + } + + var callStr = sinon.spyCall.toString.call({ + proxy: this.method || "anonymous mock expectation", + args: args + }); + + var message = callStr.replace(", [...", "[, ...") + " " + + expectedCallCountInWords(this); + + if (this.met()) { + return "Expectation met: " + message; + } + + return "Expected " + message + " (" + + callCountInWords(this.callCount) + ")"; + }, + + verify: function verify() { + if (!this.met()) { + sinon.expectation.fail(this.toString()); + } else { + sinon.expectation.pass(this.toString()); + } + + return true; + }, + + pass: function(message) { + sinon.assert.pass(message); + }, + fail: function (message) { + var exception = new Error(message); + exception.name = "ExpectationError"; + + throw exception; + } + }; + }()); + + if (commonJSModule) { + module.exports = mock; + } else { + sinon.mock = mock; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend stub.js + * @depend mock.js + */ +/*jslint eqeqeq: false, onevar: false, forin: true*/ +/*global module, require, sinon*/ +/** + * Collections of stubs, spies and mocks. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var push = [].push; + var hasOwnProperty = Object.prototype.hasOwnProperty; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function getFakes(fakeCollection) { + if (!fakeCollection.fakes) { + fakeCollection.fakes = []; + } + + return fakeCollection.fakes; + } + + function each(fakeCollection, method) { + var fakes = getFakes(fakeCollection); + + for (var i = 0, l = fakes.length; i < l; i += 1) { + if (typeof fakes[i][method] == "function") { + fakes[i][method](); + } + } + } + + function compact(fakeCollection) { + var fakes = getFakes(fakeCollection); + var i = 0; + while (i < fakes.length) { + fakes.splice(i, 1); + } + } + + var collection = { + verify: function resolve() { + each(this, "verify"); + }, + + restore: function restore() { + each(this, "restore"); + compact(this); + }, + + verifyAndRestore: function verifyAndRestore() { + var exception; + + try { + this.verify(); + } catch (e) { + exception = e; + } + + this.restore(); + + if (exception) { + throw exception; + } + }, + + add: function add(fake) { + push.call(getFakes(this), fake); + return fake; + }, + + spy: function spy() { + return this.add(sinon.spy.apply(sinon, arguments)); + }, + + stub: function stub(object, property, value) { + if (property) { + var original = object[property]; + + if (typeof original != "function") { + if (!hasOwnProperty.call(object, property)) { + throw new TypeError("Cannot stub non-existent own property " + property); + } + + object[property] = value; + + return this.add({ + restore: function () { + object[property] = original; + } + }); + } + } + if (!property && !!object && typeof object == "object") { + var stubbedObj = sinon.stub.apply(sinon, arguments); + + for (var prop in stubbedObj) { + if (typeof stubbedObj[prop] === "function") { + this.add(stubbedObj[prop]); + } + } + + return stubbedObj; + } + + return this.add(sinon.stub.apply(sinon, arguments)); + }, + + mock: function mock() { + return this.add(sinon.mock.apply(sinon, arguments)); + }, + + inject: function inject(obj) { + var col = this; + + obj.spy = function () { + return col.spy.apply(col, arguments); + }; + + obj.stub = function () { + return col.stub.apply(col, arguments); + }; + + obj.mock = function () { + return col.mock.apply(col, arguments); + }; + + return obj; + } + }; + + if (commonJSModule) { + module.exports = collection; + } else { + sinon.collection = collection; + } +}(typeof sinon == "object" && sinon || null)); + +/*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/ +/*global module, require, window*/ +/** + * Fake timer API + * setTimeout + * setInterval + * clearTimeout + * clearInterval + * tick + * reset + * Date + * + * Inspired by jsUnitMockTimeOut from JsUnit + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +if (typeof sinon == "undefined") { + var sinon = {}; +} + +(function (global) { + var id = 1; + + function addTimer(args, recurring) { + if (args.length === 0) { + throw new Error("Function requires at least 1 parameter"); + } + + var toId = id++; + var delay = args[1] || 0; + + if (!this.timeouts) { + this.timeouts = {}; + } + + this.timeouts[toId] = { + id: toId, + func: args[0], + callAt: this.now + delay, + invokeArgs: Array.prototype.slice.call(args, 2) + }; + + if (recurring === true) { + this.timeouts[toId].interval = delay; + } + + return toId; + } + + function parseTime(str) { + if (!str) { + return 0; + } + + var strings = str.split(":"); + var l = strings.length, i = l; + var ms = 0, parsed; + + if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) { + throw new Error("tick only understands numbers and 'h:m:s'"); + } + + while (i--) { + parsed = parseInt(strings[i], 10); + + if (parsed >= 60) { + throw new Error("Invalid time " + str); + } + + ms += parsed * Math.pow(60, (l - i - 1)); + } + + return ms * 1000; + } + + function createObject(object) { + var newObject; + + if (Object.create) { + newObject = Object.create(object); + } else { + var F = function () {}; + F.prototype = object; + newObject = new F(); + } + + newObject.Date.clock = newObject; + return newObject; + } + + sinon.clock = { + now: 0, + + create: function create(now) { + var clock = createObject(this); + + if (typeof now == "number") { + clock.now = now; + } + + if (!!now && typeof now == "object") { + throw new TypeError("now should be milliseconds since UNIX epoch"); + } + + return clock; + }, + + setTimeout: function setTimeout(callback, timeout) { + return addTimer.call(this, arguments, false); + }, + + clearTimeout: function clearTimeout(timerId) { + if (!this.timeouts) { + this.timeouts = []; + } + + if (timerId in this.timeouts) { + delete this.timeouts[timerId]; + } + }, + + setInterval: function setInterval(callback, timeout) { + return addTimer.call(this, arguments, true); + }, + + clearInterval: function clearInterval(timerId) { + this.clearTimeout(timerId); + }, + + tick: function tick(ms) { + ms = typeof ms == "number" ? ms : parseTime(ms); + var tickFrom = this.now, tickTo = this.now + ms, previous = this.now; + var timer = this.firstTimerInRange(tickFrom, tickTo); + + var firstException; + while (timer && tickFrom <= tickTo) { + if (this.timeouts[timer.id]) { + tickFrom = this.now = timer.callAt; + try { + this.callTimer(timer); + } catch (e) { + firstException = firstException || e; + } + } + + timer = this.firstTimerInRange(previous, tickTo); + previous = tickFrom; + } + + this.now = tickTo; + + if (firstException) { + throw firstException; + } + + return this.now; + }, + + firstTimerInRange: function (from, to) { + var timer, smallest, originalTimer; + + for (var id in this.timeouts) { + if (this.timeouts.hasOwnProperty(id)) { + if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) { + continue; + } + + if (!smallest || this.timeouts[id].callAt < smallest) { + originalTimer = this.timeouts[id]; + smallest = this.timeouts[id].callAt; + + timer = { + func: this.timeouts[id].func, + callAt: this.timeouts[id].callAt, + interval: this.timeouts[id].interval, + id: this.timeouts[id].id, + invokeArgs: this.timeouts[id].invokeArgs + }; + } + } + } + + return timer || null; + }, + + callTimer: function (timer) { + if (typeof timer.interval == "number") { + this.timeouts[timer.id].callAt += timer.interval; + } else { + delete this.timeouts[timer.id]; + } + + try { + if (typeof timer.func == "function") { + timer.func.apply(null, timer.invokeArgs); + } else { + eval(timer.func); + } + } catch (e) { + var exception = e; + } + + if (!this.timeouts[timer.id]) { + if (exception) { + throw exception; + } + return; + } + + if (exception) { + throw exception; + } + }, + + reset: function reset() { + this.timeouts = {}; + }, + + Date: (function () { + var NativeDate = Date; + + function ClockDate(year, month, date, hour, minute, second, ms) { + // Defensive and verbose to avoid potential harm in passing + // explicit undefined when user does not pass argument + switch (arguments.length) { + case 0: + return new NativeDate(ClockDate.clock.now); + case 1: + return new NativeDate(year); + case 2: + return new NativeDate(year, month); + case 3: + return new NativeDate(year, month, date); + case 4: + return new NativeDate(year, month, date, hour); + case 5: + return new NativeDate(year, month, date, hour, minute); + case 6: + return new NativeDate(year, month, date, hour, minute, second); + default: + return new NativeDate(year, month, date, hour, minute, second, ms); + } + } + + return mirrorDateProperties(ClockDate, NativeDate); + }()) + }; + + function mirrorDateProperties(target, source) { + if (source.now) { + target.now = function now() { + return target.clock.now; + }; + } else { + delete target.now; + } + + if (source.toSource) { + target.toSource = function toSource() { + return source.toSource(); + }; + } else { + delete target.toSource; + } + + target.toString = function toString() { + return source.toString(); + }; + + target.prototype = source.prototype; + target.parse = source.parse; + target.UTC = source.UTC; + target.prototype.toUTCString = source.prototype.toUTCString; + return target; + } + + var methods = ["Date", "setTimeout", "setInterval", + "clearTimeout", "clearInterval"]; + + function restore() { + var method; + + for (var i = 0, l = this.methods.length; i < l; i++) { + method = this.methods[i]; + if (global[method].hadOwnProperty) { + global[method] = this["_" + method]; + } else { + delete global[method]; + } + } + + // Prevent multiple executions which will completely remove these props + this.methods = []; + } + + function stubGlobal(method, clock) { + clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(global, method); + clock["_" + method] = global[method]; + + if (method == "Date") { + var date = mirrorDateProperties(clock[method], global[method]); + global[method] = date; + } else { + global[method] = function () { + return clock[method].apply(clock, arguments); + }; + + for (var prop in clock[method]) { + if (clock[method].hasOwnProperty(prop)) { + global[method][prop] = clock[method][prop]; + } + } + } + + global[method].clock = clock; + } + + sinon.useFakeTimers = function useFakeTimers(now) { + var clock = sinon.clock.create(now); + clock.restore = restore; + clock.methods = Array.prototype.slice.call(arguments, + typeof now == "number" ? 1 : 0); + + if (clock.methods.length === 0) { + clock.methods = methods; + } + + for (var i = 0, l = clock.methods.length; i < l; i++) { + stubGlobal(clock.methods[i], clock); + } + + return clock; + }; +}(typeof global != "undefined" && typeof global !== "function" ? global : this)); + +sinon.timers = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval, + Date: Date +}; + +if (typeof module == "object" && typeof require == "function") { + module.exports = sinon; +} + +/*jslint eqeqeq: false, onevar: false*/ +/*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ +/** + * Minimal Event interface implementation + * + * Original implementation by Sven Fuchs: https://gist.github.com/995028 + * Modifications and tests by Christian Johansen. + * + * @author Sven Fuchs (svenfuchs@artweb-design.de) + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2011 Sven Fuchs, Christian Johansen + */ + +if (typeof sinon == "undefined") { + this.sinon = {}; +} + +(function () { + var push = [].push; + + sinon.Event = function Event(type, bubbles, cancelable, target) { + this.initEvent(type, bubbles, cancelable, target); + }; + + sinon.Event.prototype = { + initEvent: function(type, bubbles, cancelable, target) { + this.type = type; + this.bubbles = bubbles; + this.cancelable = cancelable; + this.target = target; + }, + + stopPropagation: function () {}, + + preventDefault: function () { + this.defaultPrevented = true; + } + }; + + sinon.EventTarget = { + addEventListener: function addEventListener(event, listener, useCapture) { + this.eventListeners = this.eventListeners || {}; + this.eventListeners[event] = this.eventListeners[event] || []; + push.call(this.eventListeners[event], listener); + }, + + removeEventListener: function removeEventListener(event, listener, useCapture) { + var listeners = this.eventListeners && this.eventListeners[event] || []; + + for (var i = 0, l = listeners.length; i < l; ++i) { + if (listeners[i] == listener) { + return listeners.splice(i, 1); + } + } + }, + + dispatchEvent: function dispatchEvent(event) { + var type = event.type; + var listeners = this.eventListeners && this.eventListeners[type] || []; + + for (var i = 0; i < listeners.length; i++) { + if (typeof listeners[i] == "function") { + listeners[i].call(this, event); + } else { + listeners[i].handleEvent(event); + } + } + + return !!event.defaultPrevented; + } + }; +}()); + +/** + * @depend ../../sinon.js + * @depend event.js + */ +/*jslint eqeqeq: false, onevar: false*/ +/*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ +/** + * Fake XMLHttpRequest object + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +if (typeof sinon == "undefined") { + this.sinon = {}; +} +sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; + +// wrapper for global +(function(global) { + var xhr = sinon.xhr; + xhr.GlobalXMLHttpRequest = global.XMLHttpRequest; + xhr.GlobalActiveXObject = global.ActiveXObject; + xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != "undefined"; + xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined"; + xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX + ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false; + + /*jsl:ignore*/ + var unsafeHeaders = { + "Accept-Charset": true, + "Accept-Encoding": true, + "Connection": true, + "Content-Length": true, + "Cookie": true, + "Cookie2": true, + "Content-Transfer-Encoding": true, + "Date": true, + "Expect": true, + "Host": true, + "Keep-Alive": true, + "Referer": true, + "TE": true, + "Trailer": true, + "Transfer-Encoding": true, + "Upgrade": true, + "User-Agent": true, + "Via": true + }; + /*jsl:end*/ + + function FakeXMLHttpRequest() { + this.readyState = FakeXMLHttpRequest.UNSENT; + this.requestHeaders = {}; + this.requestBody = null; + this.status = 0; + this.statusText = ""; + + var xhr = this; + + ["loadstart", "load", "abort", "loadend"].forEach(function (eventName) { + xhr.addEventListener(eventName, function (event) { + var listener = xhr["on" + eventName]; + + if (listener && typeof listener == "function") { + listener(event); + } + }); + }); + + if (typeof FakeXMLHttpRequest.onCreate == "function") { + FakeXMLHttpRequest.onCreate(this); + } + } + + function verifyState(xhr) { + if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { + throw new Error("INVALID_STATE_ERR"); + } + + if (xhr.sendFlag) { + throw new Error("INVALID_STATE_ERR"); + } + } + + // filtering to enable a white-list version of Sinon FakeXhr, + // where whitelisted requests are passed through to real XHR + function each(collection, callback) { + if (!collection) return; + for (var i = 0, l = collection.length; i < l; i += 1) { + callback(collection[i]); + } + } + function some(collection, callback) { + for (var index = 0; index < collection.length; index++) { + if(callback(collection[index]) === true) return true; + }; + return false; + } + // largest arity in XHR is 5 - XHR#open + var apply = function(obj,method,args) { + switch(args.length) { + case 0: return obj[method](); + case 1: return obj[method](args[0]); + case 2: return obj[method](args[0],args[1]); + case 3: return obj[method](args[0],args[1],args[2]); + case 4: return obj[method](args[0],args[1],args[2],args[3]); + case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]); + }; + }; + + FakeXMLHttpRequest.filters = []; + FakeXMLHttpRequest.addFilter = function(fn) { + this.filters.push(fn) + }; + var IE6Re = /MSIE 6/; + FakeXMLHttpRequest.defake = function(fakeXhr,xhrArgs) { + var xhr = new sinon.xhr.workingXHR(); + each(["open","setRequestHeader","send","abort","getResponseHeader", + "getAllResponseHeaders","addEventListener","overrideMimeType","removeEventListener"], + function(method) { + fakeXhr[method] = function() { + return apply(xhr,method,arguments); + }; + }); + + var copyAttrs = function(args) { + each(args, function(attr) { + try { + fakeXhr[attr] = xhr[attr] + } catch(e) { + if(!IE6Re.test(navigator.userAgent)) throw e; + } + }); + }; + + var stateChange = function() { + fakeXhr.readyState = xhr.readyState; + if(xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) { + copyAttrs(["status","statusText"]); + } + if(xhr.readyState >= FakeXMLHttpRequest.LOADING) { + copyAttrs(["responseText"]); + } + if(xhr.readyState === FakeXMLHttpRequest.DONE) { + copyAttrs(["responseXML"]); + } + if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr); + }; + if(xhr.addEventListener) { + for(var event in fakeXhr.eventListeners) { + if(fakeXhr.eventListeners.hasOwnProperty(event)) { + each(fakeXhr.eventListeners[event],function(handler) { + xhr.addEventListener(event, handler); + }); + } + } + xhr.addEventListener("readystatechange",stateChange); + } else { + xhr.onreadystatechange = stateChange; + } + apply(xhr,"open",xhrArgs); + }; + FakeXMLHttpRequest.useFilters = false; + + function verifyRequestSent(xhr) { + if (xhr.readyState == FakeXMLHttpRequest.DONE) { + throw new Error("Request done"); + } + } + + function verifyHeadersReceived(xhr) { + if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { + throw new Error("No headers received"); + } + } + + function verifyResponseBodyType(body) { + if (typeof body != "string") { + var error = new Error("Attempted to respond to fake XMLHttpRequest with " + + body + ", which is not a string."); + error.name = "InvalidBodyException"; + throw error; + } + } + + sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, { + async: true, + + open: function open(method, url, async, username, password) { + this.method = method; + this.url = url; + this.async = typeof async == "boolean" ? async : true; + this.username = username; + this.password = password; + this.responseText = null; + this.responseXML = null; + this.requestHeaders = {}; + this.sendFlag = false; + if(sinon.FakeXMLHttpRequest.useFilters === true) { + var xhrArgs = arguments; + var defake = some(FakeXMLHttpRequest.filters,function(filter) { + return filter.apply(this,xhrArgs) + }); + if (defake) { + return sinon.FakeXMLHttpRequest.defake(this,arguments); + } + } + this.readyStateChange(FakeXMLHttpRequest.OPENED); + }, + + readyStateChange: function readyStateChange(state) { + this.readyState = state; + + if (typeof this.onreadystatechange == "function") { + try { + this.onreadystatechange(); + } catch (e) { + sinon.logError("Fake XHR onreadystatechange handler", e); + } + } + + this.dispatchEvent(new sinon.Event("readystatechange")); + + switch (this.readyState) { + case FakeXMLHttpRequest.DONE: + this.dispatchEvent(new sinon.Event("load", false, false, this)); + this.dispatchEvent(new sinon.Event("loadend", false, false, this)); + break; + } + }, + + setRequestHeader: function setRequestHeader(header, value) { + verifyState(this); + + if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { + throw new Error("Refused to set unsafe header \"" + header + "\""); + } + + if (this.requestHeaders[header]) { + this.requestHeaders[header] += "," + value; + } else { + this.requestHeaders[header] = value; + } + }, + + // Helps testing + setResponseHeaders: function setResponseHeaders(headers) { + this.responseHeaders = {}; + + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + this.responseHeaders[header] = headers[header]; + } + } + + if (this.async) { + this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); + } else { + this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; + } + }, + + // Currently treats ALL data as a DOMString (i.e. no Document) + send: function send(data) { + verifyState(this); + + if (!/^(get|head)$/i.test(this.method)) { + if (this.requestHeaders["Content-Type"]) { + var value = this.requestHeaders["Content-Type"].split(";"); + this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8"; + } else { + this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; + } + + this.requestBody = data; + } + + this.errorFlag = false; + this.sendFlag = this.async; + this.readyStateChange(FakeXMLHttpRequest.OPENED); + + if (typeof this.onSend == "function") { + this.onSend(this); + } + + this.dispatchEvent(new sinon.Event("loadstart", false, false, this)); + }, + + abort: function abort() { + this.aborted = true; + this.responseText = null; + this.errorFlag = true; + this.requestHeaders = {}; + + if (this.readyState > sinon.FakeXMLHttpRequest.UNSENT && this.sendFlag) { + this.readyStateChange(sinon.FakeXMLHttpRequest.DONE); + this.sendFlag = false; + } + + this.readyState = sinon.FakeXMLHttpRequest.UNSENT; + + this.dispatchEvent(new sinon.Event("abort", false, false, this)); + if (typeof this.onerror === "function") { + this.onerror(); + } + }, + + getResponseHeader: function getResponseHeader(header) { + if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { + return null; + } + + if (/^Set-Cookie2?$/i.test(header)) { + return null; + } + + header = header.toLowerCase(); + + for (var h in this.responseHeaders) { + if (h.toLowerCase() == header) { + return this.responseHeaders[h]; + } + } + + return null; + }, + + getAllResponseHeaders: function getAllResponseHeaders() { + if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { + return ""; + } + + var headers = ""; + + for (var header in this.responseHeaders) { + if (this.responseHeaders.hasOwnProperty(header) && + !/^Set-Cookie2?$/i.test(header)) { + headers += header + ": " + this.responseHeaders[header] + "\r\n"; + } + } + + return headers; + }, + + setResponseBody: function setResponseBody(body) { + verifyRequestSent(this); + verifyHeadersReceived(this); + verifyResponseBodyType(body); + + var chunkSize = this.chunkSize || 10; + var index = 0; + this.responseText = ""; + + do { + if (this.async) { + this.readyStateChange(FakeXMLHttpRequest.LOADING); + } + + this.responseText += body.substring(index, index + chunkSize); + index += chunkSize; + } while (index < body.length); + + var type = this.getResponseHeader("Content-Type"); + + if (this.responseText && + (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { + try { + this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText); + } catch (e) { + // Unable to parse XML - no biggie + } + } + + if (this.async) { + this.readyStateChange(FakeXMLHttpRequest.DONE); + } else { + this.readyState = FakeXMLHttpRequest.DONE; + } + }, + + respond: function respond(status, headers, body) { + this.setResponseHeaders(headers || {}); + this.status = typeof status == "number" ? status : 200; + this.statusText = FakeXMLHttpRequest.statusCodes[this.status]; + this.setResponseBody(body || ""); + if (typeof this.onload === "function"){ + this.onload(); + } + + } + }); + + sinon.extend(FakeXMLHttpRequest, { + UNSENT: 0, + OPENED: 1, + HEADERS_RECEIVED: 2, + LOADING: 3, + DONE: 4 + }); + + // Borrowed from JSpec + FakeXMLHttpRequest.parseXML = function parseXML(text) { + var xmlDoc; + + if (typeof DOMParser != "undefined") { + var parser = new DOMParser(); + xmlDoc = parser.parseFromString(text, "text/xml"); + } else { + xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); + xmlDoc.async = "false"; + xmlDoc.loadXML(text); + } + + return xmlDoc; + }; + + FakeXMLHttpRequest.statusCodes = { + 100: "Continue", + 101: "Switching Protocols", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non-Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 300: "Multiple Choice", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 307: "Temporary Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Requested Range Not Satisfiable", + 417: "Expectation Failed", + 422: "Unprocessable Entity", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported" + }; + + sinon.useFakeXMLHttpRequest = function () { + sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) { + if (xhr.supportsXHR) { + global.XMLHttpRequest = xhr.GlobalXMLHttpRequest; + } + + if (xhr.supportsActiveX) { + global.ActiveXObject = xhr.GlobalActiveXObject; + } + + delete sinon.FakeXMLHttpRequest.restore; + + if (keepOnCreate !== true) { + delete sinon.FakeXMLHttpRequest.onCreate; + } + }; + if (xhr.supportsXHR) { + global.XMLHttpRequest = sinon.FakeXMLHttpRequest; + } + + if (xhr.supportsActiveX) { + global.ActiveXObject = function ActiveXObject(objId) { + if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) { + + return new sinon.FakeXMLHttpRequest(); + } + + return new xhr.GlobalActiveXObject(objId); + }; + } + + return sinon.FakeXMLHttpRequest; + }; + + sinon.FakeXMLHttpRequest = FakeXMLHttpRequest; +})(this); + +if (typeof module == "object" && typeof require == "function") { + module.exports = sinon; +} + +/** + * @depend fake_xml_http_request.js + */ +/*jslint eqeqeq: false, onevar: false, regexp: false, plusplus: false*/ +/*global module, require, window*/ +/** + * The Sinon "server" mimics a web server that receives requests from + * sinon.FakeXMLHttpRequest and provides an API to respond to those requests, + * both synchronously and asynchronously. To respond synchronuously, canned + * answers have to be provided upfront. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +if (typeof sinon == "undefined") { + var sinon = {}; +} + +sinon.fakeServer = (function () { + var push = [].push; + function F() {} + + function create(proto) { + F.prototype = proto; + return new F(); + } + + function responseArray(handler) { + var response = handler; + + if (Object.prototype.toString.call(handler) != "[object Array]") { + response = [200, {}, handler]; + } + + if (typeof response[2] != "string") { + throw new TypeError("Fake server response body should be string, but was " + + typeof response[2]); + } + + return response; + } + + var wloc = typeof window !== "undefined" ? window.location : {}; + var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host); + + function matchOne(response, reqMethod, reqUrl) { + var rmeth = response.method; + var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase(); + var url = response.url; + var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl)); + + return matchMethod && matchUrl; + } + + function match(response, request) { + var requestMethod = this.getHTTPMethod(request); + var requestUrl = request.url; + + if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) { + requestUrl = requestUrl.replace(rCurrLoc, ""); + } + + if (matchOne(response, this.getHTTPMethod(request), requestUrl)) { + if (typeof response.response == "function") { + var ru = response.url; + var args = [request].concat(!ru ? [] : requestUrl.match(ru).slice(1)); + return response.response.apply(response, args); + } + + return true; + } + + return false; + } + + function log(response, request) { + var str; + + str = "Request:\n" + sinon.format(request) + "\n\n"; + str += "Response:\n" + sinon.format(response) + "\n\n"; + + sinon.log(str); + } + + return { + create: function () { + var server = create(this); + this.xhr = sinon.useFakeXMLHttpRequest(); + server.requests = []; + + this.xhr.onCreate = function (xhrObj) { + server.addRequest(xhrObj); + }; + + return server; + }, + + addRequest: function addRequest(xhrObj) { + var server = this; + push.call(this.requests, xhrObj); + + xhrObj.onSend = function () { + server.handleRequest(this); + }; + + if (this.autoRespond && !this.responding) { + setTimeout(function () { + server.responding = false; + server.respond(); + }, this.autoRespondAfter || 10); + + this.responding = true; + } + }, + + getHTTPMethod: function getHTTPMethod(request) { + if (this.fakeHTTPMethods && /post/i.test(request.method)) { + var matches = (request.requestBody || "").match(/_method=([^\b;]+)/); + return !!matches ? matches[1] : request.method; + } + + return request.method; + }, + + handleRequest: function handleRequest(xhr) { + if (xhr.async) { + if (!this.queue) { + this.queue = []; + } + + push.call(this.queue, xhr); + } else { + this.processRequest(xhr); + } + }, + + respondWith: function respondWith(method, url, body) { + if (arguments.length == 1 && typeof method != "function") { + this.response = responseArray(method); + return; + } + + if (!this.responses) { this.responses = []; } + + if (arguments.length == 1) { + body = method; + url = method = null; + } + + if (arguments.length == 2) { + body = url; + url = method; + method = null; + } + + push.call(this.responses, { + method: method, + url: url, + response: typeof body == "function" ? body : responseArray(body) + }); + }, + + respond: function respond() { + if (arguments.length > 0) this.respondWith.apply(this, arguments); + var queue = this.queue || []; + var request; + + while(request = queue.shift()) { + this.processRequest(request); + } + }, + + processRequest: function processRequest(request) { + try { + if (request.aborted) { + return; + } + + var response = this.response || [404, {}, ""]; + + if (this.responses) { + for (var i = 0, l = this.responses.length; i < l; i++) { + if (match.call(this, this.responses[i], request)) { + response = this.responses[i].response; + break; + } + } + } + + if (request.readyState != 4) { + log(response, request); + + request.respond(response[0], response[1], response[2]); + } + } catch (e) { + sinon.logError("Fake server request processing", e); + } + }, + + restore: function restore() { + return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments); + } + }; +}()); + +if (typeof module == "object" && typeof require == "function") { + module.exports = sinon; +} + +/** + * @depend fake_server.js + * @depend fake_timers.js + */ +/*jslint browser: true, eqeqeq: false, onevar: false*/ +/*global sinon*/ +/** + * Add-on for sinon.fakeServer that automatically handles a fake timer along with + * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery + * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead, + * it polls the object for completion with setInterval. Dispite the direct + * motivation, there is nothing jQuery-specific in this file, so it can be used + * in any environment where the ajax implementation depends on setInterval or + * setTimeout. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function () { + function Server() {} + Server.prototype = sinon.fakeServer; + + sinon.fakeServerWithClock = new Server(); + + sinon.fakeServerWithClock.addRequest = function addRequest(xhr) { + if (xhr.async) { + if (typeof setTimeout.clock == "object") { + this.clock = setTimeout.clock; + } else { + this.clock = sinon.useFakeTimers(); + this.resetClock = true; + } + + if (!this.longestTimeout) { + var clockSetTimeout = this.clock.setTimeout; + var clockSetInterval = this.clock.setInterval; + var server = this; + + this.clock.setTimeout = function (fn, timeout) { + server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); + + return clockSetTimeout.apply(this, arguments); + }; + + this.clock.setInterval = function (fn, timeout) { + server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); + + return clockSetInterval.apply(this, arguments); + }; + } + } + + return sinon.fakeServer.addRequest.call(this, xhr); + }; + + sinon.fakeServerWithClock.respond = function respond() { + var returnVal = sinon.fakeServer.respond.apply(this, arguments); + + if (this.clock) { + this.clock.tick(this.longestTimeout || 0); + this.longestTimeout = 0; + + if (this.resetClock) { + this.clock.restore(); + this.resetClock = false; + } + } + + return returnVal; + }; + + sinon.fakeServerWithClock.restore = function restore() { + if (this.clock) { + this.clock.restore(); + } + + return sinon.fakeServer.restore.apply(this, arguments); + }; +}()); + +/** + * @depend ../sinon.js + * @depend collection.js + * @depend util/fake_timers.js + * @depend util/fake_server_with_clock.js + */ +/*jslint eqeqeq: false, onevar: false, plusplus: false*/ +/*global require, module*/ +/** + * Manages fake collections as well as fake utilities such as Sinon's + * timers and fake XHR implementation in one convenient object. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +if (typeof module == "object" && typeof require == "function") { + var sinon = require("../sinon"); + sinon.extend(sinon, require("./util/fake_timers")); +} + +(function () { + var push = [].push; + + function exposeValue(sandbox, config, key, value) { + if (!value) { + return; + } + + if (config.injectInto) { + config.injectInto[key] = value; + } else { + push.call(sandbox.args, value); + } + } + + function prepareSandboxFromConfig(config) { + var sandbox = sinon.create(sinon.sandbox); + + if (config.useFakeServer) { + if (typeof config.useFakeServer == "object") { + sandbox.serverPrototype = config.useFakeServer; + } + + sandbox.useFakeServer(); + } + + if (config.useFakeTimers) { + if (typeof config.useFakeTimers == "object") { + sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers); + } else { + sandbox.useFakeTimers(); + } + } + + return sandbox; + } + + sinon.sandbox = sinon.extend(sinon.create(sinon.collection), { + useFakeTimers: function useFakeTimers() { + this.clock = sinon.useFakeTimers.apply(sinon, arguments); + + return this.add(this.clock); + }, + + serverPrototype: sinon.fakeServer, + + useFakeServer: function useFakeServer() { + var proto = this.serverPrototype || sinon.fakeServer; + + if (!proto || !proto.create) { + return null; + } + + this.server = proto.create(); + return this.add(this.server); + }, + + inject: function (obj) { + sinon.collection.inject.call(this, obj); + + if (this.clock) { + obj.clock = this.clock; + } + + if (this.server) { + obj.server = this.server; + obj.requests = this.server.requests; + } + + return obj; + }, + + create: function (config) { + if (!config) { + return sinon.create(sinon.sandbox); + } + + var sandbox = prepareSandboxFromConfig(config); + sandbox.args = sandbox.args || []; + var prop, value, exposed = sandbox.inject({}); + + if (config.properties) { + for (var i = 0, l = config.properties.length; i < l; i++) { + prop = config.properties[i]; + value = exposed[prop] || prop == "sandbox" && sandbox; + exposeValue(sandbox, config, prop, value); + } + } else { + exposeValue(sandbox, config, "sandbox", value); + } + + return sandbox; + } + }); + + sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer; + + if (typeof module == "object" && typeof require == "function") { + module.exports = sinon.sandbox; + } +}()); + +/** + * @depend ../sinon.js + * @depend stub.js + * @depend mock.js + * @depend sandbox.js + */ +/*jslint eqeqeq: false, onevar: false, forin: true, plusplus: false*/ +/*global module, require, sinon*/ +/** + * Test function, sandboxes fakes + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function test(callback) { + var type = typeof callback; + + if (type != "function") { + throw new TypeError("sinon.test needs to wrap a test function, got " + type); + } + + return function () { + var config = sinon.getConfig(sinon.config); + config.injectInto = config.injectIntoThis && this || config.injectInto; + var sandbox = sinon.sandbox.create(config); + var exception, result; + var args = Array.prototype.slice.call(arguments).concat(sandbox.args); + + try { + result = callback.apply(this, args); + } catch (e) { + exception = e; + } + + if (typeof exception !== "undefined") { + sandbox.restore(); + throw exception; + } + else { + sandbox.verifyAndRestore(); + } + + return result; + }; + } + + test.config = { + injectIntoThis: true, + injectInto: null, + properties: ["spy", "stub", "mock", "clock", "server", "requests"], + useFakeTimers: true, + useFakeServer: true + }; + + if (commonJSModule) { + module.exports = test; + } else { + sinon.test = test; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend test.js + */ +/*jslint eqeqeq: false, onevar: false, eqeqeq: false*/ +/*global module, require, sinon*/ +/** + * Test case, sandboxes all test functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon || !Object.prototype.hasOwnProperty) { + return; + } + + function createTest(property, setUp, tearDown) { + return function () { + if (setUp) { + setUp.apply(this, arguments); + } + + var exception, result; + + try { + result = property.apply(this, arguments); + } catch (e) { + exception = e; + } + + if (tearDown) { + tearDown.apply(this, arguments); + } + + if (exception) { + throw exception; + } + + return result; + }; + } + + function testCase(tests, prefix) { + /*jsl:ignore*/ + if (!tests || typeof tests != "object") { + throw new TypeError("sinon.testCase needs an object with test functions"); + } + /*jsl:end*/ + + prefix = prefix || "test"; + var rPrefix = new RegExp("^" + prefix); + var methods = {}, testName, property, method; + var setUp = tests.setUp; + var tearDown = tests.tearDown; + + for (testName in tests) { + if (tests.hasOwnProperty(testName)) { + property = tests[testName]; + + if (/^(setUp|tearDown)$/.test(testName)) { + continue; + } + + if (typeof property == "function" && rPrefix.test(testName)) { + method = property; + + if (setUp || tearDown) { + method = createTest(property, setUp, tearDown); + } + + methods[testName] = sinon.test(method); + } else { + methods[testName] = tests[testName]; + } + } + } + + return methods; + } + + if (commonJSModule) { + module.exports = testCase; + } else { + sinon.testCase = testCase; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend stub.js + */ +/*jslint eqeqeq: false, onevar: false, nomen: false, plusplus: false*/ +/*global module, require, sinon*/ +/** + * Assertions matching the test spy retrieval interface. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon, global) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var slice = Array.prototype.slice; + var assert; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function verifyIsStub() { + var method; + + for (var i = 0, l = arguments.length; i < l; ++i) { + method = arguments[i]; + + if (!method) { + assert.fail("fake is not a spy"); + } + + if (typeof method != "function") { + assert.fail(method + " is not a function"); + } + + if (typeof method.getCall != "function") { + assert.fail(method + " is not stubbed"); + } + } + } + + function failAssertion(object, msg) { + object = object || global; + var failMethod = object.fail || assert.fail; + failMethod.call(object, msg); + } + + function mirrorPropAsAssertion(name, method, message) { + if (arguments.length == 2) { + message = method; + method = name; + } + + assert[name] = function (fake) { + verifyIsStub(fake); + + var args = slice.call(arguments, 1); + var failed = false; + + if (typeof method == "function") { + failed = !method(fake); + } else { + failed = typeof fake[method] == "function" ? + !fake[method].apply(fake, args) : !fake[method]; + } + + if (failed) { + failAssertion(this, fake.printf.apply(fake, [message].concat(args))); + } else { + assert.pass(name); + } + }; + } + + function exposedName(prefix, prop) { + return !prefix || /^fail/.test(prop) ? prop : + prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1); + }; + + assert = { + failException: "AssertError", + + fail: function fail(message) { + var error = new Error(message); + error.name = this.failException || assert.failException; + + throw error; + }, + + pass: function pass(assertion) {}, + + callOrder: function assertCallOrder() { + verifyIsStub.apply(null, arguments); + var expected = "", actual = ""; + + if (!sinon.calledInOrder(arguments)) { + try { + expected = [].join.call(arguments, ", "); + var calls = slice.call(arguments); + var i = calls.length; + while (i) { + if (!calls[--i].called) { + calls.splice(i, 1); + } + } + actual = sinon.orderByFirstCall(calls).join(", "); + } catch (e) { + // If this fails, we'll just fall back to the blank string + } + + failAssertion(this, "expected " + expected + " to be " + + "called in order but were called as " + actual); + } else { + assert.pass("callOrder"); + } + }, + + callCount: function assertCallCount(method, count) { + verifyIsStub(method); + + if (method.callCount != count) { + var msg = "expected %n to be called " + sinon.timesInWords(count) + + " but was called %c%C"; + failAssertion(this, method.printf(msg)); + } else { + assert.pass("callCount"); + } + }, + + expose: function expose(target, options) { + if (!target) { + throw new TypeError("target is null or undefined"); + } + + var o = options || {}; + var prefix = typeof o.prefix == "undefined" && "assert" || o.prefix; + var includeFail = typeof o.includeFail == "undefined" || !!o.includeFail; + + for (var method in this) { + if (method != "export" && (includeFail || !/^(fail)/.test(method))) { + target[exposedName(prefix, method)] = this[method]; + } + } + + return target; + } + }; + + mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called"); + mirrorPropAsAssertion("notCalled", function (spy) { return !spy.called; }, + "expected %n to not have been called but was called %c%C"); + mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C"); + mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C"); + mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C"); + mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t"); + mirrorPropAsAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t"); + mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new"); + mirrorPropAsAssertion("alwaysCalledWithNew", "expected %n to always be called with new"); + mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C"); + mirrorPropAsAssertion("calledWithMatch", "expected %n to be called with match %*%C"); + mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C"); + mirrorPropAsAssertion("alwaysCalledWithMatch", "expected %n to always be called with match %*%C"); + mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C"); + mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C"); + mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C"); + mirrorPropAsAssertion("neverCalledWithMatch", "expected %n to never be called with match %*%C"); + mirrorPropAsAssertion("threw", "%n did not throw exception%C"); + mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C"); + + if (commonJSModule) { + module.exports = assert; + } else { + sinon.assert = assert; + } +}(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : (typeof self != "undefined") ? self : global)); + +return sinon;}.call(typeof window != 'undefined' && window || {})); diff --git a/test/view.flot.test.js b/test/view.flot.test.js index 4bf761f6..fb458181 100644 --- a/test/view.flot.test.js +++ b/test/view.flot.test.js @@ -29,8 +29,8 @@ test('initialize', function () { // check we have updated editor with state info equal(view.elSidebar.find('.editor-type select').val(), 'lines'); equal(view.elSidebar.find('.editor-group select').val(), 'x'); - var out = _.map(view.elSidebar.find('.editor-series select'), function($el) { - return $($el).val(); + var out = _.map(view.elSidebar.find('.editor-series select'), function(el) { + return $(el).val(); }); deepEqual(out, ['y', 'z']); @@ -48,7 +48,9 @@ test('dates in graph view', function () { 'series': ['y', 'z'] } }); + view.render(); $('.fixtures').append(view.el); + view.redraw(); view.remove(); }); diff --git a/test/view.map.test.js b/test/view.map.test.js index e429f646..d867410f 100644 --- a/test/view.map.test.js +++ b/test/view.map.test.js @@ -110,10 +110,14 @@ test('GeoJSON geom field', function () { test('_getGeometryFromRecord non-GeoJSON', function () { var test = [ [{ lon: 47, lat: 53}, [47,53]], + [{ lon: -47, lat: 53}, [-47,53]], ["53.3,47.32", [47.32, 53.3]], ["53.3, 47.32", [47.32, 53.3]], ["(53.3,47.32)", [47.32, 53.3]], - [[53.3,47.32], [53.3, 47.32]] + [[53.3,47.32], [53.3, 47.32]], + ["53.3 N, 113.5 W", [-113.5, 53.3]], + ["53° 18' N, 113° 30' W", [-113.5, 53.3 ]], + ["22°45′90″S, 43°15′45″W", [-43.2625, -22.775]] ]; var view = new recline.View.Map({ model: new recline.Model.Dataset({ @@ -130,7 +134,7 @@ test('_getGeometryFromRecord non-GeoJSON', function () { }); }); -test('many markers', function () { +test('many markers and clustering', function () { var data = []; for (var i = 0; i<1000; i++) { data.push({ id: i, lon: 13+3*i, lat: 52+i/10}); @@ -150,7 +154,13 @@ test('many markers', function () { dataset.query(); + // this whole test looks a bit odd now + // we used to turn on clustering automatically at a certain level but we do not any more + equal(view.state.get('cluster'), false); + + view.state.set({cluster: true}); equal(view.state.get('cluster'), true); + view.remove(); }); @@ -162,13 +172,13 @@ test('Popup', function () { $('.fixtures').append(view.el); view.render(); - var marker = view.el.find('.leaflet-marker-icon').first(); + var marker = view.$el.find('.leaflet-marker-icon').first(); assertPresent(marker); _.values(view.features._layers)[0].fire('click'); - var popup = view.el.find('.leaflet-popup-content'); + var popup = view.$el.find('.leaflet-popup-content'); assertPresent(popup); @@ -183,7 +193,7 @@ test('Popup', function () { view.remove(); }); -test('Popup - Custom', function () { +test('Popup - Custom', function (assert) { var dataset = GeoJSONFixture.getDataset(); var view = new recline.View.Map({ model: dataset @@ -195,14 +205,14 @@ test('Popup - Custom', function () { }; view.render(); - var marker = view.el.find('.leaflet-marker-icon').first(); + var marker = view.$el.find('.leaflet-marker-icon').first(); _.values(view.features._layers)[0].fire('click'); - var popup = view.el.find('.leaflet-popup-content'); + var popup = view.$el.find('.leaflet-popup-content'); assertPresent(popup); var text = popup.html(); - ok((text.indexOf('

    1

    y: 2') != -1)) + assert.htmlEqual(text, '

    1

    y: 2'); view.remove(); }); @@ -213,7 +223,7 @@ test('geoJsonLayerOptions', function () { model: dataset }); $('.fixtures').append(view.el); - view.geoJsonLayerOptions.point + view.geoJsonLayerOptions.point view.geoJsonLayerOptions.pointToLayer = function(feature, latlng) { var marker = new L.CircleMarker(latlng, { radius: 8 } ); marker.bindPopup(feature.properties.popupContent); diff --git a/test/view.multiview.test.js b/test/view.multiview.test.js index db8cbb53..38092fef 100644 --- a/test/view.multiview.test.js +++ b/test/view.multiview.test.js @@ -12,7 +12,7 @@ test('basic explorer functionality', function () { }); var $explorer = $el.find('.recline-data-explorer'); equal($explorer.length, 1); - $el.remove(); + explorer.remove(); }); test('get State', function () { @@ -34,7 +34,7 @@ test('get State', function () { equal(state.get('backend'), 'memory'); equal(state.get('dataset').url, 'xyz'); ok(state.get('url') === url); - $el.remove(); + explorer.remove(); }); test('initialize state', function () { @@ -60,16 +60,16 @@ test('initialize state', function () { ok(explorer.state.get('currentView'), 'graph'); // check the correct view is visible - var css = explorer.el.find('.navigation a[data-view="graph"]').attr('class').split(' '); + var css = explorer.$el.find('.navigation a[data-view="graph"]').attr('class').split(' '); ok(_.contains(css, 'active'), css); - var css = explorer.el.find('.navigation a[data-view="grid"]').attr('class').split(' '); + var css = explorer.$el.find('.navigation a[data-view="grid"]').attr('class').split(' '); ok(!(_.contains(css, 'active')), css); // check pass through of view config deepEqual(explorer.state.get('view-grid')['hiddenFields'], ['x']); equal(explorer.state.get('view-map')['lonField'], 'lon1'); - $el.remove(); + explorer.remove(); }); test('restore (from serialized state)', function() { @@ -83,6 +83,9 @@ test('restore (from serialized state)', function() { var out = explorerNew.state.toJSON(); equal(out.backend, state.backend); + explorer.remove(); + explorerNew.remove(); + var dataset = new recline.Model.Dataset({ url: 'http://data.london.gov.uk/datafiles/transport/tfl_passengers.csv', format: 'csv', @@ -95,6 +98,9 @@ test('restore (from serialized state)', function() { var explorerNew = recline.View.MultiView.restore(state); equal(explorerNew.model.get('backend'), 'dataproxy'); equal(explorerNew.model.get('format'), 'csv'); + + explorer.remove(); + explorerNew.remove(); }); })(this.jQuery); diff --git a/test/view.slickgrid.test.js b/test/view.slickgrid.test.js index 0ff639b1..597461cd 100644 --- a/test/view.slickgrid.test.js +++ b/test/view.slickgrid.test.js @@ -83,7 +83,7 @@ test('editable', function () { $('.fixtures .test-datatable').append(view.el); view.render(); - view.grid.init(); + view.show(); var new_item = {lon: "foo", id: 1, z: 23, date: "12", y: 3, country: 'FR'}; @@ -92,13 +92,15 @@ test('editable', function () { }); // Be sure a cell change triggers a change of the model - e = new Slick.EventData(); - return view.grid.onCellChange.notify({ - row: 1, - cell: 0, - item: new_item, - grid: view.grid - }, e, view.grid); + e = new Slick.EventData(); + view.grid.onCellChange.notify({ + row: 1, + cell: 0, + item: new_item, + grid: view.grid + }, e, view.grid); + + view.remove(); }); test('update', function() { @@ -124,9 +126,11 @@ test('update', function() { // Change the model at row 1 dataset.records.at(1).set('z', zbefore + 1); equal( zbefore + 1, view.grid.getData().getItem(1)['z']); + + view.remove(); }); -test('renderers', function () { +test('renderers', function (assert) { var dataset = Fixture.getDataset(); dataset.fields.get('country').renderer = function(val, field, doc){ @@ -148,7 +152,7 @@ test('renderers', function () { view.grid.init(); equal($(view.grid.getCellNode(0,view.grid.getColumnIndex('country'))).text(),'Country: DE'); - equal($(view.grid.getCellNode(0,view.grid.getColumnIndex('country'))).html(),'Country: DE'); + assert.htmlEqual($(view.grid.getCellNode(0,view.grid.getColumnIndex('country'))).html(),'Country: DE'); equal($(view.grid.getCellNode(0,view.grid.getColumnIndex('computed'))).text(),'10'); view.remove(); }); diff --git a/test/view.timeline.test.js b/test/view.timeline.test.js index 4f6faf48..12850279 100644 --- a/test/view.timeline.test.js +++ b/test/view.timeline.test.js @@ -19,13 +19,13 @@ test('extract dates and timelineJSON', function () { 'headline': '', 'date': [ { - 'startDate': new Date('2012-03-20'), + 'startDate': '2012,03,20', 'endDate': null, 'headline': '1', 'text': '
    Date: 2012-03-20
    title: 1
    ' }, { - 'startDate': new Date('2012-03-25'), + 'startDate': '2012,03,25', 'endDate': null, 'headline': '2', 'text': '
    Date: 2012-03-25
    title: 2
    ' @@ -37,17 +37,22 @@ test('extract dates and timelineJSON', function () { }); test('render etc', function () { - var dataset = Fixture.getDataset(); + var dataset = new recline.Model.Dataset({ + records: [ + {'Date': '-10000', 'title': 'first'}, + {'Date': '2012-03-20', 'title': 'second'}, + {'Date': '2012-03-25', 'title': 'third'} + ] + }); var view = new recline.View.Timeline({ model: dataset }); - view.render(); $('.fixtures').append(view.el); - view._initTimeline(); - assertPresent('.vmm-timeline', view.el); + view.render(); + assertPresent('.vco-timeline', view.el); assertPresent('.timenav', view.el); assertPresent('.timenav', view.el); - equal(view.el.find('.marker.active h4').text(), '2011'); + equal(view.$el.find('.marker.active h3').text(), 'first'); view.remove(); }); @@ -57,18 +62,22 @@ test('_parseDate', function () { model: dataset }); var testData = [ - [ '1st August 1914', '1914-08-01T00:00:00.000Z' ], - [ '1 August 1914', '1914-08-01T00:00:00.000Z' ], - [ 'August 1st 1914', '1914-08-01T00:00:00.000Z' ], - [ '1914-08-01', '1914-08-01T00:00:00.000Z' ], - [ '1914-08-01T08:00', '1914-08-01T08:00:00.000Z' ], - [ 'afdaf afdaf', null ], + // [ '1st August 1914', '1914-08-01T00:00:00.000Z' ], + // [ '1 August 1914', '1914-08-01T00:00:00.000Z' ], + // [ 'August 1st 1914', '1914-08-01T00:00:00.000Z' ], + [ '1914-08-01', '1914,08,01' ], + [ '1914-08-01T08:00', '1914,08,01,08,00' ], + [ '03-20-1914', '03/20/1914' ], + [ '20/03/1914', '20/03/1914' ], + [ '-10000', '-10000' ], [ null, null ] ]; _.each(testData, function(item) { var out = view._parseDate(item[0]); - if (out) out = out.toISOString(); equal(out, item[1]); }); + view.state.set({'nonUSDates': true}); + var out = view._parseDate('20/03/1914'); + equal(out, '03/20/1914'); }); diff --git a/test/widget.filtereditor.test.js b/test/widget.filtereditor.test.js index c1acbbff..5faa0189 100644 --- a/test/widget.filtereditor.test.js +++ b/test/widget.filtereditor.test.js @@ -7,10 +7,10 @@ test('basics', function () { }); $('.fixtures').append(view.el); assertPresent('.js-add-filter', view.elSidebar); - var $addForm = view.el.find('form.js-add'); + var $addForm = view.$el.find('form.js-add'); ok(!$addForm.is(":visible")); - view.el.find('.js-add-filter').click(); - ok(!view.el.find('.js-add-filter').is(":visible")); + view.$el.find('.js-add-filter').click(); + ok(!view.$el.find('.js-add-filter').is(":visible")); ok($addForm.is(":visible")); // submit the form @@ -19,7 +19,7 @@ test('basics', function () { // now check we have new filter ok(!$addForm.is(":visible")); - $editForm = view.el.find('form.js-edit'); + $editForm = view.$el.find('form.js-edit'); equal($editForm.find('.filter-term').length, 1) equal(dataset.queryState.attributes.filters[0].field, 'country'); @@ -30,30 +30,30 @@ test('basics', function () { equal(dataset.records.length, 3); // now set a second range filter ... - view.el.find('.js-add-filter').click(); - var $addForm = view.el.find('form.js-add'); + view.$el.find('.js-add-filter').click(); + var $addForm = view.$el.find('form.js-add'); $addForm.find('select.fields').val('x'); $addForm.find('select.filterType').val('range'); $addForm.submit(); - $editForm = view.el.find('form.js-edit'); + $editForm = view.$el.find('form.js-edit'); $editForm.find('.filter-range input').first().val('2'); $editForm.find('.filter-range input').last().val('4'); $editForm.submit(); equal(dataset.queryState.attributes.filters[0].term, 'UK'); - equal(dataset.queryState.attributes.filters[1].start, 2); + equal(dataset.queryState.attributes.filters[1].from, 2); equal(dataset.records.length, 2); // now remove filter - $editForm = view.el.find('form.js-edit'); + $editForm = view.$el.find('form.js-edit'); $editForm.find('.js-remove-filter').last().click(); - $editForm = view.el.find('form.js-edit'); + $editForm = view.$el.find('form.js-edit'); equal($editForm.find('.filter').length, 1) equal(dataset.records.length, 3); - $editForm = view.el.find('form.js-edit'); + $editForm = view.$el.find('form.js-edit'); $editForm.find('.js-remove-filter').last().click(); - $editForm = view.el.find('form.js-edit'); + $editForm = view.$el.find('form.js-edit'); equal($editForm.find('.filter').length, 0) equal(dataset.records.length, 6); @@ -68,18 +68,18 @@ test('add 2 filters of same type', function () { $('.fixtures').append(view.el); // add 2 term filters - var $addForm = view.el.find('form.js-add'); - view.el.find('.js-add-filter').click(); + var $addForm = view.$el.find('form.js-add'); + view.$el.find('.js-add-filter').click(); $addForm.find('select.fields').val('country'); $addForm.submit(); - var $addForm = view.el.find('form.js-add'); - view.el.find('.js-add-filter').click(); + var $addForm = view.$el.find('form.js-add'); + view.$el.find('.js-add-filter').click(); $addForm.find('select.fields').val('id'); $addForm.submit(); var fields = []; - view.el.find('form.js-edit .filter-term input').each(function(idx, item) { + view.$el.find('form.js-edit .filter-term input').each(function(idx, item) { fields.push($(item).attr('data-filter-field')); }); deepEqual(fields, ['country', 'id']); @@ -94,14 +94,14 @@ test('geo_distance', function () { }); $('.fixtures').append(view.el); - var $addForm = view.el.find('form.js-add'); + var $addForm = view.$el.find('form.js-add'); // submit the form $addForm.find('select.filterType').val('geo_distance'); $addForm.find('select.fields').val('lon'); $addForm.submit(); // now check we have new filter - $editForm = view.el.find('form.js-edit'); + $editForm = view.$el.find('form.js-edit'); equal($editForm.find('.filter-geo_distance').length, 1) deepEqual(_.sortBy(_.keys(dataset.queryState.attributes.filters[0]),_.identity), ["distance", "field", "point", "type", "unit"]); diff --git a/test/widget.valuefilter.test.js b/test/widget.valuefilter.test.js index 6b8a86a1..09fea2ed 100644 --- a/test/widget.valuefilter.test.js +++ b/test/widget.valuefilter.test.js @@ -7,10 +7,10 @@ test('basics', function () { }); $('.fixtures').append(view.el); assertPresent('.js-add-filter', view.elSidebar); - var $addForm = view.el.find('form.js-add'); + var $addForm = view.$el.find('form.js-add'); ok(!$addForm.is(":visible")); - view.el.find('.js-add-filter').click(); - ok(!view.el.find('.js-add-filter').is(":visible")); + view.$el.find('.js-add-filter').click(); + ok(!view.$el.find('.js-add-filter').is(":visible")); ok($addForm.is(":visible")); // submit the form @@ -19,7 +19,7 @@ test('basics', function () { // now check we have new filter ok(!$addForm.is(":visible")); - $editForm = view.el.find('form.js-edit'); + $editForm = view.$el.find('form.js-edit'); equal($editForm.find('.filter-term').length, 1); equal(dataset.queryState.attributes.filters[0].field, 'country'); @@ -30,9 +30,9 @@ test('basics', function () { equal(dataset.records.length, 3); // now remove filter - $editForm = view.el.find('form.js-edit'); + $editForm = view.$el.find('form.js-edit'); $editForm.find('.js-remove-filter').last().click(); - $editForm = view.el.find('form.js-edit'); + $editForm = view.$el.find('form.js-edit'); equal($editForm.find('.filter').length, 0); equal(dataset.records.length, 6); @@ -47,18 +47,19 @@ test('add 2 filters', function () { $('.fixtures').append(view.el); // add 2 term filters - var $addForm = view.el.find('form.js-add'); - view.el.find('.js-add-filter').click(); + var $addForm = view.$el.find('form.js-add'); + view.$el.find('.js-add-filter').click(); + $addForm.find('select.fields').val('country'); $addForm.submit(); - $addForm = view.el.find('form.js-add'); - view.el.find('.js-add-filter').click(); + $addForm = view.$el.find('form.js-add'); + view.$el.find('.js-add-filter').click(); $addForm.find('select.fields').val('id'); $addForm.submit(); var fields = []; - view.el.find('form.js-edit .filter-term input').each(function(idx, item) { + view.$el.find('form.js-edit .filter-term input').each(function(idx, item) { fields.push($(item).attr('data-filter-field')); }); deepEqual(fields, ['country', 'id']); diff --git a/vendor/backbone/0.9.2/backbone-min.js b/vendor/backbone/0.9.2/backbone-min.js deleted file mode 100644 index c1c0d4ff..00000000 --- a/vendor/backbone/0.9.2/backbone-min.js +++ /dev/null @@ -1,38 +0,0 @@ -// Backbone.js 0.9.2 - -// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Backbone may be freely distributed under the MIT license. -// For all details and documentation: -// http://backbonejs.org -(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks= -{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g= -z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent= -{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null== -b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent: -b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)}; -a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error, -h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t(); -return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending= -{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length|| -!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator); -this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c=b))this.iframe=i('"; - VMM.ExternalAPI.vimeo.get(m.id); - // DAILYMOTION - } else if (m.type == "dailymotion") { - mediaElem = "
    "; - // TWITTER - } else if (m.type == "twitter"){ - mediaElem = ""; - isTextMedia = true; - VMM.ExternalAPI.twitter.prettyHTML(m.id); - // TWITTER - } else if (m.type == "twitter-ready") { - isTextMedia = true; - mediaElem = m.id; - // SOUNDCLOUD - } else if (m.type == "soundcloud") { - _id = "soundcloud_" + VMM.Util.unique_ID(5); - mediaElem = "

    Loading Sound

    "; - VMM.ExternalAPI.soundcloud.get(m.id, _id); - // GOOGLE MAPS - } else if (m.type == "google-map") { - _id = "googlemap_" + VMM.Util.unique_ID(7); - mediaElem = "

    Loading Map

    "; - VMM.ExternalAPI.googlemaps.get(m.id, _id); - // WIKIPEDIA - } else if (m.type == "wikipedia") { - _id = "wikipedia_" + VMM.Util.unique_ID(7); - mediaElem = "

    Loading Wikipedia

    "; - isTextMedia = true; - VMM.ExternalAPI.wikipedia.get(m.id, _id); - // UNKNOWN - } else if (m.type == "unknown") { - trace("NO KNOWN MEDIA TYPE FOUND TRYING TO JUST PLACE THE HTML"); - isTextMedia = true; - mediaElem = "
    " + VMM.Util.properQuotes(m.id) + "
    "; - // WEBSITE - } else if (m.type == "website") { - mediaElem = "
    "; - //mediaElem = "" + ""; - // NO MATCH - } else { - trace("NO KNOWN MEDIA TYPE FOUND"); - trace(m.type); - } - - // WRAP THE MEDIA ELEMENT - mediaElem = "
    " + mediaElem + creditElem + captionElem + "
    "; - // RETURN - if (isTextMedia) { - return "
    " + mediaElem + "
    "; - } else { - return "
    " + mediaElem + "
    "; - } - - /* - if (_return) { - if (isTextMedia) { - return "
    " + mediaElem + "
    "; - } else { - return "
    " + mediaElem + "
    "; - } - } else { - VMM.appendElement($mediacontainer, mediaElem); - VMM.appendElement($mediacontainer, creditElem); - VMM.appendElement($mediacontainer, captionElem); - } - */ - }; - - }, - - }).init(); -} - -/*********************************************** - Begin VMM.MediaType.js -***********************************************/ - -/* MediaType -================================================== */ -if(typeof VMM != 'undefined' && typeof VMM.MediaType == 'undefined') { - - //VMM.mediaType.youtube(d); //should return a true or false - // VMM.MediaType(url); //returns an object with .type and .id - - VMM.MediaType = function(d) { - var success = false; - var media = {}; - - if (d.match("div class='twitter'")) { - media.type = "twitter-ready"; - media.id = d; - success = true; - } else if (d.match('(www.)?youtube|youtu\.be')) { - if (d.match('v=')) { - media.id = VMM.Util.getUrlVars(d)["v"]; - } else { - media.id = d.split(/v\/|v=|youtu\.be\//)[1].split(/[?&]/)[0]; - } - media.type = "youtube"; - success = true; - } else if (d.match('(player.)?vimeo\.com')) { - media.type = "vimeo"; - media.id = d.split(/video\/|\/\/vimeo\.com\//)[1].split(/[?&]/)[0];; - success = true; - } else if (d.match('(www.)?dailymotion\.com')) { - media.id = d.split(/video\/|\/\/dailymotion\.com\//)[1]; - media.type = "dailymotion"; - success = true; - } else if (d.match('(player.)?soundcloud\.com')) { - media.type = "soundcloud"; - media.id = d; - success = true; - } else if (d.match('(www.)?twitter\.com')) { - if (d.match("status\/")) { - media.id = d.split("status\/")[1]; - } else if (d.match("statuses\/")) { - media.id = d.split("statuses\/")[1]; - } else { - media.id = ""; - } - media.type = "twitter"; - success = true; - } else if (d.match("maps.google") && !d.match("staticmap")) { - media.type = "google-map"; - media.id = d.split(/src=['|"][^'|"]*?['|"]/gi); - success = true; - } else if (d.match("flickr.com/photos")) { - media.type = "flickr"; - media.id = d.split("photos\/")[1].split("/")[1]; - media.link = d; - success = true; - } else if (d.match(/jpg|jpeg|png|gif/i) || d.match("staticmap")) { - media.type = "image"; - media.id = d; - success = true; - } else if (VMM.FileExtention.googleDocType(d)) { - media.type = "googledoc"; - media.id = d; - success = true; - } else if (d.match('(www.)?wikipedia\.org')) { - media.type = "wikipedia"; - //media.id = d.split("wiki\/")[1]; - var wiki_id = d.split("wiki\/")[1].split("#")[0].replace("_", " "); - media.id = VMM.Util.toTitleCase(wiki_id).replace(" ", "%20"); - success = true; - } else if (d.indexOf('http://') == 0) { - media.type = "website"; - media.id = d; - success = true; - } else { - trace("unknown media"); - media.type = "unknown"; - media.id = d; - success = true; - } - - if (success) { - return media; - } else { - trace("No valid media id detected"); - trace(d); - } - return false; - } -} - -/*********************************************** - Begin VMM.Media.js -***********************************************/ - -/* Media -================================================== */ -if(typeof VMM != 'undefined' && typeof VMM.Media == 'undefined') { - - // something = new VMM.Media(parent, w, h, {thedata}); - VMM.Media = function(parent, w, h, thedata) { - - /* PRIVATE VARS - ================================================== */ - var data = {}; // HOLDS DATA - - var _valid = false; - - var config = { - width: 720, - height: 400, - content_width: 720, - content_height: 400, - ease: "easeInOutExpo", - duration: 1000, - spacing: 15 - }; - /* ELEMENTS - ================================================== */ - var $media = ""; - var $container = ""; - var $mediacontainer = ""; - var $mediaelement = ""; - var layout = parent; // expecting media div - - if (w != null && w != "") {config.width = w}; - if (h != null && h != "") {config.height = h}; - /* - if (typeof thedata != "undefined") { - data = thedata; - this.init(data); - } - */ - /* PUBLIC FUNCTIONS - ================================================== */ - this.init = function(d) { - if(typeof d != 'undefined') { - this.setData(d); - } else { - trace("WAITING ON DATA"); - } - }; - - var build = function(media, caption, credit) { - - $media = VMM.appendAndGetElement(layout, "
    ", "media"); - $container = VMM.appendAndGetElement($media, "
    ", "container"); - $mediacontainer = VMM.appendAndGetElement($container, "
    ", "media-container"); - - - if (data.media != null && data.media != "") { - - _valid = true; - var m = {}; - - m = VMM.MediaType(data.media); //returns an object with .type and .id - - if (m.type == "image") { - VMM.appendElement($mediacontainer, ""); - } else if (m.type == "youtube") { - VMM.appendElement($mediacontainer, ""; - } else { - mediaElem = ""; - } - VMM.attachElement("#"+doc.id, mediaElem); - }, - - pushQue: function() { - for(var i = 0; i < VMM.master_config.googledocs.que.length; i++) { - VMM.ExternalAPI.googledocs.create(VMM.master_config.googledocs.que[i]); - } - VMM.master_config.googledocs.que = []; - } - - }, - - flickr: { - - get: function(mid, id) { - var api_key; - if (VMM.master_config.Timeline.api_keys.flickr != "") { - api_key = VMM.master_config.Timeline.api_keys.flickr; - } else { - api_key = Aes.Ctr.decrypt(VMM.master_config.api_keys_master.flickr, VMM.master_config.vp, 256) - } - var the_url = "http://api.flickr.com/services/rest/?method=flickr.photos.getSizes&api_key=" + api_key + "&photo_id=" + mid + "&format=json&jsoncallback=?"; - VMM.getJSON(the_url, VMM.ExternalAPI.flickr.create); - }, - - create: function(d) { - var flickr_id = d.sizes.size[0].url.split("photos\/")[1].split("/")[1]; - var id = "flickr_" + flickr_id; - var flickr_large_id = id + "_large"; - var flickr_thumb_id = id + "_thumb"; - var flickr_img_size, flickr_img_thumb, flickr_size_found = false; - var flickr_best_size = "Large"; - - flickr_best_size = VMM.ExternalAPI.flickr.sizes(VMM.master_config.sizes.api.height); - - for(var i = 0; i < d.sizes.size.length; i++) { - if (d.sizes.size[i].label == flickr_best_size) { - flickr_size_found = true; - flickr_img_size = d.sizes.size[i].source; - } - } - if (!flickr_size_found) { - flickr_img_size = d.sizes.size[d.sizes.size.length - 1].source; - } - - flickr_img_thumb = d.sizes.size[0].source; - VMM.Lib.attr("#"+flickr_large_id, "src", flickr_img_size); - VMM.attachElement("#"+flickr_thumb_id, ""); - }, - - sizes: function(s) { - var _size = ""; - if (s <= 75) { - _size = "Thumbnail"; - } else if (s <= 180) { - _size = "Small"; - } else if (s <= 240) { - _size = "Small 320"; - } else if (s <= 375) { - _size = "Medium"; - } else if (s <= 480) { - _size = "Medium 640"; - } else if (s <= 600) { - _size = "Medium 800"; - } else { - _size = "Large"; - } - - return _size; - } - - }, - - soundcloud: { - - get: function(url, id) { - var sound = {url: url, id: id}; - VMM.master_config.soundcloud.que.push(sound); - VMM.master_config.soundcloud.active = true; - }, - - create: function(sound) { - var the_url = "http://soundcloud.com/oembed?url=" + sound.url + "&format=js&callback=?"; - VMM.getJSON(the_url, function(d) { - VMM.attachElement("#"+sound.id, d.html); - }); - }, - - pushQue: function() { - for(var i = 0; i < VMM.master_config.soundcloud.que.length; i++) { - VMM.ExternalAPI.soundcloud.create(VMM.master_config.soundcloud.que[i]); - } - VMM.master_config.soundcloud.que = []; - }, - - }, - - wikipedia: { - - get: function(url, id) { - var api_obj = {url: url, id: id}; - VMM.master_config.wikipedia.que.push(api_obj); - VMM.master_config.wikipedia.active = true; - }, - - create: function(api_obj) { - var the_url = "http://en.wikipedia.org/w/api.php?action=query&prop=extracts&redirects=&titles=" + api_obj.url + "&exintro=1&format=json&callback=?"; - - if ( VMM.Browser.browser == "Explorer" && parseInt(VMM.Browser.version, 10) >= 7 && window.XDomainRequest) { - var temp_text = "

    " + api_obj.url + "

    "; - temp_text += "
    From Wikipedia, the free encyclopedia"; - temp_text += "

    Wikipedia entry unable to load using Internet Explorer 8 or below.

    "; - VMM.attachElement("#"+api_obj.id, temp_text ); - } - - VMM.getJSON(the_url, function(d) { - if (d.query) { - var wiki_extract = VMM.Util.getObjectAttributeByIndex(d.query.pages, 0).extract; - var wiki_title = VMM.Util.getObjectAttributeByIndex(d.query.pages, 0).title; - var _wiki = ""; - var wiki_text = ""; - var wiki_text_array = wiki_extract.split("

    "); - var wiki_number_of_paragraphs = 1; - - for(var i = 0; i < wiki_text_array.length; i++) { - if (i+1 <= wiki_number_of_paragraphs && i+1 < wiki_text_array.length) { - wiki_text += "

    " + wiki_text_array[i+1]; - } - } - - _wiki = "

    " + wiki_title + "

    "; - _wiki += "
    " + VMM.master_config.language.messages.wikipedia + ""; - _wiki += VMM.Util.linkify_wikipedia(wiki_text); - - if (wiki_extract.match("REDIRECT")) { - - } else { - VMM.attachElement("#"+api_obj.id, _wiki ); - } - } - - }); - - }, - - pushQue: function() { - trace("WIKIPEDIA PUSH QUE"); - for(var i = 0; i < VMM.master_config.wikipedia.que.length; i++) { - VMM.ExternalAPI.wikipedia.create(VMM.master_config.wikipedia.que[i]); - } - VMM.master_config.wikipedia.que = []; - }, - - }, - - youtube: { - - get: function(id) { - var url = "http://gdata.youtube.com/feeds/api/videos/" + id + "?v=2&alt=jsonc&callback=?"; - - if (VMM.master_config.youtube.active) { - VMM.master_config.youtube.que.push(id); - } else { - VMM.master_config.youtube.que.push(id); - if (!VMM.master_config.youtube.api_loaded) { - VMM.LoadLib.js('http://www.youtube.com/player_api', function() { - trace("YouTube API Library Loaded"); - }); - } - } - - // THUMBNAIL - VMM.getJSON(url, VMM.ExternalAPI.youtube.createThumb); - }, - - create: function(id) { - - var p = { - active: false, - player: {}, - name: 'youtube_'+id, - playing: false - }; - - p.player['youtube_'+id] = new YT.Player('youtube_'+id, { - height: '390', - width: '640', - playerVars: { - enablejsapi: 1, - color: 'white', - showinfo: 0, - theme: 'light', - rel: 0 - }, - videoId: id, - events: { - 'onReady': VMM.ExternalAPI.youtube.onPlayerReady, - 'onStateChange': VMM.ExternalAPI.youtube.onStateChange - } - }); - - VMM.master_config.youtube.array.push(p); - }, - - createThumb: function(d) { - trace(d.data.id); - trace(d.data.thumbnail.sqDefault); - var thumb_id = "youtube_" + d.data.id + "_thumb"; - VMM.attachElement("#" + thumb_id, ""); - }, - - pushQue: function() { - for(var i = 0; i < VMM.master_config.youtube.que.length; i++) { - VMM.ExternalAPI.youtube.create(VMM.master_config.youtube.que[i]); - } - VMM.master_config.youtube.que = []; - }, - - onAPIReady: function() { - VMM.master_config.youtube.active = true; - VMM.ExternalAPI.youtube.pushQue(); - }, - - stopPlayers: function() { - for(var i = 0; i < VMM.master_config.youtube.array.length; i++) { - if (VMM.master_config.youtube.array[i].playing) { - var the_name = VMM.master_config.youtube.array[i].name; - VMM.master_config.youtube.array[i].player[the_name].stopVideo(); - } - } - }, - - onStateChange: function(e) { - for(var i = 0; i < VMM.master_config.youtube.array.length; i++) { - var the_name = VMM.master_config.youtube.array[i].name; - if (VMM.master_config.youtube.array[i].player[the_name] == e.target) { - if (e.data == YT.PlayerState.PLAYING) { - VMM.master_config.youtube.array[i].playing = true; - } - } - } - }, - - onPlayerReady: function(e) { - - } - - - }, - - vimeo: { - - get: function(id) { - VMM.master_config.vimeo.que.push(id); - VMM.master_config.vimeo.active = true; - }, - - create: function(d) { - trace("VIMEO CREATE"); - // THUMBNAIL - var url = "http://vimeo.com/api/v2/video/" + d + ".json"; - VMM.getJSON(url, VMM.ExternalAPI.vimeo.createThumb); - }, - - createThumb: function(d) { - trace("VIMEO CREATE THUMB"); - var thumb_id = "vimeo_" + d[0].id + "_thumb"; - VMM.attachElement("#" + thumb_id, ""); - }, - - pushQue: function() { - for(var i = 0; i < VMM.master_config.vimeo.que.length; i++) { - VMM.ExternalAPI.vimeo.create(VMM.master_config.vimeo.que[i]); - } - VMM.master_config.vimeo.que = []; - } - - } - - } - -} - -/* YOUTUBE API READY - Can't find a way to customize this callback and keep it in the VMM namespace - Youtube wants it to be this function. -================================================== */ -function onYouTubePlayerAPIReady() { - trace("GLOBAL YOUTUBE API CALLED") - VMM.ExternalAPI.youtube.onAPIReady(); -} - -/*********************************************** - Begin VMM.TouchSlider.js -***********************************************/ - -/* TOUCH SLIDER -================================================== */ -if(typeof VMM != 'undefined' && typeof VMM.TouchSlider == 'undefined') { - - // VMM.TouchSlider.createSlidePanel(touch_object, move_object, w, padding, vertical, h) ; - VMM.TouchSlider = { - createPanel: function(touch_object, move_object, w, padding, vertical, h) { - VMM.TouchSlider.vertical = false; - VMM.TouchSlider.vertical = vertical; - - var x = padding; - VMM.TouchSlider.width = w; - VMM.TouchSlider.height = h; - VMM.TouchSlider.makeTouchable(touch_object, move_object); - /* - if (sticky != null && sticky != "") { - VMM.TouchSlider.sticky = sticky; - } else { - VMM.TouchSlider.sticky = false; - } - */ - // VMM.TouchSlider.sticky = sticky; - - }, - - removePanel: function(touch_object) { - VMM.unbindEvent(touch_object, VMM.TouchSlider.onTouchStart, "touchstart"); - VMM.unbindEvent(touch_object, VMM.TouchSlider.onTouchMove, "touchmove"); - VMM.unbindEvent(touch_object, VMM.TouchSlider.onTouchEnd, "touchend"); - }, - - makeTouchable: function(touch_object, move_object) { - VMM.bindEvent(touch_object, VMM.TouchSlider.onTouchStart, "touchstart", {element: move_object}); - VMM.bindEvent(touch_object, VMM.TouchSlider.onTouchMove, "touchmove", {element: move_object}); - VMM.bindEvent(touch_object, VMM.TouchSlider.onTouchEnd, "touchend", {element: move_object}); - }, - onTouchStart: function(e) { - VMM.TouchSlider.touchStart(e.data.element, e); - e.stopPropagation(); - return true; - }, - onTouchEnd: function(e) { - e.stopPropagation(); - - if (VMM.TouchSlider.sliding) { - VMM.TouchSlider.sliding = false; - VMM.TouchSlider.touchEnd(e.data.element, e); - return false; - } else { - return true; - } - - }, - onTouchMove: function(e) { - VMM.TouchSlider.touchMove(e.data.element, e); - e.preventDefault(); - e.stopPropagation(); - return false; - }, - getLeft: function(elem) { - return parseInt(VMM.Lib.css(elem, 'left').substring(0, VMM.Lib.css(elem, 'left').length - 2), 10); - }, - getTop: function(elem) { - return parseInt(VMM.Lib.css(elem, 'top').substring(0, VMM.Lib.css(elem, 'top').length - 2), 10); - }, - touchStart: function(elem, e) { - - VMM.Lib.css(elem, '-webkit-transition-duration', '0'); - - VMM.TouchSlider.startX = e.originalEvent.touches[0].screenX; - VMM.TouchSlider.startY = e.originalEvent.touches[0].screenY; - - VMM.TouchSlider.startLeft = VMM.TouchSlider.getLeft(elem); - VMM.TouchSlider.startTop = VMM.TouchSlider.getTop(elem); - - VMM.TouchSlider.touchStartTime = new Date().getTime(); - - }, - touchEnd: function(elem, e) { - if (VMM.TouchSlider.getLeft(elem) > 0) { - - //This means they dragged to the right past the first item - - if (VMM.TouchSlider.vertical) { - VMM.Lib.animate(elem, 1000, "", {"top": 0}); - } else { - VMM.Lib.animate(elem, 1000, "", {"left": 0}); - } - - VMM.TouchSlider.startX = null; - VMM.TouchSlider.startY = null; - - VMM.fireEvent(elem, "TOUCHUPDATE", [0]); - - } else { - //This means they were just dragging within the bounds of the grid and we just need to handle the momentum and snap to the grid. - VMM.TouchSlider.slideMomentum(elem, e); - } - }, - slideMomentum: function(elem, e) { - var slideAdjust = (new Date().getTime() - VMM.TouchSlider.touchStartTime) * 10; - var timeAdjust = slideAdjust; - - var left = VMM.TouchSlider.getLeft(elem); - var top = VMM.TouchSlider.getTop(elem); - - var changeX = 6000 * (Math.abs(VMM.TouchSlider.startLeft) - Math.abs(left)); - var changeY = 6000 * (Math.abs(VMM.TouchSlider.startTop) - Math.abs(top)); - - slideAdjust = Math.round(changeX / slideAdjust); - slideAdjustY = Math.round(changeY / slideAdjust); - - var newLeft = slideAdjust + left; - var newTop = slideAdjustY + top; - - var y = newTop % VMM.TouchSlider.height; - var t = newLeft % VMM.TouchSlider.width; - - - var _r_object = { - top: Math.min(0, newTop), - left: Math.min(0, newLeft), - time: timeAdjust - } - VMM.fireEvent(elem, "TOUCHUPDATE", [_r_object]); - /* - if (VMM.TouchSlider.sticky) { - trace("sticky"); - if ((Math.abs(t)) > ((VMM.TouchSlider.width / 2))) { - //Show the next cell - newLeft -= (VMM.TouchSlider.width - Math.abs(t)); - } else { - //Stay on the current cell - newLeft -= t; - } - - VMM.fireEvent(elem, "TOUCHUPDATE", [Math.min(0, newLeft)]); - - } else { - trace("not sticky"); - //VMM.TouchSlider.doSlide(elem, Math.min(0, newLeft), '0.5s'); - VMM.Lib.animate(elem, 500, "", {"left": Math.min(0, newLeft)}); - } - */ - - VMM.TouchSlider.startX = null; - VMM.TouchSlider.startY = null; - - }, - doSlide: function(elem, x, duration) { - VMM.Lib.css(elem, '-webkit-transition-property', 'left'); - VMM.Lib.css(elem, '-webkit-transition-duration', duration); - VMM.Lib.css(elem, 'left', x); - }, - touchMove: function(elem, e) { - - if (!VMM.TouchSlider.sliding) { - //elem.parent().addClass('sliding'); - } - - VMM.TouchSlider.sliding = true; - - if (VMM.TouchSlider.vertical) { - - if (VMM.TouchSlider.startY > e.originalEvent.touches[0].screenY) { - VMM.Lib.css(elem, 'top', -(VMM.TouchSlider.startY - e.originalEvent.touches[0].screenY - VMM.TouchSlider.startTop)); - VMM.TouchSlider.slidingTop = true; - } else { - var top = (e.originalEvent.touches[0].screenY - VMM.TouchSlider.startY + VMM.TouchSlider.startTop); - VMM.Lib.css(elem, 'top', -(VMM.TouchSlider.startY - e.originalEvent.touches[0].screenY - VMM.TouchSlider.startTop)); - VMM.TouchSlider.slidingTop = false; - } - - } else { - - if (VMM.TouchSlider.startX > e.originalEvent.touches[0].screenX) { - VMM.Lib.css(elem, 'left', -(VMM.TouchSlider.startX - e.originalEvent.touches[0].screenX - VMM.TouchSlider.startLeft)); - VMM.TouchSlider.slidingLeft = true; - } else { - var left = (e.originalEvent.touches[0].screenX - VMM.TouchSlider.startX + VMM.TouchSlider.startLeft); - VMM.Lib.css(elem, 'left', -(VMM.TouchSlider.startX - e.originalEvent.touches[0].screenX - VMM.TouchSlider.startLeft)); - VMM.TouchSlider.slidingLeft = false; - } - - } - - - } - } -} - -/*********************************************** - Begin VMM.DragSlider.js -***********************************************/ - -/* DRAG SLIDER -================================================== */ -if(typeof VMM != 'undefined' && typeof VMM.DragSlider == 'undefined') { - // VMM.DragSlider.createSlidePanel(drag_object, move_object, w, padding, sticky); - // VMM.DragSlider.cancelSlide(); - VMM.DragSlider = { - createPanel: function(drag_object, move_object, w, padding, sticky) { - - - - var x = padding; - VMM.DragSlider.width = w; - VMM.DragSlider.makeDraggable(drag_object, move_object); - VMM.DragSlider.drag_elem = drag_object; - /* - if (sticky != null && sticky != "") { - VMM.TouchSlider.sticky = sticky; - } else { - VMM.TouchSlider.sticky = false; - } - */ - VMM.DragSlider.sticky = sticky; - }, - makeDraggable: function(drag_object, move_object) { - VMM.bindEvent(drag_object, VMM.DragSlider.onDragStart, "mousedown", {element: move_object, delement: drag_object}); - //VMM.bindEvent(drag_object, VMM.DragSlider.onDragMove, "mousemove", {element: move_object}); - VMM.bindEvent(drag_object, VMM.DragSlider.onDragEnd, "mouseup", {element: move_object, delement: drag_object}); - VMM.bindEvent(drag_object, VMM.DragSlider.onDragLeave, "mouseleave", {element: move_object, delement: drag_object}); - }, - cancelSlide: function(e) { - VMM.unbindEvent(VMM.DragSlider.drag_elem, VMM.DragSlider.onDragMove, "mousemove"); - //VMM.DragSlider.drag_elem.preventDefault(); - //VMM.DragSlider.drag_elem.stopPropagation(); - return true; - }, - onDragLeave: function(e) { - - VMM.unbindEvent(e.data.delement, VMM.DragSlider.onDragMove, "mousemove"); - e.preventDefault(); - e.stopPropagation(); - return true; - }, - onDragStart: function(e) { - VMM.DragSlider.dragStart(e.data.element, e.data.delement, e); - - e.preventDefault(); - e.stopPropagation(); - return true; - }, - onDragEnd: function(e) { - e.preventDefault(); - e.stopPropagation(); - - if (VMM.DragSlider.sliding) { - VMM.DragSlider.sliding = false; - VMM.DragSlider.dragEnd(e.data.element, e.data.delement, e); - return false; - } else { - return true; - } - - }, - onDragMove: function(e) { - VMM.DragSlider.dragMove(e.data.element, e); - e.preventDefault(); - e.stopPropagation(); - return false; - }, - dragStart: function(elem, delem, e) { - - VMM.DragSlider.startX = e.pageX; - - VMM.DragSlider.startLeft = VMM.DragSlider.getLeft(elem); - VMM.DragSlider.dragStartTime = new Date().getTime(); - VMM.DragSlider.dragWidth = VMM.Lib.width(delem); - - // CANCEL CURRENT ANIMATION IF ANIMATING - var _newx = Math.round(VMM.DragSlider.startX - e.pageX - VMM.DragSlider.startLeft); - - VMM.Lib.stop(elem); - VMM.bindEvent(delem, VMM.DragSlider.onDragMove, "mousemove", {element: elem}); - - }, - dragEnd: function(elem, delem, e) { - VMM.unbindEvent(delem, VMM.DragSlider.onDragMove, "mousemove"); - //VMM.DragSlider.dragMomentum(elem, e); - if (VMM.DragSlider.getLeft(elem) > 0) { - //(VMM.DragSlider.dragWidth/2) - //This means they dragged to the right past the first item - //VMM.Lib.animate(elem, 1000, "linear", {"left": 0}); - - //VMM.fireEvent(elem, "DRAGUPDATE", [0]); - } else { - //This means they were just dragging within the bounds of the grid and we just need to handle the momentum and snap to the grid. - VMM.DragSlider.dragMomentum(elem, e); - } - }, - dragMove: function(elem, e) { - if (!VMM.DragSlider.sliding) { - //elem.parent().addClass('sliding'); - } - - VMM.DragSlider.sliding = true; - if (VMM.DragSlider.startX > e.pageX) { - //Sliding to the left - VMM.Lib.css(elem, 'left', -(VMM.DragSlider.startX - e.pageX - VMM.DragSlider.startLeft)); - VMM.DragSlider.slidingLeft = true; - } else { - //Sliding to the right - var left = (e.pageX - VMM.DragSlider.startX + VMM.DragSlider.startLeft); - VMM.Lib.css(elem, 'left', -(VMM.DragSlider.startX - e.pageX - VMM.DragSlider.startLeft)); - VMM.DragSlider.slidingLeft = false; - } - }, - dragMomentum: function(elem, e) { - var slideAdjust = (new Date().getTime() - VMM.DragSlider.dragStartTime) * 10; - var timeAdjust = slideAdjust; - var left = VMM.DragSlider.getLeft(elem); - - var changeX = 6000 * (Math.abs(VMM.DragSlider.startLeft) - Math.abs(left)); - //var changeX = 6000 * (VMM.DragSlider.startLeft - left); - slideAdjust = Math.round(changeX / slideAdjust); - - var newLeft = left + slideAdjust; - - var t = newLeft % VMM.DragSlider.width; - //left: Math.min(0, newLeft), - var _r_object = { - left: Math.min(newLeft), - time: timeAdjust - } - - VMM.fireEvent(elem, "DRAGUPDATE", [_r_object]); - var _ease = "easeOutExpo"; - if (_r_object.time > 0) { - VMM.Lib.animate(elem, _r_object.time, _ease, {"left": _r_object.left}); - }; - - - //VMM.DragSlider.startX = null; - }, - getLeft: function(elem) { - return parseInt(VMM.Lib.css(elem, 'left').substring(0, VMM.Lib.css(elem, 'left').length - 2), 10); - } - - } -} - -/*********************************************** - Begin VMM.Slider.js -***********************************************/ - -/* Slider -================================================== */ -if(typeof VMM != 'undefined' && typeof VMM.Slider == 'undefined') { - - VMM.Slider = function(parent, parent_config) { - - var events = {}, config; - var $slider, $slider_mask, $slider_container, $slides_items; - var data = [], slides = [], slide_positions = []; - - var slides_content = ""; - var current_slide = 0; - var current_width = 960; - var touch = {move: false, x: 10, y:0, off: 0, dampen: 48}; - var content = ""; - var _active = false; - var layout = parent; - var navigation = {nextBtn:"", prevBtn:"", nextDate:"", prevDate:"", nextTitle:"", prevTitle:""}; - var timer; - - // CONFIG - if(typeof VMM.Timeline != 'undefined') { - config = VMM.Timeline.Config; - } else { - config = { - preload: 4, - current_slide: 0, - interval: 10, - something: 0, - width: 720, - height: 400, - ease: "easeInOutExpo", - duration: 1000, - timeline: false, - spacing: 15, - slider: { - width: 720, - height: 400, - content: { - width: 720, - height: 400, - padding: 130 - }, - nav: { - width: 100, - height: 200 - } - } - }; - } - - /* PUBLIC VARS - ================================================== */ - this.ver = "0.6"; - - config.slider.width = config.width; - config.slider.height = config.height; - - /* PUBLIC FUNCTIONS - ================================================== */ - this.init = function(d) { - slides = []; - slide_positions = []; - - if(typeof d != 'undefined') { - this.setData(d); - } else { - trace("WAITING ON DATA"); - } - }; - - this.width = function(w) { - if (w != null && w != "") { - config.slider.width = w; - reSize(); - } else { - return config.slider.width; - } - } - - this.height = function(h) { - if (h != null && h != "") { - config.slider.height = h; - reSize(); - } else { - return config.slider.height; - } - } - - /* GETTERS AND SETTERS - ================================================== */ - this.setData = function(d) { - if(typeof d != 'undefined') { - data = d; - build(); - } else{ - trace("NO DATA"); - } - }; - - this.getData = function() { - return data; - }; - - this.setConfig = function(d) { - if(typeof d != 'undefined') { - config = d; - } else{ - trace("NO CONFIG DATA"); - } - } - - this.getConfig = function() { - return config; - }; - - this.setSize = function(w, h) { - if (w != null) {config.slider.width = w}; - if (h != null) {config.slider.height = h}; - if (_active) { - reSize(); - } - - } - - this.active = function() { - return _active; - }; - - this.getCurrentNumber = function() { - return current_slide; - }; - - this.setSlide = function(n) { - goToSlide(n); - }; - - /* ON EVENT - ================================================== */ - function onConfigSet() { - trace("onConfigSet"); - }; - - function reSize(go_to_slide, from_start) { - - var _go_to_slide = true; - var _from_start = false; - - if (go_to_slide != null) {_go_to_slide = go_to_slide}; - if (from_start != null) {_from_start = from_start}; - - current_width = config.slider.width; - - config.slider.nav.height = VMM.Lib.height(navigation.prevBtnContainer); - - config.slider.content.width = current_width - (config.slider.content.padding *2); - - VMM.Lib.width($slides_items, (slides.length * config.slider.content.width)); - - if (_from_start) { - var _pos = slides[current_slide].leftpos(); - VMM.Lib.css($slider_container, "left", _pos); - } - - // RESIZE SLIDES - sizeSlides(); - - // POSITION SLIDES - positionSlides(); - - // POSITION NAV - VMM.Lib.css(navigation.nextBtn, "left", (current_width - config.slider.nav.width)); - VMM.Lib.height(navigation.prevBtn, config.slider.height); - VMM.Lib.height(navigation.nextBtn, config.slider.height); - VMM.Lib.css(navigation.nextBtnContainer, "top", ( (config.slider.height/2) - (config.slider.nav.height/2) ) + 10 ); - VMM.Lib.css(navigation.prevBtnContainer, "top", ( (config.slider.height/2) - (config.slider.nav.height/2) ) + 10 ); - - // Animate Changes - VMM.Lib.height($slider_mask, config.slider.height); - VMM.Lib.width($slider_mask, current_width); - - if (_go_to_slide) { - goToSlide(current_slide, "linear", 1); - }; - - if (current_slide == 0) { - VMM.Lib.visible(navigation.prevBtn, false); - } - - } - - /* NAVIGATION - ================================================== */ - function onNextClick(e) { - if (current_slide == slides.length - 1) { - VMM.Lib.animate($slider_container, config.duration, config.ease, {"left": -(slides[current_slide].leftpos()) } ); - } else { - goToSlide(current_slide+1); - upDate(); - } - } - - function onPrevClick(e) { - if (current_slide == 0) { - goToSlide(current_slide); - } else { - goToSlide(current_slide-1); - upDate(); - } - } - - function onKeypressNav(e) { - switch(e.keyCode) { - case 39: - // RIGHT ARROW - onNextClick(e); - break; - case 37: - // LEFT ARROW - onPrevClick(e); - break; - } - } - - function onTouchUpdate(e, b) { - if (slide_positions.length == 0) { - for(var i = 0; i < slides.length; i++) { - slide_positions.push( slides[i].leftpos() ); - } - } - if (typeof b.left == "number") { - var _pos = b.left; - var _slide_pos = -(slides[current_slide].leftpos()); - if (_pos < _slide_pos - (config.slider_width/3)) { - onNextClick(); - } else if (_pos > _slide_pos + (config.slider_width/3)) { - onPrevClick(); - } else { - VMM.Lib.animate($slider_container, config.duration, config.ease, {"left": _slide_pos }); - } - } else { - VMM.Lib.animate($slider_container, config.duration, config.ease, {"left": _slide_pos }); - } - - if (typeof b.top == "number") { - VMM.Lib.animate($slider_container, config.duration, config.ease, {"top": -b.top}); - } else { - - } - }; - - /* UPDATE - ================================================== */ - function upDate() { - config.current_slide = current_slide; - VMM.fireEvent(layout, "UPDATE"); - }; - - /* GET DATA - ================================================== */ - var getData = function(d) { - data = d; - }; - - /* BUILD SLIDES - ================================================== */ - var buildSlides = function(d) { - VMM.attachElement($slides_items, ""); - slides = []; - - for(var i = 0; i < d.length; i++) { - var _slide = new VMM.Slider.Slide(d[i], $slides_items); - //_slide.show(); - slides.push(_slide); - } - } - - var preloadSlides = function(skip) { - if (skip) { - preloadTimeOutSlides(); - } else { - timer = setTimeout(preloadTimeOutSlides, config.duration); - } - } - - var preloadTimeOutSlides = function() { - for(var j = 0; j < config.preload; j++) { - if ( !((current_slide + j) > slides.length - 1)) { - slides[current_slide + j].show(); - } - if ( !( (current_slide - j) < 0 ) ) { - slides[current_slide - j].show(); - } - } - - sizeSlides(); - } - - /* SIZE SLIDES - ================================================== */ - var sizeSlides = function() { - var layout_text_media = ".slider-item .layout-text-media .media .media-container "; - var layout_media = ".slider-item .layout-media .media .media-container "; - var layout_both = ".slider-item .media .media-container"; - var mediasize = { - text_media: { - width: (config.slider.content.width/100) * 60, - height: config.slider.height - 60, - video: { - width: 0, - height: 0 - }, - text: { - width: ((config.slider.content.width/100) * 40) - 30, - height: config.slider.height - } - }, - media: { - width: config.slider.content.width, - height: config.slider.height - 110, - video: { - width: 0, - height: 0 - } - } - } - - VMM.master_config.sizes.api.width = mediasize.media.width; - VMM.master_config.sizes.api.height = mediasize.media.height; - - mediasize.text_media.video = VMM.Util.ratio.fit(mediasize.text_media.width, mediasize.text_media.height, 16, 9); - mediasize.media.video = VMM.Util.ratio.fit(mediasize.media.width, mediasize.media.height, 16, 9); - - VMM.Lib.css(".slider-item", "width", config.slider.content.width ); - VMM.Lib.height(".slider-item", config.slider.height); - - // HANDLE SMALLER SIZES - var is_skinny = false; - - if (current_width <= 640) { - is_skinny = true; - } else if (VMM.Browser.device == "mobile" && VMM.Browser.orientation == "portrait") { - is_skinny = true; - } else if (VMM.Browser.device == "tablet" && VMM.Browser.orientation == "portrait") { - //is_skinny = true; - } - - if (is_skinny) { - - mediasize.text_media.width = config.slider.content.width; - mediasize.text_media.height = ((config.slider.height/100) * 50 ) - 50; - mediasize.media.height = ((config.slider.height/100) * 70 ) - 40; - - mediasize.text_media.video = VMM.Util.ratio.fit(mediasize.text_media.width, mediasize.text_media.height, 16, 9); - mediasize.media.video = VMM.Util.ratio.fit(mediasize.media.width, mediasize.media.height, 16, 9); - - VMM.Lib.css(".slider-item .layout-text-media .text", "width", "100%" ); - VMM.Lib.css(".slider-item .layout-text-media .text", "display", "block" ); - VMM.Lib.css(".slider-item .layout-text-media .text .container", "display", "block" ); - VMM.Lib.css(".slider-item .layout-text-media .text .container", "width", config.slider.content.width ); - - VMM.Lib.css(".slider-item .layout-text-media .media", "float", "none" ); - VMM.Lib.addClass(".slider-item .content-container", "pad-top"); - - VMM.Lib.css(".slider-item .media blockquote p", "line-height", "18px" ); - VMM.Lib.css(".slider-item .media blockquote p", "font-size", "16px" ); - - VMM.Lib.css(".slider-item", "overflow-y", "auto" ); - - - } else { - - VMM.Lib.css(".slider-item .layout-text-media .text", "width", "40%" ); - VMM.Lib.css(".slider-item .layout-text-media .text", "display", "table-cell" ); - VMM.Lib.css(".slider-item .layout-text-media .text .container", "display", "table-cell" ); - VMM.Lib.css(".slider-item .layout-text-media .text .container", "width", "auto" ); - VMM.Lib.css(".slider-item .layout-text-media .text .container .start", "width", mediasize.text_media.text.width ); - //VMM.Lib.addClass(".slider-item .content-container", "pad-left"); - VMM.Lib.removeClass(".slider-item .content-container", "pad-top"); - - VMM.Lib.css(".slider-item .layout-text-media .media", "float", "left" ); - VMM.Lib.css(".slider-item .layout-text-media", "display", "table" ); - - VMM.Lib.css(".slider-item .media blockquote p", "line-height", "36px" ); - VMM.Lib.css(".slider-item .media blockquote p", "font-size", "28px" ); - - VMM.Lib.css(".slider-item", "display", "table" ); - VMM.Lib.css(".slider-item", "overflow-y", "auto" ); - } - - // MEDIA FRAME - VMM.Lib.css( layout_text_media + ".media-frame", "max-width", mediasize.text_media.width); - VMM.Lib.height( layout_text_media + ".media-frame", mediasize.text_media.height); - VMM.Lib.width( layout_text_media + ".media-frame", mediasize.text_media.width); - - // IMAGES - VMM.Lib.css( layout_text_media + "img", "max-height", mediasize.text_media.height ); - VMM.Lib.css( layout_media + "img", "max-height", mediasize.media.height ); - - // FIX FOR NON-WEBKIT BROWSERS - VMM.Lib.css( layout_text_media + "img", "max-width", mediasize.text_media.width ); - VMM.Lib.css( layout_text_media + ".twitter .avatar img", "max-width", 32 ); - VMM.Lib.css( layout_text_media + ".twitter .avatar img", "max-height", 32 ); - VMM.Lib.css( layout_media + ".twitter .avatar img", "max-width", 32 ); - VMM.Lib.css( layout_media + ".twitter .avatar img", "max-height", 32 ); - - // IFRAME FULL SIZE VIDEO - VMM.Lib.width( layout_text_media + ".media-frame", mediasize.text_media.video.width); - VMM.Lib.height( layout_text_media + ".media-frame", mediasize.text_media.video.height); - VMM.Lib.width( layout_media + ".media-frame", mediasize.media.video.width); - VMM.Lib.height( layout_media + ".media-frame", mediasize.media.video.height); - VMM.Lib.css( layout_media + ".media-frame", "max-height", mediasize.media.video.height); - VMM.Lib.css( layout_media + ".media-frame", "max-width", mediasize.media.video.width); - - // SOUNDCLOUD - VMM.Lib.height( layout_media + ".soundcloud", 168); - VMM.Lib.height( layout_text_media + ".soundcloud", 168); - VMM.Lib.width( layout_media + ".soundcloud", mediasize.media.width); - VMM.Lib.width( layout_text_media + ".soundcloud", mediasize.text_media.width); - VMM.Lib.css( layout_both + ".soundcloud", "max-height", 168 ); - - // MAPS - VMM.Lib.height( layout_text_media + ".map", mediasize.text_media.height); - VMM.Lib.css( layout_media + ".map", "max-height", mediasize.media.height); - VMM.Lib.width( layout_media + ".map", mediasize.media.width); - - // DOCS - VMM.Lib.height( layout_text_media + ".doc", mediasize.text_media.height); - VMM.Lib.height( layout_media + ".doc", mediasize.media.height); - - // MAINTAINS VERTICAL CENTER IF IT CAN - for(var i = 0; i < slides.length; i++) { - - slides[i].layout(is_skinny); - - if (slides[i].content_height() > config.slider.height + 20) { - slides[i].css("display", "block"); - } else { - slides[i].css("display", "table"); - } - } - - } - - /* POSITION SLIDES - ================================================== */ - var positionSlides = function() { - var pos = 0; - for(var i = 0; i < slides.length; i++) { - pos = i * (config.slider.width+config.spacing); - slides[i].leftpos(pos); - } - } - - /* OPACITY SLIDES - ================================================== */ - var opacitySlides = function(n) { - var _ease = "linear"; - for(var i = 0; i < slides.length; i++) { - if (i == current_slide) { - slides[i].animate(config.duration, _ease, {"opacity": 1}); - } else if (i == current_slide - 1 || i == current_slide + 1) { - slides[i].animate(config.duration, _ease, {"opacity": 0.1}); - } else { - slides[i].opacity(n); - } - } - } - - /* GO TO SLIDE - goToSlide(n, ease, duration); - ================================================== */ - var goToSlide = function(n, ease, duration, fast, firstrun) { - - /* STOP ANY VIDEO PLAYERS ACTIVE - ================================================== */ - VMM.ExternalAPI.youtube.stopPlayers(); - - // Set current slide - current_slide = n; - - var _ease = config.ease; - var _duration = config.duration; - var is_last = false; - var is_first = false; - var _pos = slides[current_slide].leftpos(); - var _title = ""; - - if (current_slide == 0) {is_first = true}; - if (current_slide +1 >= slides.length) {is_last = true}; - if (ease != null && ease != "") {_ease = ease}; - if (duration != null && duration != "") {_duration = duration}; - - /* set proper nav titles and dates etc. - ================================================== */ - if (is_first) { - VMM.Lib.visible(navigation.prevBtn, false); - } else { - VMM.Lib.visible(navigation.prevBtn, true); - _title = VMM.Util.unlinkify(data[current_slide - 1].title) - if (config.type == "timeline") { - if(typeof data[current_slide - 1].date === "undefined") { - VMM.attachElement(navigation.prevDate, _title); - VMM.attachElement(navigation.prevTitle, ""); - } else { - VMM.attachElement(navigation.prevDate, data[current_slide - 1].startdate_str); - VMM.attachElement(navigation.prevTitle, _title); - } - } else { - VMM.attachElement(navigation.prevTitle, _title); - } - - } - if (is_last) { - VMM.Lib.visible(navigation.nextBtn, false); - } else { - VMM.Lib.visible(navigation.nextBtn, true); - _title = VMM.Util.unlinkify(data[current_slide + 1].title); - if (config.type == "timeline") { - if(typeof data[current_slide + 1].date === "undefined") { - VMM.attachElement(navigation.nextDate, _title); - VMM.attachElement(navigation.nextTitle, ""); - } else { - VMM.attachElement(navigation.nextDate, data[current_slide + 1].startdate_str); - VMM.attachElement(navigation.nextTitle, _title); - } - } else { - VMM.attachElement(navigation.nextTitle, _title); - } - - } - - /* ANIMATE SLIDE - ================================================== */ - if (fast) { - VMM.Lib.css($slider_container, "left", -(_pos - config.slider.content.padding)); - } else{ - VMM.Lib.stop($slider_container); - VMM.Lib.animate($slider_container, _duration, _ease, {"left": -(_pos - config.slider.content.padding)}); - } - - if (firstrun) { - VMM.fireEvent(layout, "LOADED"); - } - - /* SET Vertical Scoll - ================================================== */ - if (slides[current_slide].height() > config.slider_height) { - VMM.Lib.css(".slider", "overflow-y", "scroll" ); - } else { - VMM.Lib.css(layout, "overflow-y", "hidden" ); - VMM.Lib.animate(layout, _duration, _ease, {scrollTop: VMM.Lib.prop(layout, "scrollHeight") - VMM.Lib.height(layout) }); - } - - preloadSlides(); - } - - /* BUILD NAVIGATION - ================================================== */ - var buildNavigation = function() { - - var temp_icon = "
     
    "; - - navigation.nextBtn = VMM.appendAndGetElement($slider, "
    ", "nav-next"); - navigation.prevBtn = VMM.appendAndGetElement($slider, "
    ", "nav-previous"); - navigation.nextBtnContainer = VMM.appendAndGetElement(navigation.nextBtn, "
    ", "nav-container", temp_icon); - navigation.prevBtnContainer = VMM.appendAndGetElement(navigation.prevBtn, "
    ", "nav-container", temp_icon); - if (config.type == "timeline") { - navigation.nextDate = VMM.appendAndGetElement(navigation.nextBtnContainer, "
    ", "date", ""); - navigation.prevDate = VMM.appendAndGetElement(navigation.prevBtnContainer, "
    ", "date", ""); - } - navigation.nextTitle = VMM.appendAndGetElement(navigation.nextBtnContainer, "
    ", "title", "Title Goes Here"); - navigation.prevTitle = VMM.appendAndGetElement(navigation.prevBtnContainer, "
    ", "title", "Title Goes Here"); - - VMM.bindEvent(".nav-next", onNextClick); - VMM.bindEvent(".nav-previous", onPrevClick); - VMM.bindEvent(window, onKeypressNav, 'keydown'); - } - - /* BUILD - ================================================== */ - var build = function() { - - // Clear out existing content - VMM.attachElement(layout, ""); - - // Get DOM Objects to local objects - $slider = VMM.getElement("div.slider"); - $slider_mask = VMM.appendAndGetElement($slider, "
    ", "slider-container-mask"); - $slider_container = VMM.appendAndGetElement($slider_mask, "
    ", "slider-container"); - $slides_items = VMM.appendAndGetElement($slider_container, "
    ", "slider-item-container"); - - // BUILD NAVIGATION - buildNavigation(); - - // ATTACH SLIDES - buildSlides(data); - - /* MAKE SLIDER TOUCHABLE - ================================================== */ - - var __duration = 3000; - - if (VMM.Browser.device == "tablet" || VMM.Browser.device == "mobile") { - config.duration = 500; - __duration = 1000; - //VMM.TouchSlider.createPanel($slider_container, $slider_container, VMM.Lib.width(slides[0]), config.spacing, true); - //VMM.TouchSlider.createPanel($slider_container, $slider_container, slides[0].width(), config.spacing, true); - //VMM.bindEvent($slider_container, onTouchUpdate, "TOUCHUPDATE"); - } else if (VMM.Browser.device == "mobile") { - - } else { - //VMM.DragSlider.createPanel($slider_container, $slider_container, VMM.Lib.width(slides[0]), config.spacing, true); - } - - reSize(false, true); - VMM.Lib.visible(navigation.prevBtn, false); - goToSlide(config.current_slide, "easeOutExpo", __duration, true, true); - - _active = true; - }; - - }; - -} - - - - - - -/*********************************************** - Begin VMM.Slider.Slide.js -***********************************************/ - -/* Slider Slide -================================================== */ -if (typeof VMM.Slider != 'undefined') { - // VMM.Slider.Slide(element, data) - VMM.Slider.Slide = function(d, _parent) { - - var data = d; - var slide = {}; - var media = ""; - var loaded = false; - var preloaded = false; - var is_skinny = false; - var element = VMM.appendAndGetElement(_parent, "
    ", "slider-item"); - var c = {slide:"", text: "", media: "", media_element: "", layout: "content-container layout", has: { headline: false, text: false, media: false }}; - var $media, $text, $slide, $wrap; - /* PUBLIC - ================================================== */ - this.show = function(skinny) { - if (!loaded) { - if (preloaded) { - reLayout(skinny); - } else { - render(skinny); - } - } - }; - - this.hide = function() { - if (loaded) { - removeSlide(); - } - }; - - this.layout = function(skinny) { - if (loaded && preloaded) { - reLayout(skinny); - } - }; - - this.elem = function() { - return element; - }; - - this.position = function() { - return VMM.Lib.position(element); - }; - - this.leftpos = function(p) { - if(typeof p != 'undefined') { - VMM.Lib.css(element, "left", p); - } else { - return VMM.Lib.position(element).left - } - }; - - this.animate = function(d, e, p) { - VMM.Lib.animate(element, d, e, p); - }; - - this.css = function(p, v) { - VMM.Lib.css(element, p, v ); - } - - this.opacity = function(p) { - VMM.Lib.css(element, "opacity", p); - } - - this.width = function() { - return VMM.Lib.width(element); - }; - - this.height = function() { - return VMM.Lib.height(element); - }; - - this.content_height = function () { - var ch = VMM.Lib.find( element, ".content")[0]; - - if (ch != 'undefined' && ch != null) { - return VMM.Lib.height(ch); - } else { - return 0; - } - } - - /* PRIVATE - ================================================== */ - var render = function(skinny) { - buildSlide(skinny); - loaded = true; - preloaded = true; - var timer = setTimeout(VMM.ExternalAPI.pushQues, 500); - }; - - var removeSlide = function() { - //VMM.attachElement(element, ""); - loaded = false; - } - - var reLayout = function(skinny) { - - if (c.has.text) { - if (skinny) { - if (!is_skinny) { - VMM.Lib.removeClass($slide, "pad-left"); - VMM.Lib.detach($text); - VMM.Lib.prepend($slide, $text); - is_skinny = true; - } - } else { - if (is_skinny) { - VMM.Lib.addClass($slide, "pad-left"); - VMM.Lib.detach($text); - VMM.Lib.append($slide, $text); - is_skinny = false - } - } - } - } - - var buildSlide = function(skinny) { - $wrap = VMM.appendAndGetElement(element, "
    ", "content"); - $slide = VMM.appendAndGetElement($wrap, "
    "); - - /* DATE - ================================================== */ - if (data.startdate != null && data.startdate != "") { - if (type.of(data.startdate) == "date") { - if (data.type != "start") { - var st = data.startdate_str; - var en = data.enddate_str; - if (st != en) { - c.text += VMM.createElement("h2", st + " — " + en + "", "date"); - } else { - c.text += VMM.createElement("h2", st, "date"); - } - } - } - } - - /* HEADLINE - ================================================== */ - if (data.headline != null && data.headline != "") { - c.has.headline = true; - if (data.type == "start") { - c.text += VMM.createElement("h2", VMM.Util.linkify_with_twitter(data.headline, "_blank"), "start"); - } else { - c.text += VMM.createElement("h3", VMM.Util.linkify_with_twitter(data.headline, "_blank")); - } - } - - /* TEXT - ================================================== */ - if (data.text != null && data.text != "") { - c.has.text = true; - c.text += VMM.createElement("p", VMM.Util.linkify_with_twitter(data.text, "_blank")); - } - - if (c.has.text || c.has.headline) { - c.text = VMM.createElement("div", c.text, "container"); - $text = VMM.appendAndGetElement($slide, "
    ", "text", c.text); - } - - /* MEDIA - ================================================== */ - if (data.asset != null && data.asset != "") { - if (data.asset.media != null && data.asset.media != "") { - c.has.media = true; - $media = VMM.appendAndGetElement($slide, "
    ", "media", VMM.MediaElement.create(data.asset)); - } - } - - /* COMBINE - ================================================== */ - if (c.has.text) { c.layout += "-text" }; - if (c.has.media){ c.layout += "-media" }; - - if (c.has.text) { - if (skinny) { - VMM.Lib.addClass($slide, c.layout); - is_skinny = true; - } else { - VMM.Lib.addClass($slide, c.layout); - VMM.Lib.addClass($slide, "pad-left"); - VMM.Lib.detach($text); - VMM.Lib.append($slide, $text); - } - - } else { - VMM.Lib.addClass($slide, c.layout); - } - - - }; - - } - -}; - - -/*********************************************** - Begin VMM.Util.js -***********************************************/ - -/* Utilities and Useful Functions -================================================== */ -if(typeof VMM != 'undefined' && typeof VMM.Util == 'undefined') { - - VMM.Util = ({ - - init: function() { - return this; - }, - - /* CORRECT PROTOCOL (DOES NOT WORK) - ================================================== */ - correctProtocol: function(url) { - var loc = (window.parent.location.protocol).toString(); - var prefix = ""; - var _url = url.split("://", 2); - - if (loc.match("http")) { - prefix = loc; - } else { - prefix = "https"; - } - - return prefix + "://" + _url[1]; - - }, - - /* GET OBJECT ATTRIBUTE BY INDEX - ================================================== */ - getObjectAttributeByIndex: function(obj, index) { - if(typeof obj != 'undefined') { - var i = 0; - for (var attr in obj){ - if (index === i){ - return obj[attr]; - } - i++; - } - return ""; - } else { - return ""; - } - - }, - /* RANDOM BETWEEN - ================================================== */ - //VMM.Util.randomBetween(1, 3) - randomBetween: function(min, max) { - return Math.floor(Math.random() * (max - min + 1) + min); - }, - - /* AVERAGE - http://jsfromhell.com/array/average - var x = VMM.Util.average([2, 3, 4]); - VMM.Util.average([2, 3, 4]).mean - ================================================== */ - average: function(a) { - var r = {mean: 0, variance: 0, deviation: 0}, t = a.length; - for(var m, s = 0, l = t; l--; s += a[l]); - for(m = r.mean = s / t, l = t, s = 0; l--; s += Math.pow(a[l] - m, 2)); - return r.deviation = Math.sqrt(r.variance = s / t), r; - }, - - /* CUSTOM SORT - ================================================== */ - customSort: function(a, b) { - var a1= a, b1= b; - if(a1== b1) return 0; - return a1> b1? 1: -1; - }, - - /* Remove Duplicates from Array - ================================================== */ - deDupeArray: function(arr) { - var i, - len=arr.length, - out=[], - obj={}; - - for (i=0;i h) { - _fit.height = h; - //_fit.width = Math.round((w / ratio_w) * ratio_h); - _fit.width = Math.round((h / ratio_h) * ratio_w); - - if (_fit.width > w) { - trace("FIT: DIDN'T FIT!!! ") - } - } - - return _fit; - - }, - r16_9: function(w,h) { - //VMM.Util.ratio.r16_9(w, h) // Returns corresponding number - if (w !== null && w !== "") { - return Math.round((h / 16) * 9); - } else if (h !== null && h !== "") { - return Math.round((w / 9) * 16); - } - }, - r4_3: function(w,h) { - if (w !== null && w !== "") { - return Math.round((h / 4) * 3); - } else if (h !== null && h !== "") { - return Math.round((w / 3) * 4); - } - } - }, - - date: { - - dateformats: { - year: "yyyy", - month_short: "mmm", - month: "mmmm yyyy", - full_short: "mmm d", - full: "mmmm d',' yyyy", - time_no_seconds_short: "h:MM TT", - time_no_seconds_small_date: "h:MM TT'
    'mmmm d',' yyyy''", - full_long: "mmm d',' yyyy 'at' hh:MM TT", - full_long_small_date: "hh:MM TT'
    mmm d',' yyyy''", - }, - - month: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - month_abbr: ["Jan.", "Feb.", "March", "April", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec."], - day: ["Sunday","Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], - day_abbr: ["Sun.","Mon.", "Tues.", "Wed.", "Thurs.", "Fri.", "Sat."], - hour: [1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12], - hour_suffix: ["am"], - - //B.C. - bc_format: { - year: "yyyy", - month_short: "mmm", - month: "mmmm yyyy", - full_short: "mmm d", - full: "mmmm d',' yyyy", - time_no_seconds_short: "h:MM TT", - time_no_seconds_small_date: "dddd', 'h:MM TT'
    'mmmm d',' yyyy''", - full_long: "dddd',' mmm d',' yyyy 'at' hh:MM TT", - full_long_small_date: "hh:MM TT'
    'dddd',' mmm d',' yyyy''", - }, - - setLanguage: function(lang) { - trace("SET DATE LANGUAGE"); - VMM.Util.date.dateformats = lang.dateformats; - VMM.Util.date.month = lang.date.month; - VMM.Util.date.month_abbr = lang.date.month_abbr; - VMM.Util.date.day = lang.date.day; - VMM.Util.date.day_abbr = lang.date.day_abbr; - dateFormat.i18n.dayNames = lang.date.day_abbr.concat(lang.date.day); - dateFormat.i18n.monthNames = lang.date.month_abbr.concat(lang.date.month); - }, - - parse: function(d) { - if (type.of(d) == "date") { - return d; - } else { - try { - var out = new Date(d); - if (isNaN(out) === false) { - return out; - } - } catch (e) { - } - var _date = new Date(0, 0, 1, 0, 0, 0, 0); - var _d_array; // DATE ARRAY - var _t_array; // TIME ARRAY - if ( d.match(/,/gi) ) { - _d_array = d.split(","); - for(var i = 0; i < _d_array.length; i++) { - _d_array[i] = parseInt(_d_array[i]); - } - if ( _d_array[0] ) { _date.setFullYear( _d_array[0]); } - if ( _d_array[1] > 1 ) { _date.setMonth( _d_array[1] - 1); } - if ( _d_array[2] > 1 ) { _date.setDate( _d_array[2]); } - if ( _d_array[3] > 1 ) { _date.setHours( _d_array[3]); } - if ( _d_array[4] > 1 ) { _date.setMinutes( _d_array[4]); } - if ( _d_array[5] > 1 ) { _date.setSeconds( _d_array[5]); } - if ( _d_array[6] > 1 ) { _date.setMilliseconds( _d_array[6]); } - } else if (d.match("/")) { - var _time_parse; - var _times; - if (d.match(" ")) { - _time_parse = d.split(" "); - if (d.match(":")) { - _t_array = _time_parse[1].split(":"); - if ( _t_array[0] >= 1 ) { _date.setHours( _t_array[0]); } - if ( _t_array[1] >= 1 ) { _date.setMinutes( _t_array[1]); } - if ( _t_array[2] >= 1 ) { _date.setSeconds( _t_array[2]); } - if ( _t_array[3] >= 1 ) { _date.setMilliseconds( _t_array[3]); } - } - _d_array = _time_parse[0].split("/"); - } else { - _d_array = d.split("/"); - } - if ( _d_array[2] ) { _date.setFullYear( _d_array[2]); } - if ( _d_array[0] > 1 ) { _date.setMonth( _d_array[0] - 1); } - if ( _d_array[1] > 1 ) { _date.setDate( _d_array[1]); } - } else if (d.length < 5) { - _date.setFullYear(parseInt(d)); - _date.setMonth(0); - _date.setDate(1); - _date.setHours(0); - _date.setMinutes(0); - _date.setSeconds(0); - _date.setMilliseconds(0); - } else { - _date = new Date( - parseInt(d.slice(0,4)), - parseInt(d.slice(4,6)) - 1, - parseInt(d.slice(6,8)), - parseInt(d.slice(8,10)), - parseInt(d.slice(10,12)) - ); - } - return _date; - } - }, - - prettyDate: function(d, is_abbr, d2) { - var _date; - var _date2; - var format; - var bc_check; - var is_pair = false; - - if (d2 != null) { - is_pair = true; - } - - if (type.of(d) == "date") { - if (d.getMonth() === 0 && d.getDate() == 1 && d.getHours() === 0 && d.getMinutes() === 0 ) { - // YEAR ONLY - format = VMM.Util.date.dateformats.year; - } else if (d.getDate() <= 1 && d.getHours() === 0 && d.getMinutes() === 0) { - // YEAR MONTH - if (is_abbr) { - format = VMM.Util.date.dateformats.month_short; - } else { - format = VMM.Util.date.dateformats.month; - } - } else if (d.getHours() === 0 && d.getMinutes() === 0) { - // YEAR MONTH DAY - if (is_abbr) { - format = VMM.Util.date.dateformats.full_short; - } else { - format = VMM.Util.date.dateformats.full; - } - } else if (d.getMinutes() === 0) { - // YEAR MONTH DAY HOUR - if (is_abbr) { - format = VMM.Util.date.dateformats.time_no_seconds_short; - } else { - format = VMM.Util.date.dateformats.time_no_seconds_small_date; - } - } else { - // YEAR MONTH DAY HOUR MINUTE - if (is_abbr){ - format = VMM.Util.date.dateformats.time_no_seconds_short; - } else { - format = VMM.Util.date.dateformats.full_long; - } - } - - _date = dateFormat(d, format); - bc_check = _date.split(" "); - - // BC TIME SUPPORT - for(var i = 0; i < bc_check.length; i++) { - if ( parseInt(bc_check[i]) < 0 ) { - trace("YEAR IS BC"); - var bc_original = bc_check[i]; - var bc_number = Math.abs( parseInt(bc_check[i]) ); - var bc_string = bc_number.toString() + " B.C."; - _date = _date.replace(bc_original, bc_string); - } - } - - - if (is_pair) { - _date2 = dateFormat(d2, format); - bc_check = _date2.split(" "); - // BC TIME SUPPORT - for(var i = 0; i < bc_check.length; i++) { - if ( parseInt(bc_check[i]) < 0 ) { - trace("YEAR IS BC"); - var bc_original = bc_check[i]; - var bc_number = Math.abs( parseInt(bc_check[i]) ); - var bc_string = bc_number.toString() + " B.C."; - _date2 = _date2.replace(bc_original, bc_string); - } - } - - } - } else { - trace("NOT A VALID DATE?"); - trace(d); - } - - if (is_pair) { - return _date + " — " + _date2; - } else { - return _date; - } - } - - }, - - // VMM.Util.doubledigit(number). - doubledigit: function(n) { - return (n < 10 ? '0' : '') + n; - }, - - - /* Returns a truncated segement of a long string of between min and max words. If possible, ends on a period (otherwise goes to max). - ================================================== */ - truncateWords: function(s, min, max) { - - if (!min) min = 30; - if (!max) max = min; - - var initial_whitespace_rExp = /^[^A-Za-z0-9\'\-]+/gi; - var left_trimmedStr = s.replace(initial_whitespace_rExp, ""); - var words = left_trimmedStr.split(" "); - - var result = []; - - min = Math.min(words.length, min); - max = Math.min(words.length, max); - - for (var i = 0; i$&") - .replace(pseudoUrlPattern, "$1$2") - .replace(emailAddressPattern, "$1"); - }, - - linkify_with_twitter: function(text,targets,is_touch) { - - // http://, https://, ftp:// - var urlPattern = /\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim; - var url_pattern = /(\()((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&'()*+,;=:\/?#[\]@%]+)(\))|(\[)((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&'()*+,;=:\/?#[\]@%]+)(\])|(\{)((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&'()*+,;=:\/?#[\]@%]+)(\})|(<|&(?:lt|#60|#x3c);)((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&'()*+,;=:\/?#[\]@%]+)(>|&(?:gt|#62|#x3e);)|((?:^|[^=\s'"\]])\s*['"]?|[^=\s]\s+)(\b(?:ht|f)tps?:\/\/[a-z0-9\-._~!$'()*+,;=:\/?#[\]@%]+(?:(?!&(?:gt|#0*62|#x0*3e);|&(?:amp|apos|quot|#0*3[49]|#x0*2[27]);[.!&',:?;]?(?:[^a-z0-9\-._~!$&'()*+,;=:\/?#[\]@%]|$))&[a-z0-9\-._~!$'()*+,;=:\/?#[\]@%]*)*[a-z0-9\-_~$()*+=\/#[\]@%])/img; - var url_replace = '$1$4$7$10$13$2$5$8$11$14$3$6$9$12'; - - // www. sans http:// or https:// - var pseudoUrlPattern = /(^|[^\/])(www\.[\S]+(\b|$))/gim; - function replaceURLWithHTMLLinks(text) { - var exp = /(\b(https?|ftp|file):\/\/([-A-Z0-9+&@#%?=~_|!:,.;]*)([-A-Z0-9+&@#%?\/=~_|!:,.;]*)[-A-Z0-9+&@#\/%=~_|])/ig; - return text.replace(exp, "$3"); - } - // Email addresses - var emailAddressPattern = /(([a-zA-Z0-9_\-\.]+)@[a-zA-Z_]+?(?:\.[a-zA-Z]{2,6}))+/gim; - - var twitterHandlePattern = /(@([\w]+))/g; - - var twitterSearchPattern = /(#([\w]+))/g; - - return text - //.replace(urlPattern, "$&") - .replace(url_pattern, url_replace) - .replace(pseudoUrlPattern, "$1$2") - .replace(emailAddressPattern, "$1") - .replace(twitterHandlePattern, "$1") - .replace(twitterSearchPattern, "$1"); - }, - - linkify_wikipedia: function(text) { - - var urlPattern = /]*>(.*?)<\/i>/gim; - return text - .replace(urlPattern, "$&") - .replace(/]*>/gim, "") - .replace(/<\/i>/gim, "") - .replace(/]*>/gim, "") - .replace(/<\/b>/gim, ""); - }, - /* Turns plain text links into real links - ================================================== */ - // VMM.Util.unlinkify(); - unlinkify: function(text) { - if(!text) return text; - text = text.replace(/]*>/i,""); - text = text.replace(/<\/a>/i, ""); - return text; - }, - - /* TK - ================================================== */ - nl2br: function(text) { - return text.replace(/(\r\n|[\r\n]|\\n|\\r)/g,"
    "); - }, - - /* Generate a Unique ID - ================================================== */ - // VMM.Util.unique_ID(size); - unique_ID: function(size) { - - var getRandomNumber = function(range) { - return Math.floor(Math.random() * range); - }; - - var getRandomChar = function() { - var chars = "abcdefghijklmnopqurstuvwxyzABCDEFGHIJKLMNOPQURSTUVWXYZ"; - return chars.substr( getRandomNumber(62), 1 ); - }; - - var randomID = function(size) { - var str = ""; - for(var i = 0; i < size; i++) { - str += getRandomChar(); - } - return str; - }; - - return randomID(size); - }, - /* Tells you if a number is even or not - ================================================== */ - // VMM.Util.isEven(n) - isEven: function(n){ - return (n%2 === 0) ? true : false; - }, - /* Get URL Variables - ================================================== */ - // var somestring = VMM.Util.getUrlVars(str_url)["varname"]; - getUrlVars: function(string) { - - var str = string.toString(); - - if (str.match('&')) { - str = str.replace("&", "&"); - } else if (str.match('&')) { - str = str.replace("&", "&"); - } else if (str.match('&')) { - str = str.replace("&", "&"); - } - - var vars = [], hash; - var hashes = str.slice(str.indexOf('?') + 1).split('&'); - for(var i = 0; i < hashes.length; i++) { - hash = hashes[i].split('='); - vars.push(hash[0]); - vars[hash[0]] = hash[1]; - } - - - return vars; - }, - - /* Cleans up strings to become real HTML - ================================================== */ - toHTML: function(text) { - - text = this.nl2br(text); - text = this.linkify(text); - - return text.replace(/\s\s/g,"  "); - }, - - /* Returns text strings as CamelCase - ================================================== */ - toCamelCase: function(s,forceLowerCase) { - - if(forceLowerCase !== false) forceLowerCase = true; - - var sps = ((forceLowerCase) ? s.toLowerCase() : s).split(" "); - - for(var i=0; i= 7) { - return t.replace("_", "%20"); - } else { - var __TitleCase = { - __smallWords: ['a', 'an', 'and', 'as', 'at', 'but','by', 'en', 'for', 'if', 'in', 'of', 'on', 'or','the', 'to', 'v[.]?', 'via', 'vs[.]?'], - - init: function() { - this.__smallRE = this.__smallWords.join('|'); - this.__lowerCaseWordsRE = new RegExp('\\b(' + this.__smallRE + ')\\b', 'gi'); - this.__firstWordRE = new RegExp('^([^a-zA-Z0-9 \\r\\n\\t]*)(' + this.__smallRE + ')\\b', 'gi'); - this.__lastWordRE = new RegExp('\\b(' + this.__smallRE + ')([^a-zA-Z0-9 \\r\\n\\t]*)$', 'gi'); - }, - - toTitleCase: function(string) { - var line = ''; - - var split = string.split(/([:.;?!][ ]|(?:[ ]|^)["“])/); - - for (var i = 0; i < split.length; ++i) { - var s = split[i]; - - s = s.replace(/\b([a-zA-Z][a-z.'’]*)\b/g,this.__titleCaseDottedWordReplacer); - - // lowercase the list of small words - s = s.replace(this.__lowerCaseWordsRE, this.__lowerReplacer); - - // if the first word in the title is a small word then capitalize it - s = s.replace(this.__firstWordRE, this.__firstToUpperCase); - - // if the last word in the title is a small word, then capitalize it - s = s.replace(this.__lastWordRE, this.__firstToUpperCase); - - line += s; - } - - // special cases - line = line.replace(/ V(s?)\. /g, ' v$1. '); - line = line.replace(/(['’])S\b/g, '$1s'); - line = line.replace(/\b(AT&T|Q&A)\b/ig, this.__upperReplacer); - - return line; - }, - - __titleCaseDottedWordReplacer: function (w) { - return (w.match(/[a-zA-Z][.][a-zA-Z]/)) ? w : __TitleCase.__firstToUpperCase(w); - }, - - __lowerReplacer: function (w) { return w.toLowerCase() }, - - __upperReplacer: function (w) { return w.toUpperCase() }, - - __firstToUpperCase: function (w) { - var split = w.split(/(^[^a-zA-Z0-9]*[a-zA-Z0-9])(.*)$/); - if (split[1]) { - split[1] = split[1].toUpperCase(); - } - - return split.join(''); - - - }, - }; - - __TitleCase.init(); - - t = t.replace(/_/g," "); - t = __TitleCase.toTitleCase(t); - - return t; - - } - - }, - - }).init(); - - //'string'.linkify(); - if(!String.linkify) { - String.prototype.linkify = function() { - - // http://, https://, ftp:// - var urlPattern = /\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim; - - // www. sans http:// or https:// - var pseudoUrlPattern = /(^|[^\/])(www\.[\S]+(\b|$))/gim; - - // Email addresses - var emailAddressPattern = /(([a-zA-Z0-9_\-\.]+)@[a-zA-Z_]+?(?:\.[a-zA-Z]{2,6}))+/gim; - - var twitterHandlePattern = /(@([\w]+))/g; - - var twitterSearchPattern = /(#([\w]+))/g; - - return this - .replace(urlPattern, '$&') - .replace(pseudoUrlPattern, '$1$2') - .replace(emailAddressPattern, '$1') - .replace(twitterHandlePattern, "$1") - .replace(twitterSearchPattern, "$1"); - }; - }; - //str.substr(3,4) - /* - * Date Format 1.2.3 - * (c) 2007-2009 Steven Levithan - * MIT license - * - * Includes enhancements by Scott Trenda - * and Kris Kowal - * - * Accepts a date, a mask, or a date and a mask. - * Returns a formatted version of the given date. - * The date defaults to the current date/time. - * The mask defaults to dateFormat.masks.default. - */ - - var dateFormat = function () { - var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, - timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, - timezoneClip = /[^-+\dA-Z]/g, - pad = function (val, len) { - val = String(val); - len = len || 2; - while (val.length < len) val = "0" + val; - return val; - }; - - // Regexes and supporting functions are cached through closure - return function (date, mask, utc) { - var dF = dateFormat; - - // You can't provide utc if you skip other args (use the "UTC:" mask prefix) - if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { - mask = date; - date = undefined; - } - - // Passing date through Date applies Date.parse, if necessary - date = date ? new Date(date) : new Date; - if (isNaN(date)) throw SyntaxError("invalid date"); - - mask = String(dF.masks[mask] || mask || dF.masks["default"]); - - // Allow setting the utc argument via the mask - if (mask.slice(0, 4) == "UTC:") { - mask = mask.slice(4); - utc = true; - } - - var _ = utc ? "getUTC" : "get", - d = date[_ + "Date"](), - D = date[_ + "Day"](), - m = date[_ + "Month"](), - y = date[_ + "FullYear"](), - H = date[_ + "Hours"](), - M = date[_ + "Minutes"](), - s = date[_ + "Seconds"](), - L = date[_ + "Milliseconds"](), - o = utc ? 0 : date.getTimezoneOffset(), - flags = { - d: d, - dd: pad(d), - ddd: dF.i18n.dayNames[D], - dddd: dF.i18n.dayNames[D + 7], - m: m + 1, - mm: pad(m + 1), - mmm: dF.i18n.monthNames[m], - mmmm: dF.i18n.monthNames[m + 12], - yy: String(y).slice(2), - yyyy: y, - h: H % 12 || 12, - hh: pad(H % 12 || 12), - H: H, - HH: pad(H), - M: M, - MM: pad(M), - s: s, - ss: pad(s), - l: pad(L, 3), - L: pad(L > 99 ? Math.round(L / 10) : L), - t: H < 12 ? "a" : "p", - tt: H < 12 ? "am" : "pm", - T: H < 12 ? "A" : "P", - TT: H < 12 ? "AM" : "PM", - Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), - o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), - S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] - }; - - return mask.replace(token, function ($0) { - return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); - }); - }; - }(); - - // Some common format strings - dateFormat.masks = { - "default": "ddd mmm dd yyyy HH:MM:ss", - shortDate: "m/d/yy", - mediumDate: "mmm d, yyyy", - longDate: "mmmm d, yyyy", - fullDate: "dddd, mmmm d, yyyy", - shortTime: "h:MM TT", - mediumTime: "h:MM:ss TT", - longTime: "h:MM:ss TT Z", - isoDate: "yyyy-mm-dd", - isoTime: "HH:MM:ss", - isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", - isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" - }; - - // Internationalization strings - dateFormat.i18n = { - dayNames: [ - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", - "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" - ], - monthNames: [ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" - ] - }; - - // For convenience... - Date.prototype.format = function (mask, utc) { - return dateFormat(this, mask, utc); - }; - -} - -/*********************************************** - Begin VMM.LoadLib.js -***********************************************/ - -/* - LoadLib - Based on LazyLoad by Ryan Grove - https://github.com/rgrove/lazyload/ - Copyright (c) 2011 Ryan Grove - All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the 'Software'), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - -================================================== */ -window.loadedJS = []; - - -if(typeof VMM != 'undefined' && typeof VMM.LoadLib == 'undefined') { - //VMM.LoadLib.js('http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js', onJQueryLoaded); - //VMM.LoadLib.css('http://someurl.css', onCSSLoaded); - - - - VMM.LoadLib = (function (doc) { - var env, - head, - pending = {}, - pollCount = 0, - queue = {css: [], js: []}, - styleSheets = doc.styleSheets; - - var loaded_Array = []; - - function isLoaded(url) { - var has_been_loaded = false; - for(var i=0; i= 0) { - if (styleSheets[i].href === css.urls[0]) { - finish('css'); - break; - } - } - - pollCount += 1; - - if (css) { - if (pollCount < 200) { - setTimeout(pollWebKit, 50); - } else { - - finish('css'); - } - } - } - } - - return { - - css: function (urls, callback, obj, context) { - if (isLoaded(urls)) { - return callback; - } else { - load('css', urls, callback, obj, context); - } - }, - - js: function (urls, callback, obj, context) { - if (isLoaded(urls)) { - return callback; - } else { - load('js', urls, callback, obj, context); - } - } - - }; - })(this.document); -} - - - -/*********************************************** - Begin VMM.Language.js -***********************************************/ - -/* DEFAULT LANGUAGE -================================================== */ -if(typeof VMM != 'undefined' && typeof VMM.Language == 'undefined') { - VMM.Language = { - lang: "en", - api: { - wikipedia: "en" - }, - date: { - month: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - month_abbr: ["Jan.", "Feb.", "March", "April", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec."], - day: ["Sunday","Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], - day_abbr: ["Sun.","Mon.", "Tues.", "Wed.", "Thurs.", "Fri.", "Sat."], - }, - dateformats: { - year: "yyyy", - month_short: "mmm", - month: "mmmm yyyy", - full_short: "mmm d", - full: "mmmm d',' yyyy", - time_no_seconds_short: "h:MM TT", - time_no_seconds_small_date: "h:MM TT'
    'mmmm d',' yyyy''", - full_long: "mmm d',' yyyy 'at' hh:MM TT", - full_long_small_date: "hh:MM TT'
    mmm d',' yyyy''", - }, - messages: { - loading_timeline: "Loading Timeline... ", - return_to_title: "Return to Title", - expand_timeline: "Expand Timeline", - contract_timeline: "Contract Timeline", - wikipedia: "From Wikipedia, the free encyclopedia", - loading_content: "Loading Content" - } - } -}; - -/*********************************************** - Begin AES.js -***********************************************/ - -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -/* AES implementation in JavaScript (c) Chris Veness 2005-2011 */ -/* - see http://csrc.nist.gov/publications/PubsFIPS.html#197 */ -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -var Aes = {}; // Aes namespace - -/** - * AES Cipher function: encrypt 'input' state with Rijndael algorithm - * applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage - * - * @param {Number[]} input 16-byte (128-bit) input state array - * @param {Number[][]} w Key schedule as 2D byte-array (Nr+1 x Nb bytes) - * @returns {Number[]} Encrypted output state array - */ -Aes.cipher = function(input, w) { // main Cipher function [§5.1] - var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES) - var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys - - var state = [[],[],[],[]]; // initialise 4xNb byte-array 'state' with input [§3.4] - for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i]; - - state = Aes.addRoundKey(state, w, 0, Nb); - - for (var round=1; round 6 && i%Nk == 4) { - temp = Aes.subWord(temp); - } - for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t]; - } - - return w; -} - -/* - * ---- remaining routines are private, not called externally ---- - */ - -Aes.subBytes = function(s, Nb) { // apply SBox to state S [§5.1.1] - for (var r=0; r<4; r++) { - for (var c=0; c>> i*8) & 0xff; - for (var i=0; i<2; i++) counterBlock[i+2] = (nonceRnd >>> i*8) & 0xff; - for (var i=0; i<4; i++) counterBlock[i+4] = (nonceSec >>> i*8) & 0xff; - - // and convert it to a string to go on the front of the ciphertext - var ctrTxt = ''; - for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]); - - // generate key schedule - an expansion of the key into distinct Key Rounds for each round - var keySchedule = Aes.keyExpansion(key); - - var blockCount = Math.ceil(plaintext.length/blockSize); - var ciphertxt = new Array(blockCount); // ciphertext as array of strings - - for (var b=0; b>> c*8) & 0xff; - for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8) - - var cipherCntr = Aes.cipher(counterBlock, keySchedule); // -- encrypt counter block -- - - // block size is reduced on final block - var blockLength = b>> c*8) & 0xff; - for (var c=0; c<4; c++) counterBlock[15-c-4] = (((b+1)/0x100000000-1) >>> c*8) & 0xff; - - var cipherCntr = Aes.cipher(counterBlock, keySchedule); // encrypt counter block - - var plaintxtByte = new Array(ciphertext[b].length); - for (var i=0; i 0) { while (c++ < 3) { pad += '='; plain += '\0'; } } - // note: doing padding here saves us doing special-case packing for trailing 1 or 2 chars - - for (c=0; c>18 & 0x3f; - h2 = bits>>12 & 0x3f; - h3 = bits>>6 & 0x3f; - h4 = bits & 0x3f; - - // use hextets to index into code string - e[c/3] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); - } - coded = e.join(''); // join() is far faster than repeated string concatenation in IE - - // replace 'A's from padded nulls with '='s - coded = coded.slice(0, coded.length-pad.length) + pad; - - return coded; -} - -/** - * Decode string from Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648] - * (instance method extending String object). As per RFC 4648, newlines are not catered for. - * - * @param {String} str The string to be decoded from base-64 - * @param {Boolean} [utf8decode=false] Flag to indicate whether str is Unicode string to be decoded - * from UTF8 after conversion from base64 - * @returns {String} decoded string - */ -Base64.decode = function(str, utf8decode) { - utf8decode = (typeof utf8decode == 'undefined') ? false : utf8decode; - var o1, o2, o3, h1, h2, h3, h4, bits, d=[], plain, coded; - var b64 = Base64.code; - - coded = utf8decode ? str.decodeUTF8() : str; - - - for (var c=0; c>>16 & 0xff; - o2 = bits>>>8 & 0xff; - o3 = bits & 0xff; - - d[c/4] = String.fromCharCode(o1, o2, o3); - // check for padding - if (h4 == 0x40) d[c/4] = String.fromCharCode(o1, o2); - if (h3 == 0x40) d[c/4] = String.fromCharCode(o1); - } - plain = d.join(''); // join() is far faster than repeated string concatenation in IE - - return utf8decode ? plain.decodeUTF8() : plain; -} - - -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -/* Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple */ -/* single-byte character encoding (c) Chris Veness 2002-2011 */ -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -var Utf8 = {}; // Utf8 namespace - -/** - * Encode multi-byte Unicode string into utf-8 multiple single-byte characters - * (BMP / basic multilingual plane only) - * - * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars - * - * @param {String} strUni Unicode string to be encoded as UTF-8 - * @returns {String} encoded string - */ -Utf8.encode = function(strUni) { - // use regular expressions & String.replace callback function for better efficiency - // than procedural approaches - var strUtf = strUni.replace( - /[\u0080-\u07ff]/g, // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz - function(c) { - var cc = c.charCodeAt(0); - return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); } - ); - strUtf = strUtf.replace( - /[\u0800-\uffff]/g, // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz - function(c) { - var cc = c.charCodeAt(0); - return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); } - ); - return strUtf; -} - -/** - * Decode utf-8 encoded string back into multi-byte Unicode characters - * - * @param {String} strUtf UTF-8 string to be decoded back to Unicode - * @returns {String} decoded string - */ -Utf8.decode = function(strUtf) { - // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char! - var strUni = strUtf.replace( - /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars - function(c) { // (note parentheses for precence) - var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f); - return String.fromCharCode(cc); } - ); - strUni = strUni.replace( - /[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars - function(c) { // (note parentheses for precence) - var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f; - return String.fromCharCode(cc); } - ); - return strUni; -} - -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -/*********************************************** - Begin bootstrap-tooltip.js -***********************************************/ - -/* =========================================================== - * bootstrap-tooltip.js v2.0.1 - * http://twitter.github.com/bootstrap/javascript.html#tooltips - * Inspired by the original jQuery.tipsy by Jason Frame - * =========================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - -!function( $ ) { - - "use strict" - - /* TOOLTIP PUBLIC CLASS DEFINITION - * =============================== */ - - var Tooltip = function ( element, options ) { - this.init('tooltip', element, options) - } - - Tooltip.prototype = { - - constructor: Tooltip - - , init: function ( type, element, options ) { - var eventIn - , eventOut - - this.type = type - this.$element = $(element) - this.options = this.getOptions(options) - this.enabled = true - - if (this.options.trigger != 'manual') { - eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus' - eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur' - this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this)) - this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this)) - } - - this.options.selector ? - (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : - this.fixTitle() - } - - , getOptions: function ( options ) { - options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data()) - - if (options.delay && typeof options.delay == 'number') { - options.delay = { - show: options.delay - , hide: options.delay - } - } - - return options - } - - , enter: function ( e ) { - var self = $(e.currentTarget)[this.type](this._options).data(this.type) - - if (!self.options.delay || !self.options.delay.show) { - self.show() - } else { - self.hoverState = 'in' - setTimeout(function() { - if (self.hoverState == 'in') { - self.show() - } - }, self.options.delay.show) - } - } - - , leave: function ( e ) { - var self = $(e.currentTarget)[this.type](this._options).data(this.type) - - if (!self.options.delay || !self.options.delay.hide) { - self.hide() - } else { - self.hoverState = 'out' - setTimeout(function() { - if (self.hoverState == 'out') { - self.hide() - } - }, self.options.delay.hide) - } - } - - , show: function () { - var $tip - , inside - , pos - , actualWidth - , actualHeight - , placement - , tp - - if (this.hasContent() && this.enabled) { - $tip = this.tip() - this.setContent() - - if (this.options.animation) { - $tip.addClass('fade') - } - - placement = typeof this.options.placement == 'function' ? - this.options.placement.call(this, $tip[0], this.$element[0]) : - this.options.placement - - inside = /in/.test(placement) - - $tip - .remove() - .css({ top: 0, left: 0, display: 'block' }) - .appendTo(inside ? this.$element : document.body) - - pos = this.getPosition(inside) - - actualWidth = $tip[0].offsetWidth - actualHeight = $tip[0].offsetHeight - - switch (inside ? placement.split(' ')[1] : placement) { - case 'bottom': - tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2} - break - case 'top': - tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2} - break - case 'left': - tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth} - break - case 'right': - tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width} - break - } - - $tip - .css(tp) - .addClass(placement) - .addClass('in') - } - } - - , setContent: function () { - var $tip = this.tip() - $tip.find('.tooltip-inner').html(this.getTitle()) - $tip.removeClass('fade in top bottom left right') - } - - , hide: function () { - var that = this - , $tip = this.tip() - - $tip.removeClass('in') - - function removeWithAnimation() { - var timeout = setTimeout(function () { - $tip.off($.support.transition.end).remove() - }, 500) - - $tip.one($.support.transition.end, function () { - clearTimeout(timeout) - $tip.remove() - }) - } - - $.support.transition && this.$tip.hasClass('fade') ? - removeWithAnimation() : - $tip.remove() - } - - , fixTitle: function () { - var $e = this.$element - if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') { - $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title') - } - } - - , hasContent: function () { - return this.getTitle() - } - - , getPosition: function (inside) { - return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), { - width: this.$element[0].offsetWidth - , height: this.$element[0].offsetHeight - }) - } - - , getTitle: function () { - var title - , $e = this.$element - , o = this.options - - title = $e.attr('data-original-title') - || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) - - title = title.toString().replace(/(^\s*|\s*$)/, "") - - return title - } - - , tip: function () { - return this.$tip = this.$tip || $(this.options.template) - } - - , validate: function () { - if (!this.$element[0].parentNode) { - this.hide() - this.$element = null - this.options = null - } - } - - , enable: function () { - this.enabled = true - } - - , disable: function () { - this.enabled = false - } - - , toggleEnabled: function () { - this.enabled = !this.enabled - } - - , toggle: function () { - this[this.tip().hasClass('in') ? 'hide' : 'show']() - } - - } - - - /* TOOLTIP PLUGIN DEFINITION - * ========================= */ - - $.fn.tooltip = function ( option ) { - return this.each(function () { - var $this = $(this) - , data = $this.data('tooltip') - , options = typeof option == 'object' && option - if (!data) $this.data('tooltip', (data = new Tooltip(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.tooltip.Constructor = Tooltip - - $.fn.tooltip.defaults = { - animation: true - , delay: 0 - , selector: false - , placement: 'top' - , trigger: 'hover' - , title: '' - , template: '
    ' - } - -}( window.jQuery ); - -/*********************************************** - Begin bootstrap-tooltip.js -***********************************************/ - -/* =========================================================== - * bootstrap-tooltip.js v2.0.1 - * http://twitter.github.com/bootstrap/javascript.html#tooltips - * Inspired by the original jQuery.tipsy by Jason Frame - * =========================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - -!function( $ ) { - - "use strict" - - /* TOOLTIP PUBLIC CLASS DEFINITION - * =============================== */ - - var Tooltip = function ( element, options ) { - this.init('tooltip', element, options) - } - - Tooltip.prototype = { - - constructor: Tooltip - - , init: function ( type, element, options ) { - var eventIn - , eventOut - - this.type = type - this.$element = $(element) - this.options = this.getOptions(options) - this.enabled = true - - if (this.options.trigger != 'manual') { - eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus' - eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur' - this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this)) - this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this)) - } - - this.options.selector ? - (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : - this.fixTitle() - } - - , getOptions: function ( options ) { - options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data()) - - if (options.delay && typeof options.delay == 'number') { - options.delay = { - show: options.delay - , hide: options.delay - } - } - - return options - } - - , enter: function ( e ) { - var self = $(e.currentTarget)[this.type](this._options).data(this.type) - - if (!self.options.delay || !self.options.delay.show) { - self.show() - } else { - self.hoverState = 'in' - setTimeout(function() { - if (self.hoverState == 'in') { - self.show() - } - }, self.options.delay.show) - } - } - - , leave: function ( e ) { - var self = $(e.currentTarget)[this.type](this._options).data(this.type) - - if (!self.options.delay || !self.options.delay.hide) { - self.hide() - } else { - self.hoverState = 'out' - setTimeout(function() { - if (self.hoverState == 'out') { - self.hide() - } - }, self.options.delay.hide) - } - } - - , show: function () { - var $tip - , inside - , pos - , actualWidth - , actualHeight - , placement - , tp - - if (this.hasContent() && this.enabled) { - $tip = this.tip() - this.setContent() - - if (this.options.animation) { - $tip.addClass('fade') - } - - placement = typeof this.options.placement == 'function' ? - this.options.placement.call(this, $tip[0], this.$element[0]) : - this.options.placement - - inside = /in/.test(placement) - - $tip - .remove() - .css({ top: 0, left: 0, display: 'block' }) - .appendTo(inside ? this.$element : document.body) - - pos = this.getPosition(inside) - - actualWidth = $tip[0].offsetWidth - actualHeight = $tip[0].offsetHeight - - switch (inside ? placement.split(' ')[1] : placement) { - case 'bottom': - tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2} - break - case 'top': - tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2} - break - case 'left': - tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth} - break - case 'right': - tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width} - break - } - - $tip - .css(tp) - .addClass(placement) - .addClass('in') - } - } - - , setContent: function () { - var $tip = this.tip() - $tip.find('.tooltip-inner').html(this.getTitle()) - $tip.removeClass('fade in top bottom left right') - } - - , hide: function () { - var that = this - , $tip = this.tip() - - $tip.removeClass('in') - - function removeWithAnimation() { - var timeout = setTimeout(function () { - $tip.off($.support.transition.end).remove() - }, 500) - - $tip.one($.support.transition.end, function () { - clearTimeout(timeout) - $tip.remove() - }) - } - - $.support.transition && this.$tip.hasClass('fade') ? - removeWithAnimation() : - $tip.remove() - } - - , fixTitle: function () { - var $e = this.$element - if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') { - $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title') - } - } - - , hasContent: function () { - return this.getTitle() - } - - , getPosition: function (inside) { - return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), { - width: this.$element[0].offsetWidth - , height: this.$element[0].offsetHeight - }) - } - - , getTitle: function () { - var title - , $e = this.$element - , o = this.options - - title = $e.attr('data-original-title') - || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) - - title = title.toString().replace(/(^\s*|\s*$)/, "") - - return title - } - - , tip: function () { - return this.$tip = this.$tip || $(this.options.template) - } - - , validate: function () { - if (!this.$element[0].parentNode) { - this.hide() - this.$element = null - this.options = null - } - } - - , enable: function () { - this.enabled = true - } - - , disable: function () { - this.enabled = false - } - - , toggleEnabled: function () { - this.enabled = !this.enabled - } - - , toggle: function () { - this[this.tip().hasClass('in') ? 'hide' : 'show']() - } - - } - - - /* TOOLTIP PLUGIN DEFINITION - * ========================= */ - - $.fn.tooltip = function ( option ) { - return this.each(function () { - var $this = $(this) - , data = $this.data('tooltip') - , options = typeof option == 'object' && option - if (!data) $this.data('tooltip', (data = new Tooltip(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.tooltip.Constructor = Tooltip - - $.fn.tooltip.defaults = { - animation: true - , delay: 0 - , selector: false - , placement: 'top' - , trigger: 'hover' - , title: '' - , template: '
    ' - } - -}( window.jQuery ); - -/*********************************************** - Begin VMM.Timeline.js -***********************************************/ - -/*! - Timeline - Designed and built by Zach Wise at VéritéCo - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - http://www.gnu.org/licenses/ - -*/ - -/* CodeKit Import - http://incident57.com/codekit/ -================================================== */ -// @codekit-prepend "VMM.Timeline.License.js"; -// @codekit-prepend "VMM.js"; -// @codekit-prepend "VMM.Library.js"; -// @codekit-prepend "VMM.Browser.js"; -// @codekit-prepend "VMM.MediaElement.js"; -// @codekit-prepend "VMM.MediaType.js"; -// @codekit-prepend "VMM.Media.js"; -// @codekit-prepend "VMM.FileExtention.js"; -// @codekit-prepend "VMM.ExternalAPI.js"; -// @codekit-prepend "VMM.TouchSlider.js"; -// @codekit-prepend "VMM.DragSlider.js"; -// @codekit-prepend "VMM.Slider.js"; -// @codekit-prepend "VMM.Slider.Slide.js"; -// @codekit-prepend "VMM.Util.js"; -// @codekit-prepend "VMM.LoadLib.js"; -// @codekit-prepend "VMM.Language.js"; - -// @codekit-append "VMM.Timeline.TimeNav.js"; -// @codekit-append "VMM.Timeline.DataObj.js"; - -// @codekit-prepend "lib/AES.js"; -// @codekit-prepend "lib/bootstrap-tooltip.js"; - - - - -/* Timeline -================================================== */ - -if(typeof VMM != 'undefined' && typeof VMM.Timeline == 'undefined') { - - VMM.Timeline = function(w, h, conf, _timeline_id) { - - var $timeline, $feedback, $messege, slider, timenav, version, timeline_id; - var events = {}, data = {}, _dates = [], config = {}; - var has_width = false, has_height = false, ie7 = false, is_moving = false; - - if (type.of(_timeline_id) == "string") { - timeline_id = _timeline_id; - } else { - timeline_id = "#timeline"; - } - - version = "1.35"; - - trace("TIMELINE VERSION " + version); - - /* CONFIG - ================================================== */ - config = { - embed: false, - events: { - data_ready: "DATAREADY", - messege: "MESSEGE", - headline: "TIMELINE_HEADLINE", - slide_change: "SLIDE_CHANGE", - resize: "resize" - }, - id: timeline_id, - type: "timeline", - maptype: "toner", - preload: 4, - current_slide: 0, - hash_bookmark: false, - start_at_end: false, - start_page: false, - api_keys: { - google: "", - flickr: "", - twitter: "" - }, - interval: 10, - something: 0, - width: 960, - height: 540, - spacing: 15, - loaded: { - slider: false, - timenav: false, - percentloaded: 0 - }, - nav: { - start_page: false, - interval_width: 200, - density: 4, - minor_width: 0, - multiplier: { - current: 6, - min: .1, - max: 50 - }, - rows: [1, 1, 1], - width: 960, - height: 200, - marker: { - width: 150, - height: 48 - } - }, - feature: { - width: 960, - height: 540 - }, - slider: { - width: 720, - height: 400, - content: { - width: 720, - height: 400, - padding: 130, - }, - nav: { - width: 100, - height: 200 - } - }, - ease: "easeInOutExpo", - duration: 1000, - language: VMM.Language - }; - - if ( w != null && w != "") { - config.width = w; - has_width = true; - } - - if ( h != null && h != "") { - config.height = h; - has_height = true; - } - - if(window.location.hash) { - var hash = window.location.hash.substring(1); - if (!isNaN(hash)) { - config.current_slide = parseInt(hash); - } - } - - window.onhashchange = function () { - if (config.hash_bookmark) { - if (is_moving) { - var hash = window.location.hash.substring(1); - goToEvent(parseInt(hash)); - } else { - is_moving = false; - } - } - } - - /* CREATE CONFIG - ================================================== */ - var createConfig = function(conf) { - - // APPLY SUPPLIED CONFIG TO TIMELINE CONFIG - if (typeof timeline_config == 'object') { - trace("HAS TIMELINE CONFIG"); - var x; - for (x in timeline_config) { - if (Object.prototype.hasOwnProperty.call(timeline_config, x)) { - config[x] = timeline_config[x]; - } - } - } else if (typeof conf == 'object') { - var x; - for (x in conf) { - if (Object.prototype.hasOwnProperty.call(conf, x)) { - config[x] = conf[x]; - } - } - } - - config.nav.width = config.width; - config.nav.height = 200; - config.feature.width = config.width; - config.feature.height = config.height - config.nav.height; - VMM.Timeline.Config = config; - VMM.master_config.Timeline = VMM.Timeline.Config; - this.events = config.events; - } - - /* CREATE TIMELINE STRUCTURE - ================================================== */ - var createStructure = function(w, h) { - $timeline = VMM.getElement(timeline_id); - - VMM.Lib.addClass(timeline_id, "vmm-timeline"); - - $feedback = VMM.appendAndGetElement($timeline, "
    ", "feedback", ""); - $messege = VMM.appendAndGetElement($feedback, "
    ", "messege", "Timeline"); - slider = new VMM.Slider(timeline_id + " div.slider", config); - timenav = new VMM.Timeline.TimeNav(timeline_id + " div.navigation"); - - if (!has_width) { - config.width = VMM.Lib.width($timeline); - } else { - VMM.Lib.width($timeline, config.width); - } - - if (!has_height) { - config.height = VMM.Lib.height($timeline); - } else { - VMM.Lib.height($timeline, config.height); - } - - } - - /* ON EVENT - ================================================== */ - - function onDataReady(e, d) { - trace("onDataReady"); - trace(d); - data = d.timeline; - - if (type.of(data.era) == "array") { - - } else { - data.era = []; - } - - buildDates(); - - }; - - function onDatesProcessed() { - build(); - } - - function reSize() { - updateSize(); - slider.setSize(config.feature.width, config.feature.height); - timenav.setSize(config.width, config.height); - }; - - function onSliderLoaded(e) { - config.loaded.slider = true; - onComponentLoaded(); - }; - - function onComponentLoaded(e) { - config.loaded.percentloaded = config.loaded.percentloaded + 25; - - if (config.loaded.slider && config.loaded.timenav) { - hideMessege(); - } - } - - function onTimeNavLoaded(e) { - config.loaded.timenav = true; - onComponentLoaded(); - } - - function onSlideUpdate(e) { - is_moving = true; - config.current_slide = slider.getCurrentNumber(); - setHash(config.current_slide); - timenav.setMarker(config.current_slide, config.ease,config.duration); - }; - - function onMarkerUpdate(e) { - is_moving = true; - config.current_slide = timenav.getCurrentNumber(); - setHash(config.current_slide); - slider.setSlide(config.current_slide); - }; - - var goToEvent = function(n) { - if (n <= _dates.length - 1 && n >= 0) { - config.current_slide = n; - slider.setSlide(config.current_slide); - timenav.setMarker(config.current_slide, config.ease,config.duration); - } - } - - function setHash(n) { - if (config.hash_bookmark) { - window.location.hash = "#" + n.toString(); - } - } - - /* PUBLIC FUNCTIONS - ================================================== */ - this.init = function(_data, _timeline_id, conf) { - - if (type.of(_timeline_id) == "string") { - if (_timeline_id.match("#")) { - timeline_id = _timeline_id; - } else { - timeline_id = "#" + _timeline_id; - } - } - - createConfig(conf); - createStructure(w,h); - - trace('TIMELINE INIT'); - VMM.Util.date.setLanguage(VMM.Timeline.Config.language); - VMM.master_config.language = VMM.Timeline.Config.language; - - $feedback = VMM.appendAndGetElement($timeline, "
    ", "feedback", ""); - $messege = VMM.appendAndGetElement($feedback, "
    ", "messege", VMM.master_config.language.messages.loading_timeline); - - VMM.bindEvent(global, onDataReady, config.events.data_ready); - VMM.bindEvent(global, showMessege, config.events.messege); - - /* GET DATA - ================================================== */ - - if (VMM.Browser.browser == "MSIE" && parseInt(VMM.Browser.version, 10) == 7) { - ie7 = true; - VMM.fireEvent(global, config.events.messege, "Internet Explorer 7 is not supported by #Timeline."); - } else { - if (type.of(_data) == "string" || type.of(_data) == "object") { - VMM.Timeline.DataObj.getData(_data); - } else { - VMM.Timeline.DataObj.getData(VMM.getElement(timeline_id)); - } - } - - }; - - this.iframeLoaded = function() { - trace("iframeLoaded"); - }; - - this.reload = function(_d) { - trace("loadNewDates" + _d); - $messege = VMM.appendAndGetElement($feedback, "
    ", "messege", VMM.master_config.language.messages.loading_timeline); - data = {}; - VMM.Timeline.DataObj.getData(_d); - }; - - /* DATA - ================================================== */ - var getData = function(url) { - VMM.getJSON(url, function(d) { - data = VMM.Timeline.DataObj.getData(d); - VMM.fireEvent(global, config.events.data_ready); - }); - }; - - /* MESSEGES - ================================================== */ - - var showMessege = function(e, msg) { - trace("showMessege " + msg); - VMM.attachElement($messege, msg); - }; - - var hideMessege = function() { - VMM.Lib.animate($feedback, config.duration, config.ease*4, {"opacity": 0}, detachMessege); - }; - - var detachMessege = function() { - VMM.Lib.detach($feedback); - } - - /* BUILD DISPLAY - ================================================== */ - var build = function() { - - // START AT END? - if (config.start_at_end) { - config.current_slide = _dates.length - 1; - } - // CREATE DOM STRUCTURE - VMM.attachElement($timeline, ""); - VMM.appendElement($timeline, "
    "); - - reSize(); - - VMM.bindEvent("div.slider", onSliderLoaded, "LOADED"); - VMM.bindEvent("div.navigation", onTimeNavLoaded, "LOADED"); - VMM.bindEvent("div.slider", onSlideUpdate, "UPDATE"); - VMM.bindEvent("div.navigation", onMarkerUpdate, "UPDATE"); - - slider.init(_dates); - timenav.init(_dates, data.era); - - // RESIZE EVENT LISTENERS - VMM.bindEvent(global, reSize, config.events.resize); - //VMM.bindEvent(global, function(e) {e.preventDefault()}, "touchmove"); - - }; - - var updateSize = function() { - trace("UPDATE SIZE"); - config.width = VMM.Lib.width($timeline); - config.height = VMM.Lib.height($timeline); - - config.nav.width = config.width; - config.feature.width = config.width; - - if (VMM.Browser.device == "mobile") { - //config.feature.height = config.height; - } else { - //config.feature.height = config.height - config.nav.height - 3; - } - config.feature.height = config.height - config.nav.height - 3; - }; - - // BUILD DATE OBJECTS - var buildDates = function() { - - updateSize(); - _dates = []; - VMM.fireEvent(global, config.events.messege, "Building Dates"); - for(var i = 0; i < data.date.length; i++) { - - if (data.date[i].startDate != null && data.date[i].startDate != "") { - - var _date = {}; - - // START DATE - if (data.date[i].type == "tweets") { - _date.startdate = VMM.ExternalAPI.twitter.parseTwitterDate(data.date[i].startDate); - } else { - _date.startdate = VMM.Util.date.parse(data.date[i].startDate); - } - - _date.uniqueid = (data.date[i].startDate).toString() + "-" + i.toString(); - - // END DATE - if (data.date[i].endDate != null && data.date[i].endDate != "") { - if (data.date[i].type == "tweets") { - _date.enddate = VMM.ExternalAPI.twitter.parseTwitterDate(data.date[i].endDate); - } else { - _date.enddate = VMM.Util.date.parse(data.date[i].endDate); - } - } else { - _date.enddate = _date.startdate; - } - - _date.title = data.date[i].headline; - _date.headline = data.date[i].headline; - _date.type = data.date[i].type; - _date.date = VMM.Util.date.prettyDate(_date.startdate); - _date.startdate_str = VMM.Util.date.prettyDate(_date.startdate); - _date.enddate_str = VMM.Util.date.prettyDate(_date.enddate); - _date.asset = data.date[i].asset; - _date.fulldate = _date.startdate.getTime(); - _date.text = data.date[i].text; - _date.content = ""; - _date.tag = data.date[i].tag; - - - _dates.push(_date); - - } - - }; - - /* CUSTOM SORT - ================================================== */ - _dates.sort(function(a, b){ - return a.fulldate - b.fulldate - }); - - /* CREATE START PAGE IF AVAILABLE - ================================================== */ - if (data.headline != null && data.headline != "" && data.text != null && data.text != "") { - trace("HAS STARTPAGE"); - var _date = {}; - var td_num = 0; - var td = _dates[0].startdate; - _date.startdate = new Date(_dates[0].startdate); - - if (td.getMonth() === 0 && td.getDate() == 1 && td.getHours() === 0 && td.getMinutes() === 0 ) { - // trace("YEAR ONLY"); - _date.startdate.setFullYear(td.getFullYear() - 1); - } else if (td.getDate() <= 1 && td.getHours() === 0 && td.getMinutes() === 0) { - // trace("YEAR MONTH"); - _date.startdate.setMonth(td.getMonth() - 1); - } else if (td.getHours() === 0 && td.getMinutes() === 0) { - // trace("YEAR MONTH DAY"); - _date.startdate.setDate(td.getDate() - 1); - } else if (td.getMinutes() === 0) { - // trace("YEAR MONTH DAY HOUR"); - _date.startdate.setHours(td.getHours() - 1); - } else { - // trace("YEAR MONTH DAY HOUR MINUTE"); - _date.startdate.setMinutes(td.getMinutes() - 1); - } - - _date.uniqueid = VMM.Util.unique_ID(5); - _date.enddate = _date.startdate; - _date.title = data.headline; - _date.headline = data.headline; - _date.text = data.text; - _date.type = "start"; - _date.date = VMM.Util.date.prettyDate(data.startDate); - _date.asset = data.asset; - _date.fulldate = _date.startdate.getTime(); - - if (config.embed) { - VMM.fireEvent(global, config.events.headline, _date.headline); - } - - _dates.push(_date); - } - - /* CUSTOM SORT - ================================================== */ - _dates.sort(function(a, b){ - return a.fulldate - b.fulldate - }); - - onDatesProcessed(); - } - - }; - - VMM.Timeline.Config = {}; - -}; - -/*********************************************** - Begin VMM.Timeline.TimeNav.js -***********************************************/ - -/* TIMELINE NAVIGATION -================================================== */ - -if(typeof VMM.Timeline != 'undefined' && typeof VMM.Timeline.TimeNav == 'undefined') { - - VMM.Timeline.TimeNav = function(parent, content_width, content_height) { - trace("VMM.Timeline.TimeNav"); - - var events = {}, timespan = {}, layout = parent; - var data = [], era_markers = [], markers = [], interval_array = [], interval_major_array = [], eras, content, tags = []; - - var current_marker = 0; - var _active = false; - var timelookup = {day: 24, month: 12, year: 10, hour: 60, minute: 60, second: 1000, decade: 10, century: 100, millenium: 1000, age: 1000000, epoch: 10000000, era: 100000000, eon: 500000000, week: 4.34812141, days_in_month: 30.4368499, days_in_week: 7, weeks_in_month:4.34812141, weeks_in_year:52.177457, days_in_year: 365.242199, hours_in_day: 24 }; - var dateFractionBrowser = {day: 86400000, week: 7, month: 30.4166666667, year: 12, hour: 24, minute: 1440, second: 86400, decade: 10, century: 100, millenium: 1000, age: 1000000, epoch: 10000000, era: 100000000, eon: 500000000 }; - - var interval = {type: "year", number: 10, first: 1970, last: 2011, multiplier: 100, classname:"_idd", interval_type:"interval"}; - var interval_major = {type: "year", number: 10, first: 1970, last: 2011, multiplier: 100, classname:"major", interval_type:"interval major"}; - var interval_macro = {type: "year", number: 10, first: 1970, last: 2011, multiplier: 100, classname:"_dd_minor", interval_type:"interval minor"}; - var interval_calc = {day: {},month: {},year: {},hour: {},minute: {}, second: {},decade: {},century: {},millenium: {},week: {}, age: {}, epoch: {}, era: {}, eon: {} }; - - /* ELEMENTS - ================================================== */ - var $timenav, $content, $time, $timeintervalminor, $timeinterval, $timeintervalmajor, $timebackground, - $timeintervalbackground, $timenavline, $timenavindicator, $timeintervalminor_minor, $toolbar, $zoomin, $zoomout; - - /* ADD to Config - ================================================== */ - var config = VMM.Timeline.Config; - config.nav.rows = [config.nav.marker.height, config.nav.marker.height*2, 1]; - - if (content_width != null && content_width != "") { - config.nav.width = content_width; - } - if (content_height != null && content_height != "") { - config.nav.height = content_height; - } - - /* - config.nav.density = 2; - config.nav.multiplier = { - current: 6, - min: .1, - max: 50 - }; - */ - - /* INIT - ================================================== */ - this.init = function(d,e) { - trace('VMM.Timeline.TimeNav init'); - // need to evaluate d - // some function to determine type of data and prepare it - if(typeof d != 'undefined') { - this.setData(d, e); - } else { - trace("WAITING ON DATA"); - } - }; - - /* GETTERS AND SETTERS - ================================================== */ - this.setData = function(d,e) { - if(typeof d != 'undefined') { - data = {}; - data = d; - eras = e; - build(); - } else{ - trace("NO DATA"); - } - }; - - this.setSize = function(w, h) { - if (w != null) {config.width = w}; - if (h != null) {config.height = h}; - if (_active) { - reSize(); - } - - - } - - this.setMarker = function(n, ease, duration, fast) { - goToMarker(n, ease, duration); - } - - this.getCurrentNumber = function() { - return current_marker; - } - - /* ON EVENT - ================================================== */ - - function onConfigSet() { - trace("onConfigSet"); - }; - - function reSize(firstrun) { - VMM.Lib.css($timenavline, "left", Math.round(config.width/2)+2); - VMM.Lib.css($timenavindicator, "left", Math.round(config.width/2)-8); - goToMarker(config.current_slide, config.ease, config.duration, true, firstrun); - }; - - function upDate() { - VMM.fireEvent(layout, "UPDATE"); - } - - function onZoomIn() { - VMM.DragSlider.cancelSlide(); - if (config.nav.multiplier.current > config.nav.multiplier.min) { - if (config.nav.multiplier.current <= 1) { - config.nav.multiplier.current = config.nav.multiplier.current - .25; - } else { - if (config.nav.multiplier.current > 5) { - if (config.nav.multiplier.current > 16) { - config.nav.multiplier.current = Math.round(config.nav.multiplier.current - 10); - } else { - config.nav.multiplier.current = Math.round(config.nav.multiplier.current - 4); - } - } else { - config.nav.multiplier.current = Math.round(config.nav.multiplier.current - 1); - } - - } - if (config.nav.multiplier.current <= 0) { - config.nav.multiplier.current = config.nav.multiplier.min; - } - refreshTimeline(); - } - } - - function onZoomOut() { - VMM.DragSlider.cancelSlide(); - if (config.nav.multiplier.current < config.nav.multiplier.max) { - if (config.nav.multiplier.current > 4) { - if (config.nav.multiplier.current > 16) { - config.nav.multiplier.current = Math.round(config.nav.multiplier.current + 10); - } else { - config.nav.multiplier.current = Math.round(config.nav.multiplier.current + 4); - } - } else { - config.nav.multiplier.current = Math.round(config.nav.multiplier.current + 1); - } - - if (config.nav.multiplier.current >= config.nav.multiplier.max) { - config.nav.multiplier.current = config.nav.multiplier.max; - } - refreshTimeline(); - } - } - - function onBackHome(e) { - VMM.DragSlider.cancelSlide(); - goToMarker(0); - upDate(); - } - - var refreshTimeline = function() { - trace("config.nav.multiplier " + config.nav.multiplier.current); - positionMarkers(true); - positionInterval(interval_array, true, true); - positionInterval(interval_major_array, true); - }; - - /* MARKER EVENTS - ================================================== */ - function onMarkerClick(e) { - VMM.DragSlider.cancelSlide(); - goToMarker(e.data.number); - upDate(); - }; - - function onMarkerHover(e) { - VMM.Lib.toggleClass(e.data.elem, "zFront"); - }; - - var goToMarker = function(n, ease, duration, fast, firstrun) { - - current_marker = n; - var _ease = config.ease; - var _duration = config.duration; - var is_last = false; - var is_first = false; - var _pos = VMM.Lib.position(markers[current_marker].marker); - - if (current_marker == 0) { - is_first = true; - } - if (current_marker +1 == markers.length) { - is_last = true - } - if (ease != null && ease != "") {_ease = ease}; - if (duration != null && duration != "") {_duration = duration}; - - // set marker style - for(var i = 0; i < markers.length; i++) { - VMM.Lib.removeClass(markers[i].marker, "active"); - } - - if (config.start_page && markers[0].type == "start") { - VMM.Lib.visible(markers[0].marker, false); - VMM.Lib.addClass(markers[0].marker, "start"); - } - - VMM.Lib.addClass(markers[current_marker].marker, "active"); - - // ANIMATE MARKER - VMM.Lib.stop($timenav); - VMM.Lib.animate($timenav, _duration, _ease, {"left": (config.width/2) - (_pos.left)}); - - } - - /* TOUCH EVENTS - ================================================== */ - function onTouchUpdate(e, b) { - VMM.Lib.animate($timenav, b.time/2, config.ease, {"left": b.left}); - }; - - /* CALCULATIONS - ================================================== */ - var averageMarkerPositionDistance = function() { - var last_pos = 0; - var pos = 0; - var pos_dif = 0; - var mp_diff = []; - - for(var i = 0; i < markers.length; i++) { - if (data[i].type == "start") { - - } else { - var _pos = positionOnTimeline(interval, data[i].startdate, data[i].enddate); - last_pos = pos; - pos = _pos.begin; - pos_dif = pos - last_pos; - mp_diff.push(pos_dif); - } - } - return VMM.Util.average(mp_diff).mean; - } - - var averageDateDistance = function() { - var last_dd = 0; - var dd = 0; - var date_dif = 0; - var date_diffs = []; - var is_first_date = true; - - for(var i = 0; i < data.length; i++) { - if (data[i].type == "start") { - trace("DATA DATE IS START") - } else { - var _dd = data[i].startdate; - last_dd = dd; - dd = _dd; - date_dif = dd - last_dd; - - date_diffs.push(date_dif); - } - } - return VMM.Util.average(date_diffs); - } - - var calculateMultiplier = function() { - var temp_multiplier = config.nav.multiplier.current; - for(var i = 0; i < temp_multiplier; i++) { - if (averageMarkerPositionDistance() < 75) { - if (config.nav.multiplier.current > 1) { - config.nav.multiplier.current = config.nav.multiplier.current - 1; - } - } - } - } - - var calculateInterval = function() { - // NEED TO REWRITE ALL OF THIS - var _first = getDateFractions(data[0].startdate); - var _last = getDateFractions(data[data.length - 1].enddate); - - // EON - interval_calc.eon.type = "eon"; - interval_calc.eon.first = _first.eons; - interval_calc.eon.base = Math.floor(_first.eons); - interval_calc.eon.last = _last.eons; - interval_calc.eon.number = timespan.eons; - interval_calc.eon.multiplier = timelookup.eons; - interval_calc.eon.minor = timelookup.eons; - - // ERA - interval_calc.era.type = "era"; - interval_calc.era.first = _first.eras; - interval_calc.era.base = Math.floor(_first.eras); - interval_calc.era.last = _last.eras; - interval_calc.era.number = timespan.eras; - interval_calc.era.multiplier = timelookup.eras; - interval_calc.era.minor = timelookup.eras; - - // EPOCH - interval_calc.epoch.type = "epoch"; - interval_calc.epoch.first = _first.epochs; - interval_calc.epoch.base = Math.floor(_first.epochs); - interval_calc.epoch.last = _last.epochs; - interval_calc.epoch.number = timespan.epochs; - interval_calc.epoch.multiplier = timelookup.epochs; - interval_calc.epoch.minor = timelookup.epochs; - - // AGE - interval_calc.age.type = "age"; - interval_calc.age.first = _first.ages; - interval_calc.age.base = Math.floor(_first.ages); - interval_calc.age.last = _last.ages; - interval_calc.age.number = timespan.ages; - interval_calc.age.multiplier = timelookup.ages; - interval_calc.age.minor = timelookup.ages; - - // MILLENIUM - interval_calc.millenium.type = "millenium"; - interval_calc.millenium.first = _first.milleniums; - interval_calc.millenium.base = Math.floor(_first.milleniums); - interval_calc.millenium.last = _last.milleniums; - interval_calc.millenium.number = timespan.milleniums; - interval_calc.millenium.multiplier = timelookup.millenium; - interval_calc.millenium.minor = timelookup.millenium; - - // CENTURY - interval_calc.century.type = "century"; - interval_calc.century.first = _first.centuries; - interval_calc.century.base = Math.floor(_first.centuries); - interval_calc.century.last = _last.centuries; - interval_calc.century.number = timespan.centuries; - interval_calc.century.multiplier = timelookup.century; - interval_calc.century.minor = timelookup.century; - - // DECADE - interval_calc.decade.type = "decade"; - interval_calc.decade.first = _first.decades; - interval_calc.decade.base = Math.floor(_first.decades); - interval_calc.decade.last = _last.decades; - interval_calc.decade.number = timespan.decades; - interval_calc.decade.multiplier = timelookup.decade; - interval_calc.decade.minor = timelookup.decade; - - // YEAR - interval_calc.year.type = "year"; - interval_calc.year.first = _first.years; - interval_calc.year.base = Math.floor(_first.years); - interval_calc.year.last = _last.years; - interval_calc.year.number = timespan.years; - interval_calc.year.multiplier = 1; - interval_calc.year.minor = timelookup.month; - - // MONTH - interval_calc.month.type = "month"; - interval_calc.month.first = _first.months; - interval_calc.month.base = Math.floor(_first.months); - interval_calc.month.last = _last.months; - interval_calc.month.number = timespan.months; - interval_calc.month.multiplier = 1; - interval_calc.month.minor = Math.round(timelookup.week); - - // WEEK - // NOT DONE - interval_calc.week.type = "week"; - interval_calc.week.first = _first.weeks; - interval_calc.week.base = Math.floor(_first.weeks); - interval_calc.week.last = _last.weeks; - interval_calc.week.number = timespan.weeks; - interval_calc.week.multiplier = 1; - interval_calc.week.minor = 7; - - // DAY - interval_calc.day.type = "day"; - interval_calc.day.first = _first.days; - interval_calc.day.base = Math.floor(_first.days); - interval_calc.day.last = _last.days; - interval_calc.day.number = timespan.days; - interval_calc.day.multiplier = 1; - interval_calc.day.minor = 24; - - // HOUR - interval_calc.hour.type = "hour"; - interval_calc.hour.first = _first.hours; - interval_calc.hour.base = Math.floor(_first.hours); - interval_calc.hour.last = _last.hours; - interval_calc.hour.number = timespan.hours; - interval_calc.hour.multiplier = 1; - interval_calc.hour.minor = 60; - - // MINUTE - interval_calc.minute.type = "minute"; - interval_calc.minute.first = _first.minutes; - interval_calc.minute.base = Math.floor(_first.minutes); - interval_calc.minute.last = _last.minutes; - interval_calc.minute.number = timespan.minutes; - interval_calc.minute.multiplier = 1; - interval_calc.minute.minor = 60; - - // SECOND - interval_calc.second.type = "decade"; - interval_calc.second.first = _first.seconds; - interval_calc.second.base = Math.floor(_first.seconds); - interval_calc.second.last = _last.seconds; - interval_calc.second.number = timespan.seconds; - interval_calc.second.multiplier = 1; - interval_calc.second.minor = 10; - } - - var getDateFractions = function(the_date, is_utc) { - - var _time = {}; - _time.days = the_date / dateFractionBrowser.day; - _time.weeks = _time.days / dateFractionBrowser.week; - _time.months = _time.days / dateFractionBrowser.month; - _time.years = _time.months / dateFractionBrowser.year; - _time.hours = _time.days * dateFractionBrowser.hour; - _time.minutes = _time.days * dateFractionBrowser.minute; - _time.seconds = _time.days * dateFractionBrowser.second; - _time.decades = _time.years / dateFractionBrowser.decade; - _time.centuries = _time.years / dateFractionBrowser.century; - _time.milleniums = _time.years / dateFractionBrowser.millenium; - _time.ages = _time.years / dateFractionBrowser.age; - _time.epochs = _time.years / dateFractionBrowser.epoch; - _time.eras = _time.years / dateFractionBrowser.era; - _time.eons = _time.years / dateFractionBrowser.eon; - - /* - trace("AGES " + _time.ages); - trace("EPOCHS " + _time.epochs); - trace("MILLENIUMS " + _time.milleniums); - trace("CENTURIES " + _time.centuries); - trace("DECADES " + _time.decades); - trace("YEARS " + _time.years); - trace("MONTHS " + _time.months); - trace("WEEKS " + _time.weeks); - trace("DAYS " + _time.days); - trace("HOURS " + _time.hours); - trace("MINUTES " + _time.minutes); - trace("SECONDS " + _time.seconds); - */ - return _time; - } - - /* POSITION - ================================================== */ - var positionOnTimeline = function(the_interval, first, last) { - - var _type = the_interval.type; - var _multiplier = the_interval.multiplier; - - var _first = getDateFractions(first); - var _last; - - var tsd; - var ted; - /* CALCULATE POSITION ON TIMELINE - ================================================== */ - tsd = first.months; - - if (type.of(last) == "date") { - - /* LAST - ================================================== */ - _last = getDateFractions(last); - ted = last.months; - - if (_type == "eon") { - tsd = _first.eons; - ted = _last.eons; - } else if (_type == "era") { - tsd = _first.eras; - ted = _last.eras; - } else if (_type == "epoch") { - tsd = _first.epochs; - ted = _last.epochs; - } else if (_type == "age") { - tsd = _first.ages; - ted = _last.ages; - } else if (_type == "millenium") { - tsd = first.milleniums; - ted = last.milleniums; - } else if (_type == "century") { - tsd = _first.centuries; - ted = _last.centuries; - } else if (_type == "decade") { - tsd = _first.decades; - ted = _last.decades; - } else if (_type == "year") { - tsd = _first.years; - ted = _last.years; - } else if (_type == "month") { - tsd = _first.months; - ted = _last.months; - } else if (_type == "week") { - tsd = _first.weeks; - ted = _last.weeks; - } else if (_type == "day") { - tsd = _first.days; - ted = _last.days; - } else if (_type == "hour") { - tsd = _first.hours; - ted = _last.hours; - } else if (_type == "minute") { - tsd = _first.minutes; - ted = _last.minutes; - } - - _pos = ( tsd - interval.base ) * (config.nav.interval_width / config.nav.multiplier.current); - _pos_end = ( ted - interval.base ) * (config.nav.interval_width / config.nav.multiplier.current); - - } else { - if (_type == "eon") { - tsd = _first.eons; - ted = _first.eons; - } else if (_type == "era") { - tsd = _first.eras; - ted = _first.eras; - } else if (_type == "epoch") { - tsd = _first.epochs; - ted = _first.epochs; - } else if (_type == "age") { - tsd = _first.ages; - ted = _first.ages; - } else if (_type == "millenium") { - tsd = first.milleniums; - ted = first.milleniums; - } else if (_type == "century") { - tsd = _first.centuries; - ted = _first.centuries; - } else if (_type == "decade") { - tsd = _first.decades; - ted = _first.decades; - } else if (_type == "year") { - tsd = _first.years; - ted = _first.years; - } else if (_type == "month") { - tsd = _first.months; - ted = _first.months; - } else if (_type == "week") { - tsd = _first.weeks; - ted = _first.weeks; - } else if (_type == "day") { - tsd = _first.days; - ted = _first.days; - } else if (_type == "hour") { - tsd = _first.hours; - ted = _first.hours; - } else if (_type == "minute") { - tsd = _first.minutes; - ted = _first.minutes; - } - - _pos = ( tsd - interval.base ) * (config.nav.interval_width / config.nav.multiplier.current); - _pos_end = _pos; - - } - - return pos = {begin:_pos ,end:_pos_end}; - - } - - var positionMarkers = function(is_animated) { - - var _type = interval.type; - var _multiplier = interval.multiplier; - - // ROWS - var row = 2; //row - var lpos = 0; // last marker pos; - var row_depth = 0; - var _line_last_height_pos = 150; - var _line_height = 6; - var cur_mark = 0; - - VMM.Lib.removeClass(".flag", "row1"); - VMM.Lib.removeClass(".flag", "row2"); - VMM.Lib.removeClass(".flag", "row3"); - - for(var i = 0; i < markers.length; i++) { - - var _line; // EVENT LENGTH - var _marker = markers[i].marker; - var _marker_flag = markers[i].flag; - var _marker_line_event = markers[i].lineevent; - var _pos = positionOnTimeline(interval, data[i].startdate, data[i].enddate); - var _pos_offset = -2; - - pos = _pos.begin; - _pos_end = _pos.end; - - // COMPENSATE FOR DATES BEING POITIONED IN THE MIDDLE - pos = Math.round(pos + _pos_offset); - _pos_end = Math.round(_pos_end + _pos_offset); - _line = Math.round(_pos_end - pos); - - // APPLY POSITION TO MARKER - if (is_animated) { - VMM.Lib.stop(_marker); - VMM.Lib.animate(_marker, config.duration/2, config.ease, {"left": pos}); - } else { - VMM.Lib.css(_marker, "left", pos); - } - - if (i == current_marker) { - cur_mark = pos; - } - - // EVENT LENGTH LINE - if (_line > 5) { - VMM.Lib.css(_marker_line_event, "height", _line_height); - VMM.Lib.css(_marker_line_event, "width", _line); - VMM.Lib.css(_marker_line_event, "top", _line_last_height_pos); - } - - // CONTROL ROW POSITION - if (tags.length > 0) { - - for (var k = 0; k < tags.length; k++) { - trace("TAGS: " + tags[k]) - if (k < config.nav.rows.length) { - if (markers[i].tag == tags[k]) { - trace("tag match " + k); - row = k; - } - } - } - - } else { - if (pos - lpos < (config.nav.marker.width + config.spacing)) { - if (row < config.nav.rows.length - 1) { - row ++; - - } else { - row = 0; - row_depth ++; - } - } else { - row_depth = 0; - row = 0; - } - } - - - // SET LAST MARKER POSITION - lpos = pos; - - if (is_animated) { - VMM.Lib.stop(_marker_flag); - VMM.Lib.animate(_marker_flag, config.duration, config.ease, {"top": config.nav.rows[row]}); - } else { - VMM.Lib.css(_marker_flag, "top", config.nav.rows[row]); - } - - // IS THE MARKER A REPRESENTATION OF A START SCREEN? - if (config.start_page && markers[i].type == "start") { - VMM.Lib.visible(_marker, false); - } - - } - - for(var j = 0; j < era_markers.length; j++) { - var _line; - var era = era_markers[j]; - var era_elem = era.content; - var pos = positionOnTimeline(interval, era.startdate, era.enddate); - var era_length = pos.end - pos.begin; - var era_height = 25; - - // APPLY POSITION TO MARKER - VMM.Lib.css(era_elem, "left", pos.begin); - VMM.Lib.css(era_elem, "width", era_length); - } - - - // ANIMATE THE TIMELINE TO ADJUST TO CHANGES - VMM.Lib.stop($timenav); - VMM.Lib.animate($timenav, config.duration/2, config.ease, {"left": (config.width/2) - (cur_mark)}); - - - } - - var positionInterval = function(the_intervals, is_animated, is_minor) { - - var _type = interval.type; - var _multiplier = interval.multiplier; - var last_position = 0; - var last_position_major = 0; - - for(var i = 0; i < the_intervals.length; i++) { - var _interval = the_intervals[i].interval_element; - var _interval_date = the_intervals[i].interval_date; - var _interval_visible = the_intervals[i].interval_visible; - var _pos = positionOnTimeline(interval, _interval_date); - var pos = _pos.begin; - var is_visible = true; - var pos_offset = 50; - - // APPLY POSITION TO MARKER - if (is_animated) { - VMM.Lib.animate(_interval, config.duration/2, config.ease, {"left": pos}); - } else { - VMM.Lib.css(_interval, "left", pos); - } - - // CONDENSE WHAT IS DISPLAYED - if (config.nav.multiplier.current > 16 && is_minor) { - is_visible = false; - } else { - if ((pos - last_position) < 65 ) { - if ((pos - last_position) < 35 ) { - if (i%4 == 0) { - if (pos == 0) { - is_visible = false; - } - } else { - is_visible = false; - } - } else { - if (!VMM.Util.isEven(i)) { - is_visible = false; - } - } - } - } - - if (_interval_visible) { - if (!is_visible) { - if (is_animated) { - VMM.Lib.animate(_interval, config.duration*2, config.ease, {"opacity": 0}); - } else { - VMM.Lib.css(_interval, "opacity", 0); - } - the_intervals[i].interval_visible = false; - } - } else { - if (is_visible) { - if (is_animated) { - VMM.Lib.animate(_interval, config.duration*2, config.ease, {"opacity": 100}); - } else { - VMM.Lib.css(_interval, "opacity", 100); - } - the_intervals[i].interval_visible = true; - } - } - - last_position = pos; - - if (pos > config.nav.minor_width) { - config.nav.minor_width = pos; - } - - } - - VMM.Lib.css($timeintervalminor_minor, "left", -(config.width/2)); - VMM.Lib.width($timeintervalminor_minor, (config.nav.minor_width)+(config.width) ); - } - - var createIntervalElements = function(_interval, _array, _element_parent) { - - var inc_time = 0; - var _first_run = true; - var _last_pos = 0; - var _largest_pos = 0; - - VMM.attachElement(_element_parent, ""); - _interval.date = new Date(data[0].startdate.getFullYear(), 0, 1, 0,0,0); - - for(var i = 0; i < Math.ceil(_interval.number) + 1; i++) { - var _idd; - var _pos; - var pos; - var _element = VMM.appendAndGetElement(_element_parent, "
    ", _interval.classname); - var _date; - var _visible = false; - - if (_interval.type == "eon") { - if (_first_run) { - _interval.date.setFullYear( Math.floor(data[0].startdate.getFullYear() / 500000000) * 500000000 ); - } - _interval.date.setFullYear(_interval.date.getFullYear() + (inc_time * 500000000)); - } else if (_interval.type == "era") { - if (_first_run) { - _interval.date.setFullYear( Math.floor(data[0].startdate.getFullYear() / 100000000) * 100000000 ); - } - _interval.date.setFullYear(_interval.date.getFullYear() + (inc_time * 100000000)); - } else if (_interval.type == "epoch") { - if (_first_run) { - _interval.date.setFullYear( Math.floor(data[0].startdate.getFullYear() / 10000000) * 10000000 ); - } - _interval.date.setFullYear(_interval.date.getFullYear() + (inc_time * 10000000)); - } else if (_interval.type == "age") { - if (_first_run) { - _interval.date.setFullYear( Math.floor(data[0].startdate.getFullYear() / 1000000) * 1000000 ); - } - _interval.date.setFullYear(_interval.date.getFullYear() + (inc_time * 1000000)); - } else if (_interval.type == "millenium") { - if (_first_run) { - _interval.date.setFullYear( Math.floor(data[0].startdate.getFullYear() / 1000) * 1000 ); - } - _interval.date.setFullYear(_interval.date.getFullYear() + (inc_time * 1000)); - } else if (_interval.type == "century") { - if (_first_run) { - _interval.date.setFullYear( Math.floor(data[0].startdate.getFullYear() / 100) * 100 ); - } - _interval.date.setFullYear(_interval.date.getFullYear() + (inc_time * 100)); - } else if (_interval.type == "decade") { - if (_first_run) { - _interval.date.setFullYear( Math.floor(data[0].startdate.getFullYear() / 10) * 10 ); - } - _interval.date.setFullYear(_interval.date.getFullYear() + (inc_time * 10)); - } else if (interval.type == "year") { - if (_first_run) { - - } - _interval.date.setFullYear(_interval.date.getFullYear() + inc_time); - } else if (_interval.type == "month") { - if (_first_run) { - _interval.date.setMonth(data[0].startdate.getMonth()); - } - _interval.date.setMonth(_interval.date.getMonth() + inc_time); - } else if (_interval.type == "week") { - if (_first_run) { - _interval.date.setMonth( data[0].startdate.getMonth() ); - _interval.date.setDate( Math.floor(data[0].startdate.getDate() *7) ); - } - _interval.date.setDate(_interval.date.getDate() + (inc_time * 7) ); - } else if (_interval.type == "day") { - if (_first_run) { - _interval.date.setMonth( data[0].startdate.getMonth() ); - _interval.date.setDate( data[0].startdate.getDate() ); - } - _interval.date.setDate(_interval.date.getDate() + inc_time); - } else if (_interval.type == "hour") { - if (_first_run) { - _interval.date.setMonth( data[0].startdate.getMonth() ); - _interval.date.setDate( data[0].startdate.getDate() ); - _interval.date.setHours( data[0].startdate.getHours() ); - } - _interval.date.setHours(_interval.date.getHours() + inc_time); - } else if (_interval.type == "minute") { - if (_first_run) { - _interval.date.setMonth( data[0].startdate.getMonth() ); - _interval.date.setDate( data[0].startdate.getDate() ); - _interval.date.setHours( data[0].startdate.getHours() ); - _interval.date.setMinutes( data[0].startdate.getMinutes() ); - } - _interval.date.setMinutes(_interval.date.getMinutes() + inc_time); - } else if (_interval.type == "second") { - if (_first_run) { - _interval.date.setMonth( data[0].startdate.getMonth() ); - _interval.date.setDate( data[0].startdate.getDate() ); - _interval.date.setHours( data[0].startdate.getHours() ); - _interval.date.setMinutes( data[0].startdate.getMinutes() ); - _interval.date.setSeconds( data[0].startdate.getSeconds() ); - } - _interval.date.setSeconds(_interval.date.getSeconds() + inc_time); - } - - _idd = VMM.Util.date.prettyDate(_interval.date, true); - - inc_time = 1; - - _first_run = false; - - _pos = positionOnTimeline(_interval, _interval.date); - pos = _pos.begin; - - VMM.appendElement(_element, _idd); - - VMM.Lib.css(_element, "text-indent", -(VMM.Lib.width(_element)/2)); - VMM.Lib.css(_element, "opacity", "0"); - - _last_pos = pos; - - if (pos > _largest_pos) { - _largest_pos = pos; - } - - _date = new Date(_interval.date); - - var _obj = { - interval_element: _element, - interval_date: _date, - interval_visible: _visible, - type: _interval.interval_type - }; - - _array.push(_obj); - } - - VMM.Lib.width($timeintervalminor_minor, _largest_pos); - - positionInterval(_array); - - - } - - /* BUILD - ================================================== */ - var build = function() { - - VMM.attachElement(layout, ""); - - $timenav = VMM.appendAndGetElement(layout, "
    ", "timenav"); - $content = VMM.appendAndGetElement($timenav, "
    ", "content"); - $time = VMM.appendAndGetElement($timenav, "
    ", "time"); - $timeintervalminor = VMM.appendAndGetElement($time, "
    ", "time-interval-minor"); - $timeintervalminor_minor = VMM.appendAndGetElement($timeintervalminor, "
    ", "minor"); - $timeintervalmajor = VMM.appendAndGetElement($time, "
    ", "time-interval-major"); - $timeinterval = VMM.appendAndGetElement($time, "
    ", "time-interval"); - $timebackground = VMM.appendAndGetElement(layout, "
    ", "timenav-background"); - $timenavline = VMM.appendAndGetElement($timebackground, "
    ", "timenav-line"); - $timenavindicator = VMM.appendAndGetElement($timebackground, "
    ", "timenav-indicator"); - $timeintervalbackground = VMM.appendAndGetElement($timebackground, "
    ", "timenav-interval-background", "
    "); - $toolbar = VMM.appendAndGetElement(layout, "
    ", "toolbar"); - - buildInterval(); - buildMarkers(); - calculateMultiplier(); - positionMarkers(); - positionInterval(interval_array, false, true); - positionInterval(interval_major_array); - //reSize(true); - - if (config.start_page) { - $backhome = VMM.appendAndGetElement($toolbar, "
    ", "back-home", "
    "); - VMM.bindEvent(".back-home", onBackHome, "click"); - VMM.Lib.css($toolbar, "top", 27); - VMM.Lib.attribute($backhome, "title", VMM.master_config.language.messages.return_to_title); - VMM.Lib.attribute($backhome, "rel", "tooltip"); - - } - - $zoomin = VMM.appendAndGetElement($toolbar, "
    ", "zoom-in", "
    "); - $zoomout = VMM.appendAndGetElement($toolbar, "
    ", "zoom-out", "
    "); - - VMM.Lib.attribute($zoomin, "title", VMM.master_config.language.messages.expand_timeline); - VMM.Lib.attribute($zoomin, "rel", "tooltip"); - VMM.Lib.attribute($zoomout, "title", VMM.master_config.language.messages.contract_timeline); - VMM.Lib.attribute($zoomout, "rel", "tooltip"); - - $toolbar.tooltip({selector: "div[rel=tooltip]", placement: "right"}) - - // MAKE TIMELINE TOUCHABLE - if (VMM.Browser.device == "mobile" || VMM.Browser.device == "tablet") { - VMM.TouchSlider.createPanel($timebackground, $timenav, config.width, config.spacing, false); - VMM.bindEvent($timenav, onTouchUpdate, "TOUCHUPDATE"); - } else { - VMM.DragSlider.createPanel(layout, $timenav, config.width, config.spacing, false); - } - - - VMM.bindEvent(".zoom-in", onZoomIn, "click"); - VMM.bindEvent(".zoom-out", onZoomOut, "click"); - VMM.fireEvent(layout, "LOADED"); - _active = true; - - reSize(true); - - }; - - var buildInterval = function() { - - // CALCULATE INTERVAL - timespan = getDateFractions((data[data.length - 1].enddate) - (data[0].startdate), true); - trace(timespan); - calculateInterval(); - - /* DETERMINE DEFAULT INTERVAL TYPE - millenium, ages, epoch, era and eon are not working yet - ================================================== */ - /* - if (timespan.eons > data.length / config.nav.density) { - interval = interval_calc.eon; - interval_major = interval_calc.eon; - interval_macro = interval_calc.era; - } else if (timespan.eras > data.length / config.nav.density) { - interval = interval_calc.era; - interval_major = interval_calc.eon; - interval_macro = interval_calc.epoch; - } else if (timespan.epochs > data.length / config.nav.density) { - interval = interval_calc.epoch; - interval_major = interval_calc.era; - interval_macro = interval_calc.age; - } else if (timespan.ages > data.length / config.nav.density) { - interval = interval_calc.ages; - interval_major = interval_calc.epoch; - interval_macro = interval_calc.millenium; - } else if (timespan.milleniums > data.length / config.nav.density) { - interval = interval_calc.millenium; - interval_major = interval_calc.age; - interval_macro = interval_calc.century; - } else - */ - if (timespan.centuries > data.length / config.nav.density) { - interval = interval_calc.century; - interval_major = interval_calc.millenium; - interval_macro = interval_calc.decade; - } else if (timespan.decades > data.length / config.nav.density) { - interval = interval_calc.decade; - interval_major = interval_calc.century; - interval_macro = interval_calc.year; - } else if (timespan.years > data.length / config.nav.density) { - interval = interval_calc.year; - interval_major = interval_calc.decade; - interval_macro = interval_calc.month; - } else if (timespan.months > data.length / config.nav.density) { - interval = interval_calc.month; - interval_major = interval_calc.year; - interval_macro = interval_calc.day; - } else if (timespan.days > data.length / config.nav.density) { - interval = interval_calc.day; - interval_major = interval_calc.month; - interval_macro = interval_calc.hour; - } else if (timespan.hours > data.length / config.nav.density) { - interval = interval_calc.hour; - interval_major = interval_calc.day; - interval_macro = interval_calc.minute; - } else if (timespan.minutes > data.length / config.nav.density) { - interval = interval_calc.minute; - interval_major = interval_calc.hour; - interval_macro = interval_calc.second; - } else if (timespan.seconds > data.length / config.nav.density) { - interval = interval_calc.second; - interval_major = interval_calc.minute; - interval_macro = interval_calc.second; - } else { - trace("NO IDEA WHAT THE TYPE SHOULD BE"); - interval = interval_calc.day; - interval_major = interval_calc.month; - interval_macro = interval_calc.hour; - } - - trace("INTERVAL TYPE: " + interval.type); - trace("INTERVAL MAJOR TYPE: " + interval_major.type); - - createIntervalElements(interval, interval_array, $timeinterval); - createIntervalElements(interval_major, interval_major_array, $timeintervalmajor); - - } - - var buildMarkers = function() { - - var row = 2; //row - var lpos = 0; // last marker pos; - var row_depth = 0; - markers = []; - era_markers = []; - - for(var i = 0; i < data.length; i++) { - - var _marker, _marker_flag, _marker_content, _marker_dot, _marker_line, _marker_line_event; - - _marker = VMM.appendAndGetElement($content, "
    ", "marker"); - _marker_flag = VMM.appendAndGetElement(_marker, "
    ", "flag"); - _marker_content = VMM.appendAndGetElement(_marker_flag, "
    ", "flag-content"); - _marker_dot = VMM.appendAndGetElement(_marker, "
    ", "dot"); - _marker_line = VMM.appendAndGetElement(_marker, "
    ", "line"); - _marker_line_event = VMM.appendAndGetElement(_marker_line, "
    ", "event-line"); - - - // THUMBNAIL - if (data[i].asset != null && data[i].asset != "") { - VMM.appendElement(_marker_content, VMM.MediaElement.thumbnail(data[i].asset, 24, 24)); - } else { - //VMM.appendElement(_marker_content, "
    "); - VMM.appendElement(_marker_content, "
    "); - } - - // ADD DATE AND TITLE - VMM.appendElement(_marker_content, "

    " + VMM.Util.unlinkify(data[i].title) + "

    " + data[i].date + "

    "); - - // ADD ID - VMM.Lib.attr(_marker, "id", (data[i].uniqueid).toString()); - - // MARKER CLICK - VMM.bindEvent(_marker_flag, onMarkerClick, "", {number: i}); - VMM.bindEvent(_marker_flag, onMarkerHover, "mouseenter mouseleave", {number: i, elem:_marker_flag}); - - var _marker_obj = { - marker: _marker, - flag: _marker_flag, - lineevent: _marker_line_event, - type: "marker", - tag: data[i].tag - }; - - - if (data[i].type == "start") { - trace("BUILD MARKER HAS START PAGE") - config.start_page = true; - _marker_obj.type = "start"; - } - - if (data[i].tag) { - tags.push(data[i].tag); - } - - markers.push(_marker_obj); - - - - } - - // CREATE TAGS - tags = VMM.Util.deDupeArray(tags); - - for(var k = 0; k < tags.length; k++) { - if (k < config.nav.rows.length) { - var tag_element = VMM.appendAndGetElement($timebackground, "
    ", "timenav-tag"); - VMM.Lib.addClass(tag_element, "timenav-tag-row-" + (k+1)); - VMM.appendElement(tag_element, "

    " + tags[k] + "

    "); - } - - } - - - - // CREATE ERAS - for(var j = 0; j < eras.length; j++) { - - var era = { - content: VMM.appendAndGetElement($content, "
    ", "era"), - startdate: VMM.Util.parseDate(eras[j].startDate), - enddate: VMM.Util.parseDate(eras[j].endDate), - title: eras[j].headline, - uniqueid: VMM.Util.unique_ID(4), - color: eras[j].color - }; - - VMM.Lib.attr(era.content, "id", era.uniqueid); - VMM.Lib.css(era.content, "background", era.color); - VMM.appendElement(era.content, "

    " + VMM.Util.unlinkify(era.title) + "

    "); - - era_markers.push(era); - - } - - } - - }; - -} - -/*********************************************** - Begin VMM.Timeline.DataObj.js -***********************************************/ - -/* TIMELINE SOURCE DATA PROCESSOR -================================================== */ - -if(typeof VMM.Timeline != 'undefined' && typeof VMM.Timeline.DataObj == 'undefined') { - - VMM.Timeline.DataObj = { - - data_obj: {}, - - model_array: [], - - getData: function(raw_data) { - VMM.Timeline.DataObj.data_obj = {}; - data = VMM.Timeline.DataObj.data_obj; - - if (type.of(raw_data) == "object") { - trace("DATA SOURCE: JSON OBJECT"); - VMM.Timeline.DataObj.parseJSON(raw_data); - } else if (type.of(raw_data) == "string") { - if (raw_data.match("%23")) { - trace("DATA SOURCE: TWITTER SEARCH"); - VMM.Timeline.DataObj.model_Tweets.getData("%23medill"); - - } else if ( raw_data.match("spreadsheet") ) { - VMM.fireEvent(global, VMM.Timeline.Config.events.messege, VMM.Timeline.Config.language.messages.loading_timeline); - trace("DATA SOURCE: GOOGLE SPREADSHEET"); - VMM.Timeline.DataObj.model_GoogleSpreadsheet.getData(raw_data); - - } else { - VMM.fireEvent(global, VMM.Timeline.Config.events.messege, VMM.Timeline.Config.language.messages.loading_timeline); - trace("DATA SOURCE: JSON"); - trace("raw data" + raw_data); - VMM.getJSON(raw_data, VMM.Timeline.DataObj.parseJSON); - } - } else if (type.of(raw_data) == "html") { - trace("DATA SOURCE: HTML"); - VMM.Timeline.DataObj.parseHTML(raw_data); - } else { - trace("DATA SOURCE: UNKNOWN"); - } - - }, - - parseHTML: function(d) { - trace("parseHTML"); - trace("WARNING: THIS IS STILL ALPHA AND WILL NOT WORK WITH ID's other than #timeline"); - var _data_obj = VMM.Timeline.DataObj.data_template_obj; - - /* Timeline start slide - ================================================== */ - if (VMM.Lib.find("#timeline section", "time")[0]) { - _data_obj.timeline.startDate = VMM.Lib.html(VMM.Lib.find("#timeline section", "time")[0]); - _data_obj.timeline.headline = VMM.Lib.html(VMM.Lib.find("#timeline section", "h2")); - _data_obj.timeline.text = VMM.Lib.html(VMM.Lib.find("#timeline section", "article")); - - var found_main_media = false; - - if (VMM.Lib.find("#timeline section", "figure img").length != 0) { - found_main_media = true; - _data_obj.timeline.asset.media = VMM.Lib.attr(VMM.Lib.find("#timeline section", "figure img"), "src"); - } else if (VMM.Lib.find("#timeline section", "figure a").length != 0) { - found_main_media = true; - _data_obj.timeline.asset.media = VMM.Lib.attr(VMM.Lib.find("#timeline section", "figure a"), "href"); - } else { - //trace("NOT FOUND"); - } - - if (found_main_media) { - if (VMM.Lib.find("#timeline section", "cite").length != 0) { - _data_obj.timeline.asset.credit = VMM.Lib.html(VMM.Lib.find("#timeline section", "cite")); - } - if (VMM.Lib.find(this, "figcaption").length != 0) { - _data_obj.timeline.asset.caption = VMM.Lib.html(VMM.Lib.find("#timeline section", "figcaption")); - } - } - } - - /* Timeline Date Slides - ================================================== */ - VMM.Lib.each("#timeline li", function(i, elem){ - - var valid_date = false; - - var _date = { - "type":"default", - "startDate":"", - "headline":"", - "text":"", - "asset": - { - "media":"", - "credit":"", - "caption":"" - }, - "tags":"Optional" - }; - - if (VMM.Lib.find(this, "time") != 0) { - - valid_date = true; - - _date.startDate = VMM.Lib.html(VMM.Lib.find(this, "time")[0]); - - if (VMM.Lib.find(this, "time")[1]) { - _date.endDate = VMM.Lib.html(VMM.Lib.find(this, "time")[1]); - } - - _date.headline = VMM.Lib.html(VMM.Lib.find(this, "h3")); - - _date.text = VMM.Lib.html(VMM.Lib.find(this, "article")); - - var found_media = false; - if (VMM.Lib.find(this, "figure img").length != 0) { - found_media = true; - _date.asset.media = VMM.Lib.attr(VMM.Lib.find(this, "figure img"), "src"); - } else if (VMM.Lib.find(this, "figure a").length != 0) { - found_media = true; - _date.asset.media = VMM.Lib.attr(VMM.Lib.find(this, "figure a"), "href"); - } else { - //trace("NOT FOUND"); - } - - if (found_media) { - if (VMM.Lib.find(this, "cite").length != 0) { - _date.asset.credit = VMM.Lib.html(VMM.Lib.find(this, "cite")); - } - if (VMM.Lib.find(this, "figcaption").length != 0) { - _date.asset.caption = VMM.Lib.html(VMM.Lib.find(this, "figcaption")); - } - } - - trace(_date); - _data_obj.timeline.date.push(_date); - - } - - }); - - VMM.fireEvent(global, VMM.Timeline.Config.events.data_ready, _data_obj); - - }, - - parseJSON: function(d) { - if (d.timeline.type == "default") { - - trace("DATA SOURCE: JSON STANDARD TIMELINE"); - VMM.fireEvent(global, VMM.Timeline.Config.events.data_ready, d); - - } else if (d.timeline.type == "twitter") { - - trace("DATA SOURCE: JSON TWEETS"); - VMM.Timeline.DataObj.model_Tweets.buildData(d); - - } else { - - trace("DATA SOURCE: UNKNOWN JSON"); - trace(type.of(d.timeline)); - - }; - }, - - /* MODEL OBJECTS - New Types of Data can be formatted for the timeline here - ================================================== */ - - model_Tweets: { - - type: "twitter", - - buildData: function(raw_data) { - VMM.bindEvent(global, VMM.Timeline.DataObj.model_Tweets.onTwitterDataReady, "TWEETSLOADED"); - VMM.ExternalAPI.twitter.getTweets(raw_data.timeline.tweets); - }, - - getData: function(raw_data) { - VMM.bindEvent(global, VMM.Timeline.DataObj.model_Tweets.onTwitterDataReady, "TWEETSLOADED"); - VMM.ExternalAPI.twitter.getTweetSearch(raw_data); - }, - - onTwitterDataReady: function(e, d) { - var _data_obj = VMM.Timeline.DataObj.data_template_obj; - - for(var i = 0; i < d.tweetdata.length; i++) { - - var _date = { - "type":"tweets", - "startDate":"", - "headline":"", - "text":"", - "asset": - { - "media":"", - "credit":"", - "caption":"" - }, - "tags":"Optional" - }; - // pass in the 'created_at' string returned from twitter // - // stamp arrives formatted as Tue Apr 07 22:52:51 +0000 2009 // - - //var twit_date = VMM.ExternalAPI.twitter.parseTwitterDate(d.tweetdata[i].raw.created_at); - //trace(twit_date); - - _date.startDate = d.tweetdata[i].raw.created_at; - - if (type.of(d.tweetdata[i].raw.from_user_name)) { - _date.headline = d.tweetdata[i].raw.from_user_name + " (" + "@" + d.tweetdata[i].raw.from_user + ")" ; - } else { - _date.headline = d.tweetdata[i].raw.user.name + " (" + "@" + d.tweetdata[i].raw.user.screen_name + ")" ; - } - - _date.asset.media = d.tweetdata[i].content; - _data_obj.timeline.date.push(_date); - - }; - - VMM.fireEvent(global, VMM.Timeline.Config.events.data_ready, _data_obj); - } - }, - - model_GoogleSpreadsheet: { - // TEMPLATE CAN BE FOUND HERE - // https://docs.google.com/previewtemplate?id=0AppSVxABhnltdEhzQjQ4MlpOaldjTmZLclQxQWFTOUE&mode=public - type: "google spreadsheet", - - - getData: function(raw_data) { - - var _key = VMM.Util.getUrlVars(raw_data)["key"]; - var _url = "https://spreadsheets.google.com/feeds/list/" + _key + "/od6/public/values?alt=json"; - - VMM.getJSON(_url, VMM.Timeline.DataObj.model_GoogleSpreadsheet.buildData); - }, - - buildData: function(d) { - VMM.fireEvent(global, VMM.Timeline.Config.events.messege, "Parsing Data"); - var _data_obj = VMM.Timeline.DataObj.data_template_obj; - - for(var i = 0; i < d.feed.entry.length; i++) { - var dd = d.feed.entry[i]; - - if (dd.gsx$titleslide.$t.match("start")) { - _data_obj.timeline.startDate = dd.gsx$startdate.$t; - _data_obj.timeline.headline = dd.gsx$headline.$t; - _data_obj.timeline.asset.media = dd.gsx$media.$t; - _data_obj.timeline.asset.caption = dd.gsx$mediacaption.$t; - _data_obj.timeline.asset.credit = dd.gsx$mediacredit.$t; - _data_obj.timeline.text = dd.gsx$text.$t; - _data_obj.timeline.type = "google spreadsheet"; - } else { - var _date = { - "type": "google spreadsheet", - "startDate": dd.gsx$startdate.$t, - "endDate": dd.gsx$enddate.$t, - "headline": dd.gsx$headline.$t, - "text": dd.gsx$text.$t, - "asset": { - "media": dd.gsx$media.$t, - "credit": dd.gsx$mediacredit.$t, - "caption": dd.gsx$mediacaption.$t - }, - "tags": "Optional" - }; - if (dd.gsx$tag.$t) { - _date.tag = dd.gsx$tag.$t; - trace("TAG " + _date.tag); - } - _data_obj.timeline.date.push(_date); - } - }; - - VMM.fireEvent(global, VMM.Timeline.Config.events.data_ready, _data_obj); - - } - - }, - - /* TEMPLATE OBJECTS - ================================================== */ - data_template_obj: { "timeline": { "headline":"", "description":"", "asset": { "media":"", "credit":"", "caption":"" }, "date": [] } }, - date_obj: {"startDate":"2012,2,2,11,30", "headline":"", "text":"", "asset": {"media":"http://youtu.be/vjVfu8-Wp6s", "credit":"", "caption":"" }, "tags":"Optional"} - - }; - -} diff --git a/vendor/timeline/LICENSE b/vendor/timeline/LICENSE new file mode 100644 index 00000000..d061c361 --- /dev/null +++ b/vendor/timeline/LICENSE @@ -0,0 +1,365 @@ +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + + +------------------------------------------- + +Map tiles by [Stamen Design](http://stamen.com "Stamen Design"), under +[CC BY 3.0](http://creativecommons.org/licenses/by/3.0 "CC BY 3.0"). +Data by [OpenStreetMap](http://openstreetmap.org "OpenStreetMap"), +under [CC BY SA](http://creativecommons.org/licenses/by-sa/3.0 "CC BY SA"). diff --git a/vendor/timeline/README b/vendor/timeline/README new file mode 100644 index 00000000..50f93da3 --- /dev/null +++ b/vendor/timeline/README @@ -0,0 +1 @@ +Verite TimelineJS v2.25 diff --git a/vendor/timeline/css/loading.gif b/vendor/timeline/css/loading.gif new file mode 100644 index 00000000..d3eef2d6 Binary files /dev/null and b/vendor/timeline/css/loading.gif differ diff --git a/vendor/timeline/css/timeline.css b/vendor/timeline/css/timeline.css new file mode 100644 index 00000000..88e46824 --- /dev/null +++ b/vendor/timeline/css/timeline.css @@ -0,0 +1,284 @@ +.vco-storyjs{}.vco-storyjs div *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;} +.vco-storyjs h1,.vco-storyjs h2,.vco-storyjs h3,.vco-storyjs h4,.vco-storyjs h5,.vco-storyjs h6,.vco-storyjs p,.vco-storyjs blockquote,.vco-storyjs pre,.vco-storyjs a,.vco-storyjs abbr,.vco-storyjs acronym,.vco-storyjs address,.vco-storyjs cite,.vco-storyjs code,.vco-storyjs del,.vco-storyjs dfn,.vco-storyjs em,.vco-storyjs img,.vco-storyjs q,.vco-storyjs s,.vco-storyjs samp,.vco-storyjs small,.vco-storyjs strike,.vco-storyjs strong,.vco-storyjs sub,.vco-storyjs sup,.vco-storyjs tt,.vco-storyjs var,.vco-storyjs dd,.vco-storyjs dl,.vco-storyjs dt,.vco-storyjs li,.vco-storyjs ol,.vco-storyjs ul,.vco-storyjs fieldset,.vco-storyjs form,.vco-storyjs label,.vco-storyjs legend,.vco-storyjs button,.vco-storyjs table,.vco-storyjs caption,.vco-storyjs tbody,.vco-storyjs tfoot,.vco-storyjs thead,.vco-storyjs tr,.vco-storyjs th,.vco-storyjs td,.vco-storyjs .vco-container,.vco-storyjs .content-container,.vco-storyjs .media,.vco-storyjs .text,.vco-storyjs .vco-slider,.vco-storyjs .slider,.vco-storyjs .date,.vco-storyjs .title,.vco-storyjs .messege,.vco-storyjs .map,.vco-storyjs .credit,.vco-storyjs .caption,.vco-storyjs .vco-feedback,.vco-storyjs .vco-feature,.vco-storyjs .toolbar,.vco-storyjs .marker,.vco-storyjs .dot,.vco-storyjs .line,.vco-storyjs .flag,.vco-storyjs .time,.vco-storyjs .era,.vco-storyjs .major,.vco-storyjs .minor,.vco-storyjs .vco-navigation,.vco-storyjs .start,.vco-storyjs .active{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;width:auto;float:none;} +.vco-storyjs h1,.vco-storyjs h2,.vco-storyjs h3,.vco-storyjs h4,.vco-storyjs h5,.vco-storyjs h6{clear:none;} +.vco-storyjs table{border-collapse:collapse;border-spacing:0;} +.vco-storyjs ol,.vco-storyjs ul{list-style:none;} +.vco-storyjs q:before,.vco-storyjs q:after,.vco-storyjs blockquote:before,.vco-storyjs blockquote:after{content:"";} +.vco-storyjs a:focus{outline:thin dotted;} +.vco-storyjs a:hover,.vco-storyjs a:active{outline:0;} +.vco-storyjs article,.vco-storyjs aside,.vco-storyjs details,.vco-storyjs figcaption,.vco-storyjs figure,.vco-storyjs footer,.vco-storyjs header,.vco-storyjs hgroup,.vco-storyjs nav,.vco-storyjs section{display:block;} +.vco-storyjs audio,.vco-storyjs canvas,.vco-storyjs video{display:inline-block;*display:inline;*zoom:1;} +.vco-storyjs audio:not([controls]){display:none;} +.vco-storyjs div{max-width:none;} +.vco-storyjs sub,.vco-storyjs sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;} +.vco-storyjs sup{top:-0.5em;} +.vco-storyjs sub{bottom:-0.25em;} +.vco-storyjs img{border:0;-ms-interpolation-mode:bicubic;} +.vco-storyjs button,.vco-storyjs input,.vco-storyjs select,.vco-storyjs textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;} +.vco-storyjs button,.vco-storyjs input{line-height:normal;*overflow:visible;} +.vco-storyjs button::-moz-focus-inner,.vco-storyjs input::-moz-focus-inner{border:0;padding:0;} +.vco-storyjs button,.vco-storyjs input[type="button"],.vco-storyjs input[type="reset"],.vco-storyjs input[type="submit"]{cursor:pointer;-webkit-appearance:button;} +.vco-storyjs input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;} +.vco-storyjs input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;} +.vco-storyjs textarea{overflow:auto;vertical-align:top;} +.vco-storyjs{font-family:"Georgia",Times New Roman,Times,serif;}.vco-storyjs .twitter,.vco-storyjs .vcard,.vco-storyjs .messege,.vco-storyjs .credit,.vco-storyjs .caption,.vco-storyjs .zoom-in,.vco-storyjs .zoom-out,.vco-storyjs .back-home,.vco-storyjs .time-interval div,.vco-storyjs .time-interval-major div,.vco-storyjs .nav-container{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif !important;} +.vco-storyjs h1.date,.vco-storyjs h2.date,.vco-storyjs h3.date,.vco-storyjs h4.date,.vco-storyjs h5.date,.vco-storyjs h6.date{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif !important;} +.vco-storyjs .timenav h1,.vco-storyjs .flag-content h1,.vco-storyjs .era h1,.vco-storyjs .timenav h2,.vco-storyjs .flag-content h2,.vco-storyjs .era h2,.vco-storyjs .timenav h3,.vco-storyjs .flag-content h3,.vco-storyjs .era h3,.vco-storyjs .timenav h4,.vco-storyjs .flag-content h4,.vco-storyjs .era h4,.vco-storyjs .timenav h5,.vco-storyjs .flag-content h5,.vco-storyjs .era h5,.vco-storyjs .timenav h6,.vco-storyjs .flag-content h6,.vco-storyjs .era h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif !important;} +.vco-storyjs p,.vco-storyjs blockquote,.vco-storyjs blockquote p,.vco-storyjs .twitter blockquote p{font-family:"Georgia",Times New Roman,Times,serif !important;} +.vco-storyjs .vco-feature h1,.vco-storyjs .vco-feature h2,.vco-storyjs .vco-feature h3,.vco-storyjs .vco-feature h4,.vco-storyjs .vco-feature h5,.vco-storyjs .vco-feature h6{font-family:"Georgia",Times New Roman,Times,serif;} +.timeline-tooltip{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;} +.thumbnail{background-image:url(timeline.png?v4.4);} +@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2){.thumbnail{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;}}.vco-storyjs{font-size:15px;font-weight:normal;line-height:20px;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;}.vco-storyjs p{font-size:15px;font-weight:normal;line-height:20px;margin-bottom:20px;color:#666666;}.vco-storyjs p small{font-size:12px;line-height:17px;} +.vco-storyjs p:first-child{margin-top:20px;} +.vco-storyjs .vco-navigation p{color:#999999;} +.vco-storyjs .vco-feature h3,.vco-storyjs .vco-feature h4,.vco-storyjs .vco-feature h5,.vco-storyjs .vco-feature h6{margin-bottom:15px;} +.vco-storyjs .vco-feature p{color:#666666;} +.vco-storyjs .vco-feature blockquote,.vco-storyjs .vco-feature blockquote p{color:#000000;} +.vco-storyjs .date a,.vco-storyjs .title a{color:#999999;} +.vco-storyjs .hyphenate{-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-wrap:break-word;} +.vco-storyjs h1,.vco-storyjs h2,.vco-storyjs h3,.vco-storyjs h4,.vco-storyjs h5,.vco-storyjs h6{font-weight:normal;color:#000000;text-transform:none;}.vco-storyjs h1 a,.vco-storyjs h2 a,.vco-storyjs h3 a,.vco-storyjs h4 a,.vco-storyjs h5 a,.vco-storyjs h6 a{color:#999999;} +.vco-storyjs h1 small,.vco-storyjs h2 small,.vco-storyjs h3 small,.vco-storyjs h4 small,.vco-storyjs h5 small,.vco-storyjs h6 small{color:#999999;} +.vco-storyjs h1.date,.vco-storyjs h2.date,.vco-storyjs h3.date,.vco-storyjs h4.date,.vco-storyjs h5.date,.vco-storyjs h6.date{font-weight:bold;} +.vco-storyjs h2.start{font-size:36px;line-height:38px;margin-bottom:15px;} +.vco-storyjs h1{margin-bottom:15px;font-size:32px;line-height:34px;}.vco-storyjs h1 small{font-size:18px;} +.vco-storyjs h2{margin-bottom:15px;font-size:28px;line-height:30px;}.vco-storyjs h2 small{font-size:14px;line-height:16px;} +.vco-storyjs h2.date{font-size:16px;line-height:18px;margin-bottom:3.75px;color:#999999;} +.vco-storyjs h3,.vco-storyjs h4,.vco-storyjs h5,.vco-storyjs h6{line-height:40px;}.vco-storyjs h3 .active,.vco-storyjs h4 .active,.vco-storyjs h5 .active,.vco-storyjs h6 .active{color:#0088cc;} +.vco-storyjs h3{font-size:28px;line-height:30px;}.vco-storyjs h3 small{font-size:14px;} +.vco-storyjs h4{font-size:20px;line-height:22px;}.vco-storyjs h4 small{font-size:12px;} +.vco-storyjs h5{font-size:16px;line-height:18px;} +.vco-storyjs h6{font-size:13px;line-height:14px;text-transform:uppercase;} +.vco-storyjs strong{font-weight:bold;font-style:inherit;} +.vco-storyjs em{font-style:italic;font-weight:inherit;} +.vco-storyjs Q{quotes:'„' '“';font-style:italic;} +.vco-storyjs blockquote,.vco-storyjs blockquote p{font-size:24px;line-height:32px;text-align:left;margin-bottom:6px;padding-top:10px;background-color:#ffffff;color:#000000;} +.vco-storyjs .credit{color:#999999;text-align:right;font-size:10px;line-height:10px;display:block;margin:0 auto;clear:both;} +.vco-storyjs .caption{text-align:left;margin-top:5px;color:#666666;font-size:11px;line-height:14px;clear:both;} +.vco-storyjs.vco-right-to-left h1,.vco-storyjs.vco-right-to-left h2,.vco-storyjs.vco-right-to-left h3,.vco-storyjs.vco-right-to-left h4,.vco-storyjs.vco-right-to-left h5,.vco-storyjs.vco-right-to-left h6,.vco-storyjs.vco-right-to-left p,.vco-storyjs.vco-right-to-left blockquote,.vco-storyjs.vco-right-to-left pre,.vco-storyjs.vco-right-to-left a,.vco-storyjs.vco-right-to-left abbr,.vco-storyjs.vco-right-to-left acronym,.vco-storyjs.vco-right-to-left address,.vco-storyjs.vco-right-to-left cite,.vco-storyjs.vco-right-to-left code,.vco-storyjs.vco-right-to-left del,.vco-storyjs.vco-right-to-left dfn,.vco-storyjs.vco-right-to-left em,.vco-storyjs.vco-right-to-left img,.vco-storyjs.vco-right-to-left q,.vco-storyjs.vco-right-to-left s,.vco-storyjs.vco-right-to-left samp,.vco-storyjs.vco-right-to-left small,.vco-storyjs.vco-right-to-left strike,.vco-storyjs.vco-right-to-left strong,.vco-storyjs.vco-right-to-left sub,.vco-storyjs.vco-right-to-left sup,.vco-storyjs.vco-right-to-left tt,.vco-storyjs.vco-right-to-left var,.vco-storyjs.vco-right-to-left dd,.vco-storyjs.vco-right-to-left dl,.vco-storyjs.vco-right-to-left dt,.vco-storyjs.vco-right-to-left li,.vco-storyjs.vco-right-to-left ol,.vco-storyjs.vco-right-to-left ul,.vco-storyjs.vco-right-to-left fieldset,.vco-storyjs.vco-right-to-left form,.vco-storyjs.vco-right-to-left label,.vco-storyjs.vco-right-to-left legend,.vco-storyjs.vco-right-to-left button,.vco-storyjs.vco-right-to-left table,.vco-storyjs.vco-right-to-left caption,.vco-storyjs.vco-right-to-left tbody,.vco-storyjs.vco-right-to-left tfoot,.vco-storyjs.vco-right-to-left thead,.vco-storyjs.vco-right-to-left tr,.vco-storyjs.vco-right-to-left th,.vco-storyjs.vco-right-to-left td{direction:rtl;} +.timeline-tooltip{position:absolute;z-index:205;display:block;visibility:visible;padding:5px;opacity:0;filter:alpha(opacity=0);font-size:15px;font-weight:bold;line-height:20px;font-size:12px;line-height:12px;} +.timeline-tooltip.in{opacity:0.8;filter:alpha(opacity=80);} +.timeline-tooltip.top{margin-top:-2px;} +.timeline-tooltip.right{margin-left:2px;} +.timeline-tooltip.bottom{margin-top:2px;} +.timeline-tooltip.left{margin-left:-2px;} +.timeline-tooltip.top .timeline-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.timeline-tooltip.left .timeline-tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.timeline-tooltip.bottom .timeline-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.timeline-tooltip.right .timeline-tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.timeline-tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.timeline-tooltip-arrow{position:absolute;width:0;height:0;} +@media only screen and (max-width:480px),only screen and (max-device-width:480px){.vco-slider .nav-next,.vco-slider .nav-previous{display:none;}}@media (max-width:640px){}.vco-skinny .vco-slider .slider-item .content .layout-text-media .text .container{text-align:center !important;} +.vco-skinny .vco-slider .slider-item .content .layout-text-media h2,.vco-skinny .vco-slider .slider-item .content .layout-text-media h3{display:block !important;width:100% !important;text-align:center !important;} +.vco-skinny .vco-slider .slider-item .content .content-container{display:block;}.vco-skinny .vco-slider .slider-item .content .content-container .text{width:100%;max-width:100%;min-width:120px;display:block;}.vco-skinny .vco-slider .slider-item .content .content-container .text .container{display:block;-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-wrap:break-word;} +.vco-skinny .vco-slider .slider-item .content .content-container .media{width:100%;min-width:50%;float:none;}.vco-skinny .vco-slider .slider-item .content .content-container .media .media-wrapper{margin-left:0px;margin-right:0px;width:100%;display:block;} +.vco-skinny.vco-notouch .vco-slider .nav-previous,.vco-skinny.vco-notouch .vco-slider .nav-next{z-index:203;}.vco-skinny.vco-notouch .vco-slider .nav-previous .nav-container .date,.vco-skinny.vco-notouch .vco-slider .nav-next .nav-container .date,.vco-skinny.vco-notouch .vco-slider .nav-previous .nav-container .title,.vco-skinny.vco-notouch .vco-slider .nav-next .nav-container .title{filter:alpha(opacity=1);-khtml-opacity:0.01;-moz-opacity:0.01;opacity:0.01;} +.vco-skinny.vco-notouch .vco-slider .nav-previous .nav-container .icon,.vco-skinny.vco-notouch .vco-slider .nav-next .nav-container .icon{filter:alpha(opacity=15);-khtml-opacity:0.15;-moz-opacity:0.15;opacity:0.15;} +.vco-skinny.vco-notouch .vco-slider .nav-previous .icon{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-208px 0;width:24px;height:24px;overflow:hidden;margin-left:10px;} +.vco-skinny.vco-notouch .vco-slider .nav-next .icon{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-232px 0;width:24px;height:24px;overflow:hidden;margin-left:66px;} +.vco-skinny.vco-notouch .vco-slider .nav-previous:hover,.vco-skinny.vco-notouch .vco-slider .nav-next:hover{color:#aaaaaa !important;background-color:#333333;background-color:rgba(0, 0, 0, 0.65);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;}.vco-skinny.vco-notouch .vco-slider .nav-previous:hover .nav-container .icon,.vco-skinny.vco-notouch .vco-slider .nav-next:hover .nav-container .icon,.vco-skinny.vco-notouch .vco-slider .nav-previous:hover .nav-container .date,.vco-skinny.vco-notouch .vco-slider .nav-next:hover .nav-container .date,.vco-skinny.vco-notouch .vco-slider .nav-previous:hover .nav-container .title,.vco-skinny.vco-notouch .vco-slider .nav-next:hover .nav-container .title{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;font-weight:bold;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} +.vco-skinny.vco-notouch .vco-slider .nav-previous:hover .nav-container .title,.vco-skinny.vco-notouch .vco-slider .nav-next:hover .nav-container .title{padding-bottom:5px;} +.vco-skinny.vco-notouch .vco-slider .nav-previous:hover .nav-container .date,.vco-skinny.vco-notouch .vco-slider .nav-next:hover .nav-container .date,.vco-skinny.vco-notouch .vco-slider .nav-previous:hover .nav-container .title,.vco-skinny.vco-notouch .vco-slider .nav-next:hover .nav-container .title{padding-left:5px;padding-right:5px;} +@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2){.vco-skinny.vco-notouch .vco-slider .nav-previous .icon{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-208px 0;width:24px;height:24px;overflow:hidden;} .vco-skinny.vco-notouch .vco-slider .nav-next .icon{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-232px 0;width:24px;height:24px;overflow:hidden;}}.vco-slider{width:100%;height:100%;overflow:hidden;}.vco-slider .slider-container-mask{text-align:center;width:100%;height:100%;overflow:hidden;}.vco-slider .slider-container-mask .slider-container{position:absolute;top:0px;left:-2160px;width:100%;height:100%;text-align:center;display:block;}.vco-slider .slider-container-mask .slider-container .slider-item-container{display:table-cell;vertical-align:middle;} +.vco-notouch .vco-slider .nav-previous:hover,.vco-notouch .vco-slider .nav-next:hover{color:#333333;cursor:pointer;} +.vco-notouch .vco-slider .nav-previous:hover .icon{margin-left:10px;} +.vco-notouch .vco-slider .nav-next:hover .icon{margin-left:66px;} +.vco-notouch .vco-slider .slider-item .content .content-container .media .media-container .wikipedia h4 a:hover{color:#0088cc;text-decoration:none;} +.vco-notouch .vco-slider .slider-item .content .content-container .created-at:hover{filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} +.vco-notouch .vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-attachments a:hover{text-decoration:none;}.vco-notouch .vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-attachments a:hover h5{text-decoration:underline;} +.vco-slider img,.vco-slider embed,.vco-slider object,.vco-slider video,.vco-slider iframe{max-width:100%;} +.vco-slider .nav-previous,.vco-slider .nav-next{position:absolute;top:0px;width:100px;color:#DBDBDB;font-size:11px;}.vco-slider .nav-previous .nav-container,.vco-slider .nav-next .nav-container{height:100px;width:100px;position:absolute;} +.vco-slider .nav-previous .icon,.vco-slider .nav-next .icon{margin-top:12px;margin-bottom:15px;} +.vco-slider .nav-previous .date,.vco-slider .nav-next .date,.vco-slider .nav-previous .title,.vco-slider .nav-next .title{line-height:14px;}.vco-slider .nav-previous .date a,.vco-slider .nav-next .date a,.vco-slider .nav-previous .title a,.vco-slider .nav-next .title a{color:#999999;} +.vco-slider .nav-previous .date small,.vco-slider .nav-next .date small,.vco-slider .nav-previous .title small,.vco-slider .nav-next .title small{display:none;} +.vco-slider .nav-previous .date,.vco-slider .nav-next .date{font-size:13px;line-height:13px;font-weight:bold;text-transform:uppercase;margin-bottom:5px;} +.vco-slider .nav-previous .title,.vco-slider .nav-next .title{font-size:11px;line-height:13px;} +.vco-slider .nav-previous{float:left;text-align:left;}.vco-slider .nav-previous .icon{margin-left:15px;background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-160px 0;width:24px;height:24px;overflow:hidden;} +.vco-slider .nav-previous .date,.vco-slider .nav-previous .title{text-align:left;padding-left:15px;} +.vco-slider .nav-next{float:right;text-align:right;}.vco-slider .nav-next .icon{margin-left:61px;background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-184px 0;width:24px;height:24px;overflow:hidden;} +.vco-slider .nav-next .date,.vco-slider .nav-next .title{text-align:right;padding-right:15px;} +@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2){.vco-slider .nav-previous .icon{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-160px 0;width:24px;height:24px;overflow:hidden;} .vco-slider .nav-next .icon{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-184px 0;width:24px;height:24px;overflow:hidden;}}.vco-slider .slider-item{position:absolute;width:700px;height:100%;padding:0px;margin:0px;display:table;overflow-y:auto;}.vco-slider .slider-item .content{display:table-cell;vertical-align:middle;}.vco-slider .slider-item .content .pad-top .text .container{padding-top:15px;} +.vco-slider .slider-item .content .pad-right .text .container{padding-right:15px;} +.vco-slider .slider-item .content .pad-left .text .container{padding-left:30px;} +.vco-slider .slider-item .content .pad-left .media.text-media .media-wrapper .media-container{border:none;background-color:#ffffff;} +.vco-slider .slider-item .content .content-container{display:table;vertical-align:middle;}.vco-slider .slider-item .content .content-container .text{width:40%;max-width:50%;min-width:120px;display:table-cell;vertical-align:middle;}.vco-slider .slider-item .content .content-container .text .container{display:table-cell;vertical-align:middle;text-align:left;}.vco-slider .slider-item .content .content-container .text .container p{-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-wrap:break-word;} +.vco-slider .slider-item .content .content-container .text .container h2.date{font-size:15px;line-height:15px;font-weight:normal;} +.vco-slider .slider-item .content .content-container .text .container .slide-tag{font-size:11px;font-weight:bold;color:#ffffff;background-color:#cccccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;vertical-align:baseline;white-space:nowrap;line-height:11px;padding:1px 3px 1px;margin-left:7.5px;margin-bottom:7.5px;} +.vco-slider .slider-item .content .content-container .media{width:100%;min-width:50%;float:left;}.vco-slider .slider-item .content .content-container .media .media-wrapper{display:inline-block;margin-left:auto;margin-right:auto;}.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container{display:inline-block;line-height:0px;padding:0px;max-height:100%;}.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .media-frame,.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .media-image img{border:1px solid;border-color:#cccccc #999999 #999999 #cccccc;background-color:#ffffff;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .media-frame iframe{background-color:#ffffff;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .soundcloud{border:0;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .media-image{display:inline-block;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .media-shadow{position:relative;z-index:1;background:#ffffff;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .media-shadow:before,.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .media-shadow:after{z-index:-1;position:absolute;content:"";bottom:15px;left:10px;width:50%;top:80%;max-width:300px;background:#999999;-webkit-box-shadow:0 15px 10px #999999;-moz-box-shadow:0 15px 10px #999999;box-shadow:0 15px 10px #999999;-webkit-transform:rotate(-2deg);-moz-transform:rotate(-2deg);-ms-transform:rotate(-2deg);-o-transform:rotate(-2deg);transform:rotate(-2deg);} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .media-shadow::after{-webkit-transform:rotate(2deg);-moz-transform:rotate(2deg);-ms-transform:rotate(2deg);-o-transform:rotate(2deg);transform:rotate(2deg);right:10px;left:auto;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .plain-text{display:table;}.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .plain-text .container{display:table-cell;vertical-align:middle;font-size:15px;line-height:20px;color:#666666;}.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .plain-text .container p{margin-bottom:20px;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .wikipedia{font-size:15px;line-height:20px;text-align:left;margin-left:auto;margin-right:auto;margin-bottom:15px;clear:both;}.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .wikipedia .wiki-source{margin-bottom:15px;font-size:13px;line-height:19px;font-style:italic;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .wikipedia h4{border-bottom:1px solid #cccccc;margin-bottom:5px;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .wikipedia h4 a{color:#000000;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .wikipedia p{font-size:13px;line-height:19px;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .map{line-height:normal;z-index:200;text-align:left;background-color:#ffffff;}.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .map img{max-height:none !important;max-width:none !important;border:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .map .google-map{height:100%;width:100%;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .map .map-attribution{position:absolute;z-index:201;bottom:0px;width:100%;overflow:hidden;}.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .map .map-attribution .attribution-text{height:19px;overflow:hidden;-webkit-user-select:none;line-height:19px;margin-right:60px;padding-left:65px;font-family:Arial,sans-serif;font-size:10px;color:#444;white-space:nowrap;color:#ffffff;text-shadow:1px 1px 1px #333333;text-align:center;}.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .map .map-attribution .attribution-text a{color:#ffffff !important;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .credit{color:#999999;text-align:right;display:block;margin:0 auto;margin-top:6px;font-size:10px;line-height:13px;} +.vco-slider .slider-item .content .content-container .media .media-wrapper .media-container .caption{text-align:left;margin-top:10px;color:#666666;font-size:11px;line-height:14px;text-rendering:optimizeLegibility;word-wrap:break-word;} +.vco-slider .slider-item .content .content-container .media.text-media .media-wrapper .media-container{border:none;background-color:#ffffff;} +.vco-slider .slider-item .content .content-container .created-at{width:24px;height:24px;overflow:hidden;margin-left:7.5px;margin-top:2px;display:inline-block;float:right;filter:alpha(opacity=25);-khtml-opacity:0.25;-moz-opacity:0.25;opacity:0.25;} +.vco-slider .slider-item .content .content-container .storify .created-at{background-repeat:no-repeat;background-position:-328px -96px;} +.vco-slider .slider-item .content .content-container .twitter .created-at{background-repeat:no-repeat;background-position:-256px -24px;} +.vco-slider .slider-item .content .content-container .googleplus .googleplus-content{font-size:13px;line-height:19px;margin-bottom:6px;padding-top:10px;background-color:#ffffff;color:#666666;}.vco-slider .slider-item .content .content-container .googleplus .googleplus-content p{font-size:13px;line-height:19px;} +.vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-title{font-size:24px;line-height:32px;margin-bottom:6px;padding-top:10px;background-color:#ffffff;color:#000000;} +.vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-annotation{font-size:15px;line-height:20px;color:#000000;border-bottom:1px solid #e3e3e3;padding-bottom:7.5px;margin-bottom:7.5px;} +.vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-attachments{border-top:1px solid #e3e3e3;padding-top:15px;margin-top:15px;border-bottom:1px solid #e3e3e3;padding-bottom:15px;margin-bottom:15px;*zoom:1;}.vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-attachments:before,.vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-attachments:after{display:table;content:"";} +.vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-attachments:after{clear:both;} +.vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-attachments h5{margin-bottom:5px;} +.vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-attachments div{width:50%;padding-left:15px;display:inline-block;} +.vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-attachments p{font-size:11px;line-height:14px;margin-bottom:5px;} +.vco-slider .slider-item .content .content-container .googleplus .googleplus-content .googleplus-attachments img{float:left;display:block;bottom:0;left:0;margin:auto;position:relative;right:0;top:0;width:40%;} +.vco-slider .slider-item .content .content-container .googleplus .proflinkPrefix{color:#0088cc;} +.vco-slider .slider-item .content .content-container .googleplus .created-at{background-repeat:no-repeat;background-position:-208px -72px;} +.vco-slider .slider-item .content .content-container .twitter,.vco-slider .slider-item .content .content-container .plain-text-quote,.vco-slider .slider-item .content .content-container .storify,.vco-slider .slider-item .content .content-container .googleplus{text-align:left;margin-left:auto;margin-right:auto;margin-bottom:15px;clear:both;}.vco-slider .slider-item .content .content-container .twitter blockquote,.vco-slider .slider-item .content .content-container .plain-text-quote blockquote,.vco-slider .slider-item .content .content-container .storify blockquote,.vco-slider .slider-item .content .content-container .googleplus blockquote{color:#666666;}.vco-slider .slider-item .content .content-container .twitter blockquote p,.vco-slider .slider-item .content .content-container .plain-text-quote blockquote p,.vco-slider .slider-item .content .content-container .storify blockquote p,.vco-slider .slider-item .content .content-container .googleplus blockquote p{font-size:24px;line-height:32px;margin-bottom:6px;padding-top:10px;background-color:#ffffff;color:#000000;} +.vco-slider .slider-item .content .content-container .twitter blockquote .quote-mark,.vco-slider .slider-item .content .content-container .plain-text-quote blockquote .quote-mark,.vco-slider .slider-item .content .content-container .storify blockquote .quote-mark,.vco-slider .slider-item .content .content-container .googleplus blockquote .quote-mark{color:#666666;} +.vco-slider .slider-item .content .content-container .twitter blockquote{font-size:15px;}.vco-slider .slider-item .content .content-container .twitter blockquote p{font-size:24px;} +.vco-slider .slider-item .content .content-container.layout-text-media .text-media{border-top:1px solid #e3e3e3;padding-top:15px;padding-right:0;} +.vco-slider .slider-item .content .content-container.layout-text-media.pad-left .text-media{padding-right:15px;padding-top:0;border-right:1px solid #e3e3e3;border-top:0px solid #e3e3e3;} +.vco-slider .slider-item .content .content-container.layout-text{width:100%;}.vco-slider .slider-item .content .content-container.layout-text .text{width:100%;max-width:100%;}.vco-slider .slider-item .content .content-container.layout-text .text .container{display:block;vertical-align:middle;padding:0px;width:90%;text-align:left;margin-left:auto;margin-right:auto;} +.vco-slider .slider-item .content .content-container.layout-media{width:100%;}.vco-slider .slider-item .content .content-container.layout-media .text{width:100%;height:100%;max-width:100%;display:block;text-align:center;}.vco-slider .slider-item .content .content-container.layout-media .text .container{display:block;text-align:center;width:100%;margin-left:none;margin-right:none;} +.vco-slider .slider-item .content .content-container.layout-media .media{width:100%;min-width:50%;float:none;}.vco-slider .slider-item .content .content-container.layout-media .media .media-wrapper .media-container{margin-left:auto;margin-right:auto;line-height:0px;padding:0px;} +.vco-slider .slider-item .content .content-container.layout-media .twitter,.vco-slider .slider-item .content .content-container.layout-media .wikipedia,.vco-slider .slider-item .content .content-container.layout-media .googleplus{max-width:70%;} +.storyjs-embed{background-color:#ffffff;margin-bottom:20px;border:1px solid #cccccc;padding-top:20px;padding-bottom:20px;clear:both;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;-webkit-box-shadow:1px 1px 3px rgba(0, 0, 0, 0.35);-moz-box-shadow:1px 1px 3px rgba(0, 0, 0, 0.35);box-shadow:1px 1px 3px rgba(0, 0, 0, 0.35);} +.storyjs-embed.full-embed{overflow:hidden;border:0 !important;padding:0 !important;margin:0 !important;clear:both;-webkit-border-radius:0 !important;-moz-border-radius:0 !important;border-radius:0 !important;-webkit-box-shadow:0 0px 0px rgba(0, 0, 0, 0.25) !important;-moz-box-shadow:0 0px 0px rgba(0, 0, 0, 0.25) !important;box-shadow:0 0px 0px rgba(0, 0, 0, 0.25) !important;} +.storyjs-embed.sized-embed{overflow:hidden;border:1px solid #cccccc;padding-top:7px;padding-bottom:7px;margin:0 !important;clear:both;-webkit-box-shadow:0 0px 0px rgba(0, 0, 0, 0.25) !important;-moz-box-shadow:0 0px 0px rgba(0, 0, 0, 0.25) !important;box-shadow:0 0px 0px rgba(0, 0, 0, 0.25) !important;} +.vco-storyjs{width:100%;height:100%;padding:0px;margin:0px;background-color:#ffffff;position:absolute;z-index:100;clear:both;overflow:hidden;}.vco-storyjs .vmm-clear:before,.vco-storyjs .vmm-clear:after{content:"";display:table;} +.vco-storyjs .vmm-clear:after{clear:both;} +.vco-storyjs .vmm-clear{*zoom:1;} +.vco-storyjs .vco-feature{width:100%;}.vco-storyjs .vco-feature .slider,.vco-storyjs .vco-feature .vco-slider{width:100%;float:left;position:relative;z-index:10;padding-top:15px;-webkit-box-shadow:1px 1px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:1px 1px 7px rgba(0, 0, 0, 0.3);box-shadow:1px 1px 7px rgba(0, 0, 0, 0.3);} +.vco-storyjs .vco-feedback{position:absolute;display:table;overflow:hidden;top:0px;left:0px;z-index:205;width:100%;height:100%;} +.vco-storyjs div.vco-loading,.vco-storyjs div.vco-explainer{display:table;text-align:center;min-width:100px;margin-top:15px;height:100%;width:100%;background-color:#ffffff;}.vco-storyjs div.vco-loading .vco-loading-container,.vco-storyjs div.vco-explainer .vco-loading-container,.vco-storyjs div.vco-loading .vco-explainer-container,.vco-storyjs div.vco-explainer .vco-explainer-container{display:table-cell;vertical-align:middle;}.vco-storyjs div.vco-loading .vco-loading-container .vco-loading-icon,.vco-storyjs div.vco-explainer .vco-loading-container .vco-loading-icon,.vco-storyjs div.vco-loading .vco-explainer-container .vco-loading-icon,.vco-storyjs div.vco-explainer .vco-explainer-container .vco-loading-icon{display:block;background-repeat:no-repeat;vertical-align:middle;margin-left:auto;margin-right:auto;text-align:center;background-image:url(loading.gif?v3.4);width:28px;height:28px;} +.vco-storyjs div.vco-loading .vco-loading-container .vco-gesture-icon,.vco-storyjs div.vco-explainer .vco-loading-container .vco-gesture-icon,.vco-storyjs div.vco-loading .vco-explainer-container .vco-gesture-icon,.vco-storyjs div.vco-explainer .vco-explainer-container .vco-gesture-icon{display:block;vertical-align:middle;margin-left:auto;margin-right:auto;text-align:center;background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-160px -160px;width:48px;height:48px;} +.vco-storyjs div.vco-loading .vco-loading-container .vco-message,.vco-storyjs div.vco-explainer .vco-loading-container .vco-message,.vco-storyjs div.vco-loading .vco-explainer-container .vco-message,.vco-storyjs div.vco-explainer .vco-explainer-container .vco-message{display:block;} +.vco-storyjs div.vco-loading .vco-loading-container .vco-message,.vco-storyjs div.vco-explainer .vco-loading-container .vco-message,.vco-storyjs div.vco-loading .vco-explainer-container .vco-message,.vco-storyjs div.vco-explainer .vco-explainer-container .vco-message,.vco-storyjs div.vco-loading .vco-loading-container .vco-message p,.vco-storyjs div.vco-explainer .vco-loading-container .vco-message p,.vco-storyjs div.vco-loading .vco-explainer-container .vco-message p,.vco-storyjs div.vco-explainer .vco-explainer-container .vco-message p{text-align:center;font-size:11px;line-height:13px;text-transform:uppercase;margin-top:7.5px;margin-bottom:7.5px;} +.vco-storyjs div.vco-explainer{background-color:transparent;} +.vco-storyjs .vco-bezel{background-color:#333333;background-color:rgba(0, 0, 0, 0.8);width:80px;height:50px;padding:50px;padding-top:25px;padding:25px 20px 50px 20px;margin:auto;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;}.vco-storyjs .vco-bezel .vco-message,.vco-storyjs .vco-bezel .vco-message p{color:#ffffff;font-weight:bold;} +.vco-storyjs .vco-container.vco-main{position:absolute;top:0px;left:0px;padding-bottom:3px;width:auto;height:auto;margin:0px;clear:both;} +.vco-storyjs img,.vco-storyjs embed,.vco-storyjs object,.vco-storyjs video,.vco-storyjs iframe{max-width:100%;} +.vco-storyjs img{max-height:100%;border:1px solid #999999;} +.vco-storyjs a{color:#0088cc;text-decoration:none;} +.vco-storyjs a:hover{color:#005580;text-decoration:underline;} +.vco-storyjs .vcard{float:right;margin-bottom:15px;}.vco-storyjs .vcard a{color:#333333;} +.vco-storyjs .vcard a:hover{text-decoration:none;}.vco-storyjs .vcard a:hover .fn{text-decoration:underline;} +.vco-storyjs .vcard .fn,.vco-storyjs .vcard .nickname{padding-left:42px;} +.vco-storyjs .vcard .fn{display:block;font-weight:bold;} +.vco-storyjs .vcard .nickname{margin-top:1px;display:block;color:#666666;} +.vco-storyjs .vcard .avatar{float:left;display:block;width:32px;height:32px;}.vco-storyjs .vcard .avatar img{-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;} +.vco-storyjs .thumbnail{width:24px;height:24px;overflow:hidden;float:left;margin:0;margin-right:1px;margin-top:6px;border:0;padding:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.vco-storyjs a.thumbnail:hover{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.vco-storyjs .thumbnail.thumb-plaintext{background-repeat:no-repeat;background-position:-280px -48px;} +.vco-storyjs .thumbnail.thumb-quote{background-repeat:no-repeat;background-position:-232px -48px;} +.vco-storyjs .thumbnail.thumb-document{background-repeat:no-repeat;background-position:-256px -48px;} +.vco-storyjs .thumbnail.thumb-photo{background-repeat:no-repeat;background-position:-280px -24px;border:0;}.vco-storyjs .thumbnail.thumb-photo img{border:0px none #cccccc !important;} +.vco-storyjs .thumbnail.thumb-twitter{background-repeat:no-repeat;background-position:-256px -24px;} +.vco-storyjs .thumbnail.thumb-vimeo{background-repeat:no-repeat;background-position:-328px -48px;} +.vco-storyjs .thumbnail.thumb-vine{background-repeat:no-repeat;background-position:-232px -72px;} +.vco-storyjs .thumbnail.thumb-youtube{background-repeat:no-repeat;background-position:-328px -72px;} +.vco-storyjs .thumbnail.thumb-video{background-repeat:no-repeat;background-position:-328px -24px;} +.vco-storyjs .thumbnail.thumb-audio{background-repeat:no-repeat;background-position:-304px -24px;} +.vco-storyjs .thumbnail.thumb-map{background-repeat:no-repeat;background-position:-208px -48px;} +.vco-storyjs .thumbnail.thumb-website{background-repeat:no-repeat;background-position:-232px -24px;} +.vco-storyjs .thumbnail.thumb-link{background-repeat:no-repeat;background-position:-184px -72px;} +.vco-storyjs .thumbnail.thumb-wikipedia{background-repeat:no-repeat;background-position:-184px -48px;} +.vco-storyjs .thumbnail.thumb-storify{background-repeat:no-repeat;background-position:-328px -96px;} +.vco-storyjs .thumbnail.thumb-googleplus{background-repeat:no-repeat;background-position:-208px -72px;} +.vco-storyjs thumbnail.thumb-instagram{background-repeat:no-repeat;background-position:-208px -96px;} +.vco-storyjs thumbnail.thumb-instagram-full{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-232px -96px;width:48px;height:24px;} +.vco-storyjs .thumb-storify-full{height:12px;background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-280px -96px;width:48px;} +.vco-storyjs .thumbnail-inline{width:16px;height:14px;overflow:hidden;display:inline-block;margin-right:1px;margin-left:3px;margin-top:2px;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.vco-storyjs .twitter .thumbnail-inline{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-160px -96px;} +.vco-storyjs .storify .thumbnail-inline{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-184px -96px;} +.vco-storyjs .googleplus .thumbnail-inline{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-208px -96px;} +.vco-storyjs .zFront{z-index:204;} +@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2){.vco-storyjs div.vco-loading .vco-loading-container .vco-loading-icon,.vco-storyjs div.vco-explainer .vco-loading-container .vco-loading-icon,.vco-storyjs div.vco-loading .vco-explainer-container .vco-loading-icon,.vco-storyjs div.vco-explainer .vco-explainer-container .vco-loading-icon{background-image:url(loading@2x.gif?v3.4);} .vco-storyjs div.vco-loading .vco-loading-container .vco-gesture-icon,.vco-storyjs div.vco-explainer .vco-loading-container .vco-gesture-icon,.vco-storyjs div.vco-loading .vco-explainer-container .vco-gesture-icon,.vco-storyjs div.vco-explainer .vco-explainer-container .vco-gesture-icon{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-160px -160px;width:48px;height:48px;}}.vco-notouch .vco-navigation .vco-toolbar .zoom-in:hover,.vco-notouch .vco-navigation .vco-toolbar .zoom-out:hover,.vco-notouch .vco-navigation .vco-toolbar .back-home:hover{color:#0088cc;cursor:pointer;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} +.vco-notouch .vco-navigation .timenav .content .marker.active:hover{cursor:default;}.vco-notouch .vco-navigation .timenav .content .marker.active:hover .flag .flag-content h3,.vco-notouch .vco-navigation .timenav .content .marker.active:hover .flag-small .flag-content h3{color:#0088cc;} +.vco-notouch .vco-navigation .timenav .content .marker.active:hover .flag .flag-content h4,.vco-notouch .vco-navigation .timenav .content .marker.active:hover .flag-small .flag-content h4{color:#999999;} +.vco-notouch .vco-navigation .timenav .content .marker:hover .line{z-index:24;background:#999999;} +.vco-notouch .vco-navigation .timenav .content .marker .flag:hover,.vco-notouch .vco-navigation .timenav .content .marker .flag-small:hover{cursor:pointer;}.vco-notouch .vco-navigation .timenav .content .marker .flag:hover .flag-content h3,.vco-notouch .vco-navigation .timenav .content .marker .flag-small:hover .flag-content h3{color:#333333;} +.vco-notouch .vco-navigation .timenav .content .marker .flag:hover .flag-content h4,.vco-notouch .vco-navigation .timenav .content .marker .flag-small:hover .flag-content h4{color:#aaaaaa;} +.vco-notouch .vco-navigation .timenav .content .marker .flag:hover .flag-content .thumbnail,.vco-notouch .vco-navigation .timenav .content .marker .flag-small:hover .flag-content .thumbnail{filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} +.vco-notouch .vco-navigation .timenav .content .marker .flag:hover{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:0 -53px;width:153px;height:53px;} +.vco-notouch .vco-navigation .timenav .content .marker .flag-small:hover{height:56px;background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:0 -53px;width:153px;height:53px;}.vco-notouch .vco-navigation .timenav .content .marker .flag-small:hover .flag-content{height:36px;}.vco-notouch .vco-navigation .timenav .content .marker .flag-small:hover .flag-content h3{margin-top:5px;} +.vco-notouch .vco-navigation .timenav .content .marker .flag-small.flag-small-last:hover{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:0 -109px;width:153px;height:26px;}.vco-notouch .vco-navigation .timenav .content .marker .flag-small.flag-small-last:hover .flag-content{height:14px;}.vco-notouch .vco-navigation .timenav .content .marker .flag-small.flag-small-last:hover .flag-content h3{margin-top:4px;} +.vco-timeline .vco-navigation{clear:both;cursor:move;width:100%;height:200px;border-top:1px solid #e3e3e3;position:relative;}.vco-timeline .vco-navigation .vco-toolbar{position:absolute;top:45px;left:0px;z-index:202;background-color:#ffffff;border:1px solid #cccccc;-webkit-box-shadow:1px 1px 0px rgba(0, 0, 0, 0.2);-moz-box-shadow:1px 1px 0px rgba(0, 0, 0, 0.2);box-shadow:1px 1px 0px rgba(0, 0, 0, 0.2);}.vco-timeline .vco-navigation .vco-toolbar .zoom-in,.vco-timeline .vco-navigation .vco-toolbar .zoom-out,.vco-timeline .vco-navigation .vco-toolbar .back-home{font-weight:normal;font-size:10px;line-height:20px;top:0px;z-index:202;width:18px;height:18px;color:#333333;text-align:center;font-weight:bold;border:1px solid #ffffff;padding:5px;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.vco-timeline .vco-navigation .vco-toolbar .zoom-in .icon{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-256px 0;width:24px;height:24px;} +.vco-timeline .vco-navigation .vco-toolbar .zoom-out .icon{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-280px 0;width:24px;height:24px;} +.vco-timeline .vco-navigation .vco-toolbar .back-home .icon{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-328px 0;width:24px;height:24px;} +.vco-timeline .vco-navigation .vco-toolbar.touch{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;background-color:transparent;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.vco-timeline .vco-navigation .vco-toolbar.touch .zoom-in,.vco-timeline .vco-navigation .vco-toolbar.touch .zoom-out,.vco-timeline .vco-navigation .vco-toolbar.touch .back-home{width:40px;height:40px;padding:5px;background-color:#ffffff;border:1px solid #cccccc;-webkit-box-shadow:1px 1px 0px rgba(0, 0, 0, 0.2);-moz-box-shadow:1px 1px 0px rgba(0, 0, 0, 0.2);box-shadow:1px 1px 0px rgba(0, 0, 0, 0.2);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} +.vco-timeline .vco-navigation .vco-toolbar.touch .zoom-in .icon{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-208px -160px;width:40px;height:40px;} +.vco-timeline .vco-navigation .vco-toolbar.touch .zoom-out .icon{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-256px -160px;width:40px;height:40px;} +.vco-timeline .vco-navigation .vco-toolbar.touch .back-home .icon{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-304px -160px;width:40px;height:40px;} +.vco-timeline .vco-navigation .timenav-background{position:absolute;cursor:move;top:0px;left:0px;height:150px;width:100%;background-color:#e9e9e9;}.vco-timeline .vco-navigation .timenav-background .timenav-interval-background{position:absolute;top:151px;left:0px;background:#ffffff;width:100%;height:49px;-webkit-box-shadow:-1px -1px 7px rgba(0, 0, 0, 0.1);-moz-box-shadow:-1px -1px 7px rgba(0, 0, 0, 0.1);box-shadow:-1px -1px 7px rgba(0, 0, 0, 0.1);}.vco-timeline .vco-navigation .timenav-background .timenav-interval-background .top-highlight{position:absolute;top:-1px;left:0px;z-index:30;width:100%;height:1px;background:#ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;-webkit-box-shadow:1px 1px 5px rgba(0, 0, 0, 0.2);-moz-box-shadow:1px 1px 5px rgba(0, 0, 0, 0.2);box-shadow:1px 1px 5px rgba(0, 0, 0, 0.2);} +.vco-timeline .vco-navigation .timenav-background .timenav-line{position:absolute;top:0px;left:50%;width:3px;height:150px;background-color:#0088cc;z-index:1;-webkit-box-shadow:1px 1px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:1px 1px 7px rgba(0, 0, 0, 0.3);box-shadow:1px 1px 7px rgba(0, 0, 0, 0.3);} +.vco-timeline .vco-navigation .timenav-background .timenav-indicator{position:absolute;top:-1px;left:50%;z-index:202;background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-160px -48px;width:24px;height:24px;} +.vco-timeline .vco-navigation .timenav-background .timenav-tag div{height:50px;display:table;}.vco-timeline .vco-navigation .timenav-background .timenav-tag div h3{display:table-cell;vertical-align:middle;padding-left:65px;font-size:15px;color:#d0d0d0;font-weight:bold;text-shadow:0px 1px 1px #ffffff;} +.vco-timeline .vco-navigation .timenav-background .timenav-tag-size-half{height:25px;}.vco-timeline .vco-navigation .timenav-background .timenav-tag-size-half div{height:25px;} +.vco-timeline .vco-navigation .timenav-background .timenav-tag-size-full{height:50px;}.vco-timeline .vco-navigation .timenav-background .timenav-tag-size-full div{height:50px;} +.vco-timeline .vco-navigation .timenav-background .timenav-tag-row-2,.vco-timeline .vco-navigation .timenav-background .timenav-tag-row-4,.vco-timeline .vco-navigation .timenav-background .timenav-tag-row-6{background:#f1f1f1;} +.vco-timeline .vco-navigation .timenav-background .timenav-tag-row-1,.vco-timeline .vco-navigation .timenav-background .timenav-tag-row-3,.vco-timeline .vco-navigation .timenav-background .timenav-tag-row-5{background:#e9e9e9;} +.vco-timeline .vco-navigation .timenav{position:absolute;top:0px;left:-250px;z-index:1;}.vco-timeline .vco-navigation .timenav .content{position:relative;}.vco-timeline .vco-navigation .timenav .content .marker.start{display:none;} +.vco-timeline .vco-navigation .timenav .content .marker.active .dot{background:#0088cc;z-index:200;} +.vco-timeline .vco-navigation .timenav .content .marker.active .line{z-index:199;background:#0088cc;width:1px;}.vco-timeline .vco-navigation .timenav .content .marker.active .line .event-line{background:#0088cc;filter:alpha(opacity=75);-khtml-opacity:0.75;-moz-opacity:0.75;opacity:0.75;} +.vco-timeline .vco-navigation .timenav .content .marker.active .flag,.vco-timeline .vco-navigation .timenav .content .marker.active .flag-small{z-index:200;}.vco-timeline .vco-navigation .timenav .content .marker.active .flag .flag-content,.vco-timeline .vco-navigation .timenav .content .marker.active .flag-small .flag-content{height:36px;}.vco-timeline .vco-navigation .timenav .content .marker.active .flag .flag-content h3,.vco-timeline .vco-navigation .timenav .content .marker.active .flag-small .flag-content h3{color:#0088cc;margin-top:5px;} +.vco-timeline .vco-navigation .timenav .content .marker.active .flag .flag-content .thumbnail,.vco-timeline .vco-navigation .timenav .content .marker.active .flag-small .flag-content .thumbnail{filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} +.vco-timeline .vco-navigation .timenav .content .marker.active .flag.row1,.vco-timeline .vco-navigation .timenav .content .marker.active .flag.row2,.vco-timeline .vco-navigation .timenav .content .marker.active .flag.row3,.vco-timeline .vco-navigation .timenav .content .marker.active .flag-small.row1,.vco-timeline .vco-navigation .timenav .content .marker.active .flag-small.row2,.vco-timeline .vco-navigation .timenav .content .marker.active .flag-small.row3{z-index:200;} +.vco-timeline .vco-navigation .timenav .content .marker.active .flag{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:0 -53px;width:153px;height:53px;} +.vco-timeline .vco-navigation .timenav .content .marker.active .flag-small{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:0 -109px;width:153px;height:26px;}.vco-timeline .vco-navigation .timenav .content .marker.active .flag-small .flag-content{height:14px;}.vco-timeline .vco-navigation .timenav .content .marker.active .flag-small .flag-content h3{margin-top:4px;} +.vco-timeline .vco-navigation .timenav .content .marker{position:absolute;top:0px;left:150px;display:block;}.vco-timeline .vco-navigation .timenav .content .marker .dot{position:absolute;top:150px;left:0px;display:block;width:6px;height:6px;background:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;z-index:21;} +.vco-timeline .vco-navigation .timenav .content .marker .line{position:absolute;top:0px;left:3px;width:1px;height:150px;background-color:#cccccc;background-color:rgba(204, 204, 204, 0.5);-webkit-box-shadow:1px 0 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:1px 0 0 rgba(255, 255, 255, 0.5);box-shadow:1px 0 0 rgba(255, 255, 255, 0.5);z-index:22;}.vco-timeline .vco-navigation .timenav .content .marker .line .event-line{position:absolute;z-index:22;left:0px;height:1px;width:1px;background:#0088cc;filter:alpha(opacity=15);-khtml-opacity:0.15;-moz-opacity:0.15;opacity:0.15;} +.vco-timeline .vco-navigation .timenav .content .marker .flag,.vco-timeline .vco-navigation .timenav .content .marker .flag-small{position:absolute;top:15px;left:3px;padding:0px;display:block;z-index:23;width:153px;}.vco-timeline .vco-navigation .timenav .content .marker .flag .flag-content,.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content{padding:0px 7px 2px 6px;overflow:hidden;}.vco-timeline .vco-navigation .timenav .content .marker .flag .flag-content h3,.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content h3{font-weight:bold;font-size:15px;line-height:20px;font-size:11px;line-height:11px;color:#999999;margin-bottom:2px;}.vco-timeline .vco-navigation .timenav .content .marker .flag .flag-content h3 small,.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content h3 small{display:none;} +.vco-timeline .vco-navigation .timenav .content .marker .flag .flag-content h4,.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content h4{display:none;font-weight:normal;font-size:15px;line-height:20px;margin-top:5px;font-size:10px;line-height:10px;color:#aaaaaa;}.vco-timeline .vco-navigation .timenav .content .marker .flag .flag-content h4 small,.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content h4 small{display:none;} +.vco-timeline .vco-navigation .timenav .content .marker .flag .flag-content .thumbnail,.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail{margin-bottom:15px;margin-right:3px;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;}.vco-timeline .vco-navigation .timenav .content .marker .flag .flag-content .thumbnail img,.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail img{width:22px;height:22px;max-height:none;max-width:none;border:0;border:1px solid #999999;padding:0;margin:0;} +.vco-timeline .vco-navigation .timenav .content .marker .flag{height:56px;background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:0 0;width:153px;height:53px;}.vco-timeline .vco-navigation .timenav .content .marker .flag .flag-content{height:36px;}.vco-timeline .vco-navigation .timenav .content .marker .flag .flag-content h3{margin-top:5px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:0 -135px;width:153px;height:26px;}.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content{height:14px;}.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content h3{margin-top:4px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail{width:16px;height:10px;margin-right:1px;margin-top:6px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-plaintext{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-280px -130px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-quote{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-232px -130px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-document{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-256px -130px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-photo{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-280px -120px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-twitter{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-256px -120px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-vimeo{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-328px -130px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-vine{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-160px -120px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-youtube{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-304px -130px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-video{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-328px -120px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-audio{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-304px -120px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-map{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-208px -120px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-website{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-232px -120px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-link{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-232px -120px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-wikipedia{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-184px -120px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-storify{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-184px -130px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content .thumbnail.thumb-googleplus{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-208px -130px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small .flag-content thumbnail.thumb-instagram{background-image:url(timeline.png?v4.4);background-repeat:no-repeat;background-position:-208px -96px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag.row1{z-index:25;top:48px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag.row2{z-index:24;top:96px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag.row3{z-index:23;top:1px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small.row1{z-index:28;top:24px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small.row2{z-index:27;top:48px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small.row3{z-index:26;top:72px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small.row4{z-index:25;top:96px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small.row5{z-index:24;top:120px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag-small.row6{z-index:23;top:1px;} +.vco-timeline .vco-navigation .timenav .content .marker .flag.zFront,.vco-timeline .vco-navigation .timenav .content .marker .flag-small.zFront{z-index:201;} +.vco-timeline .vco-navigation .timenav .content .era{position:absolute;top:138px;left:150px;height:12px;display:block;overflow:hidden;}.vco-timeline .vco-navigation .timenav .content .era div{height:50px;width:100%;height:100%;line-height:0px;background:#e9e9e9;background:rgba(233, 233, 233, 0.33);}.vco-timeline .vco-navigation .timenav .content .era div h3,.vco-timeline .vco-navigation .timenav .content .era div h4{position:absolute;bottom:1px;padding-left:15px;font-size:15px;font-weight:bold;color:rgba(0, 136, 204, 0.35);text-shadow:0px 1px 1px #ffffff;} +.vco-timeline .vco-navigation .timenav .content .era1 div{background:#cc4400;filter:alpha(opacity=10);-khtml-opacity:0.1;-moz-opacity:0.1;opacity:0.1;border-left:1px solid rgba(204, 68, 0, 0.1);border-right:1px solid rgba(255, 85, 0, 0.05);}.vco-timeline .vco-navigation .timenav .content .era1 div h3,.vco-timeline .vco-navigation .timenav .content .era1 div h4{color:rgba(204, 68, 0, 0.35);text-shadow:0px 1px 1px #ffffff;} +.vco-timeline .vco-navigation .timenav .content .era2 div{background:#cc0022;filter:alpha(opacity=10);-khtml-opacity:0.1;-moz-opacity:0.1;opacity:0.1;border-left:1px solid rgba(204, 0, 34, 0.1);border-right:1px solid rgba(255, 0, 43, 0.05);}.vco-timeline .vco-navigation .timenav .content .era2 div h3,.vco-timeline .vco-navigation .timenav .content .era2 div h4{color:rgba(204, 0, 34, 0.35);text-shadow:0px 1px 1px #ffffff;} +.vco-timeline .vco-navigation .timenav .content .era3 div{background:#0022cc;filter:alpha(opacity=10);-khtml-opacity:0.1;-moz-opacity:0.1;opacity:0.1;border-left:1px solid rgba(0, 34, 204, 0.1);border-right:1px solid rgba(0, 43, 255, 0.05);}.vco-timeline .vco-navigation .timenav .content .era3 div h3,.vco-timeline .vco-navigation .timenav .content .era3 div h4{color:rgba(0, 34, 204, 0.35);text-shadow:0px 1px 1px #ffffff;} +.vco-timeline .vco-navigation .timenav .content .era4 div{background:#ccaa00;filter:alpha(opacity=10);-khtml-opacity:0.1;-moz-opacity:0.1;opacity:0.1;border-left:1px solid rgba(204, 170, 0, 0.1);border-right:1px solid rgba(255, 213, 0, 0.05);}.vco-timeline .vco-navigation .timenav .content .era4 div h3,.vco-timeline .vco-navigation .timenav .content .era4 div h4{color:rgba(204, 170, 0, 0.35);text-shadow:0px 1px 1px #ffffff;} +.vco-timeline .vco-navigation .timenav .content .era5 div{background:#00ccaa;filter:alpha(opacity=10);-khtml-opacity:0.1;-moz-opacity:0.1;opacity:0.1;border-left:1px solid rgba(0, 204, 170, 0.1);border-right:1px solid rgba(0, 255, 213, 0.05);}.vco-timeline .vco-navigation .timenav .content .era5 div h3,.vco-timeline .vco-navigation .timenav .content .era5 div h4{color:rgba(0, 204, 170, 0.35);text-shadow:0px 1px 1px #ffffff;} +.vco-timeline .vco-navigation .timenav .content .era6 div{background:#0088cc;filter:alpha(opacity=10);-khtml-opacity:0.1;-moz-opacity:0.1;opacity:0.1;border-left:1px solid rgba(0, 136, 204, 0.1);border-right:1px solid rgba(0, 170, 255, 0.05);}.vco-timeline .vco-navigation .timenav .content .era6 div h3,.vco-timeline .vco-navigation .timenav .content .era6 div h4{color:rgba(0, 136, 204, 0.35);text-shadow:0px 1px 1px #ffffff;} +.vco-timeline .vco-navigation .timenav .time{position:absolute;left:0px;top:150px;height:50px;background-color:#ffffff;line-height:0px;}.vco-timeline .vco-navigation .timenav .time .time-interval-minor{max-width:none;height:6px;white-space:nowrap;position:absolute;top:-2px;left:8px;z-index:10;}.vco-timeline .vco-navigation .timenav .time .time-interval-minor .minor{position:relative;top:2px;display:inline-block;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAMCAMAAACdvocfAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRFzMzM////040VdgAAAAJ0Uk5T/wDltzBKAAAAEklEQVR42mJgYAQCBopJgAADAAbwADHy2qHzAAAAAElFTkSuQmCC);width:100px;height:6px;background-position:center top;white-space:nowrap;color:#666666;margin-top:0px;padding-top:0px;} +.vco-timeline .vco-navigation .timenav .time .time-interval{white-space:nowrap;position:absolute;top:5px;left:0px;}.vco-timeline .vco-navigation .timenav .time .time-interval div{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAMCAMAAACdvocfAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRFzMzM////040VdgAAAAJ0Uk5T/wDltzBKAAAAEklEQVR42mJgYAQCBopJgAADAAbwADHy2qHzAAAAAElFTkSuQmCC);background-position:left top;background-repeat:no-repeat;padding-top:6px;position:absolute;height:3px;left:0px;display:block;font-weight:normal;font-size:10px;line-height:20px;text-transform:uppercase;text-align:left;text-indent:0px;white-space:nowrap;color:#666666;margin-left:0px;margin-right:0px;margin-top:0px;z-index:2;}.vco-timeline .vco-navigation .timenav .time .time-interval div strong{font-weight:bold;color:#000000;} +.vco-timeline .vco-navigation .timenav .time .time-interval div.era{font-weight:bold;padding-top:0px;margin-top:-3px;margin-left:2px;background-image:none;} +.vco-timeline .vco-navigation .timenav .time .time-interval .era1{color:#cc4400;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.vco-timeline .vco-navigation .timenav .time .time-interval .era2{color:#cc0022;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.vco-timeline .vco-navigation .timenav .time .time-interval .era3{color:#0022cc;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.vco-timeline .vco-navigation .timenav .time .time-interval .era4{color:#ccaa00;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.vco-timeline .vco-navigation .timenav .time .time-interval .era5{color:#00ccaa;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.vco-timeline .vco-navigation .timenav .time .time-interval .era6{color:#0088cc;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.vco-timeline .vco-navigation .timenav .time .time-interval-major{white-space:nowrap;position:absolute;top:5px;left:0px;}.vco-timeline .vco-navigation .timenav .time .time-interval-major div{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAQAQMAAADtUYf0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyOTAzRjI3REIzNDcxMUUxQUQ3QUZCOThEODQ1NDhCNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyOTAzRjI3RUIzNDcxMUUxQUQ3QUZCOThEODQ1NDhCNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjI5MDNGMjdCQjM0NzExRTFBRDdBRkI5OEQ4NDU0OEI3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjI5MDNGMjdDQjM0NzExRTFBRDdBRkI5OEQ4NDU0OEI3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+DPWNfQAAAANQTFRFzMzMylJEJwAAAAtJREFUCB1jYMAPAAAgAAHDvpOtAAAAAElFTkSuQmCC);background-position:left top;background-repeat:no-repeat;padding-top:15px;position:absolute;height:15px;left:0px;display:block;font-weight:bold;font-size:12px;line-height:20px;text-transform:uppercase;text-align:left;text-indent:0px;white-space:nowrap;color:#333333;margin-left:0px;margin-right:0px;margin-top:1px;z-index:5;}.vco-timeline .vco-navigation .timenav .time .time-interval-major div strong{font-weight:bold;color:#000000;} +@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2){.vco-notouch .vco-navigation .vco-toolbar .zoom-in .icon{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-256px 0;width:24px;height:24px;} .vco-notouch .vco-navigation .vco-toolbar .zoom-out .icon{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-280px 0;width:24px;height:24px;} .vco-notouch .vco-navigation .vco-toolbar .back-home .icon{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-328px 0;width:24px;height:24px;} .vco-notouch .vco-navigation .vco-toolbar.touch .zoom-in .icon{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-208px -160px;width:40px;height:40px;} .vco-notouch .vco-navigation .vco-toolbar.touch .zoom-out .icon{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-256px -160px;width:40px;height:40px;} .vco-notouch .vco-navigation .vco-toolbar.touch .back-home .icon{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-304px -160px;width:40px;height:40px;} .vco-notouch .vco-navigation .timenav .content .marker .flag:hover{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:0 -53px;width:153px;height:53px;} .vco-notouch .vco-navigation .timenav .content .marker .flag-small:hover{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:0 -53px;width:153px;height:53px;} .vco-notouch .vco-navigation .timenav .content .marker .flag-small.flag-small-last:hover{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:0 -109px;width:153px;height:26px;} .vco-notouch .vco-navigation .timenav-background .timenav-indicator{background-image:url(timeline@2x.png?v4.4);background-size:352px 260px;background-repeat:no-repeat;background-position:-160px -48px;width:24px;height:24px;}}@media screen and (-webkit-max-device-pixel-ratio:1){}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (-o-min-device-pixel-ratio:3/2),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (min-device-pixel-ratio:1.5){}@media screen and (max-device-width:480px) and (orientation:portrait){.storyjs-embed.full-embed{height:557px !important;width:320px !important;}.storyjs-embed.full-embed .vco-feature{height:356px !important;}}@media screen and (max-device-width:480px) and (orientation:landscape){.storyjs-embed.full-embed{height:409px !important;width:480px !important;}.storyjs-embed.full-embed .vco-feature{height:208px !important;}}@media screen and (min-device-width:481px) and (orientation:portrait){}@media screen and (min-device-width:481px) and (orientation:landscape){}@media (max-width:480px){}@media only screen and (max-width:480px){} diff --git a/vendor/timeline/css/timeline.png b/vendor/timeline/css/timeline.png new file mode 100644 index 00000000..857d0d19 Binary files /dev/null and b/vendor/timeline/css/timeline.png differ diff --git a/vendor/timeline/css/timeline@2x.png b/vendor/timeline/css/timeline@2x.png new file mode 100644 index 00000000..41b4eb25 Binary files /dev/null and b/vendor/timeline/css/timeline@2x.png differ diff --git a/vendor/timeline/js/timeline.js b/vendor/timeline/js/timeline.js new file mode 100644 index 00000000..4c7745aa --- /dev/null +++ b/vendor/timeline/js/timeline.js @@ -0,0 +1,10015 @@ +/*! + TimelineJS + Version 2.17 + Designed and built by Zach Wise at VéritéCo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +*/ + +/* ********************************************** + Begin VMM.StoryJS.License.js +********************************************** */ + +/*! + StoryJS + Designed and built by Zach Wise at VéritéCo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +/* ********************************************** + Begin VMM.js +********************************************** */ + +/** + * VéritéCo JS Core + * Designed and built by Zach Wise at VéritéCo zach@verite.co + + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + +*/ + + +/* Simple JavaScript Inheritance + By John Resig http://ejohn.org/ + MIT Licensed. +================================================== */ +(function() { + var initializing = false, + fnTest = /xyz/.test(function() { + xyz; + }) ? /\b_super\b/: /.*/; + // The base Class implementation (does nothing) + this.Class = function() {}; + + // Create a new Class that inherits from this class + Class.extend = function(prop) { + var _super = this.prototype; + + // Instantiate a base class (but only create the instance, + // don't run the init constructor) + initializing = true; + var prototype = new this(); + initializing = false; + + // Copy the properties over onto the new prototype + for (var name in prop) { + // Check if we're overwriting an existing function + prototype[name] = typeof prop[name] == "function" && + typeof _super[name] == "function" && fnTest.test(prop[name]) ? + (function(name, fn) { + return function() { + var tmp = this._super; + + // Add a new ._super() method that is the same method + // but on the super-class + this._super = _super[name]; + + // The method only need to be bound temporarily, so we + // remove it when we're done executing + var ret = fn.apply(this, arguments); + this._super = tmp; + + return ret; + }; + })(name, prop[name]) : + prop[name]; + } + + // The dummy class constructor + function Class() { + // All construction is actually done in the init method + if (!initializing && this.init) + this.init.apply(this, arguments); + } + + // Populate our constructed prototype object + Class.prototype = prototype; + + // Enforce the constructor to be what we expect + Class.prototype.constructor = Class; + + // And make this class extendable + Class.extend = arguments.callee; + + return Class; + }; +})(); + +/* Access to the Global Object + access the global object without hard-coding the identifier window +================================================== */ +var global = (function () { + return this || (1,eval)('this'); +}()); + +/* VMM +================================================== */ +if (typeof VMM == 'undefined') { + + /* Main Scope Container + ================================================== */ + //var VMM = {}; + var VMM = Class.extend({}); + + /* Debug + ================================================== */ + VMM.debug = true; + + /* Master Config + ================================================== */ + + VMM.master_config = ({ + + init: function() { + return this; + }, + + sizes: { + api: { + width: 0, + height: 0 + } + }, + + vp: "Pellentesque nibh felis, eleifend id, commodo in, interdum vitae, leo", + + api_keys_master: { + flickr: "RAIvxHY4hE/Elm5cieh4X5ptMyDpj7MYIxziGxi0WGCcy1s+yr7rKQ==", + //google: "jwNGnYw4hE9lmAez4ll0QD+jo6SKBJFknkopLS4FrSAuGfIwyj57AusuR0s8dAo=", + google: "uQKadH1VMlCsp560gN2aOiMz4evWkl1s34yryl3F/9FJOsn+/948CbBUvKLN46U=", + twitter: "" + }, + + timers: { + api: 7000 + }, + + api: { + pushques: [] + + }, + + twitter: { + active: false, + array: [], + api_loaded: false, + que: [] + }, + + flickr: { + active: false, + array: [], + api_loaded: false, + que: [] + }, + + youtube: { + active: false, + array: [], + api_loaded: false, + que: [] + }, + + vimeo: { + active: false, + array: [], + api_loaded: false, + que: [] + }, + + vine: { + active: false, + array: [], + api_loaded: false, + que: [] + }, + + webthumb: { + active: false, + array: [], + api_loaded: false, + que: [] + }, + + googlemaps: { + active: false, + map_active: false, + places_active: false, + array: [], + api_loaded: false, + que: [] + }, + + googledocs: { + active: false, + array: [], + api_loaded: false, + que: [] + }, + + googleplus: { + active: false, + array: [], + api_loaded: false, + que: [] + }, + + wikipedia: { + active: false, + array: [], + api_loaded: false, + que: [], + tries: 0 + }, + + soundcloud: { + active: false, + array: [], + api_loaded: false, + que: [] + } + + }).init(); + + //VMM.createElement(tag, value, cName, attrs, styles); + VMM.createElement = function(tag, value, cName, attrs, styles) { + + var ce = ""; + + if (tag != null && tag != "") { + + // TAG + ce += "<" + tag; + if (cName != null && cName != "") { + ce += " class='" + cName + "'"; + }; + + if (attrs != null && attrs != "") { + ce += " " + attrs; + }; + + if (styles != null && styles != "") { + ce += " style='" + styles + "'"; + }; + + ce += ">"; + + if (value != null && value != "") { + ce += value; + } + + // CLOSE TAG + ce = ce + ""; + } + + return ce; + + }; + + VMM.createMediaElement = function(media, caption, credit) { + + var ce = ""; + + var _valid = false; + + ce += "
    "; + + if (media != null && media != "") { + + valid = true; + + ce += ""; + + // CREDIT + if (credit != null && credit != "") { + ce += VMM.createElement("div", credit, "credit"); + } + + // CAPTION + if (caption != null && caption != "") { + ce += VMM.createElement("div", caption, "caption"); + } + + } + + ce += "
    "; + + return ce; + + }; + + // Hide URL Bar for iOS and Android by Scott Jehl + // https://gist.github.com/1183357 + + VMM.hideUrlBar = function () { + var win = window, + doc = win.document; + + // If there's a hash, or addEventListener is undefined, stop here + if( !location.hash || !win.addEventListener ){ + + //scroll to 1 + window.scrollTo( 0, 1 ); + var scrollTop = 1, + + //reset to 0 on bodyready, if needed + bodycheck = setInterval(function(){ + if( doc.body ){ + clearInterval( bodycheck ); + scrollTop = "scrollTop" in doc.body ? doc.body.scrollTop : 1; + win.scrollTo( 0, scrollTop === 1 ? 0 : 1 ); + } + }, 15 ); + + win.addEventListener( "load", function(){ + setTimeout(function(){ + //reset to hide addr bar at onload + win.scrollTo( 0, scrollTop === 1 ? 0 : 1 ); + }, 0); + }, false ); + } + }; + + +} + +/* Trace (console.log) +================================================== */ +function trace( msg ) { + if (VMM.debug) { + if (window.console) { + console.log(msg); + } else if ( typeof( jsTrace ) != 'undefined' ) { + jsTrace.send( msg ); + } else { + //alert(msg); + } + } +} + +/* Array Remove - By John Resig (MIT Licensed) + http://ejohn.org/blog/javascript-array-remove/ +================================================== */ +Array.prototype.remove = function(from, to) { + var rest = this.slice((to || from) + 1 || this.length); + this.length = from < 0 ? this.length + from : from; + return this.push.apply(this, rest); +} + +/* Extending Date to include Week +================================================== */ +Date.prototype.getWeek = function() { + var onejan = new Date(this.getFullYear(),0,1); + return Math.ceil((((this - onejan) / 86400000) + onejan.getDay()+1)/7); +} + +/* Extending Date to include Day of Year +================================================== */ +Date.prototype.getDayOfYear = function() { + var onejan = new Date(this.getFullYear(),0,1); + return Math.ceil((this - onejan) / 86400000); +} + +/* A MORE SPECIFIC TYPEOF(); +// http://rolandog.com/archives/2007/01/18/typeof-a-more-specific-typeof/ +================================================== */ +// type.of() +var is={ + Null:function(a){return a===null;}, + Undefined:function(a){return a===undefined;}, + nt:function(a){return(a===null||a===undefined);}, + Function:function(a){return(typeof(a)==="function")?a.constructor.toString().match(/Function/)!==null:false;}, + String:function(a){return(typeof(a)==="string")?true:(typeof(a)==="object")?a.constructor.toString().match(/string/i)!==null:false;}, + Array:function(a){return(typeof(a)==="object")?a.constructor.toString().match(/array/i)!==null||a.length!==undefined:false;}, + Boolean:function(a){return(typeof(a)==="boolean")?true:(typeof(a)==="object")?a.constructor.toString().match(/boolean/i)!==null:false;}, + Date:function(a){return(typeof(a)==="date")?true:(typeof(a)==="object")?a.constructor.toString().match(/date/i)!==null:false;}, + HTML:function(a){return(typeof(a)==="object")?a.constructor.toString().match(/html/i)!==null:false;}, + Number:function(a){return(typeof(a)==="number")?true:(typeof(a)==="object")?a.constructor.toString().match(/Number/)!==null:false;}, + Object:function(a){return(typeof(a)==="object")?a.constructor.toString().match(/object/i)!==null:false;}, + RegExp:function(a){return(typeof(a)==="function")?a.constructor.toString().match(/regexp/i)!==null:false;} +}; +var type={ + of:function(a){ + for(var i in is){ + if(is[i](a)){ + return i.toLowerCase(); + } + } + } +}; + + + + + +/* ********************************************** + Begin VMM.Library.js +********************************************** */ + +/* * LIBRARY ABSTRACTION +================================================== */ +if(typeof VMM != 'undefined') { + + VMM.smoothScrollTo = function(elem, duration, ease) { + if( typeof( jQuery ) != 'undefined' ){ + var _ease = "easein", + _duration = 1000; + + if (duration != null) { + if (duration < 1) { + _duration = 1; + } else { + _duration = Math.round(duration); + } + + } + + if (ease != null && ease != "") { + _ease = ease; + } + + if (jQuery(window).scrollTop() != VMM.Lib.offset(elem).top) { + VMM.Lib.animate('html,body', _duration, _ease, {scrollTop: VMM.Lib.offset(elem).top}) + } + + } + + }; + + VMM.attachElement = function(element, content) { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).html(content); + } + + }; + + VMM.appendElement = function(element, content) { + + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).append(content); + } + + }; + + VMM.getHTML = function(element) { + var e; + if( typeof( jQuery ) != 'undefined' ){ + e = jQuery(element).html(); + return e; + } + + }; + + VMM.getElement = function(element, p) { + var e; + if( typeof( jQuery ) != 'undefined' ){ + if (p) { + e = jQuery(element).parent().get(0); + + } else { + e = jQuery(element).get(0); + } + return e; + } + + }; + + VMM.bindEvent = function(element, the_handler, the_event_type, event_data) { + var e; + var _event_type = "click"; + var _event_data = {}; + + if (the_event_type != null && the_event_type != "") { + _event_type = the_event_type; + } + + if (_event_data != null && _event_data != "") { + _event_data = event_data; + } + + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).bind(_event_type, _event_data, the_handler); + + //return e; + } + + }; + + VMM.unbindEvent = function(element, the_handler, the_event_type) { + var e; + var _event_type = "click"; + var _event_data = {}; + + if (the_event_type != null && the_event_type != "") { + _event_type = the_event_type; + } + + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).unbind(_event_type, the_handler); + + //return e; + } + + }; + + VMM.fireEvent = function(element, the_event_type, the_data) { + var e; + var _event_type = "click"; + var _data = []; + + if (the_event_type != null && the_event_type != "") { + _event_type = the_event_type; + } + if (the_data != null && the_data != "") { + _data = the_data; + } + + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).trigger(_event_type, _data); + + //return e; + } + + }; + + VMM.getJSON = function(url, data, callback) { + if( typeof( jQuery ) != 'undefined' ){ + jQuery.ajaxSetup({ + timeout: 3000 + }); + /* CHECK FOR IE + ================================================== */ + if ( VMM.Browser.browser == "Explorer" && parseInt(VMM.Browser.version, 10) >= 7 && window.XDomainRequest) { + trace("IE JSON"); + var ie_url = url; + if (ie_url.match('^http://')){ + return jQuery.getJSON(ie_url, data, callback); + } else if (ie_url.match('^https://')) { + ie_url = ie_url.replace("https://","http://"); + return jQuery.getJSON(ie_url, data, callback); + } else { + return jQuery.getJSON(url, data, callback); + } + + } else { + return jQuery.getJSON(url, data, callback); + + } + } + } + + VMM.parseJSON = function(the_json) { + if( typeof( jQuery ) != 'undefined' ){ + return jQuery.parseJSON(the_json); + } + } + + // ADD ELEMENT AND RETURN IT + VMM.appendAndGetElement = function(append_to_element, tag, cName, content) { + var e, + _tag = "
    ", + _class = "", + _content = "", + _id = ""; + + if (tag != null && tag != "") { + _tag = tag; + } + + if (cName != null && cName != "") { + _class = cName; + } + + if (content != null && content != "") { + _content = content; + } + + if( typeof( jQuery ) != 'undefined' ){ + + e = jQuery(tag); + + e.addClass(_class); + e.html(_content); + + jQuery(append_to_element).append(e); + + } + + return e; + + }; + + VMM.Lib = { + + init: function() { + return this; + }, + + hide: function(element, duration) { + if (duration != null && duration != "") { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).hide(duration); + } + } else { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).hide(); + } + } + + }, + + remove: function(element) { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).remove(); + } + }, + + detach: function(element) { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).detach(); + } + }, + + append: function(element, value) { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).append(value); + } + }, + + prepend: function(element, value) { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).prepend(value); + } + }, + + show: function(element, duration) { + if (duration != null && duration != "") { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).show(duration); + } + } else { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).show(); + } + } + + }, + + load: function(element, callback_function, event_data) { + var _event_data = {elem:element}; // return element by default + if (_event_data != null && _event_data != "") { + _event_data = event_data; + } + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).load(_event_data, callback_function); + } + }, + + addClass: function(element, cName) { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).addClass(cName); + } + }, + + removeClass: function(element, cName) { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).removeClass(cName); + } + }, + + attr: function(element, aName, value) { + if (value != null && value != "") { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).attr(aName, value); + } + } else { + if( typeof( jQuery ) != 'undefined' ){ + return jQuery(element).attr(aName); + } + } + }, + + prop: function(element, aName, value) { + if (typeof jQuery == 'undefined' || !/[1-9]\.[3-9].[1-9]/.test(jQuery.fn.jquery)) { + VMM.Lib.attribute(element, aName, value); + } else { + jQuery(element).prop(aName, value); + } + }, + + attribute: function(element, aName, value) { + + if (value != null && value != "") { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).attr(aName, value); + } + } else { + if( typeof( jQuery ) != 'undefined' ){ + return jQuery(element).attr(aName); + } + } + }, + + visible: function(element, show) { + if (show != null) { + if( typeof( jQuery ) != 'undefined' ){ + if (show) { + jQuery(element).show(0); + } else { + jQuery(element).hide(0); + } + } + } else { + if( typeof( jQuery ) != 'undefined' ){ + if ( jQuery(element).is(':visible')){ + return true; + } else { + return false; + } + } + } + }, + + css: function(element, prop, value) { + + if (value != null && value != "") { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).css(prop, value); + } + } else { + if( typeof( jQuery ) != 'undefined' ){ + return jQuery(element).css(prop); + } + } + }, + + cssmultiple: function(element, propval) { + + if( typeof( jQuery ) != 'undefined' ){ + return jQuery(element).css(propval); + } + }, + + offset: function(element) { + var p; + if( typeof( jQuery ) != 'undefined' ){ + p = jQuery(element).offset(); + } + return p; + }, + + position: function(element) { + var p; + if( typeof( jQuery ) != 'undefined' ){ + p = jQuery(element).position(); + } + return p; + }, + + width: function(element, s) { + if (s != null && s != "") { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).width(s); + } + } else { + if( typeof( jQuery ) != 'undefined' ){ + return jQuery(element).width(); + } + } + }, + + height: function(element, s) { + if (s != null && s != "") { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).height(s); + } + } else { + if( typeof( jQuery ) != 'undefined' ){ + return jQuery(element).height(); + } + } + }, + + toggleClass: function(element, cName) { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).toggleClass(cName); + } + }, + + each:function(element, return_function) { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).each(return_function); + } + + }, + + html: function(element, str) { + var e; + if( typeof( jQuery ) != 'undefined' ){ + e = jQuery(element).html(); + return e; + } + + if (str != null && str != "") { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).html(str); + } + } else { + var e; + if( typeof( jQuery ) != 'undefined' ){ + e = jQuery(element).html(); + return e; + } + } + + }, + + find: function(element, selec) { + if( typeof( jQuery ) != 'undefined' ){ + return jQuery(element).find(selec); + } + }, + + stop: function(element) { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).stop(); + } + }, + + delay_animate: function(delay, element, duration, ease, att, callback_function) { + if (VMM.Browser.device == "mobile" || VMM.Browser.device == "tablet") { + var _tdd = Math.round((duration/1500)*10)/10, + __duration = _tdd + 's'; + + VMM.Lib.css(element, '-webkit-transition', 'all '+ __duration + ' ease'); + VMM.Lib.css(element, '-moz-transition', 'all '+ __duration + ' ease'); + VMM.Lib.css(element, '-o-transition', 'all '+ __duration + ' ease'); + VMM.Lib.css(element, '-ms-transition', 'all '+ __duration + ' ease'); + VMM.Lib.css(element, 'transition', 'all '+ __duration + ' ease'); + VMM.Lib.cssmultiple(element, _att); + } else { + if( typeof( jQuery ) != 'undefined' ){ + jQuery(element).delay(delay).animate(att, {duration:duration, easing:ease} ); + } + } + + }, + + animate: function(element, duration, ease, att, que, callback_function) { + + var _ease = "easein", + _que = false, + _duration = 1000, + _att = {}; + + if (duration != null) { + if (duration < 1) { + _duration = 1; + } else { + _duration = Math.round(duration); + } + + } + + if (ease != null && ease != "") { + _ease = ease; + } + + if (que != null && que != "") { + _que = que; + } + + + if (att != null) { + _att = att + } else { + _att = {opacity: 0} + } + + + if (VMM.Browser.device == "mobile" || VMM.Browser.device == "tablet") { + + var _tdd = Math.round((_duration/1500)*10)/10, + __duration = _tdd + 's'; + + _ease = " cubic-bezier(0.33, 0.66, 0.66, 1)"; + //_ease = " ease-in-out"; + for (x in _att) { + if (Object.prototype.hasOwnProperty.call(_att, x)) { + trace(x + " to " + _att[x]); + VMM.Lib.css(element, '-webkit-transition', x + ' ' + __duration + _ease); + VMM.Lib.css(element, '-moz-transition', x + ' ' + __duration + _ease); + VMM.Lib.css(element, '-o-transition', x + ' ' + __duration + _ease); + VMM.Lib.css(element, '-ms-transition', x + ' ' + __duration + _ease); + VMM.Lib.css(element, 'transition', x + ' ' + __duration + _ease); + } + } + + VMM.Lib.cssmultiple(element, _att); + + } else { + if( typeof( jQuery ) != 'undefined' ){ + if (callback_function != null && callback_function != "") { + jQuery(element).animate(_att, {queue:_que, duration:_duration, easing:_ease, complete:callback_function} ); + } else { + jQuery(element).animate(_att, {queue:_que, duration:_duration, easing:_ease} ); + } + } + } + + } + + } +} + +if( typeof( jQuery ) != 'undefined' ){ + + /* XDR AJAX EXTENTION FOR jQuery + https://github.com/jaubourg/ajaxHooks/blob/master/src/ajax/xdr.js + ================================================== */ + (function( jQuery ) { + if ( window.XDomainRequest ) { + jQuery.ajaxTransport(function( s ) { + if ( s.crossDomain && s.async ) { + if ( s.timeout ) { + s.xdrTimeout = s.timeout; + delete s.timeout; + } + var xdr; + return { + send: function( _, complete ) { + function callback( status, statusText, responses, responseHeaders ) { + xdr.onload = xdr.onerror = xdr.ontimeout = jQuery.noop; + xdr = undefined; + complete( status, statusText, responses, responseHeaders ); + } + xdr = new XDomainRequest(); + xdr.open( s.type, s.url ); + xdr.onload = function() { + callback( 200, "OK", { text: xdr.responseText }, "Content-Type: " + xdr.contentType ); + }; + xdr.onerror = function() { + callback( 404, "Not Found" ); + }; + if ( s.xdrTimeout ) { + xdr.ontimeout = function() { + callback( 0, "timeout" ); + }; + xdr.timeout = s.xdrTimeout; + } + xdr.send( ( s.hasContent && s.data ) || null ); + }, + abort: function() { + if ( xdr ) { + xdr.onerror = jQuery.noop(); + xdr.abort(); + } + } + }; + } + }); + } + })( jQuery ); + + /* jQuery Easing v1.3 + http://gsgd.co.uk/sandbox/jquery/easing/ + ================================================== */ + jQuery.easing['jswing'] = jQuery.easing['swing']; + + jQuery.extend( jQuery.easing, { + def: 'easeOutQuad', + swing: function (x, t, b, c, d) { + //alert(jQuery.easing.default); + return jQuery.easing[jQuery.easing.def](x, t, b, c, d); + }, + easeInExpo: function (x, t, b, c, d) { + return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; + }, + easeOutExpo: function (x, t, b, c, d) { + return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; + }, + easeInOutExpo: function (x, t, b, c, d) { + if (t==0) return b; + if (t==d) return b+c; + if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; + return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; + }, + easeInQuad: function (x, t, b, c, d) { + return c*(t/=d)*t + b; + }, + easeOutQuad: function (x, t, b, c, d) { + return -c *(t/=d)*(t-2) + b; + }, + easeInOutQuad: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t + b; + return -c/2 * ((--t)*(t-2) - 1) + b; + } + }); +} + + +/* ********************************************** + Begin VMM.Browser.js +********************************************** */ + +/* * DEVICE AND BROWSER DETECTION +================================================== */ +if(typeof VMM != 'undefined' && typeof VMM.Browser == 'undefined') { + + VMM.Browser = { + init: function () { + this.browser = this.searchString(this.dataBrowser) || "An unknown browser"; + this.version = this.searchVersion(navigator.userAgent) + || this.searchVersion(navigator.appVersion) + || "an unknown version"; + this.OS = this.searchString(this.dataOS) || "an unknown OS"; + this.device = this.searchDevice(navigator.userAgent); + this.orientation = this.searchOrientation(window.orientation); + }, + searchOrientation: function(orientation) { + var orient = ""; + if ( orientation == 0 || orientation == 180) { + orient = "portrait"; + } else if ( orientation == 90 || orientation == -90) { + orient = "landscape"; + } else { + orient = "normal"; + } + return orient; + }, + searchDevice: function(d) { + var device = ""; + if (d.match(/Android/i) || d.match(/iPhone|iPod/i)) { + device = "mobile"; + } else if (d.match(/iPad/i)) { + device = "tablet"; + } else if (d.match(/BlackBerry/i) || d.match(/IEMobile/i)) { + device = "other mobile"; + } else { + device = "desktop"; + } + return device; + }, + searchString: function (data) { + for (var i=0;i'mmmm d',' yyyy''", + full_long: "mmm d',' yyyy 'at' hh:MM TT", + full_long_small_date: "hh:MM TT'
    mmm d',' yyyy''" + }, + + month: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + month_abbr: ["Jan.", "Feb.", "March", "April", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec."], + day: ["Sunday","Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + day_abbr: ["Sun.", "Mon.", "Tues.", "Wed.", "Thurs.", "Fri.", "Sat."], + hour: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + hour_suffix: ["am"], + + //B.C. + bc_format: { + year: "yyyy", + month_short: "mmm", + month: "mmmm yyyy", + full_short: "mmm d", + full: "mmmm d',' yyyy", + time_no_seconds_short: "h:MM TT", + time_no_seconds_small_date: "dddd', 'h:MM TT'
    'mmmm d',' yyyy''", + full_long: "dddd',' mmm d',' yyyy 'at' hh:MM TT", + full_long_small_date: "hh:MM TT'
    'dddd',' mmm d',' yyyy''" + }, + + setLanguage: function(lang) { + trace("SET DATE LANGUAGE"); + VMM.Date.dateformats = lang.dateformats; + VMM.Date.month = lang.date.month; + VMM.Date.month_abbr = lang.date.month_abbr; + VMM.Date.day = lang.date.day; + VMM.Date.day_abbr = lang.date.day_abbr; + dateFormat.i18n.dayNames = lang.date.day_abbr.concat(lang.date.day); + dateFormat.i18n.monthNames = lang.date.month_abbr.concat(lang.date.month); + }, + + parse: function(d, precision) { + "use strict"; + var date, + date_array, + time_array, + time_parse, + p = { + year: false, + month: false, + day: false, + hour: false, + minute: false, + second: false, + millisecond: false + }; + + if (type.of(d) == "date") { + trace("DEBUG THIS, ITs A DATE"); + date = d; + } else { + date = new Date(0, 0, 1, 0, 0, 0, 0); + + if ( d.match(/,/gi) ) { + date_array = d.split(","); + for(var i = 0; i < date_array.length; i++) { + date_array[i] = parseInt(date_array[i], 10); + } + if (date_array[0]) { + date.setFullYear(date_array[0]); + p.year = true; + } + if (date_array[1]) { + date.setMonth(date_array[1] - 1); + p.month = true; + } + if (date_array[2]) { + date.setDate(date_array[2]); + p.day = true; + } + if (date_array[3]) { + date.setHours(date_array[3]); + p.hour = true; + } + if (date_array[4]) { + date.setMinutes(date_array[4]); + p.minute = true; + } + if (date_array[5]) { + date.setSeconds(date_array[5]); + p.second = true; + } + if (date_array[6]) { + date.setMilliseconds(date_array[6]); + p.millisecond = true; + } + } else if (d.match("/")) { + if (d.match(" ")) { + + time_parse = d.split(" "); + if (d.match(":")) { + time_array = time_parse[1].split(":"); + if (time_array[0] >= 0 ) { + date.setHours(time_array[0]); + p.hour = true; + } + if (time_array[1] >= 0) { + date.setMinutes(time_array[1]); + p.minute = true; + } + if (time_array[2] >= 0) { + date.setSeconds(time_array[2]); + p.second = true; + } + if (time_array[3] >= 0) { + date.setMilliseconds(time_array[3]); + p.millisecond = true; + } + } + date_array = time_parse[0].split("/"); + } else { + date_array = d.split("/"); + } + if (date_array[2]) { + date.setFullYear(date_array[2]); + p.year = true; + } + if (date_array[0] >= 0) { + date.setMonth(date_array[0] - 1); + p.month = true; + } + if (date_array[1] >= 0) { + if (date_array[1].length > 2) { + date.setFullYear(date_array[1]); + p.year = true; + } else { + date.setDate(date_array[1]); + p.day = true; + } + } + } else if (d.match("now")) { + var now = new Date(); + + date.setFullYear(now.getFullYear()); + p.year = true; + + date.setMonth(now.getMonth()); + p.month = true; + + date.setDate(now.getDate()); + p.day = true; + + if (d.match("hours")) { + date.setHours(now.getHours()); + p.hour = true; + } + if (d.match("minutes")) { + date.setHours(now.getHours()); + date.setMinutes(now.getMinutes()); + p.hour = true; + p.minute = true; + } + if (d.match("seconds")) { + date.setHours(now.getHours()); + date.setMinutes(now.getMinutes()); + date.setSeconds(now.getSeconds()); + p.hour = true; + p.minute = true; + p.second = true; + } + if (d.match("milliseconds")) { + date.setHours(now.getHours()); + date.setMinutes(now.getMinutes()); + date.setSeconds(now.getSeconds()); + date.setMilliseconds(now.getMilliseconds()); + p.hour = true; + p.minute = true; + p.second = true; + p.millisecond = true; + } + } else if (d.length <= 8) { + p.year = true; + date.setFullYear(parseInt(d, 10)); + date.setMonth(0); + date.setDate(1); + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + date.setMilliseconds(0); + } else if (d.match("T")) { + if (navigator.userAgent.match(/MSIE\s(?!9.0)/)) { + // IE 8 < Won't accept dates with a "-" in them. + time_parse = d.split("T"); + if (d.match(":")) { + time_array = time_parse[1].split(":"); + if (time_array[0] >= 1) { + date.setHours(time_array[0]); + p.hour = true; + } + if (time_array[1] >= 1) { + date.setMinutes(time_array[1]); + p.minute = true; + } + if (time_array[2] >= 1) { + date.setSeconds(time_array[2]); + p.second = true; + } + if (time_array[3] >= 1) { + date.setMilliseconds(time_array[3]); + p.millisecond = true; + } + } + date_array = time_parse[0].split("-"); + if (date_array[0]) { + date.setFullYear(date_array[0]); + p.year = true; + } + if (date_array[1] >= 0) { + date.setMonth(date_array[1] - 1); + p.month = true; + } + if (date_array[2] >= 0) { + date.setDate(date_array[2]); + p.day = true; + } + + } else { + date = new Date(Date.parse(d)); + p.year = true; + p.month = true; + p.day = true; + p.hour = true; + p.minute = true; + p.second = true; + p.millisecond = true; + } + } else { + p.year = true; + p.month = true; + p.day = true; + p.hour = true; + p.minute = true; + p.second = true; + p.millisecond = true; + date = new Date( + parseInt(d.slice(0,4), 10), + parseInt(d.slice(4,6), 10) - 1, + parseInt(d.slice(6,8), 10), + parseInt(d.slice(8,10), 10), + parseInt(d.slice(10,12), 10) + ); + } + + } + + if (precision != null && precision != "") { + return { + date: date, + precision: p + }; + } else { + return date; + } + }, + + + + prettyDate: function(d, is_abbr, p, d2) { + var _date, + _date2, + format, + bc_check, + is_pair = false, + bc_original, + bc_number, + bc_string; + + if (d2 != null && d2 != "" && typeof d2 != 'undefined') { + is_pair = true; + trace("D2 " + d2); + } + + + if (type.of(d) == "date") { + + if (type.of(p) == "object") { + if (p.millisecond || p.second || p.minute) { + // YEAR MONTH DAY HOUR MINUTE + if (is_abbr){ + format = VMM.Date.dateformats.time_no_seconds_short; + } else { + format = VMM.Date.dateformats.time_no_seconds_small_date; + } + } else if (p.hour) { + // YEAR MONTH DAY HOUR + if (is_abbr) { + format = VMM.Date.dateformats.time_no_seconds_short; + } else { + format = VMM.Date.dateformats.time_no_seconds_small_date; + } + } else if (p.day) { + // YEAR MONTH DAY + if (is_abbr) { + format = VMM.Date.dateformats.full_short; + } else { + format = VMM.Date.dateformats.full; + } + } else if (p.month) { + // YEAR MONTH + if (is_abbr) { + format = VMM.Date.dateformats.month_short; + } else { + format = VMM.Date.dateformats.month; + } + } else if (p.year) { + format = VMM.Date.dateformats.year; + } else { + format = VMM.Date.dateformats.year; + } + + } else { + + if (d.getMonth() === 0 && d.getDate() == 1 && d.getHours() === 0 && d.getMinutes() === 0 ) { + // YEAR ONLY + format = VMM.Date.dateformats.year; + } else if (d.getDate() <= 1 && d.getHours() === 0 && d.getMinutes() === 0) { + // YEAR MONTH + if (is_abbr) { + format = VMM.Date.dateformats.month_short; + } else { + format = VMM.Date.dateformats.month; + } + } else if (d.getHours() === 0 && d.getMinutes() === 0) { + // YEAR MONTH DAY + if (is_abbr) { + format = VMM.Date.dateformats.full_short; + } else { + format = VMM.Date.dateformats.full; + } + } else if (d.getMinutes() === 0) { + // YEAR MONTH DAY HOUR + if (is_abbr) { + format = VMM.Date.dateformats.time_no_seconds_short; + } else { + format = VMM.Date.dateformats.time_no_seconds_small_date; + } + } else { + // YEAR MONTH DAY HOUR MINUTE + if (is_abbr){ + format = VMM.Date.dateformats.time_no_seconds_short; + } else { + format = VMM.Date.dateformats.full_long; + } + } + } + + _date = dateFormat(d, format, false); + //_date = "Jan" + bc_check = _date.split(" "); + + // BC TIME SUPPORT + for(var i = 0; i < bc_check.length; i++) { + if ( parseInt(bc_check[i], 10) < 0 ) { + trace("YEAR IS BC"); + bc_original = bc_check[i]; + bc_number = Math.abs( parseInt(bc_check[i], 10) ); + bc_string = bc_number.toString() + " B.C."; + _date = _date.replace(bc_original, bc_string); + } + } + + + if (is_pair) { + _date2 = dateFormat(d2, format, false); + bc_check = _date2.split(" "); + // BC TIME SUPPORT + for(var j = 0; j < bc_check.length; j++) { + if ( parseInt(bc_check[j], 10) < 0 ) { + trace("YEAR IS BC"); + bc_original = bc_check[j]; + bc_number = Math.abs( parseInt(bc_check[j], 10) ); + bc_string = bc_number.toString() + " B.C."; + _date2 = _date2.replace(bc_original, bc_string); + } + } + + } + } else { + trace("NOT A VALID DATE?"); + trace(d); + } + + if (is_pair) { + return _date + " — " + _date2; + } else { + return _date; + } + } + + }).init(); + + /* + * Date Format 1.2.3 + * (c) 2007-2009 Steven Levithan + * MIT license + * + * Includes enhancements by Scott Trenda + * and Kris Kowal + * + * Accepts a date, a mask, or a date and a mask. + * Returns a formatted version of the given date. + * The date defaults to the current date/time. + * The mask defaults to dateFormat.masks.default. + */ + + var dateFormat = function () { + var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, + timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, + timezoneClip = /[^-+\dA-Z]/g, + pad = function (val, len) { + val = String(val); + len = len || 2; + while (val.length < len) val = "0" + val; + return val; + }; + + // Regexes and supporting functions are cached through closure + return function (date, mask, utc) { + var dF = dateFormat; + + // You can't provide utc if you skip other args (use the "UTC:" mask prefix) + if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { + mask = date; + date = undefined; + } + + // Passing date through Date applies Date.parse, if necessary + // Caused problems in IE + // date = date ? new Date(date) : new Date; + if (isNaN(date)) { + trace("invalid date " + date); + //return ""; + } + + mask = String(dF.masks[mask] || mask || dF.masks["default"]); + + // Allow setting the utc argument via the mask + if (mask.slice(0, 4) == "UTC:") { + mask = mask.slice(4); + utc = true; + } + + var _ = utc ? "getUTC" : "get", + d = date[_ + "Date"](), + D = date[_ + "Day"](), + m = date[_ + "Month"](), + y = date[_ + "FullYear"](), + H = date[_ + "Hours"](), + M = date[_ + "Minutes"](), + s = date[_ + "Seconds"](), + L = date[_ + "Milliseconds"](), + o = utc ? 0 : date.getTimezoneOffset(), + flags = { + d: d, + dd: pad(d), + ddd: dF.i18n.dayNames[D], + dddd: dF.i18n.dayNames[D + 7], + m: m + 1, + mm: pad(m + 1), + mmm: dF.i18n.monthNames[m], + mmmm: dF.i18n.monthNames[m + 12], + yy: String(y).slice(2), + yyyy: y, + h: H % 12 || 12, + hh: pad(H % 12 || 12), + H: H, + HH: pad(H), + M: M, + MM: pad(M), + s: s, + ss: pad(s), + l: pad(L, 3), + L: pad(L > 99 ? Math.round(L / 10) : L), + t: H < 12 ? "a" : "p", + tt: H < 12 ? "am" : "pm", + T: H < 12 ? "A" : "P", + TT: H < 12 ? "AM" : "PM", + Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), + o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), + S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] + }; + + return mask.replace(token, function ($0) { + return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); + }); + }; + }(); + + // Some common format strings + dateFormat.masks = { + "default": "ddd mmm dd yyyy HH:MM:ss", + shortDate: "m/d/yy", + mediumDate: "mmm d, yyyy", + longDate: "mmmm d, yyyy", + fullDate: "dddd, mmmm d, yyyy", + shortTime: "h:MM TT", + mediumTime: "h:MM:ss TT", + longTime: "h:MM:ss TT Z", + isoDate: "yyyy-mm-dd", + isoTime: "HH:MM:ss", + isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", + isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" + }; + + // Internationalization strings + dateFormat.i18n = { + dayNames: [ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + ], + monthNames: [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" + ] + }; + + // For convenience... + Date.prototype.format = function (mask, utc) { + return dateFormat(this, mask, utc); + }; + +} + +/* ********************************************** + Begin VMM.Util.js +********************************************** */ + +/* * Utilities and Useful Functions +================================================== */ +if(typeof VMM != 'undefined' && typeof VMM.Util == 'undefined') { + + VMM.Util = ({ + + init: function() { + return this; + }, + + /* * CORRECT PROTOCOL (DOES NOT WORK) + ================================================== */ + correctProtocol: function(url) { + var loc = (window.parent.location.protocol).toString(), + prefix = "", + the_url = url.split("://", 2); + + if (loc.match("http")) { + prefix = loc; + } else { + prefix = "https"; + } + + return prefix + "://" + the_url[1]; + + }, + + /* * MERGE CONFIG + ================================================== */ + mergeConfig: function(config_main, config_to_merge) { + var x; + for (x in config_to_merge) { + if (Object.prototype.hasOwnProperty.call(config_to_merge, x)) { + config_main[x] = config_to_merge[x]; + } + } + return config_main; + }, + + /* * GET OBJECT ATTRIBUTE BY INDEX + ================================================== */ + getObjectAttributeByIndex: function(obj, index) { + if(typeof obj != 'undefined') { + var i = 0; + for (var attr in obj){ + if (index === i){ + return obj[attr]; + } + i++; + } + return ""; + } else { + return ""; + } + + }, + + /* * ORDINAL + ================================================== */ + ordinal: function(n) { + return ["th","st","nd","rd"][(!( ((n%10) >3) || (Math.floor(n%100/10)==1)) ) * (n%10)]; + }, + + /* * RANDOM BETWEEN + ================================================== */ + //VMM.Util.randomBetween(1, 3) + randomBetween: function(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); + }, + + /* * AVERAGE + * http://jsfromhell.com/array/average + * var x = VMM.Util.average([2, 3, 4]); + * VMM.Util.average([2, 3, 4]).mean + ================================================== */ + average: function(a) { + var r = {mean: 0, variance: 0, deviation: 0}, t = a.length; + for(var m, s = 0, l = t; l--; s += a[l]); + for(m = r.mean = s / t, l = t, s = 0; l--; s += Math.pow(a[l] - m, 2)); + return r.deviation = Math.sqrt(r.variance = s / t), r; + }, + + /* * CUSTOM SORT + ================================================== */ + customSort: function(a, b) { + var a1= a, b1= b; + if(a1== b1) return 0; + return a1> b1? 1: -1; + }, + + /* * Remove Duplicates from Array + ================================================== */ + deDupeArray: function(arr) { + var i, + len=arr.length, + out=[], + obj={}; + + for (i=0;i h) { + _fit.height = h; + //_fit.width = Math.round((w / ratio_w) * ratio_h); + _fit.width = Math.round((h / ratio_h) * ratio_w); + + if (_fit.width > w) { + trace("FIT: DIDN'T FIT!!! ") + } + } + + return _fit; + + }, + r16_9: function(w,h) { + //VMM.Util.ratio.r16_9(w, h) // Returns corresponding number + if (w !== null && w !== "") { + return Math.round((h / 16) * 9); + } else if (h !== null && h !== "") { + return Math.round((w / 9) * 16); + } + }, + r4_3: function(w,h) { + if (w !== null && w !== "") { + return Math.round((h / 4) * 3); + } else if (h !== null && h !== "") { + return Math.round((w / 3) * 4); + } + } + }, + + doubledigit: function(n) { + return (n < 10 ? '0' : '') + n; + }, + + /* * Returns a truncated segement of a long string of between min and max words. If possible, ends on a period (otherwise goes to max). + ================================================== */ + truncateWords: function(s, min, max) { + + if (!min) min = 30; + if (!max) max = min; + + var initial_whitespace_rExp = /^[^A-Za-z0-9\'\-]+/gi; + var left_trimmedStr = s.replace(initial_whitespace_rExp, ""); + var words = left_trimmedStr.split(" "); + + var result = []; + + min = Math.min(words.length, min); + max = Math.min(words.length, max); + + for (var i = 0; i$&") + .replace(pseudoUrlPattern, "$1$2") + .replace(emailAddressPattern, "$1"); + }, + + linkify_with_twitter: function(text,targets,is_touch) { + + // http://, https://, ftp:// + var urlPattern = /\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim; + var url_pattern = /(\()((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&'()*+,;=:\/?#[\]@%]+)(\))|(\[)((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&'()*+,;=:\/?#[\]@%]+)(\])|(\{)((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&'()*+,;=:\/?#[\]@%]+)(\})|(<|&(?:lt|#60|#x3c);)((?:ht|f)tps?:\/\/[a-z0-9\-._~!$&'()*+,;=:\/?#[\]@%]+)(>|&(?:gt|#62|#x3e);)|((?:^|[^=\s'"\]])\s*['"]?|[^=\s]\s+)(\b(?:ht|f)tps?:\/\/[a-z0-9\-._~!$'()*+,;=:\/?#[\]@%]+(?:(?!&(?:gt|#0*62|#x0*3e);|&(?:amp|apos|quot|#0*3[49]|#x0*2[27]);[.!&',:?;]?(?:[^a-z0-9\-._~!$&'()*+,;=:\/?#[\]@%]|$))&[a-z0-9\-._~!$'()*+,;=:\/?#[\]@%]*)*[a-z0-9\-_~$()*+=\/#[\]@%])/img; + var url_replace = '$1$4$7$10$13$2$5$8$11$14$3$6$9$12'; + + // www. sans http:// or https:// + var pseudoUrlPattern = /(^|[^\/])(www\.[\S]+(\b|$))/gim; + function replaceURLWithHTMLLinks(text) { + var exp = /(\b(https?|ftp|file):\/\/([-A-Z0-9+&@#%?=~_|!:,.;]*)([-A-Z0-9+&@#%?\/=~_|!:,.;]*)[-A-Z0-9+&@#\/%=~_|])/ig; + return text.replace(exp, "$3"); + } + // Email addresses + var emailAddressPattern = /(([a-zA-Z0-9_\-\.]+)@[a-zA-Z_]+?(?:\.[a-zA-Z]{2,6}))+/gim; + + //var twitterHandlePattern = /(@([\w]+))/g; + var twitterHandlePattern = /\B@([\w-]+)/gm; + var twitterSearchPattern = /(#([\w]+))/g; + + return text + //.replace(urlPattern, "$&") + .replace(url_pattern, url_replace) + .replace(pseudoUrlPattern, "$1$2") + .replace(emailAddressPattern, "$1") + .replace(twitterHandlePattern, "@$1"); + + // TURN THIS BACK ON TO AUTOMAGICALLY LINK HASHTAGS TO TWITTER SEARCH + //.replace(twitterSearchPattern, "$1"); + }, + + linkify_wikipedia: function(text) { + + var urlPattern = /]*>(.*?)<\/i>/gim; + return text + .replace(urlPattern, "$&") + .replace(/]*>/gim, "") + .replace(/<\/i>/gim, "") + .replace(/]*>/gim, "") + .replace(/<\/b>/gim, ""); + }, + + /* * Turns plain text links into real links + ================================================== */ + // VMM.Util.unlinkify(); + unlinkify: function(text) { + if(!text) return text; + text = text.replace(/]*>/i,""); + text = text.replace(/<\/a>/i, ""); + return text; + }, + + untagify: function(text) { + if (!text) { + return text; + } + text = text.replace(/<\s*\w.*?>/g,""); + return text; + }, + + /* * TK + ================================================== */ + nl2br: function(text) { + return text.replace(/(\r\n|[\r\n]|\\n|\\r)/g,"
    "); + }, + + /* * Generate a Unique ID + ================================================== */ + // VMM.Util.unique_ID(size); + unique_ID: function(size) { + + var getRandomNumber = function(range) { + return Math.floor(Math.random() * range); + }; + + var getRandomChar = function() { + var chars = "abcdefghijklmnopqurstuvwxyzABCDEFGHIJKLMNOPQURSTUVWXYZ"; + return chars.substr( getRandomNumber(62), 1 ); + }; + + var randomID = function(size) { + var str = ""; + for(var i = 0; i < size; i++) { + str += getRandomChar(); + } + return str; + }; + + return randomID(size); + }, + /* * Tells you if a number is even or not + ================================================== */ + // VMM.Util.isEven(n) + isEven: function(n){ + return (n%2 === 0) ? true : false; + }, + /* * Get URL Variables + ================================================== */ + // var somestring = VMM.Util.getUrlVars(str_url)["varname"]; + getUrlVars: function(string) { + + var str = string.toString(); + + if (str.match('&')) { + str = str.replace("&", "&"); + } else if (str.match('&')) { + str = str.replace("&", "&"); + } else if (str.match('&')) { + str = str.replace("&", "&"); + } + + var vars = [], hash; + var hashes = str.slice(str.indexOf('?') + 1).split('&'); + for(var i = 0; i < hashes.length; i++) { + hash = hashes[i].split('='); + vars.push(hash[0]); + vars[hash[0]] = hash[1]; + } + + + return vars; + }, + + /* * Cleans up strings to become real HTML + ================================================== */ + toHTML: function(text) { + + text = this.nl2br(text); + text = this.linkify(text); + + return text.replace(/\s\s/g,"  "); + }, + + /* * Returns text strings as CamelCase + ================================================== */ + toCamelCase: function(s,forceLowerCase) { + + if(forceLowerCase !== false) forceLowerCase = true; + + var sps = ((forceLowerCase) ? s.toLowerCase() : s).split(" "); + + for(var i=0; i 1 ? '.' + x[1] : ''; + var rgx = /(\d+)(\d{3})/; + while (rgx.test(x1)) { + x1 = x1.replace(rgx, '$1' + ',' + '$2'); + } + return x1 + x2; + }, + /* * Transform text to Title Case + ================================================== */ + toTitleCase: function(t){ + if ( VMM.Browser.browser == "Explorer" && parseInt(VMM.Browser.version, 10) >= 7) { + return t.replace("_", "%20"); + } else { + var __TitleCase = { + __smallWords: ['a', 'an', 'and', 'as', 'at', 'but','by', 'en', 'for', 'if', 'in', 'of', 'on', 'or','the', 'to', 'v[.]?', 'via', 'vs[.]?'], + + init: function() { + this.__smallRE = this.__smallWords.join('|'); + this.__lowerCaseWordsRE = new RegExp('\\b(' + this.__smallRE + ')\\b', 'gi'); + this.__firstWordRE = new RegExp('^([^a-zA-Z0-9 \\r\\n\\t]*)(' + this.__smallRE + ')\\b', 'gi'); + this.__lastWordRE = new RegExp('\\b(' + this.__smallRE + ')([^a-zA-Z0-9 \\r\\n\\t]*)$', 'gi'); + }, + + toTitleCase: function(string) { + var line = ''; + + var split = string.split(/([:.;?!][ ]|(?:[ ]|^)["“])/); + + for (var i = 0; i < split.length; ++i) { + var s = split[i]; + + s = s.replace(/\b([a-zA-Z][a-z.'’]*)\b/g,this.__titleCaseDottedWordReplacer); + + // lowercase the list of small words + s = s.replace(this.__lowerCaseWordsRE, this.__lowerReplacer); + + // if the first word in the title is a small word then capitalize it + s = s.replace(this.__firstWordRE, this.__firstToUpperCase); + + // if the last word in the title is a small word, then capitalize it + s = s.replace(this.__lastWordRE, this.__firstToUpperCase); + + line += s; + } + + // special cases + line = line.replace(/ V(s?)\. /g, ' v$1. '); + line = line.replace(/(['’])S\b/g, '$1s'); + line = line.replace(/\b(AT&T|Q&A)\b/ig, this.__upperReplacer); + + return line; + }, + + __titleCaseDottedWordReplacer: function (w) { + return (w.match(/[a-zA-Z][.][a-zA-Z]/)) ? w : __TitleCase.__firstToUpperCase(w); + }, + + __lowerReplacer: function (w) { return w.toLowerCase() }, + + __upperReplacer: function (w) { return w.toUpperCase() }, + + __firstToUpperCase: function (w) { + var split = w.split(/(^[^a-zA-Z0-9]*[a-zA-Z0-9])(.*)$/); + if (split[1]) { + split[1] = split[1].toUpperCase(); + } + + return split.join(''); + + + } + }; + + __TitleCase.init(); + + t = t.replace(/_/g," "); + t = __TitleCase.toTitleCase(t); + + return t; + + } + + } + + }).init(); +} + +/* ********************************************** + Begin LazyLoad.js +********************************************** */ + +/*jslint browser: true, eqeqeq: true, bitwise: true, newcap: true, immed: true, regexp: false */ + +/* +LazyLoad makes it easy and painless to lazily load one or more external +JavaScript or CSS files on demand either during or after the rendering of a web +page. + +Supported browsers include Firefox 2+, IE6+, Safari 3+ (including Mobile +Safari), Google Chrome, and Opera 9+. Other browsers may or may not work and +are not officially supported. + +Visit https://github.com/rgrove/lazyload/ for more info. + +Copyright (c) 2011 Ryan Grove +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the 'Software'), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +@module lazyload +@class LazyLoad +@static +@version 2.0.3 (git) +*/ + +LazyLoad = (function (doc) { + // -- Private Variables ------------------------------------------------------ + + // User agent and feature test information. + var env, + + // Reference to the element (populated lazily). + head, + + // Requests currently in progress, if any. + pending = {}, + + // Number of times we've polled to check whether a pending stylesheet has + // finished loading. If this gets too high, we're probably stalled. + pollCount = 0, + + // Queued requests. + queue = {css: [], js: []}, + + // Reference to the browser's list of stylesheets. + styleSheets = doc.styleSheets; + + // -- Private Methods -------------------------------------------------------- + + /** + Creates and returns an HTML element with the specified name and attributes. + + @method createNode + @param {String} name element name + @param {Object} attrs name/value mapping of element attributes + @return {HTMLElement} + @private + */ + function createNode(name, attrs) { + var node = doc.createElement(name), attr; + + for (attr in attrs) { + if (attrs.hasOwnProperty(attr)) { + node.setAttribute(attr, attrs[attr]); + } + } + + return node; + } + + /** + Called when the current pending resource of the specified type has finished + loading. Executes the associated callback (if any) and loads the next + resource in the queue. + + @method finish + @param {String} type resource type ('css' or 'js') + @private + */ + function finish(type) { + var p = pending[type], + callback, + urls; + + if (p) { + callback = p.callback; + urls = p.urls; + + urls.shift(); + pollCount = 0; + + // If this is the last of the pending URLs, execute the callback and + // start the next request in the queue (if any). + if (!urls.length) { + callback && callback.call(p.context, p.obj); + pending[type] = null; + queue[type].length && load(type); + } + } + } + + /** + Populates the env variable with user agent and feature test + information. + + @method getEnv + @private + */ + function getEnv() { + var ua = navigator.userAgent; + + env = { + // True if this browser supports disabling async mode on dynamically + // created script nodes. See + // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order + async: doc.createElement('script').async === true + }; + + (env.webkit = /AppleWebKit\//.test(ua)) + || (env.ie = /MSIE/.test(ua)) + || (env.opera = /Opera/.test(ua)) + || (env.gecko = /Gecko\//.test(ua)) + || (env.unknown = true); + } + + /** + Loads the specified resources, or the next resource of the specified type + in the queue if no resources are specified. If a resource of the specified + type is already being loaded, the new request will be queued until the + first request has been finished. + + When an array of resource URLs is specified, those URLs will be loaded in + parallel if it is possible to do so while preserving execution order. All + browsers support parallel loading of CSS, but only Firefox and Opera + support parallel loading of scripts. In other browsers, scripts will be + queued and loaded one at a time to ensure correct execution order. + + @method load + @param {String} type resource type ('css' or 'js') + @param {String|Array} urls (optional) URL or array of URLs to load + @param {Function} callback (optional) callback function to execute when the + resource is loaded + @param {Object} obj (optional) object to pass to the callback function + @param {Object} context (optional) if provided, the callback function will + be executed in this object's context + @private + */ + function load(type, urls, callback, obj, context) { + var _finish = function () { finish(type); }, + isCSS = type === 'css', + nodes = [], + i, len, node, p, pendingUrls, url; + + env || getEnv(); + + if (urls) { + // If urls is a string, wrap it in an array. Otherwise assume it's an + // array and create a copy of it so modifications won't be made to the + // original. + urls = typeof urls === 'string' ? [urls] : urls.concat(); + + // Create a request object for each URL. If multiple URLs are specified, + // the callback will only be executed after all URLs have been loaded. + // + // Sadly, Firefox and Opera are the only browsers capable of loading + // scripts in parallel while preserving execution order. In all other + // browsers, scripts must be loaded sequentially. + // + // All browsers respect CSS specificity based on the order of the link + // elements in the DOM, regardless of the order in which the stylesheets + // are actually downloaded. + if (isCSS || env.async || env.gecko || env.opera) { + // Load in parallel. + queue[type].push({ + urls : urls, + callback: callback, + obj : obj, + context : context + }); + } else { + // Load sequentially. + for (i = 0, len = urls.length; i < len; ++i) { + queue[type].push({ + urls : [urls[i]], + callback: i === len - 1 ? callback : null, // callback is only added to the last URL + obj : obj, + context : context + }); + } + } + } + + // If a previous load request of this type is currently in progress, we'll + // wait our turn. Otherwise, grab the next item in the queue. + if (pending[type] || !(p = pending[type] = queue[type].shift())) { + return; + } + + head || (head = doc.head || doc.getElementsByTagName('head')[0]); + pendingUrls = p.urls; + + for (i = 0, len = pendingUrls.length; i < len; ++i) { + url = pendingUrls[i]; + + if (isCSS) { + node = env.gecko ? createNode('style') : createNode('link', { + href: url, + rel : 'stylesheet' + }); + } else { + node = createNode('script', {src: url}); + node.async = false; + } + + node.className = 'lazyload'; + node.setAttribute('charset', 'utf-8'); + + if (env.ie && !isCSS) { + node.onreadystatechange = function () { + if (/loaded|complete/.test(node.readyState)) { + node.onreadystatechange = null; + _finish(); + } + }; + } else if (isCSS && (env.gecko || env.webkit)) { + // Gecko and WebKit don't support the onload event on link nodes. + if (env.webkit) { + // In WebKit, we can poll for changes to document.styleSheets to + // figure out when stylesheets have loaded. + p.urls[i] = node.href; // resolve relative URLs (or polling won't work) + pollWebKit(); + } else { + // In Gecko, we can import the requested URL into a