├── .gitattributes
├── test
├── fixtures
│ ├── fail-script.js
│ ├── executefalse.js
│ ├── executefalse2.js
│ ├── first.js
│ ├── once.js
│ ├── once2.js
│ ├── stamp-script.js
│ ├── second.js
│ ├── modernizr.min.js
│ ├── underscore-min.js
│ └── backbone-min.js
├── index.html
├── vendor
│ └── qunit.css
└── tests.js
├── _config.yml
├── .bowerrc
├── .gitignore
├── asset
├── logo.png
└── base.css
├── media
└── logo_src.png
├── .travis.yml
├── .editorconfig
├── bower.json
├── .jshintrc
├── _includes
├── header.html
└── api.md
├── license
├── package.json
├── _layouts
└── default.html
├── Gruntfile.js
├── dist
├── basket.min.js
├── basket.min.map
├── basket.min.js.map
├── basket.js
├── basket.full.min.js
├── basket.full.map
└── basket.full.min.js.map
├── readme.md
├── index.md
└── lib
└── basket.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/test/fixtures/fail-script.js:
--------------------------------------------------------------------------------
1 | basket.fail = true;
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | safe: true
2 | markdown: redcarpet
3 |
--------------------------------------------------------------------------------
/test/fixtures/executefalse.js:
--------------------------------------------------------------------------------
1 | basket.executed = true
--------------------------------------------------------------------------------
/test/fixtures/executefalse2.js:
--------------------------------------------------------------------------------
1 | basket.executed2 = true
--------------------------------------------------------------------------------
/test/fixtures/first.js:
--------------------------------------------------------------------------------
1 | basket.order = 'first';
2 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bower_components
3 | _site
4 |
--------------------------------------------------------------------------------
/test/fixtures/once.js:
--------------------------------------------------------------------------------
1 | basket.once = ++basket.once || 1;
--------------------------------------------------------------------------------
/test/fixtures/once2.js:
--------------------------------------------------------------------------------
1 | basket.once2 = ++basket.once2 || 1;
--------------------------------------------------------------------------------
/test/fixtures/stamp-script.js:
--------------------------------------------------------------------------------
1 | basket.lastXHR = +new Date();
--------------------------------------------------------------------------------
/test/fixtures/second.js:
--------------------------------------------------------------------------------
1 | basket.order = (basket.order || '') + 'second';
2 |
--------------------------------------------------------------------------------
/asset/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/no-problemo/basket.js/gh-pages/asset/logo.png
--------------------------------------------------------------------------------
/media/logo_src.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/no-problemo/basket.js/gh-pages/media/logo_src.png
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - 'iojs'
5 | - '0.12'
6 | - '0.10'
7 | branches:
8 | only:
9 | - gh-pages
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = tab
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "basket.js",
3 | "main": "dist/basket.js",
4 | "ignore": [
5 | "_*",
6 | ".*",
7 | "asset",
8 | "lib",
9 | "media",
10 | "test",
11 | "Gruntfile.js",
12 | "index.md",
13 | "package.json"
14 | ],
15 | "dependencies": {
16 | "rsvp": "~3.0.16"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "bitwise": true,
3 | "browser": true,
4 | "camelcase": true,
5 | "curly": true,
6 | "eqeqeq": true,
7 | "esnext": true,
8 | "expr": true,
9 | "immed": true,
10 | "indent": 4,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "node": true,
15 | "noempty": true,
16 | "nonstandard": true,
17 | "quotmark": "single",
18 | "regexp": true,
19 | "smarttabs": true,
20 | "strict": true,
21 | "trailing": true,
22 | "undef": true,
23 | "unused": true
24 | }
25 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | QUnit Tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/_includes/header.html:
--------------------------------------------------------------------------------
1 | 
2 | A simple (proof-of-concept) script loader that caches scripts with localStorage
3 |
4 |
5 | Version 0.5.0:
6 | basket.js (2.2 kB)
7 | basket.min.js* (0.7 kB gzipped)
8 | basket.full.min.js (3.8 kB gzipped)
9 | CDN-Usage
10 | * = Use this version if you want to manually handle the rsvp.js dependency
11 |
12 | If you're seeing this message, something has gone wrong. Please ensure you are testing this page using a HTTP server.
13 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Basket.js team
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "basket.js",
3 | "version": "0.5.2",
4 | "description": "A script-loader that handles caching scripts in localStorage where supported",
5 | "keywords": [
6 | "script",
7 | "loader",
8 | "localstorage",
9 | "caching",
10 | "asset",
11 | "loader",
12 | "preload",
13 | "javascript",
14 | "js"
15 | ],
16 | "homepage": "http://addyosmani.github.com/basket.js",
17 | "license": "MIT",
18 | "author": {
19 | "name": "Addy Osmani",
20 | "email": "addyosmani@gmail.com",
21 | "url": "addyosmani.com"
22 | },
23 | "maintainers": [
24 | {
25 | "name": "Addy Osmani"
26 | },
27 | {
28 | "name": "Sindre Sorhus",
29 | "email": "sindresorhus@gmail.com",
30 | "url": "sindresorhus.com"
31 | },
32 | {
33 | "name": "Andrée Hansson",
34 | "email": "peolanha@gmail.com",
35 | "url": "andreehansson.se"
36 | },
37 | {
38 | "name": "Mat Scales",
39 | "email": "mat@wibbly.org.uk",
40 | "url": "github.com/wibblymat"
41 | }
42 | ],
43 | "contributors": [
44 | {
45 | "name": "Ironsjp"
46 | },
47 | {
48 | "name": "Mathias Bynens"
49 | },
50 | {
51 | "name": "Rick Waldron"
52 | },
53 | {
54 | "name": "Felipe Morais"
55 | }
56 | ],
57 | "main": "lib/basket",
58 | "repository": "addyosmani/basket.js",
59 | "scripts": {
60 | "dev": "grunt",
61 | "test": "bower install && grunt test"
62 | },
63 | "devDependencies": {
64 | "bower": "1.x",
65 | "grunt": "^0.4.0",
66 | "grunt-cli": "^0.1.13",
67 | "grunt-contrib-concat": "^0.5.0",
68 | "grunt-contrib-connect": "^0.9.0",
69 | "grunt-contrib-jshint": "^0.11.0",
70 | "grunt-contrib-qunit": "^0.5.1",
71 | "grunt-contrib-uglify": "^0.7.0",
72 | "grunt-contrib-watch": "^0.6.1"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/_layouts/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | basket.js - a simple script loader that caches scripts with localStorage
6 |
7 |
8 |
9 |
10 | {% include header.html %}
11 |
12 | {{ content | markdownify }}
13 |
14 |
15 |
18 |
19 |
20 |
21 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function( grunt ) {
2 | 'use strict';
3 |
4 | grunt.initConfig({
5 | pkg: grunt.file.readJSON('package.json'),
6 | concat: {
7 | options: {
8 | banner: '/*!\n' +
9 | '* <%= pkg.name %>\n' +
10 | '* v<%= pkg.version %> - ' +
11 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
12 | '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
13 | '* (c) <%= pkg.author.name %>;' +
14 | ' <%= _.pluck(pkg.licenses, "type").join(", ") %> License\n' +
15 | '* Created by: <%= _.pluck(pkg.maintainers, "name").join(", ") %>\n' +
16 | '* Contributors: <%= _.pluck(pkg.contributors, "name").join(", ") %>\n' +
17 | '* Uses rsvp.js, https://github.com/tildeio/rsvp.js\n' +
18 | '*/',
19 | stripBanners: true
20 | },
21 | dist: {
22 | src: ['lib/basket.js'],
23 | dest: 'dist/basket.js'
24 | }
25 | },
26 | uglify: {
27 | options: {
28 | report: 'gzip',
29 | banner: '<%= concat.options.banner %>'
30 | },
31 | dist: {
32 | options: {
33 | sourceMap: 'dist/basket.min.map'
34 | },
35 | files: {
36 | 'dist/basket.min.js': ['dist/basket.js']
37 | }
38 | },
39 | full: {
40 | options: {
41 | sourceMap: 'dist/basket.full.map'
42 | },
43 | files: {
44 | 'dist/basket.full.min.js': ['bower_components/rsvp/rsvp.min.js', 'dist/basket.js']
45 | }
46 | }
47 | },
48 | qunit: {
49 | all: {
50 | options: {
51 | urls: ['http://localhost:8080/test/index.html']
52 | }
53 | }
54 | },
55 | watch: {
56 | scripts: {
57 | files: '<%= jshint.all %>',
58 | tasks: ['test']
59 | }
60 | },
61 | connect: {
62 | server: {
63 | options: {
64 | base: '.',
65 | port: 8080
66 | }
67 | }
68 | },
69 | jshint: {
70 | options: {
71 | jshintrc: '.jshintrc'
72 | },
73 | all: ['Gruntfile.js', 'lib/basket.js', 'test/tests.js']
74 | }
75 | });
76 |
77 | grunt.loadNpmTasks('grunt-contrib-concat');
78 | grunt.loadNpmTasks('grunt-contrib-jshint');
79 | grunt.loadNpmTasks('grunt-contrib-uglify');
80 | grunt.loadNpmTasks('grunt-contrib-qunit');
81 | grunt.loadNpmTasks('grunt-contrib-connect');
82 | grunt.loadNpmTasks('grunt-contrib-watch');
83 |
84 | // Dev - default
85 | grunt.registerTask('default', ['test']);
86 |
87 | // Release
88 | grunt.registerTask('release', ['test', 'concat', 'uglify']);
89 |
90 | //Tests
91 | grunt.registerTask('test', ['jshint', 'connect', 'qunit']);
92 | };
93 |
--------------------------------------------------------------------------------
/dist/basket.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * basket.js
3 | * v0.5.2 - 2015-02-07
4 | * http://addyosmani.github.com/basket.js
5 | * (c) Addy Osmani; License
6 | * Created by: Addy Osmani, Sindre Sorhus, Andrée Hansson, Mat Scales
7 | * Contributors: Ironsjp, Mathias Bynens, Rick Waldron, Felipe Morais
8 | * Uses rsvp.js, https://github.com/tildeio/rsvp.js
9 | */
10 | !function(a,b){"use strict";var c=b.head||b.getElementsByTagName("head")[0],d="basket-",e=5e3,f=[],g=function(a,b){try{return localStorage.setItem(d+a,JSON.stringify(b)),!0}catch(c){if(c.name.toUpperCase().indexOf("QUOTA")>=0){var e,f=[];for(e in localStorage)0===e.indexOf(d)&&f.push(JSON.parse(localStorage[e]));return f.length?(f.sort(function(a,b){return a.stamp-b.stamp}),basket.remove(f[0].key),g(a,b)):void 0}return}},h=function(a){var b=new RSVP.Promise(function(b,c){var d=new XMLHttpRequest;d.open("GET",a),d.onreadystatechange=function(){4===d.readyState&&(200===d.status||0===d.status&&d.responseText?b({content:d.responseText,type:d.getResponseHeader("content-type")}):c(new Error(d.statusText)))},setTimeout(function(){d.readyState<4&&d.abort()},basket.timeout),d.send()});return b},i=function(a){return h(a.url).then(function(b){var c=j(a,b);return a.skipCache||g(a.key,c),c})},j=function(a,b){var c=+new Date;return a.data=b.content,a.originalType=b.type,a.type=a.type||b.type,a.skipCache=a.skipCache||!1,a.stamp=c,a.expire=c+60*(a.expire||e)*60*1e3,a},k=function(a,b){return!a||a.expire-+new Date<0||b.unique!==a.unique||basket.isValidItem&&!basket.isValidItem(a,b)},l=function(a){var b,c,d;if(a.url)return a.key=a.key||a.url,b=basket.get(a.key),a.execute=a.execute!==!1,d=k(b,a),a.live||d?(a.unique&&(a.url+=(a.url.indexOf("?")>0?"&":"?")+"basket-unique="+a.unique),c=i(a),a.live&&!d&&(c=c.then(function(a){return a},function(){return b}))):(b.type=a.type||b.originalType,b.execute=a.execute,c=new RSVP.Promise(function(a){a(b)})),c},m=function(a){var d=b.createElement("script");d.defer=!0,d.text=a.data,c.appendChild(d)},n={"default":m},o=function(a){return a.type&&n[a.type]?n[a.type](a):n["default"](a)},p=function(a){return a.map(function(a){return a.execute&&o(a),a})},q=function(){var a,b,c=[];for(a=0,b=arguments.length;b>a;a++)c.push(l(arguments[a]));return RSVP.all(c)},r=function(){var a=q.apply(null,arguments),b=this.then(function(){return a}).then(p);return b.thenRequire=r,b};a.basket={require:function(){for(var a=0,b=arguments.length;b>a;a++)arguments[a].execute=arguments[a].execute!==!1,arguments[a].once&&f.indexOf(arguments[a].url)>=0?arguments[a].execute=!1:arguments[a].execute!==!1&&f.indexOf(arguments[a].url)<0&&f.push(arguments[a].url);var c=q.apply(null,arguments).then(p);return c.thenRequire=r,c},remove:function(a){return localStorage.removeItem(d+a),this},get:function(a){var b=localStorage.getItem(d+a);try{return JSON.parse(b||"false")}catch(c){return!1}},clear:function(a){var b,c,e=+new Date;for(b in localStorage)c=b.split(d)[1],c&&(!a||this.get(c).expire<=e)&&this.remove(c);return this},isValidItem:null,timeout:5e3,addHandler:function(a,b){Array.isArray(a)||(a=[a]),a.forEach(function(a){n[a]=b})},removeHandler:function(a){basket.addHandler(a,void 0)}},basket.clear(!0)}(this,document);
11 | //# sourceMappingURL=basket.min.js.map
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | [](http://addyosmani.github.com/basket.js)
2 |
3 | > [Basket.js](http://addyosmani.github.com/basket.js) is a script and resource loader for caching and loading scripts using localStorage
4 |
5 | [](https://travis-ci.org/addyosmani/basket.js)
6 |
7 |
8 | ## Resources
9 |
10 | ### Examples
11 |
12 | * [Load RequireJS modules with Basket.js](https://github.com/andrewwakeling/requirejs-basketjs/blob/master/basket-loader.js)
13 | * [Loading CSS with Basket.js](https://github.com/andrewwakeling/basket-css-example)
14 |
15 | ### Articles
16 |
17 | * [Basket.js: A JavaScript Loader With LocalStorage-based script caching](http://badassjs.com/post/40850339601/basket-js-a-javascript-loader-with-localstorage-based)
18 | * [basket.js caches scripts with HTML5 localStorage](http://ahmadassaf.com/blog/web-development/scripts-plugins/basket-js-caches-scripts-with-html5-localstorage/)
19 | * [Basket.js for improved script caching](http://t3n.de/news/basketjs-performance-localstorage-515119/)
20 | * [How to Improve Loading Time with basket.js](http://www.sitepoint.com/how-to-improve-loading-time-with-basket-js)
21 |
22 |
23 | ## Contribute
24 |
25 | ### Style Guide
26 |
27 | This project follows the [Idiomatic](https://github.com/rwldrn/idiomatic.js) guide to writing JavaScript - a concise extension to the jQuery Core Style [guidelines](http://docs.jquery.com/JQuery_Core_Style_Guidelines), with the exception of multiple var statements. Please ensure any pull requests follow these closely.
28 |
29 |
30 | ### Unit Tests
31 |
32 | We are also attempting to get as much unit test coverage as possible. For this reason, please add unit tests for any new or changed functionality and remember to lint and test your code using [grunt](http://gruntjs.com).
33 |
34 | *Also, please don't edit files in the "dist" subdirectory as they are generated via grunt. You'll find source code in the "lib" subdirectory!*
35 |
36 | ### Building
37 |
38 | To build the project, you will first need to install the necessary dependencies (such as [RSVP](https://github.com/tildeio/rsvp.js)) using [npm](http://npmjs.org) and [Bower](http://bower.io).
39 |
40 | Run:
41 |
42 | ```sh
43 | $ npm install & bower install
44 | ```
45 |
46 | in the project root to get everything you need. Next, to actually build the project you will need [Grunt](http://gruntjs.com).
47 |
48 | Run:
49 |
50 | ```sh
51 | $ grunt release
52 | ```
53 |
54 | to generate a new release, otherwise just running `grunt test` will run the unit tests.
55 |
56 |
57 | ## Team
58 |
59 | |  |  |  |  |
60 | |---|---|---|---|
61 | | [Addy Osmani](https://github.com/addyosmani) | [Sindre Sorhus](https://github.com/sindresorhus) | [Andrée Hansson](https://github.com/peol) | [Mat Scales](https://github.com/wibblymat) |
62 |
63 |
64 | ## License
65 |
66 | MIT © Basket.js team
67 |
--------------------------------------------------------------------------------
/dist/basket.min.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"dist/basket.min.js","sources":["dist/basket.js"],"names":["window","document","head","getElementsByTagName","storagePrefix","defaultExpiration","addLocalStorage","key","storeObj","localStorage","setItem","JSON","stringify","e","name","toUpperCase","indexOf","item","tempScripts","push","parse","length","sort","a","b","stamp","basket","remove","getUrl","url","promise","RSVP","Promise","resolve","reject","xhr","XMLHttpRequest","open","onreadystatechange","readyState","status","content","responseText","type","getResponseHeader","Error","statusText","send","saveUrl","obj","then","result","wrapStoreData","data","now","Date","originalType","expire","isCacheValid","source","unique","isValidItem","handleStackObject","shouldFetch","get","execute","live","injectScript","script","createElement","defer","text","appendChild","handlers","default","performActions","resources","map","fetch","i","l","promises","arguments","all","thenRequire","apply","this","require","removeItem","getItem","clear","expired","split","timeout","addHandler","types","handler","Array","isArray","forEach","removeHandler","undefined"],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/basket.min.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"basket.min.js","sources":["basket.js"],"names":["window","document","head","getElementsByTagName","storagePrefix","defaultExpiration","inBasket","addLocalStorage","key","storeObj","localStorage","setItem","JSON","stringify","e","name","toUpperCase","indexOf","item","tempScripts","push","parse","length","sort","a","b","stamp","basket","remove","getUrl","url","promise","RSVP","Promise","resolve","reject","xhr","XMLHttpRequest","open","onreadystatechange","readyState","status","responseText","content","type","getResponseHeader","Error","statusText","setTimeout","abort","timeout","send","saveUrl","obj","then","result","wrapStoreData","skipCache","data","now","Date","originalType","expire","isCacheValid","source","unique","isValidItem","handleStackObject","shouldFetch","get","execute","live","injectScript","script","createElement","defer","text","appendChild","handlers","default","performActions","resources","map","fetch","i","l","promises","arguments","all","thenRequire","apply","this","require","once","removeItem","getItem","clear","expired","split","addHandler","types","handler","Array","isArray","forEach","removeHandler","undefined"],"mappings":"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"}
--------------------------------------------------------------------------------
/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
5 | ### Introduction
6 |
7 | basket.js is a small JavaScript library supporting localStorage caching of scripts. If script(s) have previously been saved locally, they will simply be loaded and injected into the current document. If not, they will be XHR'd in for use right away, but cached so that no additional loads are required in the future.
8 |
9 | ### Why localStorage?
10 |
11 | [Tests](http://www.stevesouders.com/blog/2011/03/28/storager-case-study-bing-google/) by Google and Bing have shown that there are performance benefits to caching assets in localStorage (especially on mobile) when compared to simply reading and writing from the standard browser cache. This project is currently working on replicating these tests in the public so that we have definitive statistics on whether this is true.
12 |
13 | Developers have also been wondering why we opted for localStorage as opposed to alternatives such as IndexedDB. Jens Arps has shown that IDB is at present [significantly slower](http://jsperf.com/indexeddb-vs-localstorage/2) than localStorage for reading and writing assets. Other developers exploring this space have also shown that localStorage works just fine for caching data (it's actually significantly [faster](http://www.webdirections.org/blog/localstorage-perhaps-not-so-harmful/) in Safari at the moment than any other browser).
14 |
15 | We believe that in time, once implementers have optimized localStorage, it will become more feasible to use cross-browser. In the mean time, we are also exploring solutions such as the [FileSystem API](http://www.html5rocks.com/en/tutorials/file/filesystem/) as a storage mechanism for caching scripts locally.
16 |
17 | ### Project History
18 |
19 | This project was created as a response to a [tweet](https://twitter.com/#!/souders/statuses/166928191649357824) by Steve Souders asking that the jQuery project consider caching jQuery in localStorage for performance reasons. Intrigued by the idea, we began exploring a minimalistic solution to it (just for the sake of experimentation).
20 |
21 | You may remember Souders previously did research and a [write-up](http://www.stevesouders.com/blog/2011/03/28/storager-case-study-bing-google/) on search engines making use of localStorage caching back in 2011. His conclusions were:
22 |
23 | **Bing and Google Search make extensive use of localStorage for stashing SCRIPT blocks that are used on subsequent page views. None of the other top sites from my previous post use localStorage in this way. Are Bing and Google Search onto something? Yes, definitely.**
24 |
25 | To help provide a more general solution for this problem, we put together a script loader that treats localStorage as a cache.
26 |
27 | ### Compatibility
28 |
29 | basket.js supports locally caching scripts in any browser with [localStorage capabilities](http://caniuse.com/#search=localstorage).
30 |
31 | ### About localStorage
32 |
33 | [localStorage](http://diveintohtml5.info/storage.html) is a simple API within modern browsers to allow web developers to store small amounts of data within the user's browser.
34 |
35 | The HTML5 spec suggests storage quota with a limit of 5MB for localStorage but browsers can implement their own quota if they wish. If the quota is exceeded the browser may fail to store items in the cache. If this happens, basket.js will remove entries from the cache beginning with the oldest and try again. Some browsers like Opera will ask the user about increasing the quota when it exceeds a set threshold.
36 |
37 | To free space basket.js will only remove cached scripts that it placed in localStorage itself. The data stored by other code in same origin will not be touched.
38 |
39 | {% include api.md %}
40 |
41 | ### The Future
42 |
43 | We are currently investigating a number of different features we would like to bring to the project, as well as looking to produce some high-quality [performance benchmarks](https://github.com/addyosmani/basket.js/issues/24) (compared to IndexedDB, Browser cache and more). To find out more, check out [what we're working on](https://github.com/addyosmani/basket.js/issues).
44 |
45 | ### Team, License & Contribution Guide
46 |
47 | basket.js is released under an [MIT License](http://en.wikipedia.org/wiki/MIT_License) and is currently maintained by [Addy Osmani](https://github.com/addyosmani), [Sindre Sorhus](https://github.com/sindresorhus), [Mat Scales](https://github.com/wibblymat) and [Andrée Hansson](https://github.com/peol). We would also like to extend our thanks to [Rick Waldron](https://github.com/rwldrn) for the optimizations he suggested for improving the project.
48 |
49 | For more information on our style-guide and how to get involved with basket.js, please see the README in our project [repo](http://github.com/addyosmani/basket.js).
50 |
--------------------------------------------------------------------------------
/test/vendor/qunit.css:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit v1.8.0 - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer
7 | * Dual licensed under the MIT (MIT-LICENSE.txt)
8 | * or GPL (GPL-LICENSE.txt) licenses.
9 | */
10 |
11 | /** Font Family and Sizes */
12 |
13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
15 | }
16 |
17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
18 | #qunit-tests { font-size: smaller; }
19 |
20 |
21 | /** Resets */
22 |
23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
24 | margin: 0;
25 | padding: 0;
26 | }
27 |
28 |
29 | /** Header */
30 |
31 | #qunit-header {
32 | padding: 0.5em 0 0.5em 1em;
33 |
34 | color: #8699a4;
35 | background-color: #0d3349;
36 |
37 | font-size: 1.5em;
38 | line-height: 1em;
39 | font-weight: normal;
40 |
41 | border-radius: 15px 15px 0 0;
42 | -moz-border-radius: 15px 15px 0 0;
43 | -webkit-border-top-right-radius: 15px;
44 | -webkit-border-top-left-radius: 15px;
45 | }
46 |
47 | #qunit-header a {
48 | text-decoration: none;
49 | color: #c2ccd1;
50 | }
51 |
52 | #qunit-header a:hover,
53 | #qunit-header a:focus {
54 | color: #fff;
55 | }
56 |
57 | #qunit-header label {
58 | display: inline-block;
59 | padding-left: 0.5em;
60 | }
61 |
62 | #qunit-banner {
63 | height: 5px;
64 | }
65 |
66 | #qunit-testrunner-toolbar {
67 | padding: 0.5em 0 0.5em 2em;
68 | color: #5E740B;
69 | background-color: #eee;
70 | }
71 |
72 | #qunit-userAgent {
73 | padding: 0.5em 0 0.5em 2.5em;
74 | background-color: #2b81af;
75 | color: #fff;
76 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
77 | }
78 |
79 |
80 | /** Tests: Pass/Fail */
81 |
82 | #qunit-tests {
83 | list-style-position: inside;
84 | }
85 |
86 | #qunit-tests li {
87 | padding: 0.4em 0.5em 0.4em 2.5em;
88 | border-bottom: 1px solid #fff;
89 | list-style-position: inside;
90 | }
91 |
92 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
93 | display: none;
94 | }
95 |
96 | #qunit-tests li strong {
97 | cursor: pointer;
98 | }
99 |
100 | #qunit-tests li a {
101 | padding: 0.5em;
102 | color: #c2ccd1;
103 | text-decoration: none;
104 | }
105 | #qunit-tests li a:hover,
106 | #qunit-tests li a:focus {
107 | color: #000;
108 | }
109 |
110 | #qunit-tests ol {
111 | margin-top: 0.5em;
112 | padding: 0.5em;
113 |
114 | background-color: #fff;
115 |
116 | border-radius: 15px;
117 | -moz-border-radius: 15px;
118 | -webkit-border-radius: 15px;
119 |
120 | box-shadow: inset 0px 2px 13px #999;
121 | -moz-box-shadow: inset 0px 2px 13px #999;
122 | -webkit-box-shadow: inset 0px 2px 13px #999;
123 | }
124 |
125 | #qunit-tests table {
126 | border-collapse: collapse;
127 | margin-top: .2em;
128 | }
129 |
130 | #qunit-tests th {
131 | text-align: right;
132 | vertical-align: top;
133 | padding: 0 .5em 0 0;
134 | }
135 |
136 | #qunit-tests td {
137 | vertical-align: top;
138 | }
139 |
140 | #qunit-tests pre {
141 | margin: 0;
142 | white-space: pre-wrap;
143 | word-wrap: break-word;
144 | }
145 |
146 | #qunit-tests del {
147 | background-color: #e0f2be;
148 | color: #374e0c;
149 | text-decoration: none;
150 | }
151 |
152 | #qunit-tests ins {
153 | background-color: #ffcaca;
154 | color: #500;
155 | text-decoration: none;
156 | }
157 |
158 | /*** Test Counts */
159 |
160 | #qunit-tests b.counts { color: black; }
161 | #qunit-tests b.passed { color: #5E740B; }
162 | #qunit-tests b.failed { color: #710909; }
163 |
164 | #qunit-tests li li {
165 | margin: 0.5em;
166 | padding: 0.4em 0.5em 0.4em 0.5em;
167 | background-color: #fff;
168 | border-bottom: none;
169 | list-style-position: inside;
170 | }
171 |
172 | /*** Passing Styles */
173 |
174 | #qunit-tests li li.pass {
175 | color: #5E740B;
176 | background-color: #fff;
177 | border-left: 26px solid #C6E746;
178 | }
179 |
180 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
181 | #qunit-tests .pass .test-name { color: #366097; }
182 |
183 | #qunit-tests .pass .test-actual,
184 | #qunit-tests .pass .test-expected { color: #999999; }
185 |
186 | #qunit-banner.qunit-pass { background-color: #C6E746; }
187 |
188 | /*** Failing Styles */
189 |
190 | #qunit-tests li li.fail {
191 | color: #710909;
192 | background-color: #fff;
193 | border-left: 26px solid #EE5757;
194 | white-space: pre;
195 | }
196 |
197 | #qunit-tests > li:last-child {
198 | border-radius: 0 0 15px 15px;
199 | -moz-border-radius: 0 0 15px 15px;
200 | -webkit-border-bottom-right-radius: 15px;
201 | -webkit-border-bottom-left-radius: 15px;
202 | }
203 |
204 | #qunit-tests .fail { color: #000000; background-color: #EE5757; }
205 | #qunit-tests .fail .test-name,
206 | #qunit-tests .fail .module-name { color: #000000; }
207 |
208 | #qunit-tests .fail .test-actual { color: #EE5757; }
209 | #qunit-tests .fail .test-expected { color: green; }
210 |
211 | #qunit-banner.qunit-fail { background-color: #EE5757; }
212 |
213 |
214 | /** Result */
215 |
216 | #qunit-testresult {
217 | padding: 0.5em 0.5em 0.5em 2.5em;
218 |
219 | color: #2b81af;
220 | background-color: #D2E0E6;
221 |
222 | border-bottom: 1px solid white;
223 | }
224 | #qunit-testresult .module-name {
225 | font-weight: bold;
226 | }
227 |
228 | /** Fixture */
229 |
230 | #qunit-fixture {
231 | position: absolute;
232 | top: -10000px;
233 | left: -10000px;
234 | width: 1000px;
235 | height: 1000px;
236 | }
237 |
--------------------------------------------------------------------------------
/test/fixtures/modernizr.min.js:
--------------------------------------------------------------------------------
1 | /* Modernizr 2.5.3 (Custom Build) | MIT & BSD
2 | * Build: http://modernizr.com/download/#-shiv-cssclasses-load
3 | */
4 | ;window.Modernizr=function(a,b,c){function u(a){j.cssText=a}function v(a,b){return u(prefixes.join(a+";")+(b||""))}function w(a,b){return typeof a===b}function x(a,b){return!!~(""+a).indexOf(b)}function y(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:w(f,"function")?f.bind(d||b):f}return!1}var d="2.5.3",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m={},n={},o={},p=[],q=p.slice,r,s={}.hasOwnProperty,t;!w(s,"undefined")&&!w(s.call,"undefined")?t=function(a,b){return s.call(a,b)}:t=function(a,b){return b in a&&w(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=q.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(q.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(q.call(arguments)))};return e});for(var z in m)t(m,z)&&(r=z.toLowerCase(),e[r]=m[z](),p.push((e[r]?"":"no-")+r));return u(""),i=k=null,function(a,b){function g(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function h(){var a=k.elements;return typeof a=="string"?a.split(" "):a}function i(a){var b={},c=a.createElement,e=a.createDocumentFragment,f=e();a.createElement=function(a){var e=(b[a]||(b[a]=c(a))).cloneNode();return k.shivMethods&&e.canHaveChildren&&!d.test(a)?f.appendChild(e):e},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+h().join().replace(/\w+/g,function(a){return b[a]=c(a),f.createElement(a),'c("'+a+'")'})+");return n}")(k,f)}function j(a){var b;return a.documentShived?a:(k.shivCSS&&!e&&(b=!!g(a,"article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio{display:none}canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}mark{background:#FF0;color:#000}")),f||(b=!i(a)),b&&(a.documentShived=b),a)}var c=a.html5||{},d=/^<|^(?:button|form|map|select|textarea)$/i,e,f;(function(){var a=b.createElement("a");a.innerHTML="",e="hidden"in a,f=a.childNodes.length==1||function(){try{b.createElement("a")}catch(a){return!0}var c=b.createDocumentFragment();return typeof c.cloneNode=="undefined"||typeof c.createDocumentFragment=="undefined"||typeof c.createElement=="undefined"}()})();var k={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:j};a.html5=k,j(b)}(this,b),e._version=d,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+p.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return o.call(a)=="[object Function]"}function e(a){return typeof a=="string"}function f(){}function g(a){return!a||a=="loaded"||a=="complete"||a=="uninitialized"}function h(){var a=p.shift();q=1,a?a.t?m(function(){(a.t=="c"?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){a!="img"&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l={},o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};y[c]===1&&(r=1,y[c]=[],l=b.createElement(a)),a=="object"?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),a!="img"&&(r||y[c]===2?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i(b=="c"?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),p.length==1&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&o.call(a.opera)=="[object Opera]",l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return o.call(a)=="[object Array]"},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f= 0 ) {
16 | var item;
17 | var tempScripts = [];
18 |
19 | for ( item in localStorage ) {
20 | if ( item.indexOf( storagePrefix ) === 0 ) {
21 | tempScripts.push( JSON.parse( localStorage[ item ] ) );
22 | }
23 | }
24 |
25 | if ( tempScripts.length ) {
26 | tempScripts.sort(function( a, b ) {
27 | return a.stamp - b.stamp;
28 | });
29 |
30 | basket.remove( tempScripts[ 0 ].key );
31 |
32 | return addLocalStorage( key, storeObj );
33 |
34 | } else {
35 | // no files to remove. Larger than available quota
36 | return;
37 | }
38 |
39 | } else {
40 | // some other error
41 | return;
42 | }
43 | }
44 |
45 | };
46 |
47 | var getUrl = function( url ) {
48 | var promise = new RSVP.Promise( function( resolve, reject ){
49 |
50 | var xhr = new XMLHttpRequest();
51 | xhr.open( 'GET', url );
52 |
53 | xhr.onreadystatechange = function() {
54 | if ( xhr.readyState === 4 ) {
55 | if ( ( xhr.status === 200 ) ||
56 | ( ( xhr.status === 0 ) && xhr.responseText ) ) {
57 | resolve( {
58 | content: xhr.responseText,
59 | type: xhr.getResponseHeader('content-type')
60 | } );
61 | } else {
62 | reject( new Error( xhr.statusText ) );
63 | }
64 | }
65 | };
66 |
67 | // By default XHRs never timeout, and even Chrome doesn't implement the
68 | // spec for xhr.timeout. So we do it ourselves.
69 | setTimeout( function () {
70 | if( xhr.readyState < 4 ) {
71 | xhr.abort();
72 | }
73 | }, basket.timeout );
74 |
75 | xhr.send();
76 | });
77 |
78 | return promise;
79 | };
80 |
81 | var saveUrl = function( obj ) {
82 | return getUrl( obj.url ).then( function( result ) {
83 | var storeObj = wrapStoreData( obj, result );
84 |
85 | if (!obj.skipCache) {
86 | addLocalStorage( obj.key , storeObj );
87 | }
88 |
89 | return storeObj;
90 | });
91 | };
92 |
93 | var wrapStoreData = function( obj, data ) {
94 | var now = +new Date();
95 | obj.data = data.content;
96 | obj.originalType = data.type;
97 | obj.type = obj.type || data.type;
98 | obj.skipCache = obj.skipCache || false;
99 | obj.stamp = now;
100 | obj.expire = now + ( ( obj.expire || defaultExpiration ) * 60 * 60 * 1000 );
101 |
102 | return obj;
103 | };
104 |
105 | var isCacheValid = function(source, obj) {
106 | return !source ||
107 | source.expire - +new Date() < 0 ||
108 | obj.unique !== source.unique ||
109 | (basket.isValidItem && !basket.isValidItem(source, obj));
110 | };
111 |
112 | var handleStackObject = function( obj ) {
113 | var source, promise, shouldFetch;
114 |
115 | if ( !obj.url ) {
116 | return;
117 | }
118 |
119 | obj.key = ( obj.key || obj.url );
120 | source = basket.get( obj.key );
121 |
122 | obj.execute = obj.execute !== false;
123 |
124 | shouldFetch = isCacheValid(source, obj);
125 |
126 | if( obj.live || shouldFetch ) {
127 | if ( obj.unique ) {
128 | // set parameter to prevent browser cache
129 | obj.url += ( ( obj.url.indexOf('?') > 0 ) ? '&' : '?' ) + 'basket-unique=' + obj.unique;
130 | }
131 | promise = saveUrl( obj );
132 |
133 | if( obj.live && !shouldFetch ) {
134 | promise = promise
135 | .then( function( result ) {
136 | // If we succeed, just return the value
137 | // RSVP doesn't have a .fail convenience method
138 | return result;
139 | }, function() {
140 | return source;
141 | });
142 | }
143 | } else {
144 | source.type = obj.type || source.originalType;
145 | source.execute = obj.execute;
146 | promise = new RSVP.Promise( function( resolve ){
147 | resolve( source );
148 | });
149 | }
150 |
151 | return promise;
152 | };
153 |
154 | var injectScript = function( obj ) {
155 | var script = document.createElement('script');
156 | script.defer = true;
157 | // Have to use .text, since we support IE8,
158 | // which won't allow appending to a script
159 | script.text = obj.data;
160 | head.appendChild( script );
161 | };
162 |
163 | var handlers = {
164 | 'default': injectScript
165 | };
166 |
167 | var execute = function( obj ) {
168 | if( obj.type && handlers[ obj.type ] ) {
169 | return handlers[ obj.type ]( obj );
170 | }
171 |
172 | return handlers['default']( obj ); // 'default' is a reserved word
173 | };
174 |
175 | var performActions = function( resources ) {
176 | return resources.map( function( obj ) {
177 | if( obj.execute ) {
178 | execute( obj );
179 | }
180 |
181 | return obj;
182 | } );
183 | };
184 |
185 | var fetch = function() {
186 | var i, l, promises = [];
187 |
188 | for ( i = 0, l = arguments.length; i < l; i++ ) {
189 | promises.push( handleStackObject( arguments[ i ] ) );
190 | }
191 |
192 | return RSVP.all( promises );
193 | };
194 |
195 | var thenRequire = function() {
196 | var resources = fetch.apply( null, arguments );
197 | var promise = this.then( function() {
198 | return resources;
199 | }).then( performActions );
200 | promise.thenRequire = thenRequire;
201 | return promise;
202 | };
203 |
204 | window.basket = {
205 | require: function() {
206 | for ( var a = 0, l = arguments.length; a < l; a++ ) {
207 | arguments[a].execute = arguments[a].execute !== false;
208 |
209 | if ( arguments[a].once && inBasket.indexOf(arguments[a].url) >= 0 ) {
210 | arguments[a].execute = false;
211 | } else if ( arguments[a].execute !== false && inBasket.indexOf(arguments[a].url) < 0 ) {
212 | inBasket.push(arguments[a].url);
213 | }
214 | }
215 |
216 | var promise = fetch.apply( null, arguments ).then( performActions );
217 |
218 | promise.thenRequire = thenRequire;
219 | return promise;
220 | },
221 |
222 | remove: function( key ) {
223 | localStorage.removeItem( storagePrefix + key );
224 | return this;
225 | },
226 |
227 | get: function( key ) {
228 | var item = localStorage.getItem( storagePrefix + key );
229 | try {
230 | return JSON.parse( item || 'false' );
231 | } catch( e ) {
232 | return false;
233 | }
234 | },
235 |
236 | clear: function( expired ) {
237 | var item, key;
238 | var now = +new Date();
239 |
240 | for ( item in localStorage ) {
241 | key = item.split( storagePrefix )[ 1 ];
242 | if ( key && ( !expired || this.get( key ).expire <= now ) ) {
243 | this.remove( key );
244 | }
245 | }
246 |
247 | return this;
248 | },
249 |
250 | isValidItem: null,
251 |
252 | timeout: 5000,
253 |
254 | addHandler: function( types, handler ) {
255 | if( !Array.isArray( types ) ) {
256 | types = [ types ];
257 | }
258 | types.forEach( function( type ) {
259 | handlers[ type ] = handler;
260 | });
261 | },
262 |
263 | removeHandler: function( types ) {
264 | basket.addHandler( types, undefined );
265 | }
266 | };
267 |
268 | // delete expired keys
269 | basket.clear( true );
270 |
271 | })( this, document );
272 |
--------------------------------------------------------------------------------
/_includes/api.md:
--------------------------------------------------------------------------------
1 | ## API
2 |
3 | * [`basket.require()`](#basketrequire)
4 | * [`basket.get()`](#basketget)
5 | * [`basket.remove()`](#basketremove)
6 | * [`basket.clear()`](#basketclear)
7 |
8 | ### basket.require
9 |
10 | `basket.require(details)`
11 |
12 | *details:* Either an object or an array of objects with the following fields:
13 |
14 |
15 | * **url** (*required*) The URI for the script. At present this must be a URI on the same origin as the caller.
16 | * **key** The name that will be used to refer to this script. By default this is the *uri*.
17 | * **expire** How long (in hours) before the cached item expires.
18 | * **execute** Whether to cause the script to be executed once it has been retrieved. Defaults to true.
19 | * **unique** A token stored with the cached item. If you request the same item again with a different token the script will be fetched and cached again.
20 | * **skipCache** Prevent storing the script in cache. Useful when you want load scripts in order, but only cache some. By default is *false*.
21 |
22 | `require()` returns a [promise](http://wiki.commonjs.org/wiki/Promises/A) that will be fulfilled when each of the requested items has been fetched, or rejected if any item fails.
23 |
24 | #### Examples
25 |
26 | **Single script**
27 |
28 | ```js
29 | basket.require({ url: 'jquery.js' });
30 | ```
31 |
32 | This fetches a single script and executes it. If the script was already in the localStorage cache it will be loaded from there, otherwise it will be loaded from the network. The script will then be injected into the document for the browser to execute.
33 |
34 | **Multiple scripts**
35 |
36 | ```js
37 | basket.require(
38 | { url: 'jquery.js' },
39 | { url: 'underscore.js' },
40 | { url: 'backbone.js' }
41 | );
42 | ```
43 |
44 | Multiple scripts will be requested. The scripts are requested asynchronously but executed in the same order as specified.
45 |
46 | **Multiple scripts without caching some of them**
47 |
48 | ```js
49 | basket.require(
50 | { url: 'require.js' },
51 | { url: 'require.config.js', skipCache: true },
52 | { url: 'libs.js' }
53 | );
54 | ```
55 |
56 | Multiple scripts will be requested. `require.config.js` will not be cached in localStorage. Useful if order of scripts execution is important but storing certain script is not needed, e.g. it changes with each request.
57 |
58 | **Ordering dependencies**
59 |
60 | ```js
61 | basket
62 | .require({ url: 'jquery.js' })
63 | .then(function () {
64 | basket.require({ url: 'jquery-ui.js' });
65 | });
66 | ```
67 |
68 | Here we ask basket.js to load jQuery. Once it has been fetched and executed, the promise returned by `require()` will be fulfilled and the callback passed to the `then()` method of the promise will be executed. Now we can do anything the requires jquery to be loaded including load any scripts that depend on it.
69 |
70 | **Error handling**
71 |
72 | ```js
73 | basket
74 | .require({ url: 'missing.js' })
75 | .then(function () {
76 | // Success
77 | }, function (error) {
78 | // There was an error fetching the script
79 | console.log(error);
80 | });
81 | ```
82 |
83 | The second parameter to `then()` is a function that will be called if the promise is rejected. That is, if there was an error fetching the script. The only parameter to the error callback will be an Error object with details of the failure.
84 |
85 | **Using an alias**
86 |
87 | ```js
88 | basket.require({ url: 'jquery-2.0.0.min.js', key: 'jquery' });
89 | ```
90 |
91 | If you wish to store a script under a specific key name (e.g. if you have a build process which creates a script with a name like `012345.js` and want to store it as, say, `main`), you can set the `key` property to the name you want to use.
92 |
93 | This can also be useful for libraries with version numbers in their URIs when you don't need a particular version. In the above example the cache will be checked for a script stored as "jquery" regardless of its original URI. This allows us to use an older version stored in the cache if one exists.
94 |
95 | If `key` is not set the url will be used instead.
96 |
97 | **Cache expiry**
98 |
99 | ```js
100 | basket.require({ url: 'jquery.min.js', expire: 2 })
101 | ```
102 |
103 | Here script will only be cached for up to 2 hours. After that time it will be fetched from the network again even if it exists in the cache. To re-fetch after 7 days you could set 168 ( i.e. 7 * 24 = 168 ).
104 |
105 | If `expire` is not set, the default time of 5000 hours will be used - almost 7 months.
106 |
107 | **Cache a file without executing it**
108 |
109 | ```js
110 | basket.require({ url: 'jquery.min.js', execute: false });
111 | ```
112 |
113 | The script will be cached but will not be added to the document to be executed.
114 |
115 | **Cache busting**
116 |
117 | ```js
118 | // fetch and cache the file
119 | basket.require({ url: 'jquery.min.js' });
120 | // fetch and cache again
121 | basket.require({ url: 'jquery.min.js', unique: 123 });
122 | ```
123 |
124 | Set the `unique` property to control whether the script will be loaded from the cache. If the parameter is different in the request to that stored in the cache the file is fetched again.
125 |
126 | basket.js will add the "basket-unique" parameter on to the url to also prevent the script being fetched from the browser cache.
127 |
128 |
129 | ### basket.get
130 |
131 | `basket.get(key)`
132 |
133 | **key** The key to lookup in the localStorage cache.
134 |
135 | `get()` will return an object if script is found or false if not. The object contains the same data as that passed to `require()` when it was first loaded, with some additional details added:
136 |
137 | * **stamp** The timestamp for when the file was fetched.
138 | * **expire** The timestamp for when the item will expire.
139 | * **data** The file contents of the script.
140 |
141 | ```js
142 | var req
143 | var ttl;
144 |
145 | basket.require({ url: 'jquery.min.js', key: 'jquery' });
146 | req = basket.get('jquery');
147 | // know the lifetime
148 | ttl = req.expire - req.stamp;
149 | ```
150 |
151 |
152 | ### basket.remove
153 |
154 | `basket.remove(key)`
155 |
156 | **key** The key to remove from the localStorage cache.
157 |
158 | `remove()` will simply remove a previously cached script from localStorage. An example of how to use it can be seen below:
159 |
160 | ```js
161 | basket
162 | .remove('jquery.js')
163 | .remove('modernizr');
164 | ```
165 |
166 | ### basket.clear
167 |
168 | `basket.clear(expired)`
169 |
170 | **expired** If `expired` is true then only items that have expired will be removed. Otherwise all items are removed.
171 |
172 | `clear()` removes items from the cache.
173 |
174 | ```js
175 | basket.clear();
176 | basket.clear(true);
177 | ```
178 |
179 | ### basket.isValidItem
180 |
181 | `basket.isValidItem(source, obj)`
182 |
183 | * **source** The source of the item returned from localStorage
184 | * **obj** The item passed into `require`
185 |
186 | **Optional** This property can be set to a custom validation method that should return a boolean value depending on the validity of the item `(source)`. `isValidItem` is called for each item in a `require` call.
187 |
188 | `isValidItem()` is expected to return `true` is the item is valid. If `isValidItem()` returns `false`, the item will be loaded from the network. `isValidItem()` if present is an additonal check and does not override the existing checks for expiration and uniqueness.
189 |
190 | This is targetted at advanced usage and is strictly optional. The use of `unique` and `expire` parameters are the most appropriate way to handle common scenarios.
191 |
192 | ```js
193 | basket.isValidItem = function (source, obj) {
194 | return myVerificationFunction(source, obj);
195 | };
196 | ```
197 |
--------------------------------------------------------------------------------
/dist/basket.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * basket.js
3 | * v0.5.2 - 2015-02-07
4 | * http://addyosmani.github.com/basket.js
5 | * (c) Addy Osmani; License
6 | * Created by: Addy Osmani, Sindre Sorhus, Andrée Hansson, Mat Scales
7 | * Contributors: Ironsjp, Mathias Bynens, Rick Waldron, Felipe Morais
8 | * Uses rsvp.js, https://github.com/tildeio/rsvp.js
9 | */(function( window, document ) {
10 | 'use strict';
11 |
12 | var head = document.head || document.getElementsByTagName('head')[0];
13 | var storagePrefix = 'basket-';
14 | var defaultExpiration = 5000;
15 | var inBasket = [];
16 |
17 | var addLocalStorage = function( key, storeObj ) {
18 | try {
19 | localStorage.setItem( storagePrefix + key, JSON.stringify( storeObj ) );
20 | return true;
21 | } catch( e ) {
22 | if ( e.name.toUpperCase().indexOf('QUOTA') >= 0 ) {
23 | var item;
24 | var tempScripts = [];
25 |
26 | for ( item in localStorage ) {
27 | if ( item.indexOf( storagePrefix ) === 0 ) {
28 | tempScripts.push( JSON.parse( localStorage[ item ] ) );
29 | }
30 | }
31 |
32 | if ( tempScripts.length ) {
33 | tempScripts.sort(function( a, b ) {
34 | return a.stamp - b.stamp;
35 | });
36 |
37 | basket.remove( tempScripts[ 0 ].key );
38 |
39 | return addLocalStorage( key, storeObj );
40 |
41 | } else {
42 | // no files to remove. Larger than available quota
43 | return;
44 | }
45 |
46 | } else {
47 | // some other error
48 | return;
49 | }
50 | }
51 |
52 | };
53 |
54 | var getUrl = function( url ) {
55 | var promise = new RSVP.Promise( function( resolve, reject ){
56 |
57 | var xhr = new XMLHttpRequest();
58 | xhr.open( 'GET', url );
59 |
60 | xhr.onreadystatechange = function() {
61 | if ( xhr.readyState === 4 ) {
62 | if ( ( xhr.status === 200 ) ||
63 | ( ( xhr.status === 0 ) && xhr.responseText ) ) {
64 | resolve( {
65 | content: xhr.responseText,
66 | type: xhr.getResponseHeader('content-type')
67 | } );
68 | } else {
69 | reject( new Error( xhr.statusText ) );
70 | }
71 | }
72 | };
73 |
74 | // By default XHRs never timeout, and even Chrome doesn't implement the
75 | // spec for xhr.timeout. So we do it ourselves.
76 | setTimeout( function () {
77 | if( xhr.readyState < 4 ) {
78 | xhr.abort();
79 | }
80 | }, basket.timeout );
81 |
82 | xhr.send();
83 | });
84 |
85 | return promise;
86 | };
87 |
88 | var saveUrl = function( obj ) {
89 | return getUrl( obj.url ).then( function( result ) {
90 | var storeObj = wrapStoreData( obj, result );
91 |
92 | if (!obj.skipCache) {
93 | addLocalStorage( obj.key , storeObj );
94 | }
95 |
96 | return storeObj;
97 | });
98 | };
99 |
100 | var wrapStoreData = function( obj, data ) {
101 | var now = +new Date();
102 | obj.data = data.content;
103 | obj.originalType = data.type;
104 | obj.type = obj.type || data.type;
105 | obj.skipCache = obj.skipCache || false;
106 | obj.stamp = now;
107 | obj.expire = now + ( ( obj.expire || defaultExpiration ) * 60 * 60 * 1000 );
108 |
109 | return obj;
110 | };
111 |
112 | var isCacheValid = function(source, obj) {
113 | return !source ||
114 | source.expire - +new Date() < 0 ||
115 | obj.unique !== source.unique ||
116 | (basket.isValidItem && !basket.isValidItem(source, obj));
117 | };
118 |
119 | var handleStackObject = function( obj ) {
120 | var source, promise, shouldFetch;
121 |
122 | if ( !obj.url ) {
123 | return;
124 | }
125 |
126 | obj.key = ( obj.key || obj.url );
127 | source = basket.get( obj.key );
128 |
129 | obj.execute = obj.execute !== false;
130 |
131 | shouldFetch = isCacheValid(source, obj);
132 |
133 | if( obj.live || shouldFetch ) {
134 | if ( obj.unique ) {
135 | // set parameter to prevent browser cache
136 | obj.url += ( ( obj.url.indexOf('?') > 0 ) ? '&' : '?' ) + 'basket-unique=' + obj.unique;
137 | }
138 | promise = saveUrl( obj );
139 |
140 | if( obj.live && !shouldFetch ) {
141 | promise = promise
142 | .then( function( result ) {
143 | // If we succeed, just return the value
144 | // RSVP doesn't have a .fail convenience method
145 | return result;
146 | }, function() {
147 | return source;
148 | });
149 | }
150 | } else {
151 | source.type = obj.type || source.originalType;
152 | source.execute = obj.execute;
153 | promise = new RSVP.Promise( function( resolve ){
154 | resolve( source );
155 | });
156 | }
157 |
158 | return promise;
159 | };
160 |
161 | var injectScript = function( obj ) {
162 | var script = document.createElement('script');
163 | script.defer = true;
164 | // Have to use .text, since we support IE8,
165 | // which won't allow appending to a script
166 | script.text = obj.data;
167 | head.appendChild( script );
168 | };
169 |
170 | var handlers = {
171 | 'default': injectScript
172 | };
173 |
174 | var execute = function( obj ) {
175 | if( obj.type && handlers[ obj.type ] ) {
176 | return handlers[ obj.type ]( obj );
177 | }
178 |
179 | return handlers['default']( obj ); // 'default' is a reserved word
180 | };
181 |
182 | var performActions = function( resources ) {
183 | return resources.map( function( obj ) {
184 | if( obj.execute ) {
185 | execute( obj );
186 | }
187 |
188 | return obj;
189 | } );
190 | };
191 |
192 | var fetch = function() {
193 | var i, l, promises = [];
194 |
195 | for ( i = 0, l = arguments.length; i < l; i++ ) {
196 | promises.push( handleStackObject( arguments[ i ] ) );
197 | }
198 |
199 | return RSVP.all( promises );
200 | };
201 |
202 | var thenRequire = function() {
203 | var resources = fetch.apply( null, arguments );
204 | var promise = this.then( function() {
205 | return resources;
206 | }).then( performActions );
207 | promise.thenRequire = thenRequire;
208 | return promise;
209 | };
210 |
211 | window.basket = {
212 | require: function() {
213 | for ( var a = 0, l = arguments.length; a < l; a++ ) {
214 | arguments[a].execute = arguments[a].execute !== false;
215 |
216 | if ( arguments[a].once && inBasket.indexOf(arguments[a].url) >= 0 ) {
217 | arguments[a].execute = false;
218 | } else if ( arguments[a].execute !== false && inBasket.indexOf(arguments[a].url) < 0 ) {
219 | inBasket.push(arguments[a].url);
220 | }
221 | }
222 |
223 | var promise = fetch.apply( null, arguments ).then( performActions );
224 |
225 | promise.thenRequire = thenRequire;
226 | return promise;
227 | },
228 |
229 | remove: function( key ) {
230 | localStorage.removeItem( storagePrefix + key );
231 | return this;
232 | },
233 |
234 | get: function( key ) {
235 | var item = localStorage.getItem( storagePrefix + key );
236 | try {
237 | return JSON.parse( item || 'false' );
238 | } catch( e ) {
239 | return false;
240 | }
241 | },
242 |
243 | clear: function( expired ) {
244 | var item, key;
245 | var now = +new Date();
246 |
247 | for ( item in localStorage ) {
248 | key = item.split( storagePrefix )[ 1 ];
249 | if ( key && ( !expired || this.get( key ).expire <= now ) ) {
250 | this.remove( key );
251 | }
252 | }
253 |
254 | return this;
255 | },
256 |
257 | isValidItem: null,
258 |
259 | timeout: 5000,
260 |
261 | addHandler: function( types, handler ) {
262 | if( !Array.isArray( types ) ) {
263 | types = [ types ];
264 | }
265 | types.forEach( function( type ) {
266 | handlers[ type ] = handler;
267 | });
268 | },
269 |
270 | removeHandler: function( types ) {
271 | basket.addHandler( types, undefined );
272 | }
273 | };
274 |
275 | // delete expired keys
276 | basket.clear( true );
277 |
278 | })( this, document );
279 |
--------------------------------------------------------------------------------
/asset/base.css:
--------------------------------------------------------------------------------
1 | /*--------------------- Layout and Typography ----------------------------*/
2 | body {
3 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
4 | font-size: 15px;
5 | line-height: 22px;
6 | color: #252519;
7 | margin: 0; padding: 0;
8 | }
9 | a {
10 | color: #261a3b;
11 | }
12 | a:visited {
13 | color: #261a3b;
14 | }
15 | p {
16 | margin: 0 0 15px 0;
17 | }
18 | h1, h2, h3, h4, h5, h6 {
19 | margin: 0px 0 15px 0;
20 | }
21 | h1 {
22 | margin-top: 40px;
23 | }
24 | h2{
25 | line-height:32px;
26 | }
27 | #container {
28 | position: relative;
29 | padding: 40px;
30 | width: 650px;
31 |
32 | margin:0 auto;
33 | }
34 | #background {
35 | position: fixed;
36 | top: 0; left: 525px; right: 0; bottom: 0;
37 | background: #f5f5ff;
38 | border-left: 1px solid #e5e5ee;
39 | border-right: 1px solid #e5e5ee;
40 | z-index: -1;
41 | }
42 |
43 | .content{
44 | width:600px;
45 | }
46 |
47 | blockquote {
48 | padding: 13px 13px 21px 15px;
49 | margin-bottom: 18px;
50 | font-family:georgia,serif;
51 | font-style: italic;
52 | }
53 | blockquote:before {
54 | content:"\201C";
55 | font-size:40px;
56 | margin-left:-10px;
57 | font-family:georgia,serif;
58 | color:#eee;
59 | }
60 | blockquote p {
61 | font-size: 14px;
62 | font-weight: 300;
63 | line-height: 18px;
64 | margin-bottom: 0;
65 | font-style: italic;
66 | }
67 |
68 | table td {
69 | border: 0;
70 | outline: 0;
71 | }
72 | td.docs, th.docs {
73 | max-width: 450px;
74 | min-width: 450px;
75 | min-height: 5px;
76 | padding: 10px 25px 1px 50px;
77 | overflow-x: hidden;
78 | vertical-align: top;
79 | text-align: left;
80 | }
81 | .docs pre {
82 | margin: 15px 0 15px;
83 | padding-left: 15px;
84 | }
85 | .docs p tt, .docs p code {
86 | background: #f8f8ff;
87 | border: 1px solid #dedede;
88 | font-size: 12px;
89 | padding: 0 0.2em;
90 | }
91 | .pilwrap {
92 | position: relative;
93 | }
94 | .pilcrow {
95 | font: 12px Arial;
96 | text-decoration: none;
97 | color: #454545;
98 | position: absolute;
99 | top: 3px; left: -20px;
100 | padding: 1px 2px;
101 | opacity: 0;
102 | -webkit-transition: opacity 0.2s linear;
103 | }
104 | td.docs:hover .pilcrow {
105 | opacity: 1;
106 | }
107 | td.code, th.code {
108 | padding: 14px 15px 16px 25px;
109 | width: 100%;
110 | vertical-align: top;
111 | background: #f5f5ff;
112 | border-left: 1px solid #e5e5ee;
113 | }
114 | pre, tt, code {
115 | font-size: 12px; line-height: 18px;
116 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
117 | margin: 0; padding: 0;
118 | }
119 |
120 |
121 | /*---------------------- Syntax Highlighting -----------------------------*/
122 | td.linenos { background-color: #f0f0f0; padding-right: 10px; }
123 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
124 | body .hll { background-color: #ffffcc }
125 | body .c { color: #408080; font-style: italic } /* Comment */
126 | body .err { border: 1px solid #FF0000 } /* Error */
127 | body .k { color: #954121 } /* Keyword */
128 | body .o { color: #666666 } /* Operator */
129 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */
130 | body .cp { color: #BC7A00 } /* Comment.Preproc */
131 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */
132 | body .cs { color: #408080; font-style: italic } /* Comment.Special */
133 | body .gd { color: #A00000 } /* Generic.Deleted */
134 | body .ge { font-style: italic } /* Generic.Emph */
135 | body .gr { color: #FF0000 } /* Generic.Error */
136 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */
137 | body .gi { color: #00A000 } /* Generic.Inserted */
138 | body .go { color: #808080 } /* Generic.Output */
139 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
140 | body .gs { font-weight: bold } /* Generic.Strong */
141 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
142 | body .gt { color: #0040D0 } /* Generic.Traceback */
143 | body .kc { color: #954121 } /* Keyword.Constant */
144 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */
145 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */
146 | body .kp { color: #954121 } /* Keyword.Pseudo */
147 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */
148 | body .kt { color: #B00040 } /* Keyword.Type */
149 | body .m { color: #666666 } /* Literal.Number */
150 | body .s { color: #219161 } /* Literal.String */
151 | body .na { color: #7D9029 } /* Name.Attribute */
152 | body .nb { color: #954121 } /* Name.Builtin */
153 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */
154 | body .no { color: #880000 } /* Name.Constant */
155 | body .nd { color: #AA22FF } /* Name.Decorator */
156 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */
157 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
158 | body .nf { color: #0000FF } /* Name.Function */
159 | body .nl { color: #A0A000 } /* Name.Label */
160 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
161 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */
162 | body .nv { color: #19469D } /* Name.Variable */
163 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
164 | body .w { color: #bbbbbb } /* Text.Whitespace */
165 | body .mf { color: #666666 } /* Literal.Number.Float */
166 | body .mh { color: #666666 } /* Literal.Number.Hex */
167 | body .mi { color: #666666 } /* Literal.Number.Integer */
168 | body .mo { color: #666666 } /* Literal.Number.Oct */
169 | body .sb { color: #219161 } /* Literal.String.Backtick */
170 | body .sc { color: #219161 } /* Literal.String.Char */
171 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */
172 | body .s2 { color: #219161 } /* Literal.String.Double */
173 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
174 | body .sh { color: #219161 } /* Literal.String.Heredoc */
175 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
176 | body .sx { color: #954121 } /* Literal.String.Other */
177 | body .sr { color: #BB6688 } /* Literal.String.Regex */
178 | body .s1 { color: #219161 } /* Literal.String.Single */
179 | body .ss { color: #19469D } /* Literal.String.Symbol */
180 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */
181 | body .vc { color: #19469D } /* Name.Variable.Class */
182 | body .vg { color: #19469D } /* Name.Variable.Global */
183 | body .vi { color: #19469D } /* Name.Variable.Instance */
184 | body .il { color: #666666 } /* Literal.Number.Integer.Long */
185 |
186 | code, pre {
187 | font-family: Monaco, Andale Mono, Courier New, monospace;
188 | }
189 | code {
190 | background-color: #fee9cc;
191 | color: rgba(0, 0, 0, 0.75);
192 | padding: 1px 3px;
193 | font-size: 12px;
194 | -webkit-border-radius: 3px;
195 | -moz-border-radius: 3px;
196 | border-radius: 3px;
197 | }
198 | pre {
199 | display: block;
200 | padding: 14px;
201 | margin: 0 10px 18px;
202 | line-height: 16px;
203 | font-size: 11px;
204 | border: 1px solid #d9d9d9;
205 | white-space: pre-wrap;
206 | word-wrap: break-word;
207 | }
208 | pre code {
209 | background-color: #fff;
210 | color:#737373;
211 | font-size: 11px;
212 | padding: 0;
213 | }
214 |
215 | .boxout {
216 | border-radius: 5px;
217 | border: 1px solid rgb(220, 220, 235);
218 | background-color: rgb(240, 240, 255);
219 | padding: 10px;
220 | }
221 |
--------------------------------------------------------------------------------
/test/fixtures/underscore-min.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.3.1
2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
3 | // Underscore is freely distributable under the MIT license.
4 | // Portions of Underscore are inspired or borrowed from Prototype,
5 | // Oliver Steele's Functional, and John Resig's Micro-Templating.
6 | // For all details and documentation:
7 | // http://documentcloud.github.com/underscore
8 | (function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
10 | h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
11 | b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a==
12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=
13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=
14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,
17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};
24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments,
25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};
26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};
27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),
28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+
29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]=
30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=
31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
32 |
--------------------------------------------------------------------------------
/dist/basket.full.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * basket.js
3 | * v0.5.2 - 2015-02-07
4 | * http://addyosmani.github.com/basket.js
5 | * (c) Addy Osmani; License
6 | * Created by: Addy Osmani, Sindre Sorhus, Andrée Hansson, Mat Scales
7 | * Contributors: Ironsjp, Mathias Bynens, Rick Waldron, Felipe Morais
8 | * Uses rsvp.js, https://github.com/tildeio/rsvp.js
9 | */
10 | (function(){"use strict";function a(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1}function b(a){var b=a._promiseCallbacks;return b||(b=a._promiseCallbacks={}),b}function c(a,b){return"onerror"===a?void rb.on("error",b):2!==arguments.length?rb[a]:void(rb[a]=b)}function d(a){return"function"==typeof a||"object"==typeof a&&null!==a}function e(a){return"function"==typeof a}function f(a){return"object"==typeof a&&null!==a}function g(){}function h(){setTimeout(function(){for(var a,b=0;bh;h++)u(e.resolve(a[h]),void 0,c,d);return f}function E(a,b){var c=this;if(a&&"object"==typeof a&&a.constructor===c)return a;var d=new c(k,b);return q(d,a),d}function F(a,b){var c=this,d=new c(k,b);return t(d,a),d}function G(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function H(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function I(a,b){this._id=Jb++,this._label=b,this._state=void 0,this._result=void 0,this._subscribers=[],rb.instrument&&xb("created",this),k!==a&&(e(a)||G(),this instanceof I||H(),z(this,a))}function J(){this.value=void 0}function K(a){try{return a.then}catch(b){return Lb.value=b,Lb}}function L(a,b,c){try{a.apply(b,c)}catch(d){return Lb.value=d,Lb}}function M(a,b){for(var c,d,e={},f=a.length,g=new Array(f),h=0;f>h;h++)g[h]=a[h];for(d=0;dd;d++)c[d-1]=a[d];return c}function O(a,b){return{then:function(c,d){return a.call(b,c,d)}}}function P(a,b){var c=function(){for(var c,d=this,e=arguments.length,f=new Array(e+1),g=!1,h=0;e>h;++h){if(c=arguments[h],!g){if(g=S(c),g===Mb){var i=new Kb(k);return t(i,Mb.value),i}g&&g!==!0&&(c=O(g,c))}f[h]=c}var j=new Kb(k);return f[e]=function(a,c){a?t(j,a):void 0===b?q(j,c):b===!0?q(j,N(arguments)):tb(b)?q(j,M(arguments,b)):q(j,c)},g?R(j,f,a,d):Q(j,f,a,d)};return c.__proto__=a,c}function Q(a,b,c,d){var e=L(c,d,b);return e===Lb&&t(a,e.value),a}function R(a,b,c,d){return Kb.all(b).then(function(b){var e=L(c,d,b);return e===Lb&&t(a,e.value),a})}function S(a){return a&&"object"==typeof a?a.constructor===Kb?!0:K(a):!1}function T(a,b){return Kb.all(a,b)}function U(a,b,c){this._superConstructor(a,b,!1,c)}function V(a,b){return new U(Kb,a,b).promise}function W(a,b){return Kb.race(a,b)}function X(a,b,c){this._superConstructor(a,b,!0,c)}function Y(a,b){return new Rb(Kb,a,b).promise}function Z(a,b,c){this._superConstructor(a,b,!1,c)}function $(a,b){return new Z(Kb,a,b).promise}function _(a){throw setTimeout(function(){throw a}),a}function ab(a){var b={};return b.promise=new Kb(function(a,c){b.resolve=a,b.reject=c},a),b}function bb(a,b,c){return Kb.all(a,c).then(function(a){if(!e(b))throw new TypeError("You must pass a function as map's second argument.");for(var d=a.length,f=new Array(d),g=0;d>g;g++)f[g]=b(a[g]);return Kb.all(f,c)})}function cb(a,b){return Kb.resolve(a,b)}function db(a,b){return Kb.reject(a,b)}function eb(a,b,c){return Kb.all(a,c).then(function(a){if(!e(b))throw new TypeError("You must pass a function as filter's second argument.");for(var d=a.length,f=new Array(d),g=0;d>g;g++)f[g]=b(a[g]);return Kb.all(f,c).then(function(b){for(var c=new Array(d),e=0,f=0;d>f;f++)b[f]&&(c[e]=a[f],e++);return c.length=e,c})})}function fb(a,b){gc[_b]=a,gc[_b+1]=b,_b+=2,2===_b&&Tb()}function gb(){var a=process.nextTick,b=process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);return Array.isArray(b)&&"0"===b[1]&&"10"===b[2]&&(a=setImmediate),function(){a(lb)}}function hb(){return function(){vertxNext(lb)}}function ib(){var a=0,b=new dc(lb),c=document.createTextNode("");return b.observe(c,{characterData:!0}),function(){c.data=a=++a%2}}function jb(){var a=new MessageChannel;return a.port1.onmessage=lb,function(){a.port2.postMessage(0)}}function kb(){return function(){setTimeout(lb,1)}}function lb(){for(var a=0;_b>a;a+=2){var b=gc[a],c=gc[a+1];b(c),gc[a]=void 0,gc[a+1]=void 0}_b=0}function mb(){try{var a=require("vertx");return a.runOnLoop||a.runOnContext,hb()}catch(b){return kb()}}function nb(a,b){rb.async(a,b)}function ob(){rb.on.apply(rb,arguments)}function pb(){rb.off.apply(rb,arguments)}var qb={mixin:function(a){return a.on=this.on,a.off=this.off,a.trigger=this.trigger,a._promiseCallbacks=void 0,a},on:function(c,d){var e,f=b(this);e=f[c],e||(e=f[c]=[]),-1===a(e,d)&&e.push(d)},off:function(c,d){var e,f,g=b(this);return d?(e=g[c],f=a(e,d),void(-1!==f&&e.splice(f,1))):void(g[c]=[])},trigger:function(a,c){var d,e,f=b(this);if(d=f[a])for(var g=0;g1)throw new Error("Second argument not supported");if("object"!=typeof a)throw new TypeError("Argument must be an object");return g.prototype=a,new g},wb=[],xb=i,yb=void 0,zb=1,Ab=2,Bb=new w,Cb=new w,Db=B;B.prototype._validateInput=function(a){return tb(a)},B.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},B.prototype._init=function(){this._result=new Array(this.length)},B.prototype._enumerate=function(){for(var a=this.length,b=this.promise,c=this._input,d=0;b._state===yb&&a>d;d++)this._eachEntry(c[d],d)},B.prototype._eachEntry=function(a,b){var c=this._instanceConstructor;f(a)?a.constructor===c&&a._state!==yb?(a._onError=null,this._settledAt(a._state,b,a._result)):this._willSettleAt(c.resolve(a),b):(this._remaining--,this._result[b]=this._makeResult(zb,b,a))},B.prototype._settledAt=function(a,b,c){var d=this.promise;d._state===yb&&(this._remaining--,this._abortOnReject&&a===Ab?t(d,c):this._result[b]=this._makeResult(a,b,c)),0===this._remaining&&s(d,this._result)},B.prototype._makeResult=function(a,b,c){return c},B.prototype._willSettleAt=function(a,b){var c=this;u(a,void 0,function(a){c._settledAt(zb,b,a)},function(a){c._settledAt(Ab,b,a)})};var Eb=C,Fb=D,Gb=E,Hb=F,Ib="rsvp_"+ub()+"-",Jb=0,Kb=I;I.cast=Gb,I.all=Eb,I.race=Fb,I.resolve=Gb,I.reject=Hb,I.prototype={constructor:I,_guidKey:Ib,_onError:function(a){rb.async(function(b){setTimeout(function(){b._onError&&rb.trigger("error",a)},0)},this)},then:function(a,b,c){var d=this,e=d._state;if(e===zb&&!a||e===Ab&&!b)return rb.instrument&&xb("chained",this,this),this;d._onError=null;var f=new this.constructor(k,c),g=d._result;if(rb.instrument&&xb("chained",d,f),e){var h=arguments[e-1];rb.async(function(){y(e,f,h,g)})}else u(d,f,a,b);return f},"catch":function(a,b){return this.then(null,a,b)},"finally":function(a,b){var c=this.constructor;return this.then(function(b){return c.resolve(a()).then(function(){return b})},function(b){return c.resolve(a()).then(function(){throw b})},b)}};var Lb=new J,Mb=new J,Nb=P,Ob=T;U.prototype=vb(Db.prototype),U.prototype._superConstructor=Db,U.prototype._makeResult=A,U.prototype._validationError=function(){return new Error("allSettled must be called with an array")};var Pb=V,Qb=W,Rb=X;X.prototype=vb(Db.prototype),X.prototype._superConstructor=Db,X.prototype._init=function(){this._result={}},X.prototype._validateInput=function(a){return a&&"object"==typeof a},X.prototype._validationError=function(){return new Error("Promise.hash must be called with an object")},X.prototype._enumerate=function(){var a=this.promise,b=this._input,c=[];for(var d in b)a._state===yb&&b.hasOwnProperty(d)&&c.push({position:d,entry:b[d]});var e=c.length;this._remaining=e;for(var f,g=0;a._state===yb&&e>g;g++)f=c[g],this._eachEntry(f.entry,f.position)};var Sb=Y;Z.prototype=vb(Rb.prototype),Z.prototype._superConstructor=Db,Z.prototype._makeResult=A,Z.prototype._validationError=function(){return new Error("hashSettled must be called with an object")};var Tb,Ub=$,Vb=_,Wb=ab,Xb=bb,Yb=cb,Zb=db,$b=eb,_b=0,ac=fb,bc="undefined"!=typeof window?window:void 0,cc=bc||{},dc=cc.MutationObserver||cc.WebKitMutationObserver,ec="undefined"!=typeof process&&"[object process]"==={}.toString.call(process),fc="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,gc=new Array(1e3);if(Tb=ec?gb():dc?ib():fc?jb():void 0===bc&&"function"==typeof require?mb():kb(),rb.async=ac,"undefined"!=typeof window&&"object"==typeof window.__PROMISE_INSTRUMENTATION__){var hc=window.__PROMISE_INSTRUMENTATION__;c("instrument",!0);for(var ic in hc)hc.hasOwnProperty(ic)&&ob(ic,hc[ic])}var jc={race:Qb,Promise:Kb,allSettled:Pb,hash:Sb,hashSettled:Ub,denodeify:Nb,on:ob,off:pb,map:Xb,filter:$b,resolve:Yb,reject:Zb,all:Ob,rethrow:Vb,defer:Wb,EventTarget:qb,configure:c,async:nb};"function"==typeof define&&define.amd?define(function(){return jc}):"undefined"!=typeof module&&module.exports?module.exports=jc:"undefined"!=typeof this&&(this.RSVP=jc)}).call(this),function(a,b){"use strict";var c=b.head||b.getElementsByTagName("head")[0],d="basket-",e=5e3,f=[],g=function(a,b){try{return localStorage.setItem(d+a,JSON.stringify(b)),!0}catch(c){if(c.name.toUpperCase().indexOf("QUOTA")>=0){var e,f=[];for(e in localStorage)0===e.indexOf(d)&&f.push(JSON.parse(localStorage[e]));return f.length?(f.sort(function(a,b){return a.stamp-b.stamp}),basket.remove(f[0].key),g(a,b)):void 0}return}},h=function(a){var b=new RSVP.Promise(function(b,c){var d=new XMLHttpRequest;d.open("GET",a),d.onreadystatechange=function(){4===d.readyState&&(200===d.status||0===d.status&&d.responseText?b({content:d.responseText,type:d.getResponseHeader("content-type")}):c(new Error(d.statusText)))},setTimeout(function(){d.readyState<4&&d.abort()},basket.timeout),d.send()});return b},i=function(a){return h(a.url).then(function(b){var c=j(a,b);return a.skipCache||g(a.key,c),c})},j=function(a,b){var c=+new Date;return a.data=b.content,a.originalType=b.type,a.type=a.type||b.type,a.skipCache=a.skipCache||!1,a.stamp=c,a.expire=c+60*(a.expire||e)*60*1e3,a},k=function(a,b){return!a||a.expire-+new Date<0||b.unique!==a.unique||basket.isValidItem&&!basket.isValidItem(a,b)},l=function(a){var b,c,d;if(a.url)return a.key=a.key||a.url,b=basket.get(a.key),a.execute=a.execute!==!1,d=k(b,a),a.live||d?(a.unique&&(a.url+=(a.url.indexOf("?")>0?"&":"?")+"basket-unique="+a.unique),c=i(a),a.live&&!d&&(c=c.then(function(a){return a},function(){return b}))):(b.type=a.type||b.originalType,b.execute=a.execute,c=new RSVP.Promise(function(a){a(b)})),c},m=function(a){var d=b.createElement("script");d.defer=!0,d.text=a.data,c.appendChild(d)},n={"default":m},o=function(a){return a.type&&n[a.type]?n[a.type](a):n["default"](a)},p=function(a){return a.map(function(a){return a.execute&&o(a),a})},q=function(){var a,b,c=[];for(a=0,b=arguments.length;b>a;a++)c.push(l(arguments[a]));return RSVP.all(c)},r=function(){var a=q.apply(null,arguments),b=this.then(function(){return a}).then(p);return b.thenRequire=r,b};a.basket={require:function(){for(var a=0,b=arguments.length;b>a;a++)arguments[a].execute=arguments[a].execute!==!1,arguments[a].once&&f.indexOf(arguments[a].url)>=0?arguments[a].execute=!1:arguments[a].execute!==!1&&f.indexOf(arguments[a].url)<0&&f.push(arguments[a].url);var c=q.apply(null,arguments).then(p);return c.thenRequire=r,c},remove:function(a){return localStorage.removeItem(d+a),this},get:function(a){var b=localStorage.getItem(d+a);try{return JSON.parse(b||"false")}catch(c){return!1}},clear:function(a){var b,c,e=+new Date;for(b in localStorage)c=b.split(d)[1],c&&(!a||this.get(c).expire<=e)&&this.remove(c);return this},isValidItem:null,timeout:5e3,addHandler:function(a,b){Array.isArray(a)||(a=[a]),a.forEach(function(a){n[a]=b})},removeHandler:function(a){basket.addHandler(a,void 0)}},basket.clear(!0)}(this,document);
11 | //# sourceMappingURL=basket.full.min.js.map
--------------------------------------------------------------------------------
/test/fixtures/backbone-min.js:
--------------------------------------------------------------------------------
1 | // Backbone.js 0.9.2
2 |
3 | // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
4 | // Backbone may be freely distributed under the MIT license.
5 | // For all details and documentation:
6 | // http://backbonejs.org
7 | (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=
8 | {});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=
9 | 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=
10 | {};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==
11 | 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:
12 | 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)};
13 | 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,
14 | 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();
15 | 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=
16 | {};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||
17 | !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);
18 | 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('').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
30 | this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
31 | stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
32 | function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
33 | this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
34 | f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
35 | for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b 0, 'Sending basket unique parameter' );
278 |
279 | start();
280 | });
281 | });
282 | });
283 |
284 |
285 | asyncTest( 'store data using file-versioning (same release)', 2, function() {
286 | basket
287 | .require({ url: 'fixtures/stamp-script.js', unique: 123 })
288 | .then(function() {
289 | var stamp = basket.get('fixtures/stamp-script.js').stamp;
290 | ok( basket.get('fixtures/stamp-script.js'), 'Data exists in localStorage' );
291 |
292 | basket
293 | .require({ url: 'fixtures/stamp-script.js', unique: 123 })
294 | .then(function() {
295 | var stampAfter = basket.get('fixtures/stamp-script.js').stamp;
296 | ok( stamp === stampAfter, 'Data retrieved from server' );
297 |
298 | start();
299 | });
300 | });
301 | });
302 |
303 |
304 | asyncTest( 'store data using file-versioning (different release)', 3, function() {
305 | basket
306 | .require({ url: 'fixtures/stamp-script.js', unique: 123 })
307 | .then(function() {
308 | var stamp = basket.get('fixtures/stamp-script.js').stamp;
309 | ok( basket.get('fixtures/stamp-script.js'), 'Data exists in localStorage' );
310 |
311 | basket
312 | .require({ url: 'fixtures/stamp-script.js', unique: 456 })
313 | .then(function() {
314 | var req = basket.get('fixtures/stamp-script.js');
315 | ok( stamp !== req.stamp, 'Data retrieved from server' );
316 | ok( req.url.indexOf('basket-unique=456') > 0, 'Sending basket unique parameter' );
317 | start();
318 | });
319 | });
320 | });
321 |
322 |
323 | asyncTest( 'remove oldest script in localStorage when Quote Exceeded', 2, function() {
324 | var i = 0;
325 | var l = 10;
326 |
327 | (function add() {
328 | // Try add script in localStorage
329 | basket
330 | .require({ url: 'fixtures/largeScript.js', key: 'largeScript' + i })
331 | .then(function() {
332 | if ( i < l ) {
333 | // add one more file
334 | add( ++i );
335 | } else {
336 | // check if first script added was removed from localStorage
337 | ok( !basket.get( 'largeScript0' ) , 'First Script deleted' );
338 | // check if the last script added still on localStorage
339 | ok( basket.get( 'largeScript10' ) , 'Last Script still alive' );
340 | start();
341 | }
342 | });
343 | })();
344 | });
345 |
346 | /*
347 | asyncTest( 'file is larger than quota limit ', 2, function() {
348 | basket
349 | .require({ url: 'fixtures/largeScript.js', key: 'largeScript0' }, { url: 'fixtures/largeScript.js', key: 'largeScript1' })
350 | .thenRequire({ url: 'fixtures/veryLargeScript.js', key: 'largeScript2' })
351 | .then(function() {
352 | // check if scripts added was removed from localStorage
353 | ok( !basket.get( 'largeScript0' ) , 'First Script deleted' );
354 | ok( !basket.get( 'largeScript1' ) , 'Second Script deleted' );
355 | // check if the last script added still on localStorage
356 | // TODO: Test is now failing in Chrome due to an anomoly,
357 | // but passes in Safari. Investigate later.
358 | // ok( !basket.get( 'largeScript2' ) , 'Last Script not added' );
359 | start();
360 | });
361 | });*/
362 |
363 | asyncTest( 'non-existant file causes error handler to be called', 2, function() {
364 | basket
365 | .require({ url: 'non-existant.js' })
366 | .then(function() {
367 | ok( false, 'The success callback should not be called' );
368 | start();
369 | }, function(error) {
370 | ok( error, 'Error callback called' );
371 | ok( !basket.get( 'non-existant.js' ), 'No cache entry for missing file' );
372 | start();
373 | });
374 | });
375 |
376 | asyncTest( 'handle the case where localStorage contains something we did not expect', 2, function() {
377 | localStorage.setItem( 'basket-test', 'An invalid JSON string' );
378 | basket
379 | .require({ url: 'fixtures/jquery.min.js', key: 'test' })
380 | .then(function() {
381 | start();
382 | ok( basket.get( 'test' ), 'successfully retrieved the script' );
383 | ok( basket.get( 'test' ).key === 'test', 'got a valid cache object' );
384 | });
385 | });
386 |
387 | asyncTest( 'chaining with thenRequire', 3, function() {
388 | basket.clear();
389 | basket
390 | .require({ url: 'fixtures/first.js', key: 'first' })
391 | .thenRequire({ url: 'fixtures/second.js', key: 'second' })
392 | .then(function() {
393 | start();
394 | ok( basket.get( 'first' ), 'first script loaded' );
395 | ok( basket.get( 'second' ), 'second script loaded' );
396 | ok( basket.order === 'firstsecond', 'scripts loaded in correct order' );
397 | }, function() {
398 | start();
399 | ok( false, 'error handler called unexpectedly' );
400 | });
401 | });
402 |
403 | asyncTest( 'file is fetched from server even if it exists when isValidItem answers no', 2, function() {
404 | basket
405 | .require({ url: 'fixtures/stamp-script.js'})
406 | .then(function() {
407 | var stamp = basket.get('fixtures/stamp-script.js').stamp;
408 | ok( basket.get('fixtures/stamp-script.js'), 'Data exists in localStorage' );
409 | basket.isValidItem = function() {
410 | return false;
411 | };
412 | basket
413 | .require({ url: 'fixtures/stamp-script.js' })
414 | .then(function() {
415 | var stampAfter = basket.get('fixtures/stamp-script.js').stamp;
416 | ok( stamp !== stampAfter, 'Data retrieved from server' );
417 |
418 | start();
419 | });
420 | });
421 | });
422 |
423 | asyncTest( 'when first file fails, second file is fetched but not executed', 3, function() {
424 | var server = sinon.fakeServer.create();
425 | basket.first = basket.second = 0;
426 |
427 | server.respondWith( 'GET', '/second.js', [ 200, { 'Content-Type': 'text/javascript' }, 'basket.second = 1;' ] );
428 |
429 | basket.require({ url: '/first.js' })
430 | .thenRequire({ url: '/second.js' })
431 | .then( function() {},
432 | function() {
433 | ok( !basket.get( '/first.js' ), 'first script failed to load' );
434 | ok( basket.get( '/second.js' ), 'second script was loaded and stored' );
435 | ok( basket.second === 0, 'second script did not execute' );
436 |
437 | start();
438 | server.restore();
439 | });
440 |
441 | server.respond();
442 | });
443 |
444 | asyncTest( 'second file is fetched early but executes later', 6, function() {
445 | var server = sinon.fakeServer.create();
446 | basket.first = basket.second = 0;
447 |
448 |
449 | var firstPromise = basket.require({ url: '/first.js' });
450 | firstPromise.then( function() {
451 | ok( basket.get( '/second.js' ), 'second script was already loaded and stored' );
452 | ok( basket.first === 1, 'first script should have been executed' );
453 | ok( basket.second === 0, 'second script should not have been executed yet' );
454 | });
455 |
456 | firstPromise
457 | .thenRequire({ url: '/second.js' })
458 | .then( function() {
459 | ok( basket.first === 1, 'first script is eventually executed' );
460 | ok( basket.second === 2, 'second script is eventually executed second' );
461 |
462 | start();
463 | server.restore();
464 | });
465 |
466 | ok( server.requests.length === 2, 'Both requests have been made' );
467 |
468 | server.requests[ 1 ].respond( 200, { 'Content-Type': 'text/javascript' }, 'basket.second = basket.first + 1;' );
469 |
470 | setTimeout( function() {
471 | server.requests[ 0 ].respond( 200, { 'Content-Type': 'text/javascript' }, 'basket.first = 1;' );
472 | }, 50);
473 | });
474 |
475 | test( 'with thenRequire all requests fired immediately', 1, function() {
476 | var server = sinon.fakeServer.create();
477 |
478 | basket
479 | .require({ url: '/first.js' })
480 | .thenRequire({ url: '/second.js' })
481 | .thenRequire({ url: '/third.js' });
482 |
483 | ok( server.requests.length === 3, 'all requests were fired' );
484 |
485 | server.restore();
486 | });
487 |
488 | asyncTest( 'the type of the stored object is the Content-Type of the resource', 4, function() {
489 | basket.clear();
490 |
491 | var server = sinon.fakeServer.create();
492 |
493 | server.respondWith( 'GET', '/example.txt', [ 200, { 'Content-Type': 'text/plain' }, 'Some text' ] );
494 | server.respondWith( 'GET', '/example.js', [ 200, { 'Content-Type': 'text/javascript' }, 'Some JavaScript' ] );
495 | server.respondWith( 'GET', '/example.xml', [ 200, { 'Content-Type': 'application/xml' }, 'Some XML' ] );
496 | server.respondWith( 'GET', '/example.json', [ 200, { 'Content-Type': 'application/json' }, '["some JSON"]' ] );
497 |
498 | // Without execute: false, the default handler will try to execute all of
499 | // these files as JS, leading to Syntax Errors being reported.
500 | basket.require({ url: '/example.txt', execute: false }, { url: '/example.js', execute: false }, { url: '/example.xml', execute: false }, { url: '/example.json', execute: false })
501 | .then( function() {
502 | ok( basket.get( '/example.txt' ).type === 'text/plain', 'text file had correct type' );
503 | ok( basket.get( '/example.js' ).type === 'text/javascript', 'javascript file had correct type' );
504 | ok( basket.get( '/example.xml' ).type === 'application/xml', 'xml file had correct type' );
505 | ok( basket.get( '/example.json' ).type === 'application/json', 'json file had correct type' );
506 |
507 | start();
508 | server.restore();
509 | });
510 |
511 | server.respond();
512 | });
513 |
514 | asyncTest( 'the type of the stored object can be overriden at original require time', 1, function() {
515 | basket.clear();
516 |
517 | var server = sinon.fakeServer.create();
518 |
519 | server.respondWith( 'GET', '/example.json', [ 200, { 'Content-Type': 'application/json' }, '["some JSON"]' ] );
520 |
521 | basket.require({ url: '/example.json', execute: false, type: 'misc/other' })
522 | .then( function() {
523 | ok( basket.get( '/example.json' ).type === 'misc/other', 'json file had overriden type' );
524 |
525 | start();
526 | server.restore();
527 | });
528 |
529 | server.respond();
530 | });
531 |
532 | asyncTest( 'different types can be handled separately', 1, function() {
533 | var text = 'some example text';
534 | var server = sinon.fakeServer.create();
535 |
536 | basket.clear();
537 | basket.addHandler( 'text/plain', function( obj ) {
538 | ok( obj.data === text, 'the text/plain handler was used' );
539 | start();
540 | server.restore();
541 | basket.removeHandler( 'text/plain' );
542 | });
543 |
544 | server.respondWith( 'GET', '/example.txt', [ 200, { 'Content-Type': 'text/plain' }, text ] );
545 |
546 | basket.require({ url: '/example.txt' });
547 |
548 | server.respond();
549 | });
550 |
551 | asyncTest( 'handlers can be removed', 1, function() {
552 | var js = '// has to be valid JS to avoid a Syntax Error';
553 | var handled = 0;
554 | var server = sinon.fakeServer.create();
555 |
556 | basket.clear();
557 | basket.addHandler( 'text/plain', function() {
558 | handled++;
559 | basket.removeHandler( 'text/plain' );
560 | });
561 |
562 | server.respondWith( 'GET', '/example.js', [ 200, { 'Content-Type': 'text/plain' }, js ] );
563 | server.respondWith( 'GET', '/example2.js', [ 200, { 'Content-Type': 'text/plain' }, js ] );
564 |
565 | basket.require({ url: '/example.js' })
566 | .thenRequire({ url: '/example2.js' })
567 | .then( function () {
568 | ok( handled === 1, 'the text/plain handler was only used once' );
569 | start();
570 | server.restore();
571 | });
572 |
573 | server.respond();
574 | });
575 |
576 | asyncTest( 'the same resource can be handled differently', 2, function() {
577 | var server = sinon.fakeServer.create();
578 |
579 | basket.clear();
580 |
581 | basket.addHandler( 'first', function() {
582 | ok( true, 'first handler was called' );
583 | });
584 |
585 | basket.addHandler( 'second', function() {
586 | ok( true, 'second handler was called' );
587 | start();
588 | server.restore();
589 | });
590 |
591 | server.respondWith( 'GET', '/example.txt', [ 200, { 'Content-Type': 'text/plain' }, '' ] );
592 |
593 | basket.require({ url: '/example.txt', type: 'first' })
594 | .thenRequire({ url: '/example.txt', type: 'second' });
595 |
596 | server.respond();
597 | });
598 |
599 | asyncTest( 'type falls back to Content-Type, even if previously overriden', 2, function() {
600 | var server = sinon.fakeServer.create();
601 |
602 | basket.clear();
603 |
604 | basket.addHandler( 'first', function() {
605 | ok( true, 'first handler was called' );
606 | });
607 |
608 | basket.addHandler( 'text/plain', function() {
609 | ok( true, 'text/plain handler was called' );
610 | start();
611 | server.restore();
612 | });
613 |
614 | server.respondWith( 'GET', '/example.txt', [ 200, { 'Content-Type': 'text/plain' }, '' ] );
615 |
616 | basket.require({ url: '/example.txt', type: 'first' })
617 | .thenRequire({ url: '/example.txt' });
618 |
619 | server.respond();
620 | });
621 |
622 | // This test is here to cover the full set of possibilities for this section
623 | // It doesn't really test anything that hasn't been tested elsewhere
624 | asyncTest( 'with live: false, we fallback to the network', 1, function() {
625 | basket.clear();
626 | var server = sinon.fakeServer.create();
627 | server.respondWith( 'GET', '/example.txt', [ 200, { 'Content-Type': 'text/plain' }, 'foo' ] );
628 |
629 | basket.require({ url: '/example.txt', execute: false, live: false })
630 | .then( function() {
631 | ok( basket.get( '/example.txt' ).data === 'foo', 'nothing in the cache so we fetched from the network' );
632 | server.restore();
633 | start();
634 | });
635 |
636 | server.respond();
637 | });
638 |
639 | asyncTest( 'with live: false, we attempt to fetch from the cache first', 1, function() {
640 | basket.clear();
641 | var server = sinon.fakeServer.create();
642 | server.respondWith( 'GET', '/example.txt', [ 200, { 'Content-Type': 'text/plain' }, 'bar' ] );
643 |
644 | // Add the item directly to the cache
645 | localStorage.setItem( 'basket-/example.txt', JSON.stringify( {
646 | url: '/example.txt',
647 | key: '/example.txt',
648 | data: 'foo',
649 | originalType: 'text/plain',
650 | type: 'text/plain',
651 | stamp: +new Date(),
652 | expire: +new Date() + 5000 * 60 * 60 * 1000
653 | }));
654 |
655 | basket.require({ url: '/example.txt', execute: false, live: false })
656 | .then( function() {
657 | ok( basket.get( '/example.txt' ).data === 'foo', 'fetched from the cache rather than getting fresh data from the network' );
658 | server.restore();
659 | start();
660 | });
661 |
662 |
663 | server.respond();
664 | });
665 |
666 | asyncTest( 'with live: true, we attempt to fetch from the network first', 1, function() {
667 | basket.clear();
668 | var server = sinon.fakeServer.create();
669 | server.respondWith( 'GET', '/example.txt', [ 200, { 'Content-Type': 'text/plain' }, 'bar' ] );
670 |
671 | // Add the item directly to the cache
672 | localStorage.setItem( 'basket-/example.txt', JSON.stringify( {
673 | url: '/example.txt',
674 | key: '/example.txt',
675 | data: 'foo',
676 | originalType: 'text/plain',
677 | type: 'text/plain',
678 | stamp: +new Date(),
679 | expire: +new Date() + 5000 * 60 * 60 * 1000
680 | }));
681 |
682 | basket.require({ url: '/example.txt', execute: false, live: true })
683 | .then( function() {
684 | ok( basket.get( '/example.txt' ).data === 'bar', 'fetched from the network even though cache was available' );
685 | server.restore();
686 | start();
687 | });
688 |
689 | server.respond();
690 | });
691 |
692 | asyncTest( 'with live: true, we still store the result in the cache', 1, function() {
693 | basket.clear();
694 | var server = sinon.fakeServer.create();
695 | server.respondWith( 'GET', '/example.txt', [ 200, { 'Content-Type': 'text/plain' }, 'foo' ] );
696 |
697 | basket.require({ url: '/example.txt', execute: false, live: true })
698 | .then( function() {
699 | ok( basket.get( '/example.txt' ), 'result stored in the cache' );
700 | server.restore();
701 | start();
702 | });
703 |
704 | server.respond();
705 | });
706 |
707 | asyncTest( 'with live: true, we fallback to the cache', 2, function() {
708 | // TODO: How to test the navigator.onLine case?
709 | basket.clear();
710 | var server = sinon.fakeServer.create();
711 | var clock = sinon.useFakeTimers();
712 | server.respondWith( 'GET', '/example.txt', [ 200, { 'Content-Type': 'text/plain' }, 'baz' ] );
713 |
714 | // Add the item directly to the cache
715 | localStorage.setItem( 'basket-/example.txt', JSON.stringify( {
716 | url: '/example.txt',
717 | key: '/example.txt',
718 | data: '12345',
719 | originalType: 'text/plain',
720 | type: 'text/plain',
721 | stamp: +new Date(),
722 | expire: +new Date() + 5000 * 60 * 60 * 1000
723 | }));
724 |
725 | ok( basket.get( '/example.txt' ), 'already exists in cache' );
726 |
727 | basket.timeout = 100;
728 | basket.require({ url: '/example.txt', execute: false, live: true })
729 | .then( function() {
730 | ok( basket.get( '/example.txt' ).data === '12345', 'server timed out, so fetched from cache' );
731 | server.restore();
732 | clock.restore();
733 | start();
734 | }, function () {
735 | ok( false, 'the require failed due to lack of network, but should have used the cache' );
736 | server.restore();
737 | clock.restore();
738 | start();
739 | });
740 |
741 | clock.tick(6000);
742 | server.respond();
743 | basket.timeout = 5000;
744 | });
745 |
746 | asyncTest( 'with skipCache: true, we do not cache data', 1, function() {
747 | basket
748 | .require({ url: 'fixtures/jquery.min.js', skipCache: true })
749 | .then(function() {
750 | ok( !basket.get('fixtures/jquery.min.js'), 'Data does not exist in localStorage' );
751 |
752 | start();
753 | });
754 | });
755 |
756 | asyncTest( 'execute a cached script when execute: true', 2, function() {
757 | var cancel = setTimeout(function() {
758 | ok( false, 'Callback never invoked' );
759 | start();
760 | }, 2500);
761 |
762 | function requireScript(execute, cb) {
763 | basket.require(
764 | { url: 'fixtures/executefalse.js', execute: execute }
765 | )
766 | .then(cb);
767 | }
768 |
769 | requireScript( false, function() {
770 | clearTimeout( cancel );
771 |
772 | ok( typeof basket.executed === 'undefined', 'None-cached script was not executed' );
773 |
774 | requireScript( true, function() {
775 | ok( basket.executed === true, 'Cached script executed' );
776 |
777 | start();
778 | });
779 | });
780 | });
781 |
--------------------------------------------------------------------------------