From 112ac3510233267ada38b11a2d5ea91e5934097a Mon Sep 17 00:00:00 2001 From: Jeremy Brown Date: Sun, 15 Mar 2015 14:20:11 -0500 Subject: [PATCH] Convert to EmberCLI 0.1.15 Addon --- .bowerrc | 3 +- .editorconfig | 33 + .ember-cli | 9 + .gitignore | 30 +- .jshintrc | 32 + .npmignore | 12 + .travis.yml | 20 + Brocfile.js | 21 + Gruntfile.js | 66 - LICENSE => LICENSE.md | 2 +- README.md | 56 +- addon/.gitkeep | 0 addon/adapters/application.js | 181 + addon/file.js | 3 + addon/geopoint.js | 3 + addon/initializers/initialize.js | 24 + addon/models/parse-user.js | 76 + addon/serializers/application.js | 232 + .../transforms/date.js | 24 +- addon/transforms/file.js | 57 + addon/transforms/geopoint.js | 57 + app/.gitkeep | 0 app/adapters/application.js | 4 + app/file.js | 4 + app/geopoint.js | 4 + app/initializers/initialize.js | 9 + app/serializers/application.js | 4 + app/transforms/date.js | 4 + app/transforms/file.js | 4 + app/transforms/geopoint.js | 4 + bower.json | 25 +- config/environment.js | 5 + dist/ember-parse-adapter.js | 648 -- dist/ember-parse-adapter.min.js | 1 - example.html | 497 -- index.js | 6 + lib/ember-parse-adapter.js | 4 - lib/ember-parse-adapter/adapter.js | 172 - lib/ember-parse-adapter/file.js | 10 - lib/ember-parse-adapter/geo-point.js | 10 - lib/ember-parse-adapter/parse-user.js | 68 - lib/ember-parse-adapter/serializer.js | 218 - lib/ember-parse-adapter/transforms/file.js | 49 - .../transforms/geo-point.js | 49 - lib/ember.js | 14 - lib/setup-container.js | 8 - package.json | 83 +- test/ember-parse-adapter/adapter-test.js | 371 - test/ember-parse-adapter/parse-user-test.js | 297 - test/ember-parse-adapter/serializer-test.js | 96 - .../transforms/date-test.js | 33 - .../transforms/file-test.js | 35 - .../transforms/geo-point-test.js | 30 - test/index.html | 53 - test/lib/qunit-1.10.0.css | 235 - test/lib/qunit-1.10.0.js | 1977 ----- test/lib/qunit-1.11.0.css | 244 - test/lib/qunit-1.11.0.js | 2152 ----- test/support/async.js | 20 - test/support/build-container.js | 6 - test/support/expects.js | 24 - test/support/pending.js | 3 - testem.json | 11 + tests/.jshintrc | 56 + tests/dummy/app/app.js | 16 + tests/dummy/app/components/.gitkeep | 0 tests/dummy/app/controllers/.gitkeep | 0 tests/dummy/app/controllers/belongsto.js | 32 + tests/dummy/app/controllers/facebook.js | 39 + tests/dummy/app/controllers/hasmany.js | 29 + tests/dummy/app/controllers/login.js | 36 + tests/dummy/app/controllers/signup.js | 37 + tests/dummy/app/controllers/simple.js | 31 + tests/dummy/app/helpers/.gitkeep | 0 tests/dummy/app/index.html | 28 + tests/dummy/app/models/.gitkeep | 0 tests/dummy/app/models/donut.js | 7 + tests/dummy/app/models/hole.js | 6 + tests/dummy/app/models/simple.js | 7 + tests/dummy/app/models/stripe.js | 6 + tests/dummy/app/models/tiger.js | 7 + tests/dummy/app/router.js | 17 + tests/dummy/app/routes/.gitkeep | 0 tests/dummy/app/routes/belongsto.js | 7 + tests/dummy/app/routes/hasmany.js | 7 + tests/dummy/app/routes/simple.js | 7 + tests/dummy/app/styles/.gitkeep | 0 tests/dummy/app/styles/app.css | 12 + tests/dummy/app/templates/application.hbs | 28 + tests/dummy/app/templates/belongsto.hbs | 22 + tests/dummy/app/templates/components/.gitkeep | 0 tests/dummy/app/templates/facebook.hbs | 52 + tests/dummy/app/templates/hasmany.hbs | 29 + tests/dummy/app/templates/login.hbs | 46 + tests/dummy/app/templates/signup.hbs | 55 + tests/dummy/app/templates/simple.hbs | 29 + tests/dummy/app/views/.gitkeep | 0 tests/dummy/config/environment.js | 49 + tests/dummy/public/crossdomain.xml | 15 + tests/dummy/public/robots.txt | 2 + tests/helpers/resolver.js | 11 + tests/helpers/start-app.js | 19 + tests/index.html | 33 + tests/test-helper.js | 6 + tests/unit/.gitkeep | 0 tests/unit/adapters/application-test.js | 460 ++ tests/unit/initializers/initialize-test.js | 50 + tests/unit/models/parse-user-test.js | 305 + tests/unit/serializers/application-test.js | 97 + tests/unit/transforms/date-test.js | 35 + tests/unit/transforms/file-test.js | 44 + tests/unit/transforms/geopoint-test.js | 39 + vendor/.gitignore | 1 - vendor/.gitkeep | 0 vendor/foundation.min.css | 1 - vendor/foundation.min.js | 10 - vendor/parse-1.1.15.js | 7052 ----------------- 117 files changed, 2751 insertions(+), 14558 deletions(-) create mode 100644 .editorconfig create mode 100644 .ember-cli create mode 100644 .jshintrc create mode 100644 .npmignore create mode 100644 .travis.yml create mode 100644 Brocfile.js delete mode 100644 Gruntfile.js rename LICENSE => LICENSE.md (94%) create mode 100644 addon/.gitkeep create mode 100644 addon/adapters/application.js create mode 100644 addon/file.js create mode 100644 addon/geopoint.js create mode 100644 addon/initializers/initialize.js create mode 100644 addon/models/parse-user.js create mode 100644 addon/serializers/application.js rename {lib/ember-parse-adapter => addon}/transforms/date.js (60%) create mode 100644 addon/transforms/file.js create mode 100644 addon/transforms/geopoint.js create mode 100644 app/.gitkeep create mode 100644 app/adapters/application.js create mode 100644 app/file.js create mode 100644 app/geopoint.js create mode 100644 app/initializers/initialize.js create mode 100644 app/serializers/application.js create mode 100644 app/transforms/date.js create mode 100644 app/transforms/file.js create mode 100644 app/transforms/geopoint.js create mode 100644 config/environment.js delete mode 100644 dist/ember-parse-adapter.js delete mode 100644 dist/ember-parse-adapter.min.js delete mode 100644 example.html create mode 100644 index.js delete mode 100644 lib/ember-parse-adapter.js delete mode 100644 lib/ember-parse-adapter/adapter.js delete mode 100644 lib/ember-parse-adapter/file.js delete mode 100644 lib/ember-parse-adapter/geo-point.js delete mode 100644 lib/ember-parse-adapter/parse-user.js delete mode 100644 lib/ember-parse-adapter/serializer.js delete mode 100644 lib/ember-parse-adapter/transforms/file.js delete mode 100644 lib/ember-parse-adapter/transforms/geo-point.js delete mode 100644 lib/ember.js delete mode 100644 lib/setup-container.js delete mode 100644 test/ember-parse-adapter/adapter-test.js delete mode 100644 test/ember-parse-adapter/parse-user-test.js delete mode 100644 test/ember-parse-adapter/serializer-test.js delete mode 100644 test/ember-parse-adapter/transforms/date-test.js delete mode 100644 test/ember-parse-adapter/transforms/file-test.js delete mode 100644 test/ember-parse-adapter/transforms/geo-point-test.js delete mode 100644 test/index.html delete mode 100644 test/lib/qunit-1.10.0.css delete mode 100644 test/lib/qunit-1.10.0.js delete mode 100644 test/lib/qunit-1.11.0.css delete mode 100644 test/lib/qunit-1.11.0.js delete mode 100644 test/support/async.js delete mode 100644 test/support/build-container.js delete mode 100644 test/support/expects.js delete mode 100644 test/support/pending.js create mode 100644 testem.json create mode 100644 tests/.jshintrc create mode 100644 tests/dummy/app/app.js create mode 100644 tests/dummy/app/components/.gitkeep create mode 100644 tests/dummy/app/controllers/.gitkeep create mode 100644 tests/dummy/app/controllers/belongsto.js create mode 100644 tests/dummy/app/controllers/facebook.js create mode 100644 tests/dummy/app/controllers/hasmany.js create mode 100644 tests/dummy/app/controllers/login.js create mode 100644 tests/dummy/app/controllers/signup.js create mode 100644 tests/dummy/app/controllers/simple.js create mode 100644 tests/dummy/app/helpers/.gitkeep create mode 100644 tests/dummy/app/index.html create mode 100644 tests/dummy/app/models/.gitkeep create mode 100644 tests/dummy/app/models/donut.js create mode 100644 tests/dummy/app/models/hole.js create mode 100644 tests/dummy/app/models/simple.js create mode 100644 tests/dummy/app/models/stripe.js create mode 100644 tests/dummy/app/models/tiger.js create mode 100644 tests/dummy/app/router.js create mode 100644 tests/dummy/app/routes/.gitkeep create mode 100644 tests/dummy/app/routes/belongsto.js create mode 100644 tests/dummy/app/routes/hasmany.js create mode 100644 tests/dummy/app/routes/simple.js create mode 100644 tests/dummy/app/styles/.gitkeep create mode 100644 tests/dummy/app/styles/app.css create mode 100644 tests/dummy/app/templates/application.hbs create mode 100644 tests/dummy/app/templates/belongsto.hbs create mode 100644 tests/dummy/app/templates/components/.gitkeep create mode 100644 tests/dummy/app/templates/facebook.hbs create mode 100644 tests/dummy/app/templates/hasmany.hbs create mode 100644 tests/dummy/app/templates/login.hbs create mode 100644 tests/dummy/app/templates/signup.hbs create mode 100644 tests/dummy/app/templates/simple.hbs create mode 100644 tests/dummy/app/views/.gitkeep create mode 100644 tests/dummy/config/environment.js create mode 100644 tests/dummy/public/crossdomain.xml create mode 100644 tests/dummy/public/robots.txt create mode 100644 tests/helpers/resolver.js create mode 100644 tests/helpers/start-app.js create mode 100644 tests/index.html create mode 100644 tests/test-helper.js create mode 100644 tests/unit/.gitkeep create mode 100644 tests/unit/adapters/application-test.js create mode 100644 tests/unit/initializers/initialize-test.js create mode 100644 tests/unit/models/parse-user-test.js create mode 100644 tests/unit/serializers/application-test.js create mode 100644 tests/unit/transforms/date-test.js create mode 100644 tests/unit/transforms/file-test.js create mode 100644 tests/unit/transforms/geopoint-test.js delete mode 100644 vendor/.gitignore create mode 100644 vendor/.gitkeep delete mode 100644 vendor/foundation.min.css delete mode 100644 vendor/foundation.min.js delete mode 100644 vendor/parse-1.1.15.js diff --git a/.bowerrc b/.bowerrc index 6866ac2..959e169 100644 --- a/.bowerrc +++ b/.bowerrc @@ -1,3 +1,4 @@ { - "directory": "vendor" + "directory": "bower_components", + "analytics": false } diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2fe4874 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,33 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.js] +indent_style = space +indent_size = 2 + +[*.hbs] +indent_style = space +indent_size = 2 + +[*.css] +indent_style = space +indent_size = 2 + +[*.html] +indent_style = space +indent_size = 2 + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/.ember-cli b/.ember-cli new file mode 100644 index 0000000..ee64cfe --- /dev/null +++ b/.ember-cli @@ -0,0 +1,9 @@ +{ + /** + Ember CLI sends analytics information by default. The data is completely + anonymous, but there are times when you might want to disable this behavior. + + Setting `disableAnalytics` to true will prevent any data from being sent. + */ + "disableAnalytics": false +} diff --git a/.gitignore b/.gitignore index 35d5a43..86fceae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,17 @@ -lib-cov -*.seed -*.log -*.csv -*.dat -*.out -*.pid -*.gz -*.swp -pids -logs -results +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp + +# dependencies +/node_modules +/bower_components + +# misc +/.sass-cache +/connect.lock +/coverage/* +/libpeerconnection.log npm-debug.log -node_modules -.DS_Store \ No newline at end of file +testem.log diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..08096ef --- /dev/null +++ b/.jshintrc @@ -0,0 +1,32 @@ +{ + "predef": [ + "document", + "window", + "-Promise" + ], + "browser": true, + "boss": true, + "curly": true, + "debug": false, + "devel": true, + "eqeqeq": true, + "evil": true, + "forin": false, + "immed": false, + "laxbreak": false, + "newcap": true, + "noarg": true, + "noempty": false, + "nonew": false, + "nomen": false, + "onevar": false, + "plusplus": false, + "regexp": false, + "undef": true, + "sub": true, + "strict": false, + "white": false, + "eqnull": true, + "esnext": true, + "unused": true +} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..0533b91 --- /dev/null +++ b/.npmignore @@ -0,0 +1,12 @@ +bower_components/ +tests/ + +.bowerrc +.editorconfig +.ember-cli +.travis.yml +.npmignore +**/.gitkeep +bower.json +Brocfile.js +testem.json diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..cf23938 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +--- +language: node_js + +sudo: false + +cache: + directories: + - node_modules + +before_install: + - "npm config set spin false" + - "npm install -g npm@^2" + +install: + - npm install -g bower + - npm install + - bower install + +script: + - npm test diff --git a/Brocfile.js b/Brocfile.js new file mode 100644 index 0000000..042a64d --- /dev/null +++ b/Brocfile.js @@ -0,0 +1,21 @@ +/* jshint node: true */ +/* global require, module */ + +var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); + +var app = new EmberAddon(); + +// Use `app.import` to add additional libraries to the generated +// output files. +// +// If you need to use different assets in different +// environments, specify an object as the first parameter. That +// object's keys should be the environment name and the values +// should be the asset to use in that environment. +// +// If the library that you are including contains AMD or ES6 +// modules that you would like to import into your application +// please specify an object with the list of modules as keys +// along with the exports of each module as its value. + +module.exports = app.toTree(); diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index ae89068..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,66 +0,0 @@ -module.exports = function(grunt){ - 'use strict'; - grunt.initConfig({ - - pkg: grunt.file.readJSON('package.json'), - - jshint: { - all: ['lib/**/*.js'] - }, - - concat: { - dist: { - src: [ - 'lib/ember-parse-adapter.js', - 'lib/ember-parse-adapter/serializer.js', - 'lib/ember-parse-adapter/adapter.js', - 'lib/ember-parse-adapter/parse-user.js', - 'lib/ember-parse-adapter/geo-point.js', - 'lib/ember-parse-adapter/file.js', - 'lib/ember-parse-adapter/transforms/geo-point.js', - 'lib/ember-parse-adapter/transforms/file.js', - 'lib/ember-parse-adapter/transforms/date.js', - 'lib/setup-container.js', - 'lib/ember.js' - ], - dest: 'dist/ember-parse-adapter.js' - } - }, - - uglify: { - dist: { - src: 'dist/ember-parse-adapter.js', - dest: 'dist/ember-parse-adapter.min.js' - } - }, - - qunit: { - options: { - timeout: 10000 - }, - all: { - options: { - urls: ['http://localhost:8000/test/index.html'] - } - } - }, - - connect: { - server: { - port: 8000, - base: '.' - } - } - - }); - - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-qunit'); - grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - - grunt.registerTask('test', ['connect', 'qunit']); - grunt.registerTask('default', ['jshint', 'concat', 'uglify']); - -}; diff --git a/LICENSE b/LICENSE.md similarity index 94% rename from LICENSE rename to LICENSE.md index c4fd459..d04acf2 100644 --- a/LICENSE +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Clint Hill (clint.hill@gmail.com) +Copyright (c) 2014, 2015 Clint Hill (clint.hill@gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 686c1e7..f367f33 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ the [Parse REST API](https://parse.com/docs/rest). This is a full Ember implementation against the Parse REST API without the use of the Parse JavaScript SDK. -The [example.html](example.html) file contains some example usage. +The demo application contains some example usage. Features -------- @@ -36,22 +36,22 @@ Get Started ----------- You'll want to get an account at [Parse](https://parse.com). After this you will -be provided with three keys: +be provided with two keys: * Application ID -* JavaScript Key * REST API Key -You will need each of these to configure the ParseAdapter. +You will need each of these to configure the ParseAdapter via entries in the `config/environment.js` file: ```javascript -var App = Ember.Application.create(); - -App.ApplicationAdapter = EmberParseAdapter.Adapter.extend({ - applicationId: '', - restApiId: '', - javascriptId: '' -}); +var ENV = { + ... + + APP: { + applicationId: '', + restApiId: '' + } +}; ``` Any model using this adapter will be stored on Parse. Create models @@ -71,29 +71,35 @@ App.Post = DS.Model.extend({ }); ``` +Demo +---- + +## Installation + +* `git clone` this repository +* `npm install` +* `bower install` + +## Running + +* `ember server` +* View the demo at http://localhost:4200 + +For more information on using ember-cli, visit [http://www.ember-cli.com/](http://www.ember-cli.com/). + + Roadmap ------- -* Move to ES6 modules. * Bring back relationships via Parse relation type? * Bytes type? * Parse Roles implementation. * Parse ACL implementation. -Dev Notes ---------- - -To get started with the codebase, be sure to run the standard dependency installs: - -``` -npm install -bower install -``` -Now you have several grunt tasks available: +How to use this addon in your application +----------------------------------------- ``` -grunt # -> builds the files into dist/ -grunt test # -> Runs the tests in the console -grunt connect:server:keepalive # -> Runs the test server, visit http://localhost:8000/test/ +ember install:addon ember-parse-adapter ``` diff --git a/addon/.gitkeep b/addon/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/addon/adapters/application.js b/addon/adapters/application.js new file mode 100644 index 0000000..5ba6e8c --- /dev/null +++ b/addon/adapters/application.js @@ -0,0 +1,181 @@ +import Ember from 'ember'; +import DS from 'ember-data'; + +export default DS.RESTAdapter.extend({ + + defaultSerializer: '-parse', + + init: function(){ + this._super(); + + this.set( 'headers', { + 'X-Parse-Application-Id' : Ember.get( this, 'applicationId' ), + 'X-Parse-REST-API-Key' : Ember.get( this, 'restApiId' ) + }); + }, + + host: 'https://api.parse.com', + + namespace: '1', + + classesPath: 'classes', + + pathForType: function( type ) { + if ( 'parseUser' === type ) { + return 'users'; + } else if ( 'login' === type ) { + return 'login'; + } else { + return this.classesPath + '/' + this.parsePathForType( type ); + } + }, + + // Using TitleStyle is recommended by Parse + // @TODO: test + parsePathForType: function( type ) { + return Ember.String.capitalize( Ember.String.camelize( type ) ); + }, + + /** + * Because Parse doesn't return a full set of properties on the + * responses to updates, we want to perform a merge of the response + * properties onto existing data so that the record maintains + * latest data. + */ + createRecord: function( store, type, record ) { + var serializer = store.serializerFor( type.typeKey ), + data = {}, + adapter = this; + + serializer.serializeIntoHash( data, type, record, { includeId: true } ); + + return new Ember.RSVP.Promise( function( resolve, reject ) { + adapter.ajax( adapter.buildURL( type.typeKey ), 'POST', { data: data } ).then( + function( json ) { + var completed = Ember.merge( data, json ); + resolve( completed ); + }, + function( reason ) { + reject( reason.responseJSON ); + } + ); + }); + }, + + /** + * Because Parse doesn't return a full set of properties on the + * responses to updates, we want to perform a merge of the response + * properties onto existing data so that the record maintains + * latest data. + */ + updateRecord: function(store, type, record) { + var serializer = store.serializerFor( type.typeKey ), + id = record.get( 'id' ), + sendDeletes = false, + deleteds = {}, + data = {}, + adapter = this; + + serializer.serializeIntoHash(data, type, record); + + type.eachRelationship(function( key ) { + if ( data[key] && data[key].deleteds ) { + deleteds[key] = data[key].deleteds; + delete data[key].deleteds; + sendDeletes = true; + } + }); + + return new Ember.RSVP.Promise( function( resolve, reject ) { + if ( sendDeletes ) { + adapter.ajax( adapter.buildURL( type.typeKey, id ), 'PUT', { data: deleteds } ).then( + function() { + adapter.ajax( adapter.buildURL( type.typeKey, id ), 'PUT', { data: data } ).then( + function( updates ) { + // This is the essential bit - merge response data onto existing data. + resolve( Ember.merge( data, updates ) ); + }, + function( reason ) { + reject( 'Failed to save parent in relation: ' + reason.response.JSON ); + } + ); + }, + function( reason ) { + reject( reason.responseJSON ); + } + ); + + } else { + adapter.ajax( adapter.buildURL( type.typeKey, id ), 'PUT', { data: data } ).then( + function( json ) { + // This is the essential bit - merge response data onto existing data. + resolve( Ember.merge( data, json ) ); + }, + function( reason ) { + reject( reason.responseJSON ); + } + ); + } + }); + }, + + parseClassName: function (key ) { + return Ember.String.capitalize( key ); + }, + + /** + * Implementation of a hasMany that provides a Relation query for Parse + * objects. + */ + findHasMany: function( store, record, relatedInfo ) { + var query = { + where: { + '$relatedTo': { + 'object': { + '__type' : 'Pointer', + 'className' : this.parseClassName( record.typeKey ), + 'objectId' : record.get( 'id' ) + }, + key: relatedInfo.key + } + } + }; + + // the request is to the related type and not the type for the record. + // the query is where there is a pointer to this record. + return this.ajax( this.buildURL( relatedInfo.type.typeKey ), 'GET', { data: query } ); + }, + + /** + * Implementation of findQuery that automatically wraps query in a + * JSON string. + * + * @example + * this.store.find('comment', { + * where: { + * post: { + * "__type": "Pointer", + * "className": "Post", + * "objectId": post.get('id') + * } + * } + * }); + */ + findQuery: function ( store, type, query ) { + if ( query.where && 'string' !== Ember.typeOf( query.where ) ) { + query.where = JSON.stringify( query.where ); + } + + // Pass to _super() + return this._super( store, type, query ); + }, + + sessionToken: Ember.computed( 'headers.X-Parse-Session-Token', function( key, value ) { + if ( arguments.length < 2 ) { + return this.get( 'headers.X-Parse-Session-Token' ); + } else { + this.set( 'headers.X-Parse-Session-Token', value ); + return value; + } + }) +}); diff --git a/addon/file.js b/addon/file.js new file mode 100644 index 0000000..8974bcb --- /dev/null +++ b/addon/file.js @@ -0,0 +1,3 @@ +import Ember from 'ember'; + +export default Ember.Object.extend(); \ No newline at end of file diff --git a/addon/geopoint.js b/addon/geopoint.js new file mode 100644 index 0000000..8974bcb --- /dev/null +++ b/addon/geopoint.js @@ -0,0 +1,3 @@ +import Ember from 'ember'; + +export default Ember.Object.extend(); \ No newline at end of file diff --git a/addon/initializers/initialize.js b/addon/initializers/initialize.js new file mode 100644 index 0000000..10ab0f2 --- /dev/null +++ b/addon/initializers/initialize.js @@ -0,0 +1,24 @@ +import Adapter from '../adapters/application'; +import Serializer from '../serializers/application'; +import DateTransform from '../transforms/date'; +import FileTransform from '../transforms/file'; +import GeopointTransform from '../transforms/geopoint'; +import ParseUser from '../models/parse-user'; + +/** +@module initializers +@class initialize +*/ +export default function( container, app ) { + Adapter.reopen({ + applicationId : app.get( 'applicationId' ), + restApiId : app.get( 'restApiId' ) + }); + + container.register( 'adapter:-parse', Adapter ); + container.register( 'serializer:-parse', Serializer ); + container.register( 'transform:parse-date', DateTransform ); + container.register( 'transform:parse-file', FileTransform ); + container.register( 'transform:parse-geo-point', GeopointTransform ); + container.register( 'model:parse-user', ParseUser ); +} \ No newline at end of file diff --git a/addon/models/parse-user.js b/addon/models/parse-user.js new file mode 100644 index 0000000..9d6f549 --- /dev/null +++ b/addon/models/parse-user.js @@ -0,0 +1,76 @@ +import Ember from 'ember'; +import DS from 'ember-data'; + +/** + * Parse User object implementation + * + * @type {DS.ParseModel} + */ +var ParseUser = DS.Model.extend({ + username : DS.attr( 'string' ), + password : DS.attr( 'string' ), + email : DS.attr( 'string' ), + emailVerified : DS.attr( 'boolean' ), + sessionToken : DS.attr( 'string' ), + createdAt : DS.attr( 'date' ), + updatedAt : DS.attr( 'date' ) +}); + +ParseUser.reopenClass({ + requestPasswordReset: function( email ) { + var adapter = this.get( 'store' ).adapterFor( this ), + data = { email: email }; + + return adapter.ajax( adapter.buildURL( 'requestPasswordReset' ), 'POST', { data:data } )['catch'] ( + function( response ) { + return Ember.RSVP.reject( response.responseJSON ); + } + ); + }, + + login: function( store, data ) { + var model = this, + adapter = store.adapterFor( model ), + serializer = store.serializerFor( model ); + + if ( Ember.isEmpty( this.typeKey ) ) { + throw new Error( 'Parse login must be called on a model fetched via store.modelFor' ); + } + + return adapter.ajax( adapter.buildURL( 'login' ), 'GET', { data: data } ).then( + function( response ) { + serializer.normalize( model, response ); + var record = store.push( model, response ); + return record; + }, + function( response ) { + return Ember.RSVP.reject( response.responseJSON ); + } + ); + }, + + signup: function( store, data ) { + var model = this, + adapter = store.adapterFor(model), + serializer = store.serializerFor(model); + + if ( Ember.isEmpty( this.typeKey ) ) { + throw new Error( 'Parse signup must be called on a model fetched via store.modelFor' ); + } + + return adapter.ajax( adapter.buildURL( model.typeKey ), 'POST', { data: data } ).then( + function( response ) { + serializer.normalize( model, response ); + response.email = response.email || data.email; + response.username = response.username || data.username; + var record = store.push( model, response ); + return record; + }, + function( response ) { + return Ember.RSVP.reject( response.responseJSON ); + } + ); + } +}); + +export default ParseUser; \ No newline at end of file diff --git a/addon/serializers/application.js b/addon/serializers/application.js new file mode 100644 index 0000000..9a0e375 --- /dev/null +++ b/addon/serializers/application.js @@ -0,0 +1,232 @@ +import Ember from 'ember'; +import DS from 'ember-data'; + +export default DS.RESTSerializer.extend({ + + primaryKey: 'objectId', + + extractArray: function( store, primaryType, payload ) { + var namespacedPayload = {}; + namespacedPayload[ Ember.String.pluralize( primaryType.typeKey ) ] = payload.results; + + return this._super( store, primaryType, namespacedPayload ); + }, + + extractSingle: function( store, primaryType, payload, recordId ) { + var namespacedPayload = {}; + namespacedPayload[ primaryType.typeKey ] = payload; // this.normalize(primaryType, payload); + + return this._super( store, primaryType, namespacedPayload, recordId ); + }, + + typeForRoot: function( key ) { + return Ember.String.dasherize( Ember.String.singularize( key ) ); + }, + + /** + * Because Parse only returns the updatedAt/createdAt values on updates + * we have to intercept it here to assure that the adapter knows which + * record ID we are dealing with (using the primaryKey). + */ + extract: function( store, type, payload, id, requestType ) { + if( id !== null && ( 'updateRecord' === requestType || 'deleteRecord' === requestType ) ) { + payload[ this.get( 'primaryKey' ) ] = id; + } + + return this._super( store, type, payload, id, requestType ); + }, + + /** + * Extracts count from the payload so that you can get the total number + * of records in Parse if you're using skip and limit. + */ + extractMeta: function( store, type, payload ) { + if ( payload && payload.count ) { + store.metaForType( type, { count: payload.count } ); + delete payload.count; + } + }, + + /** + * Special handling for the Date objects inside the properties of + * Parse responses. + */ + normalizeAttributes: function( type, hash ) { + type.eachAttribute( function( key, meta ) { + if ( 'date' === meta.type && 'object' === Ember.typeOf( hash[key] ) && hash[key].iso ) { + hash[key] = hash[key].iso; //new Date(hash[key].iso).toISOString(); + } + }); + + this._super( type, hash ); + }, + + /** + * Special handling of the Parse relation types. In certain + * conditions there is a secondary query to retrieve the "many" + * side of the "hasMany". + */ + normalizeRelationships: function( type, hash ) { + var store = this.get('store'), + serializer = this; + + type.eachRelationship( function( key, relationship ) { + + var options = relationship.options; + + // Handle the belongsTo relationships + if ( hash[key] && 'belongsTo' === relationship.kind ) { + hash[key] = hash[key].objectId; + } + + // Handle the hasMany relationships + if ( hash[key] && 'hasMany' === relationship.kind ) { + + // If this is a Relation hasMany then we need to supply + // the links property so the adapter can async call the + // relationship. + // The adapter findHasMany has been overridden to make use of this. + if ( options.relation ) { + hash.links = {}; + hash.links[key] = { type: relationship.type, key: key }; + } + + if ( options.array ) { + // Parse will return [null] for empty relationships + if ( hash[key].length && hash[key] ) { + hash[key].forEach( function( item, index, items ) { + // When items are pointers we just need the id + // This occurs when request was made without the include query param. + if ( 'Pointer' === item.__type ) { + items[index] = item.objectId; + + } else { + // When items are objects we need to clean them and add them to the store. + // This occurs when request was made with the include query param. + delete item.__type; + delete item.className; + item.id = item.objectId; + delete item.objectId; + item.type = relationship.type; + serializer.normalizeAttributes( relationship.type, item ); + serializer.normalizeRelationships( relationship.type, item ); + store.push( relationship.type, item ); + } + }); + } + } + } + }, this ); + + this._super( type, hash ); + }, + + serializeIntoHash: function( hash, type, record, options ) { + Ember.merge( hash, this.serialize( record, options ) ); + }, + + serializeAttribute: function( record, json, key, attribute ) { + // These are Parse reserved properties and we won't send them. + if ( 'createdAt' === key || + 'updatedAt' === key || + 'emailVerified' === key || + 'sessionToken' === key + ) { + delete json[key]; + + } else { + this._super( record, json, key, attribute ); + } + }, + + serializeBelongsTo: function( record, json, relationship ) { + var key = relationship.key, + belongsTo = record.get( key ); + + if ( belongsTo ) { + // @TODO: Perhaps this is working around a bug in Ember-Data? Why should + // promises be returned here. + if ( belongsTo instanceof DS.PromiseObject ) { + if ( !belongsTo.get('isFulfilled' ) ) { + throw new Error( 'belongsTo values *must* be fulfilled before attempting to serialize them' ); + } + + belongsTo = belongsTo.get( 'content' ); + } + + json[key] = { + '__type' : 'Pointer', + 'className' : this.parseClassName( belongsTo.constructor.typeKey ), + 'objectId' : belongsTo.get( 'id' ) + }; + } + }, + + parseClassName: function( key ) { + if ( 'parseUser' === key) { + return '_User'; + + } else { + return Ember.String.capitalize( Ember.String.camelize( key ) ); + } + }, + + serializeHasMany: function( record, json, relationship ) { + var key = relationship.key, + hasMany = record.get( key ), + options = relationship.options; + + if ( hasMany && hasMany.get( 'length' ) > 0 ) { + json[key] = { 'objects': [] }; + + if ( options.relation ) { + json[key].__op = 'AddRelation'; + } + + if ( options.array ) { + json[key].__op = 'AddUnique'; + } + + hasMany.forEach( function( child ) { + json[key].objects.push({ + '__type' : 'Pointer', + 'className' : child.parseClassName(), + 'objectId' : child.get( 'id' ) + }); + }); + + if ( hasMany._deletedItems && hasMany._deletedItems.length ) { + if ( options.relation ) { + var addOperation = json[key], + deleteOperation = { '__op': 'RemoveRelation', 'objects': [] }; + + hasMany._deletedItems.forEach( function( item ) { + deleteOperation.objects.push({ + '__type' : 'Pointer', + 'className' : item.type, + 'objectId' : item.id + }); + }); + + json[key] = { '__op': 'Batch', 'ops': [addOperation, deleteOperation] }; + } + + if ( options.array ) { + json[key].deleteds = { '__op': 'Remove', 'objects': [] }; + + hasMany._deletedItems.forEach( function( item ) { + json[key].deleteds.objects.push({ + '__type' : 'Pointer', + 'className' : item.type, + 'objectId' : item.id + }); + }); + } + } + + } else { + json[key] = []; + } + } + +}); \ No newline at end of file diff --git a/lib/ember-parse-adapter/transforms/date.js b/addon/transforms/date.js similarity index 60% rename from lib/ember-parse-adapter/transforms/date.js rename to addon/transforms/date.js index cd4e9c1..8cfdf90 100644 --- a/lib/ember-parse-adapter/transforms/date.js +++ b/addon/transforms/date.js @@ -1,3 +1,5 @@ +import DS from 'ember-data'; + /* * The date transform handles Parse's custom data format. For * example a Parse date might come back from the REST API @@ -12,25 +14,27 @@ * JavaScript date object. In also performs the inverse: * converting a date object back into Parse's custom format. * - * @class EmberParseAdapter.Transforms.Data + * @class DS.Transforms.Data */ -EmberParseAdapter.Transforms.Date = DS.Transform.extend({ +export default DS.Transform.extend({ - deserialize: function(serialized) { - if (!serialized) { + deserialize: function( serialized ) { + if ( !serialized ) { return null; } - return new Date(serialized.iso); + + return new Date( serialized ); }, - serialize: function(deserialized) { - if (!deserialized) { + serialize: function( deserialized ) { + if ( !deserialized ) { return null; } + return { - __type: 'Date', - iso: deserialized.toISOString() + __type : 'Date', + iso : deserialized.toISOString() }; } -}); +}); \ No newline at end of file diff --git a/addon/transforms/file.js b/addon/transforms/file.js new file mode 100644 index 0000000..ff0c15c --- /dev/null +++ b/addon/transforms/file.js @@ -0,0 +1,57 @@ +import DS from 'ember-data'; +import File from './file'; + +/* + * The file transform handles Parse's custom data format. For + * example a Parse file might come back from the REST API + * looking like this: + * + * "registeredAt": { + * "__type": "File", + * "name": "foo.jpg", + * "url": "http://some.s3.url.com/foo.jpg" + * } + * + * This helper deserializes that structure into a special + * File object. This object should not be changed, + * instead set a new file object to the property. + * + * this.store.find('model').then(function(model){ + * model.get('someFile'); // -> File object + * model.get('someFile.url'); // -> someFile URL + * + * var file = new File('foo.jpg', url); + * model.set('someFile', file); + * }); + * + * When saving a record, the File object is likewise + * serialized into the Parse REST API format. + * + * @class DS.Transforms.File + */ +export default DS.Transform.extend({ + + deserialize: function( serialized ) { + if ( !serialized ) { + return null; + } + + return File.create({ + name : serialized.name, + url : serialized.url + }); + }, + + serialize: function( deserialized ) { + if ( !deserialized ) { + return null; + } + + return { + __type : 'File', + name : deserialized.get( 'name' ), + url : deserialized.get( 'url' ) + }; + } + +}); \ No newline at end of file diff --git a/addon/transforms/geopoint.js b/addon/transforms/geopoint.js new file mode 100644 index 0000000..b0c3833 --- /dev/null +++ b/addon/transforms/geopoint.js @@ -0,0 +1,57 @@ +import DS from 'ember-data'; +import GeoPoint from './geopoint'; + +/* + * The file transform handles Parse's custom GeoPoint format. For + * example a Parse file might come back from the REST API + * looking like this: + * + * "registeredAt": { + * "__type": "GeoPoint", + * "latitude": 45.2934237432, + * "longitude": -17.233242432 + * } + * + * This helper deserializes that structure into a special + * GeoPoint object. This object should not be changed, + * instead set a new file object to the property. + * + * this.store.find('model').then(function(model){ + * model.get('someGeo'); // -> GeoPoint object + * model.get('someGeo.latitude'); // -> someGeo latitude + * + * var geoPoint = new GeoPoint(lat, lon); + * model.set('someGeo', geoPoint); + * }); + * + * When saving a record, the GeoPoint object + * is likewise serialized into the Parse REST API format. + * + * @class DS.Transforms.GeoPoint + */ +export default DS.Transform.extend({ + + deserialize: function( serialized ) { + if ( !serialized ) { + return null; + } + + return GeoPoint.create({ + latitude : serialized.latitude, + longitude : serialized.longitude + }); + }, + + serialize: function( deserialized ) { + if ( !deserialized ) { + return null; + } + + return { + __type : 'GeoPoint', + latitude : deserialized.get( 'latitude' ), + longitude : deserialized.get( 'longitude' ) + }; + } + +}); diff --git a/app/.gitkeep b/app/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/adapters/application.js b/app/adapters/application.js new file mode 100644 index 0000000..f23a656 --- /dev/null +++ b/app/adapters/application.js @@ -0,0 +1,4 @@ +import adapter from 'ember-parse-adapter/adapters/application'; + +/** @module adapters/application */ +export default adapter; \ No newline at end of file diff --git a/app/file.js b/app/file.js new file mode 100644 index 0000000..b6ba9c2 --- /dev/null +++ b/app/file.js @@ -0,0 +1,4 @@ +import file from 'ember-parse-adapter/file'; + +/** @module file */ +export default file; \ No newline at end of file diff --git a/app/geopoint.js b/app/geopoint.js new file mode 100644 index 0000000..25e8306 --- /dev/null +++ b/app/geopoint.js @@ -0,0 +1,4 @@ +import geopoint from 'ember-parse-adapter/geopoint'; + +/** @module geopoint */ +export default geopoint; \ No newline at end of file diff --git a/app/initializers/initialize.js b/app/initializers/initialize.js new file mode 100644 index 0000000..5d79140 --- /dev/null +++ b/app/initializers/initialize.js @@ -0,0 +1,9 @@ +import initializer from 'ember-parse-adapter/initializers/initialize'; + +export default { + name: 'ember-parse-adapter', + + after: 'ember-data', + + initialize: initializer +}; \ No newline at end of file diff --git a/app/serializers/application.js b/app/serializers/application.js new file mode 100644 index 0000000..055d584 --- /dev/null +++ b/app/serializers/application.js @@ -0,0 +1,4 @@ +import serializer from 'ember-parse-adapter/serializers/application'; + +/** @module serializers/application */ +export default serializer; \ No newline at end of file diff --git a/app/transforms/date.js b/app/transforms/date.js new file mode 100644 index 0000000..392e18b --- /dev/null +++ b/app/transforms/date.js @@ -0,0 +1,4 @@ +import transform from 'ember-parse-adapter/transforms/date'; + +/** @module transforms/date */ +export default transform; \ No newline at end of file diff --git a/app/transforms/file.js b/app/transforms/file.js new file mode 100644 index 0000000..fa5dac2 --- /dev/null +++ b/app/transforms/file.js @@ -0,0 +1,4 @@ +import transform from 'ember-parse-adapter/transforms/file'; + +/** @module transforms/file */ +export default transform; \ No newline at end of file diff --git a/app/transforms/geopoint.js b/app/transforms/geopoint.js new file mode 100644 index 0000000..2df2cc3 --- /dev/null +++ b/app/transforms/geopoint.js @@ -0,0 +1,4 @@ +import transform from 'ember-parse-adapter/transforms/geopoint'; + +/** @module transforms/geopoint */ +export default transform; \ No newline at end of file diff --git a/bower.json b/bower.json index 2de19c6..fbfe2aa 100644 --- a/bower.json +++ b/bower.json @@ -1,18 +1,17 @@ { "name": "ember-parse-adapter", - "version": "0.5.2", - "homepage": "https://github.com/clintjhill/ember-parse-adapter", - "description": "Ember Data Adapter for Parse", - "main": "dist/ember-parse-adapter.js", - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], "dependencies": { - "ember-data": "~1.0.0-beta.8" + "handlebars": "~1.3.0", + "jquery": "^1.11.1", + "ember": "1.8.1", + "ember-data": "1.0.0-beta.14.1", + "ember-resolver": "~0.1.11", + "loader.js": "ember-cli/loader.js#1.0.1", + "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", + "ember-cli-test-loader": "ember-cli/ember-cli-test-loader#0.1.1", + "ember-load-initializers": "ember-cli/ember-load-initializers#0.0.2", + "ember-qunit": "0.2.8", + "ember-qunit-notifications": "0.0.7", + "qunit": "~1.17.1" } } diff --git a/config/environment.js b/config/environment.js new file mode 100644 index 0000000..0dfaed4 --- /dev/null +++ b/config/environment.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function(/* environment, appConfig */) { + return { }; +}; diff --git a/dist/ember-parse-adapter.js b/dist/ember-parse-adapter.js deleted file mode 100644 index 7410901..0000000 --- a/dist/ember-parse-adapter.js +++ /dev/null @@ -1,648 +0,0 @@ -// A namespace for the parse adatper -// -var EmberParseAdapter = {}; -EmberParseAdapter.Transforms = {}; - -/* - Serializer to assure proper Parse-to-Ember encodings -*/ -EmberParseAdapter.Serializer = DS.RESTSerializer.extend({ - - primaryKey: "objectId", - - extractArray: function(store, primaryType, payload){ - var namespacedPayload = {}; - namespacedPayload[Ember.String.pluralize(primaryType.typeKey)] = payload.results; - return this._super(store, primaryType, namespacedPayload); - }, - - extractSingle: function(store, primaryType, payload, recordId){ - var namespacedPayload = {}; - namespacedPayload[primaryType.typeKey] = payload; // this.normalize(primaryType, payload); - return this._super(store, primaryType, namespacedPayload, recordId); - }, - - typeForRoot: function(key) { - return Ember.String.dasherize(Ember.String.singularize(key)); - }, - - /** - * Because Parse only returns the updatedAt/createdAt values on updates - * we have to intercept it here to assure that the adapter knows which - * record ID we are dealing with (using the primaryKey). - */ - extract: function(store, type, payload, id, requestType){ - if(id !== null && (requestType === "updateRecord" || requestType === "deleteRecord")){ - payload[this.get('primaryKey')] = id; - } - return this._super(store, type, payload, id, requestType); - }, - - /** - * Extracts count from the payload so that you can get the total number - * of records in Parse if you're using skip and limit. - */ - extractMeta: function(store, type, payload) { - if (payload && payload.count) { - store.metaForType(type, {count: payload.count}); - delete payload.count; - } - }, - - /** - * Special handling for the Date objects inside the properties of - * Parse responses. - */ - normalizeAttributes: function(type, hash){ - type.eachAttribute(function(key, meta){ - if(meta.type === "date" && Ember.typeOf(hash[key]) === "object" && hash[key].iso){ - hash[key] = hash[key].iso; //new Date(hash[key].iso).toISOString(); - } - }); - this._super(type, hash); - }, - - /** - * Special handling of the Parse relation types. In certain - * conditions there is a secondary query to retrieve the "many" - * side of the "hasMany". - */ - normalizeRelationships: function(type, hash){ - var store = this.get('store'); - var serializer = this; - - type.eachRelationship(function(key, relationship) { - - var options = relationship.options; - - // Handle the belongsTo relationships - if(hash[key] && relationship.kind === 'belongsTo'){ - hash[key] = hash[key].objectId; - } - - // Handle the hasMany relationships - if(hash[key] && relationship.kind === 'hasMany'){ - - // If this is a Relation hasMany then we need to supply - // the links property so the adapter can async call the - // relationship. - // The adapter findHasMany has been overridden to make use of this. - if(options.relation){ - hash.links = {}; - hash.links[key] = {type: relationship.type, key: key}; - } - - if(options.array){ - // Parse will return [null] for empty relationships - if(hash[key].length && hash[key]){ - hash[key].forEach(function(item, index, items){ - // When items are pointers we just need the id - // This occurs when request was made without the include query param. - if(item.__type === "Pointer"){ - items[index] = item.objectId; - } else { - // When items are objects we need to clean them and add them to the store. - // This occurs when request was made with the include query param. - delete item.__type; - delete item.className; - item.id = item.objectId; - delete item.objectId; - item.type = relationship.type; - serializer.normalizeAttributes(relationship.type, item); - serializer.normalizeRelationships(relationship.type, item); - store.push(relationship.type, item); - } - }); - } - } - - } - }, this); - - this._super(type, hash); - }, - - serializeIntoHash: function(hash, type, record, options){ - Ember.merge(hash, this.serialize(record, options)); - }, - - serializeAttribute: function(record, json, key, attribute) { - // These are Parse reserved properties and we won't send them. - if( key === 'createdAt' || - key === 'updatedAt' || - key === 'emailVerified' || - key === 'sessionToken' ){ - delete json[key]; - } else { - this._super(record, json, key, attribute); - } - }, - - serializeBelongsTo: function(record, json, relationship){ - var key = relationship.key; - var belongsTo = record.get(key); - if(belongsTo){ - // TODO: Perhaps this is working around a bug in Ember-Data? Why should - // promises be returned here. - if (belongsTo instanceof DS.PromiseObject) { - if (!belongsTo.get('isFulfilled')) { - throw new Error("belongsTo values *must* be fulfilled before attempting to serialize them"); - } - belongsTo = belongsTo.get('content'); - } - - json[key] = { - "__type": "Pointer", - "className": this.parseClassName(belongsTo.constructor.typeKey), - "objectId": belongsTo.get('id') - }; - } - }, - - parseClassName: function(key) { - if (key === "parseUser") { - return "_User"; - } else { - return Ember.String.capitalize(Ember.String.camelize(key)); - } - }, - - serializeHasMany: function(record, json, relationship){ - var key = relationship.key; - var hasMany = record.get(key); - var options = relationship.options; - if(hasMany && hasMany.get('length') > 0){ - - json[key] = { "objects": [] }; - - if(options.relation){ - json[key].__op = "AddRelation"; - } - - if(options.array){ - json[key].__op = "AddUnique"; - } - - hasMany.forEach(function(child){ - json[key].objects.push({ - "__type": "Pointer", - "className": child.parseClassName(), - "objectId": child.get('id') - }); - }); - - if(hasMany._deletedItems && hasMany._deletedItems.length){ - if(options.relation){ - var addOperation = json[key]; - var deleteOperation = { "__op": "RemoveRelation", "objects": [] }; - hasMany._deletedItems.forEach(function(item){ - deleteOperation.objects.push({ - "__type": "Pointer", - "className": item.type, - "objectId": item.id - }); - }); - json[key] = { "__op": "Batch", "ops": [addOperation, deleteOperation] }; - } - if(options.array){ - json[key].deleteds = { "__op": "Remove", "objects": [] }; - hasMany._deletedItems.forEach(function(item){ - json[key].deleteds.objects.push({ - "__type": "Pointer", - "className": item.type, - "objectId": item.id - }); - }); - } - } - } else { - json[key] = []; - } - } - -}); - -/** - * An Ember Data Adapter written to use Parse REST API - * @type {DS.RESTAdapter} - */ -EmberParseAdapter.Adapter = DS.RESTAdapter.extend({ - - defaultSerializer: '-parse', - - init: function(){ - this._super(); - this.set('headers', { - "X-Parse-Application-Id": this.get('applicationId'), - "X-Parse-REST-API-Key": this.get('restApiId') - }); - }, - - host: "https://api.parse.com", - namespace: '1', - classesPath: 'classes', - - pathForType: function(type) { - if ("parseUser" === type) { - return "users"; - } else if ("login" === type) { - return "login"; - } else { - return this.classesPath + '/' + this.parsePathForType(type); - } - }, - - // Using TitleStyle is recommended by Parse - // TODO: test - parsePathForType: function(type) { - return Ember.String.capitalize(Ember.String.camelize(type)); - }, - - /** - * Because Parse doesn't return a full set of properties on the - * responses to updates, we want to perform a merge of the response - * properties onto existing data so that the record maintains - * latest data. - */ - createRecord: function(store, type, record) { - var data = {}; - var serializer = store.serializerFor(type.typeKey); - serializer.serializeIntoHash(data, type, record, { includeId: true }); - var adapter = this; - return new Ember.RSVP.Promise(function(resolve, reject){ - adapter.ajax(adapter.buildURL(type.typeKey), "POST", { data: data }).then( - function(json){ - var completed = Ember.merge(data, json); - resolve(completed); - }, - function(reason){ - reject(reason.responseJSON); - }); - }); - }, - - /** - * Because Parse doesn't return a full set of properties on the - * responses to updates, we want to perform a merge of the response - * properties onto existing data so that the record maintains - * latest data. - */ - updateRecord: function(store, type, record) { - var data = {}; - var deleteds = {}; - var sendDeletes = false; - var serializer = store.serializerFor(type.typeKey); - serializer.serializeIntoHash(data, type, record); - var id = record.get('id'); - var adapter = this; - - type.eachRelationship(function(key, relationship){ - if(data[key] && data[key].deleteds){ - deleteds[key] = data[key].deleteds; - delete data[key].deleteds; - sendDeletes = true; - } - }); - - return new Ember.RSVP.Promise(function(resolve, reject) { - if(sendDeletes){ - adapter.ajax(adapter.buildURL(type.typeKey, id), "PUT", {data: deleteds}).then( - function(json){ - adapter.ajax(adapter.buildURL(type.typeKey, id), "PUT", { data: data }).then( - function(updates){ - // This is the essential bit - merge response data onto existing data. - var completed = Ember.merge(data, updates); - resolve(completed); - }, - function(reason){ - reject("Failed to save parent in relation: " + reason.response.JSON); - } - ); - }, - function(reason){ - reject(reason.responseJSON); - } - ); - } else { - adapter.ajax(adapter.buildURL(type.typeKey, id), "PUT", { data: data }).then(function(json){ - // This is the essential bit - merge response data onto existing data. - var completed = Ember.merge(data, json); - resolve(completed); - }, function(reason){ - reject(reason.responseJSON); - }); - } - }); - }, - - parseClassName: function(key) { - return Ember.String.capitalize(key); - }, - - /** - * Implementation of a hasMany that provides a Relation query for Parse - * objects. - */ - findHasMany: function(store, record, relatedInfo){ - var query = { - where: { - "$relatedTo": { - "object": { - "__type": "Pointer", - "className": this.parseClassName(record.typeKey), - "objectId": record.get('id') - }, - key: relatedInfo.key - } - } - }; - // the request is to the related type and not the type for the record. - // the query is where there is a pointer to this record. - return this.ajax(this.buildURL(relatedInfo.type.typeKey), "GET", { data: query }); - }, - - /** - * Implementation of findQuery that automatically wraps query in a - * JSON string. - * - * @example - * this.store.find('comment', { - * where: { - * post: { - * "__type": "Pointer", - * "className": "Post", - * "objectId": post.get('id') - * } - * } - * }); - */ - findQuery: function (store, type, query) { - if (query.where && Ember.typeOf(query.where) !== 'string') { - query.where = JSON.stringify(query.where); - } - - // Pass to _super() - return this._super(store, type, query); - }, - - sessionToken: Ember.computed('headers.X-Parse-Session-Token', function(key, value){ - if (arguments.length < 2) { - return this.get('headers.X-Parse-Session-Token'); - } else { - this.set('headers.X-Parse-Session-Token', value); - return value; - } - }) -}); - -/** - * Parse User object implementation - * @type {DS.ParseModel} - */ -EmberParseAdapter.ParseUser = DS.Model.extend({ - username: DS.attr('string'), - password: DS.attr('string'), - email: DS.attr('string'), - emailVerified: DS.attr('boolean'), - sessionToken: DS.attr('string'), - - createdAt: DS.attr('date'), - updatedAt: DS.attr('date') - -}); - -EmberParseAdapter.ParseUser.reopenClass({ - - requestPasswordReset: function(email){ - var adapter = this.get('store').adapterFor(this); - var data = { email: email }; - return adapter.ajax(adapter.buildURL("requestPasswordReset"), "POST", {data:data})['catch']( - function(response){ - return Ember.RSVP.reject(response.responseJSON); - } - ); - }, - - login: function(store, data){ - if(Ember.isEmpty(this.typeKey)){ - throw new Error('Parse login must be called on a model fetched via store.modelFor'); - } - var model = this; - var adapter = store.adapterFor(model); - var serializer = store.serializerFor(model); - return adapter.ajax(adapter.buildURL("login"), "GET", {data: data}).then( - function(response){ - serializer.normalize(model, response); - var record = store.push(model, response); - return record; - }, - function(response){ - return Ember.RSVP.reject(response.responseJSON); - } - ); - }, - - signup: function(store, data){ - if(Ember.isEmpty(this.typeKey)){ - throw new Error('Parse signup must be called on a model fetched via store.modelFor'); - } - var model = this; - var adapter = store.adapterFor(model); - var serializer = store.serializerFor(model); - return adapter.ajax(adapter.buildURL(model.typeKey), "POST", {data: data}).then( - function(response){ - serializer.normalize(model, response); - response.email = response.email || data.email; - response.username = response.username || data.username; - var record = store.push(model, response); - return record; - }, - function(response){ - return Ember.RSVP.reject(response.responseJSON); - } - ); - } -}); - -EmberParseAdapter.GeoPoint = Ember.Object.extend({ - latitude: Ember.computed(function(){ return this._latitude; }).readOnly(), - longitude: Ember.computed(function(){ return this._longitude; }).readOnly(), - - init: function(latitude, longitude) { - this._latitude = latitude; - this._longitude = longitude; - } - -}); - -EmberParseAdapter.File = Ember.Object.extend({ - name: Ember.computed(function(){ return this._name; }).readOnly(), - url: Ember.computed(function(){ return this._url; }).readOnly(), - - init: function(name, url) { - this._name = name; - this._url = url; - } - -}); - -/* - * The file transform handles Parse's custom GeoPoint format. For - * example a Parse file might come back from the REST API - * looking like this: - * - * "registeredAt": { - * "__type": "GeoPoint", - * "latitude": 45.2934237432, - * "longitude": -17.233242432 - * } - * - * This helper deserializes that structure into a special - * EmberParseAdapter.GeoPoint object. This object should not be - * changed, instead set a new file object to the property. - * - * this.store.find('model').then(function(model){ - * model.get('someGeo'); // -> GeoPoint object - * model.get('someGeo.latitude'); // -> someGeo latitude - * - * var geoPoint = new EmberParseAdapter.GeoPoint(lat, lon); - * model.set('someGeo', geoPoint); - * }); - * - * When saving a record, the EmberParseAdapter.GeoPoint object - * is likewise serialized into the Parse REST API format. - * - * @class EmberParseAdapter.Transforms.GeoPoint - */ -EmberParseAdapter.Transforms.GeoPoint = DS.Transform.extend({ - - deserialize: function(serialized) { - if (!serialized) { - return null; - } - return new EmberParseAdapter.GeoPoint(serialized.latitude, serialized.longitude); - }, - - serialize: function(deserialized) { - if (!deserialized) { - return null; - } - return { - __type: 'GeoPoint', - latitude: deserialized.get('latitude'), - longitude: deserialized.get('longitude') - }; - } - -}); - -/* - * The file transform handles Parse's custom data format. For - * example a Parse file might come back from the REST API - * looking like this: - * - * "registeredAt": { - * "__type": "File", - * "name": "foo.jpg", - * "url": "http://some.s3.url.com/foo.jpg" - * } - * - * This helper deserializes that structure into a special - * EmberParseAdapter.File object. This object should not be - * changed, instead set a new file object to the property. - * - * this.store.find('model').then(function(model){ - * model.get('someFile'); // -> File object - * model.get('someFile.url'); // -> someFile URL - * - * var file = new EmberParseAdapter.File('foo.jpg', url); - * model.set('someFile', file); - * }); - * - * When saving a record, the EmberParseAdapter.File object - * is likewise serialized into the Parse REST API format. - * - * @class EmberParseAdapter.Transforms.File - */ -EmberParseAdapter.Transforms.File = DS.Transform.extend({ - - deserialize: function(serialized) { - if (!serialized) { - return null; - } - return new EmberParseAdapter.File(serialized.name, serialized.url); - }, - - serialize: function(deserialized) { - if (!deserialized) { - return null; - } - return { - __type: 'File', - name: deserialized.get('name'), - url: deserialized.get('url') - }; - } - -}); - -/* - * The date transform handles Parse's custom data format. For - * example a Parse date might come back from the REST API - * looking like this: - * - * "registeredAt": { - * "__type": "Date", - * "iso": "2014-06-05T12:43:50.716Z" - * } - * - * This helper deserializes that structure into a normal - * JavaScript date object. In also performs the inverse: - * converting a date object back into Parse's custom format. - * - * @class EmberParseAdapter.Transforms.Data - */ -EmberParseAdapter.Transforms.Date = DS.Transform.extend({ - - deserialize: function(serialized) { - if (!serialized) { - return null; - } - return new Date(serialized.iso); - }, - - serialize: function(deserialized) { - if (!deserialized) { - return null; - } - return { - __type: 'Date', - iso: deserialized.toISOString() - }; - } - -}); - -EmberParseAdapter.setupContainer = function(container){ - container.register('adapter:-parse', EmberParseAdapter.Adapter); - container.register('serializer:-parse', EmberParseAdapter.Serializer); - container.register('model:parse-user', EmberParseAdapter.ParseUser); - container.register('transform:parse-geo-point', EmberParseAdapter.Transforms.GeoPoint); - container.register('transform:parse-file', EmberParseAdapter.Transforms.File); - container.register('transform:parse-date', EmberParseAdapter.Transforms.Date); -}; - -/** - * Setup the Parse Adapter in an app. - */ -Ember.onLoad("Ember.Application", function(Application) { - - Application.initializer({ - after: "ember-data", - name: "parse-adapter", - initialize: function(container, application) { - EmberParseAdapter.setupContainer(container); - } - }); - -}); diff --git a/dist/ember-parse-adapter.min.js b/dist/ember-parse-adapter.min.js deleted file mode 100644 index e62e645..0000000 --- a/dist/ember-parse-adapter.min.js +++ /dev/null @@ -1 +0,0 @@ -var EmberParseAdapter={};EmberParseAdapter.Transforms={},EmberParseAdapter.Serializer=DS.RESTSerializer.extend({primaryKey:"objectId",extractArray:function(e,t,r){var a={};return a[Ember.String.pluralize(t.typeKey)]=r.results,this._super(e,t,a)},extractSingle:function(e,t,r,a){var n={};return n[t.typeKey]=r,this._super(e,t,n,a)},typeForRoot:function(e){return Ember.String.dasherize(Ember.String.singularize(e))},extract:function(e,t,r,a,n){return null===a||"updateRecord"!==n&&"deleteRecord"!==n||(r[this.get("primaryKey")]=a),this._super(e,t,r,a,n)},extractMeta:function(e,t,r){r&&r.count&&(e.metaForType(t,{count:r.count}),delete r.count)},normalizeAttributes:function(e,t){e.eachAttribute(function(e,r){"date"===r.type&&"object"===Ember.typeOf(t[e])&&t[e].iso&&(t[e]=t[e].iso)}),this._super(e,t)},normalizeRelationships:function(e,t){var r=this.get("store"),a=this;e.eachRelationship(function(e,n){var i=n.options;t[e]&&"belongsTo"===n.kind&&(t[e]=t[e].objectId),t[e]&&"hasMany"===n.kind&&(i.relation&&(t.links={},t.links[e]={type:n.type,key:e}),i.array&&t[e].length&&t[e]&&t[e].forEach(function(e,t,i){"Pointer"===e.__type?i[t]=e.objectId:(delete e.__type,delete e.className,e.id=e.objectId,delete e.objectId,e.type=n.type,a.normalizeAttributes(n.type,e),a.normalizeRelationships(n.type,e),r.push(n.type,e))}))},this),this._super(e,t)},serializeIntoHash:function(e,t,r,a){Ember.merge(e,this.serialize(r,a))},serializeAttribute:function(e,t,r,a){"createdAt"===r||"updatedAt"===r||"emailVerified"===r||"sessionToken"===r?delete t[r]:this._super(e,t,r,a)},serializeBelongsTo:function(e,t,r){var a=r.key,n=e.get(a);if(n){if(n instanceof DS.PromiseObject){if(!n.get("isFulfilled"))throw Error("belongsTo values *must* be fulfilled before attempting to serialize them");n=n.get("content")}t[a]={__type:"Pointer",className:this.parseClassName(n.constructor.typeKey),objectId:n.get("id")}}},parseClassName:function(e){return"parseUser"===e?"_User":Ember.String.capitalize(Ember.String.camelize(e))},serializeHasMany:function(e,t,r){var a=r.key,n=e.get(a),i=r.options;if(n&&n.get("length")>0){if(t[a]={objects:[]},i.relation&&(t[a].__op="AddRelation"),i.array&&(t[a].__op="AddUnique"),n.forEach(function(e){t[a].objects.push({__type:"Pointer",className:e.parseClassName(),objectId:e.get("id")})}),n._deletedItems&&n._deletedItems.length){if(i.relation){var s=t[a],o={__op:"RemoveRelation",objects:[]};n._deletedItems.forEach(function(e){o.objects.push({__type:"Pointer",className:e.type,objectId:e.id})}),t[a]={__op:"Batch",ops:[s,o]}}i.array&&(t[a].deleteds={__op:"Remove",objects:[]},n._deletedItems.forEach(function(e){t[a].deleteds.objects.push({__type:"Pointer",className:e.type,objectId:e.id})}))}}else t[a]=[]}}),EmberParseAdapter.Adapter=DS.RESTAdapter.extend({defaultSerializer:"-parse",init:function(){this._super(),this.set("headers",{"X-Parse-Application-Id":this.get("applicationId"),"X-Parse-REST-API-Key":this.get("restApiId")})},host:"https://api.parse.com",namespace:"1",classesPath:"classes",pathForType:function(e){return"parseUser"===e?"users":"login"===e?"login":this.classesPath+"/"+this.parsePathForType(e)},parsePathForType:function(e){return Ember.String.capitalize(Ember.String.camelize(e))},createRecord:function(e,t,r){var a={},n=e.serializerFor(t.typeKey);n.serializeIntoHash(a,t,r,{includeId:!0});var i=this;return new Ember.RSVP.Promise(function(e,r){i.ajax(i.buildURL(t.typeKey),"POST",{data:a}).then(function(t){var r=Ember.merge(a,t);e(r)},function(e){r(e.responseJSON)})})},updateRecord:function(e,t,r){var a={},n={},i=!1,s=e.serializerFor(t.typeKey);s.serializeIntoHash(a,t,r);var o=r.get("id"),u=this;return t.eachRelationship(function(e){a[e]&&a[e].deleteds&&(n[e]=a[e].deleteds,delete a[e].deleteds,i=!0)}),new Ember.RSVP.Promise(function(e,r){i?u.ajax(u.buildURL(t.typeKey,o),"PUT",{data:n}).then(function(){u.ajax(u.buildURL(t.typeKey,o),"PUT",{data:a}).then(function(t){var r=Ember.merge(a,t);e(r)},function(e){r("Failed to save parent in relation: "+e.response.JSON)})},function(e){r(e.responseJSON)}):u.ajax(u.buildURL(t.typeKey,o),"PUT",{data:a}).then(function(t){var r=Ember.merge(a,t);e(r)},function(e){r(e.responseJSON)})})},parseClassName:function(e){return Ember.String.capitalize(e)},findHasMany:function(e,t,r){var a={where:{$relatedTo:{object:{__type:"Pointer",className:this.parseClassName(t.typeKey),objectId:t.get("id")},key:r.key}}};return this.ajax(this.buildURL(r.type.typeKey),"GET",{data:a})},findQuery:function(e,t,r){return r.where&&"string"!==Ember.typeOf(r.where)&&(r.where=JSON.stringify(r.where)),this._super(e,t,r)},sessionToken:Ember.computed("headers.X-Parse-Session-Token",function(e,t){return 2>arguments.length?this.get("headers.X-Parse-Session-Token"):(this.set("headers.X-Parse-Session-Token",t),t)})}),EmberParseAdapter.ParseUser=DS.Model.extend({username:DS.attr("string"),password:DS.attr("string"),email:DS.attr("string"),emailVerified:DS.attr("boolean"),sessionToken:DS.attr("string"),createdAt:DS.attr("date"),updatedAt:DS.attr("date")}),EmberParseAdapter.ParseUser.reopenClass({requestPasswordReset:function(e){var t=this.get("store").adapterFor(this),r={email:e};return t.ajax(t.buildURL("requestPasswordReset"),"POST",{data:r})["catch"](function(e){return Ember.RSVP.reject(e.responseJSON)})},login:function(e,t){if(Ember.isEmpty(this.typeKey))throw Error("Parse login must be called on a model fetched via store.modelFor");var r=this,a=e.adapterFor(r),n=e.serializerFor(r);return a.ajax(a.buildURL("login"),"GET",{data:t}).then(function(t){n.normalize(r,t);var a=e.push(r,t);return a},function(e){return Ember.RSVP.reject(e.responseJSON)})},signup:function(e,t){if(Ember.isEmpty(this.typeKey))throw Error("Parse signup must be called on a model fetched via store.modelFor");var r=this,a=e.adapterFor(r),n=e.serializerFor(r);return a.ajax(a.buildURL(r.typeKey),"POST",{data:t}).then(function(a){n.normalize(r,a),a.email=a.email||t.email,a.username=a.username||t.username;var i=e.push(r,a);return i},function(e){return Ember.RSVP.reject(e.responseJSON)})}}),EmberParseAdapter.GeoPoint=Ember.Object.extend({latitude:Ember.computed(function(){return this._latitude}).readOnly(),longitude:Ember.computed(function(){return this._longitude}).readOnly(),init:function(e,t){this._latitude=e,this._longitude=t}}),EmberParseAdapter.File=Ember.Object.extend({name:Ember.computed(function(){return this._name}).readOnly(),url:Ember.computed(function(){return this._url}).readOnly(),init:function(e,t){this._name=e,this._url=t}}),EmberParseAdapter.Transforms.GeoPoint=DS.Transform.extend({deserialize:function(e){return e?new EmberParseAdapter.GeoPoint(e.latitude,e.longitude):null},serialize:function(e){return e?{__type:"GeoPoint",latitude:e.get("latitude"),longitude:e.get("longitude")}:null}}),EmberParseAdapter.Transforms.File=DS.Transform.extend({deserialize:function(e){return e?new EmberParseAdapter.File(e.name,e.url):null},serialize:function(e){return e?{__type:"File",name:e.get("name"),url:e.get("url")}:null}}),EmberParseAdapter.Transforms.Date=DS.Transform.extend({deserialize:function(e){return e?new Date(e.iso):null},serialize:function(e){return e?{__type:"Date",iso:e.toISOString()}:null}}),EmberParseAdapter.setupContainer=function(e){e.register("adapter:-parse",EmberParseAdapter.Adapter),e.register("serializer:-parse",EmberParseAdapter.Serializer),e.register("model:parse-user",EmberParseAdapter.ParseUser),e.register("transform:parse-geo-point",EmberParseAdapter.Transforms.GeoPoint),e.register("transform:parse-file",EmberParseAdapter.Transforms.File),e.register("transform:parse-date",EmberParseAdapter.Transforms.Date)},Ember.onLoad("Ember.Application",function(e){e.initializer({after:"ember-data",name:"parse-adapter",initialize:function(e){EmberParseAdapter.setupContainer(e)}})}); \ No newline at end of file diff --git a/example.html b/example.html deleted file mode 100644 index de6d871..0000000 --- a/example.html +++ /dev/null @@ -1,497 +0,0 @@ - - - - Ember Data Adapter for Parse -- Example - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/index.js b/index.js new file mode 100644 index 0000000..01e3b98 --- /dev/null +++ b/index.js @@ -0,0 +1,6 @@ +/* jshint node: true */ +'use strict'; + +module.exports = { + name: 'ember-parse-adapter' +}; diff --git a/lib/ember-parse-adapter.js b/lib/ember-parse-adapter.js deleted file mode 100644 index b66b6e9..0000000 --- a/lib/ember-parse-adapter.js +++ /dev/null @@ -1,4 +0,0 @@ -// A namespace for the parse adatper -// -var EmberParseAdapter = {}; -EmberParseAdapter.Transforms = {}; diff --git a/lib/ember-parse-adapter/adapter.js b/lib/ember-parse-adapter/adapter.js deleted file mode 100644 index 767b406..0000000 --- a/lib/ember-parse-adapter/adapter.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * An Ember Data Adapter written to use Parse REST API - * @type {DS.RESTAdapter} - */ -EmberParseAdapter.Adapter = DS.RESTAdapter.extend({ - - defaultSerializer: '-parse', - - init: function(){ - this._super(); - this.set('headers', { - "X-Parse-Application-Id": this.get('applicationId'), - "X-Parse-REST-API-Key": this.get('restApiId') - }); - }, - - host: "https://api.parse.com", - namespace: '1', - classesPath: 'classes', - - pathForType: function(type) { - if ("parseUser" === type) { - return "users"; - } else if ("login" === type) { - return "login"; - } else { - return this.classesPath + '/' + this.parsePathForType(type); - } - }, - - // Using TitleStyle is recommended by Parse - // TODO: test - parsePathForType: function(type) { - return Ember.String.capitalize(Ember.String.camelize(type)); - }, - - /** - * Because Parse doesn't return a full set of properties on the - * responses to updates, we want to perform a merge of the response - * properties onto existing data so that the record maintains - * latest data. - */ - createRecord: function(store, type, record) { - var data = {}; - var serializer = store.serializerFor(type.typeKey); - serializer.serializeIntoHash(data, type, record, { includeId: true }); - var adapter = this; - return new Ember.RSVP.Promise(function(resolve, reject){ - adapter.ajax(adapter.buildURL(type.typeKey), "POST", { data: data }).then( - function(json){ - var completed = Ember.merge(data, json); - resolve(completed); - }, - function(reason){ - reject(reason.responseJSON); - }); - }); - }, - - /** - * Because Parse doesn't return a full set of properties on the - * responses to updates, we want to perform a merge of the response - * properties onto existing data so that the record maintains - * latest data. - */ - updateRecord: function(store, type, record) { - var data = {}; - var deleteds = {}; - var sendDeletes = false; - var serializer = store.serializerFor(type.typeKey); - serializer.serializeIntoHash(data, type, record); - var id = record.get('id'); - var adapter = this; - - type.eachRelationship(function(key, relationship){ - if(data[key] && data[key].deleteds){ - deleteds[key] = data[key].deleteds; - delete data[key].deleteds; - sendDeletes = true; - } - }); - - return new Ember.RSVP.Promise(function(resolve, reject) { - if(sendDeletes){ - adapter.ajax(adapter.buildURL(type.typeKey, id), "PUT", {data: deleteds}).then( - function(json){ - adapter.ajax(adapter.buildURL(type.typeKey, id), "PUT", { data: data }).then( - function(updates){ - // This is the essential bit - merge response data onto existing data. - var completed = Ember.merge(data, updates); - resolve(completed); - }, - function(reason){ - reject("Failed to save parent in relation: " + reason.response.JSON); - } - ); - }, - function(reason){ - reject(reason.responseJSON); - } - ); - } else { - adapter.ajax(adapter.buildURL(type.typeKey, id), "PUT", { data: data }).then(function(json){ - // This is the essential bit - merge response data onto existing data. - var completed = Ember.merge(data, json); - resolve(completed); - }, function(reason){ - reject(reason.responseJSON); - }); - } - }); - }, - - parseClassName: function(key) { - return Ember.String.capitalize(key); - }, - - /** - * Implementation of a hasMany that provides a Relation query for Parse - * objects. - */ - findHasMany: function(store, record, relatedInfo){ - var query = { - where: { - "$relatedTo": { - "object": { - "__type": "Pointer", - "className": this.parseClassName(record.typeKey), - "objectId": record.get('id') - }, - key: relatedInfo.key - } - } - }; - // the request is to the related type and not the type for the record. - // the query is where there is a pointer to this record. - return this.ajax(this.buildURL(relatedInfo.type.typeKey), "GET", { data: query }); - }, - - /** - * Implementation of findQuery that automatically wraps query in a - * JSON string. - * - * @example - * this.store.find('comment', { - * where: { - * post: { - * "__type": "Pointer", - * "className": "Post", - * "objectId": post.get('id') - * } - * } - * }); - */ - findQuery: function (store, type, query) { - if (query.where && Ember.typeOf(query.where) !== 'string') { - query.where = JSON.stringify(query.where); - } - - // Pass to _super() - return this._super(store, type, query); - }, - - sessionToken: Ember.computed('headers.X-Parse-Session-Token', function(key, value){ - if (arguments.length < 2) { - return this.get('headers.X-Parse-Session-Token'); - } else { - this.set('headers.X-Parse-Session-Token', value); - return value; - } - }) -}); diff --git a/lib/ember-parse-adapter/file.js b/lib/ember-parse-adapter/file.js deleted file mode 100644 index f04e4bf..0000000 --- a/lib/ember-parse-adapter/file.js +++ /dev/null @@ -1,10 +0,0 @@ -EmberParseAdapter.File = Ember.Object.extend({ - name: Ember.computed(function(){ return this._name; }).readOnly(), - url: Ember.computed(function(){ return this._url; }).readOnly(), - - init: function(name, url) { - this._name = name; - this._url = url; - } - -}); diff --git a/lib/ember-parse-adapter/geo-point.js b/lib/ember-parse-adapter/geo-point.js deleted file mode 100644 index d52eccf..0000000 --- a/lib/ember-parse-adapter/geo-point.js +++ /dev/null @@ -1,10 +0,0 @@ -EmberParseAdapter.GeoPoint = Ember.Object.extend({ - latitude: Ember.computed(function(){ return this._latitude; }).readOnly(), - longitude: Ember.computed(function(){ return this._longitude; }).readOnly(), - - init: function(latitude, longitude) { - this._latitude = latitude; - this._longitude = longitude; - } - -}); diff --git a/lib/ember-parse-adapter/parse-user.js b/lib/ember-parse-adapter/parse-user.js deleted file mode 100644 index ffd0d30..0000000 --- a/lib/ember-parse-adapter/parse-user.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Parse User object implementation - * @type {DS.ParseModel} - */ -EmberParseAdapter.ParseUser = DS.Model.extend({ - username: DS.attr('string'), - password: DS.attr('string'), - email: DS.attr('string'), - emailVerified: DS.attr('boolean'), - sessionToken: DS.attr('string'), - - createdAt: DS.attr('date'), - updatedAt: DS.attr('date') - -}); - -EmberParseAdapter.ParseUser.reopenClass({ - - requestPasswordReset: function(email){ - var adapter = this.get('store').adapterFor(this); - var data = { email: email }; - return adapter.ajax(adapter.buildURL("requestPasswordReset"), "POST", {data:data})['catch']( - function(response){ - return Ember.RSVP.reject(response.responseJSON); - } - ); - }, - - login: function(store, data){ - if(Ember.isEmpty(this.typeKey)){ - throw new Error('Parse login must be called on a model fetched via store.modelFor'); - } - var model = this; - var adapter = store.adapterFor(model); - var serializer = store.serializerFor(model); - return adapter.ajax(adapter.buildURL("login"), "GET", {data: data}).then( - function(response){ - serializer.normalize(model, response); - var record = store.push(model, response); - return record; - }, - function(response){ - return Ember.RSVP.reject(response.responseJSON); - } - ); - }, - - signup: function(store, data){ - if(Ember.isEmpty(this.typeKey)){ - throw new Error('Parse signup must be called on a model fetched via store.modelFor'); - } - var model = this; - var adapter = store.adapterFor(model); - var serializer = store.serializerFor(model); - return adapter.ajax(adapter.buildURL(model.typeKey), "POST", {data: data}).then( - function(response){ - serializer.normalize(model, response); - response.email = response.email || data.email; - response.username = response.username || data.username; - var record = store.push(model, response); - return record; - }, - function(response){ - return Ember.RSVP.reject(response.responseJSON); - } - ); - } -}); diff --git a/lib/ember-parse-adapter/serializer.js b/lib/ember-parse-adapter/serializer.js deleted file mode 100644 index 4e12419..0000000 --- a/lib/ember-parse-adapter/serializer.js +++ /dev/null @@ -1,218 +0,0 @@ -/* - Serializer to assure proper Parse-to-Ember encodings -*/ -EmberParseAdapter.Serializer = DS.RESTSerializer.extend({ - - primaryKey: "objectId", - - extractArray: function(store, primaryType, payload){ - var namespacedPayload = {}; - namespacedPayload[Ember.String.pluralize(primaryType.typeKey)] = payload.results; - return this._super(store, primaryType, namespacedPayload); - }, - - extractSingle: function(store, primaryType, payload, recordId){ - var namespacedPayload = {}; - namespacedPayload[primaryType.typeKey] = payload; // this.normalize(primaryType, payload); - return this._super(store, primaryType, namespacedPayload, recordId); - }, - - typeForRoot: function(key) { - return Ember.String.dasherize(Ember.String.singularize(key)); - }, - - /** - * Because Parse only returns the updatedAt/createdAt values on updates - * we have to intercept it here to assure that the adapter knows which - * record ID we are dealing with (using the primaryKey). - */ - extract: function(store, type, payload, id, requestType){ - if(id !== null && (requestType === "updateRecord" || requestType === "deleteRecord")){ - payload[this.get('primaryKey')] = id; - } - return this._super(store, type, payload, id, requestType); - }, - - /** - * Extracts count from the payload so that you can get the total number - * of records in Parse if you're using skip and limit. - */ - extractMeta: function(store, type, payload) { - if (payload && payload.count) { - store.metaForType(type, {count: payload.count}); - delete payload.count; - } - }, - - /** - * Special handling for the Date objects inside the properties of - * Parse responses. - */ - normalizeAttributes: function(type, hash){ - type.eachAttribute(function(key, meta){ - if(meta.type === "date" && Ember.typeOf(hash[key]) === "object" && hash[key].iso){ - hash[key] = hash[key].iso; //new Date(hash[key].iso).toISOString(); - } - }); - this._super(type, hash); - }, - - /** - * Special handling of the Parse relation types. In certain - * conditions there is a secondary query to retrieve the "many" - * side of the "hasMany". - */ - normalizeRelationships: function(type, hash){ - var store = this.get('store'); - var serializer = this; - - type.eachRelationship(function(key, relationship) { - - var options = relationship.options; - - // Handle the belongsTo relationships - if(hash[key] && relationship.kind === 'belongsTo'){ - hash[key] = hash[key].objectId; - } - - // Handle the hasMany relationships - if(hash[key] && relationship.kind === 'hasMany'){ - - // If this is a Relation hasMany then we need to supply - // the links property so the adapter can async call the - // relationship. - // The adapter findHasMany has been overridden to make use of this. - if(options.relation){ - hash.links = {}; - hash.links[key] = {type: relationship.type, key: key}; - } - - if(options.array){ - // Parse will return [null] for empty relationships - if(hash[key].length && hash[key]){ - hash[key].forEach(function(item, index, items){ - // When items are pointers we just need the id - // This occurs when request was made without the include query param. - if(item.__type === "Pointer"){ - items[index] = item.objectId; - } else { - // When items are objects we need to clean them and add them to the store. - // This occurs when request was made with the include query param. - delete item.__type; - delete item.className; - item.id = item.objectId; - delete item.objectId; - item.type = relationship.type; - serializer.normalizeAttributes(relationship.type, item); - serializer.normalizeRelationships(relationship.type, item); - store.push(relationship.type, item); - } - }); - } - } - - } - }, this); - - this._super(type, hash); - }, - - serializeIntoHash: function(hash, type, record, options){ - Ember.merge(hash, this.serialize(record, options)); - }, - - serializeAttribute: function(record, json, key, attribute) { - // These are Parse reserved properties and we won't send them. - if( key === 'createdAt' || - key === 'updatedAt' || - key === 'emailVerified' || - key === 'sessionToken' ){ - delete json[key]; - } else { - this._super(record, json, key, attribute); - } - }, - - serializeBelongsTo: function(record, json, relationship){ - var key = relationship.key; - var belongsTo = record.get(key); - if(belongsTo){ - // TODO: Perhaps this is working around a bug in Ember-Data? Why should - // promises be returned here. - if (belongsTo instanceof DS.PromiseObject) { - if (!belongsTo.get('isFulfilled')) { - throw new Error("belongsTo values *must* be fulfilled before attempting to serialize them"); - } - belongsTo = belongsTo.get('content'); - } - - json[key] = { - "__type": "Pointer", - "className": this.parseClassName(belongsTo.constructor.typeKey), - "objectId": belongsTo.get('id') - }; - } - }, - - parseClassName: function(key) { - if (key === "parseUser") { - return "_User"; - } else { - return Ember.String.capitalize(Ember.String.camelize(key)); - } - }, - - serializeHasMany: function(record, json, relationship){ - var key = relationship.key; - var hasMany = record.get(key); - var options = relationship.options; - if(hasMany && hasMany.get('length') > 0){ - - json[key] = { "objects": [] }; - - if(options.relation){ - json[key].__op = "AddRelation"; - } - - if(options.array){ - json[key].__op = "AddUnique"; - } - - hasMany.forEach(function(child){ - json[key].objects.push({ - "__type": "Pointer", - "className": child.parseClassName(), - "objectId": child.get('id') - }); - }); - - if(hasMany._deletedItems && hasMany._deletedItems.length){ - if(options.relation){ - var addOperation = json[key]; - var deleteOperation = { "__op": "RemoveRelation", "objects": [] }; - hasMany._deletedItems.forEach(function(item){ - deleteOperation.objects.push({ - "__type": "Pointer", - "className": item.type, - "objectId": item.id - }); - }); - json[key] = { "__op": "Batch", "ops": [addOperation, deleteOperation] }; - } - if(options.array){ - json[key].deleteds = { "__op": "Remove", "objects": [] }; - hasMany._deletedItems.forEach(function(item){ - json[key].deleteds.objects.push({ - "__type": "Pointer", - "className": item.type, - "objectId": item.id - }); - }); - } - } - } else { - json[key] = []; - } - } - -}); diff --git a/lib/ember-parse-adapter/transforms/file.js b/lib/ember-parse-adapter/transforms/file.js deleted file mode 100644 index b3b3e41..0000000 --- a/lib/ember-parse-adapter/transforms/file.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * The file transform handles Parse's custom data format. For - * example a Parse file might come back from the REST API - * looking like this: - * - * "registeredAt": { - * "__type": "File", - * "name": "foo.jpg", - * "url": "http://some.s3.url.com/foo.jpg" - * } - * - * This helper deserializes that structure into a special - * EmberParseAdapter.File object. This object should not be - * changed, instead set a new file object to the property. - * - * this.store.find('model').then(function(model){ - * model.get('someFile'); // -> File object - * model.get('someFile.url'); // -> someFile URL - * - * var file = new EmberParseAdapter.File('foo.jpg', url); - * model.set('someFile', file); - * }); - * - * When saving a record, the EmberParseAdapter.File object - * is likewise serialized into the Parse REST API format. - * - * @class EmberParseAdapter.Transforms.File - */ -EmberParseAdapter.Transforms.File = DS.Transform.extend({ - - deserialize: function(serialized) { - if (!serialized) { - return null; - } - return new EmberParseAdapter.File(serialized.name, serialized.url); - }, - - serialize: function(deserialized) { - if (!deserialized) { - return null; - } - return { - __type: 'File', - name: deserialized.get('name'), - url: deserialized.get('url') - }; - } - -}); diff --git a/lib/ember-parse-adapter/transforms/geo-point.js b/lib/ember-parse-adapter/transforms/geo-point.js deleted file mode 100644 index d6e197d..0000000 --- a/lib/ember-parse-adapter/transforms/geo-point.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * The file transform handles Parse's custom GeoPoint format. For - * example a Parse file might come back from the REST API - * looking like this: - * - * "registeredAt": { - * "__type": "GeoPoint", - * "latitude": 45.2934237432, - * "longitude": -17.233242432 - * } - * - * This helper deserializes that structure into a special - * EmberParseAdapter.GeoPoint object. This object should not be - * changed, instead set a new file object to the property. - * - * this.store.find('model').then(function(model){ - * model.get('someGeo'); // -> GeoPoint object - * model.get('someGeo.latitude'); // -> someGeo latitude - * - * var geoPoint = new EmberParseAdapter.GeoPoint(lat, lon); - * model.set('someGeo', geoPoint); - * }); - * - * When saving a record, the EmberParseAdapter.GeoPoint object - * is likewise serialized into the Parse REST API format. - * - * @class EmberParseAdapter.Transforms.GeoPoint - */ -EmberParseAdapter.Transforms.GeoPoint = DS.Transform.extend({ - - deserialize: function(serialized) { - if (!serialized) { - return null; - } - return new EmberParseAdapter.GeoPoint(serialized.latitude, serialized.longitude); - }, - - serialize: function(deserialized) { - if (!deserialized) { - return null; - } - return { - __type: 'GeoPoint', - latitude: deserialized.get('latitude'), - longitude: deserialized.get('longitude') - }; - } - -}); diff --git a/lib/ember.js b/lib/ember.js deleted file mode 100644 index bd447f7..0000000 --- a/lib/ember.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Setup the Parse Adapter in an app. - */ -Ember.onLoad("Ember.Application", function(Application) { - - Application.initializer({ - after: "ember-data", - name: "parse-adapter", - initialize: function(container, application) { - EmberParseAdapter.setupContainer(container); - } - }); - -}); diff --git a/lib/setup-container.js b/lib/setup-container.js deleted file mode 100644 index 064c0b4..0000000 --- a/lib/setup-container.js +++ /dev/null @@ -1,8 +0,0 @@ -EmberParseAdapter.setupContainer = function(container){ - container.register('adapter:-parse', EmberParseAdapter.Adapter); - container.register('serializer:-parse', EmberParseAdapter.Serializer); - container.register('model:parse-user', EmberParseAdapter.ParseUser); - container.register('transform:parse-geo-point', EmberParseAdapter.Transforms.GeoPoint); - container.register('transform:parse-file', EmberParseAdapter.Transforms.File); - container.register('transform:parse-date', EmberParseAdapter.Transforms.Date); -}; diff --git a/package.json b/package.json index a38393b..0575a31 100644 --- a/package.json +++ b/package.json @@ -1,52 +1,55 @@ { - "name": "ember-data-parse-adapter", - "description": "Ember Data Adapter for Parse", + "name": "ember-parse-adapter", "version": "0.5.2", - "main": "dist/ember-parse-adapter.js", - "devDependencies": { - "grunt": "~0.4.1", - "grunt-contrib-jshint": "~0.1.0", - "grunt-contrib-qunit": "~0.1.1", - "grunt-contrib-uglify": "~0.1.1", - "grunt-contrib-connect": "~0.1.0", - "grunt-contrib-concat": "~0.1.2" - }, + "description": "Ember Data Adapter for Parse", "directories": { - "lib": "lib" - }, - "keywords": [ - "parse", - "ember.js", - "ember-data" - ], - "files": [ - "dist/" - ], - "author": { - "name": "Clint Hill" + "doc": "doc", + "test": "tests" }, - "contributors": [ - { - "name": "Matt Beale", - "email": "matt.beale@madhatted.com" - } - ], - "maintainers": [ - { - "name": "Matt Beale", - "email": "matt.beale@madhatted.com" - } - ], - "license": "MIT", "scripts": { - "test": "grunt test", - "prepublish": "grunt" + "start": "ember server", + "build": "ember build", + "test": "ember test" }, + "bugs": "https://github.com/clintjhill/ember-parse-adapter/issues", "repository": { "type": "git", "url": "git://github.com/clintjhill/ember-parse-adapter.git" }, - "bugs": { - "url": "https://github.com/clintjhill/ember-parse-adapter/issues" + "engines": { + "node": ">= 0.10.0" + }, + "contributors": [ + "Clint Hill ", + "Matt Beale " + ], + "license": "MIT", + "devDependencies": { + "broccoli-asset-rev": "^2.0.0", + "broccoli-ember-hbs-template-compiler": "^1.6.1", + "ember-cli": "0.1.15", + "ember-cli-app-version": "0.3.1", + "ember-cli-babel": "^4.0.0", + "ember-cli-dependency-checker": "0.0.7", + "ember-cli-ic-ajax": "0.1.1", + "ember-cli-inject-live-reload": "^1.3.0", + "ember-cli-qunit": "0.3.7", + "ember-cli-uglify": "1.0.1", + "ember-data": "1.0.0-beta.14.1", + "ember-export-application-global": "^1.0.2", + "express": "^4.8.5", + "glob": "^4.4.2" + }, + "keywords": [ + "ember-addon", + "parse", + "ember.js", + "ember-data" + ], + "ember-addon": { + "configPath": "tests/dummy/config" + }, + "dependencies": { + "rimraf": "^2.3.2" } } diff --git a/test/ember-parse-adapter/adapter-test.js b/test/ember-parse-adapter/adapter-test.js deleted file mode 100644 index 188c30c..0000000 --- a/test/ember-parse-adapter/adapter-test.js +++ /dev/null @@ -1,371 +0,0 @@ -var get = Ember.get, set = Ember.set; - -var adapter, store, serializer, ajaxUrl, ajaxType, ajaxHash; -var Post, Comment; - -module("Integration - Adapter", { - setup: function() { - ajaxUrl = undefined; - ajaxType = undefined; - ajaxHash = undefined; - - container = buildContainer(); - - container.register('adapter:application', EmberParseAdapter.Adapter.extend({ - ajax: function(url, method, hash) { - return new Ember.RSVP.Promise(function(res, rej){ - hash = hash || {}; - var success = hash.success; - - hash.context = adapter; - - ajaxUrl = url; - ajaxType = method; - ajaxHash = hash; - - hash.success = function(json) { - Ember.run(function(){ - res(json); - }); - }; - - hash.error = function(xhr) { - Ember.run(function(){ - rej(xhr); - }); - }; - }); - } - })); - - container.register('model:post', DS.Model.extend({ - title: DS.attr('string'), - comments: DS.hasMany('comment', {async: true}) - })); - - container.register('model:comment', DS.Model.extend({ - content: DS.attr('string'), - post: DS.belongsTo('post', {async: true}) - })); - - store = container.lookup('store:main'); - adapter = container.lookup('adapter:application'); - serializer = container.lookup('serializer:-parse'); - - Post = store.modelFor('post'); - Comment = store.modelFor('comment'); - }, - - teardown: function() { - Ember.run(container, 'destroy'); - } -}); - -test("find", function(){ - post = Ember.run(function(){ return store.find('post', 'firstPost'); }); - ok(!get(post, 'isLoaded')); - equal(ajaxUrl, "https://api.parse.com/1/classes/Post/firstPost", "The Parse API version and classes with Post and ID."); - equal(ajaxType, "GET"); - ok( - Ember.get(ajaxHash, 'context.headers').hasOwnProperty('X-Parse-REST-API-Key'), - 'has rest api header' ); - ok( - Ember.get(ajaxHash, 'context.headers').hasOwnProperty('X-Parse-Application-Id'), - 'has rest application header' ); - ajaxHash.success({objectId: 'firstPost', title: 'Testing Find'}); - ok(get(post, 'isLoaded')); - ok(!get(post, 'isDirty')); - var isLoaded = store.recordIsLoaded('post', 'firstPost'); - ok(isLoaded, "the record is now in the store, and can be looked up by ID without another AJAX request"); -}); - -test("find with sessionToken", function(){ - post = Ember.run(function(){ - adapter.set('sessionToken', 'some-odd-token'); - return store.find('post', 'firstPost'); - }); - ok(!get(post, 'isLoaded')); - equal(ajaxUrl, "https://api.parse.com/1/classes/Post/firstPost", "The Parse API version and classes with Post and ID."); - equal(ajaxType, "GET"); - equal( - Ember.get(ajaxHash, 'context.headers.X-Parse-Session-Token'), 'some-odd-token', - 'has session header' ); - ajaxHash.success({objectId: 'firstPost', title: 'Testing Find'}); - ok(get(post, 'isLoaded')); - ok(!get(post, 'isDirty')); - var isLoaded = store.recordIsLoaded('post', 'firstPost'); - ok(isLoaded, "the record is now in the store, and can be looked up by ID without another AJAX request"); -}); - -test("can get a set sessionToken", function(){ - token = Ember.run(function(){ - adapter.set('sessionToken', 'some-odd-token'); - return adapter.get('sessionToken'); - }); - equal(token, 'some-odd-token'); -}); - -test("findAll", function(){ - posts = store.find('post'); - expect(ajaxUrl, "/1/classes/Post", "The Parse API version and classes with Post."); - equal(ajaxType, "GET"); - // Parse REST API wraps the collections in a results JSON label. - ajaxHash.success({results: [ - {objectId: '1', title: 'First Post.'}, - {objectId: '2', title: 'Second Post.'} - ]}); - posts.then(function(_posts){ - posts = _posts; - }); - equal(get(posts, 'length'), 2); - post = posts.objectAt(0); - ok(get(post, 'isLoaded')); - ok(!get(post, 'isDirty')); - var isLoaded = store.recordIsLoaded('post', '1'); - ok(isLoaded, "the record is now in the store, and can be looked up by ID without another Ajax request"); -}); - - -pending("findMany via a hasMany relationship", function(){ - Ember.run(function(){ - store.push('post', {id: 'one', comments: ['aa1', 'bb2', 'cc3']}); - }); - Ember.run(function(){ - store.find('post', 'one').then(function(post){ - return get(post, 'comments'); - }).then(function(comments){ - equal(get(comments, 'length'), 3, "there are three comments in the relationship already"); - var comment1 = comments.objectAt(0); - equal(get(comment1, 'id'), 'aa1'); - var comment2 = comments.objectAt(1); - equal(get(comment2, 'id'), 'bb2'); - var comment3 = comments.objectAt(2); - equal(get(comment3, 'id'), 'cc3'); - comments.forEach(function(comment){ - equal(get(comment, 'isLoaded'), true, "comment is loaded"); - }); - }); - }); - expect(ajaxUrl, "/1/classes/Comment", "requests the comment class"); - equal(ajaxType, "POST"); - deepEqual(ajaxHash.data, {where: {objectId: {"$in": "aa1,bb2,cc3"}}}, "the hash was passed along"); - ajaxHash.success({ - results: [ - {objectId: 'aa1', content: 'Comment 1'}, - {objectId: 'bb2', content: 'Comment 2'}, - {objectId: 'cc3', content: 'Comment 3'} - ] - }); -}); - -test("Find Query with non-string where", function(){ - posts = store.find('post', {where: {title: 'First Post'}}); - equal(get(posts, 'length'), 0, "there are no posts yet as the query has not returned."); - expect(ajaxUrl, "/1/classes/Post", "requests the post class"); - equal(ajaxType, "GET"); - deepEqual(ajaxHash.data, {where: JSON.stringify({title: 'First Post'}) }, "where clause is passed as stringified data"); - ajaxHash.success({ - results: [ - { objectId: 'bad1', title: 'First Post'}, - { objectId: 'bad2', title: 'First Post'} - ] - }); -}); - -test("Find Query with where as string", function(){ - posts = store.find('post', {where: "{title: 'First Post'}"}); - equal(get(posts, 'length'), 0, "there are no posts yet as the query has not returned."); - expect(ajaxUrl, "/1/classes/Post", "requests the post class"); - equal(ajaxType, "GET"); - deepEqual(ajaxHash.data, {where: "{title: 'First Post'}"}, "where clause is passed through as string"); - ajaxHash.success({ - results: [ - { objectId: 'bad1', title: 'First Post'}, - { objectId: 'bad2', title: 'First Post'} - ] - }); - - equal(get(posts, 'length'), 2, "there are 2 posts loaded"); - posts.forEach(function(post){ - equal(get(post, 'isLoaded'), true, "the post is being loaded"); - }); -}); - -test("Create Record", function(){ - stop(); - var post, promise; - Ember.run(function(){ - post = store.createRecord('post', {title: 'Testing Create'}); - ok(get(post, 'isNew'), "record is new"); - promise = post.save(); - }); - ok(get(post, 'isSaving'), "record is saving"); - equal(ajaxUrl, "https://api.parse.com/1/classes/Post", "requests the post class"); - equal(ajaxType, "POST"); - // Passing comments as an Ember array. This is due to a bug in Ember-Data - // expecting an Ember array for data and not a raw array: - // - // https://github.com/emberjs/data/pull/1939 - // - deepEqual(ajaxHash.data, {comments: Ember.A(), title: 'Testing Create'}, "raw data is posted"); - ajaxHash.success({objectId: 'created321', createdAt: (new Date()).toISOString()}); - Ember.run(function(){ - promise.then(function(){ - ok(!get(post, 'isSaving'), 'post is not saving after save'); - ok(!get(post, 'isDirty'), 'post is not dirty after save'); - start(); - }); - }); -}); - -pending("testing", function(){ - -test("Create Record - bulkCommit", function(){ - posts = new Ember.Set([ - store.createRecord(Post, {title: 'Post 1'}), - store.createRecord(Post, {title: 'Post 2'}) - ]); - expectStates(posts, 'new'); - store.commit(); - expectStates(posts, 'saving'); - expectUrl("/1/batch"); - expectType("POST"); - /* - This payload should match expected schema: https://www.parse.com/docs/rest#objects-batch - */ - expectData({ - requests: [ - { - method: "POST", - path: "/1/classes/Post", - body: {comments: [], title: 'Post 1', updatedAt: undefined, createdAt: undefined} - }, - { - method: "POST", - path: "/1/classes/Post", - body: {comments: [], title: 'Post 2', updatedAt: undefined, createdAt: undefined} - } - ] - }); - ajaxHash.success([ - {success: {objectId: 'post1', createdAt: (new Date()).toISOString()}}, - {success: {objectId: 'post2', createdAt: (new Date()).toISOString()}} - ]); - expectStates(posts, 'saving', false); - expect(posts[0], store.find(Post, 'post1'), "should match first post."); - expect(posts[1], store.find(Post, 'post2'), "should match second post."); -}); - -test("Update Record - not bulkCommit", function(){ - store.load(Post, {title: 'Test Post Update', objectId: 'postUpdated'}); - // force it to use single record update - adapter.bulkCommit = false; - post = store.find(Post, 'postUpdated'); - expectState('loaded'); - expectState('dirty', false); - post.set('title', 'Test Post Updated - true'); - expectState('dirty'); - store.commit(); - expectState('saving'); - expectUrl("/1/classes/Post/postUpdated"); - expectType("PUT"); - expectData({objectId: 'postUpdated', comments: [], title: 'Test Post Updated - true', updatedAt: undefined, createdAt: undefined}); - ajaxHash.success({objectId: 'postUpdated', updatedAt: (new Date()).toISOString()}); - expectState('saving', false); -}); - -test("Update Record - bulkCommit", function(){ - store.loadMany(Post, [ - {objectId: 'post1', title: 'Post 1'}, - {objectId: 'post2', title: 'Post 2'} - ]); - posts = store.findMany(Post, ['post1', 'post2']); - expectStates(posts, 'loaded'); - posts.forEach(function(post){ - post.set('title', post.get('title') + ' updated.'); - }); - expectStates(posts, 'dirty'); - store.commit(); - expectStates(posts, 'saving'); - expectUrl("/1/batch"); - expectType("POST"); - expectData({ - requests: [ - { - method: "PUT", - path: "/1/classes/Post/post1", - body: {objectId: 'post1', comments: [], title: 'Post 1 updated.', updatedAt: undefined, createdAt: undefined} - }, - { - method: "PUT", - path: "/1/classes/Post/post2", - body: {objectId: 'post2', comments: [], title: 'Post 2 updated.', updatedAt: undefined, createdAt: undefined} - } - ] - }); - ajaxHash.success([ - {success: {objectId: 'post1', updatedAt: (new Date()).toISOString()}}, - {success: {objectId: 'post2', updatedAt: (new Date()).toISOString()}} - ]); - expectStates(posts, 'saving', false); - expect(posts[0], store.find(Post, 'post1'), "should match first post."); - expect(posts[1], store.find(Post, 'post2'), "should match second post."); -}); - -test("Delete Record - not bulkCommit", function(){ - store.load(Post, {objectId: 'post1', title: 'Post to delete.'}); - // force single record delete - adapter.bulkCommit = false; - post = store.find(Post, 'post1'); - expectState('new', false); - expectState('loaded'); - expectState('dirty', false); - post.deleteRecord(); - expectState('dirty'); - expectState('deleted'); - store.commit(); - expectState('saving'); - expectUrl("/1/classes/Post/post1"); - expectType("DELETE"); - ajaxHash.success(); - expectState('deleted'); -}); - -test("Delete Record - bulkCommit", function(){ - store.loadMany(Post, [ - {objectId: 'post1', title: 'Post 1'}, - {objectId: 'post2', title: 'Post 2'} - ]); - posts = store.findMany(Post, ['post1', 'post2']); - expectStates(posts, 'loaded'); - expectStates(posts, 'new', false); - expectStates(posts, 'dirty', false); - posts.forEach(function(post){ - post.deleteRecord(); - }); - expectStates(posts, 'dirty'); - expectStates(posts, 'deleted'); - store.commit(); - expectStates(posts, 'saving'); - expectUrl("/1/batch"); - expectType('POST'); - expectData({ - requests: [ - { - method: "DELETE", - path: "/1/classes/Post/post1", - body: {objectId: 'post1', comments: [], title: 'Post 1', updatedAt: undefined, createdAt: undefined} - }, - { - method: "DELETE", - path: "/1/classes/Post/post2", - body: {objectId: 'post2', comments: [], title: 'Post 2', updatedAt: undefined, createdAt: undefined} - } - ] - }); - ajaxHash.success(); - expectStates(posts, 'saving', false); - expectStates(posts, 'dirty', false); -}); - -}); diff --git a/test/ember-parse-adapter/parse-user-test.js b/test/ember-parse-adapter/parse-user-test.js deleted file mode 100644 index 3e25df7..0000000 --- a/test/ember-parse-adapter/parse-user-test.js +++ /dev/null @@ -1,297 +0,0 @@ -var get = Ember.get, set = Ember.set; - -var adapter, serializer, store, ajaxUrl, ajaxType, ajaxHash; -var ParseUser; - -module("Integration - Parse User", { - setup: function(){ - ajaxUrl = undefined; - ajaxType = undefined; - ajaxHash = undefined; - - container = buildContainer(); - - container.register("adapter:application", EmberParseAdapter.Adapter.extend({ - ajax: function(url, method, hash) { - return new Ember.RSVP.Promise(function(res, rej){ - hash = hash || {}; - var success = hash.success; - - hash.context = adapter; - - ajaxUrl = url; - ajaxType = method; - ajaxHash = hash; - - hash.success = function(json) { - Ember.run(function(){ - res(json); - }); - }; - - hash.error = function(xhr) { - Ember.run(function(){ - rej(xhr); - }); - }; - }); - } - })); - - store = container.lookup("store:main"); - adapter = container.lookup("adapter:application"); - serializer = container.lookup("serializer:-parse"); - - ParseUser = store.modelFor("parse-user"); - - }, - teardown: function() { - Ember.run(container, "destroy"); - } -}); - -test("Signup", function(){ - var promise; - Ember.run(function(){ - promise = ParseUser.signup(store, { - username: "clintjhill", - password: "loveyouall", - email: "clint@foo.com" - }); - }); - equal(ajaxUrl, "https://api.parse.com/1/users", "The Parse API version and user path"); - equal(ajaxType, "POST"); - deepEqual(ajaxHash.data, { - username: "clintjhill", - password: "loveyouall", - email: "clint@foo.com" - }, "the hash was passed along"); - ajaxHash.success({ - "createdAt": "2011-11-07T20:58:34.448Z", - "objectId": "g7y9tkhB7O", - "sessionToken": "pnktnjyb996sj4p156gjtp4im" - }); - Ember.run(function(){ - promise.then(function(user){ - ok(!get(user, "isSaving"), "user is not saving"); - ok(!get(user, "isDirty"), "user is not dirty"); - equal(get(user, "id"), "g7y9tkhB7O", "Be sure objectId is set."); - equal(get(user, "password"), null, "Be sure that password gets dumped."); - equal(get(user, "sessionToken"), "pnktnjyb996sj4p156gjtp4im", "Make sure session token set."); - }); - }); -}); - -test("Signup with Facebook", function(){ - var expirationDate = (new Date()).toISOString(); - var promise; - Ember.run(function(){ - promise = ParseUser.signup(store, { - authData: { - facebook: { - access_token: "some-fake-token", - id: "some-id", - expiration_date: expirationDate - } - } - }); - }); - equal(ajaxUrl, "https://api.parse.com/1/users", "The Parse API version and user path"); - equal(ajaxType, "POST"); - deepEqual(ajaxHash.data, { - authData: { - facebook: { - access_token: "some-fake-token", - id: "some-id", - expiration_date: expirationDate - } - } - }, "the hash was passed along"); - ajaxHash.success({ - "authData": {}, - "createdAt": "2011-11-07T20:58:34.448Z", - "objectId": "g7y9tkhB7O", - "sessionToken": "pnktnjyb996sj4p156gjtp4im", - "username": "foofoo-username" - }); - Ember.run(function(){ - promise.then(function(user){ - ok(!get(user, "isSaving"), "user is not saving"); - ok(!get(user, "isDirty"), "user is not dirty"); - equal(get(user, "id"), "g7y9tkhB7O", "Be sure objectId is set."); - equal(get(user, "password"), null, "Be sure that password gets dumped."); - equal(get(user, "sessionToken"), "pnktnjyb996sj4p156gjtp4im", "Make sure session token set."); - equal(get(user, "username"), "foofoo-username", "Make sure username set."); - }); - }); -}); - -test("Find", function(){ - var user; - Ember.run(function(){ - user = store.find("parse-user", "h8mgfgL1yS"); - }) - ok(!get(user, "isLoaded")) - equal(ajaxUrl, "https://api.parse.com/1/users/h8mgfgL1yS", "The Parse API version and user path"); - equal(ajaxType, "GET"); - ajaxHash.success({ - "createdAt": "2011-11-07T20:58:34.448Z", - "objectId": "h8mgfgL1yS", - "username": "clintjhill" - }); - ok(get(user, "isLoaded")) - ok(!get(user, "isCurrent"), "User should not be current during a find."); -}); - -test("Login", function(){ - var user; - Ember.run(function(){ - promise = ParseUser.login(store, {username: "clint", password: "loveyouall"}); - }); - equal(ajaxUrl, "https://api.parse.com/1/login", "The Parse API version and user path"); - equal(ajaxType, "GET"); - deepEqual(ajaxHash.data, { - username: "clint", - password: "loveyouall" - }); - ajaxHash.success({ - "username": "clint", - "createdAt": "2011-11-07T20:58:34.448Z", - "updatedAt": "2011-11-07T20:58:34.448Z", - "objectId": "g7y9tkhB7O", - "sessionToken": "pnktnjyb996sj4p156gjtp4im" - }); - Ember.run(function(){ - promise.then(function(user){ - ok(get(user, "isLoaded")); - equal(get(user, "password"), null, "Be sure that password gets dumped."); - }); - }) -}); - -pending("Password Reset Request", function(){ - store.load(User, {objectId: "aid8nalX"}); - user = store.find(User, "aid8nalX"); - // expected events - user.on("requestingPasswordReset", function(){ - // while password reset request is being sent - expectState("passwordResetting"); - }); - user.on("didRequestPasswordReset", function(){ - // password reset request happened - expectState("loaded"); - }); - // reset it - user.requestPasswordReset("clint.hill@gmail.com"); - expectType("POST"); - expectUrl("/1/requestPasswordReset", "Request password path from Parse."); - expectState("passwordResetting"); - ajaxHash.success(); - expectState("loaded"); -}); - -pending("Update (batch) - Session token handling", function(){ - store.loadMany(User, [ - {objectId: "xuF8hlkrg", username: "clintjhill", email: "nope@yep.com"}, - {objectId: "inol8HFer", username: "clinthill", email: "yep@nope.com", sessionToken: "ivegotasession"} - ]); - var allowsUpdate = store.find(User, "inol8HFer"); - var noUpdates = store.find(User, "xuF8hlkrg"); - - allowsUpdate.set("password", "notHacked"); - noUpdates.set("password", "youGotHacked"); - - expectState("dirty", true, allowsUpdate); - expectState("dirty", true, noUpdates); - - store.commit(); - - ajaxHash.success([ - {success: {updatedAt: (new Date()).toISOString()}}, - {error: {code: 101, error: "some message"}} - ]); - - expectState("error", true, noUpdates); - expectState("loaded", true, allowsUpdate); - -}); - -module("Integration - Subclassing Parse User", { - setup: function(){ - ajaxUrl = undefined; - ajaxType = undefined; - ajaxHash = undefined; - - container = buildContainer(); - - container.register("adapter:application", EmberParseAdapter.Adapter.extend({ - ajax: function(url, method, hash) { - return new Ember.RSVP.Promise(function(res, rej){ - hash = hash || {}; - var success = hash.success; - - hash.context = adapter; - - ajaxUrl = url; - ajaxType = method; - ajaxHash = hash; - - hash.success = function(json) { - Ember.run(function(){ - res(json); - }); - }; - - hash.error = function(xhr) { - Ember.run(function(){ - rej(xhr); - }); - }; - }); - } - })); - - container.register("model:parse-user", EmberParseAdapter.ParseUser.extend({ - nickname: DS.attr("string") - })); - - store = container.lookup("store:main"); - adapter = container.lookup("adapter:application"); - serializer = container.lookup("serializer:-parse"); - - ParseUser = store.modelFor("parse-user"); - - }, - teardown: function() { - Ember.run(container, "destroy"); - } -}); - -test("Login", function(){ - var user; - Ember.run(function(){ - promise = ParseUser.login(store, {username: "clint", password: "loveyouall", nickname: "rick"}); - }); - equal(ajaxUrl, "https://api.parse.com/1/login", "The Parse API version and user path"); - equal(ajaxType, "GET"); - deepEqual(ajaxHash.data, { - username: "clint", - password: "loveyouall", - nickname: "rick" - }); - ajaxHash.success({ - "username": "clint", - "nickname": "rick", - "createdAt": "2011-11-07T20:58:34.448Z", - "updatedAt": "2011-11-07T20:58:34.448Z", - "objectId": "g7y9tkhB7O", - "sessionToken": "pnktnjyb996sj4p156gjtp4im" - }); - Ember.run(function(){ - promise.then(function(user){ - ok(get(user, "isLoaded")); - equal(get(user, "password"), null, "Be sure that password gets dumped."); - equal(get(user, "nickname"), "rick", "Additional attributes are added."); - }); - }) -}); diff --git a/test/ember-parse-adapter/serializer-test.js b/test/ember-parse-adapter/serializer-test.js deleted file mode 100644 index ecf9641..0000000 --- a/test/ember-parse-adapter/serializer-test.js +++ /dev/null @@ -1,96 +0,0 @@ -var get = Ember.get, - set = Ember.set; - -var container, store, serializer, Post, Comment; - -module("Unit - Serializer", { - setup: function() { - container = buildContainer(); - serializer = container.lookup('serializer:-parse'); - store = container.lookup('store:main'); - - container.register('model:post', DS.Model.extend({ - title: DS.attr('string') - })); - Post = store.modelFor('post'); - }, - - teardown: function() { - Ember.run(container, 'destroy'); - } -}); - -test("requires objectId as key", function(){ - equal(get(serializer, "primaryKey"), "objectId", "Should be objectId."); -}); - -test("a single post is extracted", function(){ - var id = 'test', - title = 'Test rooting'; - var res = serializer.extractSingle(store, Post, {objectId: id, title: title}, id); - equal(res.id, id, 'objectId should be put on post namespace'); - equal(res.title, title, 'Title should be put on post namespace'); -}); - -module("Integration - Serializer", { - setup: function() { - container = buildContainer(); - - container.register('model:post', DS.Model.extend({ - title: DS.attr('string'), - comments: DS.hasMany('comment') - })); - - container.register('model:comment', DS.Model.extend({ - content: DS.attr('string'), - post: DS.belongsTo('post') - })); - - container.register('serializer:post', EmberParseAdapter.Serializer); - container.register('serializer:comment', EmberParseAdapter.Serializer); - - store = container.lookup('store:main') - Post = store.modelFor('post'); - Comment = store.modelFor('comment'); - }, - - teardown: function() { - Ember.run(container, 'destroy'); - } -}); - -test("many posts are extracted", function(){ - var array = [{ - objectId: 'testA', - title: 'Test A' - }, { - objectId: 'testB', - title: 'Test B' - }]; - - var res = store.serializerFor(Post).extractArray(store, Post, { results: array }); - - equal(res.length, 2, 'normalized array of posts'); - - equal(res[0].id, 'testA', 'objectId should be put on post namespace'); - equal(res[0].title, 'Test A', 'Title should be put on post namespace'); - - equal(res[1].id, 'testB', 'objectId should be put on post namespace'); - equal(res[1].title, 'Test B', 'Title should be put on post namespace'); -}); - -pending("hasMany for serialization (Parse Pointer)", function(){ - var serialized, - hash = {}, - relationship = { options: { embedded: false }}, - post, - comment; - store.load(Post, "1", {title: 'Testing hasMany serialization.'}); - store.load(Comment, "1", {content: 'Comment 1'}); - post = store.find(Post, "1"); - comment = store.find(Comment, "1"); - post.get('comments').pushObject(comment); - serializer.addHasMany(hash, post, "comments", relationship); - equal(hash.comments[0]["__type"], "Pointer", "Should be a Pointer __type/class."); - equal(hash.comments[0]["className"], "Comment", "Should be Comment class."); -}); diff --git a/test/ember-parse-adapter/transforms/date-test.js b/test/ember-parse-adapter/transforms/date-test.js deleted file mode 100644 index bf1d74d..0000000 --- a/test/ember-parse-adapter/transforms/date-test.js +++ /dev/null @@ -1,33 +0,0 @@ -var transform; - -module("Unit - Date transform", { - setup: function(){ - transform = EmberParseAdapter.Transforms.Date.create(); - }, - teardown: function(){ - Ember.run(transform, "destroy"); - } -}); - -test("should serialize", function(){ - var date = new Date(2013,10,10); - var origIso = date.toISOString(); - var data = transform.serialize(date); - equal(data.iso, origIso, "iso is rendered"); - equal(data.__type, "Date", "has the proper type"); -}); - -test("should deserialize", function(){ - var data = { - iso: "2013-11-10T05:00:00.000Z", - __type: "Date" - }; - var date = transform.deserialize(data); - ok(date instanceof Date, "is a date"); - equal(date.getTime(), 1384059600000, "timestamp is correct"); -}); - -test("should deserialize null to null", function(){ - var file = transform.deserialize(null); - ok(file === null, "serialziation of null is null"); -}); diff --git a/test/ember-parse-adapter/transforms/file-test.js b/test/ember-parse-adapter/transforms/file-test.js deleted file mode 100644 index 60a6d45..0000000 --- a/test/ember-parse-adapter/transforms/file-test.js +++ /dev/null @@ -1,35 +0,0 @@ -var transform; - -module("Unit - File transform", { - setup: function(){ - transform = EmberParseAdapter.Transforms.File.create(); - }, - teardown: function(){ - Ember.run(transform, 'destroy'); - } -}); - -test("should serialize", function(){ - var file = new EmberParseAdapter.File('car', 'http://example.com/car.png'); - var data = transform.serialize(file); - equal(data.name, file.get('name'), 'name is preserved'); - equal(data.url, file.get('url'), 'url is preserved'); - equal(data.__type, 'File', 'has the proper type'); -}); - -test("should deserialize", function(){ - var data = { - name: 'Plane', - url: 'http://example.com/plane.png', - __type: 'File' - }; - var file = transform.deserialize(data); - ok(file instanceof EmberParseAdapter.File, 'is a geo point'); - equal(file.get('name'), data.name, 'name is preserved'); - equal(file.get('url'), data.url, 'url is preserved'); -}); - -test("should deserialize null to null", function(){ - var file = transform.deserialize(null); - ok(file === null, 'serialziation of null is null'); -}); diff --git a/test/ember-parse-adapter/transforms/geo-point-test.js b/test/ember-parse-adapter/transforms/geo-point-test.js deleted file mode 100644 index 6bd01e1..0000000 --- a/test/ember-parse-adapter/transforms/geo-point-test.js +++ /dev/null @@ -1,30 +0,0 @@ -var transform; - -module("Unit - GeoPoint transform", { - setup: function(){ - transform = EmberParseAdapter.Transforms.GeoPoint.create(); - }, - teardown: function(){ - Ember.run(transform, 'destroy'); - } -}); - -test("should serialize", function(){ - var geoPoint = new EmberParseAdapter.GeoPoint(4.53, 3,33); - var data = transform.serialize(geoPoint); - equal(data.latitude, geoPoint.get('latitude'), 'latitude is preserved'); - equal(data.longitude, geoPoint.get('longitude'), 'longitude is preserved'); - equal(data.__type, 'GeoPoint', 'has the proper type'); -}); - -test("should deserialize", function(){ - var data = { - latitude: 3.43, - longitude: 4.2, - __type: 'GeoPoint' - }; - var point = transform.deserialize(data); - ok(point instanceof EmberParseAdapter.GeoPoint, 'is a geo point'); - equal(point.get('latitude'), data.latitude, 'latitude is preserved'); - equal(point.get('longitude'), data.longitude, 'longitude is preserved'); -}); diff --git a/test/index.html b/test/index.html deleted file mode 100644 index 53bf859..0000000 --- a/test/index.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - Ember Data Adapter for Parse - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/lib/qunit-1.10.0.css b/test/lib/qunit-1.10.0.css deleted file mode 100644 index 7c0cda1..0000000 --- a/test/lib/qunit-1.10.0.css +++ /dev/null @@ -1,235 +0,0 @@ -/** - * QUnit v1.10.0 - A JavaScript Unit Testing Framework - * - * http://qunitjs.com - * - * Copyright 2012 jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -/** Font Family and Sizes */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { - font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; -} - -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } -#qunit-tests { font-size: smaller; } - - -/** Resets */ - -#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { - margin: 0; - padding: 0; -} - - -/** Header */ - -#qunit-header { - padding: 0.5em 0 0.5em 1em; - - color: #8699a4; - background-color: #0d3349; - - font-size: 1.5em; - line-height: 1em; - font-weight: normal; - - border-radius: 5px 5px 0 0; - -moz-border-radius: 5px 5px 0 0; - -webkit-border-top-right-radius: 5px; - -webkit-border-top-left-radius: 5px; -} - -#qunit-header a { - text-decoration: none; - color: #c2ccd1; -} - -#qunit-header a:hover, -#qunit-header a:focus { - color: #fff; -} - -#qunit-testrunner-toolbar label { - display: inline-block; - padding: 0 .5em 0 .1em; -} - -#qunit-banner { - height: 5px; -} - -#qunit-testrunner-toolbar { - padding: 0.5em 0 0.5em 2em; - color: #5E740B; - background-color: #eee; - overflow: hidden; -} - -#qunit-userAgent { - padding: 0.5em 0 0.5em 2.5em; - background-color: #2b81af; - color: #fff; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; -} - -#qunit-modulefilter-container { - float: right; -} - -/** Tests: Pass/Fail */ - -#qunit-tests { - list-style-position: inside; -} - -#qunit-tests li { - padding: 0.4em 0.5em 0.4em 2.5em; - border-bottom: 1px solid #fff; - list-style-position: inside; -} - -#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { - display: none; -} - -#qunit-tests li strong { - cursor: pointer; -} - -#qunit-tests li a { - padding: 0.5em; - color: #c2ccd1; - text-decoration: none; -} -#qunit-tests li a:hover, -#qunit-tests li a:focus { - color: #000; -} - -#qunit-tests ol { - margin-top: 0.5em; - padding: 0.5em; - - background-color: #fff; - - border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; -} - -#qunit-tests table { - border-collapse: collapse; - margin-top: .2em; -} - -#qunit-tests th { - text-align: right; - vertical-align: top; - padding: 0 .5em 0 0; -} - -#qunit-tests td { - vertical-align: top; -} - -#qunit-tests pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; -} - -#qunit-tests del { - background-color: #e0f2be; - color: #374e0c; - text-decoration: none; -} - -#qunit-tests ins { - background-color: #ffcaca; - color: #500; - text-decoration: none; -} - -/*** Test Counts */ - -#qunit-tests b.counts { color: black; } -#qunit-tests b.passed { color: #5E740B; } -#qunit-tests b.failed { color: #710909; } - -#qunit-tests li li { - padding: 5px; - background-color: #fff; - border-bottom: none; - list-style-position: inside; -} - -/*** Passing Styles */ - -#qunit-tests li li.pass { - color: #3c510c; - background-color: #fff; - border-left: 10px solid #C6E746; -} - -#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } -#qunit-tests .pass .test-name { color: #366097; } - -#qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999999; } - -#qunit-banner.qunit-pass { background-color: #C6E746; } - -/*** Failing Styles */ - -#qunit-tests li li.fail { - color: #710909; - background-color: #fff; - border-left: 10px solid #EE5757; - white-space: pre; -} - -#qunit-tests > li:last-child { - border-radius: 0 0 5px 5px; - -moz-border-radius: 0 0 5px 5px; - -webkit-border-bottom-right-radius: 5px; - -webkit-border-bottom-left-radius: 5px; -} - -#qunit-tests .fail { color: #000000; background-color: #EE5757; } -#qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000000; } - -#qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: green; } - -#qunit-banner.qunit-fail { background-color: #EE5757; } - - -/** Result */ - -#qunit-testresult { - padding: 0.5em 0.5em 0.5em 2.5em; - - color: #2b81af; - background-color: #D2E0E6; - - border-bottom: 1px solid white; -} -#qunit-testresult .module-name { - font-weight: bold; -} - -/** Fixture */ - -#qunit-fixture { - position: absolute; - top: -10000px; - left: -10000px; - width: 1000px; - height: 1000px; -} \ No newline at end of file diff --git a/test/lib/qunit-1.10.0.js b/test/lib/qunit-1.10.0.js deleted file mode 100644 index f68ddfc..0000000 --- a/test/lib/qunit-1.10.0.js +++ /dev/null @@ -1,1977 +0,0 @@ -/** - * QUnit v1.10.0 - A JavaScript Unit Testing Framework - * - * http://qunitjs.com - * - * Copyright 2012 jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -(function( window ) { - -var QUnit, - config, - onErrorFnPrev, - testId = 0, - fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - // Keep a local reference to Date (GH-283) - Date = window.Date, - defined = { - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch( e ) { - return false; - } - }()) -}; - -function Test( settings ) { - extend( this, settings ); - this.assertions = []; - this.testNumber = ++Test.count; -} - -Test.count = 0; - -Test.prototype = { - init: function() { - var a, b, li, - tests = id( "qunit-tests" ); - - if ( tests ) { - b = document.createElement( "strong" ); - b.innerHTML = this.name; - - // `a` initialized at top of scope - a = document.createElement( "a" ); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ testNumber: this.testNumber }); - - li = document.createElement( "li" ); - li.appendChild( b ); - li.appendChild( a ); - li.className = "running"; - li.id = this.id = "qunit-test-output" + testId++; - - tests.appendChild( li ); - } - }, - setup: function() { - if ( this.module !== config.previousModule ) { - if ( config.previousModule ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - runLoggingCallbacks( "moduleStart", QUnit, { - name: this.module - }); - } else if ( config.autorun ) { - runLoggingCallbacks( "moduleStart", QUnit, { - name: this.module - }); - } - - config.current = this; - - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment ); - - runLoggingCallbacks( "testStart", QUnit, { - name: this.testName, - module: this.module - }); - - // allow utility functions to access the current test environment - // TODO why?? - QUnit.current_testEnvironment = this.testEnvironment; - - if ( !config.pollution ) { - saveGlobal(); - } - if ( config.notrycatch ) { - this.testEnvironment.setup.call( this.testEnvironment ); - return; - } - try { - this.testEnvironment.setup.call( this.testEnvironment ); - } catch( e ) { - QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); - } - }, - run: function() { - config.current = this; - - var running = id( "qunit-testresult" ); - - if ( running ) { - running.innerHTML = "Running:
" + this.name; - } - - if ( this.async ) { - QUnit.stop(); - } - - if ( config.notrycatch ) { - this.callback.call( this.testEnvironment, QUnit.assert ); - return; - } - - try { - this.callback.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) ); - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - QUnit.start(); - } - } - }, - teardown: function() { - config.current = this; - if ( config.notrycatch ) { - this.testEnvironment.teardown.call( this.testEnvironment ); - return; - } else { - try { - this.testEnvironment.teardown.call( this.testEnvironment ); - } catch( e ) { - QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); - } - } - checkPollution(); - }, - finish: function() { - config.current = this; - if ( config.requireExpects && this.expected == null ) { - QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); - } else if ( this.expected != null && this.expected != this.assertions.length ) { - QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); - } else if ( this.expected == null && !this.assertions.length ) { - QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); - } - - var assertion, a, b, i, li, ol, - test = this, - good = 0, - bad = 0, - tests = id( "qunit-tests" ); - - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; - - if ( tests ) { - ol = document.createElement( "ol" ); - - for ( i = 0; i < this.assertions.length; i++ ) { - assertion = this.assertions[i]; - - li = document.createElement( "li" ); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - // store result when possible - if ( QUnit.config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); - } - } - - if ( bad === 0 ) { - ol.style.display = "none"; - } - - // `b` initialized at top of scope - b = document.createElement( "strong" ); - b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - addEvent(b, "click", function() { - var next = b.nextSibling.nextSibling, - display = next.style.display; - next.style.display = display === "none" ? "block" : "none"; - }); - - addEvent(b, "dblclick", function( e ) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ testNumber: test.testNumber }); - } - }); - - // `li` initialized at top of scope - li = id( this.id ); - li.className = bad ? "fail" : "pass"; - li.removeChild( li.firstChild ); - a = li.firstChild; - li.appendChild( b ); - li.appendChild ( a ); - li.appendChild( ol ); - - } else { - for ( i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - } - - runLoggingCallbacks( "testDone", QUnit, { - name: this.testName, - module: this.module, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length - }); - - QUnit.reset(); - - config.current = undefined; - }, - - queue: function() { - var bad, - test = this; - - synchronize(function() { - test.init(); - }); - function run() { - // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); - } - - // `bad` initialized at top of scope - // defer when previous test run passed, if storage is available - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); - - if ( bad ) { - run(); - } else { - synchronize( run, true ); - } - } -}; - -// Root QUnit object. -// `QUnit` initialized at top of scope -QUnit = { - - // call on start of module test to prepend name to all tests - module: function( name, testEnvironment ) { - config.currentModule = name; - config.currentModuleTestEnvironment = testEnvironment; - config.modules[name] = true; - }, - - asyncTest: function( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - QUnit.test( testName, expected, callback, true ); - }, - - test: function( testName, expected, callback, async ) { - var test, - name = "" + escapeInnerText( testName ) + ""; - - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - if ( config.currentModule ) { - name = "" + config.currentModule + ": " + name; - } - - test = new Test({ - name: name, - testName: testName, - expected: expected, - async: async, - callback: callback, - module: config.currentModule, - moduleTestEnvironment: config.currentModuleTestEnvironment, - stack: sourceFromStacktrace( 2 ) - }); - - if ( !validTest( test ) ) { - return; - } - - test.queue(); - }, - - // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. - expect: function( asserts ) { - if (arguments.length === 1) { - config.current.expected = asserts; - } else { - return config.current.expected; - } - }, - - start: function( count ) { - config.semaphore -= count || 1; - // don't start until equal number of stop-calls - if ( config.semaphore > 0 ) { - return; - } - // ignore if start is called more often then stop - if ( config.semaphore < 0 ) { - config.semaphore = 0; - } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - window.setTimeout(function() { - if ( config.semaphore > 0 ) { - return; - } - if ( config.timeout ) { - clearTimeout( config.timeout ); - } - - config.blocking = false; - process( true ); - }, 13); - } else { - config.blocking = false; - process( true ); - } - }, - - stop: function( count ) { - config.semaphore += count || 1; - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = window.setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - config.semaphore = 1; - QUnit.start(); - }, config.testTimeout ); - } - } -}; - -// Asssert helpers -// All of these must call either QUnit.push() or manually do: -// - runLoggingCallbacks( "log", .. ); -// - config.current.assertions.push({ .. }); -QUnit.assert = { - /** - * Asserts rough true-ish result. - * @name ok - * @function - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function( result, msg ) { - if ( !config.current ) { - throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - result = !!result; - - var source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: msg - }; - - msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); - msg = "" + msg + ""; - - if ( !result ) { - source = sourceFromStacktrace( 2 ); - if ( source ) { - details.source = source; - msg += "
Source:
" + escapeInnerText( source ) + "
"; - } - } - runLoggingCallbacks( "log", QUnit, details ); - config.current.assertions.push({ - result: result, - message: msg - }); - }, - - /** - * Assert that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * @name equal - * @function - * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); - */ - equal: function( actual, expected, message ) { - QUnit.push( expected == actual, actual, expected, message ); - }, - - /** - * @name notEqual - * @function - */ - notEqual: function( actual, expected, message ) { - QUnit.push( expected != actual, actual, expected, message ); - }, - - /** - * @name deepEqual - * @function - */ - deepEqual: function( actual, expected, message ) { - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name notDeepEqual - * @function - */ - notDeepEqual: function( actual, expected, message ) { - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name strictEqual - * @function - */ - strictEqual: function( actual, expected, message ) { - QUnit.push( expected === actual, actual, expected, message ); - }, - - /** - * @name notStrictEqual - * @function - */ - notStrictEqual: function( actual, expected, message ) { - QUnit.push( expected !== actual, actual, expected, message ); - }, - - throws: function( block, expected, message ) { - var actual, - ok = false; - - // 'expected' is optional - if ( typeof expected === "string" ) { - message = expected; - expected = null; - } - - config.current.ignoreGlobalErrors = true; - try { - block.call( config.current.testEnvironment ); - } catch (e) { - actual = e; - } - config.current.ignoreGlobalErrors = false; - - if ( actual ) { - // we don't want to validate thrown error - if ( !expected ) { - ok = true; - // expected is a regexp - } else if ( QUnit.objectType( expected ) === "regexp" ) { - ok = expected.test( actual ); - // expected is a constructor - } else if ( actual instanceof expected ) { - ok = true; - // expected is a validation function which returns true is validation passed - } else if ( expected.call( {}, actual ) === true ) { - ok = true; - } - - QUnit.push( ok, actual, null, message ); - } else { - QUnit.pushFailure( message, null, 'No exception was thrown.' ); - } - } -}; - -/** - * @deprecate since 1.8.0 - * Kept assertion helpers in root for backwards compatibility - */ -extend( QUnit, QUnit.assert ); - -/** - * @deprecated since 1.9.0 - * Kept global "raises()" for backwards compatibility - */ -QUnit.raises = QUnit.assert.throws; - -/** - * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.equals = function() { - QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); -}; -QUnit.same = function() { - QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); -}; - -// We want access to the constructor's prototype -(function() { - function F() {} - F.prototype = QUnit; - QUnit = new F(); - // Make F QUnit's constructor so that we can add to the prototype later - QUnit.constructor = F; -}()); - -/** - * Config object: Maintain internal state - * Later exposed as QUnit.config - * `config` initialized at top of scope - */ -config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true, - - // when enabled, show only failing tests - // gets persisted through sessionStorage and can be changed in UI via checkbox - hidepassed: false, - - // by default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - // by default, modify document.title when suite is done - altertitle: true, - - // when enabled, all tests must call expect() - requireExpects: false, - - // add checkboxes that are persisted in the query-string - // when enabled, the id is set to `true` as a `QUnit.config` property - urlConfig: [ - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." - } - ], - - // Set of all modules. - modules: {}, - - // logging callback queues - begin: [], - done: [], - log: [], - testStart: [], - testDone: [], - moduleStart: [], - moduleDone: [] -}; - -// Initialize more QUnit.config and QUnit.urlParams -(function() { - var i, - location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}, - current; - - if ( params[ 0 ] ) { - for ( i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - urlParams[ current[ 0 ] ] = current[ 1 ]; - } - } - - QUnit.urlParams = urlParams; - - // String search anywhere in moduleName+testName - config.filter = urlParams.filter; - - // Exact match of the module name - config.module = urlParams.module; - - config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = location.protocol === "file:"; -}()); - -// Export global variables, unless an 'exports' object exists, -// in that case we assume we're in CommonJS (dealt with on the bottom of the script) -if ( typeof exports === "undefined" ) { - extend( window, QUnit ); - - // Expose QUnit object - window.QUnit = QUnit; -} - -// Extend QUnit object, -// these after set here because they should not be exposed as global functions -extend( QUnit, { - config: config, - - // Initialize the configuration options - init: function() { - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date(), - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - filter: "", - queue: [], - semaphore: 0 - }); - - var tests, banner, result, - qunit = id( "qunit" ); - - if ( qunit ) { - qunit.innerHTML = - "

" + escapeInnerText( document.title ) + "

" + - "

" + - "
" + - "

" + - "
    "; - } - - tests = id( "qunit-tests" ); - banner = id( "qunit-banner" ); - result = id( "qunit-testresult" ); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
     "; - } - }, - - // Resets the test setup. Useful for tests that modify the DOM. - reset: function() { - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } - }, - - // Trigger an event on an element. - // @example triggerEvent( document.body, "click" ); - triggerEvent: function( elem, type, event ) { - if ( document.createEvent ) { - event = document.createEvent( "MouseEvents" ); - event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, - 0, 0, 0, 0, 0, false, false, false, false, 0, null); - - elem.dispatchEvent( event ); - } else if ( elem.fireEvent ) { - elem.fireEvent( "on" + type ); - } - }, - - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) == type; - }, - - objectType: function( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - // consider: typeof null === object - } - if ( obj === null ) { - return "null"; - } - - var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; - - switch ( type ) { - case "Number": - if ( isNaN(obj) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Date": - case "RegExp": - case "Function": - return type.toLowerCase(); - } - if ( typeof obj === "object" ) { - return "object"; - } - return undefined; - }, - - push: function( result, actual, expected, message ) { - if ( !config.current ) { - throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); - } - - var output, source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: message, - actual: actual, - expected: expected - }; - - message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); - message = "" + message + ""; - output = message; - - if ( !result ) { - expected = escapeInnerText( QUnit.jsDump.parse(expected) ); - actual = escapeInnerText( QUnit.jsDump.parse(actual) ); - output += ""; - - if ( actual != expected ) { - output += ""; - output += ""; - } - - source = sourceFromStacktrace(); - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    "; - } - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: !!result, - message: output - }); - }, - - pushFailure: function( message, source, actual ) { - if ( !config.current ) { - throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - - var output, - details = { - module: config.current.module, - name: config.current.testName, - result: false, - message: message - }; - - message = escapeInnerText( message ) || "error"; - message = "" + message + ""; - output = message; - - output += ""; - - if ( actual ) { - output += ""; - } - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
    Result:
    " + escapeInnerText( actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    "; - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: false, - message: output - }); - }, - - url: function( params ) { - params = extend( extend( {}, QUnit.urlParams ), params ); - var key, - querystring = "?"; - - for ( key in params ) { - if ( !hasOwn.call( params, key ) ) { - continue; - } - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; - } - return window.location.pathname + querystring.slice( 0, -1 ); - }, - - extend: extend, - id: id, - addEvent: addEvent - // load, equiv, jsDump, diff: Attached later -}); - -/** - * @deprecated: Created for backwards compatibility with test runner that set the hook function - * into QUnit.{hook}, instead of invoking it and passing the hook function. - * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. - * Doing this allows us to tell if the following methods have been overwritten on the actual - * QUnit object. - */ -extend( QUnit.constructor.prototype, { - - // Logging callbacks; all receive a single argument with the listed properties - // run test/logs.html for any related changes - begin: registerLoggingCallback( "begin" ), - - // done: { failed, passed, total, runtime } - done: registerLoggingCallback( "done" ), - - // log: { result, actual, expected, message } - log: registerLoggingCallback( "log" ), - - // testStart: { name } - testStart: registerLoggingCallback( "testStart" ), - - // testDone: { name, failed, passed, total } - testDone: registerLoggingCallback( "testDone" ), - - // moduleStart: { name } - moduleStart: registerLoggingCallback( "moduleStart" ), - - // moduleDone: { name, failed, passed, total } - moduleDone: registerLoggingCallback( "moduleDone" ) -}); - -if ( typeof document === "undefined" || document.readyState === "complete" ) { - config.autorun = true; -} - -QUnit.load = function() { - runLoggingCallbacks( "begin", QUnit, {} ); - - // Initialize the config, saving the execution queue - var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter, - numModules = 0, - moduleFilterHtml = "", - urlConfigHtml = "", - oldconfig = extend( {}, config ); - - QUnit.init(); - extend(config, oldconfig); - - config.blocking = false; - - len = config.urlConfig.length; - - for ( i = 0; i < len; i++ ) { - val = config.urlConfig[i]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val, - tooltip: "[no tooltip available]" - }; - } - config[ val.id ] = QUnit.urlParams[ val.id ]; - urlConfigHtml += ""; - } - - moduleFilterHtml += ""; - - // `userAgent` initialized at top of scope - userAgent = id( "qunit-userAgent" ); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } - - // `banner` initialized at top of scope - banner = id( "qunit-header" ); - if ( banner ) { - banner.innerHTML = "" + banner.innerHTML + " "; - } - - // `toolbar` initialized at top of scope - toolbar = id( "qunit-testrunner-toolbar" ); - if ( toolbar ) { - // `filter` initialized at top of scope - filter = document.createElement( "input" ); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - - addEvent( filter, "click", function() { - var tmp, - ol = document.getElementById( "qunit-tests" ); - - if ( filter.checked ) { - ol.className = ol.className + " hidepass"; - } else { - tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace( / hidepass /, " " ); - } - if ( defined.sessionStorage ) { - if (filter.checked) { - sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); - } else { - sessionStorage.removeItem( "qunit-filter-passed-tests" ); - } - } - }); - - if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { - filter.checked = true; - // `ol` initialized at top of scope - ol = document.getElementById( "qunit-tests" ); - ol.className = ol.className + " hidepass"; - } - toolbar.appendChild( filter ); - - // `label` initialized at top of scope - label = document.createElement( "label" ); - label.setAttribute( "for", "qunit-filter-pass" ); - label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." ); - label.innerHTML = "Hide passed tests"; - toolbar.appendChild( label ); - - urlConfigCheckboxes = document.createElement( 'span' ); - urlConfigCheckboxes.innerHTML = urlConfigHtml; - addEvent( urlConfigCheckboxes, "change", function( event ) { - var params = {}; - params[ event.target.name ] = event.target.checked ? true : undefined; - window.location = QUnit.url( params ); - }); - toolbar.appendChild( urlConfigCheckboxes ); - - if (numModules > 1) { - moduleFilter = document.createElement( 'span' ); - moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' ); - moduleFilter.innerHTML = moduleFilterHtml; - addEvent( moduleFilter, "change", function() { - var selectBox = moduleFilter.getElementsByTagName("select")[0], - selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); - - window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } ); - }); - toolbar.appendChild(moduleFilter); - } - } - - // `main` initialized at top of scope - main = id( "qunit-fixture" ); - if ( main ) { - config.fixture = main.innerHTML; - } - - if ( config.autostart ) { - QUnit.start(); - } -}; - -addEvent( window, "load", QUnit.load ); - -// `onErrorFnPrev` initialized at top of scope -// Preserve other handlers -onErrorFnPrev = window.onerror; - -// Cover uncaught exceptions -// Returning true will surpress the default browser handler, -// returning false will let it run. -window.onerror = function ( error, filePath, linerNr ) { - var ret = false; - if ( onErrorFnPrev ) { - ret = onErrorFnPrev( error, filePath, linerNr ); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not surpressed. - if ( ret !== true ) { - if ( QUnit.config.current ) { - if ( QUnit.config.current.ignoreGlobalErrors ) { - return true; - } - QUnit.pushFailure( error, filePath + ":" + linerNr ); - } else { - QUnit.test( "global failure", extend( function() { - QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: validTest } ) ); - } - return false; - } - - return ret; -}; - -function done() { - config.autorun = true; - - // Log the last module results - if ( config.currentModule ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.currentModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - runtime = +new Date() - config.started, - passed = config.stats.all - config.stats.bad, - html = [ - "Tests completed in ", - runtime, - " milliseconds.
    ", - "", - passed, - " tests of ", - config.stats.all, - " passed, ", - config.stats.bad, - " failed." - ].join( "" ); - - if ( banner ) { - banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( config.altertitle && typeof document !== "undefined" && document.title ) { - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( config.stats.bad ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { - // `key` & `i` initialized at top of scope - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); - } - } - } - - // scroll back to top to show results - if ( window.scrollTo ) { - window.scrollTo(0, 0); - } - - runLoggingCallbacks( "done", QUnit, { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - }); -} - -/** @return Boolean: true if this test should be ran */ -function validTest( test ) { - var include, - filter = config.filter && config.filter.toLowerCase(), - module = config.module && config.module.toLowerCase(), - fullName = (test.module + ": " + test.testName).toLowerCase(); - - // Internally-generated tests are always valid - if ( test.callback && test.callback.validTest === validTest ) { - delete test.callback.validTest; - return true; - } - - if ( config.testNumber ) { - return test.testNumber === config.testNumber; - } - - if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { - return false; - } - - if ( !filter ) { - return true; - } - - include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.slice( 1 ); - } - - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } - - // Otherwise, do the opposite - return !include; -} - -// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) -// Later Safari and IE10 are supposed to support error.stack as well -// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack -function extractStacktrace( e, offset ) { - offset = offset === undefined ? 3 : offset; - - var stack, include, i, regex; - - if ( e.stacktrace ) { - // Opera - return e.stacktrace.split( "\n" )[ offset + 3 ]; - } else if ( e.stack ) { - // Firefox, Chrome - stack = e.stack.split( "\n" ); - if (/^error$/i.test( stack[0] ) ) { - stack.shift(); - } - if ( fileName ) { - include = []; - for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) != -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } else if ( e.sourceURL ) { - // Safari, PhantomJS - // hopefully one day Safari provides actual stacktraces - // exclude useless self-reference for generated Error objects - if ( /qunit.js$/.test( e.sourceURL ) ) { - return; - } - // for actual exceptions, this is useful - return e.sourceURL + ":" + e.line; - } -} -function sourceFromStacktrace( offset ) { - try { - throw new Error(); - } catch ( e ) { - return extractStacktrace( e, offset ); - } -} - -function escapeInnerText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - return s.replace( /[\&<>]/g, function( s ) { - switch( s ) { - case "&": return "&"; - case "<": return "<"; - case ">": return ">"; - default: return s; - } - }); -} - -function synchronize( callback, last ) { - config.queue.push( callback ); - - if ( config.autorun && !config.blocking ) { - process( last ); - } -} - -function process( last ) { - function next() { - process( last ); - } - var start = new Date().getTime(); - config.depth = config.depth ? config.depth + 1 : 1; - - while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { - config.queue.shift()(); - } else { - window.setTimeout( next, 13 ); - break; - } - } - config.depth--; - if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { - done(); - } -} - -function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( var key in window ) { - // in Opera sometimes DOM element ids show up here, ignore them - if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); - } - } -} - -function checkPollution( name ) { - var newGlobals, - deletedGlobals, - old = config.pollution; - - saveGlobal(); - - newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); - } - - deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); - } -} - -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var i, j, - result = a.slice(); - - for ( i = 0; i < result.length; i++ ) { - for ( j = 0; j < b.length; j++ ) { - if ( result[i] === b[j] ) { - result.splice( i, 1 ); - i--; - break; - } - } - } - return result; -} - -function extend( a, b ) { - for ( var prop in b ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - - // Avoid "Member not found" error in IE8 caused by setting window.constructor - } else if ( prop !== "constructor" || a !== window ) { - a[ prop ] = b[ prop ]; - } - } - - return a; -} - -function addEvent( elem, type, fn ) { - if ( elem.addEventListener ) { - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, fn ); - } else { - fn(); - } -} - -function id( name ) { - return !!( typeof document !== "undefined" && document && document.getElementById ) && - document.getElementById( name ); -} - -function registerLoggingCallback( key ) { - return function( callback ) { - config[key].push( callback ); - }; -} - -// Supports deprecated method of completely overwriting logging callbacks -function runLoggingCallbacks( key, scope, args ) { - //debugger; - var i, callbacks; - if ( QUnit.hasOwnProperty( key ) ) { - QUnit[ key ].call(scope, args ); - } else { - callbacks = config[ key ]; - for ( i = 0; i < callbacks.length; i++ ) { - callbacks[ i ].call( scope, args ); - } - } -} - -// Test for equality any JavaScript type. -// Author: Philippe Rathé -QUnit.equiv = (function() { - - // Call the o related callback with the given arguments. - function bindCallbacks( o, callbacks, args ) { - var prop = QUnit.objectType( o ); - if ( prop ) { - if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { - return callbacks[ prop ].apply( callbacks, args ); - } else { - return callbacks[ prop ]; // or undefined - } - } - } - - // the real equiv function - var innerEquiv, - // stack to decide between skip/abort functions - callers = [], - // stack to avoiding loops from circular referencing - parents = [], - - getProto = Object.getPrototypeOf || function ( obj ) { - return obj.__proto__; - }, - callbacks = (function () { - - // for string, boolean, number and null - function useStrictEquality( b, a ) { - if ( b instanceof a.constructor || a instanceof b.constructor ) { - // to catch short annotaion VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function( b ) { - return isNaN( b ); - }, - - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && - // the regex itself - a.source === b.source && - // and its modifers - a.global === b.global && - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function() { - var caller = callers[callers.length - 1]; - return caller !== Object && typeof caller !== "undefined"; - }, - - "array": function( b, a ) { - var i, j, len, loop; - - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } - - len = a.length; - if ( len !== b.length ) { - // safe and faster - return false; - } - - // track reference to avoid circular references - parents.push( a ); - for ( i = 0; i < len; i++ ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - if ( parents[j] === a[i] ) { - loop = true;// dont rewalk array - } - } - if ( !loop && !innerEquiv(a[i], b[i]) ) { - parents.pop(); - return false; - } - } - parents.pop(); - return true; - }, - - "object": function( b, a ) { - var i, j, loop, - // Default to true - eq = true, - aProperties = [], - bProperties = []; - - // comparing constructors is more strict than using - // instanceof - if ( a.constructor !== b.constructor ) { - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || - ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { - return false; - } - } - - // stack constructor before traversing properties - callers.push( a.constructor ); - // track reference to avoid circular references - parents.push( a ); - - for ( i in a ) { // be strict: don't ensures hasOwnProperty - // and go deep - loop = false; - for ( j = 0; j < parents.length; j++ ) { - if ( parents[j] === a[i] ) { - // don't go down the same path twice - loop = true; - } - } - aProperties.push(i); // collect a's properties - - if (!loop && !innerEquiv( a[i], b[i] ) ) { - eq = false; - break; - } - } - - callers.pop(); // unstack, we are done - parents.pop(); - - for ( i in b ) { - bProperties.push( i ); // collect b's properties - } - - // Ensures identical properties name - return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); - } - }; - }()); - - innerEquiv = function() { // can take multiple arguments - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { - return true; // end transition - } - - return (function( a, b ) { - if ( a === b ) { - return true; // catch the most you can - } else if ( a === null || b === null || typeof a === "undefined" || - typeof b === "undefined" || - QUnit.objectType(a) !== QUnit.objectType(b) ) { - return false; // don't lose time with error prone cases - } else { - return bindCallbacks(a, callbacks, [ b, a ]); - } - - // apply transition with (1..n) arguments - }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) ); - }; - - return innerEquiv; -}()); - -/** - * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | - * http://flesler.blogspot.com Licensed under BSD - * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 - * - * @projectDescription Advanced and extensible data dumping for Javascript. - * @version 1.0.0 - * @author Ariel Flesler - * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} - */ -QUnit.jsDump = (function() { - function quote( str ) { - return '"' + str.toString().replace( /"/g, '\\"' ) + '"'; - } - function literal( o ) { - return o + ""; - } - function join( pre, arr, post ) { - var s = jsDump.separator(), - base = jsDump.indent(), - inner = jsDump.indent(1); - if ( arr.join ) { - arr = arr.join( "," + s + inner ); - } - if ( !arr ) { - return pre + post; - } - return [ pre, inner + arr, base + post ].join(s); - } - function array( arr, stack ) { - var i = arr.length, ret = new Array(i); - this.up(); - while ( i-- ) { - ret[i] = this.parse( arr[i] , undefined , stack); - } - this.down(); - return join( "[", ret, "]" ); - } - - var reName = /^function (\w+)/, - jsDump = { - parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance - stack = stack || [ ]; - var inStack, res, - parser = this.parsers[ type || this.typeOf(obj) ]; - - type = typeof parser; - inStack = inArray( obj, stack ); - - if ( inStack != -1 ) { - return "recursion(" + (inStack - stack.length) + ")"; - } - //else - if ( type == "function" ) { - stack.push( obj ); - res = parser.call( this, obj, stack ); - stack.pop(); - return res; - } - // else - return ( type == "string" ) ? parser : this.parsers.error; - }, - typeOf: function( obj ) { - var type; - if ( obj === null ) { - type = "null"; - } else if ( typeof obj === "undefined" ) { - type = "undefined"; - } else if ( QUnit.is( "regexp", obj) ) { - type = "regexp"; - } else if ( QUnit.is( "date", obj) ) { - type = "date"; - } else if ( QUnit.is( "function", obj) ) { - type = "function"; - } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { - type = "window"; - } else if ( obj.nodeType === 9 ) { - type = "document"; - } else if ( obj.nodeType ) { - type = "node"; - } else if ( - // native arrays - toString.call( obj ) === "[object Array]" || - // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) - ) { - type = "array"; - } else { - type = typeof obj; - } - return type; - }, - separator: function() { - return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; - }, - indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing - if ( !this.multiline ) { - return ""; - } - var chr = this.indentChar; - if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); - } - return new Array( this._depth_ + (extra||0) ).join(chr); - }, - up: function( a ) { - this._depth_ += a || 1; - }, - down: function( a ) { - this._depth_ -= a || 1; - }, - setParser: function( name, parser ) { - this.parsers[name] = parser; - }, - // The next 3 are exposed so you can use them - quote: quote, - literal: literal, - join: join, - // - _depth_: 1, - // This is the list of parsers, to modify them, use jsDump.setParser - parsers: { - window: "[Window]", - document: "[Document]", - error: "[ERROR]", //when no parser is found, shouldn"t happen - unknown: "[Unknown]", - "null": "null", - "undefined": "undefined", - "function": function( fn ) { - var ret = "function", - name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE - - if ( name ) { - ret += " " + name; - } - ret += "( "; - - ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); - }, - array: array, - nodelist: array, - "arguments": array, - object: function( map, stack ) { - var ret = [ ], keys, key, val, i; - QUnit.jsDump.up(); - if ( Object.keys ) { - keys = Object.keys( map ); - } else { - keys = []; - for ( key in map ) { - keys.push( key ); - } - } - keys.sort(); - for ( i = 0; i < keys.length; i++ ) { - key = keys[ i ]; - val = map[ key ]; - ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); - } - QUnit.jsDump.down(); - return join( "{", ret, "}" ); - }, - node: function( node ) { - var a, val, - open = QUnit.jsDump.HTML ? "<" : "<", - close = QUnit.jsDump.HTML ? ">" : ">", - tag = node.nodeName.toLowerCase(), - ret = open + tag; - - for ( a in QUnit.jsDump.DOMAttrs ) { - val = node[ QUnit.jsDump.DOMAttrs[a] ]; - if ( val ) { - ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); - } - } - return ret + close + open + "/" + tag + close; - }, - functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function - var args, - l = fn.length; - - if ( !l ) { - return ""; - } - - args = new Array(l); - while ( l-- ) { - args[l] = String.fromCharCode(97+l);//97 is 'a' - } - return " " + args.join( ", " ) + " "; - }, - key: quote, //object calls it internally, the key part of an item in a map - functionCode: "[code]", //function calls it internally, it's the content of the function - attribute: quote, //node calls it internally, it's an html attribute value - string: quote, - date: quote, - regexp: literal, //regex - number: literal, - "boolean": literal - }, - DOMAttrs: { - //attributes to dump from nodes, name=>realName - id: "id", - name: "name", - "class": "className" - }, - HTML: false,//if true, entities are escaped ( <, >, \t, space and \n ) - indentChar: " ",//indentation unit - multiline: true //if true, items in a collection, are separated by a \n, else just a space. - }; - - return jsDump; -}()); - -// from Sizzle.js -function getText( elems ) { - var i, elem, - ret = ""; - - for ( i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += getText( elem.childNodes ); - } - } - - return ret; -} - -// from jquery.js -function inArray( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; -} - -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" - */ -QUnit.diff = (function() { - function diff( o, n ) { - var i, - ns = {}, - os = {}; - - for ( i = 0; i < n.length; i++ ) { - if ( ns[ n[i] ] == null ) { - ns[ n[i] ] = { - rows: [], - o: null - }; - } - ns[ n[i] ].rows.push( i ); - } - - for ( i = 0; i < o.length; i++ ) { - if ( os[ o[i] ] == null ) { - os[ o[i] ] = { - rows: [], - n: null - }; - } - os[ o[i] ].rows.push( i ); - } - - for ( i in ns ) { - if ( !hasOwn.call( ns, i ) ) { - continue; - } - if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { - n[ ns[i].rows[0] ] = { - text: n[ ns[i].rows[0] ], - row: os[i].rows[0] - }; - o[ os[i].rows[0] ] = { - text: o[ os[i].rows[0] ], - row: ns[i].rows[0] - }; - } - } - - for ( i = 0; i < n.length - 1; i++ ) { - if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && - n[ i + 1 ] == o[ n[i].row + 1 ] ) { - - n[ i + 1 ] = { - text: n[ i + 1 ], - row: n[i].row + 1 - }; - o[ n[i].row + 1 ] = { - text: o[ n[i].row + 1 ], - row: i + 1 - }; - } - } - - for ( i = n.length - 1; i > 0; i-- ) { - if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && - n[ i - 1 ] == o[ n[i].row - 1 ]) { - - n[ i - 1 ] = { - text: n[ i - 1 ], - row: n[i].row - 1 - }; - o[ n[i].row - 1 ] = { - text: o[ n[i].row - 1 ], - row: i - 1 - }; - } - } - - return { - o: o, - n: n - }; - } - - return function( o, n ) { - o = o.replace( /\s+$/, "" ); - n = n.replace( /\s+$/, "" ); - - var i, pre, - str = "", - out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), - oSpace = o.match(/\s+/g), - nSpace = n.match(/\s+/g); - - if ( oSpace == null ) { - oSpace = [ " " ]; - } - else { - oSpace.push( " " ); - } - - if ( nSpace == null ) { - nSpace = [ " " ]; - } - else { - nSpace.push( " " ); - } - - if ( out.n.length === 0 ) { - for ( i = 0; i < out.o.length; i++ ) { - str += "" + out.o[i] + oSpace[i] + ""; - } - } - else { - if ( out.n[0].text == null ) { - for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { - str += "" + out.o[n] + oSpace[n] + ""; - } - } - - for ( i = 0; i < out.n.length; i++ ) { - if (out.n[i].text == null) { - str += "" + out.n[i] + nSpace[i] + ""; - } - else { - // `pre` initialized at top of scope - pre = ""; - - for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { - pre += "" + out.o[n] + oSpace[n] + ""; - } - str += " " + out.n[i].text + nSpace[i] + pre; - } - } - } - - return str; - }; -}()); - -// for CommonJS enviroments, export everything -if ( typeof exports !== "undefined" ) { - extend(exports, QUnit); -} - -// get at whatever the global object is, like window in browsers -}( (function() {return this;}.call()) )); \ No newline at end of file diff --git a/test/lib/qunit-1.11.0.css b/test/lib/qunit-1.11.0.css deleted file mode 100644 index d7fc0c8..0000000 --- a/test/lib/qunit-1.11.0.css +++ /dev/null @@ -1,244 +0,0 @@ -/** - * QUnit v1.11.0 - A JavaScript Unit Testing Framework - * - * http://qunitjs.com - * - * Copyright 2012 jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -/** Font Family and Sizes */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { - font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; -} - -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } -#qunit-tests { font-size: smaller; } - - -/** Resets */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { - margin: 0; - padding: 0; -} - - -/** Header */ - -#qunit-header { - padding: 0.5em 0 0.5em 1em; - - color: #8699a4; - background-color: #0d3349; - - font-size: 1.5em; - line-height: 1em; - font-weight: normal; - - border-radius: 5px 5px 0 0; - -moz-border-radius: 5px 5px 0 0; - -webkit-border-top-right-radius: 5px; - -webkit-border-top-left-radius: 5px; -} - -#qunit-header a { - text-decoration: none; - color: #c2ccd1; -} - -#qunit-header a:hover, -#qunit-header a:focus { - color: #fff; -} - -#qunit-testrunner-toolbar label { - display: inline-block; - padding: 0 .5em 0 .1em; -} - -#qunit-banner { - height: 5px; -} - -#qunit-testrunner-toolbar { - padding: 0.5em 0 0.5em 2em; - color: #5E740B; - background-color: #eee; - overflow: hidden; -} - -#qunit-userAgent { - padding: 0.5em 0 0.5em 2.5em; - background-color: #2b81af; - color: #fff; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; -} - -#qunit-modulefilter-container { - float: right; -} - -/** Tests: Pass/Fail */ - -#qunit-tests { - list-style-position: inside; -} - -#qunit-tests li { - padding: 0.4em 0.5em 0.4em 2.5em; - border-bottom: 1px solid #fff; - list-style-position: inside; -} - -#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { - display: none; -} - -#qunit-tests li strong { - cursor: pointer; -} - -#qunit-tests li a { - padding: 0.5em; - color: #c2ccd1; - text-decoration: none; -} -#qunit-tests li a:hover, -#qunit-tests li a:focus { - color: #000; -} - -#qunit-tests li .runtime { - float: right; - font-size: smaller; -} - -.qunit-assert-list { - margin-top: 0.5em; - padding: 0.5em; - - background-color: #fff; - - border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; -} - -.qunit-collapsed { - display: none; -} - -#qunit-tests table { - border-collapse: collapse; - margin-top: .2em; -} - -#qunit-tests th { - text-align: right; - vertical-align: top; - padding: 0 .5em 0 0; -} - -#qunit-tests td { - vertical-align: top; -} - -#qunit-tests pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; -} - -#qunit-tests del { - background-color: #e0f2be; - color: #374e0c; - text-decoration: none; -} - -#qunit-tests ins { - background-color: #ffcaca; - color: #500; - text-decoration: none; -} - -/*** Test Counts */ - -#qunit-tests b.counts { color: black; } -#qunit-tests b.passed { color: #5E740B; } -#qunit-tests b.failed { color: #710909; } - -#qunit-tests li li { - padding: 5px; - background-color: #fff; - border-bottom: none; - list-style-position: inside; -} - -/*** Passing Styles */ - -#qunit-tests li li.pass { - color: #3c510c; - background-color: #fff; - border-left: 10px solid #C6E746; -} - -#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } -#qunit-tests .pass .test-name { color: #366097; } - -#qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999999; } - -#qunit-banner.qunit-pass { background-color: #C6E746; } - -/*** Failing Styles */ - -#qunit-tests li li.fail { - color: #710909; - background-color: #fff; - border-left: 10px solid #EE5757; - white-space: pre; -} - -#qunit-tests > li:last-child { - border-radius: 0 0 5px 5px; - -moz-border-radius: 0 0 5px 5px; - -webkit-border-bottom-right-radius: 5px; - -webkit-border-bottom-left-radius: 5px; -} - -#qunit-tests .fail { color: #000000; background-color: #EE5757; } -#qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000000; } - -#qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: green; } - -#qunit-banner.qunit-fail { background-color: #EE5757; } - - -/** Result */ - -#qunit-testresult { - padding: 0.5em 0.5em 0.5em 2.5em; - - color: #2b81af; - background-color: #D2E0E6; - - border-bottom: 1px solid white; -} -#qunit-testresult .module-name { - font-weight: bold; -} - -/** Fixture */ - -#qunit-fixture { - position: absolute; - top: -10000px; - left: -10000px; - width: 1000px; - height: 1000px; -} diff --git a/test/lib/qunit-1.11.0.js b/test/lib/qunit-1.11.0.js deleted file mode 100644 index 302545f..0000000 --- a/test/lib/qunit-1.11.0.js +++ /dev/null @@ -1,2152 +0,0 @@ -/** - * QUnit v1.11.0 - A JavaScript Unit Testing Framework - * - * http://qunitjs.com - * - * Copyright 2012 jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -(function( window ) { - -var QUnit, - assert, - config, - onErrorFnPrev, - testId = 0, - fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - // Keep a local reference to Date (GH-283) - Date = window.Date, - defined = { - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch( e ) { - return false; - } - }()) - }, - /** - * Provides a normalized error string, correcting an issue - * with IE 7 (and prior) where Error.prototype.toString is - * not properly implemented - * - * Based on http://es5.github.com/#x15.11.4.4 - * - * @param {String|Error} error - * @return {String} error message - */ - errorString = function( error ) { - var name, message, - errorString = error.toString(); - if ( errorString.substring( 0, 7 ) === "[object" ) { - name = error.name ? error.name.toString() : "Error"; - message = error.message ? error.message.toString() : ""; - if ( name && message ) { - return name + ": " + message; - } else if ( name ) { - return name; - } else if ( message ) { - return message; - } else { - return "Error"; - } - } else { - return errorString; - } - }, - /** - * Makes a clone of an object using only Array or Object as base, - * and copies over the own enumerable properties. - * - * @param {Object} obj - * @return {Object} New object with only the own properties (recursively). - */ - objectValues = function( obj ) { - // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. - /*jshint newcap: false */ - var key, val, - vals = QUnit.is( "array", obj ) ? [] : {}; - for ( key in obj ) { - if ( hasOwn.call( obj, key ) ) { - val = obj[key]; - vals[key] = val === Object(val) ? objectValues(val) : val; - } - } - return vals; - }; - -function Test( settings ) { - extend( this, settings ); - this.assertions = []; - this.testNumber = ++Test.count; -} - -Test.count = 0; - -Test.prototype = { - init: function() { - var a, b, li, - tests = id( "qunit-tests" ); - - if ( tests ) { - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml; - - // `a` initialized at top of scope - a = document.createElement( "a" ); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ testNumber: this.testNumber }); - - li = document.createElement( "li" ); - li.appendChild( b ); - li.appendChild( a ); - li.className = "running"; - li.id = this.id = "qunit-test-output" + testId++; - - tests.appendChild( li ); - } - }, - setup: function() { - if ( this.module !== config.previousModule ) { - if ( config.previousModule ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - runLoggingCallbacks( "moduleStart", QUnit, { - name: this.module - }); - } else if ( config.autorun ) { - runLoggingCallbacks( "moduleStart", QUnit, { - name: this.module - }); - } - - config.current = this; - - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment ); - - this.started = +new Date(); - runLoggingCallbacks( "testStart", QUnit, { - name: this.testName, - module: this.module - }); - - // allow utility functions to access the current test environment - // TODO why?? - QUnit.current_testEnvironment = this.testEnvironment; - - if ( !config.pollution ) { - saveGlobal(); - } - if ( config.notrycatch ) { - this.testEnvironment.setup.call( this.testEnvironment ); - return; - } - try { - this.testEnvironment.setup.call( this.testEnvironment ); - } catch( e ) { - QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); - } - }, - run: function() { - config.current = this; - - var running = id( "qunit-testresult" ); - - if ( running ) { - running.innerHTML = "Running:
    " + this.nameHtml; - } - - if ( this.async ) { - QUnit.stop(); - } - - this.callbackStarted = +new Date(); - - if ( config.notrycatch ) { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; - return; - } - - try { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; - } catch( e ) { - this.callbackRuntime = +new Date() - this.callbackStarted; - - QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - QUnit.start(); - } - } - }, - teardown: function() { - config.current = this; - if ( config.notrycatch ) { - if ( typeof this.callbackRuntime === "undefined" ) { - this.callbackRuntime = +new Date() - this.callbackStarted; - } - this.testEnvironment.teardown.call( this.testEnvironment ); - return; - } else { - try { - this.testEnvironment.teardown.call( this.testEnvironment ); - } catch( e ) { - QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); - } - } - checkPollution(); - }, - finish: function() { - config.current = this; - if ( config.requireExpects && this.expected === null ) { - QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); - } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); - } else if ( this.expected === null && !this.assertions.length ) { - QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); - } - - var i, assertion, a, b, time, li, ol, - test = this, - good = 0, - bad = 0, - tests = id( "qunit-tests" ); - - this.runtime = +new Date() - this.started; - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; - - if ( tests ) { - ol = document.createElement( "ol" ); - ol.className = "qunit-assert-list"; - - for ( i = 0; i < this.assertions.length; i++ ) { - assertion = this.assertions[i]; - - li = document.createElement( "li" ); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - // store result when possible - if ( QUnit.config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); - } - } - - if ( bad === 0 ) { - addClass( ol, "qunit-collapsed" ); - } - - // `b` initialized at top of scope - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - addEvent(b, "click", function() { - var next = b.parentNode.lastChild, - collapsed = hasClass( next, "qunit-collapsed" ); - ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); - }); - - addEvent(b, "dblclick", function( e ) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ testNumber: test.testNumber }); - } - }); - - // `time` initialized at top of scope - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = this.runtime + " ms"; - - // `li` initialized at top of scope - li = id( this.id ); - li.className = bad ? "fail" : "pass"; - li.removeChild( li.firstChild ); - a = li.firstChild; - li.appendChild( b ); - li.appendChild( a ); - li.appendChild( time ); - li.appendChild( ol ); - - } else { - for ( i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - } - - runLoggingCallbacks( "testDone", QUnit, { - name: this.testName, - module: this.module, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length, - duration: this.runtime - }); - - QUnit.reset(); - - config.current = undefined; - }, - - queue: function() { - var bad, - test = this; - - synchronize(function() { - test.init(); - }); - function run() { - // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); - } - - // `bad` initialized at top of scope - // defer when previous test run passed, if storage is available - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); - - if ( bad ) { - run(); - } else { - synchronize( run, true ); - } - } -}; - -// Root QUnit object. -// `QUnit` initialized at top of scope -QUnit = { - - // call on start of module test to prepend name to all tests - module: function( name, testEnvironment ) { - config.currentModule = name; - config.currentModuleTestEnvironment = testEnvironment; - config.modules[name] = true; - }, - - asyncTest: function( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - QUnit.test( testName, expected, callback, true ); - }, - - test: function( testName, expected, callback, async ) { - var test, - nameHtml = "" + escapeText( testName ) + ""; - - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - if ( config.currentModule ) { - nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; - } - - test = new Test({ - nameHtml: nameHtml, - testName: testName, - expected: expected, - async: async, - callback: callback, - module: config.currentModule, - moduleTestEnvironment: config.currentModuleTestEnvironment, - stack: sourceFromStacktrace( 2 ) - }); - - if ( !validTest( test ) ) { - return; - } - - test.queue(); - }, - - // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. - expect: function( asserts ) { - if (arguments.length === 1) { - config.current.expected = asserts; - } else { - return config.current.expected; - } - }, - - start: function( count ) { - // QUnit hasn't been initialized yet. - // Note: RequireJS (et al) may delay onLoad - if ( config.semaphore === undefined ) { - QUnit.begin(function() { - // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first - setTimeout(function() { - QUnit.start( count ); - }); - }); - return; - } - - config.semaphore -= count || 1; - // don't start until equal number of stop-calls - if ( config.semaphore > 0 ) { - return; - } - // ignore if start is called more often then stop - if ( config.semaphore < 0 ) { - config.semaphore = 0; - QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); - return; - } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - window.setTimeout(function() { - if ( config.semaphore > 0 ) { - return; - } - if ( config.timeout ) { - clearTimeout( config.timeout ); - } - - config.blocking = false; - process( true ); - }, 13); - } else { - config.blocking = false; - process( true ); - } - }, - - stop: function( count ) { - config.semaphore += count || 1; - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = window.setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - config.semaphore = 1; - QUnit.start(); - }, config.testTimeout ); - } - } -}; - -// `assert` initialized at top of scope -// Asssert helpers -// All of these must either call QUnit.push() or manually do: -// - runLoggingCallbacks( "log", .. ); -// - config.current.assertions.push({ .. }); -// We attach it to the QUnit object *after* we expose the public API, -// otherwise `assert` will become a global variable in browsers (#341). -assert = { - /** - * Asserts rough true-ish result. - * @name ok - * @function - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function( result, msg ) { - if ( !config.current ) { - throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - result = !!result; - - var source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: msg - }; - - msg = escapeText( msg || (result ? "okay" : "failed" ) ); - msg = "" + msg + ""; - - if ( !result ) { - source = sourceFromStacktrace( 2 ); - if ( source ) { - details.source = source; - msg += "
    Source:
    " + escapeText( source ) + "
    "; - } - } - runLoggingCallbacks( "log", QUnit, details ); - config.current.assertions.push({ - result: result, - message: msg - }); - }, - - /** - * Assert that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * @name equal - * @function - * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); - */ - equal: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - QUnit.push( expected == actual, actual, expected, message ); - }, - - /** - * @name notEqual - * @function - */ - notEqual: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - QUnit.push( expected != actual, actual, expected, message ); - }, - - /** - * @name propEqual - * @function - */ - propEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name notPropEqual - * @function - */ - notPropEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name deepEqual - * @function - */ - deepEqual: function( actual, expected, message ) { - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name notDeepEqual - * @function - */ - notDeepEqual: function( actual, expected, message ) { - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name strictEqual - * @function - */ - strictEqual: function( actual, expected, message ) { - QUnit.push( expected === actual, actual, expected, message ); - }, - - /** - * @name notStrictEqual - * @function - */ - notStrictEqual: function( actual, expected, message ) { - QUnit.push( expected !== actual, actual, expected, message ); - }, - - "throws": function( block, expected, message ) { - var actual, - expectedOutput = expected, - ok = false; - - // 'expected' is optional - if ( typeof expected === "string" ) { - message = expected; - expected = null; - } - - config.current.ignoreGlobalErrors = true; - try { - block.call( config.current.testEnvironment ); - } catch (e) { - actual = e; - } - config.current.ignoreGlobalErrors = false; - - if ( actual ) { - // we don't want to validate thrown error - if ( !expected ) { - ok = true; - expectedOutput = null; - // expected is a regexp - } else if ( QUnit.objectType( expected ) === "regexp" ) { - ok = expected.test( errorString( actual ) ); - // expected is a constructor - } else if ( actual instanceof expected ) { - ok = true; - // expected is a validation function which returns true is validation passed - } else if ( expected.call( {}, actual ) === true ) { - expectedOutput = null; - ok = true; - } - - QUnit.push( ok, actual, expectedOutput, message ); - } else { - QUnit.pushFailure( message, null, 'No exception was thrown.' ); - } - } -}; - -/** - * @deprecate since 1.8.0 - * Kept assertion helpers in root for backwards compatibility. - */ -extend( QUnit, assert ); - -/** - * @deprecated since 1.9.0 - * Kept root "raises()" for backwards compatibility. - * (Note that we don't introduce assert.raises). - */ -QUnit.raises = assert[ "throws" ]; - -/** - * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.equals = function() { - QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); -}; -QUnit.same = function() { - QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); -}; - -// We want access to the constructor's prototype -(function() { - function F() {} - F.prototype = QUnit; - QUnit = new F(); - // Make F QUnit's constructor so that we can add to the prototype later - QUnit.constructor = F; -}()); - -/** - * Config object: Maintain internal state - * Later exposed as QUnit.config - * `config` initialized at top of scope - */ -config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true, - - // when enabled, show only failing tests - // gets persisted through sessionStorage and can be changed in UI via checkbox - hidepassed: false, - - // by default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - // by default, modify document.title when suite is done - altertitle: true, - - // when enabled, all tests must call expect() - requireExpects: false, - - // add checkboxes that are persisted in the query-string - // when enabled, the id is set to `true` as a `QUnit.config` property - urlConfig: [ - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." - } - ], - - // Set of all modules. - modules: {}, - - // logging callback queues - begin: [], - done: [], - log: [], - testStart: [], - testDone: [], - moduleStart: [], - moduleDone: [] -}; - -// Export global variables, unless an 'exports' object exists, -// in that case we assume we're in CommonJS (dealt with on the bottom of the script) -if ( typeof exports === "undefined" ) { - extend( window, QUnit ); - - // Expose QUnit object - window.QUnit = QUnit; -} - -// Initialize more QUnit.config and QUnit.urlParams -(function() { - var i, - location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}, - current; - - if ( params[ 0 ] ) { - for ( i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - urlParams[ current[ 0 ] ] = current[ 1 ]; - } - } - - QUnit.urlParams = urlParams; - - // String search anywhere in moduleName+testName - config.filter = urlParams.filter; - - // Exact match of the module name - config.module = urlParams.module; - - config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = location.protocol === "file:"; -}()); - -// Extend QUnit object, -// these after set here because they should not be exposed as global functions -extend( QUnit, { - assert: assert, - - config: config, - - // Initialize the configuration options - init: function() { - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date(), - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - filter: "", - queue: [], - semaphore: 1 - }); - - var tests, banner, result, - qunit = id( "qunit" ); - - if ( qunit ) { - qunit.innerHTML = - "

    " + escapeText( document.title ) + "

    " + - "

    " + - "
    " + - "

    " + - "
      "; - } - - tests = id( "qunit-tests" ); - banner = id( "qunit-banner" ); - result = id( "qunit-testresult" ); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
       "; - } - }, - - // Resets the test setup. Useful for tests that modify the DOM. - reset: function() { - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } - }, - - // Trigger an event on an element. - // @example triggerEvent( document.body, "click" ); - triggerEvent: function( elem, type, event ) { - if ( document.createEvent ) { - event = document.createEvent( "MouseEvents" ); - event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, - 0, 0, 0, 0, 0, false, false, false, false, 0, null); - - elem.dispatchEvent( event ); - } else if ( elem.fireEvent ) { - elem.fireEvent( "on" + type ); - } - }, - - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) === type; - }, - - objectType: function( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - // consider: typeof null === object - } - if ( obj === null ) { - return "null"; - } - - var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), - type = match && match[1] || ""; - - switch ( type ) { - case "Number": - if ( isNaN(obj) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Date": - case "RegExp": - case "Function": - return type.toLowerCase(); - } - if ( typeof obj === "object" ) { - return "object"; - } - return undefined; - }, - - push: function( result, actual, expected, message ) { - if ( !config.current ) { - throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); - } - - var output, source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: message, - actual: actual, - expected: expected - }; - - message = escapeText( message ) || ( result ? "okay" : "failed" ); - message = "" + message + ""; - output = message; - - if ( !result ) { - expected = escapeText( QUnit.jsDump.parse(expected) ); - actual = escapeText( QUnit.jsDump.parse(actual) ); - output += ""; - - if ( actual !== expected ) { - output += ""; - output += ""; - } - - source = sourceFromStacktrace(); - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
      Expected:
      " + expected + "
      Result:
      " + actual + "
      Diff:
      " + QUnit.diff( expected, actual ) + "
      Source:
      " + escapeText( source ) + "
      "; - } - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: !!result, - message: output - }); - }, - - pushFailure: function( message, source, actual ) { - if ( !config.current ) { - throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - - var output, - details = { - module: config.current.module, - name: config.current.testName, - result: false, - message: message - }; - - message = escapeText( message ) || "error"; - message = "" + message + ""; - output = message; - - output += ""; - - if ( actual ) { - output += ""; - } - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
      Result:
      " + escapeText( actual ) + "
      Source:
      " + escapeText( source ) + "
      "; - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: false, - message: output - }); - }, - - url: function( params ) { - params = extend( extend( {}, QUnit.urlParams ), params ); - var key, - querystring = "?"; - - for ( key in params ) { - if ( !hasOwn.call( params, key ) ) { - continue; - } - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; - } - return window.location.protocol + "//" + window.location.host + - window.location.pathname + querystring.slice( 0, -1 ); - }, - - extend: extend, - id: id, - addEvent: addEvent - // load, equiv, jsDump, diff: Attached later -}); - -/** - * @deprecated: Created for backwards compatibility with test runner that set the hook function - * into QUnit.{hook}, instead of invoking it and passing the hook function. - * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. - * Doing this allows us to tell if the following methods have been overwritten on the actual - * QUnit object. - */ -extend( QUnit.constructor.prototype, { - - // Logging callbacks; all receive a single argument with the listed properties - // run test/logs.html for any related changes - begin: registerLoggingCallback( "begin" ), - - // done: { failed, passed, total, runtime } - done: registerLoggingCallback( "done" ), - - // log: { result, actual, expected, message } - log: registerLoggingCallback( "log" ), - - // testStart: { name } - testStart: registerLoggingCallback( "testStart" ), - - // testDone: { name, failed, passed, total, duration } - testDone: registerLoggingCallback( "testDone" ), - - // moduleStart: { name } - moduleStart: registerLoggingCallback( "moduleStart" ), - - // moduleDone: { name, failed, passed, total } - moduleDone: registerLoggingCallback( "moduleDone" ) -}); - -if ( typeof document === "undefined" || document.readyState === "complete" ) { - config.autorun = true; -} - -QUnit.load = function() { - runLoggingCallbacks( "begin", QUnit, {} ); - - // Initialize the config, saving the execution queue - var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, - urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter, - numModules = 0, - moduleFilterHtml = "", - urlConfigHtml = "", - oldconfig = extend( {}, config ); - - QUnit.init(); - extend(config, oldconfig); - - config.blocking = false; - - len = config.urlConfig.length; - - for ( i = 0; i < len; i++ ) { - val = config.urlConfig[i]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val, - tooltip: "[no tooltip available]" - }; - } - config[ val.id ] = QUnit.urlParams[ val.id ]; - urlConfigHtml += ""; - } - - moduleFilterHtml += ""; - - // `userAgent` initialized at top of scope - userAgent = id( "qunit-userAgent" ); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } - - // `banner` initialized at top of scope - banner = id( "qunit-header" ); - if ( banner ) { - banner.innerHTML = "" + banner.innerHTML + " "; - } - - // `toolbar` initialized at top of scope - toolbar = id( "qunit-testrunner-toolbar" ); - if ( toolbar ) { - // `filter` initialized at top of scope - filter = document.createElement( "input" ); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - - addEvent( filter, "click", function() { - var tmp, - ol = document.getElementById( "qunit-tests" ); - - if ( filter.checked ) { - ol.className = ol.className + " hidepass"; - } else { - tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace( / hidepass /, " " ); - } - if ( defined.sessionStorage ) { - if (filter.checked) { - sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); - } else { - sessionStorage.removeItem( "qunit-filter-passed-tests" ); - } - } - }); - - if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { - filter.checked = true; - // `ol` initialized at top of scope - ol = document.getElementById( "qunit-tests" ); - ol.className = ol.className + " hidepass"; - } - toolbar.appendChild( filter ); - - // `label` initialized at top of scope - label = document.createElement( "label" ); - label.setAttribute( "for", "qunit-filter-pass" ); - label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." ); - label.innerHTML = "Hide passed tests"; - toolbar.appendChild( label ); - - urlConfigCheckboxesContainer = document.createElement("span"); - urlConfigCheckboxesContainer.innerHTML = urlConfigHtml; - urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input"); - // For oldIE support: - // * Add handlers to the individual elements instead of the container - // * Use "click" instead of "change" - // * Fallback from event.target to event.srcElement - addEvents( urlConfigCheckboxes, "click", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.checked ? true : undefined; - window.location = QUnit.url( params ); - }); - toolbar.appendChild( urlConfigCheckboxesContainer ); - - if (numModules > 1) { - moduleFilter = document.createElement( 'span' ); - moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' ); - moduleFilter.innerHTML = moduleFilterHtml; - addEvent( moduleFilter.lastChild, "change", function() { - var selectBox = moduleFilter.getElementsByTagName("select")[0], - selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); - - window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } ); - }); - toolbar.appendChild(moduleFilter); - } - } - - // `main` initialized at top of scope - main = id( "qunit-fixture" ); - if ( main ) { - config.fixture = main.innerHTML; - } - - if ( config.autostart ) { - QUnit.start(); - } -}; - -addEvent( window, "load", QUnit.load ); - -// `onErrorFnPrev` initialized at top of scope -// Preserve other handlers -onErrorFnPrev = window.onerror; - -// Cover uncaught exceptions -// Returning true will surpress the default browser handler, -// returning false will let it run. -window.onerror = function ( error, filePath, linerNr ) { - var ret = false; - if ( onErrorFnPrev ) { - ret = onErrorFnPrev( error, filePath, linerNr ); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not surpressed. - if ( ret !== true ) { - if ( QUnit.config.current ) { - if ( QUnit.config.current.ignoreGlobalErrors ) { - return true; - } - QUnit.pushFailure( error, filePath + ":" + linerNr ); - } else { - QUnit.test( "global failure", extend( function() { - QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: validTest } ) ); - } - return false; - } - - return ret; -}; - -function done() { - config.autorun = true; - - // Log the last module results - if ( config.currentModule ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.currentModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - runtime = +new Date() - config.started, - passed = config.stats.all - config.stats.bad, - html = [ - "Tests completed in ", - runtime, - " milliseconds.
      ", - "", - passed, - " assertions of ", - config.stats.all, - " passed, ", - config.stats.bad, - " failed." - ].join( "" ); - - if ( banner ) { - banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( config.altertitle && typeof document !== "undefined" && document.title ) { - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( config.stats.bad ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { - // `key` & `i` initialized at top of scope - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); - } - } - } - - // scroll back to top to show results - if ( window.scrollTo ) { - window.scrollTo(0, 0); - } - - runLoggingCallbacks( "done", QUnit, { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - }); -} - -/** @return Boolean: true if this test should be ran */ -function validTest( test ) { - var include, - filter = config.filter && config.filter.toLowerCase(), - module = config.module && config.module.toLowerCase(), - fullName = (test.module + ": " + test.testName).toLowerCase(); - - // Internally-generated tests are always valid - if ( test.callback && test.callback.validTest === validTest ) { - delete test.callback.validTest; - return true; - } - - if ( config.testNumber ) { - return test.testNumber === config.testNumber; - } - - if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { - return false; - } - - if ( !filter ) { - return true; - } - - include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.slice( 1 ); - } - - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } - - // Otherwise, do the opposite - return !include; -} - -// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) -// Later Safari and IE10 are supposed to support error.stack as well -// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack -function extractStacktrace( e, offset ) { - offset = offset === undefined ? 3 : offset; - - var stack, include, i; - - if ( e.stacktrace ) { - // Opera - return e.stacktrace.split( "\n" )[ offset + 3 ]; - } else if ( e.stack ) { - // Firefox, Chrome - stack = e.stack.split( "\n" ); - if (/^error$/i.test( stack[0] ) ) { - stack.shift(); - } - if ( fileName ) { - include = []; - for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) !== -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } else if ( e.sourceURL ) { - // Safari, PhantomJS - // hopefully one day Safari provides actual stacktraces - // exclude useless self-reference for generated Error objects - if ( /qunit.js$/.test( e.sourceURL ) ) { - return; - } - // for actual exceptions, this is useful - return e.sourceURL + ":" + e.line; - } -} -function sourceFromStacktrace( offset ) { - try { - throw new Error(); - } catch ( e ) { - return extractStacktrace( e, offset ); - } -} - -/** - * Escape text for attribute or text content. - */ -function escapeText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch( s ) { - case '\'': - return '''; - case '"': - return '"'; - case '<': - return '<'; - case '>': - return '>'; - case '&': - return '&'; - } - }); -} - -function synchronize( callback, last ) { - config.queue.push( callback ); - - if ( config.autorun && !config.blocking ) { - process( last ); - } -} - -function process( last ) { - function next() { - process( last ); - } - var start = new Date().getTime(); - config.depth = config.depth ? config.depth + 1 : 1; - - while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { - config.queue.shift()(); - } else { - window.setTimeout( next, 13 ); - break; - } - } - config.depth--; - if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { - done(); - } -} - -function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( var key in window ) { - // in Opera sometimes DOM element ids show up here, ignore them - if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); - } - } -} - -function checkPollution() { - var newGlobals, - deletedGlobals, - old = config.pollution; - - saveGlobal(); - - newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); - } - - deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); - } -} - -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var i, j, - result = a.slice(); - - for ( i = 0; i < result.length; i++ ) { - for ( j = 0; j < b.length; j++ ) { - if ( result[i] === b[j] ) { - result.splice( i, 1 ); - i--; - break; - } - } - } - return result; -} - -function extend( a, b ) { - for ( var prop in b ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - - // Avoid "Member not found" error in IE8 caused by setting window.constructor - } else if ( prop !== "constructor" || a !== window ) { - a[ prop ] = b[ prop ]; - } - } - - return a; -} - -/** - * @param {HTMLElement} elem - * @param {string} type - * @param {Function} fn - */ -function addEvent( elem, type, fn ) { - // Standards-based browsers - if ( elem.addEventListener ) { - elem.addEventListener( type, fn, false ); - // IE - } else { - elem.attachEvent( "on" + type, fn ); - } -} - -/** - * @param {Array|NodeList} elems - * @param {string} type - * @param {Function} fn - */ -function addEvents( elems, type, fn ) { - var i = elems.length; - while ( i-- ) { - addEvent( elems[i], type, fn ); - } -} - -function hasClass( elem, name ) { - return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; -} - -function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += (elem.className ? " " : "") + name; - } -} - -function removeClass( elem, name ) { - var set = " " + elem.className + " "; - // Class name may appear multiple times - while ( set.indexOf(" " + name + " ") > -1 ) { - set = set.replace(" " + name + " " , " "); - } - // If possible, trim it for prettiness, but not neccecarily - elem.className = window.jQuery ? jQuery.trim( set ) : ( set.trim ? set.trim() : set ); -} - -function id( name ) { - return !!( typeof document !== "undefined" && document && document.getElementById ) && - document.getElementById( name ); -} - -function registerLoggingCallback( key ) { - return function( callback ) { - config[key].push( callback ); - }; -} - -// Supports deprecated method of completely overwriting logging callbacks -function runLoggingCallbacks( key, scope, args ) { - var i, callbacks; - if ( QUnit.hasOwnProperty( key ) ) { - QUnit[ key ].call(scope, args ); - } else { - callbacks = config[ key ]; - for ( i = 0; i < callbacks.length; i++ ) { - callbacks[ i ].call( scope, args ); - } - } -} - -// Test for equality any JavaScript type. -// Author: Philippe Rathé -QUnit.equiv = (function() { - - // Call the o related callback with the given arguments. - function bindCallbacks( o, callbacks, args ) { - var prop = QUnit.objectType( o ); - if ( prop ) { - if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { - return callbacks[ prop ].apply( callbacks, args ); - } else { - return callbacks[ prop ]; // or undefined - } - } - } - - // the real equiv function - var innerEquiv, - // stack to decide between skip/abort functions - callers = [], - // stack to avoiding loops from circular referencing - parents = [], - - getProto = Object.getPrototypeOf || function ( obj ) { - return obj.__proto__; - }, - callbacks = (function () { - - // for string, boolean, number and null - function useStrictEquality( b, a ) { - /*jshint eqeqeq:false */ - if ( b instanceof a.constructor || a instanceof b.constructor ) { - // to catch short annotaion VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function( b ) { - return isNaN( b ); - }, - - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && - // the regex itself - a.source === b.source && - // and its modifers - a.global === b.global && - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function() { - var caller = callers[callers.length - 1]; - return caller !== Object && typeof caller !== "undefined"; - }, - - "array": function( b, a ) { - var i, j, len, loop; - - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } - - len = a.length; - if ( len !== b.length ) { - // safe and faster - return false; - } - - // track reference to avoid circular references - parents.push( a ); - for ( i = 0; i < len; i++ ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - if ( parents[j] === a[i] ) { - loop = true;// dont rewalk array - } - } - if ( !loop && !innerEquiv(a[i], b[i]) ) { - parents.pop(); - return false; - } - } - parents.pop(); - return true; - }, - - "object": function( b, a ) { - var i, j, loop, - // Default to true - eq = true, - aProperties = [], - bProperties = []; - - // comparing constructors is more strict than using - // instanceof - if ( a.constructor !== b.constructor ) { - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || - ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { - return false; - } - } - - // stack constructor before traversing properties - callers.push( a.constructor ); - // track reference to avoid circular references - parents.push( a ); - - for ( i in a ) { // be strict: don't ensures hasOwnProperty - // and go deep - loop = false; - for ( j = 0; j < parents.length; j++ ) { - if ( parents[j] === a[i] ) { - // don't go down the same path twice - loop = true; - } - } - aProperties.push(i); // collect a's properties - - if (!loop && !innerEquiv( a[i], b[i] ) ) { - eq = false; - break; - } - } - - callers.pop(); // unstack, we are done - parents.pop(); - - for ( i in b ) { - bProperties.push( i ); // collect b's properties - } - - // Ensures identical properties name - return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); - } - }; - }()); - - innerEquiv = function() { // can take multiple arguments - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { - return true; // end transition - } - - return (function( a, b ) { - if ( a === b ) { - return true; // catch the most you can - } else if ( a === null || b === null || typeof a === "undefined" || - typeof b === "undefined" || - QUnit.objectType(a) !== QUnit.objectType(b) ) { - return false; // don't lose time with error prone cases - } else { - return bindCallbacks(a, callbacks, [ b, a ]); - } - - // apply transition with (1..n) arguments - }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) ); - }; - - return innerEquiv; -}()); - -/** - * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | - * http://flesler.blogspot.com Licensed under BSD - * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 - * - * @projectDescription Advanced and extensible data dumping for Javascript. - * @version 1.0.0 - * @author Ariel Flesler - * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} - */ -QUnit.jsDump = (function() { - function quote( str ) { - return '"' + str.toString().replace( /"/g, '\\"' ) + '"'; - } - function literal( o ) { - return o + ""; - } - function join( pre, arr, post ) { - var s = jsDump.separator(), - base = jsDump.indent(), - inner = jsDump.indent(1); - if ( arr.join ) { - arr = arr.join( "," + s + inner ); - } - if ( !arr ) { - return pre + post; - } - return [ pre, inner + arr, base + post ].join(s); - } - function array( arr, stack ) { - var i = arr.length, ret = new Array(i); - this.up(); - while ( i-- ) { - ret[i] = this.parse( arr[i] , undefined , stack); - } - this.down(); - return join( "[", ret, "]" ); - } - - var reName = /^function (\w+)/, - jsDump = { - // type is used mostly internally, you can fix a (custom)type in advance - parse: function( obj, type, stack ) { - stack = stack || [ ]; - var inStack, res, - parser = this.parsers[ type || this.typeOf(obj) ]; - - type = typeof parser; - inStack = inArray( obj, stack ); - - if ( inStack !== -1 ) { - return "recursion(" + (inStack - stack.length) + ")"; - } - if ( type === "function" ) { - stack.push( obj ); - res = parser.call( this, obj, stack ); - stack.pop(); - return res; - } - return ( type === "string" ) ? parser : this.parsers.error; - }, - typeOf: function( obj ) { - var type; - if ( obj === null ) { - type = "null"; - } else if ( typeof obj === "undefined" ) { - type = "undefined"; - } else if ( QUnit.is( "regexp", obj) ) { - type = "regexp"; - } else if ( QUnit.is( "date", obj) ) { - type = "date"; - } else if ( QUnit.is( "function", obj) ) { - type = "function"; - } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { - type = "window"; - } else if ( obj.nodeType === 9 ) { - type = "document"; - } else if ( obj.nodeType ) { - type = "node"; - } else if ( - // native arrays - toString.call( obj ) === "[object Array]" || - // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) - ) { - type = "array"; - } else if ( obj.constructor === Error.prototype.constructor ) { - type = "error"; - } else { - type = typeof obj; - } - return type; - }, - separator: function() { - return this.multiline ? this.HTML ? "
      " : "\n" : this.HTML ? " " : " "; - }, - // extra can be a number, shortcut for increasing-calling-decreasing - indent: function( extra ) { - if ( !this.multiline ) { - return ""; - } - var chr = this.indentChar; - if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); - } - return new Array( this._depth_ + (extra||0) ).join(chr); - }, - up: function( a ) { - this._depth_ += a || 1; - }, - down: function( a ) { - this._depth_ -= a || 1; - }, - setParser: function( name, parser ) { - this.parsers[name] = parser; - }, - // The next 3 are exposed so you can use them - quote: quote, - literal: literal, - join: join, - // - _depth_: 1, - // This is the list of parsers, to modify them, use jsDump.setParser - parsers: { - window: "[Window]", - document: "[Document]", - error: function(error) { - return "Error(\"" + error.message + "\")"; - }, - unknown: "[Unknown]", - "null": "null", - "undefined": "undefined", - "function": function( fn ) { - var ret = "function", - // functions never have name in IE - name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; - - if ( name ) { - ret += " " + name; - } - ret += "( "; - - ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); - }, - array: array, - nodelist: array, - "arguments": array, - object: function( map, stack ) { - var ret = [ ], keys, key, val, i; - QUnit.jsDump.up(); - keys = []; - for ( key in map ) { - keys.push( key ); - } - keys.sort(); - for ( i = 0; i < keys.length; i++ ) { - key = keys[ i ]; - val = map[ key ]; - ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); - } - QUnit.jsDump.down(); - return join( "{", ret, "}" ); - }, - node: function( node ) { - var len, i, val, - open = QUnit.jsDump.HTML ? "<" : "<", - close = QUnit.jsDump.HTML ? ">" : ">", - tag = node.nodeName.toLowerCase(), - ret = open + tag, - attrs = node.attributes; - - if ( attrs ) { - for ( i = 0, len = attrs.length; i < len; i++ ) { - val = attrs[i].nodeValue; - // IE6 includes all attributes in .attributes, even ones not explicitly set. - // Those have values like undefined, null, 0, false, "" or "inherit". - if ( val && val !== "inherit" ) { - ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); - } - } - } - ret += close; - - // Show content of TextNode or CDATASection - if ( node.nodeType === 3 || node.nodeType === 4 ) { - ret += node.nodeValue; - } - - return ret + open + "/" + tag + close; - }, - // function calls it internally, it's the arguments part of the function - functionArgs: function( fn ) { - var args, - l = fn.length; - - if ( !l ) { - return ""; - } - - args = new Array(l); - while ( l-- ) { - // 97 is 'a' - args[l] = String.fromCharCode(97+l); - } - return " " + args.join( ", " ) + " "; - }, - // object calls it internally, the key part of an item in a map - key: quote, - // function calls it internally, it's the content of the function - functionCode: "[code]", - // node calls it internally, it's an html attribute value - attribute: quote, - string: quote, - date: quote, - regexp: literal, - number: literal, - "boolean": literal - }, - // if true, entities are escaped ( <, >, \t, space and \n ) - HTML: false, - // indentation unit - indentChar: " ", - // if true, items in a collection, are separated by a \n, else just a space. - multiline: true - }; - - return jsDump; -}()); - -// from jquery.js -function inArray( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; -} - -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" - */ -QUnit.diff = (function() { - /*jshint eqeqeq:false, eqnull:true */ - function diff( o, n ) { - var i, - ns = {}, - os = {}; - - for ( i = 0; i < n.length; i++ ) { - if ( !hasOwn.call( ns, n[i] ) ) { - ns[ n[i] ] = { - rows: [], - o: null - }; - } - ns[ n[i] ].rows.push( i ); - } - - for ( i = 0; i < o.length; i++ ) { - if ( !hasOwn.call( os, o[i] ) ) { - os[ o[i] ] = { - rows: [], - n: null - }; - } - os[ o[i] ].rows.push( i ); - } - - for ( i in ns ) { - if ( !hasOwn.call( ns, i ) ) { - continue; - } - if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { - n[ ns[i].rows[0] ] = { - text: n[ ns[i].rows[0] ], - row: os[i].rows[0] - }; - o[ os[i].rows[0] ] = { - text: o[ os[i].rows[0] ], - row: ns[i].rows[0] - }; - } - } - - for ( i = 0; i < n.length - 1; i++ ) { - if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && - n[ i + 1 ] == o[ n[i].row + 1 ] ) { - - n[ i + 1 ] = { - text: n[ i + 1 ], - row: n[i].row + 1 - }; - o[ n[i].row + 1 ] = { - text: o[ n[i].row + 1 ], - row: i + 1 - }; - } - } - - for ( i = n.length - 1; i > 0; i-- ) { - if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && - n[ i - 1 ] == o[ n[i].row - 1 ]) { - - n[ i - 1 ] = { - text: n[ i - 1 ], - row: n[i].row - 1 - }; - o[ n[i].row - 1 ] = { - text: o[ n[i].row - 1 ], - row: i - 1 - }; - } - } - - return { - o: o, - n: n - }; - } - - return function( o, n ) { - o = o.replace( /\s+$/, "" ); - n = n.replace( /\s+$/, "" ); - - var i, pre, - str = "", - out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), - oSpace = o.match(/\s+/g), - nSpace = n.match(/\s+/g); - - if ( oSpace == null ) { - oSpace = [ " " ]; - } - else { - oSpace.push( " " ); - } - - if ( nSpace == null ) { - nSpace = [ " " ]; - } - else { - nSpace.push( " " ); - } - - if ( out.n.length === 0 ) { - for ( i = 0; i < out.o.length; i++ ) { - str += "" + out.o[i] + oSpace[i] + ""; - } - } - else { - if ( out.n[0].text == null ) { - for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { - str += "" + out.o[n] + oSpace[n] + ""; - } - } - - for ( i = 0; i < out.n.length; i++ ) { - if (out.n[i].text == null) { - str += "" + out.n[i] + nSpace[i] + ""; - } - else { - // `pre` initialized at top of scope - pre = ""; - - for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { - pre += "" + out.o[n] + oSpace[n] + ""; - } - str += " " + out.n[i].text + nSpace[i] + pre; - } - } - } - - return str; - }; -}()); - -// for CommonJS enviroments, export everything -if ( typeof exports !== "undefined" ) { - extend( exports, QUnit ); -} - -// get at whatever the global object is, like window in browsers -}( (function() {return this;}.call()) )); diff --git a/test/support/async.js b/test/support/async.js deleted file mode 100644 index 1e8ac86..0000000 --- a/test/support/async.js +++ /dev/null @@ -1,20 +0,0 @@ -function async(callback, timeout) { - stop(); - - timeout = Ember.run.later(function() { - start(); - ok(false, "Timeout was reached"); - }, timeout || 100); - - return function() { - Ember.run.cancel(timeout); - start(); - callback.apply(this, arguments) - }; -}; - -function invokeAsync(callback, timeout) { - timeout = timeout || 1; - - setTimeout(async(callback, timeout+100), timeout); -}; diff --git a/test/support/build-container.js b/test/support/build-container.js deleted file mode 100644 index 4ad82f5..0000000 --- a/test/support/build-container.js +++ /dev/null @@ -1,6 +0,0 @@ -function buildContainer() { - var container = new Ember.Container(); - DS._setupContainer(container); - EmberParseAdapter.setupContainer(container); - return container; -} diff --git a/test/support/expects.js b/test/support/expects.js deleted file mode 100644 index 57e7337..0000000 --- a/test/support/expects.js +++ /dev/null @@ -1,24 +0,0 @@ -function expectUrl(url, desc) { - // because the Parse API is CORS and we have a server URL ... - equal(ajaxUrl, adapter.serverUrl + url, "the URL is " + desc); -} - -function expectType(type) { - equal(ajaxType, type, "the HTTP method is " + type); -} - -function expectData(hash) { - deepEqual(ajaxHash.data, hash, "the hash was passed along"); -} - -function expectState(state, value, obj) { - if (value === undefined) { value = true; } - var flag = "is" + state.charAt(0).toUpperCase() + state.substr(1); - equal(Ember.get(obj, flag), value, "the object is " + (value === false ? "not " : "") + state); -} - -function expectStates(coll, state, value) { - coll.forEach(function(thing) { - expectState(state, value, thing); - }); -} diff --git a/test/support/pending.js b/test/support/pending.js deleted file mode 100644 index df80e20..0000000 --- a/test/support/pending.js +++ /dev/null @@ -1,3 +0,0 @@ -function pending(description) { - console.log('Skipping pending test: '+description); -} diff --git a/testem.json b/testem.json new file mode 100644 index 0000000..42a4ddb --- /dev/null +++ b/testem.json @@ -0,0 +1,11 @@ +{ + "framework": "qunit", + "test_page": "tests/index.html?hidepassed", + "launch_in_ci": [ + "PhantomJS" + ], + "launch_in_dev": [ + "PhantomJS", + "Chrome" + ] +} diff --git a/tests/.jshintrc b/tests/.jshintrc new file mode 100644 index 0000000..3bb0fc8 --- /dev/null +++ b/tests/.jshintrc @@ -0,0 +1,56 @@ +{ + "predef": [ + "document", + "window", + "location", + "setTimeout", + "$", + "-Promise", + "QUnit", + "define", + "console", + "module", + "test", + "stop", + "start", + "visit", + "exists", + "fillIn", + "click", + "keyEvent", + "triggerEvent", + "find", + "findWithAssert", + "wait", + "DS", + "andThen", + "currentURL", + "currentPath", + "currentRouteName" + ], + "node": false, + "browser": false, + "boss": true, + "curly": false, + "debug": false, + "devel": false, + "eqeqeq": true, + "evil": true, + "forin": false, + "immed": false, + "laxbreak": false, + "newcap": true, + "noarg": true, + "noempty": false, + "nonew": false, + "nomen": false, + "onevar": false, + "plusplus": false, + "regexp": false, + "undef": true, + "sub": true, + "strict": false, + "white": false, + "eqnull": true, + "esnext": true +} diff --git a/tests/dummy/app/app.js b/tests/dummy/app/app.js new file mode 100644 index 0000000..757df38 --- /dev/null +++ b/tests/dummy/app/app.js @@ -0,0 +1,16 @@ +import Ember from 'ember'; +import Resolver from 'ember/resolver'; +import loadInitializers from 'ember/load-initializers'; +import config from './config/environment'; + +Ember.MODEL_FACTORY_INJECTIONS = true; + +var App = Ember.Application.extend({ + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix, + Resolver: Resolver +}); + +loadInitializers(App, config.modulePrefix); + +export default App; diff --git a/tests/dummy/app/components/.gitkeep b/tests/dummy/app/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/controllers/.gitkeep b/tests/dummy/app/controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/controllers/belongsto.js b/tests/dummy/app/controllers/belongsto.js new file mode 100644 index 0000000..0660b05 --- /dev/null +++ b/tests/dummy/app/controllers/belongsto.js @@ -0,0 +1,32 @@ +import Ember from 'ember'; + +export default Ember.Controller.extend({ + + succeeded: null, + + actions: { + reset: function() { + this.set( 'succeeded', false ); + }, + + save: function() { + var hole = this.get( 'store' ).createRecord( 'hole' ), + controller = this, + donut; + + this.get( 'model' ).set( 'flavor', $( '#donutFlavor' ).val() ); + + donut = this.get( 'model' ); + + donut.save().then( function( donut ) { + hole.set( 'donut', donut ); + hole.save().then( function( hole ) { + donut.set( 'hole', hole ); + donut.save().then( function( donut ) { + controller.set( 'succeeded', 'true' ); + }); + }); + }); + } + } +}); \ No newline at end of file diff --git a/tests/dummy/app/controllers/facebook.js b/tests/dummy/app/controllers/facebook.js new file mode 100644 index 0000000..d851b1d --- /dev/null +++ b/tests/dummy/app/controllers/facebook.js @@ -0,0 +1,39 @@ +import Ember from 'ember'; + +export default Ember.Controller.extend({ + accessToken: null, + + facebookUid: null, + + loggedIn: false, + + loginMessage: null, + + actions: { + login: function() { + var ParseUser = this.store.modelFor( 'parse-user' ), + controller = this, + data = { + // username: 'Some facebook user', + authData: { + facebook: { + access_token : this.get( 'accessToken' ), + id : this.get( 'facebookUid' ), + expiration_date : ( new Date(2032,2,2) ) + } + } + }; + + ParseUser.signup( this.store, data ).then( + function( user ) { + controller.set( 'loggedIn', true ); + controller.set( 'loginMessage', 'Welcome!' ); + }, + function( error ) { + controller.set( 'loggedIn', false ); + controller.set( 'loginMessage', error.message || error.error ); + } + ); + } + } +}); \ No newline at end of file diff --git a/tests/dummy/app/controllers/hasmany.js b/tests/dummy/app/controllers/hasmany.js new file mode 100644 index 0000000..14b2993 --- /dev/null +++ b/tests/dummy/app/controllers/hasmany.js @@ -0,0 +1,29 @@ +import Ember from 'ember'; + +export default Ember.Controller.extend({ + saved: false, + + name: null, + + actions: { + save: function() { + var tiger = this.get( 'model' ), + controller = this; + + tiger.set( 'name', this.get( 'name' ) ); + + tiger.save().then( function() { + controller.set( 'saved', true ); + }); + }, + + add: function() { + var tiger = this.get( 'model' ), + stripe = tiger.get( 'stripes' ).createRecord(); + + stripe.save().then( function( tiger ) { + tiger.save(); + }); + } + } +}); \ No newline at end of file diff --git a/tests/dummy/app/controllers/login.js b/tests/dummy/app/controllers/login.js new file mode 100644 index 0000000..2f7701b --- /dev/null +++ b/tests/dummy/app/controllers/login.js @@ -0,0 +1,36 @@ +import Ember from 'ember'; + +export default Ember.Controller.extend({ + + username: null, + + password: null, + + email: null, + + loggedIn: false, + + loginMessage: null, + + actions: { + login: function() { + var controller = this, + ParseUser = this.store.modelFor( 'parse-user' ), + data = { + username: this.get( 'username' ), + password: this.get( 'password' ) + }; + + ParseUser.login( this.store, data ).then( + function( user ) { + controller.set( 'loggedIn', true ); + controller.set( 'loginMessage', 'Welcome!' ); + }, + function( error ) { + controller.set( 'loggedIn', false ); + controller.set( 'loginMessage', error.message || error.error ); + } + ); + }, + } +}); \ No newline at end of file diff --git a/tests/dummy/app/controllers/signup.js b/tests/dummy/app/controllers/signup.js new file mode 100644 index 0000000..d1c5eb5 --- /dev/null +++ b/tests/dummy/app/controllers/signup.js @@ -0,0 +1,37 @@ +import Ember from 'ember'; + +export default Ember.Controller.extend({ + + username: null, + + password: null, + + email: null, + + loggedIn: false, + + loginMessage: null, + + actions: { + signup: function(){ + var controller = this, + ParseUser = this.store.modelFor( 'parse-user' ), + data = { + username: this.get( 'username' ), + password: this.get( 'password' ), + email: this.get( 'email' ) + }; + + ParseUser.signup( this.store, data ).then( + function( user ){ + controller.set( 'loggedIn', true ); + controller.set( 'loginMessage', 'Welcome!' ); + }, + function( error ) { + controller.set( 'loggedIn', false ); + controller.set( 'loginMessage', error.message || error.error ); + } + ); + } + } +}); \ No newline at end of file diff --git a/tests/dummy/app/controllers/simple.js b/tests/dummy/app/controllers/simple.js new file mode 100644 index 0000000..ae2468a --- /dev/null +++ b/tests/dummy/app/controllers/simple.js @@ -0,0 +1,31 @@ +import Ember from 'ember'; + +export default Ember.Controller.extend({ + lastSavedModel: null, + + succeeded: false, + + name: null, + + actions: { + reset: function() { + this.set( 'name', null ); + this.set( 'succeeded', false ); + this.set( 'lastSavedModel', null ); + }, + + save: function(){ + var controller = this; + this.get( 'model' ).set( 'name', this.get( 'name' ) ); + this.get( 'model' ).save().then( + function( simple ) { + controller.set( 'succeeded', true ); + controller.set( 'lastSavedModel', simple ); + }, + function( error ) { + controller.set( 'succeeded', false ); + } + ); + } + } +}); \ No newline at end of file diff --git a/tests/dummy/app/helpers/.gitkeep b/tests/dummy/app/helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/index.html b/tests/dummy/app/index.html new file mode 100644 index 0000000..8837991 --- /dev/null +++ b/tests/dummy/app/index.html @@ -0,0 +1,28 @@ + + + + + + Dummy + + + + {{content-for 'head'}} + + + + + + + {{content-for 'head-footer'}} + + + {{content-for 'body'}} + + + + + + {{content-for 'body-footer'}} + + diff --git a/tests/dummy/app/models/.gitkeep b/tests/dummy/app/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/models/donut.js b/tests/dummy/app/models/donut.js new file mode 100644 index 0000000..e62a9c1 --- /dev/null +++ b/tests/dummy/app/models/donut.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; +import DS from 'ember-data'; + +export default DS.Model.extend({ + flavor: DS.attr( 'string' ), + hole: DS.belongsTo( 'hole' ) +}); \ No newline at end of file diff --git a/tests/dummy/app/models/hole.js b/tests/dummy/app/models/hole.js new file mode 100644 index 0000000..25421d5 --- /dev/null +++ b/tests/dummy/app/models/hole.js @@ -0,0 +1,6 @@ +import Ember from 'ember'; +import DS from 'ember-data'; + +export default DS.Model.extend({ + donut: DS.belongsTo( 'donut' ) +}); \ No newline at end of file diff --git a/tests/dummy/app/models/simple.js b/tests/dummy/app/models/simple.js new file mode 100644 index 0000000..1a445e1 --- /dev/null +++ b/tests/dummy/app/models/simple.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; +import DS from 'ember-data'; + +export default DS.Model.extend({ + name: DS.attr( 'string' ), + createdAt: DS.attr( 'date' ) +}); \ No newline at end of file diff --git a/tests/dummy/app/models/stripe.js b/tests/dummy/app/models/stripe.js new file mode 100644 index 0000000..a8f780e --- /dev/null +++ b/tests/dummy/app/models/stripe.js @@ -0,0 +1,6 @@ +import Ember from 'ember'; +import DS from 'ember-data'; + +export default DS.Model.extend({ + tiger: DS.belongsTo( 'tiger' ) +}); \ No newline at end of file diff --git a/tests/dummy/app/models/tiger.js b/tests/dummy/app/models/tiger.js new file mode 100644 index 0000000..ae11fe4 --- /dev/null +++ b/tests/dummy/app/models/tiger.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; +import DS from 'ember-data'; + +export default DS.Model.extend({ + name: DS.attr( 'string' ), + stripes: DS.hasMany( 'stripe' ) +}); \ No newline at end of file diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js new file mode 100644 index 0000000..26e45f5 --- /dev/null +++ b/tests/dummy/app/router.js @@ -0,0 +1,17 @@ +import Ember from 'ember'; +import config from './config/environment'; + +var Router = Ember.Router.extend({ + location: config.locationType +}); + +Router.map(function() { + this.route( 'signup', { path: '/signup' } ); + this.route( 'login', { path: '/login' } ); + this.route( 'simple', { path: '/simple' } ); + this.route( 'belongsto', { path: '/belongsto' } ); + this.route( 'hasmany', { path: '/hasmany' } ); + this.route( 'facebook', { path: '/facebook' } ); +}); + +export default Router; diff --git a/tests/dummy/app/routes/.gitkeep b/tests/dummy/app/routes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/routes/belongsto.js b/tests/dummy/app/routes/belongsto.js new file mode 100644 index 0000000..1d3e946 --- /dev/null +++ b/tests/dummy/app/routes/belongsto.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model: function() { + return this.get( 'store' ).createRecord( 'donut' ); + } +}); \ No newline at end of file diff --git a/tests/dummy/app/routes/hasmany.js b/tests/dummy/app/routes/hasmany.js new file mode 100644 index 0000000..8ef8a57 --- /dev/null +++ b/tests/dummy/app/routes/hasmany.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model: function() { + return this.get( 'store' ).createRecord( 'tiger' ); + } +}); \ No newline at end of file diff --git a/tests/dummy/app/routes/simple.js b/tests/dummy/app/routes/simple.js new file mode 100644 index 0000000..b50ba97 --- /dev/null +++ b/tests/dummy/app/routes/simple.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model: function(){ + return this.get( 'store' ).createRecord( 'simple' ); + } +}); \ No newline at end of file diff --git a/tests/dummy/app/styles/.gitkeep b/tests/dummy/app/styles/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/styles/app.css b/tests/dummy/app/styles/app.css new file mode 100644 index 0000000..b116e9a --- /dev/null +++ b/tests/dummy/app/styles/app.css @@ -0,0 +1,12 @@ +html, body { + margin: 20px; +} + +a { + padding: 5px; +} + +a.active { + border: 1px solid #000000; + background-color: #F8F8FA; +} \ No newline at end of file diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs new file mode 100644 index 0000000..07a6117 --- /dev/null +++ b/tests/dummy/app/templates/application.hbs @@ -0,0 +1,28 @@ +
      +
      +

      Ember Data Adapter for Parse

      +

      An adapter built specifically to use with the Parse.com REST API

      +
      +
      + +
      +
      + SELECT A DEMO: +       + {{#link-to "signup"}}Sign Up{{/link-to}} +       + {{#link-to "login"}}Login{{/link-to}} +       + {{#link-to "simple"}}Simple Model{{/link-to}} +       + {{#link-to "belongsto"}}Belongs To{{/link-to}} +       + {{#link-to "hasmany"}}Has Many{{/link-to}} +       + {{#link-to "facebook"}}Facebook Login{{/link-to}} +
      +
      + +


      + +{{outlet}} diff --git a/tests/dummy/app/templates/belongsto.hbs b/tests/dummy/app/templates/belongsto.hbs new file mode 100644 index 0000000..99caa60 --- /dev/null +++ b/tests/dummy/app/templates/belongsto.hbs @@ -0,0 +1,22 @@ +
      +
      +
      + +
      +
      + +
      +
      +
      + +
      +{{#if succeeded}} +
      +
      + Successfully saved Belongs To Model +
      +
      +{{/if}} \ No newline at end of file diff --git a/tests/dummy/app/templates/components/.gitkeep b/tests/dummy/app/templates/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/templates/facebook.hbs b/tests/dummy/app/templates/facebook.hbs new file mode 100644 index 0000000..c60e8b6 --- /dev/null +++ b/tests/dummy/app/templates/facebook.hbs @@ -0,0 +1,52 @@ +
      +
      +

      Fetch a valid access_token and id at the Facebook Graph console

      +
      +
      + +{{#if loggedIn}} +
      +
      +

      Successfully Logged In

      +

      {{username}} {{loginMessage}}

      +
      +
      + +{{else}} +
      +
      +
      + +
      +
      + {{input value=accessToken id="accessToken" placeholder="a29gg2"}} +
      +
      + +
      +
      + +
      +
      + {{input value=password id="password"}} +
      +
      + +
      +
      +
      + +
      +
      + + {{#if loginMessage}} +
      +
      +
      +

      {{loginMessage}}

      +
      +
      + {{/if}} + +
      +{{/if}} \ No newline at end of file diff --git a/tests/dummy/app/templates/hasmany.hbs b/tests/dummy/app/templates/hasmany.hbs new file mode 100644 index 0000000..3474da9 --- /dev/null +++ b/tests/dummy/app/templates/hasmany.hbs @@ -0,0 +1,29 @@ +{{#if saved}} + +
      +
      + +
      +
      + +{{else}} + +
      +
      +
      + +
      +
      + {{input value=name id="tigerName" placeholder="Tigra"}} +
      +
      + +
      +
      +
      + +
      +
      + +
      +{{/if}} diff --git a/tests/dummy/app/templates/login.hbs b/tests/dummy/app/templates/login.hbs new file mode 100644 index 0000000..5f29e1a --- /dev/null +++ b/tests/dummy/app/templates/login.hbs @@ -0,0 +1,46 @@ +{{#if loggedIn}} +
      +
      +

      Successfully Logged In

      +

      {{username}} {{loginMessage}}

      +
      +
      + +{{else}} +
      +
      +
      + +
      +
      + {{input value=username id="username" placeholder="happydev"}} +
      +
      + +
      +
      + +
      +
      + {{input value=password id="password"}} +
      +
      + +
      +
      +
      + +
      +
      + + {{#if loginMessage}} +
      +
      +
      +

      {{loginMessage}}

      +
      +
      + {{/if}} + +
      +{{/if}} \ No newline at end of file diff --git a/tests/dummy/app/templates/signup.hbs b/tests/dummy/app/templates/signup.hbs new file mode 100644 index 0000000..92f0e2e --- /dev/null +++ b/tests/dummy/app/templates/signup.hbs @@ -0,0 +1,55 @@ +{{#if loggedIn}} +
      +
      +

      Successfully Signed Up

      +

      {{username}} {{loginMessage}}

      +
      +
      + +{{else}} +
      +
      +
      + +
      +
      + {{input value=username id="username" placeholder="happydev"}} +
      +
      + +
      +
      + +
      +
      + {{input value=password id="password"}} +
      +
      + +
      +
      + +
      +
      + {{input value=email id="email" placeholder="happydev@happyco.com"}} +
      +
      + +
      +
      +
      + +
      +
      + + {{#if loginMessage}} +
      +
      +
      +

      {{loginMessage}}

      +
      +
      + {{/if}} + +
      +{{/if}} diff --git a/tests/dummy/app/templates/simple.hbs b/tests/dummy/app/templates/simple.hbs new file mode 100644 index 0000000..b415d83 --- /dev/null +++ b/tests/dummy/app/templates/simple.hbs @@ -0,0 +1,29 @@ +
      +
      +
      + +
      +
      + {{input name="simple" value=name placeholder="Simple Object"}} +
      +
      +
      + +
      +{{#if succeeded}} +
      +
      + Successfully saved Simple Model +
      +
      +{{/if}} + +{{#if lastSavedModel}} +
      +
      +

      id: {{lastSavedModel.id}}

      +

      Name: {{lastSavedModel.name}}

      +

      Created At: {{lastSavedModel.createdAt}}

      +
      +
      +{{/if}} \ No newline at end of file diff --git a/tests/dummy/app/views/.gitkeep b/tests/dummy/app/views/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js new file mode 100644 index 0000000..8c7195e --- /dev/null +++ b/tests/dummy/config/environment.js @@ -0,0 +1,49 @@ +/* jshint node: true */ + +module.exports = function(environment) { + var ENV = { + modulePrefix: 'dummy', + environment: environment, + baseURL: '/', + locationType: 'auto', + EmberENV: { + FEATURES: { + // Here you can enable experimental features on an ember canary build + // e.g. 'with-controller': true + } + }, + + APP: { + // Here you can pass flags/options to your application instance + // when it is created + applicationId: 'Hf7dgrv4WPBcJYUsLgDMZCwKxf3hdbAc1nnSsVza', + restApiId: 'BH0IoMxroXSVU3GTMQTVaM4BXjvdX7lKtFujgvzO' + } + }; + + if (environment === 'development') { + // ENV.APP.LOG_RESOLVER = true; + // ENV.APP.LOG_ACTIVE_GENERATION = true; + // ENV.APP.LOG_TRANSITIONS = true; + // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; + // ENV.APP.LOG_VIEW_LOOKUPS = true; + } + + if (environment === 'test') { + // Testem prefers this... + ENV.baseURL = '/'; + ENV.locationType = 'none'; + + // keep test console output quieter + ENV.APP.LOG_ACTIVE_GENERATION = false; + ENV.APP.LOG_VIEW_LOOKUPS = false; + + ENV.APP.rootElement = '#ember-testing'; + } + + if (environment === 'production') { + + } + + return ENV; +}; diff --git a/tests/dummy/public/crossdomain.xml b/tests/dummy/public/crossdomain.xml new file mode 100644 index 0000000..29a035d --- /dev/null +++ b/tests/dummy/public/crossdomain.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/tests/dummy/public/robots.txt b/tests/dummy/public/robots.txt new file mode 100644 index 0000000..5debfa4 --- /dev/null +++ b/tests/dummy/public/robots.txt @@ -0,0 +1,2 @@ +# http://www.robotstxt.org +User-agent: * diff --git a/tests/helpers/resolver.js b/tests/helpers/resolver.js new file mode 100644 index 0000000..28f4ece --- /dev/null +++ b/tests/helpers/resolver.js @@ -0,0 +1,11 @@ +import Resolver from 'ember/resolver'; +import config from '../../config/environment'; + +var resolver = Resolver.create(); + +resolver.namespace = { + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix +}; + +export default resolver; diff --git a/tests/helpers/start-app.js b/tests/helpers/start-app.js new file mode 100644 index 0000000..16cc7c3 --- /dev/null +++ b/tests/helpers/start-app.js @@ -0,0 +1,19 @@ +import Ember from 'ember'; +import Application from '../../app'; +import Router from '../../router'; +import config from '../../config/environment'; + +export default function startApp(attrs) { + var application; + + var attributes = Ember.merge({}, config.APP); + attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; + + Ember.run(function() { + application = Application.create(attributes); + application.setupForTesting(); + application.injectTestHelpers(); + }); + + return application; +} diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 0000000..8fea6fe --- /dev/null +++ b/tests/index.html @@ -0,0 +1,33 @@ + + + + + + Dummy Tests + + + + {{content-for 'head'}} + {{content-for 'test-head'}} + + + + + + {{content-for 'head-footer'}} + {{content-for 'test-head-footer'}} + + + + {{content-for 'body'}} + {{content-for 'test-body'}} + + + + + + + {{content-for 'body-footer'}} + {{content-for 'test-body-footer'}} + + diff --git a/tests/test-helper.js b/tests/test-helper.js new file mode 100644 index 0000000..e6cfb70 --- /dev/null +++ b/tests/test-helper.js @@ -0,0 +1,6 @@ +import resolver from './helpers/resolver'; +import { + setResolver +} from 'ember-qunit'; + +setResolver(resolver); diff --git a/tests/unit/.gitkeep b/tests/unit/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/adapters/application-test.js b/tests/unit/adapters/application-test.js new file mode 100644 index 0000000..2f9ed85 --- /dev/null +++ b/tests/unit/adapters/application-test.js @@ -0,0 +1,460 @@ +import Ember from 'ember'; +import Adapter from 'ember-parse-adapter/adapters/application'; +import Serializer from 'ember-parse-adapter/serializers/application'; + +var buildContainer = function() { + var container = new Ember.Container(); + DS._setupContainer( container ); + + container.register( 'serializer:-parse', Serializer ); + + return container; + }, + get = Ember.get, + set = Ember.set, + adapter, + container, + store, + serializer, + ajaxUrl, + ajaxType, + ajaxHash, + Post, + Comment; + +module( 'Unit - adapter:application', { + beforeEach: function() { + ajaxUrl = undefined; + ajaxType = undefined; + ajaxHash = undefined; + + container = buildContainer(); + + container.register( 'adapter:application', Adapter.extend({ + ajax: function( url, method, hash ) { + return new Ember.RSVP.Promise( function( res, rej ) { + hash = hash || {}; + var success = hash.success; + + hash.context = adapter; + + ajaxUrl = url; + ajaxType = method; + ajaxHash = hash; + + hash.success = function( json ) { + Ember.run(function(){ + res(json); + }); + }; + + hash.error = function( xhr ) { + Ember.run( function() { + rej(xhr); + }); + }; + }); + } + })); + + container.register( 'model:post', DS.Model.extend({ + title : DS.attr( 'string' ), + comments : DS.hasMany( 'comment', {async: true} ) + })); + + container.register( 'model:comment', DS.Model.extend({ + content : DS.attr( 'string'), + post : DS.belongsTo( 'post', {async: true} ) + })); + + store = container.lookup( 'store:main' ); + adapter = container.lookup( 'adapter:application' ); + serializer = container.lookup( 'serializer:-parse' ); + + Post = store.modelFor( 'post' ); + Comment = store.modelFor( 'comment' ); + }, + + afterEach: function() { + Ember.run( container, 'destroy' ); + } +}); + +test( 'find', function( assert ) { + var post = Ember.run( function() { + return store.find( 'post', 'firstPost' ); + }), + isLoaded; + + assert.ok( !get( post, 'isLoaded' ) ); + assert.equal( ajaxUrl, 'https://api.parse.com/1/classes/Post/firstPost', 'The Parse API version and classes with Post and ID.' ); + assert.equal( ajaxType, 'GET' ); + assert.ok( Ember.get( ajaxHash, 'context.headers' ).hasOwnProperty( 'X-Parse-REST-API-Key' ), 'has rest api header' ); + assert.ok( Ember.get( ajaxHash, 'context.headers' ).hasOwnProperty( 'X-Parse-Application-Id' ), 'has rest application header' ); + + ajaxHash.success({ + objectId : 'firstPost', + title : 'Testing Find' + }); + + assert.ok( get( post, 'isLoaded' ) ); + assert.ok( !get( post, 'isDirty' ) ); + + isLoaded = store.recordIsLoaded( 'post', 'firstPost' ); + assert.ok( isLoaded, 'the record is now in the store, and can be looked up by ID without another AJAX request' ); +}); + +test( 'find with sessionToken', function( assert ) { + var post = Ember.run( function() { + adapter.set( 'sessionToken', 'some-odd-token' ); + return store.find( 'post', 'firstPost' ); + }), + isLoaded; + + assert.ok( !get( post, 'isLoaded' ) ); + assert.equal( ajaxUrl, 'https://api.parse.com/1/classes/Post/firstPost', 'The Parse API version and classes with Post and ID.' ); + assert.equal( ajaxType, 'GET' ); + assert.equal( Ember.get( ajaxHash, 'context.headers.X-Parse-Session-Token'), 'some-odd-token', 'has session header' ); + + ajaxHash.success({ + objectId: 'firstPost', + title: 'Testing Find' + }); + + assert.ok( get(post, 'isLoaded' ) ); + assert.ok( !get(post, 'isDirty' ) ); + + isLoaded = store.recordIsLoaded( 'post', 'firstPost' ); + assert.ok( isLoaded, 'the record is now in the store, and can be looked up by ID without another AJAX request' ); +}); + +test( 'can get a set sessionToken', function( assert ) { + var token = Ember.run( function() { + adapter.set( 'sessionToken', 'some-odd-token' ); + return adapter.get( 'sessionToken' ); + }); + + assert.equal( token, 'some-odd-token' ); +}); + +test( 'findAll', function( assert ) { + var posts, + post, + isLoaded; + + posts = store.find( 'post' ); + + assert.expect( ajaxUrl, '/1/classes/Post', 'The Parse API version and classes with Post.' ); + assert.equal( ajaxType, 'GET' ); + + // Parse REST API wraps the collections in a results JSON label. + ajaxHash.success({ results: [ + {objectId: '1', title: 'First Post.'}, + {objectId: '2', title: 'Second Post.'} + ]}); + + + posts.then( function( _posts ) { + posts = _posts; + }); + + Ember.run( function() { + assert.equal( get( posts, 'length' ), 2 ); + }); + + post = posts.objectAt(0); + + assert.ok( get( post, 'isLoaded' ) ); + assert.ok( !get( post, 'isDirty' ) ); + + isLoaded = store.recordIsLoaded( 'post', '1' ); + + assert.ok( isLoaded, 'the record is now in the store, and can be looked up by ID without another Ajax request' ); +}); + +QUnit.skip( 'findMany via a hasMany relationship', function( assert ) { + Ember.run( function() { + store.push( 'post', {id: 'one', comments: ['aa1', 'bb2', 'cc3']} ); + }); + + Ember.run( function() { + store.find( 'post', 'one' ).then( function( post ) { + return get( post, 'comments' ); + }).then( function( comments ) { + var comment1, + comment2, + comment3; + + assert.equal( get( comments, 'length' ), 3, 'there are three comments in the relationship already' ); + + comment1 = comments.objectAt(0); + assert.equal( get( comment1, 'id' ), 'aa1' ); + + comment2 = comments.objectAt(1); + assert.equal( get( comment2, 'id' ), 'bb2' ); + + comment3 = comments.objectAt(2); + assert.equal( get( comment3, 'id' ), 'cc3' ); + + comments.forEach( function( comment ) { + assert.equal( get( comment, 'isLoaded' ), true, 'comment is loaded' ); + }); + }); + }); + + assert.expect( ajaxUrl, '/1/classes/Comment', 'requests the comment class' ); + assert.equal( ajaxType, 'POST' ); + assert.deepEqual( ajaxHash.data, {where: {objectId: {'$in': 'aa1,bb2,cc3'}}}, 'the hash was passed along' ); + + ajaxHash.success({ results: [ + {objectId: 'aa1', content: 'Comment 1'}, + {objectId: 'bb2', content: 'Comment 2'}, + {objectId: 'cc3', content: 'Comment 3'} + ]}); +}); + +test( 'Find Query with non-string where', function( assert ) { + var posts = store.find( 'post', {where: {title: 'First Post'}} ); + + assert.equal( get( posts, 'length' ), 0, 'there are no posts yet as the query has not returned.' ); + assert.expect( ajaxUrl, '/1/classes/Post', 'requests the post class' ); + assert.equal( ajaxType, 'GET' ); + assert.deepEqual( ajaxHash.data, {where: JSON.stringify({title: 'First Post'}) }, 'where clause is passed as stringified data' ); + + ajaxHash.success({ results: [ + { objectId: 'bad1', title: 'First Post' }, + { objectId: 'bad2', title: 'First Post' } + ]}); +}); + +test( 'Find Query with where as string', function( assert ){ + var posts = store.find( 'post', {where: "{title: 'First Post'}"} ); + + assert.equal( get( posts, 'length' ), 0, 'there are no posts yet as the query has not returned.' ); + assert.expect( ajaxUrl, '/1/classes/Post', 'requests the post class' ); + assert.equal( ajaxType, 'GET' ); + assert.deepEqual( ajaxHash.data, {where: "{title: 'First Post'}"}, 'where clause is passed through as string' ); + + ajaxHash.success({ results: [ + { objectId: 'bad1', title: 'First Post'}, + { objectId: 'bad2', title: 'First Post'} + ]}); + + assert.equal( get( posts, 'length' ), 2, 'there are 2 posts loaded' ); + + posts.forEach( function(post) { + assert.equal( get( post, 'isLoaded' ), true, 'the post is being loaded' ); + }); +}); + +test( 'Create Record', function( assert ) { + stop(); + + var post, + promise; + + Ember.run( function() { + post = store.createRecord( 'post', {title: 'Testing Create'} ); + assert.ok( get( post, 'isNew' ), 'record is new' ); + promise = post.save(); + }); + + assert.ok( get( post, 'isSaving' ), 'record is saving' ); + assert.equal( ajaxUrl, 'https://api.parse.com/1/classes/Post', 'requests the post class' ); + assert.equal( ajaxType, 'POST' ); + + // Passing comments as an Ember array. This is due to a bug in Ember-Data + // expecting an Ember array for data and not a raw array: + // https://github.com/emberjs/data/pull/1939 + assert.deepEqual( ajaxHash.data, {comments: Ember.A(), title: 'Testing Create'}, 'raw data is posted' ); + + ajaxHash.success({ + objectId : 'created321', + createdAt : ( new Date() ).toISOString() + }); + + Ember.run( function() { + promise.then( function() { + assert.ok( !get( post, 'isSaving'), 'post is not saving after save' ); + assert.ok( !get( post, 'isDirty'), 'post is not dirty after save' ); + start(); + }); + }); +}); + +QUnit.skip( 'Create Record - bulkCommit', function( assert ) { + var posts = new Ember.Set([ + store.createRecord(Post, {title: 'Post 1'}), + store.createRecord(Post, {title: 'Post 2'}) + ]), + expectStates, + expectUrl, + expectType, + expectData, + expect; + + expectStates( posts, 'new' ); + store.commit(); + + expectStates( posts, 'saving' ); + expectUrl( '/1/batch' ); + expectType( 'POST' ); + + + //This payload should match expected schema: https://www.parse.com/docs/rest#objects-batch + expectData({ requests: [ + { + method: 'POST', + path: '/1/classes/Post', + body: {comments: [], title: 'Post 1', updatedAt: undefined, createdAt: undefined} + }, + { + method: 'POST', + path: '/1/classes/Post', + body: {comments: [], title: 'Post 2', updatedAt: undefined, createdAt: undefined} + } + ]}); + + ajaxHash.success([ + {success: {objectId: 'post1', createdAt: (new Date()).toISOString()}}, + {success: {objectId: 'post2', createdAt: (new Date()).toISOString()}} + ]); + + expectStates( posts, 'saving', false ); + expect( posts[0], store.find(Post, 'post1'), 'should match first post.' ); + expect( posts[1], store.find(Post, 'post2'), 'should match second post.' ); +}); + +QUnit.skip( 'Update Record - not bulkCommit', function( assert ) { + store.load( Post, {title: 'Test Post Update', objectId: 'postUpdated'} ); + + // force it to use single record update + adapter.bulkCommit = false; + var post = store.find( Post, 'postUpdated' ), + expectState, + expectUrl, + expectType, + expectData; + + expectState('loaded'); + expectState('dirty', false); + post.set('title', 'Test Post Updated - true'); + expectState('dirty'); + store.commit(); + expectState('saving'); + expectUrl('/1/classes/Post/postUpdated'); + expectType('PUT'); + expectData({objectId: 'postUpdated', comments: [], title: 'Test Post Updated - true', updatedAt: undefined, createdAt: undefined}); + ajaxHash.success({objectId: 'postUpdated', updatedAt: (new Date()).toISOString()}); + expectState('saving', false); +}); + +QUnit.skip( 'Update Record - bulkCommit', function( assert ) { + store.loadMany(Post, [ + {objectId: 'post1', title: 'Post 1'}, + {objectId: 'post2', title: 'Post 2'} + ]); + + var posts = store.findMany(Post, ['post1', 'post2']), + expectStates, + expectUrl, + expectType, + expectData, + expect; + + expectStates(posts, 'loaded'); + posts.forEach(function(post){ + post.set('title', post.get('title') + ' updated.'); + }); + expectStates(posts, 'dirty'); + store.commit(); + expectStates(posts, 'saving'); + expectUrl('/1/batch'); + expectType('POST'); + expectData({ + requests: [ + { + method: 'PUT', + path: '/1/classes/Post/post1', + body: {objectId: 'post1', comments: [], title: 'Post 1 updated.', updatedAt: undefined, createdAt: undefined} + }, + { + method: 'PUT', + path: '/1/classes/Post/post2', + body: {objectId: 'post2', comments: [], title: 'Post 2 updated.', updatedAt: undefined, createdAt: undefined} + } + ] + }); + ajaxHash.success([ + {success: {objectId: 'post1', updatedAt: (new Date()).toISOString()}}, + {success: {objectId: 'post2', updatedAt: (new Date()).toISOString()}} + ]); + expectStates(posts, 'saving', false); + expect(posts[0], store.find(Post, 'post1'), 'should match first post.'); + expect(posts[1], store.find(Post, 'post2'), 'should match second post.'); +}); + +QUnit.skip( 'Delete Record - not bulkCommit', function( assert ){ + store.load(Post, {objectId: 'post1', title: 'Post to delete.'}); + // force single record delete + adapter.bulkCommit = false; + var post = store.find(Post, 'post1'), + expectState, + expectUrl, + expectType; + + expectState('new', false); + expectState('loaded'); + expectState('dirty', false); + post.deleteRecord(); + expectState('dirty'); + expectState('deleted'); + store.commit(); + expectState('saving'); + expectUrl('/1/classes/Post/post1'); + expectType('DELETE'); + ajaxHash.success(); + expectState('deleted'); +}); + +QUnit.skip( 'Delete Record - bulkCommit', function( assert ){ + store.loadMany(Post, [ + {objectId: 'post1', title: 'Post 1'}, + {objectId: 'post2', title: 'Post 2'} + ]); + var posts = store.findMany(Post, ['post1', 'post2']), + expectStates, + expectUrl, + expectType, + expectData; + + expectStates(posts, 'loaded'); + expectStates(posts, 'new', false); + expectStates(posts, 'dirty', false); + posts.forEach(function(post){ + post.deleteRecord(); + }); + expectStates(posts, 'dirty'); + expectStates(posts, 'deleted'); + store.commit(); + expectStates(posts, 'saving'); + expectUrl('/1/batch'); + expectType('POST'); + expectData({ + requests: [ + { + method: 'DELETE', + path: '/1/classes/Post/post1', + body: {objectId: 'post1', comments: [], title: 'Post 1', updatedAt: undefined, createdAt: undefined} + }, + { + method: 'DELETE', + path: '/1/classes/Post/post2', + body: {objectId: 'post2', comments: [], title: 'Post 2', updatedAt: undefined, createdAt: undefined} + } + ] + }); + ajaxHash.success(); + expectStates(posts, 'saving', false); + expectStates(posts, 'dirty', false); +}); diff --git a/tests/unit/initializers/initialize-test.js b/tests/unit/initializers/initialize-test.js new file mode 100644 index 0000000..a645237 --- /dev/null +++ b/tests/unit/initializers/initialize-test.js @@ -0,0 +1,50 @@ +import Ember from 'ember'; +import { test, moduleForComponent } from 'ember-qunit'; +import startApp from '../../helpers/start-app'; + +var App; + +module( 'Unit - initializer:initialize', { + beforeEach: function() { + App = startApp(); + }, + + afterEach: function() { + Ember.run( App, App.destroy ); + } +}); + +test( 'Adapter is registered on container', function( assert ) { + assert.equal( typeof App.__container__.lookup( 'adapter:-parse' ), 'object' ); + assert.equal( App.__container__._options['adapter:-parse'].instantiate, undefined ); +}); + +test( 'Adapter has header values set to expected values', function( assert ) { + assert.equal( App.__container__.lookup( 'adapter:-parse' ).headers['X-Parse-Application-Id'], 'Hf7dgrv4WPBcJYUsLgDMZCwKxf3hdbAc1nnSsVza' ); + assert.equal( App.__container__.lookup( 'adapter:-parse' ).headers['X-Parse-REST-API-Key'], 'BH0IoMxroXSVU3GTMQTVaM4BXjvdX7lKtFujgvzO' ); +}); + +test( 'Serializer is registered on container', function( assert ) { + assert.equal( typeof App.__container__.lookup( 'serializer:-parse' ), 'object' ); + assert.equal( App.__container__._options['serializer:-parse'].instantiate, undefined ); +}); + +test( 'Parse Date transform is registered on container', function( assert ) { + assert.equal( typeof App.__container__.lookup( 'transform:parse-date' ), 'object' ); + assert.equal( App.__container__._options['transform:parse-date'].instantiate, undefined ); +}); + +test( 'Parse File transform is registered on container', function( assert ) { + assert.equal( typeof App.__container__.lookup( 'transform:parse-file' ), 'object' ); + assert.equal( App.__container__._options['transform:parse-file'].instantiate, undefined ); +}); + +test( 'Parse GeoPoint transform is registered on container', function( assert ) { + assert.equal( typeof App.__container__.lookup( 'transform:parse-geo-point' ), 'object' ); + assert.equal( App.__container__._options['transform:parse-geo-point'].instantiate, undefined ); +}); + +test( 'Parse User model is registered on container', function( assert ) { + assert.equal( typeof App.__container__._options['model:parse-user'], 'object' ); + assert.equal( App.__container__._options['model:parse-user'].instantiate, undefined ); +}); \ No newline at end of file diff --git a/tests/unit/models/parse-user-test.js b/tests/unit/models/parse-user-test.js new file mode 100644 index 0000000..7d55966 --- /dev/null +++ b/tests/unit/models/parse-user-test.js @@ -0,0 +1,305 @@ +import Ember from 'ember'; +import DS from 'ember-data'; +import Adapter from 'ember-parse-adapter/adapters/application'; +import Serializer from 'ember-parse-adapter/serializers/application'; +import ParseUserImport from 'ember-parse-adapter/models/parse-user'; + +var buildContainer = function() { + var container = new Ember.Container(); + DS._setupContainer( container ); + + container.register( 'adapter:-parse', Adapter ); + container.register( 'serializer:-parse', Serializer ); + container.register( 'model:parse-user', ParseUserImport.extend({ + nickname: DS.attr( 'string' ) + })); + + return container; + }, + get = Ember.get, + set = Ember.set, + container, + adapter, + serializer, + store, + ajaxUrl, + ajaxType, + ajaxHash, + ParseUser; + +module( 'Unit - model:parse-user', { + beforeEach: function() { + ajaxUrl = undefined; + ajaxType = undefined; + ajaxHash = undefined; + + container = buildContainer(); + + container.register( 'adapter:application', Adapter.extend({ + ajax: function( url, method, hash ) { + return new Ember.RSVP.Promise( function( res, rej ) { + hash = hash || {}; + var success = hash.success; + + hash.context = adapter; + + ajaxUrl = url; + ajaxType = method; + ajaxHash = hash; + + hash.success = function( json ) { + Ember.run( function() { + res(json); + }); + }; + + hash.error = function( xhr ) { + Ember.run( function() { + rej(xhr); + }); + }; + }); + } + })); + + store = container.lookup( 'store:main' ); + adapter = container.lookup( 'adapter:application' ); + serializer = container.lookup( 'serializer:-parse' ); + + ParseUser = store.modelFor( 'parse-user' ); + }, + + afterEach: function() { + Ember.run( container, 'destroy' ); + } +}); + +test( 'Signup', function( assert ) { + var promise; + + Ember.run( function() { + promise = ParseUser.signup( store, { + username : 'clintjhill', + password : 'loveyouall', + email : 'clint@foo.com' + }); + }); + + assert.equal( ajaxUrl, 'https://api.parse.com/1/users', 'The Parse API version and user path' ); + assert.equal( ajaxType, 'POST' ); + assert.deepEqual( ajaxHash.data, { + username : 'clintjhill', + password : 'loveyouall', + email : 'clint@foo.com' + }, 'the hash was passed along' ); + + ajaxHash.success({ + 'createdAt' : '2011-11-07T20:58:34.448Z', + 'objectId' : 'g7y9tkhB7O', + 'sessionToken' : 'pnktnjyb996sj4p156gjtp4im' + }); + + Ember.run( function() { + promise.then( function( user ) { + assert.ok( !get( user, 'isSaving'), 'user is not saving' ); + assert.ok( !get( user, 'isDirty'), 'user is not dirty' ); + assert.equal( get( user, 'id'), 'g7y9tkhB7O', 'Be sure objectId is set.' ); + assert.equal( get( user, 'password'), null, 'Be sure that password gets dumped.' ); + assert.equal( get( user, 'sessionToken'), 'pnktnjyb996sj4p156gjtp4im', 'Make sure session token set.' ); + }); + }); +}); + +test( 'Signup with Facebook', function( assert ) { + var expirationDate = ( new Date() ).toISOString(), + promise; + + Ember.run( function() { + promise = ParseUser.signup( store, { + authData: { + facebook: { + access_token : 'some-fake-token', + id : 'some-id', + expiration_date : expirationDate + } + } + }); + }); + + assert.equal( ajaxUrl, 'https://api.parse.com/1/users', 'The Parse API version and user path' ); + assert.equal( ajaxType, 'POST' ); + assert.deepEqual( ajaxHash.data, { + authData: { + facebook: { + access_token : 'some-fake-token', + id : 'some-id', + expiration_date : expirationDate + } + } + }, 'the hash was passed along' ); + + ajaxHash.success({ + 'authData' : {}, + 'createdAt' : '2011-11-07T20:58:34.448Z', + 'objectId' : 'g7y9tkhB7O', + 'sessionToken' : 'pnktnjyb996sj4p156gjtp4im', + 'username' : 'foofoo-username' + }); + + Ember.run( function() { + promise.then( function( user ) { + assert.ok( !get( user, 'isSaving' ), 'user is not saving' ); + assert.ok( !get( user, 'isDirty' ), 'user is not dirty' ); + assert.equal( get( user, 'id' ), 'g7y9tkhB7O', 'Be sure objectId is set.' ); + assert.equal( get( user, 'password' ), null, 'Be sure that password gets dumped.' ); + assert.equal( get( user, 'sessionToken' ), 'pnktnjyb996sj4p156gjtp4im', 'Make sure session token set.' ); + assert.equal( get( user, 'username' ), 'foofoo-username', 'Make sure username set.' ); + }); + }); +}); + +test( 'Find', function( assert ) { + var user; + + Ember.run( function() { + user = store.find( 'parse-user', 'h8mgfgL1yS' ); + }); + + assert.ok( !get( user, 'isLoaded' ) ); + assert.equal( ajaxUrl, 'https://api.parse.com/1/users/h8mgfgL1yS', 'The Parse API version and user path' ); + assert.equal( ajaxType, 'GET' ); + + ajaxHash.success({ + 'createdAt' : '2011-11-07T20:58:34.448Z', + 'objectId' : 'h8mgfgL1yS', + 'username' : 'clintjhill' + }); + + assert.ok( get( user, 'isLoaded') ); + assert.ok( !get( user, 'isCurrent' ), 'User should not be current during a find.' ); +}); + +test( 'Login', function( assert ) { + var user, + promise; + + Ember.run( function() { + promise = ParseUser.login( store, {username: 'clint', password: 'loveyouall'} ); + }); + + assert.equal( ajaxUrl, 'https://api.parse.com/1/login', 'The Parse API version and user path' ); + assert.equal( ajaxType, 'GET'); + assert.deepEqual(ajaxHash.data, { + username : 'clint', + password : 'loveyouall' + }); + + ajaxHash.success({ + 'username' : 'clint', + 'createdAt' : '2011-11-07T20:58:34.448Z', + 'updatedAt' : '2011-11-07T20:58:34.448Z', + 'objectId' : 'g7y9tkhB7O', + 'sessionToken' : 'pnktnjyb996sj4p156gjtp4im' + }); + + Ember.run( function() { + promise.then( function( user ) { + assert.ok( get( user, 'isLoaded' ) ); + assert.equal( get( user, 'password' ), null, 'Be sure that password gets dumped.' ); + }); + }); +}); + +QUnit.skip( 'Password Reset Request', function( assert ) { + var User, + user, + expectState, + expectType, + expectUrl; + + store.load( User, {objectId: 'aid8nalX'} ); + + user = store.find( User, 'aid8nalX' ); + + // expected events + user.on( 'requestingPasswordReset', function() { + // while password reset request is being sent + expectState( 'passwordResetting' ); + }); + + user.on( 'didRequestPasswordReset', function() { + // password reset request happened + expectState( 'loaded' ); + }); + + // reset it + user.requestPasswordReset( 'clint.hill@gmail.com' ); + + expectType( 'POST' ); + expectUrl( '/1/requestPasswordReset', 'Request password path from Parse.' ); + expectState( 'passwordResetting' ); + ajaxHash.success(); + expectState( 'loaded' ); +}); + +QUnit.skip( 'Update (batch) - Session token handling', function( assert ) { + var allowsUpdate, + noUpdates, + User, + expectState; + + store.loadMany( User, [ + {objectId: 'xuF8hlkrg', username: 'clintjhill', email: 'nope@yep.com'}, + {objectId: 'inol8HFer', username: 'clinthill', email: 'yep@nope.com', sessionToken: 'ivegotasession'} + ]); + + allowsUpdate = store.find( User, 'inol8HFer' ); + noUpdates = store.find( User, 'xuF8hlkrg' ); + + allowsUpdate.set( 'password', 'notHacked' ); + noUpdates.set( 'password', 'youGotHacked' ); + + expectState( 'dirty', true, allowsUpdate ); + expectState( 'dirty', true, noUpdates ); + + store.commit(); + + ajaxHash.success([ + {success: {updatedAt: (new Date()).toISOString()}}, + {error: {code: 101, error: 'some message'}} + ]); + + expectState( 'error', true, noUpdates ); + expectState( 'loaded', true, allowsUpdate ); +}); + +test( 'Subclassing Parse User', function( assert ) { + var user, + promise; + + Ember.run( function() { + promise = ParseUser.login( store, {username: 'clint', password: 'loveyouall', nickname: 'rick'} ); + }); + + assert.deepEqual( ajaxHash.data, { + username: 'clint', + password: 'loveyouall', + nickname: 'rick' + }); + + ajaxHash.success({ + 'username': 'clint', + 'nickname': 'rick', + 'createdAt': '2011-11-07T20:58:34.448Z', + 'updatedAt': '2011-11-07T20:58:34.448Z', + 'objectId': 'g7y9tkhB7O', + 'sessionToken': 'pnktnjyb996sj4p156gjtp4im' + }); + + Ember.run( function() { + promise.then( function( user ) { + assert.ok( get( user, 'isLoaded' ) ); + assert.equal( get( user, 'nickname' ), 'rick', 'Additional attributes are added.' ); + }); + }); +}); \ No newline at end of file diff --git a/tests/unit/serializers/application-test.js b/tests/unit/serializers/application-test.js new file mode 100644 index 0000000..63480c2 --- /dev/null +++ b/tests/unit/serializers/application-test.js @@ -0,0 +1,97 @@ +import Ember from 'ember'; +import DS from 'ember-data'; +import Adapter from 'ember-parse-adapter/adapters/application'; +import Serializer from 'ember-parse-adapter/serializers/application'; + +var buildContainer = function() { + var container = new Ember.Container(); + DS._setupContainer( container ); + + container.register( 'adapter:-parse', Adapter ); + container.register( 'serializer:-parse', Serializer ); + + return container; + }, + container, + store, + serializer, + Post, + Comment; + +module( 'Unit - serializer:application', { + beforeEach: function() { + container = buildContainer(); + serializer = container.lookup( 'serializer:-parse' ); + store = container.lookup( 'store:main' ); + + container.register( 'model:post', DS.Model.extend({ + title : DS.attr( 'string' ), + comments : DS.hasMany('comment') + })); + + container.register( 'model:comment', DS.Model.extend({ + content : DS.attr( 'string' ), + post : DS.belongsTo( 'post' ) + })); + + Post = store.modelFor( 'post' ); + Comment = store.modelFor( 'comment' ); + + container.register( 'serializer:post', Serializer ); + container.register( 'serializer:comment', Serializer ); + }, + + afterEach: function() { + Ember.run( container, 'destroy' ); + } +}); + +test( 'Requires objectId as key', function( assert ) { + assert.equal( Ember.get( serializer, 'primaryKey' ), 'objectId', 'Should be objectId.' ); +}); + +test( 'A single post is extracted', function( assert ) { + var id = 'test', + title = 'Test rooting', + res = serializer.extractSingle( store, Post, {objectId: id, title: title}, id ); + + assert.equal( res.id, id, 'objectId should be put on post namespace' ); + assert.equal( res.title, title, 'Title should be put on post namespace' ); +}); + +test( 'Many posts are extracted', function( assert ) { + var array = [{ + objectId: 'testA', + title: 'Test A' + }, { + objectId: 'testB', + title: 'Test B' + }], + res = store.serializerFor( Post ).extractArray( store, Post, { results: array } ); + + assert.equal( res.length, 2, 'normalized array of posts' ); + + assert.equal( res[0].id, 'testA', 'objectId should be put on post namespace' ); + assert.equal( res[0].title, 'Test A', 'Title should be put on post namespace' ); + + assert.equal( res[1].id, 'testB', 'objectId should be put on post namespace' ); + assert.equal( res[1].title, 'Test B', 'Title should be put on post namespace' ); +}); + +QUnit.skip( 'hasMany for serialization (Parse Pointer)', function( assert ) { + var serialized, + hash = {}, + relationship = { options: { embedded: false }}, + post, + comment; + + store.load( Post, '1', {title: 'Testing hasMany serialization.'} ); + store.load( Comment, '1', {content: 'Comment 1'} ); + post = store.find(Post, '1'); + comment = store.find(Comment, '1'); + post.get( 'comments').pushObject( comment ); + serializer.addHasMany( hash, post, 'comments', relationship ); + + assert.equal( hash.comments[0]['__type'], 'Pointer', 'Should be a Pointer __type/class.' ); + assert.equal( hash.comments[0]['className'], 'Comment', 'Should be Comment class.' ); +}); \ No newline at end of file diff --git a/tests/unit/transforms/date-test.js b/tests/unit/transforms/date-test.js new file mode 100644 index 0000000..6ebe7f9 --- /dev/null +++ b/tests/unit/transforms/date-test.js @@ -0,0 +1,35 @@ +import Ember from 'ember'; +import DateTransform from 'ember-parse-adapter/transforms/date'; + +var transform; + +module( 'Unit - transforms:date', { + setup: function() { + transform = DateTransform.create(); + }, + teardown: function() { + Ember.run( transform, 'destroy' ); + } +}); + +test( 'Serializes', function( assert ) { + var date = new Date( 2013, 10, 10 ), + origIso = date.toISOString(), + data = transform.serialize( date ); + + assert.equal( data.iso, origIso, 'iso is rendered' ); + assert.equal( data.__type, 'Date', 'has the proper type' ); +}); + +test( 'Deserializes', function( assert ) { + var date = transform.deserialize( '2013-11-10T05:00:00.000Z' ); + + assert.ok( date instanceof Date, 'is a date' ); + assert.equal( date.getTime(), 1384059600000, 'timestamp is correct' ); +}); + +test( 'Deserializes null to null', function( assert ) { + var data = transform.deserialize( null ); + + assert.ok( data === null, 'Serialization of null is null' ); +}); \ No newline at end of file diff --git a/tests/unit/transforms/file-test.js b/tests/unit/transforms/file-test.js new file mode 100644 index 0000000..0da2ad8 --- /dev/null +++ b/tests/unit/transforms/file-test.js @@ -0,0 +1,44 @@ +import Ember from 'ember'; +import FileTransform from 'ember-parse-adapter/transforms/file'; +import File from 'ember-parse-adapter/file'; + +var transform; + +module( 'Unit - transforms:file', { + setup: function() { + transform = FileTransform.create(); + }, + teardown: function() { + Ember.run( transform, 'destroy' ); + } +}); + +test( 'Serializes', function( assert ) { + var file = File.create({ + name : 'car', + url : 'http://example.com/car.png' + }), + data = transform.serialize( file ); + + assert.equal( data.name, file.get( 'name' ), 'name is preserved' ); + assert.equal( data.url, file.get( 'url' ), 'url is preserved' ); + assert.equal( data.__type, 'File', 'has the proper type' ); +}); + +test( 'Deserializes', function( assert ) { + var data = { + name : 'Plane', + url : 'http://example.com/plane.png', + __type : 'File' + }, + file = transform.deserialize( data ); + + assert.ok( file instanceof DS.Transform, 'is a DS.Transform' ); + assert.equal( file.get( 'name' ), data.name, 'name is preserved' ); + assert.equal( file.get( 'url' ), data.url, 'url is preserved' ); +}); + +test( 'Deserializes null to null', function( assert ) { + var file = transform.deserialize( null ); + assert.ok( file === null, 'Deserialization of null is null' ); +}); \ No newline at end of file diff --git a/tests/unit/transforms/geopoint-test.js b/tests/unit/transforms/geopoint-test.js new file mode 100644 index 0000000..23dbefb --- /dev/null +++ b/tests/unit/transforms/geopoint-test.js @@ -0,0 +1,39 @@ +import Ember from 'ember'; +import GeoPointTransform from 'ember-parse-adapter/transforms/geopoint'; +import GeoPoint from 'ember-parse-adapter/geopoint'; + +var transform; + +module( 'Unit - transforms:geopoint', { + setup: function() { + transform = GeoPointTransform.create(); + }, + teardown: function() { + Ember.run( transform, 'destroy' ); + } +}); + +test( 'Serializes', function( assert ) { + var geoPoint = GeoPoint.create({ + latitude : 4.53, + longitude : 3.33 + }), + data = transform.serialize( geoPoint ); + + assert.equal( data.latitude, geoPoint.get( 'latitude' ), 'latitude is preserved' ); + assert.equal( data.longitude, geoPoint.get( 'longitude' ), 'longitude is preserved' ); + assert.equal( data.__type, 'GeoPoint', 'has the proper type' ); +}); + +test( 'Deserializes', function( assert ) { + var data = { + latitude : 3.43, + longitude : 4.2, + __type : 'GeoPoint' + }, + point = transform.deserialize( data ); + + assert.ok( point instanceof DS.Transform, 'is a geo point' ); + assert.equal( point.get( 'latitude' ), data.latitude, 'latitude is preserved' ); + assert.equal( point.get( 'longitude' ), data.longitude, 'longitude is preserved' ); +}); \ No newline at end of file diff --git a/vendor/.gitignore b/vendor/.gitignore deleted file mode 100644 index 72e8ffc..0000000 --- a/vendor/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/vendor/.gitkeep b/vendor/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/vendor/foundation.min.css b/vendor/foundation.min.css deleted file mode 100644 index b97a40e..0000000 --- a/vendor/foundation.min.css +++ /dev/null @@ -1 +0,0 @@ -@import url("//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,700");meta.foundation-mq-small{font-family:"/only screen and (max-width: 40em)/";width:0em}meta.foundation-mq-medium{font-family:"/only screen and (min-width:40.063em) and (max-width:64em)/";width:40.063em}meta.foundation-mq-large{font-family:"/only screen and (min-width:64.063em)/";width:64.063em}meta.foundation-mq-xlarge{font-family:"/only screen and (min-width:90.063em)/";width:90.063em}meta.foundation-mq-xxlarge{font-family:"/only screen and (min-width:120.063em)/";width:120.063em}*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}html,body{font-size:100%}body{background:#fff;color:#222;padding:0;margin:0;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;font-weight:normal;font-style:normal;line-height:1;position:relative;cursor:default}a:hover{cursor:pointer}img,object,embed{max-width:100%;height:auto}object,embed{height:100%}img{-ms-interpolation-mode:bicubic}#map_canvas img,#map_canvas embed,#map_canvas object,.map_canvas img,.map_canvas embed,.map_canvas object{max-width:none !important}.left{float:left !important}.right{float:right !important}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}.text-left{text-align:left !important}.text-right{text-align:right !important}.text-center{text-align:center !important}.text-justify{text-align:justify !important}.hide{display:none}.antialiased{-webkit-font-smoothing:antialiased}img{display:inline-block;vertical-align:middle}textarea{height:auto;min-height:50px}select{width:100%}.row{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5rem;*zoom:1}.row:before,.row:after{content:" ";display:table}.row:after{clear:both}.row.collapse>.column,.row.collapse>.columns{position:relative;padding-left:0;padding-right:0;float:left}.row.collapse .row{margin-left:0;margin-right:0}.row .row{width:auto;margin-left:-0.9375rem;margin-right:-0.9375rem;margin-top:0;margin-bottom:0;max-width:none;*zoom:1}.row .row:before,.row .row:after{content:" ";display:table}.row .row:after{clear:both}.row .row.collapse{width:auto;margin:0;max-width:none;*zoom:1}.row .row.collapse:before,.row .row.collapse:after{content:" ";display:table}.row .row.collapse:after{clear:both}.column,.columns{position:relative;padding-left:0.9375rem;padding-right:0.9375rem;width:100%;float:left}@media only screen{.small-push-1{position:relative;left:8.33333%;right:auto}.small-pull-1{position:relative;right:8.33333%;left:auto}.small-push-2{position:relative;left:16.66667%;right:auto}.small-pull-2{position:relative;right:16.66667%;left:auto}.small-push-3{position:relative;left:25%;right:auto}.small-pull-3{position:relative;right:25%;left:auto}.small-push-4{position:relative;left:33.33333%;right:auto}.small-pull-4{position:relative;right:33.33333%;left:auto}.small-push-5{position:relative;left:41.66667%;right:auto}.small-pull-5{position:relative;right:41.66667%;left:auto}.small-push-6{position:relative;left:50%;right:auto}.small-pull-6{position:relative;right:50%;left:auto}.small-push-7{position:relative;left:58.33333%;right:auto}.small-pull-7{position:relative;right:58.33333%;left:auto}.small-push-8{position:relative;left:66.66667%;right:auto}.small-pull-8{position:relative;right:66.66667%;left:auto}.small-push-9{position:relative;left:75%;right:auto}.small-pull-9{position:relative;right:75%;left:auto}.small-push-10{position:relative;left:83.33333%;right:auto}.small-pull-10{position:relative;right:83.33333%;left:auto}.small-push-11{position:relative;left:91.66667%;right:auto}.small-pull-11{position:relative;right:91.66667%;left:auto}.column,.columns{position:relative;padding-left:0.9375rem;padding-right:0.9375rem;float:left}.small-1{position:relative;width:8.33333%}.small-2{position:relative;width:16.66667%}.small-3{position:relative;width:25%}.small-4{position:relative;width:33.33333%}.small-5{position:relative;width:41.66667%}.small-6{position:relative;width:50%}.small-7{position:relative;width:58.33333%}.small-8{position:relative;width:66.66667%}.small-9{position:relative;width:75%}.small-10{position:relative;width:83.33333%}.small-11{position:relative;width:91.66667%}.small-12{position:relative;width:100%}.small-offset-0{position:relative;margin-left:0%}.small-offset-1{position:relative;margin-left:8.33333%}.small-offset-2{position:relative;margin-left:16.66667%}.small-offset-3{position:relative;margin-left:25%}.small-offset-4{position:relative;margin-left:33.33333%}.small-offset-5{position:relative;margin-left:41.66667%}.small-offset-6{position:relative;margin-left:50%}.small-offset-7{position:relative;margin-left:58.33333%}.small-offset-8{position:relative;margin-left:66.66667%}.small-offset-9{position:relative;margin-left:75%}.small-offset-10{position:relative;margin-left:83.33333%}[class*="column"]+[class*="column"]:last-child{float:right}[class*="column"]+[class*="column"].end{float:left}.column.small-centered,.columns.small-centered{position:relative;margin-left:auto;margin-right:auto;float:none !important}.column.small-uncentered,.columns.small-uncentered{margin-left:0;margin-right:0;float:left !important}.column.small-uncentered.opposite,.columns.small-uncentered.opposite{float:right !important}}@media only screen and (min-width: 40.063em){.medium-push-1{position:relative;left:8.33333%;right:auto}.medium-pull-1{position:relative;right:8.33333%;left:auto}.medium-push-2{position:relative;left:16.66667%;right:auto}.medium-pull-2{position:relative;right:16.66667%;left:auto}.medium-push-3{position:relative;left:25%;right:auto}.medium-pull-3{position:relative;right:25%;left:auto}.medium-push-4{position:relative;left:33.33333%;right:auto}.medium-pull-4{position:relative;right:33.33333%;left:auto}.medium-push-5{position:relative;left:41.66667%;right:auto}.medium-pull-5{position:relative;right:41.66667%;left:auto}.medium-push-6{position:relative;left:50%;right:auto}.medium-pull-6{position:relative;right:50%;left:auto}.medium-push-7{position:relative;left:58.33333%;right:auto}.medium-pull-7{position:relative;right:58.33333%;left:auto}.medium-push-8{position:relative;left:66.66667%;right:auto}.medium-pull-8{position:relative;right:66.66667%;left:auto}.medium-push-9{position:relative;left:75%;right:auto}.medium-pull-9{position:relative;right:75%;left:auto}.medium-push-10{position:relative;left:83.33333%;right:auto}.medium-pull-10{position:relative;right:83.33333%;left:auto}.medium-push-11{position:relative;left:91.66667%;right:auto}.medium-pull-11{position:relative;right:91.66667%;left:auto}.column,.columns{position:relative;padding-left:0.9375rem;padding-right:0.9375rem;float:left}.medium-1{position:relative;width:8.33333%}.medium-2{position:relative;width:16.66667%}.medium-3{position:relative;width:25%}.medium-4{position:relative;width:33.33333%}.medium-5{position:relative;width:41.66667%}.medium-6{position:relative;width:50%}.medium-7{position:relative;width:58.33333%}.medium-8{position:relative;width:66.66667%}.medium-9{position:relative;width:75%}.medium-10{position:relative;width:83.33333%}.medium-11{position:relative;width:91.66667%}.medium-12{position:relative;width:100%}.medium-offset-0{position:relative;margin-left:0%}.medium-offset-1{position:relative;margin-left:8.33333%}.medium-offset-2{position:relative;margin-left:16.66667%}.medium-offset-3{position:relative;margin-left:25%}.medium-offset-4{position:relative;margin-left:33.33333%}.medium-offset-5{position:relative;margin-left:41.66667%}.medium-offset-6{position:relative;margin-left:50%}.medium-offset-7{position:relative;margin-left:58.33333%}.medium-offset-8{position:relative;margin-left:66.66667%}.medium-offset-9{position:relative;margin-left:75%}.medium-offset-10{position:relative;margin-left:83.33333%}[class*="column"]+[class*="column"]:last-child{float:right}[class*="column"]+[class*="column"].end{float:left}.column.medium-centered,.columns.medium-centered{position:relative;margin-left:auto;margin-right:auto;float:none !important}.column.medium-uncentered,.columns.medium-uncentered{margin-left:0;margin-right:0;float:left !important}.column.medium-uncentered.opposite,.columns.medium-uncentered.opposite{float:right !important}.push-1{position:relative;left:8.33333%;right:auto}.pull-1{position:relative;right:8.33333%;left:auto}.push-2{position:relative;left:16.66667%;right:auto}.pull-2{position:relative;right:16.66667%;left:auto}.push-3{position:relative;left:25%;right:auto}.pull-3{position:relative;right:25%;left:auto}.push-4{position:relative;left:33.33333%;right:auto}.pull-4{position:relative;right:33.33333%;left:auto}.push-5{position:relative;left:41.66667%;right:auto}.pull-5{position:relative;right:41.66667%;left:auto}.push-6{position:relative;left:50%;right:auto}.pull-6{position:relative;right:50%;left:auto}.push-7{position:relative;left:58.33333%;right:auto}.pull-7{position:relative;right:58.33333%;left:auto}.push-8{position:relative;left:66.66667%;right:auto}.pull-8{position:relative;right:66.66667%;left:auto}.push-9{position:relative;left:75%;right:auto}.pull-9{position:relative;right:75%;left:auto}.push-10{position:relative;left:83.33333%;right:auto}.pull-10{position:relative;right:83.33333%;left:auto}.push-11{position:relative;left:91.66667%;right:auto}.pull-11{position:relative;right:91.66667%;left:auto}}@media only screen and (min-width: 64.063em){.large-push-1{position:relative;left:8.33333%;right:auto}.large-pull-1{position:relative;right:8.33333%;left:auto}.large-push-2{position:relative;left:16.66667%;right:auto}.large-pull-2{position:relative;right:16.66667%;left:auto}.large-push-3{position:relative;left:25%;right:auto}.large-pull-3{position:relative;right:25%;left:auto}.large-push-4{position:relative;left:33.33333%;right:auto}.large-pull-4{position:relative;right:33.33333%;left:auto}.large-push-5{position:relative;left:41.66667%;right:auto}.large-pull-5{position:relative;right:41.66667%;left:auto}.large-push-6{position:relative;left:50%;right:auto}.large-pull-6{position:relative;right:50%;left:auto}.large-push-7{position:relative;left:58.33333%;right:auto}.large-pull-7{position:relative;right:58.33333%;left:auto}.large-push-8{position:relative;left:66.66667%;right:auto}.large-pull-8{position:relative;right:66.66667%;left:auto}.large-push-9{position:relative;left:75%;right:auto}.large-pull-9{position:relative;right:75%;left:auto}.large-push-10{position:relative;left:83.33333%;right:auto}.large-pull-10{position:relative;right:83.33333%;left:auto}.large-push-11{position:relative;left:91.66667%;right:auto}.large-pull-11{position:relative;right:91.66667%;left:auto}.column,.columns{position:relative;padding-left:0.9375rem;padding-right:0.9375rem;float:left}.large-1{position:relative;width:8.33333%}.large-2{position:relative;width:16.66667%}.large-3{position:relative;width:25%}.large-4{position:relative;width:33.33333%}.large-5{position:relative;width:41.66667%}.large-6{position:relative;width:50%}.large-7{position:relative;width:58.33333%}.large-8{position:relative;width:66.66667%}.large-9{position:relative;width:75%}.large-10{position:relative;width:83.33333%}.large-11{position:relative;width:91.66667%}.large-12{position:relative;width:100%}.large-offset-0{position:relative;margin-left:0%}.large-offset-1{position:relative;margin-left:8.33333%}.large-offset-2{position:relative;margin-left:16.66667%}.large-offset-3{position:relative;margin-left:25%}.large-offset-4{position:relative;margin-left:33.33333%}.large-offset-5{position:relative;margin-left:41.66667%}.large-offset-6{position:relative;margin-left:50%}.large-offset-7{position:relative;margin-left:58.33333%}.large-offset-8{position:relative;margin-left:66.66667%}.large-offset-9{position:relative;margin-left:75%}.large-offset-10{position:relative;margin-left:83.33333%}[class*="column"]+[class*="column"]:last-child{float:right}[class*="column"]+[class*="column"].end{float:left}.column.large-centered,.columns.large-centered{position:relative;margin-left:auto;margin-right:auto;float:none !important}.column.large-uncentered,.columns.large-uncentered{margin-left:0;margin-right:0;float:left !important}.column.large-uncentered.opposite,.columns.large-uncentered.opposite{float:right !important}}@media only screen and (min-width: 90.063em){.xlarge-push-1{position:relative;left:8.33333%;right:auto}.xlarge-pull-1{position:relative;right:8.33333%;left:auto}.xlarge-push-2{position:relative;left:16.66667%;right:auto}.xlarge-pull-2{position:relative;right:16.66667%;left:auto}.xlarge-push-3{position:relative;left:25%;right:auto}.xlarge-pull-3{position:relative;right:25%;left:auto}.xlarge-push-4{position:relative;left:33.33333%;right:auto}.xlarge-pull-4{position:relative;right:33.33333%;left:auto}.xlarge-push-5{position:relative;left:41.66667%;right:auto}.xlarge-pull-5{position:relative;right:41.66667%;left:auto}.xlarge-push-6{position:relative;left:50%;right:auto}.xlarge-pull-6{position:relative;right:50%;left:auto}.xlarge-push-7{position:relative;left:58.33333%;right:auto}.xlarge-pull-7{position:relative;right:58.33333%;left:auto}.xlarge-push-8{position:relative;left:66.66667%;right:auto}.xlarge-pull-8{position:relative;right:66.66667%;left:auto}.xlarge-push-9{position:relative;left:75%;right:auto}.xlarge-pull-9{position:relative;right:75%;left:auto}.xlarge-push-10{position:relative;left:83.33333%;right:auto}.xlarge-pull-10{position:relative;right:83.33333%;left:auto}.xlarge-push-11{position:relative;left:91.66667%;right:auto}.xlarge-pull-11{position:relative;right:91.66667%;left:auto}.column,.columns{position:relative;padding-left:0.9375rem;padding-right:0.9375rem;float:left}.xlarge-1{position:relative;width:8.33333%}.xlarge-2{position:relative;width:16.66667%}.xlarge-3{position:relative;width:25%}.xlarge-4{position:relative;width:33.33333%}.xlarge-5{position:relative;width:41.66667%}.xlarge-6{position:relative;width:50%}.xlarge-7{position:relative;width:58.33333%}.xlarge-8{position:relative;width:66.66667%}.xlarge-9{position:relative;width:75%}.xlarge-10{position:relative;width:83.33333%}.xlarge-11{position:relative;width:91.66667%}.xlarge-12{position:relative;width:100%}.xlarge-offset-0{position:relative;margin-left:0%}.xlarge-offset-1{position:relative;margin-left:8.33333%}.xlarge-offset-2{position:relative;margin-left:16.66667%}.xlarge-offset-3{position:relative;margin-left:25%}.xlarge-offset-4{position:relative;margin-left:33.33333%}.xlarge-offset-5{position:relative;margin-left:41.66667%}.xlarge-offset-6{position:relative;margin-left:50%}.xlarge-offset-7{position:relative;margin-left:58.33333%}.xlarge-offset-8{position:relative;margin-left:66.66667%}.xlarge-offset-9{position:relative;margin-left:75%}.xlarge-offset-10{position:relative;margin-left:83.33333%}[class*="column"]+[class*="column"]:last-child{float:right}[class*="column"]+[class*="column"].end{float:left}.column.xlarge-centered,.columns.xlarge-centered{position:relative;margin-left:auto;margin-right:auto;float:none !important}.column.xlarge-uncentered,.columns.xlarge-uncentered{margin-left:0;margin-right:0;float:left !important}.column.xlarge-uncentered.opposite,.columns.xlarge-uncentered.opposite{float:right !important}}@media only screen and (min-width: 120.063em){.xxlarge-push-1{position:relative;left:8.33333%;right:auto}.xxlarge-pull-1{position:relative;right:8.33333%;left:auto}.xxlarge-push-2{position:relative;left:16.66667%;right:auto}.xxlarge-pull-2{position:relative;right:16.66667%;left:auto}.xxlarge-push-3{position:relative;left:25%;right:auto}.xxlarge-pull-3{position:relative;right:25%;left:auto}.xxlarge-push-4{position:relative;left:33.33333%;right:auto}.xxlarge-pull-4{position:relative;right:33.33333%;left:auto}.xxlarge-push-5{position:relative;left:41.66667%;right:auto}.xxlarge-pull-5{position:relative;right:41.66667%;left:auto}.xxlarge-push-6{position:relative;left:50%;right:auto}.xxlarge-pull-6{position:relative;right:50%;left:auto}.xxlarge-push-7{position:relative;left:58.33333%;right:auto}.xxlarge-pull-7{position:relative;right:58.33333%;left:auto}.xxlarge-push-8{position:relative;left:66.66667%;right:auto}.xxlarge-pull-8{position:relative;right:66.66667%;left:auto}.xxlarge-push-9{position:relative;left:75%;right:auto}.xxlarge-pull-9{position:relative;right:75%;left:auto}.xxlarge-push-10{position:relative;left:83.33333%;right:auto}.xxlarge-pull-10{position:relative;right:83.33333%;left:auto}.xxlarge-push-11{position:relative;left:91.66667%;right:auto}.xxlarge-pull-11{position:relative;right:91.66667%;left:auto}.column,.columns{position:relative;padding-left:0.9375rem;padding-right:0.9375rem;float:left}.xxlarge-1{position:relative;width:8.33333%}.xxlarge-2{position:relative;width:16.66667%}.xxlarge-3{position:relative;width:25%}.xxlarge-4{position:relative;width:33.33333%}.xxlarge-5{position:relative;width:41.66667%}.xxlarge-6{position:relative;width:50%}.xxlarge-7{position:relative;width:58.33333%}.xxlarge-8{position:relative;width:66.66667%}.xxlarge-9{position:relative;width:75%}.xxlarge-10{position:relative;width:83.33333%}.xxlarge-11{position:relative;width:91.66667%}.xxlarge-12{position:relative;width:100%}.xxlarge-offset-0{position:relative;margin-left:0%}.xxlarge-offset-1{position:relative;margin-left:8.33333%}.xxlarge-offset-2{position:relative;margin-left:16.66667%}.xxlarge-offset-3{position:relative;margin-left:25%}.xxlarge-offset-4{position:relative;margin-left:33.33333%}.xxlarge-offset-5{position:relative;margin-left:41.66667%}.xxlarge-offset-6{position:relative;margin-left:50%}.xxlarge-offset-7{position:relative;margin-left:58.33333%}.xxlarge-offset-8{position:relative;margin-left:66.66667%}.xxlarge-offset-9{position:relative;margin-left:75%}.xxlarge-offset-10{position:relative;margin-left:83.33333%}[class*="column"]+[class*="column"]:last-child{float:right}[class*="column"]+[class*="column"].end{float:left}.column.xxlarge-centered,.columns.xxlarge-centered{position:relative;margin-left:auto;margin-right:auto;float:none !important}.column.xxlarge-uncentered,.columns.xxlarge-uncentered{margin-left:0;margin-right:0;float:left !important}.column.xxlarge-uncentered.opposite,.columns.xxlarge-uncentered.opposite{float:right !important}}meta.foundation-mq-topbar{font-family:"/only screen and (min-width:40.063em)/";width:40.063em}.contain-to-grid{width:100%;background:#333}.contain-to-grid .top-bar{margin-bottom:0}.fixed{width:100%;left:0;position:fixed;top:0;z-index:99}.fixed.expanded:not(.top-bar){overflow-y:auto;height:auto;width:100%;max-height:100%}.fixed.expanded:not(.top-bar) .title-area{position:fixed;width:100%;z-index:99}.fixed.expanded:not(.top-bar) .top-bar-section{z-index:98;margin-top:45px}.top-bar{overflow:hidden;height:45px;line-height:45px;position:relative;background:#333;margin-bottom:0}.top-bar ul{margin-bottom:0;list-style:none}.top-bar .row{max-width:none}.top-bar form,.top-bar input{margin-bottom:0}.top-bar input{height:auto;padding-top:.35rem;padding-bottom:.35rem;font-size:0.75rem}.top-bar .button{padding-top:.45rem;padding-bottom:.35rem;margin-bottom:0;font-size:0.75rem}.top-bar .title-area{position:relative;margin:0}.top-bar .name{height:45px;margin:0;font-size:16px}.top-bar .name h1{line-height:45px;font-size:1.0625rem;margin:0}.top-bar .name h1 a{font-weight:normal;color:#fff;width:50%;display:block;padding:0 15px}.top-bar .toggle-topbar{position:absolute;right:0;top:0}.top-bar .toggle-topbar a{color:#fff;text-transform:uppercase;font-size:0.8125rem;font-weight:bold;position:relative;display:block;padding:0 15px;height:45px;line-height:45px}.top-bar .toggle-topbar.menu-icon{right:15px;top:50%;margin-top:-16px;padding-left:40px}.top-bar .toggle-topbar.menu-icon a{text-indent:-48px;width:34px;height:34px;line-height:33px;padding:0;color:#fff}.top-bar .toggle-topbar.menu-icon a span{position:absolute;right:0;display:block;width:16px;height:0;-webkit-box-shadow:0 10px 0 1px #fff,0 16px 0 1px #fff,0 22px 0 1px #fff;box-shadow:0 10px 0 1px #fff,0 16px 0 1px #fff,0 22px 0 1px #fff}.top-bar.expanded{height:auto;background:transparent}.top-bar.expanded .title-area{background:#333}.top-bar.expanded .toggle-topbar a{color:#888}.top-bar.expanded .toggle-topbar a span{-webkit-box-shadow:0 10px 0 1px #888,0 16px 0 1px #888,0 22px 0 1px #888;box-shadow:0 10px 0 1px #888,0 16px 0 1px #888,0 22px 0 1px #888}.top-bar-section{left:0;position:relative;width:auto;-webkit-transition:left 300ms ease-out;-moz-transition:left 300ms ease-out;transition:left 300ms ease-out}.top-bar-section ul{width:100%;height:auto;display:block;background:#333;font-size:16px;margin:0}.top-bar-section .divider,.top-bar-section [role="separator"]{border-top:solid 1px #1a1a1a;clear:both;height:1px;width:100%}.top-bar-section ul li>a{display:block;width:100%;color:#fff;padding:12px 0 12px 0;padding-left:15px;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;font-size:0.8125rem;font-weight:normal;background:#333}.top-bar-section ul li>a.button{background:#008cba;font-size:0.8125rem;padding-right:15px;padding-left:15px}.top-bar-section ul li>a.button:hover{background:#006687}.top-bar-section ul li>a.button.secondary{background:#e7e7e7}.top-bar-section ul li>a.button.secondary:hover{background:#cecece}.top-bar-section ul li>a.button.success{background:#43ac6a}.top-bar-section ul li>a.button.success:hover{background:#358753}.top-bar-section ul li>a.button.alert{background:#f04124}.top-bar-section ul li>a.button.alert:hover{background:#d32a0e}.top-bar-section ul li:hover>a{background:#272727;color:#fff}.top-bar-section ul li.active>a{background:#008cba;color:#fff}.top-bar-section ul li.active>a:hover{background:#0079a1}.top-bar-section .has-form{padding:15px}.top-bar-section .has-dropdown{position:relative}.top-bar-section .has-dropdown>a:after{content:"";display:block;width:0;height:0;border:inset 5px;border-color:transparent transparent transparent rgba(255,255,255,0.4);border-left-style:solid;margin-right:15px;margin-top:-4.5px;position:absolute;top:50%;right:0}.top-bar-section .has-dropdown.moved{position:static}.top-bar-section .has-dropdown.moved>.dropdown{display:block}.top-bar-section .dropdown{position:absolute;left:100%;top:0;display:none;z-index:99}.top-bar-section .dropdown li{width:100%;height:auto}.top-bar-section .dropdown li a{font-weight:normal;padding:8px 15px}.top-bar-section .dropdown li a.parent-link{font-weight:normal}.top-bar-section .dropdown li.title h5{margin-bottom:0}.top-bar-section .dropdown li.title h5 a{color:#fff;line-height:22.5px;display:block}.top-bar-section .dropdown label{padding:8px 15px 2px;margin-bottom:0;text-transform:uppercase;color:#777;font-weight:bold;font-size:0.625rem}.js-generated{display:block}@media only screen and (min-width: 40.063em){.top-bar{background:#333;*zoom:1;overflow:visible}.top-bar:before,.top-bar:after{content:" ";display:table}.top-bar:after{clear:both}.top-bar .toggle-topbar{display:none}.top-bar .title-area{float:left}.top-bar .name h1 a{width:auto}.top-bar input,.top-bar .button{font-size:0.875rem;position:relative;top:7px}.top-bar.expanded{background:#333}.contain-to-grid .top-bar{max-width:62.5rem;margin:0 auto;margin-bottom:0}.top-bar-section{-webkit-transition:none 0 0;-moz-transition:none 0 0;transition:none 0 0;left:0 !important}.top-bar-section ul{width:auto;height:auto !important;display:inline}.top-bar-section ul li{float:left}.top-bar-section ul li .js-generated{display:none}.top-bar-section li.hover>a:not(.button){background:#272727;color:#fff}.top-bar-section li a:not(.button){padding:0 15px;line-height:45px;background:#333}.top-bar-section li a:not(.button):hover{background:#272727}.top-bar-section .has-dropdown>a{padding-right:35px !important}.top-bar-section .has-dropdown>a:after{content:"";display:block;width:0;height:0;border:inset 5px;border-color:rgba(255,255,255,0.4) transparent transparent transparent;border-top-style:solid;margin-top:-2.5px;top:22.5px}.top-bar-section .has-dropdown.moved{position:relative}.top-bar-section .has-dropdown.moved>.dropdown{display:none}.top-bar-section .has-dropdown.hover>.dropdown,.top-bar-section .has-dropdown.not-click:hover>.dropdown{display:block}.top-bar-section .has-dropdown .dropdown li.has-dropdown>a:after{border:none;content:"\00bb";top:1rem;margin-top:-2px;right:5px}.top-bar-section .dropdown{left:0;top:auto;background:transparent;min-width:100%}.top-bar-section .dropdown li a{color:#fff;line-height:1;white-space:nowrap;padding:12px 15px;background:#333}.top-bar-section .dropdown li label{white-space:nowrap;background:#333}.top-bar-section .dropdown li .dropdown{left:100%;top:0}.top-bar-section>ul>.divider,.top-bar-section>ul>[role="separator"]{border-bottom:none;border-top:none;border-right:solid 1px #4d4d4d;clear:none;height:45px;width:0}.top-bar-section .has-form{background:#333;padding:0 15px;height:45px}.top-bar-section ul.right li .dropdown{left:auto;right:0}.top-bar-section ul.right li .dropdown li .dropdown{right:100%}.no-js .top-bar-section ul li:hover>a{background:#272727;color:#fff}.no-js .top-bar-section ul li:active>a{background:#008cba;color:#fff}.no-js .top-bar-section .has-dropdown:hover>.dropdown{display:block}}.breadcrumbs{display:block;padding:0.5625rem 0.875rem 0.5625rem;overflow:hidden;margin-left:0;list-style:none;border-style:solid;border-width:1px;background-color:#f4f4f4;border-color:#dadada;-webkit-border-radius:3px;border-radius:3px}.breadcrumbs>*{margin:0;float:left;font-size:0.6875rem;text-transform:uppercase}.breadcrumbs>*:hover a,.breadcrumbs>*:focus a{text-decoration:underline}.breadcrumbs>* a,.breadcrumbs>* span{text-transform:uppercase;color:#008cba}.breadcrumbs>*.current{cursor:default;color:#333}.breadcrumbs>*.current a{cursor:default;color:#333}.breadcrumbs>*.current:hover,.breadcrumbs>*.current:hover a,.breadcrumbs>*.current:focus,.breadcrumbs>*.current:focus a{text-decoration:none}.breadcrumbs>*.unavailable{color:#999}.breadcrumbs>*.unavailable a{color:#999}.breadcrumbs>*.unavailable:hover,.breadcrumbs>*.unavailable:hover a,.breadcrumbs>*.unavailable:focus,.breadcrumbs>*.unavailable a:focus{text-decoration:none;color:#999;cursor:default}.breadcrumbs>*:before{content:"/";color:#aaa;margin:0 0.75rem;position:relative;top:1px}.breadcrumbs>*:first-child:before{content:" ";margin:0}.alert-box{border-style:solid;border-width:1px;display:block;font-weight:normal;margin-bottom:1.25rem;position:relative;padding:0.875rem 1.5rem 0.875rem 0.875rem;font-size:0.8125rem;background-color:#008cba;border-color:#0079a1;color:#fff}.alert-box .close{font-size:1.375rem;padding:9px 6px 4px;line-height:0;position:absolute;top:50%;margin-top:-0.6875rem;right:0.25rem;color:#333;opacity:0.3}.alert-box .close:hover,.alert-box .close:focus{opacity:0.5}.alert-box.radius{-webkit-border-radius:3px;border-radius:3px}.alert-box.round{-webkit-border-radius:1000px;border-radius:1000px}.alert-box.success{background-color:#43ac6a;border-color:#3c9a5f;color:#fff}.alert-box.alert{background-color:#f04124;border-color:#ea2f10;color:#fff}.alert-box.secondary{background-color:#e7e7e7;border-color:#dadada;color:#4e4e4e}.alert-box.warning{background-color:#f08a24;border-color:#ea7d10;color:#fff}.alert-box.info{background-color:#a0d3e8;border-color:#8bc9e3;color:#4e4e4e}.inline-list{margin:0 auto 1.0625rem auto;margin-left:-1.375rem;margin-right:0;padding:0;list-style:none;overflow:hidden}.inline-list>li{list-style:none;float:left;margin-left:1.375rem;display:block}.inline-list>li>*{display:block}button,.button{cursor:pointer;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;font-weight:normal;line-height:normal;margin:0 0 1.25rem;position:relative;text-decoration:none;text-align:center;display:inline-block;padding-top:1rem;padding-right:2rem;padding-bottom:1.0625rem;padding-left:2rem;font-size:1rem;background-color:#008cba;border-color:#0079a1;color:#fff;-webkit-transition:background-color 300ms ease-out;-moz-transition:background-color 300ms ease-out;transition:background-color 300ms ease-out;padding-top:1.0625rem;padding-bottom:1rem;-webkit-appearance:none;border:none;font-weight:normal !important}button:hover,button:focus,.button:hover,.button:focus{background-color:#0079a1}button:hover,button:focus,.button:hover,.button:focus{color:#fff}button.secondary,.button.secondary{background-color:#e7e7e7;border-color:#dadada;color:#333}button.secondary:hover,button.secondary:focus,.button.secondary:hover,.button.secondary:focus{background-color:#dadada}button.secondary:hover,button.secondary:focus,.button.secondary:hover,.button.secondary:focus{color:#333}button.success,.button.success{background-color:#43ac6a;border-color:#3c9a5f;color:#fff}button.success:hover,button.success:focus,.button.success:hover,.button.success:focus{background-color:#3c9a5f}button.success:hover,button.success:focus,.button.success:hover,.button.success:focus{color:#fff}button.alert,.button.alert{background-color:#f04124;border-color:#ea2f10;color:#fff}button.alert:hover,button.alert:focus,.button.alert:hover,.button.alert:focus{background-color:#ea2f10}button.alert:hover,button.alert:focus,.button.alert:hover,.button.alert:focus{color:#fff}button.large,.button.large{padding-top:1.125rem;padding-right:2.25rem;padding-bottom:1.1875rem;padding-left:2.25rem;font-size:1.25rem}button.small,.button.small{padding-top:0.875rem;padding-right:1.75rem;padding-bottom:0.9375rem;padding-left:1.75rem;font-size:0.8125rem}button.tiny,.button.tiny{padding-top:0.625rem;padding-right:1.25rem;padding-bottom:0.6875rem;padding-left:1.25rem;font-size:0.6875rem}button.expand,.button.expand{padding-right:0;padding-left:0;width:100%}button.left-align,.button.left-align{text-align:left;text-indent:0.75rem}button.right-align,.button.right-align{text-align:right;padding-right:0.75rem}button.radius,.button.radius{-webkit-border-radius:3px;border-radius:3px}button.round,.button.round{-webkit-border-radius:1000px;border-radius:1000px}button.disabled,button[disabled],.button.disabled,.button[disabled]{background-color:#008cba;border-color:#0079a1;color:#fff;cursor:default;opacity:0.7;-webkit-box-shadow:none;box-shadow:none}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{background-color:#0079a1}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{color:#fff}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{background-color:#008cba}button.disabled.secondary,button[disabled].secondary,.button.disabled.secondary,.button[disabled].secondary{background-color:#e7e7e7;border-color:#dadada;color:#333;cursor:default;opacity:0.7;-webkit-box-shadow:none;box-shadow:none}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{background-color:#dadada}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{color:#333}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{background-color:#e7e7e7}button.disabled.success,button[disabled].success,.button.disabled.success,.button[disabled].success{background-color:#43ac6a;border-color:#3c9a5f;color:#fff;cursor:default;opacity:0.7;-webkit-box-shadow:none;box-shadow:none}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{background-color:#3c9a5f}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{color:#fff}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{background-color:#43ac6a}button.disabled.alert,button[disabled].alert,.button.disabled.alert,.button[disabled].alert{background-color:#f04124;border-color:#ea2f10;color:#fff;cursor:default;opacity:0.7;-webkit-box-shadow:none;box-shadow:none}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{background-color:#ea2f10}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{color:#fff}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{background-color:#f04124}@media only screen and (min-width: 40.063em){button,.button{display:inline-block}}.button-group{list-style:none;margin:0;*zoom:1}.button-group:before,.button-group:after{content:" ";display:table}.button-group:after{clear:both}.button-group>*{margin:0;float:left}.button-group>*>button,.button-group>* .button{border-right:1px solid;border-color:rgba(255,255,255,0.5)}.button-group>*:first-child{margin-left:0}.button-group.radius>*>button,.button-group.radius>* .button{border-right:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.radius>*:first-child,.button-group.radius>*:first-child>a,.button-group.radius>*:first-child>button,.button-group.radius>*:first-child>.button{-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.button-group.radius>*:last-child,.button-group.radius>*:last-child>a,.button-group.radius>*:last-child>button,.button-group.radius>*:last-child>.button{-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.button-group.round>*>button,.button-group.round>* .button{border-right:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.round>*:first-child,.button-group.round>*:first-child>a,.button-group.round>*:first-child>button,.button-group.round>*:first-child>.button{-moz-border-radius-bottomleft:1000px;-moz-border-radius-topleft:1000px;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}.button-group.round>*:last-child,.button-group.round>*:last-child>a,.button-group.round>*:last-child>button,.button-group.round>*:last-child>.button{-moz-border-radius-topright:1000px;-moz-border-radius-bottomright:1000px;-webkit-border-top-right-radius:1000px;-webkit-border-bottom-right-radius:1000px;border-top-right-radius:1000px;border-bottom-right-radius:1000px}.button-group.even-2 li{width:50%}.button-group.even-2 li>button,.button-group.even-2 li .button{border-right:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-2 li button,.button-group.even-2 li .button{width:100%}.button-group.even-3 li{width:33.33333%}.button-group.even-3 li>button,.button-group.even-3 li .button{border-right:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-3 li button,.button-group.even-3 li .button{width:100%}.button-group.even-4 li{width:25%}.button-group.even-4 li>button,.button-group.even-4 li .button{border-right:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-4 li button,.button-group.even-4 li .button{width:100%}.button-group.even-5 li{width:20%}.button-group.even-5 li>button,.button-group.even-5 li .button{border-right:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-5 li button,.button-group.even-5 li .button{width:100%}.button-group.even-6 li{width:16.66667%}.button-group.even-6 li>button,.button-group.even-6 li .button{border-right:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-6 li button,.button-group.even-6 li .button{width:100%}.button-group.even-7 li{width:14.28571%}.button-group.even-7 li>button,.button-group.even-7 li .button{border-right:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-7 li button,.button-group.even-7 li .button{width:100%}.button-group.even-8 li{width:12.5%}.button-group.even-8 li>button,.button-group.even-8 li .button{border-right:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-8 li button,.button-group.even-8 li .button{width:100%}.button-bar{*zoom:1}.button-bar:before,.button-bar:after{content:" ";display:table}.button-bar:after{clear:both}.button-bar .button-group{float:left;margin-right:0.625rem}.button-bar .button-group div{overflow:hidden}.panel{border-style:solid;border-width:1px;border-color:#d9d9d9;margin-bottom:1.25rem;padding:1.25rem;background:#f2f2f2}.panel>:first-child{margin-top:0}.panel>:last-child{margin-bottom:0}.panel h1,.panel h2,.panel h3,.panel h4,.panel h5,.panel h6,.panel p{color:#333}.panel h1,.panel h2,.panel h3,.panel h4,.panel h5,.panel h6{line-height:1;margin-bottom:0.625rem}.panel h1.subheader,.panel h2.subheader,.panel h3.subheader,.panel h4.subheader,.panel h5.subheader,.panel h6.subheader{line-height:1.4}.panel.callout{border-style:solid;border-width:1px;border-color:#baeeff;margin-bottom:1.25rem;padding:1.25rem;background:#edfbff}.panel.callout>:first-child{margin-top:0}.panel.callout>:last-child{margin-bottom:0}.panel.callout h1,.panel.callout h2,.panel.callout h3,.panel.callout h4,.panel.callout h5,.panel.callout h6,.panel.callout p{color:#333}.panel.callout h1,.panel.callout h2,.panel.callout h3,.panel.callout h4,.panel.callout h5,.panel.callout h6{line-height:1;margin-bottom:0.625rem}.panel.callout h1.subheader,.panel.callout h2.subheader,.panel.callout h3.subheader,.panel.callout h4.subheader,.panel.callout h5.subheader,.panel.callout h6.subheader{line-height:1.4}.panel.callout a{color:#008cba}.panel.radius{-webkit-border-radius:3px;border-radius:3px}.dropdown.button{position:relative;padding-right:3.5625rem}.dropdown.button:before{position:absolute;content:"";width:0;height:0;display:block;border-style:solid;border-color:#fff transparent transparent transparent;top:50%}.dropdown.button:before{border-width:0.375rem;right:1.40625rem;margin-top:-0.15625rem}.dropdown.button:before{border-color:#fff transparent transparent transparent}.dropdown.button.tiny{padding-right:2.625rem}.dropdown.button.tiny:before{border-width:0.375rem;right:1.125rem;margin-top:-0.125rem}.dropdown.button.tiny:before{border-color:#fff transparent transparent transparent}.dropdown.button.small{padding-right:3.0625rem}.dropdown.button.small:before{border-width:0.4375rem;right:1.3125rem;margin-top:-0.15625rem}.dropdown.button.small:before{border-color:#fff transparent transparent transparent}.dropdown.button.large{padding-right:3.625rem}.dropdown.button.large:before{border-width:0.3125rem;right:1.71875rem;margin-top:-0.15625rem}.dropdown.button.large:before{border-color:#fff transparent transparent transparent}.dropdown.button.secondary:before{border-color:#333 transparent transparent transparent}div.switch{position:relative;padding:0;display:block;overflow:hidden;border-style:solid;border-width:1px;margin-bottom:1.25rem;height:2.25rem;background:#fff;border-color:#ccc}div.switch label{position:relative;left:0;z-index:2;float:left;width:50%;height:100%;margin:0;font-weight:bold;text-align:left;-webkit-transition:all 0.1s ease-out;-moz-transition:all 0.1s ease-out;transition:all 0.1s ease-out}div.switch input{position:absolute;z-index:3;opacity:0;width:100%;height:100%;-moz-appearance:none}div.switch input:hover,div.switch input:focus{cursor:pointer}div.switch span:last-child{position:absolute;top:-1px;left:-1px;z-index:1;display:block;padding:0;border-width:1px;border-style:solid;-webkit-transition:all 0.1s ease-out;-moz-transition:all 0.1s ease-out;transition:all 0.1s ease-out}div.switch input:not(:checked)+label{opacity:0}div.switch input:checked{display:none !important}div.switch input{left:0;display:block !important}div.switch input:first-of-type+label,div.switch input:first-of-type+span+label{left:-50%}div.switch input:first-of-type:checked+label,div.switch input:first-of-type:checked+span+label{left:0%}div.switch input:last-of-type+label,div.switch input:last-of-type+span+label{right:-50%;left:auto;text-align:right}div.switch input:last-of-type:checked+label,div.switch input:last-of-type:checked+span+label{right:0%;left:auto}div.switch span.custom{display:none !important}form.custom div.switch .hidden-field{margin-left:auto;position:absolute;visibility:visible}div.switch label{padding:0;line-height:2.3rem;font-size:0.875rem}div.switch input:first-of-type:checked ~ span:last-child{left:100%;margin-left:-2.1875rem}div.switch span:last-child{width:2.25rem;height:2.25rem}div.switch span:last-child{border-color:#b3b3b3;background:#fff;background:-moz-linear-gradient(top, #fff 0%, #f2f2f2 100%);background:-webkit-linear-gradient(top, #fff 0%, #f2f2f2 100%);background:linear-gradient(to bottom, #fff 0%, #f2f2f2 100%);-webkit-box-shadow:2px 0 10px 0 rgba(0,0,0,0.07),1000px 0 0 1000px #f3fbf6,-2px 0 10px 0 rgba(0,0,0,0.07),-1000px 0 0 1000px #f5f5f5;box-shadow:2px 0 10px 0 rgba(0,0,0,0.07),1000px 0 0 980px #f3fbf6,-2px 0 10px 0 rgba(0,0,0,0.07),-1000px 0 0 1000px #f5f5f5}div.switch:hover span:last-child,div.switch:focus span:last-child{background:#fff;background:-moz-linear-gradient(top, #fff 0%, #e6e6e6 100%);background:-webkit-linear-gradient(top, #fff 0%, #e6e6e6 100%);background:linear-gradient(to bottom, #fff 0%, #e6e6e6 100%)}div.switch:active{background:transparent}div.switch.large{height:2.75rem}div.switch.large label{padding:0;line-height:2.3rem;font-size:1.0625rem}div.switch.large input:first-of-type:checked ~ span:last-child{left:100%;margin-left:-2.6875rem}div.switch.large span:last-child{width:2.75rem;height:2.75rem}div.switch.small{height:1.75rem}div.switch.small label{padding:0;line-height:2.1rem;font-size:0.75rem}div.switch.small input:first-of-type:checked ~ span:last-child{left:100%;margin-left:-1.6875rem}div.switch.small span:last-child{width:1.75rem;height:1.75rem}div.switch.tiny{height:1.375rem}div.switch.tiny label{padding:0;line-height:1.9rem;font-size:0.6875rem}div.switch.tiny input:first-of-type:checked ~ span:last-child{left:100%;margin-left:-1.3125rem}div.switch.tiny span:last-child{width:1.375rem;height:1.375rem}div.switch.radius{-webkit-border-radius:4px;border-radius:4px}div.switch.radius span:last-child{-webkit-border-radius:3px;border-radius:3px}div.switch.round{-webkit-border-radius:1000px;border-radius:1000px}div.switch.round span:last-child{-webkit-border-radius:999px;border-radius:999px}div.switch.round label{padding:0 0.5625rem}@-webkit-keyframes webkitSiblingBugfix{from{position:relative}to{position:relative}}.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px rgba(0,0,0,0.2);box-shadow:0 0 0 1px rgba(0,0,0,0.2);-webkit-transition:all 200ms ease-out;-moz-transition:all 200ms ease-out;transition:all 200ms ease-out}.th:hover,.th:focus{-webkit-box-shadow:0 0 6px 1px rgba(0,140,186,0.5);box-shadow:0 0 6px 1px rgba(0,140,186,0.5)}.th.radius{-webkit-border-radius:3px;border-radius:3px}a.th{display:inline-block;max-width:100%}.pricing-table{border:solid 1px #ddd;margin-left:0;margin-bottom:1.25rem}.pricing-table *{list-style:none;line-height:1}.pricing-table .title{background-color:#333;padding:0.9375rem 1.25rem;text-align:center;color:#eee;font-weight:normal;font-size:1rem;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif}.pricing-table .price{background-color:#f6f6f6;padding:0.9375rem 1.25rem;text-align:center;color:#333;font-weight:normal;font-size:2rem;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif}.pricing-table .description{background-color:#fff;padding:0.9375rem;text-align:center;color:#777;font-size:0.75rem;font-weight:normal;line-height:1.4;border-bottom:dotted 1px #ddd}.pricing-table .bullet-item{background-color:#fff;padding:0.9375rem;text-align:center;color:#333;font-size:0.875rem;font-weight:normal;border-bottom:dotted 1px #ddd}.pricing-table .cta-button{background-color:#fff;text-align:center;padding:1.25rem 1.25rem 0}@-webkit-keyframes rotate{from{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(360deg)}}@-moz-keyframes rotate{from{-moz-transform:rotate(0deg)}to{-moz-transform:rotate(360deg)}}@-o-keyframes rotate{from{-o-transform:rotate(0deg)}to{-o-transform:rotate(360deg)}}@keyframes rotate{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}.slideshow-wrapper{position:relative}.slideshow-wrapper ul{list-style-type:none;margin:0}.slideshow-wrapper ul li,.slideshow-wrapper ul li .orbit-caption{display:none}.slideshow-wrapper ul li:first-child{display:block}.slideshow-wrapper .orbit-container{background-color:transparent}.slideshow-wrapper .orbit-container li{display:block}.slideshow-wrapper .orbit-container li .orbit-caption{display:block}.preloader{display:block;width:40px;height:40px;position:absolute;top:50%;left:50%;margin-top:-20px;margin-left:-20px;border:solid 3px;border-color:#555 #fff;-webkit-border-radius:1000px;border-radius:1000px;-webkit-animation-name:rotate;-webkit-animation-duration:1.5s;-webkit-animation-iteration-count:infinite;-webkit-animation-timing-function:linear;-moz-animation-name:rotate;-moz-animation-duration:1.5s;-moz-animation-iteration-count:infinite;-moz-animation-timing-function:linear;-o-animation-name:rotate;-o-animation-duration:1.5s;-o-animation-iteration-count:infinite;-o-animation-timing-function:linear;animation-name:rotate;animation-duration:1.5s;animation-iteration-count:infinite;animation-timing-function:linear}.orbit-container{overflow:hidden;width:100%;position:relative;background:none}.orbit-container .orbit-slides-container{list-style:none;margin:0;padding:0;position:relative}.orbit-container .orbit-slides-container img{display:block;max-width:100%}.orbit-container .orbit-slides-container>*{position:absolute;top:0;width:100%;margin-left:100%}.orbit-container .orbit-slides-container>*:first-child{margin-left:0%}.orbit-container .orbit-slides-container>* .orbit-caption{position:absolute;bottom:0;background-color:rgba(51,51,51,0.8);color:#fff;width:100%;padding:10px 14px;font-size:0.875rem}.orbit-container .orbit-slide-number{position:absolute;top:10px;left:10px;font-size:12px;color:#fff;background:rgba(0,0,0,0);z-index:10}.orbit-container .orbit-slide-number span{font-weight:700;padding:0.3125rem}.orbit-container .orbit-timer{position:absolute;top:12px;right:10px;height:6px;width:100px;z-index:10}.orbit-container .orbit-timer .orbit-progress{height:3px;background-color:rgba(255,255,255,0.3);display:block;width:0%;position:relative;right:20px;top:5px}.orbit-container .orbit-timer>span{display:none;position:absolute;top:0px;right:0;width:11px;height:14px;border:solid 4px #fff;border-top:none;border-bottom:none}.orbit-container .orbit-timer.paused>span{right:-4px;top:0px;width:11px;height:14px;border:inset 8px;border-right-style:solid;border-color:transparent transparent transparent #fff}.orbit-container .orbit-timer.paused>span.dark{border-color:transparent transparent transparent #333}.orbit-container:hover .orbit-timer>span{display:block}.orbit-container .orbit-prev,.orbit-container .orbit-next{position:absolute;top:45%;margin-top:-25px;width:36px;height:60px;line-height:50px;color:white;text-indent:-9999px !important;z-index:10}.orbit-container .orbit-prev:hover,.orbit-container .orbit-next:hover{background-color:rgba(0,0,0,0.3)}.orbit-container .orbit-prev>span,.orbit-container .orbit-next>span{position:absolute;top:50%;margin-top:-10px;display:block;width:0;height:0;border:inset 10px}.orbit-container .orbit-prev{left:0}.orbit-container .orbit-prev>span{border-right-style:solid;border-color:transparent;border-right-color:#fff}.orbit-container .orbit-prev:hover>span{border-right-color:#fff}.orbit-container .orbit-next{right:0}.orbit-container .orbit-next>span{border-color:transparent;border-left-style:solid;border-left-color:#fff;left:50%;margin-left:-4px}.orbit-container .orbit-next:hover>span{border-left-color:#fff}.orbit-bullets-container{text-align:center}.orbit-bullets{margin:0 auto 30px auto;overflow:hidden;position:relative;top:10px;float:none;text-align:center;display:inline-block}.orbit-bullets li{display:block;width:0.5625rem;height:0.5625rem;background:#ccc;float:left;margin-right:6px;-webkit-border-radius:1000px;border-radius:1000px}.orbit-bullets li.active{background:#999}.orbit-bullets li:last-child{margin-right:0}.touch .orbit-container .orbit-prev,.touch .orbit-container .orbit-next{display:none}.touch .orbit-bullets{display:none}@media only screen and (min-width: 40.063em){.touch .orbit-container .orbit-prev,.touch .orbit-container .orbit-next{display:inherit}.touch .orbit-bullets{display:block}}@media only screen and (max-width: 40em){.orbit-stack-on-small .orbit-slides-container{height:auto !important}.orbit-stack-on-small .orbit-slides-container>*{position:relative;margin-left:0% !important}.orbit-stack-on-small .orbit-timer,.orbit-stack-on-small .orbit-next,.orbit-stack-on-small .orbit-prev,.orbit-stack-on-small .orbit-bullets{display:none}}[data-magellan-expedition]{background:#fff;z-index:50;min-width:100%;padding:10px}[data-magellan-expedition] .sub-nav{margin-bottom:0}[data-magellan-expedition] .sub-nav dd{margin-bottom:0}[data-magellan-expedition] .sub-nav .active{line-height:1.8em}.tabs{*zoom:1;margin-bottom:0 !important}.tabs:before,.tabs:after{content:" ";display:table}.tabs:after{clear:both}.tabs dd{position:relative;margin-bottom:0 !important;top:1px;float:left}.tabs dd>a{display:block;background:#efefef;color:#222;padding-top:1rem;padding-right:2rem;padding-bottom:1.0625rem;padding-left:2rem;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;font-size:1rem}.tabs dd>a:hover{background:#e2e2e2}.tabs dd.active a{background:#fff}.tabs.radius dd:first-child a{-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.tabs.radius dd:last-child a{-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.tabs.vertical dd{position:inherit;float:none;display:block;top:auto}.tabs-content{*zoom:1;margin-bottom:1.5rem}.tabs-content:before,.tabs-content:after{content:" ";display:table}.tabs-content:after{clear:both}.tabs-content>.content{display:none;float:left;padding:0.9375rem 0}.tabs-content>.content.active{display:block}.tabs-content>.content.contained{padding:0.9375rem}.tabs-content.vertical{display:block}.tabs-content.vertical>.content{padding:0 0.9375rem}@media only screen and (min-width: 40.063em){.tabs.vertical{width:20%;float:left;margin-bottom:1.25rem}.tabs-content.vertical{width:80%;float:left;margin-left:-1px}}ul.pagination{display:block;height:1.5rem;margin-left:-0.3125rem}ul.pagination li{height:1.5rem;color:#222;font-size:0.875rem;margin-left:0.3125rem}ul.pagination li a{display:block;padding:0.0625rem 0.625rem 0.0625rem;color:#999;-webkit-border-radius:3px;border-radius:3px}ul.pagination li:hover a,ul.pagination li a:focus{background:#e6e6e6}ul.pagination li.unavailable a{cursor:default;color:#999}ul.pagination li.unavailable:hover a,ul.pagination li.unavailable a:focus{background:transparent}ul.pagination li.current a{background:#008cba;color:#fff;font-weight:bold;cursor:default}ul.pagination li.current a:hover,ul.pagination li.current a:focus{background:#008cba}ul.pagination li{float:left;display:block}.pagination-centered{text-align:center}.pagination-centered ul.pagination li{float:none;display:inline-block}.side-nav{display:block;margin:0;padding:0.875rem 0;list-style-type:none;list-style-position:inside;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif}.side-nav li{margin:0 0 0.4375rem 0;font-size:0.875rem}.side-nav li a{display:block;color:#008cba}.side-nav li.active>a:first-child{color:#4d4d4d;font-weight:normal;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif}.side-nav li.divider{border-top:1px solid;height:0;padding:0;list-style:none;border-top-color:#e6e6e6}.accordion{*zoom:1;margin-bottom:0}.accordion:before,.accordion:after{content:" ";display:table}.accordion:after{clear:both}.accordion dd{display:block;margin-bottom:0 !important}.accordion dd.active a{background:#e7e7e7}.accordion dd>a{background:#efefef;color:#222;padding:1rem;display:block;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;font-size:1rem}.accordion dd>a:hover{background:#e2e2e2}.accordion .content{display:none;padding:0.9375rem}.accordion .content.active{display:block;background:#fff}p.lead{font-size:1.21875rem;line-height:1.6}.subheader{line-height:1.4;color:#6f6f6f;font-weight:300;margin-top:0.2rem;margin-bottom:0.5rem}div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}a{color:#008cba;text-decoration:none;line-height:inherit}a:hover,a:focus{color:#0079a1}a img{border:none}p{font-family:inherit;font-weight:normal;font-size:1rem;line-height:1.6;margin-bottom:1.25rem;text-rendering:optimizeLegibility}p aside{font-size:0.875rem;line-height:1.35;font-style:italic}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;font-weight:300;font-style:normal;color:#222;text-rendering:optimizeLegibility;margin-top:0.2rem;margin-bottom:0.5rem;line-height:1.4}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-size:60%;color:#6f6f6f;line-height:0}h1{font-size:2.125rem}h2{font-size:1.6875rem}h3{font-size:1.375rem}h4{font-size:1.125rem}h5{font-size:1.125rem}h6{font-size:1rem}hr{border:solid #ddd;border-width:1px 0 0;clear:both;margin:1.25rem 0 1.1875rem;height:0}em,i{font-style:italic;line-height:inherit}strong,b{font-weight:bold;line-height:inherit}small{font-size:60%;line-height:inherit}code{font-family:Consolas,"Liberation Mono",Courier,monospace;font-weight:bold;color:#bb260d}ul,ol,dl{font-size:1rem;line-height:1.6;margin-bottom:1.25rem;list-style-position:outside;font-family:inherit}ul{margin-left:1.1rem}ul.no-bullet{margin-left:0}ul.no-bullet li ul,ul.no-bullet li ol{margin-left:1.25rem;margin-bottom:0;list-style:none}ul li ul,ul li ol{margin-left:1.25rem;margin-bottom:0;font-size:1rem}ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}ul.square{list-style-type:square;margin-left:1.1rem}ul.circle{list-style-type:circle;margin-left:1.1rem}ul.disc{list-style-type:disc;margin-left:1.1rem}ul.no-bullet{list-style:none}ol{margin-left:1.4rem}ol li ul,ol li ol{margin-left:1.25rem;margin-bottom:0}dl dt{margin-bottom:0.3rem;font-weight:bold}dl dd{margin-bottom:0.75rem}abbr,acronym{text-transform:uppercase;font-size:90%;color:#222;border-bottom:1px dotted #ddd;cursor:help}abbr{text-transform:none}blockquote{margin:0 0 1.25rem;padding:0.5625rem 1.25rem 0 1.1875rem;border-left:1px solid #ddd}blockquote cite{display:block;font-size:0.8125rem;color:#555}blockquote cite:before{content:"\2014 \0020"}blockquote cite a,blockquote cite a:visited{color:#555}blockquote,blockquote p{line-height:1.6;color:#6f6f6f}.vcard{display:inline-block;margin:0 0 1.25rem 0;border:1px solid #ddd;padding:0.625rem 0.75rem}.vcard li{margin:0;display:block}.vcard .fn{font-weight:bold;font-size:0.9375rem}.vevent .summary{font-weight:bold}.vevent abbr{cursor:default;text-decoration:none;font-weight:bold;border:none;padding:0 0.0625rem}@media only screen and (min-width: 40.063em){h1,h2,h3,h4,h5,h6{line-height:1.4}h1{font-size:2.75rem}h2{font-size:2.3125rem}h3{font-size:1.6875rem}h4{font-size:1.4375rem}}.print-only{display:none !important}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.hide-on-print{display:none !important}.print-only{display:block !important}.hide-for-print{display:none !important}.show-for-print{display:inherit !important}}.split.button{position:relative;padding-right:5.0625rem}.split.button span{display:block;height:100%;position:absolute;right:0;top:0;border-left:solid 1px}.split.button span:before{position:absolute;content:"";width:0;height:0;display:block;border-style:inset;top:50%;left:50%}.split.button span:active{background-color:rgba(0,0,0,0.1)}.split.button span{border-left-color:rgba(255,255,255,0.5)}.split.button span{width:3.09375rem}.split.button span:before{border-top-style:solid;border-width:0.375rem;top:48%;margin-left:-0.375rem}.split.button span:before{border-color:#fff transparent transparent transparent}.split.button.secondary span{border-left-color:rgba(255,255,255,0.5)}.split.button.secondary span:before{border-color:#fff transparent transparent transparent}.split.button.alert span{border-left-color:rgba(255,255,255,0.5)}.split.button.success span{border-left-color:rgba(255,255,255,0.5)}.split.button.tiny{padding-right:3.75rem}.split.button.tiny span{width:2.25rem}.split.button.tiny span:before{border-top-style:solid;border-width:0.375rem;top:48%;margin-left:-0.375rem}.split.button.small{padding-right:4.375rem}.split.button.small span{width:2.625rem}.split.button.small span:before{border-top-style:solid;border-width:0.4375rem;top:48%;margin-left:-0.375rem}.split.button.large{padding-right:5.5rem}.split.button.large span{width:3.4375rem}.split.button.large span:before{border-top-style:solid;border-width:0.3125rem;top:48%;margin-left:-0.375rem}.split.button.expand{padding-left:2rem}.split.button.secondary span:before{border-color:#333 transparent transparent transparent}.split.button.radius span{-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.split.button.round span{-moz-border-radius-topright:1000px;-moz-border-radius-bottomright:1000px;-webkit-border-top-right-radius:1000px;-webkit-border-bottom-right-radius:1000px;border-top-right-radius:1000px;border-bottom-right-radius:1000px}.reveal-modal-bg{position:fixed;height:100%;width:100%;background:#000;background:rgba(0,0,0,0.45);z-index:98;display:none;top:0;left:0}.reveal-modal{visibility:hidden;display:none;position:absolute;left:50%;z-index:99;height:auto;margin-left:-40%;width:80%;background-color:#fff;padding:1.25rem;border:solid 1px #666;-webkit-box-shadow:0 0 10px rgba(0,0,0,0.4);box-shadow:0 0 10px rgba(0,0,0,0.4);top:50px}.reveal-modal .column,.reveal-modal .columns{min-width:0}.reveal-modal>:first-child{margin-top:0}.reveal-modal>:last-child{margin-bottom:0}.reveal-modal .close-reveal-modal{font-size:1.375rem;line-height:1;position:absolute;top:0.5rem;right:0.6875rem;color:#aaa;font-weight:bold;cursor:pointer}@media only screen and (min-width: 40.063em){.reveal-modal{padding:1.875rem;top:6.25rem}.reveal-modal.tiny{margin-left:-15%;width:30%}.reveal-modal.small{margin-left:-20%;width:40%}.reveal-modal.medium{margin-left:-30%;width:60%}.reveal-modal.large{margin-left:-35%;width:70%}.reveal-modal.xlarge{margin-left:-47.5%;width:95%}}@media print{.reveal-modal{background:#fff !important}}.has-tip{border-bottom:dotted 1px #ccc;cursor:help;font-weight:bold;color:#333}.has-tip:hover,.has-tip:focus{border-bottom:dotted 1px #003f54;color:#008cba}.has-tip.tip-left,.has-tip.tip-right{float:none !important}.tooltip{display:none;position:absolute;z-index:999;font-weight:normal;font-size:0.875rem;line-height:1.3;padding:0.75rem;max-width:85%;left:50%;width:100%;color:#fff;background:#333;-webkit-border-radius:3px;border-radius:3px}.tooltip>.nub{display:block;left:5px;position:absolute;width:0;height:0;border:solid 5px;border-color:transparent transparent #333 transparent;top:-10px}.tooltip.opened{color:#008cba !important;border-bottom:dotted 1px #003f54 !important}.tap-to-close{display:block;font-size:0.625rem;color:#777;font-weight:normal}@media only screen and (min-width: 40.063em){.tooltip>.nub{border-color:transparent transparent #333 transparent;top:-10px}.tooltip.tip-top>.nub{border-color:#333 transparent transparent transparent;top:auto;bottom:-10px}.tooltip.tip-left,.tooltip.tip-right{float:none !important}.tooltip.tip-left>.nub{border-color:transparent transparent transparent #333;right:-10px;left:auto;top:50%;margin-top:-5px}.tooltip.tip-right>.nub{border-color:transparent #333 transparent transparent;right:auto;left:-10px;top:50%;margin-top:-5px}}[data-clearing]{*zoom:1;margin-bottom:0;margin-left:0;list-style:none}[data-clearing]:before,[data-clearing]:after{content:" ";display:table}[data-clearing]:after{clear:both}[data-clearing] li{float:left;margin-right:10px}.clearing-blackout{background:#333;position:fixed;width:100%;height:100%;top:0;left:0;z-index:998}.clearing-blackout .clearing-close{display:block}.clearing-container{position:relative;z-index:998;height:100%;overflow:hidden;margin:0}.visible-img{height:95%;position:relative}.visible-img img{position:absolute;left:50%;top:50%;margin-left:-50%;max-height:100%;max-width:100%}.clearing-caption{color:#ccc;font-size:0.875em;line-height:1.3;margin-bottom:0;text-align:center;bottom:0;background:#333;width:100%;padding:10px 30px 20px;position:absolute;left:0}.clearing-close{z-index:999;padding-left:20px;padding-top:10px;font-size:30px;line-height:1;color:#ccc;display:none}.clearing-close:hover,.clearing-close:focus{color:#ccc}.clearing-assembled .clearing-container{height:100%}.clearing-assembled .clearing-container .carousel>ul{display:none}.clearing-feature li{display:none}.clearing-feature li.clearing-featured-img{display:block}@media only screen and (min-width: 40.063em){.clearing-main-prev,.clearing-main-next{position:absolute;height:100%;width:40px;top:0}.clearing-main-prev>span,.clearing-main-next>span{position:absolute;top:50%;display:block;width:0;height:0;border:solid 12px}.clearing-main-prev>span:hover,.clearing-main-next>span:hover{opacity:0.8}.clearing-main-prev{left:0}.clearing-main-prev>span{left:5px;border-color:transparent;border-right-color:#ccc}.clearing-main-next{right:0}.clearing-main-next>span{border-color:transparent;border-left-color:#ccc}.clearing-main-prev.disabled,.clearing-main-next.disabled{opacity:0.3}.clearing-assembled .clearing-container .carousel{background:rgba(51,51,51,0.8);height:120px;margin-top:10px;text-align:center}.clearing-assembled .clearing-container .carousel>ul{display:inline-block;z-index:999;height:100%;position:relative;float:none}.clearing-assembled .clearing-container .carousel>ul li{display:block;width:120px;min-height:inherit;float:left;overflow:hidden;margin-right:0;padding:0;position:relative;cursor:pointer;opacity:0.4}.clearing-assembled .clearing-container .carousel>ul li.fix-height img{height:100%;max-width:none}.clearing-assembled .clearing-container .carousel>ul li a.th{border:none;-webkit-box-shadow:none;box-shadow:none;display:block}.clearing-assembled .clearing-container .carousel>ul li img{cursor:pointer !important;width:100% !important}.clearing-assembled .clearing-container .carousel>ul li.visible{opacity:1}.clearing-assembled .clearing-container .carousel>ul li:hover{opacity:0.8}.clearing-assembled .clearing-container .visible-img{background:#333;overflow:hidden;height:85%}.clearing-close{position:absolute;top:10px;right:20px;padding-left:0;padding-top:0}}.progress{background-color:#f6f6f6;height:1.5625rem;border:1px solid #ccc;padding:0.125rem;margin-bottom:0.625rem}.progress .meter{background:#008cba;height:100%;display:block}.progress.secondary .meter{background:#e7e7e7;height:100%;display:block}.progress.success .meter{background:#43ac6a;height:100%;display:block}.progress.alert .meter{background:#f04124;height:100%;display:block}.progress.radius{-webkit-border-radius:3px;border-radius:3px}.progress.radius .meter{-webkit-border-radius:2px;border-radius:2px}.progress.round{-webkit-border-radius:1000px;border-radius:1000px}.progress.round .meter{-webkit-border-radius:999px;border-radius:999px}.sub-nav{display:block;width:auto;overflow:hidden;margin:-0.25rem 0 1.125rem;padding-top:0.25rem;margin-right:0;margin-left:-0.75rem}.sub-nav dt{text-transform:uppercase}.sub-nav dt,.sub-nav dd,.sub-nav li{float:left;display:inline;margin-left:1rem;margin-bottom:0.625rem;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;font-weight:normal;font-size:0.875rem;color:#999}.sub-nav dt a,.sub-nav dd a,.sub-nav li a{text-decoration:none;color:#999}.sub-nav dt a:hover,.sub-nav dd a:hover,.sub-nav li a:hover{color:#0079a1}.sub-nav dt.active a,.sub-nav dd.active a,.sub-nav li.active a{-webkit-border-radius:3px;border-radius:3px;font-weight:normal;background:#008cba;padding:0.1875rem 1rem;cursor:default;color:#fff}.sub-nav dt.active a:hover,.sub-nav dd.active a:hover,.sub-nav li.active a:hover{background:#0079a1}.joyride-list{display:none}.joyride-tip-guide{display:none;position:absolute;background:#333;color:#fff;z-index:101;top:0;left:2.5%;font-family:inherit;font-weight:normal;width:95%}.lt-ie9 .joyride-tip-guide{max-width:800px;left:50%;margin-left:-400px}.joyride-content-wrapper{width:100%;padding:1.125rem 1.25rem 1.5rem}.joyride-content-wrapper .button{margin-bottom:0 !important}.joyride-tip-guide .joyride-nub{display:block;position:absolute;left:22px;width:0;height:0;border:10px solid #333}.joyride-tip-guide .joyride-nub.top{border-top-style:solid;border-color:#333;border-top-color:transparent !important;border-left-color:transparent !important;border-right-color:transparent !important;top:-20px}.joyride-tip-guide .joyride-nub.bottom{border-bottom-style:solid;border-color:#333 !important;border-bottom-color:transparent !important;border-left-color:transparent !important;border-right-color:transparent !important;bottom:-20px}.joyride-tip-guide .joyride-nub.right{right:-20px}.joyride-tip-guide .joyride-nub.left{left:-20px}.joyride-tip-guide h1,.joyride-tip-guide h2,.joyride-tip-guide h3,.joyride-tip-guide h4,.joyride-tip-guide h5,.joyride-tip-guide h6{line-height:1.25;margin:0;font-weight:bold;color:#fff}.joyride-tip-guide p{margin:0 0 1.125rem 0;font-size:0.875rem;line-height:1.3}.joyride-timer-indicator-wrap{width:50px;height:3px;border:solid 1px #555;position:absolute;right:1.0625rem;bottom:1rem}.joyride-timer-indicator{display:block;width:0;height:inherit;background:#666}.joyride-close-tip{position:absolute;right:12px;top:10px;color:#777 !important;text-decoration:none;font-size:24px;font-weight:normal;line-height:0.5 !important}.joyride-close-tip:hover,.joyride-close-tip:focus{color:#eee !important}.joyride-modal-bg{position:fixed;height:100%;width:100%;background:transparent;background:rgba(0,0,0,0.5);z-index:100;display:none;top:0;left:0;cursor:pointer}.joyride-expose-wrapper{background-color:#ffffff;position:absolute;border-radius:3px;z-index:102;-moz-box-shadow:0 0 30px #fff;-webkit-box-shadow:0 0 15px #fff;box-shadow:0 0 15px #fff}.joyride-expose-cover{background:transparent;border-radius:3px;position:absolute;z-index:9999;top:0;left:0}@media only screen and (min-width: 40.063em){.joyride-tip-guide{width:300px;left:inherit}.joyride-tip-guide .joyride-nub.bottom{border-color:#333 !important;border-bottom-color:transparent !important;border-left-color:transparent !important;border-right-color:transparent !important;bottom:-20px}.joyride-tip-guide .joyride-nub.right{border-color:#333 !important;border-top-color:transparent !important;border-right-color:transparent !important;border-bottom-color:transparent !important;top:22px;left:auto;right:-20px}.joyride-tip-guide .joyride-nub.left{border-color:#333 !important;border-top-color:transparent !important;border-left-color:transparent !important;border-bottom-color:transparent !important;top:22px;left:-20px;right:auto}}.label{font-weight:normal;font-family:"Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif;text-align:center;text-decoration:none;line-height:1;white-space:nowrap;display:inline-block;position:relative;margin-bottom:inherit;padding:0.25rem 0.5rem 0.375rem;font-size:0.6875rem;background-color:#008cba;color:#fff}.label.radius{-webkit-border-radius:3px;border-radius:3px}.label.round{-webkit-border-radius:1000px;border-radius:1000px}.label.alert{background-color:#f04124;color:#fff}.label.success{background-color:#43ac6a;color:#fff}.label.secondary{background-color:#e7e7e7;color:#333}p.lead{font-size:1.21875rem;line-height:1.6}.subheader{line-height:1.4;color:#6f6f6f;font-weight:300;margin-top:0.2rem;margin-bottom:0.5rem}meta.foundation-mq-topbar{font-family:"/only screen and (min-width:40.063em)/";width:40.063em}.off-canvas-wrap,.inner-wrap,nav.tab-bar,.left-off-canvas-menu,.left-off-canvas-menu *,.right-off-canvas-menu,.move-right a.exit-off-canvas,.move-left a.exit-off-canvas{-webkit-backface-visibility:hidden}.off-canvas-wrap,.inner-wrap{position:relative;width:100%}.left-off-canvas-menu,.right-off-canvas-menu{width:250px;top:0;bottom:0;height:100%;position:absolute;overflow-y:auto;background:#333;z-index:1001;box-sizing:content-box}section.left-small,section.right-small{width:2.8125rem;height:2.8125rem;position:absolute;top:0}.off-canvas-wrap{overflow:hidden}.inner-wrap{*zoom:1;-webkit-transition:-webkit-transform 500ms ease;-moz-transition:-moz-transform 500ms ease;-ms-transition:-ms-transform 500ms ease;-o-transition:-o-transform 500ms ease;transition:transform 500ms ease}.inner-wrap:before,.inner-wrap:after{content:" ";display:table}.inner-wrap:after{clear:both}nav.tab-bar{background:#333;color:#fff;height:2.8125rem;line-height:2.8125rem;position:relative}nav.tab-bar h1,nav.tab-bar h2,nav.tab-bar h3,nav.tab-bar h4,nav.tab-bar h5,nav.tab-bar h6{color:#fff;font-weight:bold;line-height:2.8125rem;margin:0}nav.tab-bar h1,nav.tab-bar h2,nav.tab-bar h3,nav.tab-bar h4{font-size:1.125rem}section.left-small{border-right:solid 1px #1a1a1a;box-shadow:1px 0 0 #4d4d4d;left:0}section.right-small{border-left:solid 1px #4d4d4d;box-shadow:-1px 0 0 #1a1a1a;right:0}section.tab-bar-section{padding:0 0.625rem;position:absolute;text-align:center;height:2.8125rem;top:0}@media only screen and (min-width: 40.063em){section.tab-bar-section{text-align:left}}section.tab-bar-section.left{left:0;right:2.8125rem}section.tab-bar-section.right{left:2.8125rem;right:0}section.tab-bar-section.middle{left:2.8125rem;right:2.8125rem}a.menu-icon{text-indent:2.1875rem;width:2.8125rem;height:2.8125rem;display:block;line-height:2.0625rem;padding:0;color:#fff;position:relative}a.menu-icon span{position:absolute;display:block;width:1rem;height:0;left:0.8125rem;top:0.3125rem;-webkit-box-shadow:0 10px 0 1px #fff,0 16px 0 1px #fff,0 22px 0 1px #fff;box-shadow:0 10px 0 1px #fff,0 16px 0 1px #fff,0 22px 0 1px #fff}a.menu-icon:hover span{-webkit-box-shadow:0 10px 0 1px #b3b3b3,0 16px 0 1px #b3b3b3,0 22px 0 1px #b3b3b3;box-shadow:0 10px 0 1px #b3b3b3,0 16px 0 1px #b3b3b3,0 22px 0 1px #b3b3b3}.left-off-canvas-menu{-webkit-transform:translate3d(-100%, 0, 0);-moz-transform:translate3d(-100%, 0, 0);-ms-transform:translate3d(-100%, 0, 0);-o-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0)}.right-off-canvas-menu{-webkit-transform:translate3d(100%, 0, 0);-moz-transform:translate3d(100%, 0, 0);-ms-transform:translate3d(100%, 0, 0);-o-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);right:0}ul.off-canvas-list{list-style-type:none;padding:0;margin:0}ul.off-canvas-list li label{padding:0.3rem 0.9375rem;color:#999;text-transform:uppercase;font-weight:bold;background:#444;border-top:1px solid #5e5e5e;border-bottom:none;margin:0}ul.off-canvas-list li a{display:block;padding:0.66667rem;color:rgba(255,255,255,0.7);border-bottom:1px solid #262626}.move-right>.inner-wrap{-webkit-transform:translate3d(250px, 0, 0);-moz-transform:translate3d(250px, 0, 0);-ms-transform:translate3d(250px, 0, 0);-o-transform:translate3d(250px, 0, 0);transform:translate3d(250px, 0, 0)}.move-right a.exit-off-canvas{transition:background 300ms ease;cursor:pointer;box-shadow:-4px 0 4px rgba(0,0,0,0.5),4px 0 4px rgba(0,0,0,0.5);display:block;position:absolute;background:rgba(255,255,255,0.2);top:0;bottom:0;left:0;right:0;z-index:1002}@media only screen and (min-width: 40.063em){.move-right a.exit-off-canvas:hover{background:rgba(255,255,255,0.05)}}.move-left>.inner-wrap{-webkit-transform:translate3d(-250px, 0, 0);-moz-transform:translate3d(-250px, 0, 0);-ms-transform:translate3d(-250px, 0, 0);-o-transform:translate3d(-250px, 0, 0);transform:translate3d(-250px, 0, 0)}.move-left a.exit-off-canvas{transition:background 300ms ease;cursor:pointer;box-shadow:-4px 0 4px rgba(0,0,0,0.5),4px 0 4px rgba(0,0,0,0.5);display:block;position:absolute;background:rgba(255,255,255,0.2);top:0;bottom:0;left:0;right:0;z-index:1002}@media only screen and (min-width: 40.063em){.move-left a.exit-off-canvas:hover{background:rgba(255,255,255,0.05)}}.lt-ie10 .left-off-canvas-menu{left:-250px}.lt-ie10 .right-off-canvas-menu{right:-250px}.lt-ie10 .move-left>.inner-wrap{right:250px}.lt-ie10 .move-right>.inner-wrap{left:250px}@media only screen and (max-width: 40em){.f-dropdown{max-width:100%;left:0}}.f-dropdown{position:absolute;top:-9999px;list-style:none;margin-left:0;width:100%;max-height:none;height:auto;background:#fff;border:solid 1px #ccc;font-size:16px;z-index:99;margin-top:2px;max-width:200px}.f-dropdown>*:first-child{margin-top:0}.f-dropdown>*:last-child{margin-bottom:0}.f-dropdown:before{content:"";display:block;width:0;height:0;border:inset 6px;border-color:transparent transparent #fff transparent;border-bottom-style:solid;position:absolute;top:-12px;left:10px;z-index:99}.f-dropdown:after{content:"";display:block;width:0;height:0;border:inset 7px;border-color:transparent transparent #ccc transparent;border-bottom-style:solid;position:absolute;top:-14px;left:9px;z-index:98}.f-dropdown.right:before{left:auto;right:10px}.f-dropdown.right:after{left:auto;right:9px}.f-dropdown li{font-size:0.875rem;cursor:pointer;line-height:1.125rem;margin:0}.f-dropdown li:hover,.f-dropdown li:focus{background:#eee}.f-dropdown li a{display:block;padding:0.5rem;color:#555}.f-dropdown.content{position:absolute;top:-9999px;list-style:none;margin-left:0;padding:1.25rem;width:100%;height:auto;max-height:none;background:#fff;border:solid 1px #ccc;font-size:16px;z-index:99;max-width:200px}.f-dropdown.content>*:first-child{margin-top:0}.f-dropdown.content>*:last-child{margin-bottom:0}.f-dropdown.tiny{max-width:200px}.f-dropdown.small{max-width:300px}.f-dropdown.medium{max-width:500px}.f-dropdown.large{max-width:800px}table{background:#fff;margin-bottom:1.25rem;border:solid 1px #ddd}table thead,table tfoot{background:#f5f5f5;font-weight:bold}table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:0.5rem 0.625rem 0.625rem;font-size:0.875rem;color:#222;text-align:left}table tr th,table tr td{padding:0.5625rem 0.625rem;font-size:0.875rem;color:#222}table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f9f9f9}table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.125rem}form{margin:0 0 1rem}form .row .row{margin:0 -0.5rem}form .row .row .column,form .row .row .columns{padding:0 0.5rem}form .row .row.collapse{margin:0}form .row .row.collapse .column,form .row .row.collapse .columns{padding:0}form .row .row.collapse input{-moz-border-radius-bottomright:0;-moz-border-radius-topright:0;-webkit-border-bottom-right-radius:0;-webkit-border-top-right-radius:0}form .row input.column,form .row input.columns,form .row textarea.column,form .row textarea.columns{padding-left:0.5rem}label{font-size:0.875rem;color:#4d4d4d;cursor:pointer;display:block;font-weight:normal;margin-bottom:0.5rem}label.right{float:none;text-align:right}label.inline{margin:0 0 1rem 0;padding:0.625rem 0}label small{text-transform:capitalize;color:#666}select{-webkit-appearance:none !important;background:#fafafa url("data:image/svg+xml;base64, PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iM3B4IiB2aWV3Qm94PSIwIDAgNiAzIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA2IDMiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwb2x5Z29uIHBvaW50cz0iNS45OTIsMCAyLjk5MiwzIC0wLjAwOCwwICIvPjwvc3ZnPg==") no-repeat;background-position-x:97%;background-position-y:center;border:1px solid #ccc;padding:0.5rem;font-size:0.875rem;-webkit-border-radius:0;border-radius:0}select.radius{-webkit-border-radius:3px;border-radius:3px}select:hover{background:#f2f2f2 url("data:image/svg+xml;base64, PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iM3B4IiB2aWV3Qm94PSIwIDAgNiAzIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA2IDMiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwb2x5Z29uIHBvaW50cz0iNS45OTIsMCAyLjk5MiwzIC0wLjAwOCwwICIvPjwvc3ZnPg==") no-repeat;background-position-x:97%;background-position-y:center;border-color:#999}@-moz-document url-prefix(){select{background:#fafafa}select:hover{background:#f2f2f2}}.prefix,.postfix{display:block;position:relative;z-index:2;text-align:center;width:100%;padding-top:0;padding-bottom:0;border-style:solid;border-width:1px;overflow:hidden;font-size:0.875rem;height:2.3125rem;line-height:2.3125rem}.postfix.button{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0;text-align:center;line-height:2.125rem;border:none}.prefix.button{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0;text-align:center;line-height:2.125rem;border:none}.prefix.button.radius{-webkit-border-radius:0;border-radius:0;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.postfix.button.radius{-webkit-border-radius:0;border-radius:0;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.prefix.button.round{-webkit-border-radius:0;border-radius:0;-moz-border-radius-bottomleft:1000px;-moz-border-radius-topleft:1000px;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}.postfix.button.round{-webkit-border-radius:0;border-radius:0;-moz-border-radius-topright:1000px;-moz-border-radius-bottomright:1000px;-webkit-border-top-right-radius:1000px;-webkit-border-bottom-right-radius:1000px;border-top-right-radius:1000px;border-bottom-right-radius:1000px}span.prefix,label.prefix{background:#f2f2f2;border-color:#d9d9d9;border-right:none;color:#333}span.prefix.radius,label.prefix.radius{-webkit-border-radius:0;border-radius:0;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}span.postfix,label.postfix{background:#f2f2f2;border-color:#ccc;border-left:none;color:#333}span.postfix.radius,label.postfix.radius{-webkit-border-radius:0;border-radius:0;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.input-group.radius>*:first-child,.input-group.radius>*:first-child *{-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.input-group.radius>*:last-child,.input-group.radius>*:last-child *{-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px;-webkit-border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.input-group.round>*:first-child,.input-group.round>*:first-child *{-moz-border-radius-bottomleft:1000px;-moz-border-radius-topleft:1000px;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}.input-group.round>*:last-child,.input-group.round>*:last-child *{-moz-border-radius-topright:1000px;-moz-border-radius-bottomright:1000px;-webkit-border-top-right-radius:1000px;-webkit-border-bottom-right-radius:1000px;border-top-right-radius:1000px;border-bottom-right-radius:1000px}input[type="text"],input[type="password"],input[type="date"],input[type="datetime"],input[type="datetime-local"],input[type="month"],input[type="week"],input[type="email"],input[type="number"],input[type="search"],input[type="tel"],input[type="time"],input[type="url"],textarea{-webkit-appearance:none;-webkit-border-radius:0;border-radius:0;background-color:#fff;font-family:inherit;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);color:rgba(0,0,0,0.75);display:block;font-size:0.875rem;margin:0 0 1rem 0;padding:0.5rem;height:2.3125rem;width:100%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transition:-webkit-box-shadow 0.45s,border-color 0.45s ease-in-out;-moz-transition:-moz-box-shadow 0.45s,border-color 0.45s ease-in-out;transition:box-shadow 0.45s,border-color 0.45s ease-in-out}input[type="text"]:focus,input[type="password"]:focus,input[type="date"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="month"]:focus,input[type="week"]:focus,input[type="email"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="time"]:focus,input[type="url"]:focus,textarea:focus{-webkit-box-shadow:0 0 5px #999;-moz-box-shadow:0 0 5px #999;box-shadow:0 0 5px #999;border-color:#999}input[type="text"]:focus,input[type="password"]:focus,input[type="date"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="month"]:focus,input[type="week"]:focus,input[type="email"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="time"]:focus,input[type="url"]:focus,textarea:focus{background:#fafafa;border-color:#999;outline:none}input[type="text"][disabled],input[type="password"][disabled],input[type="date"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="month"][disabled],input[type="week"][disabled],input[type="email"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="time"][disabled],input[type="url"][disabled],textarea[disabled]{background-color:#ddd}input[type="file"],input[type="checkbox"],input[type="radio"],select{margin:0 0 1rem 0}input[type="checkbox"]+label,input[type="radio"]+label{display:inline-block;margin-left:0.5rem;margin-right:1rem;margin-bottom:0;vertical-align:baseline}input[type="file"]{width:100%}fieldset{border:solid 1px #ddd;padding:1.25rem;margin:1.125rem 0}fieldset legend{font-weight:bold;background:#fff;padding:0 0.1875rem;margin:0;margin-left:-0.1875rem}[data-abide] .error small.error,[data-abide] span.error,[data-abide] small.error{display:block;padding:0.375rem 0.5625rem 0.5625rem;margin-top:-1px;margin-bottom:1rem;font-size:0.75rem;font-weight:normal;font-style:italic;background:#f04124;color:#fff}[data-abide] span.error,[data-abide] small.error{display:none}span.error,small.error{display:block;padding:0.375rem 0.5625rem 0.5625rem;margin-top:-1px;margin-bottom:1rem;font-size:0.75rem;font-weight:normal;font-style:italic;background:#f04124;color:#fff}.error input,.error textarea,.error select{margin-bottom:0}.error label,.error label.error{color:#f04124}.error>small,.error small.error{display:block;padding:0.375rem 0.5625rem 0.5625rem;margin-top:-1px;margin-bottom:1rem;font-size:0.75rem;font-weight:normal;font-style:italic;background:#f04124;color:#fff}.error span.error-message{display:block}input.error,textarea.error{margin-bottom:0}label.error{color:#f04124}[class*="block-grid-"]{display:block;padding:0;margin:0 -0.625rem;*zoom:1}[class*="block-grid-"]:before,[class*="block-grid-"]:after{content:" ";display:table}[class*="block-grid-"]:after{clear:both}[class*="block-grid-"]>li{display:inline;height:auto;float:left;padding:0 0.625rem 1.25rem}@media only screen{.small-block-grid-1>li{width:100%;padding:0 0.625rem 1.25rem}.small-block-grid-1>li:nth-of-type(n){clear:none}.small-block-grid-1>li:nth-of-type(1n+1){clear:both}.small-block-grid-2>li{width:50%;padding:0 0.625rem 1.25rem}.small-block-grid-2>li:nth-of-type(n){clear:none}.small-block-grid-2>li:nth-of-type(2n+1){clear:both}.small-block-grid-3>li{width:33.33333%;padding:0 0.625rem 1.25rem}.small-block-grid-3>li:nth-of-type(n){clear:none}.small-block-grid-3>li:nth-of-type(3n+1){clear:both}.small-block-grid-4>li{width:25%;padding:0 0.625rem 1.25rem}.small-block-grid-4>li:nth-of-type(n){clear:none}.small-block-grid-4>li:nth-of-type(4n+1){clear:both}.small-block-grid-5>li{width:20%;padding:0 0.625rem 1.25rem}.small-block-grid-5>li:nth-of-type(n){clear:none}.small-block-grid-5>li:nth-of-type(5n+1){clear:both}.small-block-grid-6>li{width:16.66667%;padding:0 0.625rem 1.25rem}.small-block-grid-6>li:nth-of-type(n){clear:none}.small-block-grid-6>li:nth-of-type(6n+1){clear:both}.small-block-grid-7>li{width:14.28571%;padding:0 0.625rem 1.25rem}.small-block-grid-7>li:nth-of-type(n){clear:none}.small-block-grid-7>li:nth-of-type(7n+1){clear:both}.small-block-grid-8>li{width:12.5%;padding:0 0.625rem 1.25rem}.small-block-grid-8>li:nth-of-type(n){clear:none}.small-block-grid-8>li:nth-of-type(8n+1){clear:both}.small-block-grid-9>li{width:11.11111%;padding:0 0.625rem 1.25rem}.small-block-grid-9>li:nth-of-type(n){clear:none}.small-block-grid-9>li:nth-of-type(9n+1){clear:both}.small-block-grid-10>li{width:10%;padding:0 0.625rem 1.25rem}.small-block-grid-10>li:nth-of-type(n){clear:none}.small-block-grid-10>li:nth-of-type(10n+1){clear:both}.small-block-grid-11>li{width:9.09091%;padding:0 0.625rem 1.25rem}.small-block-grid-11>li:nth-of-type(n){clear:none}.small-block-grid-11>li:nth-of-type(11n+1){clear:both}.small-block-grid-12>li{width:8.33333%;padding:0 0.625rem 1.25rem}.small-block-grid-12>li:nth-of-type(n){clear:none}.small-block-grid-12>li:nth-of-type(12n+1){clear:both}}@media only screen and (min-width: 40.063em){.medium-block-grid-1>li{width:100%;padding:0 0.625rem 1.25rem}.medium-block-grid-1>li:nth-of-type(n){clear:none}.medium-block-grid-1>li:nth-of-type(1n+1){clear:both}.medium-block-grid-2>li{width:50%;padding:0 0.625rem 1.25rem}.medium-block-grid-2>li:nth-of-type(n){clear:none}.medium-block-grid-2>li:nth-of-type(2n+1){clear:both}.medium-block-grid-3>li{width:33.33333%;padding:0 0.625rem 1.25rem}.medium-block-grid-3>li:nth-of-type(n){clear:none}.medium-block-grid-3>li:nth-of-type(3n+1){clear:both}.medium-block-grid-4>li{width:25%;padding:0 0.625rem 1.25rem}.medium-block-grid-4>li:nth-of-type(n){clear:none}.medium-block-grid-4>li:nth-of-type(4n+1){clear:both}.medium-block-grid-5>li{width:20%;padding:0 0.625rem 1.25rem}.medium-block-grid-5>li:nth-of-type(n){clear:none}.medium-block-grid-5>li:nth-of-type(5n+1){clear:both}.medium-block-grid-6>li{width:16.66667%;padding:0 0.625rem 1.25rem}.medium-block-grid-6>li:nth-of-type(n){clear:none}.medium-block-grid-6>li:nth-of-type(6n+1){clear:both}.medium-block-grid-7>li{width:14.28571%;padding:0 0.625rem 1.25rem}.medium-block-grid-7>li:nth-of-type(n){clear:none}.medium-block-grid-7>li:nth-of-type(7n+1){clear:both}.medium-block-grid-8>li{width:12.5%;padding:0 0.625rem 1.25rem}.medium-block-grid-8>li:nth-of-type(n){clear:none}.medium-block-grid-8>li:nth-of-type(8n+1){clear:both}.medium-block-grid-9>li{width:11.11111%;padding:0 0.625rem 1.25rem}.medium-block-grid-9>li:nth-of-type(n){clear:none}.medium-block-grid-9>li:nth-of-type(9n+1){clear:both}.medium-block-grid-10>li{width:10%;padding:0 0.625rem 1.25rem}.medium-block-grid-10>li:nth-of-type(n){clear:none}.medium-block-grid-10>li:nth-of-type(10n+1){clear:both}.medium-block-grid-11>li{width:9.09091%;padding:0 0.625rem 1.25rem}.medium-block-grid-11>li:nth-of-type(n){clear:none}.medium-block-grid-11>li:nth-of-type(11n+1){clear:both}.medium-block-grid-12>li{width:8.33333%;padding:0 0.625rem 1.25rem}.medium-block-grid-12>li:nth-of-type(n){clear:none}.medium-block-grid-12>li:nth-of-type(12n+1){clear:both}}@media only screen and (min-width: 64.063em){.large-block-grid-1>li{width:100%;padding:0 0.625rem 1.25rem}.large-block-grid-1>li:nth-of-type(n){clear:none}.large-block-grid-1>li:nth-of-type(1n+1){clear:both}.large-block-grid-2>li{width:50%;padding:0 0.625rem 1.25rem}.large-block-grid-2>li:nth-of-type(n){clear:none}.large-block-grid-2>li:nth-of-type(2n+1){clear:both}.large-block-grid-3>li{width:33.33333%;padding:0 0.625rem 1.25rem}.large-block-grid-3>li:nth-of-type(n){clear:none}.large-block-grid-3>li:nth-of-type(3n+1){clear:both}.large-block-grid-4>li{width:25%;padding:0 0.625rem 1.25rem}.large-block-grid-4>li:nth-of-type(n){clear:none}.large-block-grid-4>li:nth-of-type(4n+1){clear:both}.large-block-grid-5>li{width:20%;padding:0 0.625rem 1.25rem}.large-block-grid-5>li:nth-of-type(n){clear:none}.large-block-grid-5>li:nth-of-type(5n+1){clear:both}.large-block-grid-6>li{width:16.66667%;padding:0 0.625rem 1.25rem}.large-block-grid-6>li:nth-of-type(n){clear:none}.large-block-grid-6>li:nth-of-type(6n+1){clear:both}.large-block-grid-7>li{width:14.28571%;padding:0 0.625rem 1.25rem}.large-block-grid-7>li:nth-of-type(n){clear:none}.large-block-grid-7>li:nth-of-type(7n+1){clear:both}.large-block-grid-8>li{width:12.5%;padding:0 0.625rem 1.25rem}.large-block-grid-8>li:nth-of-type(n){clear:none}.large-block-grid-8>li:nth-of-type(8n+1){clear:both}.large-block-grid-9>li{width:11.11111%;padding:0 0.625rem 1.25rem}.large-block-grid-9>li:nth-of-type(n){clear:none}.large-block-grid-9>li:nth-of-type(9n+1){clear:both}.large-block-grid-10>li{width:10%;padding:0 0.625rem 1.25rem}.large-block-grid-10>li:nth-of-type(n){clear:none}.large-block-grid-10>li:nth-of-type(10n+1){clear:both}.large-block-grid-11>li{width:9.09091%;padding:0 0.625rem 1.25rem}.large-block-grid-11>li:nth-of-type(n){clear:none}.large-block-grid-11>li:nth-of-type(11n+1){clear:both}.large-block-grid-12>li{width:8.33333%;padding:0 0.625rem 1.25rem}.large-block-grid-12>li:nth-of-type(n){clear:none}.large-block-grid-12>li:nth-of-type(12n+1){clear:both}}.flex-video{position:relative;padding-top:1.5625rem;padding-bottom:67.5%;height:0;margin-bottom:1rem;overflow:hidden}.flex-video.widescreen{padding-bottom:57.25%}.flex-video.vimeo{padding-top:0}.flex-video iframe,.flex-video object,.flex-video embed,.flex-video video{position:absolute;top:0;left:0;width:100%;height:100%}.keystroke,kbd{background-color:#ededed;border-color:#dbdbdb;color:#222;border-style:solid;border-width:1px;margin:0;font-family:"Consolas","Menlo","Courier",monospace;font-size:0.875rem;padding:0.125rem 0.25rem 0;-webkit-border-radius:3px;border-radius:3px}.show-for-small,.show-for-small-only,.show-for-medium-down,.show-for-large-down,.hide-for-medium,.hide-for-medium-up,.hide-for-medium-only,.hide-for-large,.hide-for-large-up,.hide-for-large-only,.hide-for-xlarge,.hide-for-xlarge-up,.hide-for-xlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge-only{display:inherit !important}.hide-for-small,.hide-for-small-only,.hide-for-medium-down,.show-for-medium,.show-for-medium-up,.show-for-medium-only,.hide-for-large-down,.show-for-large,.show-for-large-up,.show-for-large-only,.show-for-xlarge,.show-for-xlarge-up,.show-for-xlarge-only,.show-for-xxlarge-up,.show-for-xxlarge-only{display:none !important}table.show-for-small,table.show-for-small-only,table.show-for-medium-down,table.show-for-large-down,table.hide-for-medium,table.hide-for-medium-up,table.hide-for-medium-only,table.hide-for-large,table.hide-for-large-up,table.hide-for-large-only,table.hide-for-xlarge,table.hide-for-xlarge-up,table.hide-for-xlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge-only{display:table}thead.show-for-small,thead.show-for-small-only,thead.show-for-medium-down,thead.show-for-large-down,thead.hide-for-medium,thead.hide-for-medium-up,thead.hide-for-medium-only,thead.hide-for-large,thead.hide-for-large-up,thead.hide-for-large-only,thead.hide-for-xlarge,thead.hide-for-xlarge-up,thead.hide-for-xlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge-only{display:table-header-group !important}tbody.show-for-small,tbody.show-for-small-only,tbody.show-for-medium-down,tbody.show-for-large-down,tbody.hide-for-medium,tbody.hide-for-medium-up,tbody.hide-for-medium-only,tbody.hide-for-large,tbody.hide-for-large-up,tbody.hide-for-large-only,tbody.hide-for-xlarge,tbody.hide-for-xlarge-up,tbody.hide-for-xlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge-only{display:table-row-group !important}tr.show-for-small,tr.show-for-small-only,tr.show-for-medium-down,tr.show-for-large-down,tr.hide-for-medium,tr.hide-for-medium-up,tr.hide-for-medium-only,tr.hide-for-large,tr.hide-for-large-up,tr.hide-for-large-only,tr.hide-for-xlarge,tr.hide-for-xlarge-up,tr.hide-for-xlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge-only{display:table-row !important}td.show-for-small,td.show-for-small-only,td.show-for-medium-down td.show-for-large-down,td.hide-for-medium,td.hide-for-medium-up,td.hide-for-large,td.hide-for-large-up,td.hide-for-xlarge td.hide-for-xlarge-up,td.hide-for-xxlarge-up,th.show-for-small,th.show-for-small-only,th.show-for-medium-down th.show-for-large-down,th.hide-for-medium,th.hide-for-medium-up,th.hide-for-large,th.hide-for-large-up,th.hide-for-xlarge th.hide-for-xlarge-up,th.hide-for-xxlarge-up{display:table-cell !important}@media only screen and (min-width: 40.063em){.hide-for-small,.hide-for-small-only,.show-for-medium,.show-for-medium-down,.show-for-medium-up,.show-for-medium-only,.hide-for-large,.hide-for-large-up,.hide-for-large-only,.hide-for-xlarge,.hide-for-xlarge-up,.hide-for-xlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge-only{display:inherit !important}.show-for-small,.show-for-small-only,.hide-for-medium,.hide-for-medium-down,.hide-for-medium-up,.hide-for-medium-only,.hide-for-large-down,.show-for-large,.show-for-large-up,.show-for-large-only,.show-for-xlarge,.show-for-xlarge-up,.show-for-xlarge-only,.show-for-xxlarge-up,.show-for-xxlarge-only{display:none !important}table.hide-for-small,table.hide-for-small-only,table.show-for-medium,table.show-for-medium-down,table.show-for-medium-up,table.show-for-medium-only,table.hide-for-large,table.hide-for-large-up,table.hide-for-large-only,table.hide-for-xlarge,table.hide-for-xlarge-up,table.hide-for-xlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge-only{display:table}thead.hide-for-small,thead.hide-for-small-only,thead.show-for-medium,thead.show-for-medium-down,thead.show-for-medium-up,thead.show-for-medium-only,thead.hide-for-large,thead.hide-for-large-up,thead.hide-for-large-only,thead.hide-for-xlarge,thead.hide-for-xlarge-up,thead.hide-for-xlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge-only{display:table-header-group !important}tbody.hide-for-small,tbody.hide-for-small-only,tbody.show-for-medium,tbody.show-for-medium-down,tbody.show-for-medium-up,tbody.show-for-medium-only,tbody.hide-for-large,tbody.hide-for-large-up,tbody.hide-for-large-only,tbody.hide-for-xlarge,tbody.hide-for-xlarge-up,tbody.hide-for-xlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge-only{display:table-row-group !important}tr.hide-for-small,tr.hide-for-small-only,tr.show-for-medium,tr.show-for-medium-down,tr.show-for-medium-up,tr.show-for-medium-only,tr.hide-for-large,tr.hide-for-large-up,tr.hide-for-large-only,tr.hide-for-xlarge,tr.hide-for-xlarge-up,tr.hide-for-xlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge-only{display:table-row !important}td.hide-for-small,td.hide-for-small-only,td.show-for-medium,td.show-for-medium-down,td.show-for-medium-up,td.show-for-medium-only,td.hide-for-large,td.hide-for-large-up,td.hide-for-large-only,td.hide-for-xlarge,td.hide-for-xlarge-up,td.hide-for-xlarge-only,td.hide-for-xxlarge-up,td.hide-for-xxlarge-only,th.hide-for-small,th.hide-for-small-only,th.show-for-medium,th.show-for-medium-down,th.show-for-medium-up,th.show-for-medium-only,th.hide-for-large,th.hide-for-large-up,th.hide-for-large-only,th.hide-for-xlarge,th.hide-for-xlarge-up,th.hide-for-xlarge-only,th.hide-for-xxlarge-up,th.hide-for-xxlarge-only{display:table-cell !important}}@media only screen and (min-width: 64.063em){.hide-for-small,.hide-for-small-only,.hide-for-medium,.hide-for-medium-down,.hide-for-medium-only,.show-for-medium-up,.show-for-large,.show-for-large-up,.show-for-large-only,.hide-for-xlarge,.hide-for-xlarge-up,.hide-for-xlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge-only{display:inherit !important}.show-for-small-only,.show-for-medium,.show-for-medium-down,.show-for-medium-only,.hide-for-large,.hide-for-large-up,.hide-for-large-only,.show-for-xlarge,.show-for-xlarge-up,.show-for-xlarge-only,.show-for-xxlarge-up,.show-for-xxlarge-only{display:none !important}table.hide-for-small,table.hide-for-small-only,table.hide-for-medium,table.hide-for-medium-down,table.hide-for-medium-only,table.show-for-medium-up,table.show-for-large,table.show-for-large-up,table.show-for-large-only,table.hide-for-xlarge,table.hide-for-xlarge-up,table.hide-for-xlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge-only{display:table}thead.hide-for-small,thead.hide-for-small-only,thead.hide-for-medium,thead.hide-for-medium-down,thead.hide-for-medium-only,thead.show-for-medium-up,thead.show-for-large,thead.show-for-large-up,thead.show-for-large-only,thead.hide-for-xlarge,thead.hide-for-xlarge-up,thead.hide-for-xlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge-only{display:table-header-group !important}tbody.hide-for-small,tbody.hide-for-small-only,tbody.hide-for-medium,tbody.hide-for-medium-down,tbody.hide-for-medium-only,tbody.show-for-medium-up,tbody.show-for-large,tbody.show-for-large-up,tbody.show-for-large-only,tbody.hide-for-xlarge,tbody.hide-for-xlarge-up,tbody.hide-for-xlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge-only{display:table-row-group !important}tr.hide-for-small,tr.hide-for-small-only,tr.hide-for-medium,tr.hide-for-medium-down,tr.hide-for-medium-only,tr.show-for-medium-up,tr.show-for-large,tr.show-for-large-up,tr.show-for-large-only,tr.hide-for-xlarge,tr.hide-for-xlarge-up,tr.hide-for-xlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge-only{display:table-row !important}td.hide-for-small,td.hide-for-small-only,td.hide-for-medium,td.hide-for-medium-down,td.hide-for-medium-only,td.show-for-medium-up,td.show-for-large,td.show-for-large-up,td.show-for-large-only,td.hide-for-xlarge,td.hide-for-xlarge-up,td.hide-for-xlarge-only,td.hide-for-xxlarge-up,td.hide-for-xxlarge-only,th.hide-for-small,th.hide-for-small-only,th.hide-for-medium,th.hide-for-medium-down,th.hide-for-medium-only,th.show-for-medium-up,th.show-for-large,th.show-for-large-up,th.show-for-large-only,th.hide-for-xlarge,th.hide-for-xlarge-up,th.hide-for-xlarge-only,th.hide-for-xxlarge-up,th.hide-for-xxlarge-only{display:table-cell !important}}@media only screen and (min-width: 90.063em){.hide-for-small,.hide-for-small-only,.hide-for-medium,.hide-for-medium-down,.hide-for-medium-only,.show-for-medium-up,.show-for-large-up,.show-for-xlarge,.show-for-xlarge-up,.show-for-xlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge-only{display:inherit !important}.show-for-small-only,.show-for-medium,.show-for-medium-down,.show-for-medium-only,.show-for-large,.show-for-large-only,.show-for-large-down,.hide-for-xlarge,.hide-for-xlarge-up,.hide-for-xlarge-only,.show-for-xxlarge-up,.show-for-xxlarge-only{display:none !important}table.hide-for-small,table.hide-for-small-only,table.hide-for-medium,table.hide-for-medium-down,table.hide-for-medium-only,table.show-for-medium-up,table.show-for-large-up,table.show-for-xlarge,table.show-for-xlarge-up,table.show-for-xlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge-only{display:table}thead.hide-for-small,thead.hide-for-small-only,thead.hide-for-medium,thead.hide-for-medium-down,thead.hide-for-medium-only,thead.show-for-medium-up,thead.show-for-large-up,thead.show-for-xlarge,thead.show-for-xlarge-up,thead.show-for-xlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge-only{display:table-header-group !important}tbody.hide-for-small,tbody.hide-for-small-only,tbody.hide-for-medium,tbody.hide-for-medium-down,tbody.hide-for-medium-only,tbody.show-for-medium-up,tbody.show-for-large-up,tbody.show-for-xlarge,tbody.show-for-xlarge-up,tbody.show-for-xlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge-only{display:table-row-group !important}tr.hide-for-small,tr.hide-for-small-only,tr.hide-for-medium,tr.hide-for-medium-down,tr.hide-for-medium-only,tr.show-for-medium-up,tr.show-for-large-up,tr.show-for-xlarge,tr.show-for-xlarge-up,tr.show-for-xlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge-only{display:table-row !important}td.hide-for-small,td.hide-for-small-only,td.hide-for-medium,td.hide-for-medium-down,td.hide-for-medium-only,td.show-for-medium-up,td.show-for-large-up,td.show-for-xlarge,td.show-for-xlarge-up,td.show-for-xlarge-only,td.hide-for-xxlarge-up,td.hide-for-xxlarge-only,th.hide-for-small,th.hide-for-small-only,th.hide-for-medium,th.hide-for-medium-down,th.hide-for-medium-only,th.show-for-medium-up,th.show-for-large-up,th.show-for-xlarge,th.show-for-xlarge-up,th.show-for-xlarge-only,th.hide-for-xxlarge-up,th.hide-for-xxlarge-only{display:table-cell !important}}@media only screen and (min-width: 120.063em){.hide-for-small,.hide-for-small-only,.hide-for-medium,.hide-for-medium-down,.hide-for-medium-only,.show-for-medium-up,.show-for-large-up,.show-for-xlarge-up,.show-for-xxlarge-up,.show-for-xxlarge-only{display:inherit !important}.show-for-small-only,.show-for-medium,.show-for-medium-down,.show-for-medium-only,.show-for-large,.show-for-large-only,.show-for-large-down,.hide-for-xlarge,.show-for-xlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge-only{display:none !important}table.hide-for-small,table.hide-for-small-only,table.hide-for-medium,table.hide-for-medium-down,table.hide-for-medium-only,table.show-for-medium-up,table.show-for-large-up,table.show-for-xlarge-up,table.show-for-xxlarge-up,table.show-for-xxlarge-only{display:table}thead.hide-for-small,thead.hide-for-small-only,thead.hide-for-medium,thead.hide-for-medium-down,thead.hide-for-medium-only,thead.show-for-medium-up,thead.show-for-large-up,thead.show-for-xlarge-up,thead.show-for-xxlarge-up,thead.show-for-xxlarge-only{display:table-header-group !important}tbody.hide-for-small,tbody.hide-for-small-only,tbody.hide-for-medium,tbody.hide-for-medium-down,tbody.hide-for-medium-only,tbody.show-for-medium-up,tbody.show-for-large-up,tbody.show-for-xlarge-up,tbody.show-for-xxlarge-up,tbody.show-for-xxlarge-only{display:table-row-group !important}tr.hide-for-small,tr.hide-for-small-only,tr.hide-for-medium,tr.hide-for-medium-down,tr.hide-for-medium-only,tr.show-for-medium-up,tr.show-for-large-up,tr.show-for-xlarge-up,tr.show-for-xxlarge-up,tr.show-for-xxlarge-only{display:table-row !important}td.hide-for-small,td.hide-for-small-only,td.hide-for-medium,td.hide-for-medium-down,td.hide-for-medium-only,td.show-for-medium-up,td.show-for-large-up,td.show-for-xlarge-up,td.show-for-xxlarge-up,td.show-for-xxlarge-only,th.hide-for-small,th.hide-for-small-only,th.hide-for-medium,th.hide-for-medium-down,th.hide-for-medium-only,th.show-for-medium-up,th.show-for-large-up,th.show-for-xlarge-up,th.show-for-xxlarge-up,th.show-for-xxlarge-only{display:table-cell !important}}.show-for-landscape,.hide-for-portrait{display:inherit !important}.hide-for-landscape,.show-for-portrait{display:none !important}table.hide-for-landscape,table.show-for-portrait{display:table}thead.hide-for-landscape,thead.show-for-portrait{display:table-header-group !important}tbody.hide-for-landscape,tbody.show-for-portrait{display:table-row-group !important}tr.hide-for-landscape,tr.show-for-portrait{display:table-row !important}td.hide-for-landscape,td.show-for-portrait,th.hide-for-landscape,th.show-for-portrait{display:table-cell !important}@media only screen and (orientation: landscape){.show-for-landscape,.hide-for-portrait{display:inherit !important}.hide-for-landscape,.show-for-portrait{display:none !important}table.show-for-landscape,table.hide-for-portrait{display:table}thead.show-for-landscape,thead.hide-for-portrait{display:table-header-group !important}tbody.show-for-landscape,tbody.hide-for-portrait{display:table-row-group !important}tr.show-for-landscape,tr.hide-for-portrait{display:table-row !important}td.show-for-landscape,td.hide-for-portrait,th.show-for-landscape,th.hide-for-portrait{display:table-cell !important}}@media only screen and (orientation: portrait){.show-for-portrait,.hide-for-landscape{display:inherit !important}.hide-for-portrait,.show-for-landscape{display:none !important}table.show-for-portrait,table.hide-for-landscape{display:table}thead.show-for-portrait,thead.hide-for-landscape{display:table-header-group !important}tbody.show-for-portrait,tbody.hide-for-landscape{display:table-row-group !important}tr.show-for-portrait,tr.hide-for-landscape{display:table-row !important}td.show-for-portrait,td.hide-for-landscape,th.show-for-portrait,th.hide-for-landscape{display:table-cell !important}}.show-for-touch{display:none !important}.hide-for-touch{display:inherit !important}.touch .show-for-touch{display:inherit !important}.touch .hide-for-touch{display:none !important}table.hide-for-touch{display:table}.touch table.show-for-touch{display:table}thead.hide-for-touch{display:table-header-group !important}.touch thead.show-for-touch{display:table-header-group !important}tbody.hide-for-touch{display:table-row-group !important}.touch tbody.show-for-touch{display:table-row-group !important}tr.hide-for-touch{display:table-row !important}.touch tr.show-for-touch{display:table-row !important}td.hide-for-touch{display:table-cell !important}.touch td.show-for-touch{display:table-cell !important}th.hide-for-touch{display:table-cell !important}.touch th.show-for-touch{display:table-cell !important} diff --git a/vendor/foundation.min.js b/vendor/foundation.min.js deleted file mode 100644 index a6ba8c2..0000000 --- a/vendor/foundation.min.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Foundation Responsive Library - * http://foundation.zurb.com - * Copyright 2013, ZURB - * Free to use under the MIT license. - * http://www.opensource.org/licenses/mit-license.php -*/ -(function(e,t,n,r){"use strict";function i(e){var t,n=this;this.trackingClick=!1,this.trackingClickStart=0,this.targetElement=null,this.touchStartX=0,this.touchStartY=0,this.lastTouchIdentifier=0,this.touchBoundary=10,this.layer=e;if(!e||!e.nodeType)throw new TypeError("Layer must be a document node");this.onClick=function(){return i.prototype.onClick.apply(n,arguments)},this.onMouse=function(){return i.prototype.onMouse.apply(n,arguments)},this.onTouchStart=function(){return i.prototype.onTouchStart.apply(n,arguments)},this.onTouchMove=function(){return i.prototype.onTouchMove.apply(n,arguments)},this.onTouchEnd=function(){return i.prototype.onTouchEnd.apply(n,arguments)},this.onTouchCancel=function(){return i.prototype.onTouchCancel.apply(n,arguments)};if(i.notNeeded(e))return;this.deviceIsAndroid&&(e.addEventListener("mouseover",this.onMouse,!0),e.addEventListener("mousedown",this.onMouse,!0),e.addEventListener("mouseup",this.onMouse,!0)),e.addEventListener("click",this.onClick,!0),e.addEventListener("touchstart",this.onTouchStart,!1),e.addEventListener("touchmove",this.onTouchMove,!1),e.addEventListener("touchend",this.onTouchEnd,!1),e.addEventListener("touchcancel",this.onTouchCancel,!1),Event.prototype.stopImmediatePropagation||(e.removeEventListener=function(t,n,r){var i=Node.prototype.removeEventListener;t==="click"?i.call(e,t,n.hijacked||n,r):i.call(e,t,n,r)},e.addEventListener=function(t,n,r){var i=Node.prototype.addEventListener;t==="click"?i.call(e,t,n.hijacked||(n.hijacked=function(e){e.propagationStopped||n(e)}),r):i.call(e,t,n,r)}),typeof e.onclick=="function"&&(t=e.onclick,e.addEventListener("click",function(e){t(e)},!1),e.onclick=null)}function o(e){if(typeof e=="string"||e instanceof String)e=e.replace(/^[\\/'"]+|(;\s?})+|[\\/'"]+$/g,"");return e}e("head").has(".foundation-mq-small").length===0&&e("head").append(''),e("head").has(".foundation-mq-medium").length===0&&e("head").append(''),e("head").has(".foundation-mq-large").length===0&&e("head").append(''),e("head").has(".foundation-mq-xlarge").length===0&&e("head").append(''),e("head").has(".foundation-mq-xxlarge").length===0&&e("head").append(''),i.prototype.deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,i.prototype.deviceIsIOS=/iP(ad|hone|od)/.test(navigator.userAgent),i.prototype.deviceIsIOS4=i.prototype.deviceIsIOS&&/OS 4_\d(_\d)?/.test(navigator.userAgent),i.prototype.deviceIsIOSWithBadTarget=i.prototype.deviceIsIOS&&/OS ([6-9]|\d{2})_\d/.test(navigator.userAgent),i.prototype.needsClick=function(e){switch(e.nodeName.toLowerCase()){case"button":case"select":case"textarea":if(e.disabled)return!0;break;case"input":if(this.deviceIsIOS&&e.type==="file"||e.disabled)return!0;break;case"label":case"video":return!0}return/\bneedsclick\b/.test(e.className)},i.prototype.needsFocus=function(e){switch(e.nodeName.toLowerCase()){case"textarea":case"select":return!0;case"input":switch(e.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1}return!e.disabled&&!e.readOnly;default:return/\bneedsfocus\b/.test(e.className)}},i.prototype.sendClick=function(e,r){var i,s;n.activeElement&&n.activeElement!==e&&n.activeElement.blur(),s=r.changedTouches[0],i=n.createEvent("MouseEvents"),i.initMouseEvent("click",!0,!0,t,1,s.screenX,s.screenY,s.clientX,s.clientY,!1,!1,!1,!1,0,null),i.forwardedTouchEvent=!0,e.dispatchEvent(i)},i.prototype.focus=function(e){var t;this.deviceIsIOS&&e.setSelectionRange?(t=e.value.length,e.setSelectionRange(t,t)):e.focus()},i.prototype.updateScrollParent=function(e){var t,n;t=e.fastClickScrollParent;if(!t||!t.contains(e)){n=e;do{if(n.scrollHeight>n.offsetHeight){t=n,e.fastClickScrollParent=n;break}n=n.parentElement}while(n)}t&&(t.fastClickLastScrollTop=t.scrollTop)},i.prototype.getTargetElementFromEventTarget=function(e){return e.nodeType===Node.TEXT_NODE?e.parentNode:e},i.prototype.onTouchStart=function(e){var n,r,i;if(e.targetTouches.length>1)return!0;n=this.getTargetElementFromEventTarget(e.target),r=e.targetTouches[0];if(this.deviceIsIOS){i=t.getSelection();if(i.rangeCount&&!i.isCollapsed)return!0;if(!this.deviceIsIOS4){if(r.identifier===this.lastTouchIdentifier)return e.preventDefault(),!1;this.lastTouchIdentifier=r.identifier,this.updateScrollParent(n)}}return this.trackingClick=!0,this.trackingClickStart=e.timeStamp,this.targetElement=n,this.touchStartX=r.pageX,this.touchStartY=r.pageY,e.timeStamp-this.lastClickTime<200&&e.preventDefault(),!0},i.prototype.touchHasMoved=function(e){var t=e.changedTouches[0],n=this.touchBoundary;return Math.abs(t.pageX-this.touchStartX)>n||Math.abs(t.pageY-this.touchStartY)>n?!0:!1},i.prototype.onTouchMove=function(e){if(!this.trackingClick)return!0;if(this.targetElement!==this.getTargetElementFromEventTarget(e.target)||this.touchHasMoved(e))this.trackingClick=!1,this.targetElement=null;return!0},i.prototype.findControl=function(e){return e.control!==r?e.control:e.htmlFor?n.getElementById(e.htmlFor):e.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")},i.prototype.onTouchEnd=function(e){var r,i,s,o,u,a=this.targetElement;if(!this.trackingClick)return!0;if(e.timeStamp-this.lastClickTime<200)return this.cancelNextClick=!0,!0;this.lastClickTime=e.timeStamp,i=this.trackingClickStart,this.trackingClick=!1,this.trackingClickStart=0,this.deviceIsIOSWithBadTarget&&(u=e.changedTouches[0],a=n.elementFromPoint(u.pageX-t.pageXOffset,u.pageY-t.pageYOffset)||a,a.fastClickScrollParent=this.targetElement.fastClickScrollParent),s=a.tagName.toLowerCase();if(s==="label"){r=this.findControl(a);if(r){this.focus(a);if(this.deviceIsAndroid)return!1;a=r}}else if(this.needsFocus(a)){if(e.timeStamp-i>100||this.deviceIsIOS&&t.top!==t&&s==="input")return this.targetElement=null,!1;this.focus(a);if(!this.deviceIsIOS4||s!=="select")this.targetElement=null,e.preventDefault();return!1}if(this.deviceIsIOS&&!this.deviceIsIOS4){o=a.fastClickScrollParent;if(o&&o.fastClickLastScrollTop!==o.scrollTop)return!0}return this.needsClick(a)||(e.preventDefault(),this.sendClick(a,e)),!1},i.prototype.onTouchCancel=function(){this.trackingClick=!1,this.targetElement=null},i.prototype.onMouse=function(e){return this.targetElement?e.forwardedTouchEvent?!0:e.cancelable?!this.needsClick(this.targetElement)||this.cancelNextClick?(e.stopImmediatePropagation?e.stopImmediatePropagation():e.propagationStopped=!0,e.stopPropagation(),e.preventDefault(),!1):!0:!0:!0},i.prototype.onClick=function(e){var t;return this.trackingClick?(this.targetElement=null,this.trackingClick=!1,!0):e.target.type==="submit"&&e.detail===0?!0:(t=this.onMouse(e),t||(this.targetElement=null),t)},i.prototype.destroy=function(){var e=this.layer;this.deviceIsAndroid&&(e.removeEventListener("mouseover",this.onMouse,!0),e.removeEventListener("mousedown",this.onMouse,!0),e.removeEventListener("mouseup",this.onMouse,!0)),e.removeEventListener("click",this.onClick,!0),e.removeEventListener("touchstart",this.onTouchStart,!1),e.removeEventListener("touchmove",this.onTouchMove,!1),e.removeEventListener("touchend",this.onTouchEnd,!1),e.removeEventListener("touchcancel",this.onTouchCancel,!1)},i.notNeeded=function(e){var r;if(typeof t.ontouchstart=="undefined")return!0;if(/Chrome\/[0-9]+/.test(navigator.userAgent)){if(!i.prototype.deviceIsAndroid)return!0;r=n.querySelector("meta[name=viewport]");if(r&&r.content.indexOf("user-scalable=no")!==-1)return!0}return e.style.msTouchAction==="none"?!0:!1},i.attach=function(e){return new i(e)},typeof define!="undefined"&&define.amd?define(function(){return i}):typeof module!="undefined"&&module.exports?(module.exports=i.attach,module.exports.FastClick=i):t.FastClick=i,typeof i!="undefined"&&i.attach(n.body);var s=function(t,r){return typeof t=="string"?r?e(r.querySelectorAll(t)):e(n.querySelectorAll(t)):e(t,r)};t.matchMedia=t.matchMedia||function(e,t){var n,r=e.documentElement,i=r.firstElementChild||r.firstChild,s=e.createElement("body"),o=e.createElement("div");return o.id="mq-test-1",o.style.cssText="position:absolute;top:-100em",s.style.background="none",s.appendChild(o),function(e){return o.innerHTML='­',r.insertBefore(s,i),n=o.offsetWidth===42,r.removeChild(s),{matches:n,media:e}}}(n),function(e){function u(){n&&(s(u),jQuery.fx.tick())}var n,r=0,i=["webkit","moz"],s=t.requestAnimationFrame,o=t.cancelAnimationFrame;for(;r").appendTo("head")[0].sheet,init:function(e,t,n,r,i){var o,u=[e,n,r,i],a=[];this.rtl=/rtl/i.test(s("html").attr("dir")),this.scope=e||this.scope;if(t&&typeof t=="string"&&!/reflow/i.test(t))this.libs.hasOwnProperty(t)&&a.push(this.init_lib(t,u));else for(var f in this.libs)a.push(this.init_lib(f,t));return e},init_lib:function(e,t){return this.libs.hasOwnProperty(e)?(this.patch(this.libs[e]),t&&t.hasOwnProperty(e)?this.libs[e].init.apply(this.libs[e],[this.scope,t[e]]):this.libs[e].init.apply(this.libs[e],t)):function(){}},patch:function(e){e.scope=this.scope,e.data_options=this.lib_methods.data_options,e.bindings=this.lib_methods.bindings,e.S=s,e.rtl=this.rtl},inherit:function(e,t){var n=t.split(" ");for(var r=n.length-1;r>=0;r--)this.lib_methods.hasOwnProperty(n[r])&&(this.libs[e.name][n[r]]=this.lib_methods[n[r]])},random_str:function(e){var t="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("");e||(e=Math.floor(Math.random()*t.length));var n="";for(var r=0;r=0;r--)i=s[r].split(":"),/true/i.test(i[1])&&(i[1]=!0),/false/i.test(i[1])&&(i[1]=!1),a(i[1])&&(i[1]=parseInt(i[1],10)),i.length===2&&i[0].length>0&&(n[f(i[0])]=f(i[1]));return n},delay:function(e,t){return setTimeout(e,t)},empty:function(e){if(e.length&&e.length>0)return!1;if(e.length&&e.length===0)return!0;for(var t in e)if(hasOwnProperty.call(e,t))return!1;return!0},register_media:function(t,n){Foundation.media_queries[t]===r&&(e("head").append(''),Foundation.media_queries[t]=o(e("."+n).css("font-family")))},addCustomRule:function(e,t){if(t===r)Foundation.stylesheet.insertRule(e,Foundation.stylesheet.cssRules.length);else{var n=Foundation.media_queries[t];n!==r&&Foundation.stylesheet.insertRule("@media "+Foundation.media_queries[t]+"{ "+e+" }")}},loaded:function(e,t){function n(){t(e[0])}function r(){this.one("load",n);if(/MSIE (\d+\.\d+);/.test(navigator.userAgent)){var e=this.attr("src"),t=e.match(/\?/)?"&":"?";t+="random="+(new Date).getTime(),this.attr("src",e+t)}}if(!e.attr("src")){n();return}e[0].complete||e[0].readyState===4?n():r.call(e)},bindings:function(t,n){var r=this,i=!s(this).data(this.name+"-init");if(typeof t=="string")return this[t].call(this);s(this.scope).is("[data-"+this.name+"]")?(s(this.scope).data(this.name+"-init",e.extend({},this.settings,n||t,this.data_options(s(this.scope)))),i&&this.events(this.scope)):s("[data-"+this.name+"]",this.scope).each(function(){var i=!s(this).data(r.name+"-init");s(this).data(r.name+"-init",e.extend({},r.settings,n||t,r.data_options(s(this)))),i&&r.events(this)})}}},e.fn.foundation=function(){var e=Array.prototype.slice.call(arguments,0);return this.each(function(){return Foundation.init.apply(Foundation,[this].concat(e)),this})}})(jQuery,this,this.document),function(e,t,n,r){"use strict";var i=i||!1;Foundation.libs.joyride={name:"joyride",version:"5.0.0",defaults:{expose:!1,modal:!0,tip_location:"bottom",nub_position:"auto",scroll_speed:1500,scroll_animation:"linear",timer:0,start_timer_on_click:!0,start_offset:0,next_button:!0,tip_animation:"fade",pause_after:[],exposed:[],tip_animation_fade_speed:300,cookie_monster:!1,cookie_name:"joyride",cookie_domain:!1,cookie_expires:365,tip_container:"body",tip_location_patterns:{top:["bottom"],bottom:[],left:["right","top","bottom"],right:["left","top","bottom"]},post_ride_callback:function(){},post_step_callback:function(){},pre_step_callback:function(){},pre_ride_callback:function(){},post_expose_callback:function(){},template:{link:'×',timer:'
      ',tip:'
      ',wrapper:'
      ',button:'',modal:'
      ',expose:'
      ',expose_cover:'
      '},expose_add_class:""},init:function(e,t,n){Foundation.inherit(this,"throttle delay"),this.settings=this.defaults,this.bindings(t,n)},events:function(){var n=this;e(this.scope).off(".joyride").on("click.fndtn.joyride",".joyride-next-tip, .joyride-modal-bg",function(e){e.preventDefault(),this.settings.$li.next().length<1?this.end():this.settings.timer>0?(clearTimeout(this.settings.automate),this.hide(),this.show(),this.startTimer()):(this.hide(),this.show())}.bind(this)).on("click.fndtn.joyride",".joyride-close-tip",function(e){e.preventDefault(),this.end()}.bind(this)),e(t).off(".joyride").on("resize.fndtn.joyride",n.throttle(function(){if(e("[data-joyride]").length>0&&n.settings.$next_tip){if(n.settings.exposed.length>0){var t=e(n.settings.exposed);t.each(function(){var t=e(this);n.un_expose(t),n.expose(t)})}n.is_phone()?n.pos_phone():n.pos_default(!1,!0)}},100))},start:function(){var t=this,n=e("[data-joyride]",this.scope),r=["timer","scrollSpeed","startOffset","tipAnimationFadeSpeed","cookieExpires"],i=r.length;if(!n.length>0)return;this.settings.init||this.events(),this.settings=n.data("joyride-init"),this.settings.$content_el=n,this.settings.$body=e(this.settings.tip_container),this.settings.body_offset=e(this.settings.tip_container).position(),this.settings.$tip_content=this.settings.$content_el.find("> li"),this.settings.paused=!1,this.settings.attempts=0,typeof e.cookie!="function"&&(this.settings.cookie_monster=!1);if(!this.settings.cookie_monster||this.settings.cookie_monster&&e.cookie(this.settings.cookie_name)===null)this.settings.$tip_content.each(function(n){var s=e(this);this.settings=e.extend({},t.defaults,t.data_options(s));for(var o=i-1;o>=0;o--)t.settings[r[o]]=parseInt(t.settings[r[o]],10);t.create({$li:s,index:n})}),!this.settings.start_timer_on_click&&this.settings.timer>0?(this.show("init"),this.startTimer()):this.show("init")},resume:function(){this.set_li(),this.show()},tip_template:function(t){var n,r;return t.tip_class=t.tip_class||"",n=e(this.settings.template.tip).addClass(t.tip_class),r=e.trim(e(t.li).html())+this.button_text(t.button_text)+this.settings.template.link+this.timer_instance(t.index),n.append(e(this.settings.template.wrapper)),n.first().attr("data-index",t.index),e(".joyride-content-wrapper",n).append(r),n[0]},timer_instance:function(t){var n;return t===0&&this.settings.start_timer_on_click&&this.settings.timer>0||this.settings.timer===0?n="":n=e(this.settings.template.timer)[0].outerHTML,n},button_text:function(t){return this.settings.next_button?(t=e.trim(t)||"Next",t=e(this.settings.template.button).append(t)[0].outerHTML):t="",t},create:function(t){var n=t.$li.attr("data-button")||t.$li.attr("data-text"),r=t.$li.attr("class"),i=e(this.tip_template({tip_class:r,index:t.index,button_text:n,li:t.$li}));e(this.settings.tip_container).append(i)},show:function(t){var n=null;this.settings.$li===r||e.inArray(this.settings.$li.index(),this.settings.pause_after)===-1?(this.settings.paused?this.settings.paused=!1:this.set_li(t),this.settings.attempts=0,this.settings.$li.length&&this.settings.$target.length>0?(t&&(this.settings.pre_ride_callback(this.settings.$li.index(),this.settings.$next_tip),this.settings.modal&&this.show_modal()),this.settings.pre_step_callback(this.settings.$li.index(),this.settings.$next_tip),this.settings.modal&&this.settings.expose&&this.expose(),this.settings.tip_settings=e.extend({},this.settings,this.data_options(this.settings.$li)),this.settings.timer=parseInt(this.settings.timer,10),this.settings.tip_settings.tip_location_pattern=this.settings.tip_location_patterns[this.settings.tip_settings.tip_location],/body/i.test(this.settings.$target.selector)||this.scroll_to(),this.is_phone()?this.pos_phone(!0):this.pos_default(!0),n=this.settings.$next_tip.find(".joyride-timer-indicator"),/pop/i.test(this.settings.tip_animation)?(n.width(0),this.settings.timer>0?(this.settings.$next_tip.show(),this.delay(function(){n.animate({width:n.parent().width()},this.settings.timer,"linear")}.bind(this),this.settings.tip_animation_fade_speed)):this.settings.$next_tip.show()):/fade/i.test(this.settings.tip_animation)&&(n.width(0),this.settings.timer>0?(this.settings.$next_tip.fadeIn(this.settings.tip_animation_fade_speed).show(),this.delay(function(){n.animate({width:n.parent().width()},this.settings.timer,"linear")}.bind(this),this.settings.tip_animation_fadeSpeed)):this.settings.$next_tip.fadeIn(this.settings.tip_animation_fade_speed)),this.settings.$current_tip=this.settings.$next_tip):this.settings.$li&&this.settings.$target.length<1?this.show():this.end()):this.settings.paused=!0},is_phone:function(){return matchMedia(Foundation.media_queries.small).matches&&!matchMedia(Foundation.media_queries.medium).matches},hide:function(){this.settings.modal&&this.settings.expose&&this.un_expose(),this.settings.modal||e(".joyride-modal-bg").hide(),this.settings.$current_tip.css("visibility","hidden"),setTimeout(e.proxy(function(){this.hide(),this.css("visibility","visible")},this.settings.$current_tip),0),this.settings.post_step_callback(this.settings.$li.index(),this.settings.$current_tip)},set_li:function(e){e?(this.settings.$li=this.settings.$tip_content.eq(this.settings.start_offset),this.set_next_tip(),this.settings.$current_tip=this.settings.$next_tip):(this.settings.$li=this.settings.$li.next(),this.set_next_tip()),this.set_target()},set_next_tip:function(){this.settings.$next_tip=e(".joyride-tip-guide").eq(this.settings.$li.index()),this.settings.$next_tip.data("closed","")},set_target:function(){var t=this.settings.$li.attr("data-class"),r=this.settings.$li.attr("data-id"),i=function(){return r?e(n.getElementById(r)):t?e("."+t).first():e("body")};this.settings.$target=i()},scroll_to:function(){var n,r;n=e(t).height()/2,r=Math.ceil(this.settings.$target.offset().top-n+this.settings.$next_tip.outerHeight()),r>0&&e("html, body").animate({scrollTop:r},this.settings.scroll_speed,"swing")},paused:function(){return e.inArray(this.settings.$li.index()+1,this.settings.pause_after)===-1},restart:function(){this.hide(),this.settings.$li=r,this.show("init")},pos_default:function(n,r){var i=Math.ceil(e(t).height()/2),s=this.settings.$next_tip.offset(),o=this.settings.$next_tip.find(".joyride-nub"),u=Math.ceil(o.outerWidth()/2),a=Math.ceil(o.outerHeight()/2),f=n||!1;f&&(this.settings.$next_tip.css("visibility","hidden"),this.settings.$next_tip.show()),typeof r=="undefined"&&(r=!1);if(!/body/i.test(this.settings.$target.selector)){if(this.bottom()){var l=this.settings.$target.offset().left;Foundation.rtl&&(l=this.settings.$target.offset().width-this.settings.$next_tip.width()+l),this.settings.$next_tip.css({top:this.settings.$target.offset().top+a+this.settings.$target.outerHeight(),left:l}),this.nub_position(o,this.settings.tip_settings.nub_position,"top")}else if(this.top()){var l=this.settings.$target.offset().left;Foundation.rtl&&(l=this.settings.$target.offset().width-this.settings.$next_tip.width()+l),this.settings.$next_tip.css({top:this.settings.$target.offset().top-this.settings.$next_tip.outerHeight()-a,left:l}),this.nub_position(o,this.settings.tip_settings.nub_position,"bottom")}else this.right()?(this.settings.$next_tip.css({top:this.settings.$target.offset().top,left:this.outerWidth(this.settings.$target)+this.settings.$target.offset().left+u}),this.nub_position(o,this.settings.tip_settings.nub_position,"left")):this.left()&&(this.settings.$next_tip.css({top:this.settings.$target.offset().top,left:this.settings.$target.offset().left-this.outerWidth(this.settings.$next_tip)-u}),this.nub_position(o,this.settings.tip_settings.nub_position,"right"));!this.visible(this.corners(this.settings.$next_tip))&&this.settings.attempts0&&arguments[0]instanceof e)i=arguments[0];else{if(!this.settings.$target||!!/body/i.test(this.settings.$target.selector))return!1;i=this.settings.$target}if(i.length<1)return t.console&&console.error("element not valid",i),!1;n=e(this.settings.template.expose),this.settings.$body.append(n),n.css({top:i.offset().top,left:i.offset().left,width:i.outerWidth(!0),height:i.outerHeight(!0)}),r=e(this.settings.template.expose_cover),s={zIndex:i.css("z-index"),position:i.css("position")},o=i.attr("class")==null?"":i.attr("class"),i.css("z-index",parseInt(n.css("z-index"))+1),s.position=="static"&&i.css("position","relative"),i.data("expose-css",s),i.data("orig-class",o),i.attr("class",o+" "+this.settings.expose_add_class),r.css({top:i.offset().top,left:i.offset().left,width:i.outerWidth(!0),height:i.outerHeight(!0)}),this.settings.modal&&this.show_modal(),this.settings.$body.append(r),n.addClass(u),r.addClass(u),i.data("expose",u),this.settings.post_expose_callback(this.settings.$li.index(),this.settings.$next_tip,i),this.add_exposed(i)},un_expose:function(){var n,r,i,s,o,u=!1;if(arguments.length>0&&arguments[0]instanceof e)r=arguments[0];else{if(!this.settings.$target||!!/body/i.test(this.settings.$target.selector))return!1;r=this.settings.$target}if(r.length<1)return t.console&&console.error("element not valid",r),!1;n=r.data("expose"),i=e("."+n),arguments.length>1&&(u=arguments[1]),u===!0?e(".joyride-expose-wrapper,.joyride-expose-cover").remove():i.remove(),s=r.data("expose-css"),s.zIndex=="auto"?r.css("z-index",""):r.css("z-index",s.zIndex),s.position!=r.css("position")&&(s.position=="static"?r.css("position",""):r.css("position",s.position)),o=r.data("orig-class"),r.attr("class",o),r.removeData("orig-classes"),r.removeData("expose"),r.removeData("expose-z-index"),this.remove_exposed(r)},add_exposed:function(t){this.settings.exposed=this.settings.exposed||[],t instanceof e||typeof t=="object"?this.settings.exposed.push(t[0]):typeof t=="string"&&this.settings.exposed.push(t)},remove_exposed:function(t){var n,r;t instanceof e?n=t[0]:typeof t=="string"&&(n=t),this.settings.exposed=this.settings.exposed||[],r=this.settings.exposed.length;for(var i=0;ia&&(a=u),[n.offset().topn.offset().left]},visible:function(e){var t=e.length;while(t--)if(e[t])return!1;return!0},nub_position:function(e,t,n){t==="auto"?e.addClass(n):e.addClass(t)},startTimer:function(){this.settings.$li.length?this.settings.automate=setTimeout(function(){this.hide(),this.show(),this.startTimer()}.bind(this),this.settings.timer):clearTimeout(this.settings.automate)},end:function(){this.settings.cookie_monster&&e.cookie(this.settings.cookie_name,"ridden",{expires:this.settings.cookie_expires,domain:this.settings.cookie_domain}),this.settings.timer>0&&clearTimeout(this.settings.automate),this.settings.modal&&this.settings.expose&&this.un_expose(),this.settings.$next_tip.data("closed",!0),e(".joyride-modal-bg").hide(),this.settings.$current_tip.hide(),this.settings.post_step_callback(this.settings.$li.index(),this.settings.$current_tip),this.settings.post_ride_callback(this.settings.$li.index(),this.settings.$current_tip),e(".joyride-tip-guide").remove()},off:function(){e(this.scope).off(".joyride"),e(t).off(".joyride"),e(".joyride-close-tip, .joyride-next-tip, .joyride-modal-bg").off(".joyride"),e(".joyride-tip-guide, .joyride-modal-bg").remove(),clearTimeout(this.settings.automate),this.settings={}},reflow:function(){}}}(jQuery,this,this.document),function(e,t,n,r){"use strict";Foundation.libs.dropdown={name:"dropdown",version:"5.0.0",settings:{active_class:"open",is_hover:!1,opened:function(){},closed:function(){}},init:function(e,t,n){Foundation.inherit(this,"throttle"),this.bindings(t,n)},events:function(n){var r=this;e(this.scope).off(".dropdown").on("click.fndtn.dropdown","[data-dropdown]",function(t){var n=e(this).data("dropdown-init");t.preventDefault(),(!n.is_hover||Modernizr.touch)&&r.toggle(e(this))}).on("mouseenter.fndtn.dropdown","[data-dropdown], [data-dropdown-content]",function(t){var n=e(this);clearTimeout(r.timeout);if(n.data("dropdown"))var i=e("#"+n.data("dropdown")),s=n;else{var i=n;s=e("[data-dropdown='"+i.attr("id")+"']")}var o=s.data("dropdown-init");o.is_hover&&r.open.apply(r,[i,s])}).on("mouseleave.fndtn.dropdown","[data-dropdown], [data-dropdown-content]",function(t){var n=e(this);r.timeout=setTimeout(function(){if(n.data("dropdown")){var t=n.data("dropdown-init");t.is_hover&&r.close.call(r,e("#"+n.data("dropdown")))}else{var i=e('[data-dropdown="'+e(this).attr("id")+'"]'),t=i.data("dropdown-init");t.is_hover&&r.close.call(r,n)}}.bind(this),150)}).on("click.fndtn.dropdown",function(t){var n=e(t.target).closest("[data-dropdown-content]");if(e(t.target).data("dropdown")||e(t.target).parent().data("dropdown"))return;if(!e(t.target).data("revealId")&&n.length>0&&(e(t.target).is("[data-dropdown-content]")||e.contains(n.first()[0],t.target))){t.stopPropagation();return}r.close.call(r,e("[data-dropdown-content]"))}).on("opened.fndtn.dropdown","[data-dropdown-content]",this.settings.opened).on("closed.fndtn.dropdown","[data-dropdown-content]",this.settings.closed),e(t).off(".dropdown").on("resize.fndtn.dropdown",r.throttle(function(){r.resize.call(r)},50)).trigger("resize")},close:function(t){var n=this;t.each(function(){e(this).hasClass(n.settings.active_class)&&(e(this).css(Foundation.rtl?"right":"left","-99999px").removeClass(n.settings.active_class),e(this).trigger("closed"))})},open:function(e,t){this.css(e.addClass(this.settings.active_class),t),e.trigger("opened")},toggle:function(t){var n=e("#"+t.data("dropdown"));if(n.length===0)return;this.close.call(this,e("[data-dropdown-content]").not(n)),n.hasClass(this.settings.active_class)?this.close.call(this,n):(this.close.call(this,e("[data-dropdown-content]")),this.open.call(this,n,t))},resize:function(){var t=e("[data-dropdown-content].open"),n=e("[data-dropdown='"+t.attr("id")+"']");t.length&&n.length&&this.css(t,n)},css:function(n,r){var i=n.offsetParent(),s=r.offset();s.top-=i.offset().top,s.left-=i.offset().left;if(this.small())n.css({position:"absolute",width:"95%","max-width":"none",top:s.top+r.outerHeight()}),n.css(Foundation.rtl?"right":"left","2.5%");else{if(!Foundation.rtl&&e(t).width()>n.outerWidth()+r.offset().left){var o=s.left;n.hasClass("right")&&n.removeClass("right")}else{n.hasClass("right")||n.addClass("right");var o=s.left-(n.outerWidth()-r.outerWidth())}n.attr("style","").css({position:"absolute",top:s.top+r.outerHeight(),left:o})}return n},small:function(){return matchMedia(Foundation.media_queries.small).matches&&!matchMedia(Foundation.media_queries.medium).matches},off:function(){e(this.scope).off(".fndtn.dropdown"),e("html, body").off(".fndtn.dropdown"),e(t).off(".fndtn.dropdown"),e("[data-dropdown-content]").off(".fndtn.dropdown"),this.settings.init=!1},reflow:function(){}}}(jQuery,this,this.document),function(e,t,n,r){"use strict";Foundation.libs.clearing={name:"clearing",version:"5.0.0",settings:{templates:{viewing:'×'},close_selectors:".clearing-close",init:!1,locked:!1},init:function(t,n,r){var i=this;Foundation.inherit(this,"throttle loaded"),this.bindings(n,r),e(this.scope).is("[data-clearing]")?this.assemble(e("li",this.scope)):e("[data-clearing]",this.scope).each(function(){i.assemble(e("li",this))})},events:function(n){var r=this;e(this.scope).off(".clearing").on("click.fndtn.clearing","ul[data-clearing] li",function(t,n,i){var n=n||e(this),i=i||n,s=n.next("li"),o=n.closest("[data-clearing]").data("clearing-init"),u=e(t.target);t.preventDefault(),o||(r.init(),o=n.closest("[data-clearing]").data("clearing-init")),i.hasClass("visible")&&n[0]===i[0]&&s.length>0&&r.is_open(n)&&(i=s,u=e("img",i)),r.open(u,n,i),r.update_paddles(i)}).on("click.fndtn.clearing",".clearing-main-next",function(e){r.nav(e,"next")}).on("click.fndtn.clearing",".clearing-main-prev",function(e){r.nav(e,"prev")}).on("click.fndtn.clearing",this -.settings.close_selectors,function(e){Foundation.libs.clearing.close(e,this)}).on("keydown.fndtn.clearing",function(e){r.keydown(e)}),e(t).off(".clearing").on("resize.fndtn.clearing",function(){r.resize()}),this.swipe_events(n)},swipe_events:function(t){var n=this;e(this.scope).on("touchstart.fndtn.clearing",".visible-img",function(t){t.touches||(t=t.originalEvent);var n={start_page_x:t.touches[0].pageX,start_page_y:t.touches[0].pageY,start_time:(new Date).getTime(),delta_x:0,is_scrolling:r};e(this).data("swipe-transition",n),t.stopPropagation()}).on("touchmove.fndtn.clearing",".visible-img",function(t){t.touches||(t=t.originalEvent);if(t.touches.length>1||t.scale&&t.scale!==1)return;var r=e(this).data("swipe-transition");typeof r=="undefined"&&(r={}),r.delta_x=t.touches[0].pageX-r.start_page_x,typeof r.is_scrolling=="undefined"&&(r.is_scrolling=!!(r.is_scrolling||Math.abs(r.delta_x)');var r=e("#foundationClearingHolder"),i=n.data("clearing-init"),s=n.detach(),o={grid:'",viewing:i.templates.viewing},u='
      '+o.viewing+o.grid+"
      ";return r.after(u).remove()},open:function(t,n,r){var i=r.closest(".clearing-assembled"),s=e("div",i).first(),o=e(".visible-img",s),u=e("img",o).not(t);this.locked()||(u.attr("src",this.load(t)).css("visibility","hidden"),this.loaded(u,function(){u.css("visibility","visible"),i.addClass("clearing-blackout"),s.addClass("clearing-container"),o.show(),this.fix_height(r).caption(e(".clearing-caption",o),t).center(u).shift(n,r,function(){r.siblings().removeClass("visible"),r.addClass("visible")})}.bind(this)))},close:function(t,n){t.preventDefault();var r=function(e){return/blackout/.test(e.selector)?e:e.closest(".clearing-blackout")}(e(n)),i,s;return n===t.target&&r&&(i=e("div",r).first(),s=e(".visible-img",i),this.settings.prev_index=0,e("ul[data-clearing]",r).attr("style","").closest(".clearing-blackout").removeClass("clearing-blackout"),i.removeClass("clearing-container"),s.hide()),!1},is_open:function(e){return e.parent().prop("style").length>0},keydown:function(t){var n=e("ul[data-clearing]",".clearing-blackout");t.which===39&&this.go(n,"next"),t.which===37&&this.go(n,"prev"),t.which===27&&e("a.clearing-close").trigger("click")},nav:function(t,n){var r=e("ul[data-clearing]",".clearing-blackout");t.preventDefault(),this.go(r,n)},resize:function(){var t=e("img",".clearing-blackout .visible-img");t.length&&this.center(t)},fix_height:function(t){var n=t.parent().children(),r=this;return n.each(function(){var t=e(this),n=t.find("img");t.height()>n.outerHeight()&&t.addClass("fix-height")}).closest("ul").width(n.length*100+"%"),this},update_paddles:function(t){var n=t.closest(".carousel").siblings(".visible-img");t.next().length>0?e(".clearing-main-next",n).removeClass("disabled"):e(".clearing-main-next",n).addClass("disabled"),t.prev().length>0?e(".clearing-main-prev",n).removeClass("disabled"):e(".clearing-main-prev",n).addClass("disabled")},center:function(e){return this.rtl?e.css({marginRight:-(e.outerWidth()/2),marginTop:-(e.outerHeight()/2)}):e.css({marginLeft:-(e.outerWidth()/2),marginTop:-(e.outerHeight()/2)}),this},load:function(e){if(e[0].nodeName==="A")var t=e.attr("href");else var t=e.parent().attr("href");return this.preload(e),t?t:e.attr("src")},preload:function(e){this.img(e.closest("li").next()).img(e.closest("li").prev())},img:function(t){if(t.length){var n=new Image,r=e("a",t);r.length?n.src=r.attr("href"):n.src=e("img",t).attr("src")}return this},caption:function(e,t){var n=t.data("caption");return n?e.html(n).show():e.text("").hide(),this},go:function(t,n){var r=e(".visible",t),i=r[n]();i.length&&e("img",i).trigger("click",[r,i])},shift:function(e,t,n){var r=t.parent(),i=this.settings.prev_index||t.index(),s=this.direction(r,e,t),o=parseInt(r.css("left"),10),u=t.outerWidth(),a;t.index()!==i&&!/skip/.test(s)?/left/.test(s)?(this.lock(),r.animate({left:o+u},300,this.unlock())):/right/.test(s)&&(this.lock(),r.animate({left:o-u},300,this.unlock())):/skip/.test(s)&&(a=t.index()-this.settings.up_count,this.lock(),a>0?r.animate({left:-(a*u)},300,this.unlock()):r.animate({left:0},300,this.unlock())),n()},direction:function(t,n,r){var i=e("li",t),s=i.outerWidth()+i.outerWidth()/4,o=Math.floor(e(".clearing-container").outerWidth()/s)-1,u=i.index(r),a;return this.settings.up_count=o,this.adjacent(this.settings.prev_index,u)?u>o&&u>this.settings.prev_index?a="right":u>o-1&&u<=this.settings.prev_index?a="left":a=!1:a="skip",this.settings.prev_index=u,a},adjacent:function(e,t){for(var n=t+1;n>=t-1;n--)if(n===e)return!0;return!1},lock:function(){this.settings.locked=!0},unlock:function(){this.settings.locked=!1},locked:function(){return this.settings.locked},off:function(){e(this.scope).off(".fndtn.clearing"),e(t).off(".fndtn.clearing")},reflow:function(){this.init()}}}(jQuery,this,this.document),function(e,t,n,r){"use strict";var i=function(){},s=function(i,s){if(i.hasClass(s.slides_container_class))return this;var f=this,l,c=i,h,p,d,v=0,m,g,y=!1,b=!1;c.children().first().addClass(s.active_slide_class),f.update_slide_number=function(t){s.slide_number&&(h.find("span:first").text(parseInt(t)+1),h.find("span:last").text(c.children().length)),s.bullets&&(p.children().removeClass(s.bullets_active_class),e(p.children().get(t)).addClass(s.bullets_active_class))},f.update_active_link=function(t){var n=e('a[data-orbit-link="'+c.children().eq(t).attr("data-orbit-slide")+'"]');n.parents("ul").find("[data-orbit-link]").removeClass(s.bullets_active_class),n.addClass(s.bullets_active_class)},f.build_markup=function(){c.wrap('
      '),l=c.parent(),c.addClass(s.slides_container_class),s.navigation_arrows&&(l.append(e('').addClass(s.prev_class)),l.append(e('').addClass(s.next_class))),s.timer&&(d=e("
      ").addClass(s.timer_container_class),d.append(""),d.append(e("
      ").addClass(s.timer_progress_class)),d.addClass(s.timer_paused_class),l.append(d)),s.slide_number&&(h=e("
      ").addClass(s.slide_number_class),h.append(" "+s.slide_number_text+" "),l.append(h)),s.bullets&&(p=e("
        ").addClass(s.bullets_container_class),l.append(p),p.wrap('
        '),c.children().each(function(t,n){var r=e("
      1. ").attr("data-orbit-slide",t);p.append(r)})),s.stack_on_small&&l.addClass(s.stack_on_small_class),f.update_slide_number(0),f.update_active_link(0)},f._goto=function(t,n){if(t===v)return!1;typeof g=="object"&&g.restart();var r=c.children(),i="next";y=!0,t=r.length?t=0:t<0&&(t=r.length-1);var o=e(r.get(v)),u=e(r.get(t));o.css("zIndex",2),o.removeClass(s.active_slide_class),u.css("zIndex",4).addClass(s.active_slide_class),c.trigger("before-slide-change.fndtn.orbit"),s.before_slide_change(),f.update_active_link(t);var a=function(){var e=function(){v=t,y=!1,n===!0&&(g=f.create_timer(),g.start()),f.update_slide_number(v),c.trigger("after-slide-change.fndtn.orbit",[{slide_number:v,total_slides:r.length}]),s.after_slide_change(v,r.length)};c.height()!=u.height()&&s.variable_height?c.animate({height:u.height()},250,"linear",e):e()};if(r.length===1)return a(),!1;var l=function(){i==="next"&&m.next(o,u,a),i==="prev"&&m.prev(o,u,a)};u.height()>c.height()&&s.variable_height?c.animate({height:u.height()},250,"linear",l):l()},f.next=function(e){e.stopImmediatePropagation(),e.preventDefault(),f._goto(v+1)},f.prev=function(e){e.stopImmediatePropagation(),e.preventDefault(),f._goto(v-1)},f.link_custom=function(t){t.preventDefault();var n=e(this).attr("data-orbit-link");if(typeof n=="string"&&(n=e.trim(n))!=""){var r=l.find("[data-orbit-slide="+n+"]");r.index()!=-1&&f._goto(r.index())}},f.link_bullet=function(t){var n=e(this).attr("data-orbit-slide");typeof n=="string"&&(n=e.trim(n))!=""&&f._goto(parseInt(n))},f.timer_callback=function(){f._goto(v+1,!0)},f.compute_dimensions=function(){var t=e(c.children().get(v)),n=t.height();s.variable_height||c.children().each(function(){e(this).height()>n&&(n=e(this).height())}),c.height(n)},f.create_timer=function(){var e=new o(l.find("."+s.timer_container_class),s,f.timer_callback);return e},f.stop_timer=function(){typeof g=="object"&&g.stop()},f.toggle_timer=function(){var e=l.find("."+s.timer_container_class);e.hasClass(s.timer_paused_class)?(typeof g=="undefined"&&(g=f.create_timer()),g.start()):typeof g=="object"&&g.stop()},f.init=function(){f.build_markup(),s.timer&&(g=f.create_timer(),g.start()),m=new a(s,c),s.animation==="slide"&&(m=new u(s,c)),l.on("click","."+s.next_class,f.next),l.on("click","."+s.prev_class,f.prev),l.on("click","[data-orbit-slide]",f.link_bullet),l.on("click",f.toggle_timer),s.swipe&&l.on("touchstart.fndtn.orbit",function(e){e.touches||(e=e.originalEvent);var t={start_page_x:e.touches[0].pageX,start_page_y:e.touches[0].pageY,start_time:(new Date).getTime(),delta_x:0,is_scrolling:r};l.data("swipe-transition",t),e.stopPropagation()}).on("touchmove.fndtn.orbit",function(e){e.touches||(e=e.originalEvent);if(e.touches.length>1||e.scale&&e.scale!==1)return;var t=l.data("swipe-transition");typeof t=="undefined"&&(t={}),t.delta_x=e.touches[0].pageX-t.start_page_x,typeof t.is_scrolling=="undefined"&&(t.is_scrolling=!!(t.is_scrolling||Math.abs(t.delta_x)0?e(this.scope).on("open.fndtn.reveal",this.settings.open).on("opened.fndtn.reveal",this.settings.opened).on("opened.fndtn.reveal",this.open_video).on("close.fndtn.reveal",this.settings.close).on("closed.fndtn.reveal",this.settings.closed).on("closed.fndtn.reveal",this.close_video):e(this.scope).on("open.fndtn.reveal","[data-reveal]",this.settings.open).on("opened.fndtn.reveal","[data-reveal]",this.settings.opened).on("opened.fndtn.reveal","[data-reveal]",this.open_video).on("close.fndtn.reveal","[data-reveal]",this.settings.close).on("closed.fndtn.reveal","[data-reveal]",this.settings.closed).on("closed.fndtn.reveal","[data-reveal]",this.close_video),e("body").on("keyup.fndtn.reveal",function(t){var n=e("[data-reveal].open"),r=n.data("reveal-init");t.which===27&&r.close_on_esc&&n.foundation("reveal","close")}),!0},open:function(t,n){if(t)if(typeof t.selector!="undefined")var r=e("#"+t.data("reveal-id"));else{var r=e(this.scope);n=t}else var r=e(this.scope);if(!r.hasClass("open")){var i=e("[data-reveal].open");typeof r.data("css-top")=="undefined"&&r.data("css-top",parseInt(r.css("top"),10)).data("offset",this.cache_offset(r)),r.trigger("open"),i.length<1&&this.toggle_bg();if(typeof n=="undefined"||!n.url)this.hide(i,this.settings.css.close),this.show(r,this.settings.css.open);else{var s=this,o=typeof n.success!="undefined"?n.success:null;e.extend(n,{success:function(t,n,u){e.isFunction(o)&&o(t,n,u),r.html(t),e(r).foundation("section","reflow"),s.hide(i,s.settings.css.close),s.show(r,s.settings.css.open)}}),e.ajax(n)}}},close:function(t){var t=t&&t.length?t:e(this.scope),n=e("[data-reveal].open");n.length>0&&(this.locked=!0,t.trigger("close"),this.toggle_bg(),this.hide(n,this.settings.css.close))},close_targets:function(){var e="."+this.settings.dismiss_modal_class;return this.settings.close_on_background_click?e+", ."+this.settings.bg_class:e},toggle_bg:function(){e("."+this.settings.bg_class).length===0&&(this.settings.bg=e("
        ",{"class":this.settings.bg_class}).appendTo("body")),this.settings.bg.filter(":visible").length>0?this.hide(this.settings.bg):this.show(this.settings.bg)},show:function(n,r){if(r){if(n.parent("body").length===0){var i=n.wrap('
        ').parent();n.on("closed.fndtn.reveal.wrapped",function(){n.detach().appendTo(i),n.unwrap().unbind("closed.fndtn.reveal.wrapped")}),n.detach().appendTo("body")}if(/pop/i.test(this.settings.animation)){r.top=e(t).scrollTop()-n.data("offset")+"px";var s={top:e(t).scrollTop()+n.data("css-top")+"px",opacity:1};return this.delay(function(){return n.css(r).animate(s,this.settings.animation_speed,"linear",function(){this.locked=!1,n.trigger("opened")}.bind(this)).addClass("open")}.bind(this),this.settings.animation_speed/2)}if(/fade/i.test(this.settings.animation)){var s={opacity:1};return this.delay(function(){return n.css(r).animate(s,this.settings.animation_speed,"linear",function(){this.locked=!1,n.trigger("opened")}.bind(this)).addClass("open")}.bind(this),this.settings.animation_speed/2)}return n.css(r).show().css({opacity:1}).addClass("open").trigger("opened")}return/fade/i.test(this.settings.animation)?n.fadeIn(this.settings.animation_speed/2):n.show()},hide:function(n,r){if(r){if(/pop/i.test(this.settings.animation)){var i={top:-e(t).scrollTop()-n.data("offset")+"px",opacity:0};return this.delay(function(){return n.animate(i,this.settings.animation_speed,"linear",function(){this.locked=!1,n.css(r).trigger("closed")}.bind(this)).removeClass("open")}.bind(this),this.settings.animation_speed/2)}if(/fade/i.test(this.settings.animation)){var i={opacity:0};return this.delay(function(){return n.animate(i,this.settings.animation_speed,"linear",function(){this.locked=!1,n.css(r).trigger("closed")}.bind(this)).removeClass("open")}.bind(this),this.settings.animation_speed/2)}return n.hide().css(r).removeClass("open").trigger("closed")}return/fade/i.test(this.settings.animation)?n.fadeOut(this.settings.animation_speed/2):n.hide()},close_video:function(t){var n=e(this).find(".flex-video"),r=n.find("iframe");r.length>0&&(r.attr("data-src",r[0].src),r.attr("src","about:blank"),n.hide())},open_video:function(t){var n=e(this).find(".flex-video"),i=n.find("iframe");if(i.length>0){var s=i.attr("data-src");if(typeof s=="string")i[0].src=i.attr("data-src");else{var o=i[0].src;i[0].src=r,i[0].src=o}n.show()}},cache_offset:function(e){var t=e.show().height()+parseInt(e.css("top"),10);return e.hide(),t},off:function(){e(this.scope).off(".fndtn.reveal")},reflow:function(){}}}(jQuery,this,this.document),function(e,t,n,r){"use strict";Foundation.libs.interchange={name:"interchange",version:"5.0.0",cache:{},images_loaded:!1,nodes_loaded:!1,settings:{load_attr:"interchange",named_queries:{"default":Foundation.media_queries.small,small:Foundation.media_queries.small,medium:Foundation.media_queries.medium,large:Foundation.media_queries.large,xlarge:Foundation.media_queries.xlarge,xxlarge:Foundation.media_queries.xxlarge,landscape:"only screen and (orientation: landscape)",portrait:"only screen and (orientation: portrait)",retina:"only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx)"},directives:{replace:function(t,n,r){if(/IMG/.test(t[0].nodeName)){var i=t[0].src;if((new RegExp(n,"i")).test(i))return;return t[0].src=n,r(t[0].src)}var s=t.data("interchange-last-path");if(s==n)return;return e.get(n,function(e){t.html(e),t.data("interchange-last-path",n),r()})}}},init:function(e,t,n){Foundation.inherit(this,"throttle"),this.data_attr="data-"+this.settings.load_attr,this.bindings(t,n),this.load("images"),this.load("nodes")},events:function(){var n=this;return e(t).off(".interchange").on("resize.fndtn.interchange",n.throttle(function(){n.resize.call(n)},50)),this},resize:function(){var t=this.cache;if(!this.images_loaded||!this.nodes_loaded){setTimeout(e.proxy(this.resize,this),50);return}for(var n in t)if(t.hasOwnProperty(n)){var r=this.results(n,t[n]);r&&this.settings.directives[r.scenario[1]](r.el,r.scenario[0],function(){if(arguments[0]instanceof Array)var e=arguments[0];else var e=Array.prototype.slice.call(arguments,0);r.el.trigger(r.scenario[1],e)})}},results:function(e,t){var n=t.length;if(n>0){var r=this.S('[data-uuid="'+e+'"]');for(var i=n-1;i>=0;i--){var s,o=t[i][2];this.settings.named_queries.hasOwnProperty(o)?s=matchMedia(this.settings.named_queries[o]):s=matchMedia(o);if(s.matches)return{el:r,scenario:t[i]}}}return!1},load:function(e,t){return(typeof this["cached_"+e]=="undefined"||t)&&this["update_"+e](),this["cached_"+e]},update_images:function(){var e=this.S("img["+this.data_attr+"]"),t=e.length,n=0,r=this.data_attr;this.cache={},this.cached_images=[],this.images_loaded=t===0;for(var i=t-1;i>=0;i--){n++;if(e[i]){var s=e[i].getAttribute(r)||"";s.length>0&&this.cached_images.push(e[i])}n===t&&(this.images_loaded=!0,this.enhance("images"))}return this},update_nodes:function(){var e=this.S("["+this.data_attr+"]:not(img)"),t=e.length,n=0,r=this.data_attr;this.cached_nodes=[],this.nodes_loaded=t===0;for(var i=t-1;i>=0;i--){n++;var s=e[i].getAttribute(r)||"";s.length>0&&this.cached_nodes.push(e[i]),n===t&&(this.nodes_loaded=!0,this.enhance("nodes"))}return this},enhance:function(n){var r=this["cached_"+n].length;for(var i=r-1;i>=0;i--)this.object(e(this["cached_"+n][i]));return e(t).trigger("resize")},parse_params:function(e,t,n){return[this.trim(e),this.convert_directive(t),this.trim(n)]},convert_directive:function(e){var t=this.trim(e);return t.length>0?t:"replace"},object:function(e){var t=this.parse_data_attr(e),n=[],r=t.length;if(r>0)for(var i=r-1;i>=0;i--){var s=t[i].split(/\((.*?)(\))$/);if(s.length>1){var o=s[0].split(","),u=this.parse_params(o[0],o[1],s[1]);n.push(u)}}return this.store(e,n)},uuid:function(e){function n(){return((1+Math.random())*65536|0).toString(16).substring(1)}var t=e||"-";return n()+n()+t+n()+t+n()+t+n()+t+n()+n()+n()},store:function(e,t){var n=this.uuid(),r=e.data("uuid");return r?this.cache[r]:(e.attr("data-uuid",n),this.cache[n]=t)},trim:function(t){return typeof t=="string"?e.trim(t):t},parse_data_attr:function(e){var t=e.data(this.settings.load_attr).split(/\[(.*?)\]/),n=t.length,r=[];for(var i=n-1;i>=0;i--)t[i].replace(/[\W\d]+/,"").length>4&&r.push(t[i]);return r},reflow:function(){this.load("images",!0),this.load("nodes",!0)}}}(jQuery,this,this.document),function(e,t,n,r){"use strict";Foundation.libs.magellan={name:"magellan",version:"5.0.0",settings:{active_class:"active",threshold:0},init:function(t,n,r){this.fixed_magellan=e("[data-magellan-expedition]"),this.set_threshold(),this.last_destination=e("[data-magellan-destination]").last(),this.events()},events:function(){var n=this;e(this.scope).off(".magellan").on("arrival.fndtn.magellan","[data-magellan-arrival]",function(t){var r=e(this),i=r.closest("[data-magellan-expedition]"),s=i.attr("data-magellan-active-class")||n.settings.active_class;r.closest("[data-magellan-expedition]").find("[data-magellan-arrival]").not(r).removeClass(s),r.addClass(s)}),this.fixed_magellan.off(".magellan").on("update-position.fndtn.magellan",function(){var t=e(this)}).trigger("update-position"),e(t).off(".magellan").on("resize.fndtn.magellan",function(){this.fixed_magellan.trigger("update-position")}.bind(this)).on("scroll.fndtn.magellan",function(){var r=e(t).scrollTop();n.fixed_magellan.each(function(){var t=e(this);typeof t.data("magellan-top-offset")=="undefined"&&t.data("magellan-top-offset",t.offset().top),typeof t.data("magellan-fixed-position")=="undefined"&&t.data("magellan-fixed-position",!1);var i=r+n.settings.threshold>t.data("magellan-top-offset"),s=t.attr("data-magellan-top-offset");t.data("magellan-fixed-position")!=i&&(t.data("magellan-fixed-position",i),i?(t.addClass("fixed"),t.css({position:"fixed",top:0})):(t.removeClass("fixed"),t.css({position:"",top:""})),i&&typeof s!="undefined"&&s!=0&&t.css({position:"fixed",top:s+"px"}))})}),this.last_destination.length>0&&e(t).on("scroll.fndtn.magellan",function(r){var i=e(t).scrollTop(),s=i+e(t).height(),o=Math.ceil(n.last_destination.offset().top);e("[data-magellan-destination]").each(function(){var t=e(this),r=t.attr("data-magellan-destination"),u=t.offset().top-t.outerHeight(!0)-i;u<=n.settings.threshold&&e("[data-magellan-arrival='"+r+"']").trigger("arrival"),s>=e(n.scope).height()&&o>i&&o0?this.fixed_magellan.outerHeight(!0):0)},off:function(){e(this.scope).off(".fndtn.magellan"),e(t).off(".fndtn.magellan")},reflow:function(){}}}(jQuery,this,this.document),function(e,t,n,r){"use strict";Foundation.libs.accordion={name:"accordion",version:"5.0.1",settings:{active_class:"active",toggleable:!0},init:function(e,t,n){this.bindings(t,n)},events:function(){e(this.scope).off(".accordion").on("click.fndtn.accordion","[data-accordion] > dd > a",function(t){var n=e(this).parent(),r=e("#"+this.href.split("#")[1]),i=e("> dd > .content",r.closest("[data-accordion]")),s=n.parent().data("accordion-init"),o=e("> dd > .content."+s.active_class,n.parent());t.preventDefault();if(o[0]==r[0]&&s.toggleable)return r.toggleClass(s.active_class);i.removeClass(s.active_class),r.addClass(s.active_class)})},off:function(){},reflow:function(){}}}(jQuery,this,this.document),function(e,t,n,r){"use strict";Foundation.libs.topbar={name:"topbar",version:"5.0.1",settings:{index:0,sticky_class:"sticky",custom_back_text:!0,back_text:"Back",is_hover:!0,mobile_show_parent_link:!1,scrolltop:!0},init:function(t,n,r){Foundation.inherit(this,"addCustomRule register_media throttle");var i=this;i.register_media("topbar","foundation-mq-topbar"),this.bindings(n,r),e("[data-topbar]",this.scope).each(function(){var t=e(this),n=t.data("topbar-init"),r=e("section",this),s=e("> ul",this).first();t.data("index",0);var o=t.parent();o.hasClass("fixed")||o.hasClass(n.sticky_class)?(i.settings.sticky_class=n.sticky_class,i.settings.stick_topbar=t,t.data("height",o.outerHeight()),t.data("stickyoffset",o.offset().top)):t.data("height",t.outerHeight()),n.assembled||i.assemble(t),n.is_hover?e(".has-dropdown",t).addClass("not-click"):e(".has-dropdown",t).removeClass("not-click"),i.addCustomRule(".f-topbar-fixed { padding-top: "+t.data("height")+"px }"),o.hasClass("fixed")&&e("body").addClass("f-topbar-fixed")})},toggle:function(n){var r=this;if(n)var i=e(n).closest("[data-topbar]");else var i=e("[data-topbar]");var s=i.data("topbar-init"),o=e("section, .section",i);r.breakpoint()&&(r.rtl?(o.css({right:"0%"}),e(">.name",o).css({right:"100%"})):(o.css({left:"0%"}),e(">.name",o).css({left:"100%"})),e("li.moved",o).removeClass("moved"),i.data("index",0),i.toggleClass("expanded").css("height","")),s.scrolltop?i.hasClass("expanded")?i.parent().hasClass("fixed")&&(s.scrolltop?(i.parent().removeClass("fixed"),i.addClass("fixed"),e("body").removeClass("f-topbar-fixed"),t.scrollTo(0,0)):i.parent().removeClass("expanded")):i.hasClass("fixed")&&(i.parent().addClass("fixed"),i.removeClass("fixed"),e("body").addClass("f-topbar-fixed")):(i.parent().hasClass(r.settings.sticky_class)&&i.parent().addClass("fixed"),i.parent().hasClass("fixed")&&(i.hasClass("expanded")?(i.addClass("fixed"),i.parent().addClass("expanded")):(i.removeClass("fixed"),i.parent().removeClass("expanded"),r.update_sticky_positioning())))},timer:null,events:function(n){var r=this;e(this.scope).off(".topbar").on("click.fndtn.topbar","[data-topbar] .toggle-topbar",function(e){e.preventDefault(),r.toggle(this)}).on("click.fndtn.topbar","[data-topbar] li.has-dropdown",function(t){var n=e(this),i=e(t.target),s=n.closest("[data-topbar]"),o=s.data("topbar-init");if(i.data("revealId")){r.toggle();return}if(r.breakpoint())return;if(o.is_hover&&!Modernizr.touch)return;t.stopImmediatePropagation(),n.hasClass("hover")?(n.removeClass("hover").find("li").removeClass("hover"),n.parents("li.hover").removeClass("hover")):(n.addClass("hover"),i[0].nodeName==="A"&&i.parent().hasClass("has-dropdown")&&t.preventDefault())}).on("click.fndtn.topbar","[data-topbar] .has-dropdown>a",function(t){if(r.breakpoint()){t.preventDefault();var n=e(this),i=n.closest("[data-topbar]"),s=i.find("section, .section"),o=n.next(".dropdown").outerHeight(),u=n.closest("li");i.data("index",i.data("index")+1),u.addClass("moved"),r.rtl?(s.css({right:-(100*i.data("index"))+"%"}),s.find(">.name").css({right:100*i.data("index")+"%"})):(s.css({left:-(100*i.data("index"))+"%"}),s.find(">.name").css({left:100*i.data("index")+"%"})),i.css("height",n.siblings("ul").outerHeight(!0)+i.data("height"))}}),e(t).off(".topbar").on("resize.fndtn.topbar",r.throttle(function(){r.resize.call(r)},50)).trigger("resize"),e("body").off(".topbar").on("click.fndtn.topbar touchstart.fndtn.topbar",function(t){var n=e(t.target).closest("li").closest("li.hover");if(n.length>0)return;e("[data-topbar] li").removeClass("hover")}),e(this.scope).on("click.fndtn.topbar","[data-topbar] .has-dropdown .back",function(t){t.preventDefault();var n=e(this),i=n.closest("[data-topbar]"),s=i.find("section, .section"),o=i.data("topbar-init"),u=n.closest("li.moved"),a=u.parent();i.data("index",i.data("index")-1),r.rtl?(s.css({right:-(100*i.data("index"))+"%"}),s.find(">.name").css({right:100*i.data("index")+"%"})):(s.css({left:-(100*i.data("index"))+"%"}),s.find(">.name").css({left:100*i.data("index")+"%"})),i.data("index")===0?i.css("height",""):i.css("height",a.outerHeight(!0)+i.data("height")),setTimeout(function(){u.removeClass("moved")},300)})},resize:function(){var t=this;e("[data-topbar]").each(function(){var r=e(this),i=r.data("topbar-init"),s=r.parent("."+t.settings.sticky_class),o;if(!t.breakpoint()){var u=r.hasClass("expanded");r.css("height","").removeClass("expanded").find("li").removeClass("hover"),u&&t.toggle(r)}s.length>0&&(s.hasClass("fixed")?(s.removeClass("fixed"),o=s.offset().top,e(n.body).hasClass("f-topbar-fixed")&&(o-=r.data("height")),r.data("stickyoffset",o),s.addClass("fixed")):(o=s.offset().top,r.data("stickyoffset",o)))})},breakpoint:function(){return!matchMedia(Foundation.media_queries.topbar).matches},assemble:function(t){var n=this,r=t.data("topbar-init"),i=e("section",t),s=e("> ul",t).first();i.detach(),e(".has-dropdown>a",i).each(function(){var t=e(this),n=t.siblings(".dropdown"),i=t.attr("href");if(r.mobile_show_parent_link&&i&&i.length>1)var s=e('
      2. '+t.text()+"
      3. ");else var s=e('
      4. ');r.custom_back_text==1?e("h5>a",s).html(r.back_text):e("h5>a",s).html("« "+t.html()),n.prepend(s)}),i.appendTo(t),this.sticky(),this.assembled(t)},assembled:function(t){t.data("topbar-init",e.extend({},t.data("topbar-init"),{assembled:!0}))},height:function(t){var n=0,r=this;return e("> li",t).each(function(){n+=e(this).outerHeight(!0)}),n},sticky:function( -){var n=e(t),r=this;e(t).on("scroll",function(){r.update_sticky_positioning()})},update_sticky_positioning:function(){var n="."+this.settings.sticky_class,r=e(t);if(e(n).length>0){var i=this.settings.sticky_topbar.data("stickyoffset");e(n).hasClass("expanded")||(r.scrollTop()>i?e(n).hasClass("fixed")||(e(n).addClass("fixed"),e("body").addClass("f-topbar-fixed")):r.scrollTop()<=i&&e(n).hasClass("fixed")&&(e(n).removeClass("fixed"),e("body").removeClass("f-topbar-fixed")))}},off:function(){e(this.scope).off(".fndtn.topbar"),e(t).off(".fndtn.topbar")},reflow:function(){}}}(jQuery,this,this.document),function(e,t,n,r){"use strict";Foundation.libs.tab={name:"tab",version:"5.0.1",settings:{active_class:"active"},init:function(e,t,n){this.bindings(t,n)},events:function(){e(this.scope).off(".tab").on("click.fndtn.tab","[data-tab] > dd > a",function(t){t.preventDefault();var n=e(this).parent(),r=e("#"+this.href.split("#")[1]),i=n.siblings(),s=n.closest("[data-tab]").data("tab-init");n.addClass(s.active_class),i.removeClass(s.active_class),r.siblings().removeClass(s.active_class).end().addClass(s.active_class)})},off:function(){},reflow:function(){}}}(jQuery,this,this.document),function(e,t,n,r){"use strict";Foundation.libs.abide={name:"abide",version:"5.0.0",settings:{focus_on_invalid:!0,timeout:1e3,patterns:{alpha:/[a-zA-Z]+/,alpha_numeric:/[a-zA-Z0-9]+/,integer:/-?\d+/,number:/-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?/,password:/(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/,card:/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/,cvv:/^([0-9]){3,4}$/,email:/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,url:/(https?|ftp|file|ssh):\/\/(((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?/,domain:/^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$/,datetime:/([0-2][0-9]{3})\-([0-1][0-9])\-([0-3][0-9])T([0-5][0-9])\:([0-5][0-9])\:([0-5][0-9])(Z|([\-\+]([0-1][0-9])\:00))/,date:/(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))/,time:/(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}/,dateISO:/\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/,month_day_year:/(0[1-9]|1[012])[- \/.](0[1-9]|[12][0-9]|3[01])[- \/.](19|20)\d\d/,color:/^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/}},timer:null,init:function(e,t,n){this.bindings(t,n)},events:function(t){var n=this,r=e(t).attr("novalidate","novalidate"),i=r.data("abide-init");r.off(".abide").on("submit.fndtn.abide validate.fndtn.abide",function(t){var r=/ajax/i.test(e(this).attr("data-abide"));return n.validate(e(this).find("input, textarea, select").get(),t,r)}).find("input, textarea, select").off(".abide").on("blur.fndtn.abide change.fndtn.abide",function(e){n.validate([this],e)}).on("keydown.fndtn.abide",function(t){var r=e(this).closest("form").data("abide-init");clearTimeout(n.timer),n.timer=setTimeout(function(){n.validate([this],t)}.bind(this),r.timeout)})},validate:function(t,n,r){var i=this.parse_patterns(t),s=i.length,o=e(t[0]).closest("form"),u=/submit/.test(n.type);for(var a=0;a=0;r--)n.push(this.pattern(e[r]));return this.check_validation_and_apply_styles(n)},pattern:function(e){var t=e.getAttribute("type"),n=typeof e.getAttribute("required")=="string";if(this.settings.patterns.hasOwnProperty(t))return[e,this.settings.patterns[t],n];var r=e.getAttribute("pattern")||"";return this.settings.patterns.hasOwnProperty(r)&&r.length>0?[e,this.settings.patterns[r],n]:r.length>0?[e,new RegExp(r),n]:(r=/.*/,[e,r,n])},check_validation_and_apply_styles:function(t){var n=t.length,r=[];for(var i=n-1;i>=0;i--){var s=t[i][0],o=t[i][2],u=s.value,a=s.getAttribute("data-equalto"),f=s.type==="radio",l=o?s.value.length>0:!0;f&&o?r.push(this.valid_radio(s,o)):a&&o?r.push(this.valid_equal(s,o)):t[i][1].test(u)&&l||!o&&s.value.length<1?(e(s).removeAttr("data-invalid").parent().removeClass("error"),r.push(!0)):(e(s).attr("data-invalid","").parent().addClass("error"),r.push(!1))}return r},valid_radio:function(t,r){var i=t.getAttribute("name"),s=n.getElementsByName(i),o=s.length,u=!1;for(var a=0;a'+t+''}},cache:{},init:function(e,t,n){this.bindings(t,n)},events:function(){var t=this;Modernizr.touch?e(this.scope).off(".tooltip").on("click.fndtn.tooltip touchstart.fndtn.tooltip touchend.fndtn.tooltip","[data-tooltip]",function(n){var r=e.extend({},t.settings,t.data_options(e(this)));r.disable_for_touch||(n.preventDefault(),e(r.tooltip_class).hide(),t.showOrCreateTip(e(this)))}).on("click.fndtn.tooltip touchstart.fndtn.tooltip touchend.fndtn.tooltip",this.settings.tooltip_class,function(t){t.preventDefault(),e(this).fadeOut(150)}):e(this.scope).off(".tooltip").on("mouseenter.fndtn.tooltip mouseleave.fndtn.tooltip","[data-tooltip]",function(n){var r=e(this);/enter|over/i.test(n.type)?t.showOrCreateTip(r):(n.type==="mouseout"||n.type==="mouseleave")&&t.hide(r)})},showOrCreateTip:function(e){var t=this.getTip(e);return t&&t.length>0?this.show(e):this.create(e)},getTip:function(t){var n=this.selector(t),r=null;return n&&(r=e('span[data-selector="'+n+'"]'+this.settings.tooltip_class)),typeof r=="object"?r:!1},selector:function(e){var t=e.attr("id"),n=e.attr("data-tooltip")||e.attr("data-selector");return(t&&t.length<1||!t)&&typeof n!="string"&&(n="tooltip"+Math.random().toString(36).substring(7),e.attr("data-selector",n)),t&&t.length>0?t:n},create:function(t){var n=e(this.settings.tip_template(this.selector(t),e("
        ").html(t.attr("title")).html())),r=this.inheritable_classes(t);n.addClass(r).appendTo(this.settings.append_to),Modernizr.touch&&n.append(''+this.settings.touch_close_text+""),t.removeAttr("title").attr("title",""),this.show(t)},reposition:function(t,n,r){var i,s,o,u,a,f;n.css("visibility","hidden").show(),i=t.data("width"),s=n.children(".nub"),o=s.outerHeight(),u=s.outerHeight(),f=function(e,t,n,r,i,s){return e.css({top:t?t:"auto",bottom:r?r:"auto",left:i?i:"auto",right:n?n:"auto",width:s?s:"auto"}).end()},f(n,t.offset().top+t.outerHeight()+10,"auto","auto",t.offset().left,i);if(this.small())f(n,t.offset().top+t.outerHeight()+10,"auto","auto",12.5,e(this.scope).width()),n.addClass("tip-override"),f(s,-o,"auto","auto",t.offset().left);else{var l=t.offset().left;Foundation.rtl&&(l=t.offset().left+t.offset().width-n.outerWidth()),f(n,t.offset().top+t.outerHeight()+10,"auto","auto",l,i),n.removeClass("tip-override"),r&&r.indexOf("tip-top")>-1?f(n,t.offset().top-n.outerHeight(),"auto","auto",l,i).removeClass("tip-override"):r&&r.indexOf("tip-left")>-1?f(n,t.offset().top+t.outerHeight()/2-o*2.5,"auto","auto",t.offset().left-n.outerWidth()-o,i).removeClass("tip-override"):r&&r.indexOf("tip-right")>-1&&f(n,t.offset().top+t.outerHeight()/2-o*2.5,"auto","auto",t.offset().left+t.outerWidth()+o,i).removeClass("tip-override")}n.css("visibility","visible").hide()},small:function(){return matchMedia(Foundation.media_queries.small).matches},inheritable_classes:function(t){var n=["tip-top","tip-left","tip-bottom","tip-right","noradius"].concat(this.settings.additional_inheritable_classes),r=t.attr("class"),i=r?e.map(r.split(" "),function(t,r){if(e.inArray(t,n)!==-1)return t}).join(" "):"";return e.trim(i)},show:function(e){var t=this.getTip(e);this.reposition(e,t,e.attr("class")),t.fadeIn(150)},hide:function(e){var t=this.getTip(e);t.fadeOut(150)},reload:function(){var t=e(this);return t.data("fndtn-tooltips")?t.foundationTooltips("destroy").foundationTooltips("init"):t.foundationTooltips("init")},off:function(){e(this.scope).off(".fndtn.tooltip"),e(this.settings.tooltip_class).each(function(t){e("[data-tooltip]").get(t).attr("title",e(this).text())}).remove()},reflow:function(){}}}(jQuery,this,this.document); diff --git a/vendor/parse-1.1.15.js b/vendor/parse-1.1.15.js deleted file mode 100644 index da7b84b..0000000 --- a/vendor/parse-1.1.15.js +++ /dev/null @@ -1,7052 +0,0 @@ -/*! - * Parse JavaScript SDK - * Version: 1.1.15 - * Built: Wed Dec 05 2012 15:59:58 - * http://parse.com - * - * Copyright 2012 Parse, Inc. - * The Parse JavaScript SDK is freely distributable under the MIT license. - * - * Includes: Underscore.js - * Copyright 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. - * Released under the MIT license. - */ -(function(root) { - root.Parse = root.Parse || {}; - root.Parse.VERSION = "js1.1.15"; -}(this)); - - -// Underscore.js 1.3.3 -// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the MIT license. -// Portions of Underscore are inspired or borrowed from Prototype, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore - -(function() { - - // Baseline setup - // -------------- - - // Establish the root object, `window` in the browser, or `global` on the server. - var root = this; - - // Save the previous value of the `_` variable. - var previousUnderscore = root._; - - // Establish the object that gets returned to break out of a loop iteration. - var breaker = {}; - - // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; - - // Create quick reference variables for speed access to core prototypes. - var slice = ArrayProto.slice, - unshift = ArrayProto.unshift, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty; - - // All **ECMAScript 5** native function implementations that we hope to use - // are declared here. - var - nativeForEach = ArrayProto.forEach, - nativeMap = ArrayProto.map, - nativeReduce = ArrayProto.reduce, - nativeReduceRight = ArrayProto.reduceRight, - nativeFilter = ArrayProto.filter, - nativeEvery = ArrayProto.every, - nativeSome = ArrayProto.some, - nativeIndexOf = ArrayProto.indexOf, - nativeLastIndexOf = ArrayProto.lastIndexOf, - nativeIsArray = Array.isArray, - nativeKeys = Object.keys, - nativeBind = FuncProto.bind; - - // Create a safe reference to the Underscore object for use below. - var _ = function(obj) { return new wrapper(obj); }; - - // Export the Underscore object for **Node.js**, with - // backwards-compatibility for the old `require()` API. If we're in - // the browser, add `_` as a global object via a string identifier, - // for Closure Compiler "advanced" mode. - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = _; - } - exports._ = _; - } else { - root['_'] = _; - } - - // Current version. - _.VERSION = '1.3.3'; - - // Collection Functions - // -------------------- - - // The cornerstone, an `each` implementation, aka `forEach`. - // Handles objects with the built-in `forEach`, arrays, and raw objects. - // Delegates to **ECMAScript 5**'s native `forEach` if available. - var each = _.each = _.forEach = function(obj, iterator, context) { - if (obj == null) return; - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (obj.length === +obj.length) { - for (var i = 0, l = obj.length; i < l; i++) { - if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; - } - } else { - for (var key in obj) { - if (_.has(obj, key)) { - if (iterator.call(context, obj[key], key, obj) === breaker) return; - } - } - } - }; - - // Return the results of applying the iterator to each element. - // Delegates to **ECMAScript 5**'s native `map` if available. - _.map = _.collect = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - each(obj, function(value, index, list) { - results[results.length] = iterator.call(context, value, index, list); - }); - if (obj.length === +obj.length) results.length = obj.length; - return results; - }; - - // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. - _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduce && obj.reduce === nativeReduce) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); - } - each(obj, function(value, index, list) { - if (!initial) { - memo = value; - initial = true; - } else { - memo = iterator.call(context, memo, value, index, list); - } - }); - if (!initial) throw new TypeError('Reduce of empty array with no initial value'); - return memo; - }; - - // The right-associative version of reduce, also known as `foldr`. - // Delegates to **ECMAScript 5**'s native `reduceRight` if available. - _.reduceRight = _.foldr = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); - } - var reversed = _.toArray(obj).reverse(); - if (context && !initial) iterator = _.bind(iterator, context); - return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); - }; - - // Return the first value which passes a truth test. Aliased as `detect`. - _.find = _.detect = function(obj, iterator, context) { - var result; - any(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) { - result = value; - return true; - } - }); - return result; - }; - - // Return all the elements that pass a truth test. - // Delegates to **ECMAScript 5**'s native `filter` if available. - // Aliased as `select`. - _.filter = _.select = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); - each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Return all the elements for which a truth test fails. - _.reject = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - each(obj, function(value, index, list) { - if (!iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Determine whether all of the elements match a truth test. - // Delegates to **ECMAScript 5**'s native `every` if available. - // Aliased as `all`. - _.every = _.all = function(obj, iterator, context) { - var result = true; - if (obj == null) return result; - if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); - each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) return breaker; - }); - return !!result; - }; - - // Determine if at least one element in the object matches a truth test. - // Delegates to **ECMAScript 5**'s native `some` if available. - // Aliased as `any`. - var any = _.some = _.any = function(obj, iterator, context) { - iterator || (iterator = _.identity); - var result = false; - if (obj == null) return result; - if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); - each(obj, function(value, index, list) { - if (result || (result = iterator.call(context, value, index, list))) return breaker; - }); - return !!result; - }; - - // Determine if a given value is included in the array or object using `===`. - // Aliased as `contains`. - _.include = _.contains = function(obj, target) { - var found = false; - if (obj == null) return found; - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - found = any(obj, function(value) { - return value === target; - }); - return found; - }; - - // Invoke a method (with arguments) on every item in a collection. - _.invoke = function(obj, method) { - var args = slice.call(arguments, 2); - return _.map(obj, function(value) { - return (_.isFunction(method) ? method || value : value[method]).apply(value, args); - }); - }; - - // Convenience version of a common use case of `map`: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, function(value){ return value[key]; }); - }; - - // Return the maximum element or (element-based computation). - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj); - if (!iterator && _.isEmpty(obj)) return -Infinity; - var result = {computed : -Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed >= result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj); - if (!iterator && _.isEmpty(obj)) return Infinity; - var result = {computed : Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed < result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Shuffle an array. - _.shuffle = function(obj) { - var shuffled = [], rand; - each(obj, function(value, index, list) { - rand = Math.floor(Math.random() * (index + 1)); - shuffled[index] = shuffled[rand]; - shuffled[rand] = value; - }); - return shuffled; - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, val, context) { - var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; - return _.pluck(_.map(obj, function(value, index, list) { - return { - value : value, - criteria : iterator.call(context, value, index, list) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - if (a === void 0) return 1; - if (b === void 0) return -1; - return a < b ? -1 : a > b ? 1 : 0; - }), 'value'); - }; - - // Groups the object's values by a criterion. Pass either a string attribute - // to group by, or a function that returns the criterion. - _.groupBy = function(obj, val) { - var result = {}; - var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; - each(obj, function(value, index) { - var key = iterator(value, index); - (result[key] || (result[key] = [])).push(value); - }); - return result; - }; - - // Use a comparator function to figure out at what index an object should - // be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator) { - iterator || (iterator = _.identity); - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >> 1; - iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; - } - return low; - }; - - // Safely convert anything iterable into a real, live array. - _.toArray = function(obj) { - if (!obj) return []; - if (_.isArray(obj)) return slice.call(obj); - if (_.isArguments(obj)) return slice.call(obj); - if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray(); - return _.values(obj); - }; - - // Return the number of elements in an object. - _.size = function(obj) { - return _.isArray(obj) ? obj.length : _.keys(obj).length; - }; - - // Array Functions - // --------------- - - // Get the first element of an array. Passing **n** will return the first N - // values in the array. Aliased as `head` and `take`. The **guard** check - // allows it to work with `_.map`. - _.first = _.head = _.take = function(array, n, guard) { - return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; - }; - - // Returns everything but the last entry of the array. Especcialy useful on - // the arguments object. Passing **n** will return all the values in - // the array, excluding the last N. The **guard** check allows it to work with - // `_.map`. - _.initial = function(array, n, guard) { - return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); - }; - - // Get the last element of an array. Passing **n** will return the last N - // values in the array. The **guard** check allows it to work with `_.map`. - _.last = function(array, n, guard) { - if ((n != null) && !guard) { - return slice.call(array, Math.max(array.length - n, 0)); - } else { - return array[array.length - 1]; - } - }; - - // Returns everything but the first entry of the array. Aliased as `tail`. - // Especially useful on the arguments object. Passing an **index** will return - // the rest of the values in the array from that index onward. The **guard** - // check allows it to work with `_.map`. - _.rest = _.tail = function(array, index, guard) { - return slice.call(array, (index == null) || guard ? 1 : index); - }; - - // Trim out all falsy values from an array. - _.compact = function(array) { - return _.filter(array, function(value){ return !!value; }); - }; - - // Return a completely flattened version of an array. - _.flatten = function(array, shallow) { - return _.reduce(array, function(memo, value) { - if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value)); - memo[memo.length] = value; - return memo; - }, []); - }; - - // Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - return _.difference(array, slice.call(arguments, 1)); - }; - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted, iterator) { - var initial = iterator ? _.map(array, iterator) : array; - var results = []; - // The `isSorted` flag is irrelevant if the array only contains two elements. - if (array.length < 3) isSorted = true; - _.reduce(initial, function (memo, value, index) { - if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) { - memo.push(value); - results.push(array[index]); - } - return memo; - }, []); - return results; - }; - - // Produce an array that contains the union: each distinct element from all of - // the passed-in arrays. - _.union = function() { - return _.uniq(_.flatten(arguments, true)); - }; - - // Produce an array that contains every item shared between all the - // passed-in arrays. (Aliased as "intersect" for back-compat.) - _.intersection = _.intersect = function(array) { - var rest = slice.call(arguments, 1); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.indexOf(other, item) >= 0; - }); - }); - }; - - // Take the difference between one array and a number of other arrays. - // Only the elements present in just the first array will remain. - _.difference = function(array) { - var rest = _.flatten(slice.call(arguments, 1), true); - return _.filter(array, function(value){ return !_.include(rest, value); }); - }; - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - _.zip = function() { - var args = slice.call(arguments); - var length = _.max(_.pluck(args, 'length')); - var results = new Array(length); - for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); - return results; - }; - - // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), - // we need this function. Return the position of the first occurrence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to **ECMAScript 5**'s native `indexOf` if available. - // If the array is large and already in sort order, pass `true` - // for **isSorted** to use binary search. - _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i, l; - if (isSorted) { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); - for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i; - return -1; - }; - - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. - _.lastIndexOf = function(array, item) { - if (array == null) return -1; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); - var i = array.length; - while (i--) if (i in array && array[i] === item) return i; - return -1; - }; - - // Generate an integer Array containing an arithmetic progression. A port of - // the native Python `range()` function. See - // [the Python documentation](http://docs.python.org/library/functions.html#range). - _.range = function(start, stop, step) { - if (arguments.length <= 1) { - stop = start || 0; - start = 0; - } - step = arguments[2] || 1; - - var len = Math.max(Math.ceil((stop - start) / step), 0); - var idx = 0; - var range = new Array(len); - - while(idx < len) { - range[idx++] = start; - start += step; - } - - return range; - }; - - // Function (ahem) Functions - // ------------------ - - // Reusable constructor function for prototype setting. - var ctor = function(){}; - - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). Binding with arguments is also known as `curry`. - // Delegates to **ECMAScript 5**'s native `Function.bind` if available. - // We check for `func.bind` first, to fail fast when `func` is undefined. - _.bind = function bind(func, context) { - var bound, args; - if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - if (!_.isFunction(func)) throw new TypeError; - args = slice.call(arguments, 2); - return bound = function() { - if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); - ctor.prototype = func.prototype; - var self = new ctor; - var result = func.apply(self, args.concat(slice.call(arguments))); - if (Object(result) === result) return result; - return self; - }; - }; - - // Bind all of an object's methods to that object. Useful for ensuring that - // all callbacks defined on an object belong to it. - _.bindAll = function(obj) { - var funcs = slice.call(arguments, 1); - if (funcs.length == 0) funcs = _.functions(obj); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); - return obj; - }; - - // Memoize an expensive function by storing its results. - _.memoize = function(func, hasher) { - var memo = {}; - hasher || (hasher = _.identity); - return function() { - var key = hasher.apply(this, arguments); - return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); - }; - }; - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - _.delay = function(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(null, args); }, wait); - }; - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; - - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. - _.throttle = function(func, wait) { - var context, args, timeout, throttling, more, result; - var whenDone = _.debounce(function(){ more = throttling = false; }, wait); - return function() { - context = this; args = arguments; - var later = function() { - timeout = null; - if (more) func.apply(context, args); - whenDone(); - }; - if (!timeout) timeout = setTimeout(later, wait); - if (throttling) { - more = true; - } else { - result = func.apply(context, args); - } - whenDone(); - throttling = true; - return result; - }; - }; - - // Returns a function, that, as long as it continues to be invoked, will not - // be triggered. The function will be called after it stops being called for - // N milliseconds. If `immediate` is passed, trigger the function on the - // leading edge, instead of the trailing. - _.debounce = function(func, wait, immediate) { - var timeout; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) func.apply(context, args); - }; - if (immediate && !timeout) func.apply(context, args); - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; - }; - - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = function(func) { - var ran = false, memo; - return function() { - if (ran) return memo; - ran = true; - return memo = func.apply(this, arguments); - }; - }; - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - _.wrap = function(func, wrapper) { - return function() { - var args = [func].concat(slice.call(arguments, 0)); - return wrapper.apply(this, args); - }; - }; - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = arguments; - return function() { - var args = arguments; - for (var i = funcs.length - 1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; - }; - }; - - // Returns a function that will only be executed after being called N times. - _.after = function(times, func) { - if (times <= 0) return func(); - return function() { - if (--times < 1) { return func.apply(this, arguments); } - }; - }; - - // Object Functions - // ---------------- - - // Retrieve the names of an object's properties. - // Delegates to **ECMAScript 5**'s native `Object.keys` - _.keys = nativeKeys || function(obj) { - if (obj !== Object(obj)) throw new TypeError('Invalid object'); - var keys = []; - for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; - return keys; - }; - - // Retrieve the values of an object's properties. - _.values = function(obj) { - return _.map(obj, _.identity); - }; - - // Return a sorted list of the function names available on the object. - // Aliased as `methods` - _.functions = _.methods = function(obj) { - var names = []; - for (var key in obj) { - if (_.isFunction(obj[key])) names.push(key); - } - return names.sort(); - }; - - // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Return a copy of the object only containing the whitelisted properties. - _.pick = function(obj) { - var result = {}; - each(_.flatten(slice.call(arguments, 1)), function(key) { - if (key in obj) result[key] = obj[key]; - }); - return result; - }; - - // Fill in a given object with default properties. - _.defaults = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - if (obj[prop] == null) obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - if (!_.isObject(obj)) return obj; - return _.isArray(obj) ? obj.slice() : _.extend({}, obj); - }; - - // Invokes interceptor with the obj, and then returns obj. - // The primary purpose of this method is to "tap into" a method chain, in - // order to perform operations on intermediate results within the chain. - _.tap = function(obj, interceptor) { - interceptor(obj); - return obj; - }; - - // Internal recursive comparison function. - function eq(a, b, stack) { - // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. - if (a === b) return a !== 0 || 1 / a == 1 / b; - // A strict comparison is necessary because `null == undefined`. - if (a == null || b == null) return a === b; - // Unwrap any wrapped objects. - if (a._chain) a = a._wrapped; - if (b._chain) b = b._wrapped; - // Invoke a custom `isEqual` method if one is provided. - if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); - if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); - // Compare `[[Class]]` names. - var className = toString.call(a); - if (className != toString.call(b)) return false; - switch (className) { - // Strings, numbers, dates, and booleans are compared by value. - case '[object String]': - // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is - // equivalent to `new String("5")`. - return a == String(b); - case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); - case '[object Date]': - case '[object Boolean]': - // Coerce dates and booleans to numeric primitive values. Dates are compared by their - // millisecond representations. Note that invalid dates with millisecond representations - // of `NaN` are not equivalent. - return +a == +b; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - } - if (typeof a != 'object' || typeof b != 'object') return false; - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = stack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (stack[length] == a) return true; - } - // Add the first object to the stack of traversed objects. - stack.push(a); - var size = 0, result = true; - // Recursively compare objects and arrays. - if (className == '[object Array]') { - // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - // Ensure commutative equality for sparse arrays. - if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; - } - } - } else { - // Objects with different constructors are not equivalent. - if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false; - // Deep compare objects. - for (var key in a) { - if (_.has(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break; - } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (_.has(b, key) && !(size--)) break; - } - result = !size; - } - } - // Remove the first object from the stack of traversed objects. - stack.pop(); - return result; - } - - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - return eq(a, b, []); - }; - - // Is a given array, string, or object empty? - // An "empty" object has no enumerable own-properties. - _.isEmpty = function(obj) { - if (obj == null) return true; - if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; - for (var key in obj) if (_.has(obj, key)) return false; - return true; - }; - - // Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType == 1); - }; - - // Is a given value an array? - // Delegates to ECMA5's native Array.isArray - _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) == '[object Array]'; - }; - - // Is a given variable an object? - _.isObject = function(obj) { - return obj === Object(obj); - }; - - // Is a given variable an arguments object? - _.isArguments = function(obj) { - return toString.call(obj) == '[object Arguments]'; - }; - if (!_.isArguments(arguments)) { - _.isArguments = function(obj) { - return !!(obj && _.has(obj, 'callee')); - }; - } - - // Is a given value a function? - _.isFunction = function(obj) { - return toString.call(obj) == '[object Function]'; - }; - - // Is a given value a string? - _.isString = function(obj) { - return toString.call(obj) == '[object String]'; - }; - - // Is a given value a number? - _.isNumber = function(obj) { - return toString.call(obj) == '[object Number]'; - }; - - // Is a given object a finite number? - _.isFinite = function(obj) { - return _.isNumber(obj) && isFinite(obj); - }; - - // Is the given value `NaN`? - _.isNaN = function(obj) { - // `NaN` is the only value for which `===` is not reflexive. - return obj !== obj; - }; - - // Is a given value a boolean? - _.isBoolean = function(obj) { - return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; - }; - - // Is a given value a date? - _.isDate = function(obj) { - return toString.call(obj) == '[object Date]'; - }; - - // Is the given value a regular expression? - _.isRegExp = function(obj) { - return toString.call(obj) == '[object RegExp]'; - }; - - // Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - // Is a given variable undefined? - _.isUndefined = function(obj) { - return obj === void 0; - }; - - // Has own property? - _.has = function(obj, key) { - return hasOwnProperty.call(obj, key); - }; - - // Utility Functions - // ----------------- - - // Run Underscore.js in *noConflict* mode, returning the `_` variable to its - // previous owner. Returns a reference to the Underscore object. - _.noConflict = function() { - root._ = previousUnderscore; - return this; - }; - - // Keep the identity function around for default iterators. - _.identity = function(value) { - return value; - }; - - // Run a function **n** times. - _.times = function (n, iterator, context) { - for (var i = 0; i < n; i++) iterator.call(context, i); - }; - - // Escape a string for HTML interpolation. - _.escape = function(string) { - return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); - }; - - // If the value of the named property is a function then invoke it; - // otherwise, return it. - _.result = function(object, property) { - if (object == null) return null; - var value = object[property]; - return _.isFunction(value) ? value.call(object) : value; - }; - - // Add your own custom functions to the Underscore object, ensuring that - // they're correctly added to the OOP wrapper as well. - _.mixin = function(obj) { - each(_.functions(obj), function(name){ - addToWrapper(name, _[name] = obj[name]); - }); - }; - - // Generate a unique integer id (unique within the entire client session). - // Useful for temporary DOM ids. - var idCounter = 0; - _.uniqueId = function(prefix) { - var id = idCounter++; - return prefix ? prefix + id : id; - }; - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g, - escape : /<%-([\s\S]+?)%>/g - }; - - // When customizing `templateSettings`, if you don't want to define an - // interpolation, evaluation or escaping regex, we need one that is - // guaranteed not to match. - var noMatch = /.^/; - - // Certain characters need to be escaped so that they can be put into a - // string literal. - var escapes = { - '\\': '\\', - "'": "'", - 'r': '\r', - 'n': '\n', - 't': '\t', - 'u2028': '\u2028', - 'u2029': '\u2029' - }; - - for (var p in escapes) escapes[escapes[p]] = p; - var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; - var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g; - - // Within an interpolation, evaluation, or escaping, remove HTML escaping - // that had been previously added. - var unescape = function(code) { - return code.replace(unescaper, function(match, escape) { - return escapes[escape]; - }); - }; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - _.template = function(text, data, settings) { - settings = _.defaults(settings || {}, _.templateSettings); - - // Compile the template source, taking care to escape characters that - // cannot be included in a string literal and then unescape them in code - // blocks. - var source = "__p+='" + text - .replace(escaper, function(match) { - return '\\' + escapes[match]; - }) - .replace(settings.escape || noMatch, function(match, code) { - return "'+\n_.escape(" + unescape(code) + ")+\n'"; - }) - .replace(settings.interpolate || noMatch, function(match, code) { - return "'+\n(" + unescape(code) + ")+\n'"; - }) - .replace(settings.evaluate || noMatch, function(match, code) { - return "';\n" + unescape(code) + "\n;__p+='"; - }) + "';\n"; - - // If a variable is not specified, place data values in local scope. - if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; - - source = "var __p='';" + - "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + - source + "return __p;\n"; - - var render = new Function(settings.variable || 'obj', '_', source); - if (data) return render(data, _); - var template = function(data) { - return render.call(this, data, _); - }; - - // Provide the compiled function source as a convenience for build time - // precompilation. - template.source = 'function(' + (settings.variable || 'obj') + '){\n' + - source + '}'; - - return template; - }; - - // Add a "chain" function, which will delegate to the wrapper. - _.chain = function(obj) { - return _(obj).chain(); - }; - - // The OOP Wrapper - // --------------- - - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - var wrapper = function(obj) { this._wrapped = obj; }; - - // Expose `wrapper.prototype` as `_.prototype` - _.prototype = wrapper.prototype; - - // Helper function to continue chaining intermediate results. - var result = function(obj, chain) { - return chain ? _(obj).chain() : obj; - }; - - // A method to easily add functions to the OOP wrapper. - var addToWrapper = function(name, func) { - wrapper.prototype[name] = function() { - var args = slice.call(arguments); - unshift.call(args, this._wrapped); - return result(func.apply(_, args), this._chain); - }; - }; - - // Add all of the Underscore functions to the wrapper object. - _.mixin(_); - - // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - var wrapped = this._wrapped; - method.apply(wrapped, arguments); - var length = wrapped.length; - if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0]; - return result(wrapped, this._chain); - }; - }); - - // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - return result(method.apply(this._wrapped, arguments), this._chain); - }; - }); - - // Start chaining a wrapped Underscore object. - wrapper.prototype.chain = function() { - this._chain = true; - return this; - }; - - // Extracts the result from a wrapped and chained object. - wrapper.prototype.value = function() { - return this._wrapped; - }; - -}).call(this); - -/*global _: false, $: false, localStorage: false, XMLHttpRequest: false, - XDomainRequest: false, exports: false, require: false */ -(function(root) { - root.Parse = root.Parse || {}; - /** - * Contains all Parse API classes and functions. - * @name Parse - * @namespace - * - * Contains all Parse API classes and functions. - */ - var Parse = root.Parse; - - // Import Parse's local copy of underscore. - if (typeof(exports) !== "undefined" && exports._) { - // We're running in Node.js. Pull in the dependencies. - Parse._ = exports._.noConflict(); - Parse.localStorage = require('localStorage'); - Parse.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; - exports.Parse = Parse; - } else { - Parse._ = _.noConflict(); - if (typeof(localStorage) !== "undefined") { - Parse.localStorage = localStorage; - } - if (typeof(XMLHttpRequest) !== "undefined") { - Parse.XMLHttpRequest = XMLHttpRequest; - } - } - - // If jQuery or Zepto has been included, grab a reference to it. - if (typeof($) !== "undefined") { - Parse.$ = $; - } - - // Helpers - // ------- - - // Shared empty constructor function to aid in prototype-chain creation. - var EmptyConstructor = function() {}; - - - // Helper function to correctly set up the prototype chain, for subclasses. - // Similar to `goog.inherits`, but uses a hash of prototype properties and - // class properties to be extended. - var inherits = function(parent, protoProps, staticProps) { - var child; - - // The constructor function for the new subclass is either defined by you - // (the "constructor" property in your `extend` definition), or defaulted - // by us to simply call the parent's constructor. - if (protoProps && protoProps.hasOwnProperty('constructor')) { - child = protoProps.constructor; - } else { - /** @ignore */ - child = function(){ parent.apply(this, arguments); }; - } - - // Inherit class (static) properties from parent. - Parse._.extend(child, parent); - - // Set the prototype chain to inherit from `parent`, without calling - // `parent`'s constructor function. - EmptyConstructor.prototype = parent.prototype; - child.prototype = new EmptyConstructor(); - - // Add prototype properties (instance properties) to the subclass, - // if supplied. - if (protoProps) { - Parse._.extend(child.prototype, protoProps); - } - - // Add static properties to the constructor function, if supplied. - if (staticProps) { - Parse._.extend(child, staticProps); - } - - // Correctly set child's `prototype.constructor`. - child.prototype.constructor = child; - - // Set a convenience property in case the parent's prototype is - // needed later. - child.__super__ = parent.prototype; - - return child; - }; - - // Set the server for Parse to talk to. - Parse.serverURL = "https://api.parse.com"; - - /** - * Call this method first to set up your authentication tokens for Parse. - * You can get your keys from the Data Browser on parse.com. - * @param {String} applicationId Your Parse Application ID. - * @param {String} javaScriptKey Your Parse JavaScript Key. - */ - Parse.initialize = function(applicationId, javaScriptKey) { - Parse._initialize(applicationId, javaScriptKey); - }; - - /** - * Call this method first to set up master authentication tokens for Parse. - * This method is for Parse's own private use. - * @param {String} applicationId Your Parse Application ID. - * @param {String} javaScriptKey Your Parse JavaScript Key. - * @param {String} masterKey Your Parse Master Key. - */ - Parse._initialize = function(applicationId, javaScriptKey, masterKey) { - Parse.applicationId = applicationId; - Parse.javaScriptKey = javaScriptKey; - Parse.masterKey = masterKey; - Parse._useMasterKey = false; - }; - - /** - * Returns prefix for localStorage keys used by this instance of Parse. - * @param {String} path The relative suffix to append to it. - * null or undefined is treated as the empty string. - * @return {String} The full key name. - */ - Parse._getParsePath = function(path) { - if (!Parse.applicationId) { - throw "You need to call Parse.initialize before using Parse."; - } - if (!path) { - path = ""; - } - if (!Parse._.isString(path)) { - throw "Tried to get a localStorage path that wasn't a String."; - } - if (path[0] === "/") { - path = path.substring(1); - } - return "Parse/" + Parse.applicationId + "/" + path; - }; - - /** - * Returns the unique string for this app on this machine. - * Gets reset when localStorage is cleared. - */ - Parse._installationId = null; - Parse._getInstallationId = function() { - // See if it's cached in RAM. - if (Parse._installationId) { - return Parse._installationId; - } - - // Try to get it from localStorage. - var path = Parse._getParsePath("installationId"); - Parse._installationId = Parse.localStorage.getItem(path); - - if (!Parse._installationId || Parse._installationId === "") { - // It wasn't in localStorage, so create a new one. - var hexOctet = function() { - return Math.floor((1+Math.random())*0x10000).toString(16).substring(1); - }; - Parse._installationId = ( - hexOctet() + hexOctet() + "-" + - hexOctet() + "-" + - hexOctet() + "-" + - hexOctet() + "-" + - hexOctet() + hexOctet() + hexOctet()); - Parse.localStorage.setItem(path, Parse._installationId); - } - - return Parse._installationId; - }; - - Parse._parseDate = function(iso8601) { - var regexp = new RegExp( - "^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})" + "T" + - "([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})" + - "(.([0-9]+))?" + "Z$"); - var match = regexp.exec(iso8601); - if (!match) { - return null; - } - - var year = match[1] || 0; - var month = (match[2] || 1) - 1; - var day = match[3] || 0; - var hour = match[4] || 0; - var minute = match[5] || 0; - var second = match[6] || 0; - var milli = match[8] || 0; - - return new Date(Date.UTC(year, month, day, hour, minute, second, milli)); - }; - - Parse._ajaxIE8 = function(method, url, data, success, error) { - var xdr = new XDomainRequest(); - xdr.onload = function() { - var response; - try { - response = JSON.parse(xdr.responseText); - } catch (e) { - if (error) { - error(xdr); - } - } - if (response) { - if (success) { - success(response, xdr); - } - } - }; - xdr.onerror = xdr.ontimeout = function() { - error(xdr); - }; - xdr.onprogress = function() {}; - xdr.open(method, url); - xdr.send(data); - }; - - Parse._ajax = function(method, url, data, success, error) { - if (typeof(XDomainRequest) !== "undefined") { - return Parse._ajaxIE8(method, url, data, success, error); - } - - var handled = false; - - var xhr = new Parse.XMLHttpRequest(); - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - if (handled) { - return; - } - handled = true; - - if (xhr.status >= 200 && xhr.status < 300) { - var response; - try { - response = JSON.parse(xhr.responseText); - } catch (e) { - if (error) { - error(xhr); - } - } - if (response) { - if (success) { - success(response, xhr); - } - } - } else { - if (error) { - error(xhr); - } - } - } - }; - xhr.open(method, url, true); - xhr.setRequestHeader("Content-Type", "text/plain"); // avoid pre-flight. - xhr.send(data); - }; - - // A self-propagating extend function. - Parse._extend = function(protoProps, classProps) { - var child = inherits(this, protoProps, classProps); - child.extend = this.extend; - return child; - }; - - /** - * route is classes, users, login, etc. - * objectId is null if there is no associated objectId. - * method is the http method for the REST API. - * dataObject is the payload as an object, or null if there is none. - * options is just a success/error callback hash. - * @ignore - */ - Parse._request = function(route, className, objectId, method, dataObject, - options) { - if (!Parse.applicationId) { - throw "You must specify your applicationId using Parse.initialize"; - } - - if (!Parse.javaScriptKey && !Parse.masterKey) { - throw "You must specify a key using Parse.initialize"; - } - - - if (route !== "classes" && - route !== "push" && - route !== "users" && - route !== "login" && - route !== "functions" && - route !== "requestPasswordReset") { - throw "First argument must be one of classes, users, functions, or " + - "login, not '" + route + "'."; - } - - var url = Parse.serverURL; - if (url.charAt(url.length - 1) !== "/") { - url += "/"; - } - url += "1/" + route; - if (className) { - url += "/" + className; - } - if (objectId) { - url += "/" + objectId; - } - - dataObject = Parse._.clone(dataObject || {}); - if (method !== "POST") { - dataObject._method = method; - method = "POST"; - } - dataObject._ApplicationId = Parse.applicationId; - if (!Parse._useMasterKey) { - dataObject._JavaScriptKey = Parse.javaScriptKey; - } else { - dataObject._MasterKey = Parse.masterKey; - } - - dataObject._ClientVersion = Parse.VERSION; - dataObject._InstallationId = Parse._getInstallationId(); - // Pass the session token on every request. - var currentUser = Parse.User.current(); - if (currentUser && currentUser._sessionToken) { - dataObject._SessionToken = currentUser._sessionToken; - } - var data = JSON.stringify(dataObject); - - Parse._ajax(method, url, data, options.success, options.error); - }; - - // Helper function to get a value from a Backbone object as a property - // or as a function. - Parse._getValue = function(object, prop) { - if (!(object && object[prop])) { - return null; - } - return Parse._.isFunction(object[prop]) ? object[prop]() : object[prop]; - }; - - /** - * Converts a value in a Parse Object into the appropriate representation. - * This is the JS equivalent of Java's Parse.maybeReferenceAndEncode(Object) - * if seenObjects is falsey. Otherwise any Parse.Objects not in - * seenObjects will be fully embedded rather than encoded - * as a pointer. This array will be used to prevent going into an infinite - * loop because we have circular references. If - * is set, then none of the Parse Objects that are serialized can be dirty. - */ - Parse._encode = function(value, seenObjects, disallowObjects) { - var _ = Parse._; - if (value instanceof Parse.Object) { - if (disallowObjects) { - throw "Parse.Objects not allowed here"; - } - if (!seenObjects || _.include(seenObjects, value) || !value._hasData) { - return value._toPointer(); - } else if (!value.dirty()) { - seenObjects = seenObjects.concat(value); - return Parse._encode(value._toFullJSON(seenObjects), - seenObjects, - disallowObjects); - } else { - throw "Can't fully embed a dirty object"; - } - } else if (value instanceof Parse.ACL) { - return value.toJSON(); - } else if (value instanceof Date) { - return { "__type": "Date", "iso": value.toJSON() }; - } else if (value instanceof Parse.GeoPoint) { - return value.toJSON(); - } else if (_.isArray(value)) { - return _.map(value, function(x) { - return Parse._encode(x, seenObjects, disallowObjects); - }); - } else if (_.isRegExp(value)) { - return value.source; - } else if (value instanceof Parse.Relation) { - return value.toJSON(); - } else if (value instanceof Parse.Op) { - return value.toJSON(); - } else if (value instanceof Object) { - var output = {}; - Parse._each(value, function(v, k) { - output[k] = Parse._encode(v, seenObjects, disallowObjects); - }); - return output; - } else { - return value; - } - }; - - /** - * The inverse function of Parse._encode. - * TODO: make decode not mutate value. - */ - Parse._decode = function(key, value) { - var _ = Parse._; - if (!_.isObject(value)) { - return value; - } else if (_.isArray(value)) { - Parse._each(value, function(v, k) { - value[k] = Parse._decode(k, v); - }); - return value; - } else if (value instanceof Parse.Object) { - return value; - } else if (value instanceof Parse.Op) { - return value; - } else if (value.__op) { - // Must be a Op. - return Parse.Op._decode(value); - } else if (value.__type === "Pointer") { - var pointer = Parse.Object._create(value.className); - pointer._finishFetch({ objectId: value.objectId }, false); - return pointer; - } else if (value.__type === "Object") { - // It's an Object included in a query result. - var className = value.className; - delete value.__type; - delete value.className; - var object = Parse.Object._create(className); - object._finishFetch(value, true); - return object; - } else if (value.__type === "Date") { - return Parse._parseDate(value.iso); - } else if (value.__type === "GeoPoint") { - return new Parse.GeoPoint({ - latitude: value.latitude, - longitude: value.longitude - }); - } else if (key === "ACL") { - if (value instanceof Parse.ACL) { - return value; - } else { - return new Parse.ACL(value); - } - } else if (value.__type === "Relation") { - var relation = new Parse.Relation(null, key); - relation.targetClassName = value.className; - return relation; - } else { - Parse._each(value, function(v, k) { - value[k] = Parse._decode(k, v); - }); - return value; - } - }; - - /** - * This is like _.each, except: - * * it doesn't work for so-called array-like objects, - * * it does work for dictionaries with a "length" attribute. - */ - Parse._each = function(obj, callback) { - var _ = Parse._; - if (_.isObject(obj)) { - _.each(_.keys(obj), function(key) { - callback(obj[key], key); - }); - } else { - _.each(obj, callback); - } - }; - - // Helper function to check null or undefined. - Parse._isNullOrUndefined = function(x) { - return Parse._.isNull(x) || Parse._.isUndefined(x); - }; -}(this)); - -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - /** - * Constructs a new Parse.Error object with the given code and message. - * @param {Number} code An error code constant from Parse.Error. - * @param {String} message A detailed description of the error. - * @class - * - *

        Class used for all objects passed to error callbacks.

        - */ - Parse.Error = function(code, message) { - this.code = code; - this.message = message; - }; - - _.extend(Parse.Error, /** @lends Parse.Error */ { - /** - * Error code indicating some error other than those enumerated here. - * @constant - */ - OTHER_CAUSE: -1, - - /** - * Error code indicating that something has gone wrong with the server. - * If you get this error code, it is Parse's fault. Email feedback@parse.com - * to criticize us. - * @constant - */ - INTERNAL_SERVER_ERROR: 1, - - /** - * Error code indicating the connection to the Parse servers failed. - * @constant - */ - CONNECTION_FAILED: 100, - - /** - * Error code indicating the specified object doesn't exist. - * @constant - */ - OBJECT_NOT_FOUND: 101, - - /** - * Error code indicating you tried to query with a datatype that doesn't - * support it, like exact matching an array or object. - * @constant - */ - INVALID_QUERY: 102, - - /** - * Error code indicating a missing or invalid classname. Classnames are - * case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the - * only valid characters. - * @constant - */ - INVALID_CLASS_NAME: 103, - - /** - * Error code indicating an unspecified object id. - * @constant - */ - MISSING_OBJECT_ID: 104, - - /** - * Error code indicating an invalid key name. Keys are case-sensitive. They - * must start with a letter, and a-zA-Z0-9_ are the only valid characters. - * @constant - */ - INVALID_KEY_NAME: 105, - - /** - * Error code indicating a malformed pointer. You should not see this unless - * you have been mucking about changing internal Parse code. - * @constant - */ - INVALID_POINTER: 106, - - /** - * Error code indicating that badly formed JSON was received upstream. This - * either indicates you have done something unusual with modifying how - * things encode to JSON, or the network is failing badly. - * @constant - */ - INVALID_JSON: 107, - - /** - * Error code indicating that the feature you tried to access is only - * available internally for testing purposes. - * @constant - */ - COMMAND_UNAVAILABLE: 108, - - /** - * You must call Parse.initialize before using the Parse library. - * @constant - */ - NOT_INITIALIZED: 109, - - /** - * Error code indicating that a field was set to an inconsistent type. - * @constant - */ - INCORRECT_TYPE: 111, - - /** - * Error code indicating an invalid channel name. A channel name is either - * an empty string (the broadcast channel) or contains only a-zA-Z0-9_ - * characters and starts with a letter. - * @constant - */ - INVALID_CHANNEL_NAME: 112, - - /** - * Error code indicating that push is misconfigured. - * @constant - */ - PUSH_MISCONFIGURED: 115, - - /** - * Error code indicating that the object is too large. - * @constant - */ - OBJECT_TOO_LARGE: 116, - - /** - * Error code indicating that the operation isn't allowed for clients. - * @constant - */ - OPERATION_FORBIDDEN: 119, - - /** - * Error code indicating the result was not found in the cache. - * @constant - */ - CACHE_MISS: 120, - - /** - * Error code indicating that an invalid key was used in a nested - * JSONObject. - * @constant - */ - INVALID_NESTED_KEY: 121, - - /** - * Error code indicating that an invalid filename was used for ParseFile. - * A valid file name contains only a-zA-Z0-9_. characters and is between 1 - * and 128 characters. - * @constant - */ - INVALID_FILE_NAME: 122, - - /** - * Error code indicating an invalid ACL was provided. - * @constant - */ - INVALID_ACL: 123, - - /** - * Error code indicating that the request timed out on the server. Typically - * this indicates that the request is too expensive to run. - * @constant - */ - TIMEOUT: 124, - - /** - * Error code indicating that the email address was invalid. - * @constant - */ - INVALID_EMAIL_ADDRESS: 125, - - /** - * Error code indicating that a unique field was given a value that is - * already taken. - * @constant - */ - DUPLICATE_VALUE: 137, - - /** - * Error code indicating that a role's name is invalid. - * @constant - */ - INVALID_ROLE_NAME: 139, - - /** - * Error code indicating that an application quota was exceeded. Upgrade to - * resolve. - * @constant - */ - EXCEEDED_QUOTA: 140, - - /** - * Error code indicating that a Cloud Code script failed. - * @constant - */ - SCRIPT_FAILED: 141, - - /** - * Error code indicating that a Cloud Code validation failed. - * @constant - */ - VALIDATION_ERROR: 142, - - /** - * Error code indicating that the username is missing or empty. - * @constant - */ - USERNAME_MISSING: 200, - - /** - * Error code indicating that the password is missing or empty. - * @constant - */ - PASSWORD_MISSING: 201, - - /** - * Error code indicating that the username has already been taken. - * @constant - */ - USERNAME_TAKEN: 202, - - /** - * Error code indicating that the email has already been taken. - * @constant - */ - EMAIL_TAKEN: 203, - - /** - * Error code indicating that the email is missing, but must be specified. - * @constant - */ - EMAIL_MISSING: 204, - - /** - * Error code indicating that a user with the specified email was not found. - * @constant - */ - EMAIL_NOT_FOUND: 205, - - /** - * Error code indicating that a user object without a valid session could - * not be altered. - * @constant - */ - SESSION_MISSING: 206, - - /** - * Error code indicating that a user can only be created through signup. - * @constant - */ - MUST_CREATE_USER_THROUGH_SIGNUP: 207, - - /** - * Error code indicating that an an account being linked is already linked - * to another user. - * @constant - */ - ACCOUNT_ALREADY_LINKED: 208, - - /** - * Error code indicating that a user cannot be linked to an account because - * that account's id could not be found. - * @constant - */ - LINKED_ID_MISSING: 250, - - /** - * Error code indicating that a user with a linked (e.g. Facebook) account - * has an invalid session. - * @constant - */ - INVALID_LINKED_SESSION: 251, - - /** - * Error code indicating that a service being linked (e.g. Facebook or - * Twitter) is unsupported. - * @constant - */ - UNSUPPORTED_SERVICE: 252 - }); - -}(this)); - -/*global _: false */ -(function() { - var root = this; - var Parse = (root.Parse || (root.Parse = {})); - var eventSplitter = /\s+/; - var slice = Array.prototype.slice; - - /** - * @class - * - *

        Parse.Events is a fork of Backbone's Events module, provided for your - * convenience.

        - * - *

        A module that can be mixed in to any object in order to provide - * it with custom events. You may bind callback functions to an event - * with `on`, or remove these functions with `off`. - * Triggering an event fires all callbacks in the order that `on` was - * called. - * - *

        -   *     var object = {};
        -   *     _.extend(object, Parse.Events);
        -   *     object.on('expand', function(){ alert('expanded'); });
        -   *     object.trigger('expand');

        - * - *

        For more information, see the - * Backbone - * documentation.

        - */ - Parse.Events = { - /** - * Bind one or more space separated events, `events`, to a `callback` - * function. Passing `"all"` will bind the callback to all events fired. - */ - on: function(events, callback, context) { - - var calls, event, node, tail, list; - if (!callback) { - return this; - } - events = events.split(eventSplitter); - calls = this._callbacks || (this._callbacks = {}); - - // Create an immutable callback list, allowing traversal during - // modification. The tail is an empty object that will always be used - // as the next node. - event = events.shift(); - while (event) { - list = calls[event]; - node = list ? list.tail : {}; - node.next = tail = {}; - node.context = context; - node.callback = callback; - calls[event] = {tail: tail, next: list ? list.next : node}; - event = events.shift(); - } - - return this; - }, - - /** - * Remove one or many callbacks. If `context` is null, removes all callbacks - * with that function. If `callback` is null, removes all callbacks for the - * event. If `events` is null, removes all bound callbacks for all events. - */ - off: function(events, callback, context) { - var event, calls, node, tail, cb, ctx; - - // No events, or removing *all* events. - if (!(calls = this._callbacks)) { - return; - } - if (!(events || callback || context)) { - delete this._callbacks; - return this; - } - - // Loop through the listed events and contexts, splicing them out of the - // linked list of callbacks if appropriate. - events = events ? events.split(eventSplitter) : _.keys(calls); - event = events.shift(); - while (event) { - node = calls[event]; - delete calls[event]; - if (!node || !(callback || context)) { - continue; - } - // Create a new list, omitting the indicated callbacks. - tail = node.tail; - node = node.next; - while (node !== tail) { - cb = node.callback; - ctx = node.context; - if ((callback && cb !== callback) || (context && ctx !== context)) { - this.on(event, cb, ctx); - } - node = node.next; - } - event = events.shift(); - } - - return this; - }, - - /** - * Trigger one or many events, firing all bound callbacks. Callbacks are - * passed the same arguments as `trigger` is, apart from the event name - * (unless you're listening on `"all"`, which will cause your callback to - * receive the true name of the event as the first argument). - */ - trigger: function(events) { - var event, node, calls, tail, args, all, rest; - if (!(calls = this._callbacks)) { - return this; - } - all = calls.all; - events = events.split(eventSplitter); - rest = slice.call(arguments, 1); - - // For each event, walk through the linked list of callbacks twice, - // first to trigger the event, then to trigger any `"all"` callbacks. - event = events.shift(); - while (event) { - node = calls[event]; - if (node) { - tail = node.tail; - while ((node = node.next) !== tail) { - node.callback.apply(node.context || this, rest); - } - } - node = all; - if (node) { - tail = node.tail; - args = [event].concat(rest); - while ((node = node.next) !== tail) { - node.callback.apply(node.context || this, args); - } - } - event = events.shift(); - } - - return this; - } - }; - - /** - * @function - */ - Parse.Events.bind = Parse.Events.on; - - /** - * @function - */ - Parse.Events.unbind = Parse.Events.off; -}.call(this)); - - -/*global navigator: false */ -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - /** - * Creates a new GeoPoint with any of the following forms:
        - *
        -   *   new GeoPoint(otherGeoPoint)
        -   *   new GeoPoint(30, 30)
        -   *   new GeoPoint([30, 30])
        -   *   new GeoPoint({latitude: 30, longitude: 30})
        -   *   new GeoPoint()  // defaults to (0, 0)
        -   *   
        - * @class - * - *

        Represents a latitude / longitude point that may be associated - * with a key in a ParseObject or used as a reference point for geo queries. - * This allows proximity-based queries on the key.

        - * - *

        Only one key in a class may contain a GeoPoint.

        - * - *

        Example:

        -   *   var point = new Parse.GeoPoint(30.0, -20.0);
        -   *   var object = new Parse.Object("PlaceObject");
        -   *   object.set("location", point);
        -   *   object.save();

        - */ - Parse.GeoPoint = function(arg1, arg2) { - if (_.isArray(arg1)) { - Parse.GeoPoint._validate(arg1[0], arg1[1]); - this.latitude = arg1[0]; - this.longitude = arg1[1]; - } else if (_.isObject(arg1)) { - Parse.GeoPoint._validate(arg1.latitude, arg1.longitude); - this.latitude = arg1.latitude; - this.longitude = arg1.longitude; - } else if (_.isNumber(arg1) && _.isNumber(arg2)) { - Parse.GeoPoint._validate(arg1, arg2); - this.latitude = arg1; - this.longitude = arg2; - } else { - this.latitude = 0; - this.longitude = 0; - } - - // Add properties so that anyone using Webkit or Mozilla will get an error - // if they try to set values that are out of bounds. - var self = this; - if (this.__defineGetter__ && this.__defineSetter__) { - // Use _latitude and _longitude to actually store the values, and add - // getters and setters for latitude and longitude. - this._latitude = this.latitude; - this._longitude = this.longitude; - this.__defineGetter__("latitude", function() { - return self._latitude; - }); - this.__defineGetter__("longitude", function() { - return self._longitude; - }); - this.__defineSetter__("latitude", function(val) { - Parse.GeoPoint._validate(val, self.longitude); - self._latitude = val; - }); - this.__defineSetter__("longitude", function(val) { - Parse.GeoPoint._validate(self.latitude, val); - self._longitude = val; - }); - } - }; - - /** - * @lends Parse.GeoPoint.prototype - * @property {float} latitude North-south portion of the coordinate, in range - * [-90, 90]. Throws an exception if set out of range in a modern browser. - * @property {float} longitude East-west portion of the coordinate, in range - * [-180, 180]. Throws if set out of range in a modern browser. - */ - - /** - * Throws an exception if the given lat-long is out of bounds. - */ - Parse.GeoPoint._validate = function(latitude, longitude) { - if (latitude < -90.0) { - throw "Parse.GeoPoint latitude " + latitude + " < -90.0."; - } - if (latitude > 90.0) { - throw "Parse.GeoPoint latitude " + latitude + " > 90.0."; - } - if (longitude < -180.0) { - throw "Parse.GeoPoint longitude " + longitude + " < -180.0."; - } - if (longitude > 180.0) { - throw "Parse.GeoPoint longitude " + longitude + " > 180.0."; - } - }; - - /** - * Creates a GeoPoint with the user's current location, if available. - * Calls options.success with a new GeoPoint instance or calls options.error. - * @param {Object} options An object with success and error callbacks. - */ - Parse.GeoPoint.current = function(options) { - var success = function(location) { - if (options.success) { - options.success(new Parse.GeoPoint({ - latitude: location.coords.latitude, - longitude: location.coords.longitude - })); - } - }; - var error = function(e) { - if (options.error) { - options.error(e); - } - }; - navigator.geolocation.getCurrentPosition(success, error); - }; - - Parse.GeoPoint.prototype = { - /** - * Returns a JSON representation of the GeoPoint, suitable for Parse. - * @return {Object} - */ - toJSON: function() { - Parse.GeoPoint._validate(this.latitude, this.longitude); - return { - "__type": "GeoPoint", - latitude: this.latitude, - longitude: this.longitude - }; - }, - - /** - * Returns the distance from this GeoPoint to another in radians. - * @param {Parse.GeoPoint} point the other Parse.GeoPoint. - * @return {Number} - */ - radiansTo: function(point) { - var d2r = Math.PI / 180.0; - var lat1rad = this.latitude * d2r; - var long1rad = this.longitude * d2r; - var lat2rad = point.latitude * d2r; - var long2rad = point.longitude * d2r; - var deltaLat = lat1rad - lat2rad; - var deltaLong = long1rad - long2rad; - var sinDeltaLatDiv2 = Math.sin(deltaLat / 2); - var sinDeltaLongDiv2 = Math.sin(deltaLong / 2); - // Square of half the straight line chord distance between both points. - var a = ((sinDeltaLatDiv2 * sinDeltaLatDiv2) + - (Math.cos(lat1rad) * Math.cos(lat2rad) * - sinDeltaLongDiv2 * sinDeltaLongDiv2)); - a = Math.min(1.0, a); - return 2 * Math.asin(Math.sqrt(a)); - }, - - /** - * Returns the distance from this GeoPoint to another in kilometers. - * @param {Parse.GeoPoint} point the other Parse.GeoPoint. - * @return {Number} - */ - kilometersTo: function(point) { - return this.radiansTo(point) * 6371.0; - }, - - /** - * Returns the distance from this GeoPoint to another in miles. - * @param {Parse.GeoPoint} point the other Parse.GeoPoint. - * @return {Number} - */ - milesTo: function(point) { - return this.radiansTo(point) * 3958.8; - } - }; -}(this)); - -/*global navigator: false */ -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - var PUBLIC_KEY = "*"; - - /** - * Creates a new ACL. - * If no argument is given, the ACL has no permissions for anyone. - * If the argument is a Parse.User, the ACL will have read and write - * permission for only that user. - * If the argument is any other JSON object, that object will be interpretted - * as a serialized ACL created with toJSON(). - * @see Parse.Object#setACL - * @class - * - *

        An ACL, or Access Control List can be added to any - * Parse.Object to restrict access to only a subset of users - * of your application.

        - */ - Parse.ACL = function(arg1) { - var self = this; - self.permissionsById = {}; - if (_.isObject(arg1)) { - if (arg1 instanceof Parse.User) { - self.setReadAccess(arg1, true); - self.setWriteAccess(arg1, true); - } else { - if (_.isFunction(arg1)) { - throw "Parse.ACL() called with a function. Did you forget ()?"; - } - Parse._each(arg1, function(accessList, userId) { - if (!_.isString(userId)) { - throw "Tried to create an ACL with an invalid userId."; - } - self.permissionsById[userId] = {}; - Parse._each(accessList, function(allowed, permission) { - if (permission !== "read" && permission !== "write") { - throw "Tried to create an ACL with an invalid permission type."; - } - if (!_.isBoolean(allowed)) { - throw "Tried to create an ACL with an invalid permission value."; - } - self.permissionsById[userId][permission] = allowed; - }); - }); - } - } - }; - - /** - * Returns a JSON-encoded version of the ACL. - * @return {Object} - */ - Parse.ACL.prototype.toJSON = function() { - return _.clone(this.permissionsById); - }; - - Parse.ACL.prototype._setAccess = function(accessType, userId, allowed) { - if (userId instanceof Parse.User) { - userId = userId.id; - } else if (userId instanceof Parse.Role) { - userId = "role:" + userId.getName(); - } - if (!_.isString(userId)) { - throw "userId must be a string."; - } - if (!_.isBoolean(allowed)) { - throw "allowed must be either true or false."; - } - var permissions = this.permissionsById[userId]; - if (!permissions) { - if (!allowed) { - // The user already doesn't have this permission, so no action needed. - return; - } else { - permissions = {}; - this.permissionsById[userId] = permissions; - } - } - - if (allowed) { - this.permissionsById[userId][accessType] = true; - } else { - delete permissions[accessType]; - if (_.isEmpty(permissions)) { - delete permissions[userId]; - } - } - }; - - Parse.ACL.prototype._getAccess = function(accessType, userId) { - if (userId instanceof Parse.User) { - userId = userId.id; - } else if (userId instanceof Parse.Role) { - userId = "role:" + userId.getName(); - } - var permissions = this.permissionsById[userId]; - if (!permissions) { - return false; - } - return permissions[accessType] ? true : false; - }; - - /** - * Set whether the given user is allowed to read this object. - * @param userId An instance of Parse.User or its objectId. - * @param {Boolean} allowed Whether that user should have read access. - */ - Parse.ACL.prototype.setReadAccess = function(userId, allowed) { - this._setAccess("read", userId, allowed); - }; - - /** - * Get whether the given user id is *explicitly* allowed to read this object. - * Even if this returns false, the user may still be able to access it if - * getPublicReadAccess returns true or a role that the user belongs to has - * write access. - * @param userId An instance of Parse.User or its objectId, or a Parse.Role. - * @return {Boolean} - */ - Parse.ACL.prototype.getReadAccess = function(userId) { - return this._getAccess("read", userId); - }; - - /** - * Set whether the given user id is allowed to write this object. - * @param userId An instance of Parse.User or its objectId, or a Parse.Role.. - * @param {Boolean} allowed Whether that user should have write access. - */ - Parse.ACL.prototype.setWriteAccess = function(userId, allowed) { - this._setAccess("write", userId, allowed); - }; - - /** - * Get whether the given user id is *explicitly* allowed to write this object. - * Even if this returns false, the user may still be able to write it if - * getPublicWriteAccess returns true or a role that the user belongs to has - * write access. - * @param userId An instance of Parse.User or its objectId, or a Parse.Role. - * @return {Boolean} - */ - Parse.ACL.prototype.getWriteAccess = function(userId) { - return this._getAccess("write", userId); - }; - - /** - * Set whether the public is allowed to read this object. - * @param {Boolean} allowed - */ - Parse.ACL.prototype.setPublicReadAccess = function(allowed) { - this.setReadAccess(PUBLIC_KEY, allowed); - }; - - /** - * Get whether the public is allowed to read this object. - * @return {Boolean} - */ - Parse.ACL.prototype.getPublicReadAccess = function() { - return this.getReadAccess(PUBLIC_KEY); - }; - - /** - * Set whether the public is allowed to write this object. - * @param {Boolean} allowed - */ - Parse.ACL.prototype.setPublicWriteAccess = function(allowed) { - this.setWriteAccess(PUBLIC_KEY, allowed); - }; - - /** - * Get whether the public is allowed to write this object. - * @return {Boolean} - */ - Parse.ACL.prototype.getPublicWriteAccess = function() { - return this.getWriteAccess(PUBLIC_KEY); - }; - - /** - * Get whether users belonging to the given role are allowed - * to read this object. Even if this returns false, the role may - * still be able to write it if a parent role has read access. - * - * @param role The name of the role, or a Parse.Role object. - * @return {Boolean} true if the role has read access. false otherwise. - * @throws {String} If role is neither a Parse.Role nor a String. - */ - Parse.ACL.prototype.getRoleReadAccess = function(role) { - if (role instanceof Parse.Role) { - // Normalize to the String name - role = role.getName(); - } - if (_.isString(role)) { - return this.getReadAccess("role:" + role); - } - throw "role must be a Parse.Role or a String"; - }; - - /** - * Get whether users belonging to the given role are allowed - * to write this object. Even if this returns false, the role may - * still be able to write it if a parent role has write access. - * - * @param role The name of the role, or a Parse.Role object. - * @return {Boolean} true if the role has write access. false otherwise. - * @throws {String} If role is neither a Parse.Role nor a String. - */ - Parse.ACL.prototype.getRoleWriteAccess = function(role) { - if (role instanceof Parse.Role) { - // Normalize to the String name - role = role.getName(); - } - if (_.isString(role)) { - return this.getWriteAccess("role:" + role); - } - throw "role must be a Parse.Role or a String"; - }; - - /** - * Set whether users belonging to the given role are allowed - * to read this object. - * - * @param role The name of the role, or a Parse.Role object. - * @param {Boolean} allowed Whether the given role can read this object. - * @throws {String} If role is neither a Parse.Role nor a String. - */ - Parse.ACL.prototype.setRoleReadAccess = function(role, allowed) { - if (role instanceof Parse.Role) { - // Normalize to the String name - role = role.getName(); - } - if (_.isString(role)) { - this.setReadAccess("role:" + role, allowed); - return; - } - throw "role must be a Parse.Role or a String"; - }; - - /** - * Set whether users belonging to the given role are allowed - * to write this object. - * - * @param role The name of the role, or a Parse.Role object. - * @param {Boolean} allowed Whether the given role can write this object. - * @throws {String} If role is neither a Parse.Role nor a String. - */ - Parse.ACL.prototype.setRoleWriteAccess = function(role, allowed) { - if (role instanceof Parse.Role) { - // Normalize to the String name - role = role.getName(); - } - if (_.isString(role)) { - this.setWriteAccess("role:" + role, allowed); - return; - } - throw "role must be a Parse.Role or a String"; - }; - -}(this)); - -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - /** - * @class - * A Parse.Op is an atomic operation that can be applied to a field in a - * Parse.Object. For example, calling object.set("foo", "bar") - * is an example of a Parse.Op.Set. Calling object.unset("foo") - * is a Parse.Op.Unset. These operations are stored in a Parse.Object and - * sent to the server as part of object.save() operations. - * Instances of Parse.Op should be immutable. - * - * You should not create subclasses of Parse.Op or instantiate Parse.Op - * directly. - */ - Parse.Op = function() { - this._initialize.apply(this, arguments); - }; - - Parse.Op.prototype = { - _initialize: function() {} - }; - - _.extend(Parse.Op, { - /** - * To create a new Op, call Parse.Op._extend(); - */ - _extend: Parse._extend, - - // A map of __op string to decoder function. - _opDecoderMap: {}, - - /** - * Registers a function to convert a json object with an __op field into an - * instance of a subclass of Parse.Op. - */ - _registerDecoder: function(opName, decoder) { - Parse.Op._opDecoderMap[opName] = decoder; - }, - - /** - * Converts a json object into an instance of a subclass of Parse.Op. - */ - _decode: function(json) { - var decoder = Parse.Op._opDecoderMap[json.__op]; - if (decoder) { - return decoder(json); - } else { - return undefined; - } - } - }); - - /* - * Add a handler for Batch ops. - */ - Parse.Op._registerDecoder("Batch", function(json) { - var op = null; - _.each(json.ops, function(nextOp) { - nextOp = Parse.Op._decode(nextOp); - op = nextOp._mergeWithPrevious(op); - }); - return op; - }); - - /** - * @class - * A Set operation indicates that either the field was changed using - * Parse.Object.set, or it is a mutable container that was detected as being - * changed. - */ - Parse.Op.Set = Parse.Op._extend(/** @lends Parse.Op.Set.prototype */ { - _initialize: function(value) { - this._value = value; - }, - - /** - * Returns the new value of this field after the set. - */ - value: function() { - return this._value; - }, - - /** - * Returns a JSON version of the operation suitable for sending to Parse. - * @return {Object} - */ - toJSON: function() { - return Parse._encode(this.value()); - }, - - _mergeWithPrevious: function(previous) { - return this; - }, - - _estimate: function(oldValue) { - return this.value(); - } - }); - - /** - * A sentinel value that is returned by Parse.Op.Unset._estimate to - * indicate the field should be deleted. Basically, if you find _UNSET as a - * value in your object, you should remove that key. - */ - Parse.Op._UNSET = {}; - - /** - * @class - * An Unset operation indicates that this field has been deleted from the - * object. - */ - Parse.Op.Unset = Parse.Op._extend(/** @lends Parse.Op.Unset.prototype */ { - /** - * Returns a JSON version of the operation suitable for sending to Parse. - * @return {Object} - */ - toJSON: function() { - return { __op: "Delete" }; - }, - - _mergeWithPrevious: function(previous) { - return this; - }, - - _estimate: function(oldValue) { - return Parse.Op._UNSET; - } - }); - - Parse.Op._registerDecoder("Delete", function(json) { - return new Parse.Op.Unset(); - }); - - /** - * @class - * An Increment is an atomic operation where the numeric value for the field - * will be increased by a given amount. - */ - Parse.Op.Increment = Parse.Op._extend( - /** @lends Parse.Op.Increment.prototype */ { - - _initialize: function(amount) { - this._amount = amount; - }, - - /** - * Returns the amount to increment by. - * @return {Number} the amount to increment by. - */ - amount: function() { - return this._amount; - }, - - /** - * Returns a JSON version of the operation suitable for sending to Parse. - * @return {Object} - */ - toJSON: function() { - return { __op: "Increment", amount: this._amount }; - }, - - _mergeWithPrevious: function(previous) { - if (!previous) { - return this; - } else if (previous instanceof Parse.Op.Unset) { - return new Parse.Op.Set(this.amount()); - } else if (previous instanceof Parse.Op.Set) { - return new Parse.Op.Set(previous.value() + this.amount()); - } else if (previous instanceof Parse.Op.Increment) { - return new Parse.Op.Increment(this.amount() + previous.amount()); - } else { - throw "Op is invalid after previous op."; - } - }, - - _estimate: function(oldValue) { - if (!oldValue) { - return this.amount(); - } - return oldValue + this.amount(); - } - }); - - Parse.Op._registerDecoder("Increment", function(json) { - return new Parse.Op.Increment(json.amount); - }); - - /** - * @class - * Add is an atomic operation where the given objects will be appended to the - * array that is stored in this field. - */ - Parse.Op.Add = Parse.Op._extend(/** @lends Parse.Op.Add.prototype */ { - _initialize: function(objects) { - this._objects = objects; - }, - - /** - * Returns the objects to be added to the array. - * @return {Array} The objects to be added to the array. - */ - objects: function() { - return this._objects; - }, - - /** - * Returns a JSON version of the operation suitable for sending to Parse. - * @return {Object} - */ - toJSON: function() { - return { __op: "Add", objects: Parse._encode(this.objects()) }; - }, - - _mergeWithPrevious: function(previous) { - if (!previous) { - return this; - } else if (previous instanceof Parse.Op.Unset) { - return new Parse.Op.Set(this.objects()); - } else if (previous instanceof Parse.Op.Set) { - return new Parse.Op.Set(this._estimate(previous.value())); - } else if (previous instanceof Parse.Op.Add) { - return new Parse.Op.Add(previous.objects().concat(this.objects())); - } else { - throw "Op is invalid after previous op."; - } - }, - - _estimate: function(oldValue) { - if (!oldValue) { - return _.clone(this.objects()); - } else { - return oldValue.concat(this.objects()); - } - } - }); - - Parse.Op._registerDecoder("Add", function(json) { - return new Parse.Op.Add(Parse._decode(undefined, json.objects)); - }); - - /** - * @class - * AddUnique is an atomic operation where the given items will be appended to - * the array that is stored in this field only if they were not already - * present in the array. - */ - Parse.Op.AddUnique = Parse.Op._extend( - /** @lends Parse.Op.AddUnique.prototype */ { - - _initialize: function(objects) { - this._objects = _.uniq(objects); - }, - - /** - * Returns the objects to be added to the array. - * @return {Array} The objects to be added to the array. - */ - objects: function() { - return this._objects; - }, - - /** - * Returns a JSON version of the operation suitable for sending to Parse. - * @return {Object} - */ - toJSON: function() { - return { __op: "AddUnique", objects: Parse._encode(this.objects()) }; - }, - - _mergeWithPrevious: function(previous) { - if (!previous) { - return this; - } else if (previous instanceof Parse.Op.Unset) { - return new Parse.Op.Set(this.objects()); - } else if (previous instanceof Parse.Op.Set) { - return new Parse.Op.Set(this._estimate(previous.value())); - } else if (previous instanceof Parse.Op.AddUnique) { - return new Parse.Op.AddUnique( - _.union(previous.objects(), this.objects())); - } else { - throw "Op is invalid after previous op."; - } - }, - - _estimate: function(oldValue) { - if (!oldValue) { - return _.clone(this.objects()); - } else { - return oldValue.concat(_.difference(this.objects(), oldValue)); - } - } - }); - - Parse.Op._registerDecoder("AddUnique", function(json) { - return new Parse.Op.AddUnique(Parse._decode(undefined, json.objects)); - }); - - /** - * @class - * Remove is an atomic operation where the given objects will be removed from - * the array that is stored in this field. - */ - Parse.Op.Remove = Parse.Op._extend(/** @lends Parse.Op.Remove.prototype */ { - _initialize: function(objects) { - this._objects = _.uniq(objects); - }, - - /** - * Returns the objects to be removed from the array. - * @return {Array} The objects to be removed from the array. - */ - objects: function() { - return this._objects; - }, - - /** - * Returns a JSON version of the operation suitable for sending to Parse. - * @return {Object} - */ - toJSON: function() { - return { __op: "Remove", objects: Parse._encode(this.objects()) }; - }, - - _mergeWithPrevious: function(previous) { - if (!previous) { - return this; - } else if (previous instanceof Parse.Op.Unset) { - return previous; - } else if (previous instanceof Parse.Op.Set) { - return new Parse.Op.Set(this._estimate(previous.value())); - } else if (previous instanceof Parse.Op.Remove) { - return new Parse.Op.Remove(_.union(previous.objects(), this.objects())); - } else { - throw "Op is invalid after previous op."; - } - }, - - _estimate: function(oldValue) { - if (!oldValue) { - return []; - } else { - var newValue = _.difference(oldValue, this.objects()); - // If there are saved Parse Objects being removed, also remove them. - _.each(this.objects(), function(obj) { - if (obj instanceof Parse.Object && obj.id) { - newValue = _.reject(newValue, function(other) { - return (other instanceof Parse.Object) && (other.id === obj.id); - }); - } - }); - return newValue; - } - } - }); - - Parse.Op._registerDecoder("Remove", function(json) { - return new Parse.Op.Remove(Parse._decode(undefined, json.objects)); - }); - - /** - * @class - * A Relation operation indicates that the field is an instance of - * Parse.Relation, and objects are being added to, or removed from, that - * relation. - */ - Parse.Op.Relation = Parse.Op._extend( - /** @lends Parse.Op.Relation.prototype */ { - - _initialize: function(adds, removes) { - this._targetClassName = null; - - var self = this; - - var pointerToId = function(object) { - if (object instanceof Parse.Object) { - if (!object.id) { - throw "You can't add an unsaved Parse.Object to a relation."; - } - if (!self._targetClassName) { - self._targetClassName = object.className; - } - if (self._targetClassName !== object.className) { - throw "Tried to create a Parse.Relation with 2 different types: " + - self._targetClassName + " and " + object.className + "."; - } - return object.id; - } - return object; - }; - - this.relationsToAdd = _.uniq(_.map(adds, pointerToId)); - this.relationsToRemove = _.uniq(_.map(removes, pointerToId)); - }, - - /** - * Returns an array of unfetched Parse.Object that are being added to the - * relation. - * @return {Array} - */ - added: function() { - var self = this; - return _.map(this.relationsToAdd, function(objectId) { - var object = Parse.Object._create(self._targetClassName); - object.id = objectId; - return object; - }); - }, - - /** - * Returns an array of unfetched Parse.Object that are being removed from - * the relation. - * @return {Array} - */ - removed: function() { - var self = this; - return _.map(this.relationsToRemove, function(objectId) { - var object = Parse.Object._create(self._targetClassName); - object.id = objectId; - return object; - }); - }, - - /** - * Returns a JSON version of the operation suitable for sending to Parse. - * @return {Object} - */ - toJSON: function() { - var adds = null; - var removes = null; - var self = this; - var idToPointer = function(id) { - return { __type: 'Pointer', - className: self._targetClassName, - objectId: id }; - }; - var pointers = null; - if (this.relationsToAdd.length > 0) { - pointers = _.map(this.relationsToAdd, idToPointer); - adds = { "__op": "AddRelation", "objects": pointers }; - } - - if (this.relationsToRemove.length > 0) { - pointers = _.map(this.relationsToRemove, idToPointer); - removes = { "__op": "RemoveRelation", "objects": pointers }; - } - - if (adds && removes) { - return { "__op": "Batch", "ops": [adds, removes]}; - } - - return adds || removes || {}; - }, - - _mergeWithPrevious: function(previous) { - if (!previous) { - return this; - } else if (previous instanceof Parse.Op.Unset) { - throw "You can't modify a relation after deleting it."; - } else if (previous instanceof Parse.Op.Relation) { - if (previous._targetClassName && - previous._targetClassName !== this._targetClassName) { - throw "Related object must be of class " + previous._targetClassName + - ", but " + this._targetClassName + " was passed in."; - } - var newAdd = _.union(_.difference(previous.relationsToAdd, - this.relationsToRemove), - this.relationsToAdd); - var newRemove = _.union(_.difference(previous.relationsToRemove, - this.relationsToAdd), - this.relationsToRemove); - - var newRelation = new Parse.Op.Relation(newAdd, newRemove); - newRelation._targetClassName = this._targetClassName; - return newRelation; - } else { - throw "Op is invalid after previous op."; - } - }, - - _estimate: function(oldValue, object, key) { - if (!oldValue) { - var relation = new Parse.Relation(object, key); - relation.targetClassName = this._targetClassName; - } else if (oldValue instanceof Parse.Relation) { - if (this._targetClassName) { - if (oldValue.targetClassName) { - if (oldValue.targetClassName !== this._targetClassName) { - throw "Related object must be a " + oldValue.targetClassName + - ", but a " + this._targetClassName + " was passed in."; - } - } else { - oldValue.targetClassName = this._targetClassName; - } - } - return oldValue; - } else { - throw "Op is invalid after previous op."; - } - } - }); - - Parse.Op._registerDecoder("AddRelation", function(json) { - return new Parse.Op.Relation(Parse._decode(undefined, json.objects), []); - }); - Parse.Op._registerDecoder("RemoveRelation", function(json) { - return new Parse.Op.Relation([], Parse._decode(undefined, json.objects)); - }); - -}(this)); - -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - /** - * Creates a new Relation for the given parent object and key. This - * constructor should rarely be used directly, but rather created by - * Parse.Object.relation. - * @param {Parse.Object} parent The parent of this relation. - * @param {String} key The key for this relation on the parent. - * @see Parse.Object#relation - * @class - * - *

        - * A class that is used to access all of the children of a many-to-many - * relationship. Each instance of Parse.Relation is associated with a - * particular parent object and key. - *

        - */ - Parse.Relation = function(parent, key) { - this.parent = parent; - this.key = key; - this.targetClassName = null; - }; - - Parse.Relation.prototype = { - /** - * Makes sure that this relation has the right parent and key. - */ - _ensureParentAndKey: function(parent, key) { - this.parent = this.parent || parent; - this.key = this.key || key; - if (this.parent !== parent) { - throw "Internal Error. Relation retrieved from two different Objects."; - } - if (this.key !== key) { - throw "Internal Error. Relation retrieved from two different keys."; - } - }, - - /** - * Adds a Parse.Object or an array of Parse.Objects to the relation. - * @param {} objects The item or items to add. - */ - add: function(objects) { - if (!_.isArray(objects)) { - objects = [objects]; - } - - var change = new Parse.Op.Relation(objects, []); - this.parent.set(this.key, change); - this.targetClassName = change._targetClassName; - }, - - /** - * Removes a Parse.Object or an array of Parse.Objects from this relation. - * @param {} objects The item or items to remove. - */ - remove: function(objects) { - if (!_.isArray(objects)) { - objects = [objects]; - } - - var change = new Parse.Op.Relation([], objects); - this.parent.set(this.key, change); - this.targetClassName = change._targetClassName; - }, - - /** - * Returns a JSON version of the object suitable for saving to disk. - * @return {Object} - */ - toJSON: function() { - return { "__type": "Relation", "className": this.targetClassName }; - }, - - /** - * Returns a Parse.Query that is limited to objects in this - * relation. - * @return {Parse.Query} - */ - query: function() { - var targetClass; - var query; - if (!this.targetClassName) { - targetClass = Parse.Object._getSubclass(this.parent.className); - query = new Parse.Query(targetClass); - query._extraOptions.redirectClassNameForKey = this.key; - } else { - targetClass = Parse.Object._getSubclass(this.targetClassName); - query = new Parse.Query(targetClass); - } - query._addCondition("$relatedTo", "object", this.parent._toPointer()); - query._addCondition("$relatedTo", "key", this.key); - - return query; - } - }; -}(this)); - -// Parse.Object is analogous to the Java ParseObject. -// It also implements the same interface as a Backbone model. - -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - /** - * Creates a new model with defined attributes. A client id (cid) is - * automatically generated and assigned for you. - * - *

        You won't normally call this method directly. It is recommended that - * you use a subclass of Parse.Object instead, created by calling - * extend.

        - * - *

        However, if you don't want to use a subclass, or aren't sure which - * subclass is appropriate, you can use this form:

        -   *     var object = new Parse.Object("ClassName");
        -   * 
        - * That is basically equivalent to:
        -   *     var MyClass = Parse.Object.extend("ClassName");
        -   *     var object = new MyClass();
        -   * 

        - * - * @param {Object} attributes The initial set of data to store in the object. - * @param {Object} options A set of Backbone-like options for creating the - * object. The only option currently supported is "collection". - * @see Parse.Object.extend - * - * @class - * - *

        The fundamental unit of Parse data, which implements the Backbone Model - * interface.

        - */ - Parse.Object = function(attributes, options) { - // Allow new Parse.Object("ClassName") as a shortcut to _create. - if (_.isString(attributes)) { - return Parse.Object._create.apply(this, arguments); - } - - attributes = attributes || {}; - if (options && options.parse) { - attributes = this.parse(attributes); - } - var defaults = Parse._getValue(this, 'defaults'); - if (defaults) { - attributes = _.extend({}, defaults, attributes); - } - if (options && options.collection) { - this.collection = options.collection; - } - - this._serverData = {}; // The last known data for this object from cloud. - this._opSetQueue = [{}]; // List of sets of changes to the data. - this.attributes = {}; // The best estimate of this's current data. - - this._hashedJSON = {}; // Hash of values of containers at last save. - this._escapedAttributes = {}; - this.cid = _.uniqueId('c'); - this.changed = {}; - this._silent = {}; - this._pending = {}; - if (!this.set(attributes, {silent: true})) { - throw new Error("Can't create an invalid Parse.Object"); - } - this.changed = {}; - this._silent = {}; - this._pending = {}; - this._hasData = true; - this._previousAttributes = _.clone(this.attributes); - this.initialize.apply(this, arguments); - }; - - /** - * @lends Parse.Object.prototype - * @property {String} id The objectId of the Parse Object. - */ - - /** - * Internal function for saveAll. This calls func on every item in list, - * and adds the results to results. When it's done, optionsOrCallback is - * called with the accumulated results. See saveAll for more info. - * - * @param list - A list of Parse.Object. - * @param func - function(Parse.Object, callback); - * @param results - List of results. Should be [] for non-recursion. - * @param optionsOrCallback - See saveAll. - */ - var _doAll = function(list, func, results, optionsOrCallback) { - results = results || []; - var options; - if (_.isFunction(optionsOrCallback)) { - var callback = optionsOrCallback; - options = { - success: function(list) { callback(list, null); }, - error: function(e) { callback(null, e); } - }; - } else { - options = optionsOrCallback; - } - if (list.length) { - var oldOptions = options; - var newOptions = options ? _.clone(options) : {}; - newOptions.success = function(model, response) { - results.push(model); - _doAll(_.rest(list), func, results, oldOptions); - }; - func.call(this, _.first(list), newOptions); - } else { - if (options.success) { - options.success(results); - } - } - }; - - /** - * Saves the given list of Parse.Object. - * If any error is encountered, stops and calls the error handler. - * There are two ways you can call this function. - * - * The Backbone way:
        -   *   Parse.Object.saveAll([object1, object2, ...], {
        -   *     success: function(list) {
        -   *       // All the objects were saved.
        -   *     },
        -   *     error: function(error) {
        -   *       // An error occurred while saving one of the objects.
        -   *     },
        -   *   });
        -   * 
        - * A simplified syntax:
        -   *   Parse.Object.saveAll([object1, object2, ...], function(list, error) {
        -   *     if (list) {
        -   *       // All the objects were saved.
        -   *     } else {
        -   *       // An error occurred.
        -   *     }
        -   *   });
        -   * 
        - * - * @param {Array} list A list of Parse.Object. - * @param {Object} optionsOrCallback A Backbone-style callback object. - */ - Parse.Object.saveAll = function(list, optionsOrCallback) { - _doAll(list, function(obj, options) { - obj.save(null, options); - }, [], optionsOrCallback); - }; - - Parse.Object._signUpAll = function(list, optionsOrCallback) { - _doAll(list, function(obj, options) { - obj.signUp(null, options); - }, [], optionsOrCallback); - }; - - // Attach all inheritable methods to the Parse.Object prototype. - _.extend(Parse.Object.prototype, Parse.Events, - /** @lends Parse.Object.prototype */ { - _existed: false, - - /** - * Initialize is an empty function by default. Override it with your own - * initialization logic. - */ - initialize: function(){}, - - /** - * Returns a JSON version of the object suitable for saving to Parse. - * @return {Object} - */ - toJSON: function() { - var json = this._toFullJSON(); - _.each(["__type", "className"], - function(key) { delete json[key]; }); - return json; - }, - - _toFullJSON: function(seenObjects) { - var json = _.clone(this.attributes); - Parse._each(json, function(val, key) { - json[key] = Parse._encode(val, seenObjects); - }); - Parse._each(this._operations, function(val, key) { - json[key] = val; - }); - - if (_.has(this, "id")) { - json.objectId = this.id; - } - if (_.has(this, "createdAt")) { - if (_.isDate(this.createdAt)) { - json.createdAt = this.createdAt.toJSON(); - } else { - json.createdAt = this.createdAt; - } - } - - if (_.has(this, "updatedAt")) { - if (_.isDate(this.updatedAt)) { - json.updatedAt = this.updatedAt.toJSON(); - } else { - json.updatedAt = this.updatedAt; - } - } - json.__type = "Object"; - json.className = this.className; - return json; - }, - - /** - * Updates _hashedJSON to reflect the current state of this object. - * Adds any changed hash values to the set of pending changes. - */ - _refreshCache: function() { - var self = this; - Parse._each(this.attributes, function(value, key) { - if (value instanceof Parse.Object) { - value._refreshCache(); - } else if (_.isObject(value)) { - if (self._resetCacheForKey(key)) { - self.set(key, new Parse.Op.Set(value), { silent: true }); - } - } - }); - }, - - /** - * Returns true if this object has been modified since its last - * save/refresh. If an attribute is specified, it returns true only if that - * particular attribute has been modified since the last save/refresh. - * @param {String} attr An attribute name (optional). - * @return {Boolean} - */ - dirty: function(attr) { - this._refreshCache(); - - var currentChanges = _.last(this._opSetQueue); - - if (attr) { - return (currentChanges[attr] ? true : false); - } - if (!this.id) { - return true; - } - if (_.keys(currentChanges).length > 0) { - return true; - } - return false; - }, - - /** - * Gets a Pointer referencing this Object. - */ - _toPointer: function() { - if (!this.id) { - throw new Error("Can't serialize an unsaved Parse.Object"); - } - return { __type: "Pointer", - className: this.className, - objectId: this.id }; - }, - - /** - * Gets the value of an attribute. - * @param {String} attr The string name of an attribute. - */ - get: function(attr) { - return this.attributes[attr]; - }, - - /** - * Gets a relation on the given class for the attribute. - * @param String attr The attribute to get the relation for. - */ - relation: function(attr) { - var value = this.get(attr); - if (value) { - if (!(value instanceof Parse.Relation)) { - throw "Called relation() on non-relation field " + attr; - } - value._ensureParentAndKey(this, attr); - return value; - } else { - return new Parse.Relation(this, attr); - } - }, - - /** - * Gets the HTML-escaped value of an attribute. - */ - escape: function(attr) { - var html = this._escapedAttributes[attr]; - if (html) { - return html; - } - var val = this.attributes[attr]; - var escaped; - if (Parse._isNullOrUndefined(val)) { - escaped = ''; - } else { - escaped = _.escape(val.toString()); - } - this._escapedAttributes[attr] = escaped; - return escaped; - }, - - /** - * Returns true if the attribute contains a value that is not - * null or undefined. - * @param {String} attr The string name of the attribute. - * @return {Boolean} - */ - has: function(attr) { - return !Parse._isNullOrUndefined(this.attributes[attr]); - }, - - /** - * Pulls "special" fields like objectId, createdAt, etc. out of attrs - * and puts them on "this" directly. Removes them from attrs. - * @param attrs - A dictionary with the data for this Parse.Object. - */ - _mergeMagicFields: function(attrs) { - // Check for changes of magic fields. - var model = this; - _.each(["id", "objectId", "createdAt", "updatedAt"], function(attr) { - if (attrs[attr]) { - if (attr === "objectId") { - model.id = attrs[attr]; - } else if ((attr === "createdAt" || attr === "updatedAt") && - !_.isDate(attrs[attr])) { - model[attr] = Parse._parseDate(attrs[attr]); - } else { - model[attr] = attrs[attr]; - } - delete attrs[attr]; - } - }); - }, - - /** - * Returns the json to be sent to the server. - */ - _startSave: function() { - this._opSetQueue.push({}); - }, - - /** - * If any save has been started since the current one running, process the - * next one in the queue. - */ - _processSaveQueue: function() { - if (this._saveQueue && this._saveQueue.length > 0) { - var nextSave = _.first(this._saveQueue); - this._saveQueue = _.rest(this._saveQueue); - nextSave(); - } else { - this._saving = false; - } - }, - - /** - * Called when a save fails because of an error. Any changes that were part - * of the save need to be merged with changes made after the save. This - * might throw an exception is you do conflicting operations. For example, - * if you do: - * object.set("foo", "bar"); - * object.set("invalid field name", "baz"); - * object.save(); - * object.increment("foo"); - * then this will throw when the save fails and the client tries to merge - * "bar" with the +1. - */ - _cancelSave: function() { - var self = this; - var failedChanges = _.first(this._opSetQueue); - this._opSetQueue = _.rest(this._opSetQueue); - var nextChanges = _.first(this._opSetQueue); - Parse._each(failedChanges, function(op, key) { - var op1 = failedChanges[key]; - var op2 = nextChanges[key]; - if (op1 && op2) { - nextChanges[key] = op2._mergeWithPrevious(op1); - } else if (op1) { - nextChanges[key] = op1; - } - }); - this._processSaveQueue(); - }, - - /** - * Called when a save completes successfully. This merges the changes that - * were saved into the known server data, and overrides it with any data - * sent directly from the server. - */ - _finishSave: function(serverData) { - var savedChanges = _.first(this._opSetQueue); - this._opSetQueue = _.rest(this._opSetQueue); - this._applyOpSet(savedChanges, this._serverData); - this._mergeMagicFields(serverData); - var self = this; - Parse._each(serverData, function(value, key) { - self._serverData[key] = Parse._decode(key, value); - }); - this._rebuildAllEstimatedData(); - this._processSaveQueue(); - }, - - /** - * Called when a fetch or login is complete to set the known server data to - * the given object. - */ - _finishFetch: function(serverData, hasData) { - // Clear out any changes the user might have made previously. - this._opSetQueue = [{}]; - - // Bring in all the new server data. - this._mergeMagicFields(serverData); - var self = this; - Parse._each(serverData, function(value, key) { - self._serverData[key] = Parse._decode(key, value); - }); - - // Refresh the attributes. - this._rebuildAllEstimatedData(); - - // Clear out the cache of mutable containers. - this._refreshCache(); - this._opSetQueue = [{}]; - - this._hasData = hasData; - }, - - /** - * Applies the set of Parse.Op in opSet to the object target. - */ - _applyOpSet: function(opSet, target) { - var self = this; - Parse._.each(opSet, function(change, key) { - target[key] = change._estimate(target[key], self, key); - if (target[key] === Parse.Op._UNSET) { - delete target[key]; - } - }); - }, - - /** - * Replaces the cached value for key with the current value. - * Returns true if the new value is different than the old value. - */ - _resetCacheForKey: function(key) { - var value = this.attributes[key]; - if (_.isObject(value) && !(value instanceof Parse.Object)) { - value = value.toJSON ? value.toJSON() : value; - var json = JSON.stringify(value); - if (this._hashedJSON[key] !== json) { - this._hashedJSON[key] = json; - return true; - } - } - return false; - }, - - /** - * Populates attributes[key] by starting with the last known data from the - * server, and applying all of the local changes that have been made to that - * key since then. - */ - _rebuildEstimatedDataForKey: function(key) { - var self = this; - delete this.attributes[key]; - if (this._serverData[key]) { - this.attributes[key] = this._serverData[key]; - } - _.each(this._opSetQueue, function(opSet) { - var op = opSet[key]; - if (op) { - self.attributes[key] = op._estimate(self.attributes[key], self, key); - if (self.attributes[key] === Parse.Op._UNSET) { - delete self.attributes[key]; - } else { - self._resetCacheForKey(key); - } - } - }); - }, - - /** - * Populates attributes by starting with the last known data from the - * server, and applying all of the local changes that have been made since - * then. - */ - _rebuildAllEstimatedData: function() { - var self = this; - this.attributes = _.clone(this._serverData); - _.each(this._opSetQueue, function(opSet) { - self._applyOpSet(opSet, self.attributes); - _.each(opSet, function(op, key) { - self._resetCacheForKey(key); - }); - }); - }, - - /** - * Sets a hash of model attributes on the object, firing - * "change" unless you choose to silence it. - * - *

        You can call it with an object containing keys and values, or with one - * key and value. For example:

        -     *   gameTurn.set({
        -     *     player: player1,
        -     *     diceRoll: 2
        -     *   }, {
        -     *     error: function(gameTurnAgain, error) {
        -     *       // The set failed validation.
        -     *     }
        -     *   });
        -     *
        -     *   game.set("currentPlayer", player2, {
        -     *     error: function(gameTurnAgain, error) {
        -     *       // The set failed validation.
        -     *     }
        -     *   });
        -     *
        -     *   game.set("finished", true);

        - * - * @param {String} key The key to set. - * @param {} value The value to give it. - * @param {Object} options A set of Backbone-like options for the set. - * The only supported options are silent and - * error. - * @return {Boolean} true if the set succeeded. - * @see Parse.Object#validate - * @see Parse.Error - */ - set: function(key, value, options) { - var attrs, attr; - if (_.isObject(key) || Parse._isNullOrUndefined(key)) { - attrs = key; - Parse._each(attrs, function(v, k) { - attrs[k] = Parse._decode(k, v); - }); - options = value; - } else { - attrs = {}; - attrs[key] = Parse._decode(key, value); - } - - // Extract attributes and options. - options = options || {}; - if (!attrs) { - return this; - } - if (attrs instanceof Parse.Object) { - attrs = attrs.attributes; - } - - // If the unset option is used, every attribute should be a Unset. - if (options.unset) { - Parse._each(attrs, function(unused_value, key) { - attrs[key] = new Parse.Op.Unset(); - }); - } - - // Apply all the attributes to get the estimated values. - var dataToValidate = _.clone(attrs); - var self = this; - Parse._each(dataToValidate, function(value, key) { - if (value instanceof Parse.Op) { - dataToValidate[key] = value._estimate(self.attributes[key], - self, key); - if (dataToValidate[key] === Parse.Op._UNSET) { - delete dataToValidate[key]; - } - } - }); - - // Run validation. - if (!this._validate(attrs, options)) { - return false; - } - - this._mergeMagicFields(attrs); - - options.changes = {}; - var escaped = this._escapedAttributes; - var prev = this._previousAttributes || {}; - - // Update attributes. - Parse._each(_.keys(attrs), function(attr) { - var val = attrs[attr]; - - // If this is a relation object we need to set the parent correctly, - // since the location where it was parsed does not have access to - // this object. - if (val instanceof Parse.Relation) { - val.parent = self; - } - - if (!(val instanceof Parse.Op)) { - val = new Parse.Op.Set(val); - } - - // See if this change will actually have any effect. - var isRealChange = true; - if (val instanceof Parse.Op.Set && - _.isEqual(self.attributes[attr], val.value)) { - isRealChange = false; - } - - if (isRealChange) { - delete escaped[attr]; - if (options.silent) { - self._silent[attr] = true; - } else { - options.changes[attr] = true; - } - } - - var currentChanges = _.last(self._opSetQueue); - currentChanges[attr] = val._mergeWithPrevious(currentChanges[attr]); - self._rebuildEstimatedDataForKey(attr); - - if (isRealChange) { - self.changed[attr] = self.attributes[attr]; - if (!options.silent) { - self._pending[attr] = true; - } - } else { - delete self.changed[attr]; - delete self._pending[attr]; - } - }); - - if (!options.silent) { - this.change(options); - } - return this; - }, - - /** - * Remove an attribute from the model, firing "change" unless - * you choose to silence it. This is a noop if the attribute doesn't - * exist. - */ - unset: function(attr, options) { - options = options || {}; - options.unset = true; - return this.set(attr, null, options); - }, - - /** - * Atomically increments the value of the given attribute the next time the - * object is saved. If no amount is specified, 1 is used by default. - * - * @param attr {String} The key. - * @param amount {Number} The amount to increment by. - */ - increment: function(attr, amount) { - if (_.isUndefined(amount) || _.isNull(amount)) { - amount = 1; - } - return this.set(attr, new Parse.Op.Increment(amount)); - }, - - /** - * Atomically add an object to the end of the array associated with a given - * key. - * @param attr {String} The key. - * @param item {} The item to add. - */ - add: function(attr, item) { - return this.set(attr, new Parse.Op.Add([item])); - }, - - /** - * Atomically add an object to the array associated with a given key, only - * if it is not already present in the array. The position of the insert is - * not guaranteed. - * - * @param attr {String} The key. - * @param item {} The object to add. - */ - addUnique: function(attr, item) { - return this.set(attr, new Parse.Op.AddUnique([item])); - }, - - /** - * Atomically remove all instances of an object from the array associated - * with a given key. - * - * @param attr {String} The key. - * @param item {} The object to remove. - */ - remove: function(attr, item) { - return this.set(attr, new Parse.Op.Remove([item])); - }, - - /** - * Returns an instance of a subclass of Parse.Op describing what kind of - * modification has been performed on this field since the last time it was - * saved. For example, after calling object.increment("x"), calling - * object.op("x") would return an instance of Parse.Op.Increment. - * - * @param attr {String} The key. - * @returns {Parse.Op} The operation, or undefined if none. - */ - op: function(attr) { - return _.last(this._opSetQueue)[attr]; - }, - - /** - * Clear all attributes on the model, firing "change" unless - * you choose to silence it. - */ - clear: function(options) { - options = options || {}; - options.unset = true; - var keysToClear = _.extend(this.attributes, this._operations); - return this.set(keysToClear, options); - }, - - /** - * Returns a JSON-encoded set of operations to be sent with the next save - * request. - */ - _getSaveJSON: function() { - var json = _.clone(_.first(this._opSetQueue)); - Parse._each(json, function(op, key) { - json[key] = op.toJSON(); - }); - return json; - }, - - /** - * Fetch the model from the server. If the server's representation of the - * model differs from its current attributes, they will be overriden, - * triggering a "change" event. - */ - fetch: function(options) { - options = options ? _.clone(options) : {}; - var model = this; - var success = options.success; - options.success = function(resp, status, xhr) { - model._finishFetch(model.parse(resp, status, xhr), true); - if (success) { - success(model, resp); - } - }; - options.error = Parse.Object._wrapError(options.error, model, options); - Parse._request( - "classes", model.className, model.id, 'GET', null, options); - }, - - /** - * Set a hash of model attributes, and save the model to the server. - * updatedAt will be updated when the request returns. - * You can either call it as:
        -     *   object.save();
        - * or
        -     *   object.save(null, options);
        - * or
        -     *   object.save(attrs, options);
        - * or
        -     *   object.save(key, value, options);
        - * - * For example,
        -     *   gameTurn.save({
        -     *     player: "Jake Cutter",
        -     *     diceRoll: 2
        -     *   }, {
        -     *     success: function(gameTurnAgain) {
        -     *       // The save was successful.
        -     *     },
        -     *     error: function(gameTurnAgain, error) {
        -     *       // The save failed.  Error is an instance of Parse.Error.
        -     *     }
        -     *   });
        - * - * @see Parse.Error - */ - save: function(arg1, arg2, arg3) { - var i, attrs, current, options, saved; - if (_.isObject(arg1) || Parse._isNullOrUndefined(arg1)) { - attrs = arg1; - options = arg2; - } else { - attrs = {}; - attrs[arg1] = arg2; - options = arg3; - } - - // Make save({ success: function() {} }) work. - if (!options && attrs) { - var extra_keys = _.reject(attrs, function(value, key) { - return _.include(["success", "error", "wait"], key); - }); - if (extra_keys.length === 0) { - var all_functions = true; - if (_.has(attrs, "success") && !_.isFunction(attrs.success)) { - all_functions = false; - } - if (_.has(attrs, "error") && !_.isFunction(attrs.error)) { - all_functions = false; - } - if (all_functions) { - // This attrs object looks like it's really an options object, - // and there's no other options object, so let's just use it. - return this.save(null, attrs); - } - } - } - - options = options ? _.clone(options) : {}; - if (options.wait) { - current = _.clone(this.attributes); - } - var silentOptions = _.extend({}, options, {silent: true}); - if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) { - return false; - } - var oldOptions = options; // Psuedonym more accurate in some contexts. - var newOptions = _.clone(options); - - var model = this; - - // If there is any unsaved child, save it first. - model._refreshCache(); - var unsavedChildren = Parse.Object._findUnsavedChildren(model.attributes); - if (unsavedChildren.length > 0) { - Parse.Object.saveAll(unsavedChildren, { - success: function(results) { - model.save(null, oldOptions); - }, - error: function(error) { - if (options.error) { - options.error.apply(this, arguments); - } - } - }); - return this; - } - - /** ignore */ - newOptions.success = function(resp, status, xhr) { - var serverAttrs = model.parse(resp, status, xhr); - if (newOptions.wait) { - serverAttrs = _.extend(attrs || {}, serverAttrs); - } - - model._finishSave(serverAttrs); - - if (oldOptions.success) { - oldOptions.success(model, resp); - } else { - model.trigger('sync', model, resp, newOptions); - } - }; - - newOptions.error = function() { - model._cancelSave(); - if (oldOptions.error) { - oldOptions.error.apply(this, arguments); - } - }; - - newOptions.error = Parse.Object._wrapError(newOptions.error, model, - newOptions); - - this._startSave(); - - var doSave = function() { - var method = model.id ? 'PUT' : 'POST'; - - var json = model._getSaveJSON(); - - var route = "classes"; - var className = model.className; - if (model.className === "_User" && !model.id) { - // Special-case user sign-up. - route = "users"; - className = null; - } - Parse._request(route, className, model.id, method, json, newOptions); - if (newOptions.wait) { - model.set(current, silentOptions); - } - }; - - if (this._saving) { - this._saveQueue = this._saveQueue || []; - this._saveQueue.push(doSave); - } else { - this._saving = true; - doSave(); - } - - return this; - }, - - /** - * Destroy this model on the server if it was already persisted. - * Optimistically removes the model from its collection, if it has one. - * If `wait: true` is passed, waits for the server to respond - * before removal. - */ - destroy: function(options) { - options = options ? _.clone(options) : {}; - var model = this; - var success = options.success; - - var triggerDestroy = function() { - model.trigger('destroy', model, model.collection, options); - }; - - if (!this.id) { - return triggerDestroy(); - } - /** ignore */ - options.success = function(resp) { - if (options.wait) { - triggerDestroy(); - } - if (success) { - success(model, resp); - } else { - model.trigger('sync', model, resp, options); - } - }; - options.error = Parse.Object._wrapError(options.error, model, options); - - Parse._request("classes", this.className, this.id, 'DELETE', null, - options); - if (!options.wait) { - triggerDestroy(); - } - }, - - /** - * Converts a response into the hash of attributes to be set on the model. - * @ignore - */ - parse: function(resp, status, xhr) { - var output = _.clone(resp); - _(["createdAt", "updatedAt"]).each(function(key) { - if (output[key]) { - output[key] = Parse._parseDate(output[key]); - } - }); - if (!output.updatedAt) { - output.updatedAt = output.createdAt; - } - if (status) { - this._existed = (status.status !== 201); - } - return output; - }, - - /** - * Creates a new model with identical attributes to this one. - * @return {Parse.Object} - */ - clone: function() { - return new this.constructor(this.attributes); - }, - - /** - * Returns true if this object has never been saved to Parse. - * @return {Boolean} - */ - isNew: function() { - return !this.id; - }, - - /** - * Call this method to manually fire a `"change"` event for this model and - * a `"change:attribute"` event for each changed attribute. - * Calling this will cause all objects observing the model to update. - */ - change: function(options) { - options = options || {}; - var changing = this._changing; - this._changing = true; - - // Silent changes become pending changes. - var self = this; - Parse._each(this._silent, function(attr) { - self._pending[attr] = true; - }); - - // Silent changes are triggered. - var changes = _.extend({}, options.changes, this._silent); - this._silent = {}; - Parse._each(changes, function(unused_value, attr) { - self.trigger('change:' + attr, self, self.get(attr), options); - }); - if (changing) { - return this; - } - - // This is to get around lint not letting us make a function in a loop. - var deleteChanged = function(attr) { - if (!self._pending[attr] && !self._silent[attr]) { - delete self.changed[attr]; - } - }; - - // Continue firing `"change"` events while there are pending changes. - while (!_.isEmpty(this._pending)) { - this._pending = {}; - this.trigger('change', this, options); - // Pending and silent changes still remain. - Parse._each(this.changed, deleteChanged); - self._previousAttributes = _.clone(this.attributes); - } - - this._changing = false; - return this; - }, - - /** - * Returns true if this object was created by the Parse server when the - * object might have already been there (e.g. in the case of a Facebook - * login) - */ - existed: function() { - return this._existed; - }, - - /** - * Determine if the model has changed since the last "change" - * event. If you specify an attribute name, determine if that attribute - * has changed. - * @param {String} attr Optional attribute name - * @return {Boolean} - */ - hasChanged: function(attr) { - if (!arguments.length) { - return !_.isEmpty(this._changed); - } - return this._changed && _.has(this._changed, attr); - }, - - /** - * Returns an object containing all the attributes that have changed, or - * false if there are no changed attributes. Useful for determining what - * parts of a view need to be updated and/or what attributes need to be - * persisted to the server. Unset attributes will be set to undefined. - * You can also pass an attributes object to diff against the model, - * determining if there *would be* a change. - */ - changedAttributes: function(diff) { - if (!diff) { - return this.hasChanged() ? _.clone(this._changed) : false; - } - var changed = {}; - var old = this._previousAttributes; - Parse._each(diff, function(diffVal, attr) { - if (!_.isEqual(old[attr], diffVal)) { - changed[attr] = diffVal; - } - }); - return changed; - }, - - /** - * Gets the previous value of an attribute, recorded at the time the last - * "change" event was fired. - * @param {String} attr Name of the attribute to get. - */ - previous: function(attr) { - if (!arguments.length || !this._previousAttributes) { - return null; - } - return this._previousAttributes[attr]; - }, - - /** - * Gets all of the attributes of the model at the time of the previous - * "change" event. - * @return {Object} - */ - previousAttributes: function() { - return _.clone(this._previousAttributes); - }, - - /** - * Checks if the model is currently in a valid state. It's only possible to - * get into an *invalid* state if you're using silent changes. - * @return {Boolean} - */ - isValid: function() { - return !this.validate(this.attributes); - }, - - /** - * You should not call this function directly unless you subclass - * Parse.Object, in which case you can override this method - * to provide additional validation on set and - * save. Your implementation should return - * - * @param {Object} attrs The current data to validate. - * @param {Object} options A Backbone-like options object. - * @return {} False if the data is valid. An error object otherwise. - * @see Parse.Object#set - */ - validate: function(attrs, options) { - if (_.has(attrs, "ACL") && !(attrs.ACL instanceof Parse.ACL)) { - return new Parse.Error(Parse.Error.OTHER_CAUSE, - "ACL must be a Parse.ACL."); - } - return false; - }, - - /** - * Run validation against a set of incoming attributes, returning `true` - * if all is well. If a specific `error` callback has been passed, - * call that instead of firing the general `"error"` event. - */ - _validate: function(attrs, options) { - if (options.silent || !this.validate) { - return true; - } - attrs = _.extend({}, this.attributes, attrs); - var error = this.validate(attrs, options); - if (!error) { - return true; - } - if (options && options.error) { - options.error(this, error, options); - } else { - this.trigger('error', this, error, options); - } - return false; - }, - - /** - * Returns the ACL for this object. - * @returns {Parse.ACL} An instance of Parse.ACL. - * @see Parse.Object#get - */ - getACL: function() { - return this.get("ACL"); - }, - - /** - * Sets the ACL to be used for this object. - * @param {Parse.ACL} acl An instance of Parse.ACL. - * @param {Object} options Optional Backbone-like options object to be - * passed in to set. - * @return {Boolean} Whether the set passed validation. - * @see Parse.Object#set - */ - setACL: function(acl, options) { - return this.set("ACL", acl, options); - } - - }); - - /** - * Returns the appropriate subclass for making new instances of the given - * className string. - */ - Parse.Object._getSubclass = function(className) { - if (!_.isString(className)) { - throw "Parse.Object._getSubclass requires a string argument."; - } - var ObjectClass = Parse.Object._classMap[className]; - if (!ObjectClass) { - ObjectClass = Parse.Object.extend(className); - Parse.Object._classMap[className] = ObjectClass; - } - return ObjectClass; - }; - - /** - * Creates an instance of a subclass of Parse.Object for the given classname. - */ - Parse.Object._create = function(className, attributes, options) { - var ObjectClass = Parse.Object._getSubclass(className); - return new ObjectClass(attributes, options); - }; - - // Set up a map of className to class so that we can create new instances of - // Parse Objects from JSON automatically. - Parse.Object._classMap = {}; - - Parse.Object._extend = Parse._extend; - - /** - * Creates a new subclass of Parse.Object for the given Parse class name. - * - *

        Every extension of a Parse class will inherit from the most recent - * previous extension of that class. When a Parse.Object is automatically - * created by parsing JSON, it will use the most recent extension of that - * class.

        - * - *

        You should call either:

        -   *     var MyClass = Parse.Object.extend("MyClass", {
        -   *         Instance properties
        -   *     }, {
        -   *         Class properties
        -   *     });
        - * or, for Backbone compatibility:
        -   *     var MyClass = Parse.Object.extend({
        -   *         className: "MyClass",
        -   *         Other instance properties
        -   *     }, {
        -   *         Class properties
        -   *     });

        - * - * @param {String} className The name of the Parse class backing this model. - * @param {Object} protoProps Instance properties to add to instances of the - * class returned from this method. - * @param {Object} classProps Class properties to add the class returned from - * this method. - * @return {Class} A new subclass of Parse.Object. - */ - Parse.Object.extend = function(className, protoProps, classProps) { - // Handle the case with only two args. - if (!_.isString(className)) { - if (className && _.has(className, "className")) { - return Parse.Object.extend(className.className, className, protoProps); - } else { - throw new Error( - "Parse.Object.extend's first argument should be the className."); - } - } - - // If someone tries to subclass "User", coerce it to the right type. - if (className === "User") { - className = "_User"; - } - - var NewClassObject = null; - if (_.has(Parse.Object._classMap, className)) { - var OldClassObject = Parse.Object._classMap[className]; - // This new subclass has been told to extend both from "this" and from - // OldClassObject. This is multiple inheritance, which isn't supported. - // For now, let's just pick one. - NewClassObject = OldClassObject._extend(protoProps, classProps); - } else { - protoProps = protoProps || {}; - protoProps.className = className; - NewClassObject = this._extend(protoProps, classProps); - } - // Extending a subclass should reuse the classname automatically. - NewClassObject.extend = function(arg0) { - if (_.isString(arg0) || (arg0 && _.has(arg0, "className"))) { - return Parse.Object.extend.apply(NewClassObject, arguments); - } - var newArguments = [className].concat(Parse._.toArray(arguments)); - return Parse.Object.extend.apply(NewClassObject, newArguments); - }; - Parse.Object._classMap[className] = NewClassObject; - return NewClassObject; - }; - - /** - * Wrap an optional error callback with a fallback error event. - */ - Parse.Object._wrapError = function(onError, originalModel, options) { - return function(model, response) { - if (model !== originalModel) { - response = model; - } - var error = new Parse.Error(-1, response.responseText); - if (response.responseText) { - var errorJSON = JSON.parse(response.responseText); - if (errorJSON) { - error = new Parse.Error(errorJSON.code, errorJSON.error); - } - } - if (onError) { - onError(originalModel, error, options); - } else { - originalModel.trigger('error', originalModel, error, options); - } - }; - }; - - Parse.Object._findUnsavedChildren = function(object) { - var results = []; - if (object instanceof Parse.Object) { - object._refreshCache(); - if (object.dirty()) { - results = [object]; - } - results.push.apply(results, - Parse.Object._findUnsavedChildren(object.attributes)); - } else if (object instanceof Parse.Relation) { - // Nothing needs to be done, but we don't want to recurse into the - // relation's parent infinitely, so we catch this case. - var unused = null; - } else if (_.isArray(object)) { - _.each(object, function(child) { - results.push.apply(results, Parse.Object._findUnsavedChildren(child)); - }); - } else if (_.isObject(object)) { - Parse._each(object, function(child) { - results.push.apply(results, Parse.Object._findUnsavedChildren(child)); - }); - } - return results; - }; - -}(this)); - -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - /** - * Represents a Role on the Parse server. Roles represent groupings of - * Users for the purposes of granting permissions (e.g. specifying an ACL - * for an Object). Roles are specified by their sets of child users and - * child roles, all of which are granted any permissions that the parent - * role has. - * - *

        Roles must have a name (which cannot be changed after creation of the - * role), and must specify an ACL.

        - * @class - * A Parse.Role is a local representation of a role persisted to the Parse - * cloud. - */ - Parse.Role = Parse.Object.extend("_Role", /** @lends Parse.Role.prototype */ { - // Instance Methods - - /** - * Constructs a new ParseRole with the given name and ACL. - * - * @param {String} name The name of the Role to create. - * @param {Parse.ACL} acl The ACL for this role. Roles must have an ACL. - */ - constructor: function(name, acl) { - if (_.isString(name) && (acl instanceof Parse.ACL)) { - Parse.Object.prototype.constructor.call(this, null, null); - this.setName(name); - this.setACL(acl); - } else { - Parse.Object.prototype.constructor.call(this, name, acl); - } - }, - - /** - * Gets the name of the role. You can alternatively call role.get("name") - * - * @return {String} the name of the role. - */ - getName: function() { - return this.get("name"); - }, - - /** - * Sets the name for a role. This value must be set before the role has - * been saved to the server, and cannot be set once the role has been - * saved. - * - *

        - * A role's name can only contain alphanumeric characters, _, -, and - * spaces. - *

        - * - *

        This is equivalent to calling role.set("name", name)

        - * - * @param {String} name The name of the role. - * @param {Object} options Standard options object with success and error - * callbacks. - */ - setName: function(name, options) { - return this.set("name", name, options); - }, - - /** - * Gets the Parse.Relation for the Parse.Users that are direct - * children of this role. These users are granted any privileges that this - * role has been granted (e.g. read or write access through ACLs). You can - * add or remove users from the role through this relation. - * - *

        This is equivalent to calling role.relation("users")

        - * - * @return {Parse.Relation} the relation for the users belonging to this - * role. - */ - getUsers: function() { - return this.relation("users"); - }, - - /** - * Gets the Parse.Relation for the Parse.Roles that are direct - * children of this role. These roles' users are granted any privileges that - * this role has been granted (e.g. read or write access through ACLs). You - * can add or remove child roles from this role through this relation. - * - *

        This is equivalent to calling role.relation("roles")

        - * - * @return {Parse.Relation} the relation for the roles belonging to this - * role. - */ - getRoles: function() { - return this.relation("roles"); - }, - - /** - * @ignore - */ - validate: function(attrs, options) { - if ("name" in attrs && attrs.name !== this.getName()) { - var newName = attrs.name; - if (this.id && this.id !== attrs.objectId) { - // Check to see if the objectId being set matches this.id. - // This happens during a fetch -- the id is set before calling fetch. - // Let the name be set in this case. - return new Parse.Error(Parse.Error.OTHER_CAUSE, - "A role's name can only be set before it has been saved."); - } - if (!_.isString(newName)) { - return new Parse.Error(Parse.Error.OTHER_CAUSE, - "A role's name must be a String."); - } - if (!(/^[0-9a-zA-Z\-_ ]+$/).test(newName)) { - return new Parse.Error(Parse.Error.OTHER_CAUSE, - "A role's name can only contain alphanumeric characters, _," + - " -, and spaces."); - } - } - if (Parse.Object.prototype.validate) { - return Parse.Object.prototype.validate.call(this, attrs, options); - } - return false; - } - }); -}(this)); - - -/*global _: false */ -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - /** - * Creates a new instance with the given models and options. Typically, you - * will not call this method directly, but will instead make a subclass using - * Parse.Collection.extend. - * - * @param {Array} models An array of instances of Parse.Object. - * - * @param {Object} options An optional object with Backbone-style options. - * Valid options are:
          - *
        • model: The Parse.Object subclass that this collection contains. - *
        • query: An instance of Parse.Query to use when fetching items. - *
        • comparator: A string property name or function to sort by. - *
        - * - * @see Parse.Collection.extend - * - * @class - * - *

        Provides a standard collection class for our sets of models, ordered - * or unordered. For more information, see the - * Backbone - * documentation.

        - */ - Parse.Collection = function(models, options) { - options = options || {}; - if (options.comparator) { - this.comparator = options.comparator; - } - if (options.model) { - this.model = options.model; - } - if (options.query) { - this.query = options.query; - } - this._reset(); - this.initialize.apply(this, arguments); - if (models) { - this.reset(models, {silent: true, parse: options.parse}); - } - }; - - // Define the Collection's inheritable methods. - _.extend(Parse.Collection.prototype, Parse.Events, - /** @lends Parse.Collection.prototype */ { - - // The default model for a collection is just a Parse.Object. - // This should be overridden in most cases. - - model: Parse.Object, - - /** - * Initialize is an empty function by default. Override it with your own - * initialization logic. - */ - initialize: function(){}, - - /** - * The JSON representation of a Collection is an array of the - * models' attributes. - */ - toJSON: function() { - return this.map(function(model){ return model.toJSON(); }); - }, - - /** - * Add a model, or list of models to the set. Pass **silent** to avoid - * firing the `add` event for every new model. - */ - add: function(models, options) { - var i, index, length, model, cid, id, cids = {}, ids = {}; - options = options || {}; - models = _.isArray(models) ? models.slice() : [models]; - - // Begin by turning bare objects into model references, and preventing - // invalid models or duplicate models from being added. - for (i = 0, length = models.length; i < length; i++) { - models[i] = this._prepareModel(models[i], options); - model = models[i]; - if (!model) { - throw new Error("Can't add an invalid model to a collection"); - } - cid = model.cid; - if (cids[cid] || this._byCid[cid]) { - throw new Error("Duplicate cid: can't add the same model " + - "to a collection twice"); - } - id = model.id; - if (!Parse._isNullOrUndefined(id) && (ids[id] || this._byId[id])) { - throw new Error("Duplicate id: can't add the same model " + - "to a collection twice"); - } - ids[id] = model; - cids[cid] = model; - } - - // Listen to added models' events, and index models for lookup by - // `id` and by `cid`. - for (i = 0; i < length; i++) { - (model = models[i]).on('all', this._onModelEvent, this); - this._byCid[model.cid] = model; - if (model.id) { - this._byId[model.id] = model; - } - } - - // Insert models into the collection, re-sorting if needed, and triggering - // `add` events unless silenced. - this.length += length; - index = Parse._isNullOrUndefined(options.at) ? - this.models.length : options.at; - this.models.splice.apply(this.models, [index, 0].concat(models)); - if (this.comparator) { - this.sort({silent: true}); - } - if (options.silent) { - return this; - } - for (i = 0, length = this.models.length; i < length; i++) { - model = this.models[i]; - if (cids[model.cid]) { - options.index = i; - model.trigger('add', model, this, options); - } - } - return this; - }, - - /** - * Remove a model, or a list of models from the set. Pass silent to avoid - * firing the remove event for every model removed. - */ - remove: function(models, options) { - var i, l, index, model; - options = options || {}; - models = _.isArray(models) ? models.slice() : [models]; - for (i = 0, l = models.length; i < l; i++) { - model = this.getByCid(models[i]) || this.get(models[i]); - if (!model) { - continue; - } - delete this._byId[model.id]; - delete this._byCid[model.cid]; - index = this.indexOf(model); - this.models.splice(index, 1); - this.length--; - if (!options.silent) { - options.index = index; - model.trigger('remove', model, this, options); - } - this._removeReference(model); - } - return this; - }, - - /** - * Gets a model from the set by id. - */ - get: function(id) { - return id && this._byId[id.id || id]; - }, - - /** - * Gets a model from the set by client id. - */ - getByCid: function(cid) { - return cid && this._byCid[cid.cid || cid]; - }, - - /** - * Gets the model at the given index. - */ - at: function(index) { - return this.models[index]; - }, - - /** - * Forces the collection to re-sort itself. You don't need to call this - * under normal circumstances, as the set will maintain sort order as each - * item is added. - */ - sort: function(options) { - options = options || {}; - if (!this.comparator) { - throw new Error('Cannot sort a set without a comparator'); - } - var boundComparator = _.bind(this.comparator, this); - if (this.comparator.length === 1) { - this.models = this.sortBy(boundComparator); - } else { - this.models.sort(boundComparator); - } - if (!options.silent) { - this.trigger('reset', this, options); - } - return this; - }, - - /** - * Plucks an attribute from each model in the collection. - */ - pluck: function(attr) { - return _.map(this.models, function(model){ return model.get(attr); }); - }, - - /** - * When you have more items than you want to add or remove individually, - * you can reset the entire set with a new list of models, without firing - * any `add` or `remove` events. Fires `reset` when finished. - */ - reset: function(models, options) { - var self = this; - models = models || []; - options = options || {}; - _.each(this.models, function(model) { - self._removeReference(model); - }); - this._reset(); - this.add(models, {silent: true, parse: options.parse}); - if (!options.silent) { - this.trigger('reset', this, options); - } - return this; - }, - - /** - * Fetches the default set of models for this collection, resetting the - * collection when they arrive. If `add: true` is passed, appends the - * models to the collection instead of resetting. - */ - fetch: function(options) { - options = options ? _.clone(options) : {}; - if (options.parse === undefined) { - options.parse = true; - } - var collection = this; - var success = options.success; - options.success = function(results, resp) { - if (options.add) { - collection.add(results, options); - } else { - collection.reset(results, options); - } - if (success) { - success(collection, resp); - } - }; - options.error = Parse.Object._wrapError(options.error, collection, - options); - var query = this.query || new Parse.Query(this.model); - query.find(options); - }, - - /** - * Creates a new instance of a model in this collection. Add the model to - * the collection immediately, unless `wait: true` is passed, in which case - * we wait for the server to agree. - */ - create: function(model, options) { - var coll = this; - options = options ? _.clone(options) : {}; - model = this._prepareModel(model, options); - if (!model) { - return false; - } - if (!options.wait) { - coll.add(model, options); - } - var success = options.success; - options.success = function(nextModel, resp, xhr) { - if (options.wait) { - coll.add(nextModel, options); - } - if (success) { - success(nextModel, resp); - } else { - nextModel.trigger('sync', model, resp, options); - } - }; - model.save(null, options); - return model; - }, - - /** - * Converts a response into a list of models to be added to the collection. - * The default implementation is just to pass it through. - * @ignore - */ - parse: function(resp, xhr) { - return resp; - }, - - /** - * Proxy to _'s chain. Can't be proxied the same way the rest of the - * underscore methods are proxied because it relies on the underscore - * constructor. - */ - chain: function() { - return _(this.models).chain(); - }, - - /** - * Reset all internal state. Called when the collection is reset. - */ - _reset: function(options) { - this.length = 0; - this.models = []; - this._byId = {}; - this._byCid = {}; - }, - - /** - * Prepare a model or hash of attributes to be added to this collection. - */ - _prepareModel: function(model, options) { - if (!(model instanceof Parse.Object)) { - var attrs = model; - options.collection = this; - model = new this.model(attrs, options); - if (!model._validate(model.attributes, options)) { - model = false; - } - } else if (!model.collection) { - model.collection = this; - } - return model; - }, - - /** - * Internal method to remove a model's ties to a collection. - */ - _removeReference: function(model) { - if (this === model.collection) { - delete model.collection; - } - model.off('all', this._onModelEvent, this); - }, - - /** - * Internal method called every time a model in the set fires an event. - * Sets need to update their indexes when models change ids. All other - * events simply proxy through. "add" and "remove" events that originate - * in other collections are ignored. - */ - _onModelEvent: function(ev, model, collection, options) { - if ((ev === 'add' || ev === 'remove') && collection !== this) { - return; - } - if (ev === 'destroy') { - this.remove(model, options); - } - if (model && ev === 'change:objectId') { - delete this._byId[model.previous("objectId")]; - this._byId[model.id] = model; - } - this.trigger.apply(this, arguments); - } - - }); - - // Underscore methods that we want to implement on the Collection. - var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', - 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', - 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', - 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf', - 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy']; - - // Mix in each Underscore method as a proxy to `Collection#models`. - _.each(methods, function(method) { - Parse.Collection.prototype[method] = function() { - return _[method].apply(_, [this.models].concat(_.toArray(arguments))); - }; - }); - - /** - * Creates a new subclass of Parse.Collection. For example,
        -   *   var MyCollection = Parse.Collection.extend({
        -   *     // Instance properties
        -   *
        -   *     model: MyClass,
        -   *     query: MyQuery,
        -   *
        -   *     getFirst: function() {
        -   *       return this.at(0);
        -   *     }
        -   *   }, {
        -   *     // Class properties
        -   *
        -   *     makeOne: function() {
        -   *       return new MyCollection();
        -   *     }
        -   *   });
        -   *
        -   *   var collection = new MyCollection();
        -   * 
        - * - * @function - * @param {Object} instanceProps Instance properties for the collection. - * @param {Object} classProps Class properies for the collection. - * @return {Class} A new subclass of Parse.Collection. - */ - Parse.Collection.extend = Parse._extend; - -}(this)); - -/*global _: false, document: false */ -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - /** - * Creating a Parse.View creates its initial element outside of the DOM, - * if an existing element is not provided... - * @class - * - *

        A fork of Backbone.View, provided for your convenience. If you use this - * class, you must also include jQuery, or another library that provides a - * jQuery-compatible $ function. For more information, see the - * Backbone - * documentation.

        - *

        Available in the client SDK only.

        - */ - Parse.View = function(options) { - this.cid = _.uniqueId('view'); - this._configure(options || {}); - this._ensureElement(); - this.initialize.apply(this, arguments); - this.delegateEvents(); - }; - - // Cached regex to split keys for `delegate`. - var eventSplitter = /^(\S+)\s*(.*)$/; - - // List of view options to be merged as properties. - - var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', - 'className', 'tagName']; - - // Set up all inheritable **Parse.View** properties and methods. - _.extend(Parse.View.prototype, Parse.Events, - /** @lends Parse.View.prototype */ { - - // The default `tagName` of a View's element is `"div"`. - tagName: 'div', - - /** - * jQuery delegate for element lookup, scoped to DOM elements within the - * current view. This should be prefered to global lookups where possible. - */ - $: function(selector) { - return this.$el.find(selector); - }, - - /** - * Initialize is an empty function by default. Override it with your own - * initialization logic. - */ - initialize: function(){}, - - /** - * The core function that your view should override, in order - * to populate its element (`this.el`), with the appropriate HTML. The - * convention is for **render** to always return `this`. - */ - render: function() { - return this; - }, - - /** - * Remove this view from the DOM. Note that the view isn't present in the - * DOM by default, so calling this method may be a no-op. - */ - remove: function() { - this.$el.remove(); - return this; - }, - - /** - * For small amounts of DOM Elements, where a full-blown template isn't - * needed, use **make** to manufacture elements, one at a time. - *
        -     *     var el = this.make('li', {'class': 'row'},
        -     *                        this.model.escape('title'));
        - */ - make: function(tagName, attributes, content) { - var el = document.createElement(tagName); - if (attributes) { - Parse.$(el).attr(attributes); - } - if (content) { - Parse.$(el).html(content); - } - return el; - }, - - /** - * Changes the view's element (`this.el` property), including event - * re-delegation. - */ - setElement: function(element, delegate) { - this.$el = Parse.$(element); - this.el = this.$el[0]; - if (delegate !== false) { - this.delegateEvents(); - } - return this; - }, - - /** - * Set callbacks. this.events is a hash of - *
        -     * *{"event selector": "callback"}*
        -     *
        -     *     {
        -     *       'mousedown .title':  'edit',
        -     *       'click .button':     'save'
        -     *       'click .open':       function(e) { ... }
        -     *     }
        -     * 
        - * pairs. Callbacks will be bound to the view, with `this` set properly. - * Uses event delegation for efficiency. - * Omitting the selector binds the event to `this.el`. - * This only works for delegate-able events: not `focus`, `blur`, and - * not `change`, `submit`, and `reset` in Internet Explorer. - */ - delegateEvents: function(events) { - events = events || Parse._getValue(this, 'events'); - if (!events) { - return; - } - this.undelegateEvents(); - var self = this; - Parse._each(events, function(method, key) { - if (!_.isFunction(method)) { - method = self[events[key]]; - } - if (!method) { - throw new Error('Event "' + events[key] + '" does not exist'); - } - var match = key.match(eventSplitter); - var eventName = match[1], selector = match[2]; - method = _.bind(method, self); - eventName += '.delegateEvents' + self.cid; - if (selector === '') { - self.$el.bind(eventName, method); - } else { - self.$el.delegate(selector, eventName, method); - } - }); - }, - - /** - * Clears all callbacks previously bound to the view with `delegateEvents`. - * You usually don't need to use this, but may wish to if you have multiple - * Backbone views attached to the same DOM element. - */ - undelegateEvents: function() { - this.$el.unbind('.delegateEvents' + this.cid); - }, - - /** - * Performs the initial configuration of a View with a set of options. - * Keys with special meaning *(model, collection, id, className)*, are - * attached directly to the view. - */ - _configure: function(options) { - if (this.options) { - options = _.extend({}, this.options, options); - } - var self = this; - _.each(viewOptions, function(attr) { - if (options[attr]) { - self[attr] = options[attr]; - } - }); - this.options = options; - }, - - /** - * Ensure that the View has a DOM element to render into. - * If `this.el` is a string, pass it through `$()`, take the first - * matching element, and re-assign it to `el`. Otherwise, create - * an element from the `id`, `className` and `tagName` properties. - */ - _ensureElement: function() { - if (!this.el) { - var attrs = Parse._getValue(this, 'attributes') || {}; - if (this.id) { - attrs.id = this.id; - } - if (this.className) { - attrs['class'] = this.className; - } - this.setElement(this.make(this.tagName, attrs), false); - } else { - this.setElement(this.el, false); - } - } - - }); - - /** - * @function - * @param {Object} instanceProps Instance properties for the view. - * @param {Object} classProps Class properies for the view. - * @return {Class} A new subclass of Parse.View. - */ - Parse.View.extend = Parse._extend; - -}(this)); - -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - /** - * @class - * - *

        A Parse.User object is a local representation of a user persisted to the - * Parse cloud. This class is a subclass of a Parse.Object, and retains the - * same functionality of a Parse.Object, but also extends it with various - * user specific methods, like authentication, signing up, and validation of - * uniqueness.

        - */ - Parse.User = Parse.Object.extend("_User", /** @lends Parse.User.prototype */ { - // Instance Variables - _isCurrentUser: false, - - - // Instance Methods - - /** - * Internal method to handle special fields in a _User response. - */ - _mergeMagicFields: function(attrs) { - if (attrs.sessionToken) { - this._sessionToken = attrs.sessionToken; - delete attrs.sessionToken; - } - Parse.User.__super__._mergeMagicFields.call(this, attrs); - }, - - /** - * Removes null values from authData (which exist temporarily for - * unlinking) - */ - _cleanupAuthData: function() { - if (!this.isCurrent()) { - return; - } - var authData = this.get('authData'); - if (!authData) { - return; - } - _.each(this.get('authData'), function(value, key) { - if (!authData[key]) { - delete authData[key]; - } - }); - }, - - /** - * Synchronizes authData for all providers. - */ - _synchronizeAllAuthData: function() { - var authData = this.get('authData'); - if (!authData) { - return; - } - - var self = this; - _.each(this.get('authData'), function(value, key) { - self._synchronizeAuthData(key); - }); - }, - - /** - * Synchronizes auth data for a provider (e.g. puts the access token in the - * right place to be used by the Facebook SDK). - */ - _synchronizeAuthData: function(provider) { - if (!this.isCurrent()) { - return; - } - var authType; - if (_.isString(provider)) { - authType = provider; - provider = Parse.User._authProviders[authType]; - } else { - authType = provider.getAuthType(); - } - var authData = this.get('authData'); - if (!authData || !provider) { - return; - } - var success = provider.restoreAuthentication(authData[authType]); - if (!success) { - this._unlinkFrom(provider); - } - }, - - _handleSaveResult: function(makeCurrent) { - // Clean up and synchronize the authData object, removing any unset values - if (makeCurrent) { - this._isCurrentUser = true; - } - this._cleanupAuthData(); - this._synchronizeAllAuthData(); - // Don't keep the password around. - delete this._serverData.password; - this._rebuildEstimatedDataForKey("password"); - this._refreshCache(); - if (makeCurrent || this.isCurrent()) { - Parse.User._saveCurrentUser(this); - } - }, - - /** - * Unlike in the Android/iOS SDKs, logInWith is unnecessary, since you can - * call linkWith on the user (even if it doesn't exist yet on the server). - */ - _linkWith: function(provider, options) { - var authType; - if (_.isString(provider)) { - authType = provider; - provider = Parse.User._authProviders[provider]; - } else { - authType = provider.getAuthType(); - } - if (_.has(options, 'authData')) { - var authData = this.get('authData') || {}; - authData[authType] = options.authData; - this.set('authData', authData); - - // Overridden so that the user can be made the current user. - var newOptions = _.clone(options); - newOptions.success = function(model) { - model._handleSaveResult(true); - if (options.success) { - options.success.apply(this, arguments); - } - }; - return this.save({'authData': authData}, newOptions); - } else { - var self = this; - return provider.authenticate({ - success: function(provider, result) { - self._linkWith(provider, { - authData: result, - success: options.success, - error: options.error - }); - }, - error: function(provider, error) { - if (options.error) { - options.error(self, error); - } - } - }); - } - }, - - /** - * Unlinks a user from a service. - */ - _unlinkFrom: function(provider, options) { - var authType; - if (_.isString(provider)) { - authType = provider; - provider = Parse.User._authProviders[provider]; - } else { - authType = provider.getAuthType(); - } - var newOptions = _.clone(options); - var self = this; - newOptions.authData = null; - newOptions.success = function(model) { - self._synchronizeAuthData(provider); - if (options.success) { - options.success.apply(this, arguments); - } - }; - return this._linkWith(provider, newOptions); - }, - - /** - * Checks whether a user is linked to a service. - */ - _isLinked: function(provider) { - var authType; - if (_.isString(provider)) { - authType = provider; - } else { - authType = provider.getAuthType(); - } - var authData = this.get('authData') || {}; - return !!authData[authType]; - }, - - /** - * Deauthenticates all providers. - */ - _logOutWithAll: function() { - var authData = this.get('authData'); - if (!authData) { - return; - } - var self = this; - _.each(this.get('authData'), function(value, key) { - self._logOutWith(key); - }); - }, - - /** - * Deauthenticates a single provider (e.g. removing access tokens from the - * Facebook SDK). - */ - _logOutWith: function(provider) { - if (!this.isCurrent()) { - return; - } - if (_.isString(provider)) { - provider = Parse.User._authProviders[provider]; - } - if (provider && provider.deauthenticate) { - provider.deauthenticate(); - } - }, - - /** - * Signs up a new user. You should call this instead of save for - * new Parse.Users. This will create a new Parse.User on the server, and - * also persist the session on disk so that you can access the user using - * current. - * - *

        A username and password must be set before calling signUp.

        - * - *

        Calls options.success or options.error on completion.

        - * - * @param {Object} attrs Extra fields to set on the new user, or null. - * @param {Object} options A Backbone-style options object. - * @return {} False if validation failed. The user otherwise. - * @see Parse.User.signUp - */ - signUp: function(attrs, options) { - var error; - - var username = (attrs && attrs.username) || this.get("username"); - if (!username || (username === "")) { - if (options && options.error) { - error = new Parse.Error( - Parse.Error.OTHER_CAUSE, - "Cannot sign up user with an empty name."); - options.error(this, error); - } - return false; - } - - var password = (attrs && attrs.password) || this.get("password"); - if (!password || (password === "")) { - if (options && options.error) { - error = new Parse.Error( - Parse.Error.OTHER_CAUSE, - "Cannot sign up user with an empty password."); - options.error(this, error); - } - return false; - } - - // Overridden so that the user can be made the current user. - var newOptions = _.clone(options); - newOptions.success = function(model) { - model._handleSaveResult(true); - if (options.success) { - options.success.apply(this, arguments); - } - }; - return this.save(attrs, newOptions); - }, - - /** - * Logs in a Parse.User. On success, this saves the session to localStorage, - * so you can retrieve the currently logged in user using - * current. - * - *

        A username and password must be set before calling logIn.

        - * - *

        Calls options.success or options.error on completion.

        - * - * @param {Object} options A Backbone-style options object. - * @see Parse.User.logIn - */ - logIn: function(options) { - var model = this; - var newOptions = _.clone(options); - newOptions.success = function(resp, status, xhr) { - var serverAttrs = model.parse(resp, status, xhr); - model._finishFetch(serverAttrs); - model._handleSaveResult(true); - if (options.success) { - options.success(model, resp); - } else { - model.trigger('sync', model, resp, newOptions); - } - }; - newOptions.error = Parse.Object._wrapError(options.error, model, - newOptions); - Parse._request("login", null, null, "GET", this.toJSON(), newOptions); - }, - - /** - * @see Parse.Object#save - */ - save: function(arg1, arg2, arg3) { - var i, attrs, current, options, saved; - if (_.isObject(arg1) || _.isNull(arg1) || _.isUndefined(arg1)) { - attrs = arg1; - options = arg2; - } else { - attrs = {}; - attrs[arg1] = arg2; - options = arg3; - } - - var newOptions = _.clone(options); - newOptions.success = function(model) { - model._handleSaveResult(false); - if (options.success) { - options.success.apply(this, arguments); - } - }; - return Parse.Object.prototype.save.call(this, attrs, newOptions); - }, - - /** - * @see Parse.Object#fetch - */ - fetch: function(options) { - var newOptions = _.clone(options); - newOptions.success = function(model) { - model._handleSaveResult(false); - if (options.success) { - options.success.apply(this, arguments); - } - }; - return Parse.Object.prototype.fetch.call(this, newOptions); - }, - - /** - * Returns true if current would return this user. - * @see Parse.User#current - */ - isCurrent: function() { - return this._isCurrentUser; - }, - - /** - * Returns get("username"). - * @return {String} - * @see Parse.Object#get - */ - getUsername: function() { - return this.get("username"); - }, - - /** - * Calls set("username", username, options) and returns the result. - * @param {String} username - * @param {Object} options A Backbone-style options object. - * @return {Boolean} - * @see Parse.Object.set - */ - setUsername: function(username, options) { - return this.set("username", username, options); - }, - - /** - * Calls set("password", password, options) and returns the result. - * @param {String} password - * @param {Object} options A Backbone-style options object. - * @return {Boolean} - * @see Parse.Object.set - */ - setPassword: function(password, options) { - return this.set("password", password, options); - }, - - /** - * Returns get("email"). - * @return {String} - * @see Parse.Object#get - */ - getEmail: function() { - return this.get("email"); - }, - - /** - * Calls set("email", email, options) and returns the result. - * @param {String} email - * @param {Object} options A Backbone-style options object. - * @return {Boolean} - * @see Parse.Object.set - */ - setEmail: function(email, options) { - return this.set("email", email, options); - }, - - /** - * Checks whether this user is the current user and has been authenticated. - * @return (Boolean) whether this user is the current user and is logged in. - */ - authenticated: function() { - return !!this._sessionToken && - (Parse.User.current() && Parse.User.current().id === this.id); - } - - }, /** @lends Parse.User */ { - // Class Variables - - // The currently logged-in user. - _currentUser: null, - - // Whether currentUser is known to match the serialized version on disk. - // This is useful for saving a localstorage check if you try to load - // _currentUser frequently while there is none stored. - _currentUserMatchesDisk: false, - - // The localStorage key suffix that the current user is stored under. - _CURRENT_USER_KEY: "currentUser", - - // The mapping of auth provider names to actual providers - _authProviders: {}, - - - // Class Methods - - /** - * Signs up a new user with a username (or email) and password. - * This will create a new Parse.User on the server, and also persist the - * session in localStorage so that you can access the user using - * {@link #current}. - * - *

        A username and password must be set before calling signUp.

        - * - *

        Calls options.success or options.error on completion.

        - * - * @param {String} username The username (or email) to sign up with. - * @param {String} password The password to sign up with. - * @param {Object} attrs Extra fields to set on the new user. - * @param {Object} options A Backbone-style options object. - * @return {} False if validation failed. The user otherwise. - * @see Parse.User#signUp - */ - signUp: function(username, password, attrs, options) { - attrs = attrs || {}; - attrs.username = username; - attrs.password = password; - var user = Parse.Object._create("_User"); - return user.signUp(attrs, options); - }, - - /** - * Logs in a user with a username (or email) and password. On success, this - * saves the session to disk, so you can retrieve the currently logged in - * user using current. - * - *

        Calls options.success or options.error on completion.

        - * - * @param {String} username The username (or email) to log in with. - * @param {String} password The password to log in with. - * @param {Object} options A Backbone-style options object. - * @see Parse.User#logIn - */ - logIn: function(username, password, options) { - var user = Parse.Object._create("_User"); - user._finishFetch({ username: username, password: password }); - user.logIn(options); - }, - - /** - * Logs out the currently logged in user session. This will remove the - * session from disk, log out of linked services, and future calls to - * current will return null. - */ - logOut: function() { - if (Parse.User._currentUser !== null) { - Parse.User._currentUser._logOutWithAll(); - Parse.User._currentUser._isCurrentUser = false; - } - Parse.User._currentUserMatchesDisk = true; - Parse.User._currentUser = null; - Parse.localStorage.removeItem( - Parse._getParsePath(Parse.User._CURRENT_USER_KEY)); - }, - - /** - * Requests a password reset email to be sent to the specified email address - * associated with the user account. This email allows the user to securely - * reset their password on the Parse site. - * - *

        Calls options.success or options.error on completion.

        - * - * @param {String} email The email address associated with the user that - * forgot their password. - * @param {Object} options A Backbone-style options object. - */ - requestPasswordReset: function(email, options) { - var json = { email: email }; - options.error = Parse.Query._wrapError(options.error, options); - Parse._request("requestPasswordReset", null, null, "POST", json, options); - }, - - /** - * Retrieves the currently logged in ParseUser with a valid session, - * either from memory or localStorage, if necessary. - * @return {Parse.Object} The currently logged in Parse.User. - */ - current: function() { - if (Parse.User._currentUser) { - return Parse.User._currentUser; - } - - if (Parse.User._currentUserMatchesDisk) { - - return Parse.User._currentUser; - } - - // Load the user from local storage. - Parse.User._currentUserMatchesDisk = true; - - var userData = Parse.localStorage.getItem(Parse._getParsePath( - Parse.User._CURRENT_USER_KEY)); - if (!userData) { - - return null; - } - Parse.User._currentUser = new Parse.Object._create("_User"); - Parse.User._currentUser._isCurrentUser = true; - - var json = JSON.parse(userData); - Parse.User._currentUser.id = json._id; - delete json._id; - Parse.User._currentUser._sessionToken = json._sessionToken; - delete json._sessionToken; - Parse.User._currentUser.set(json); - - Parse.User._currentUser._synchronizeAllAuthData(); - Parse.User._currentUser._refreshCache(); - Parse.User._currentUser._opSetQueue = [{}]; - return Parse.User._currentUser; - }, - - /** - * Persists a user as currentUser to localStorage, and into the singleton. - */ - _saveCurrentUser: function(user) { - if (Parse.User._currentUser !== user) { - Parse.User.logOut(); - } - user._isCurrentUser = true; - Parse.User._currentUser = user; - Parse.User._currentUserMatchesDisk = true; - - var json = user.toJSON(); - json._id = user.id; - json._sessionToken = user._sessionToken; - Parse.localStorage.setItem( - Parse._getParsePath(Parse.User._CURRENT_USER_KEY), - JSON.stringify(json)); - }, - - _registerAuthenticationProvider: function(provider) { - Parse.User._authProviders[provider.getAuthType()] = provider; - // Synchronize the current user with the auth provider. - if (Parse.User.current()) { - Parse.User.current()._synchronizeAuthData(provider.getAuthType()); - } - }, - - _logInWith: function(provider, options) { - var user = new Parse.User(); - return user._linkWith(provider, options); - } - - }); -}(this)); - - -// Parse.Query is a way to create a list of Parse.Objects. -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - /** - * Creates a new parse Parse.Query for the given Parse.Object subclass. - * @param objectClass - - * An instance of a subclass of Parse.Object, or a Parse className string. - * @class - * - *

        Parse.Query defines a query that is used to fetch Parse.Objects. The - * most common use case is finding all objects that match a query through the - * find method. For example, this sample code fetches all objects - * of class MyClass. It calls a different function depending on - * whether the fetch succeeded or not. - * - *

        -   * var query = new Parse.Query(MyClass);
        -   * query.find({
        -   *   success: function(results) {
        -   *     // results is an array of Parse.Object.
        -   *   },
        -   *
        -   *   error: function(error) {
        -   *     // error is an instance of Parse.Error.
        -   *   }
        -   * });

        - * - *

        A Parse.Query can also be used to retrieve a single object whose id is - * known, through the get method. For example, this sample code fetches an - * object of class MyClass and id myId. It calls a - * different function depending on whether the fetch succeeded or not. - * - *

        -   * var query = new Parse.Query(MyClass);
        -   * query.get(myId, {
        -   *   success: function(object) {
        -   *     // object is an instance of Parse.Object.
        -   *   },
        -   *
        -   *   error: function(object, error) {
        -   *     // error is an instance of Parse.Error.
        -   *   }
        -   * });

        - * - *

        A Parse.Query can also be used to count the number of objects that match - * the query without retrieving all of those objects. For example, this - * sample code counts the number of objects of the class MyClass - *

        -   * var query = new Parse.Query(MyClass);
        -   * query.count({
        -   *   success: function(number) {
        -   *     // There are number instances of MyClass.
        -   *   },
        -   *
        -   *   error: function(error) {
        -   *     // error is an instance of Parse.Error.
        -   *   }
        -   * });

        - */ - Parse.Query = function(objectClass) { - if (_.isString(objectClass)) { - objectClass = Parse.Object._getSubclass(objectClass); - } - - this.objectClass = objectClass; - - this.className = objectClass.prototype.className; - - this._where = {}; - this._include = []; - this._limit = -1; // negative limit means, do not send a limit - this._skip = 0; - this._extraOptions = {}; - }; - - /** - * Constructs a Parse.Query that is the OR of the passed in queries. For - * example: - *
        var compoundQuery = Parse.Query.or(query1, query2, query3);
        - * - * will create a compoundQuery that is an or of the query1, query2, and - * query3. - * @param {...Parse.Query} var_args The list of queries to OR. - * @return {Parse.Query} The query that is the OR of the passed in queries. - */ - Parse.Query.or = function() { - var queries = _.toArray(arguments); - var className = null; - _.each(queries, function(q) { - if (_.isNull(className)) { - className = q.className; - } - - if (className !== q.className) { - throw "All queries must be for the same class"; - } - }); - var query = new Parse.Query(className); - query._orQuery(queries); - return query; - }; - - Parse.Query.prototype = { - /** - * Constructs a Parse.Object whose id is already known by fetching data from - * the server. Either options.success or options.error is called when the - * find completes. - * - * @param {} objectId The id of the object to be fetched. - * @param {Object} options A Backbone-style options object. - */ - get: function(objectId, options) { - var self = this; - var success = options.success || function() {}; - var error = options.error || function() {}; - - /** ignore */ - var ajaxOptions = { - error: function(errorObject) { - error(null, errorObject); - }, - success: function(response) { - if (response) { - success(response); - } else { - error(null, new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, - "Object not found.")); - } - } - }; - - self.equalTo('objectId', objectId); - self.first(ajaxOptions); - }, - - /** - * Returns a JSON representation of this query. - * @return {Object} - */ - toJSON: function() { - var params = { - where: this._where - }; - - if (this._include.length > 0) { - params.include = this._include.join(","); - } - if (this._limit >= 0) { - params.limit = this._limit; - } - if (this._skip > 0) { - params.skip = this._skip; - } - if (this._order !== undefined) { - params.order = this._order; - } - - Parse._each(this._extraOptions, function(v, k) { - params[k] = v; - }); - - return params; - }, - - /** - * Retrieves a list of ParseObjects that satisfy this query. - * Either options.success or options.error is called when the find - * completes. - * - * @param {Object} options A Backbone-style options object. - */ - find: function(options) { - var self = this; - var success = options.success || function() {}; - - /** ignore */ - var ajaxOptions = { - error: options.error, - success: function(response) { - success(_.map(response.results, function(json) { - var obj; - if (response.className) { - obj = new Parse.Object(response.className); - } else { - obj = new self.objectClass(); - } - obj._finishFetch(json, true); - return obj; - })); - } - }; - - var params = this.toJSON(); - ajaxOptions.error = Parse.Query._wrapError(options.error, ajaxOptions); - Parse._request("classes", this.className, null, "GET", params, - ajaxOptions); - }, - - /** - * Counts the number of objects that match this query. - * Either options.success or options.error is called when the count - * completes. - * - * @param {Object} options A Backbone-style options object. - */ - count: function(options) { - var self = this; - var success = options.success || function() {}; - - /** ignore */ - var ajaxOptions = { - error: options.error, - success: function(response) { - success(response.count); - } - }; - - var params = this.toJSON(); - params.limit = 0; - params.count = 1; - ajaxOptions.error = Parse.Query._wrapError(options.error, ajaxOptions); - Parse._request("classes", this.className, null, "GET", params, - ajaxOptions); - }, - - /** - * Retrieves at most one Parse.Object that satisfies this query. - * - * Either options.success or options.error is called when the find completes. - * success is passed the object if there is one. otherwise, undefined. - * - * @param {Object} options A Backbone-style options object. - */ - first: function(options) { - var self = this; - var success = options.success || function() {}; - - /** ignore */ - var ajaxOptions = { - error: options.error, - success: function(response) { - success(_.map(response.results, function(json) { - var obj = new self.objectClass(); - obj._finishFetch(json, true); - return obj; - })[0]); - } - }; - - var params = this.toJSON(); - params.limit = 1; - ajaxOptions.error = Parse.Query._wrapError(options.error, ajaxOptions); - Parse._request("classes", this.className, null, "GET", params, - ajaxOptions); - }, - - /** - * Returns a new instance of Parse.Collection backed by this query. - * @return {Parse.Collection} - */ - collection: function(items, options) { - options = options || {}; - return new Parse.Collection(items, _.extend(options, { - model: this.objectClass, - query: this - })); - }, - - /** - * Sets the number of results to skip before returning any results. - * This is useful for pagination. - * Default is to skip zero results. - * @param {Number} n the number of results to skip. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - skip: function(n) { - this._skip = n; - return this; - }, - - /** - * Sets the limit of the number of results to return. - * @param {Number} n the number of results to limit to. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - limit: function(n) { - this._limit = n; - return this; - }, - - /** - * Add a constraint to the query that requires a particular key's value to - * be equal to the provided value. - * @param {String} key The key to check. - * @param value The value that the Parse.Object must contain. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - equalTo: function(key, value) { - this._where[key] = Parse._encode(value); - return this; - }, - - /** - * Helper for condition queries - */ - _addCondition: function(key, condition, value) { - // Check if we already have a condition - if (!this._where[key]) { - this._where[key] = {}; - } - this._where[key][condition] = Parse._encode(value); - return this; - }, - - /** - * Add a constraint to the query that requires a particular key's value to - * be not equal to the provided value. - * @param {String} key The key to check. - * @param value The value that must not be equalled. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - notEqualTo: function(key, value) { - this._addCondition(key, "$ne", value); - return this; - }, - - /** - * Add a constraint to the query that requires a particular key's value to - * be less than the provided value. - * @param {String} key The key to check. - * @param value The value that provides an upper bound. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - lessThan: function(key, value) { - this._addCondition(key, "$lt", value); - return this; - }, - - /** - * Add a constraint to the query that requires a particular key's value to - * be greater than the provided value. - * @param {String} key The key to check. - * @param value The value that provides an lower bound. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - greaterThan: function(key, value) { - this._addCondition(key, "$gt", value); - return this; - }, - - /** - * Add a constraint to the query that requires a particular key's value to - * be less than or equal to the provided value. - * @param {String} key The key to check. - * @param value The value that provides an upper bound. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - lessThanOrEqualTo: function(key, value) { - this._addCondition(key, "$lte", value); - return this; - }, - - /** - * Add a constraint to the query that requires a particular key's value to - * be greater than or equal to the provided value. - * @param {String} key The key to check. - * @param value The value that provides an lower bound. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - greaterThanOrEqualTo: function(key, value) { - this._addCondition(key, "$gte", value); - return this; - }, - - /** - * Add a constraint to the query that requires a particular key's value to - * be contained in the provided list of values. - * @param {String} key The key to check. - * @param {Array} values The values that will match. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - containedIn: function(key, values) { - this._addCondition(key, "$in", values); - return this; - }, - - /** - * Add a constraint to the query that requires a particular key's value to - * not be contained in the provided list of values. - * @param {String} key The key to check. - * @param {Array} values The values that will not match. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - notContainedIn: function(key, values) { - this._addCondition(key, "$nin", values); - return this; - }, - - - /** - * Add a constraint for finding objects that contain the given key. - * @param {String} key The key that should exist. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - exists: function(key) { - this._addCondition(key, "$exists", true); - return this; - }, - - /** - * Add a constraint for finding objects that do not contain a given key. - * @param {String} key The key that should not exist - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - doesNotExist: function(key) { - this._addCondition(key, "$exists", false); - return this; - }, - - /** - * Add a regular expression constraint for finding string values that match - * the provided regular expression. - * This may be slow for large datasets. - * @param {String} key The key that the string to match is stored in. - * @param {RegExp} regex The regular expression pattern to match. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - matches: function(key, regex, modifiers) { - this._addCondition(key, "$regex", regex); - if (!modifiers) { modifiers = ""; } - // Javascript regex options support mig as inline options but store them - // as properties of the object. We support mi & should migrate them to - // modifiers - if (regex.ignoreCase) { modifiers += 'i'; } - if (regex.multiline) { modifiers += 'm'; } - - if (modifiers && modifiers.length) { - this._addCondition(key, "$options", modifiers); - } - return this; - }, - - /** - * Add a constraint that requires that a key's value matches a Parse.Query - * constraint. - * @param {String} key The key that the contains the object to match the - * query. - * @param {Parse.Query} query The query that should match. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - matchesQuery: function(key, query) { - var queryJSON = query.toJSON(); - queryJSON.className = query.className; - this._addCondition(key, "$inQuery", queryJSON); - return this; - }, - - /** - * Add a constraint that requires that a key's value not matches a - * Parse.Query constraint. - * @param {String} key The key that the contains the object to match the - * query. - * @param {Parse.Query} query The query that should not match. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - doesNotMatchQuery: function(key, query) { - var queryJSON = query.toJSON(); - queryJSON.className = query.className; - this._addCondition(key, "$notInQuery", queryJSON); - return this; - }, - - - /** - * Add a constraint that requires that a key's value matches a value in - * an object returned by a different Parse.Query. - * @param {String} key The key that contains the value that is being - * matched. - * @param {String} queryKey The key in the objects returned by the query to - * match against. - * @param {Parse.Query} query The query to run. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - matchesKeyInQuery: function(key, queryKey, query) { - var queryJSON = query.toJSON(); - queryJSON.className = query.className; - this._addCondition(key, "$select", - { key: queryKey, query: queryJSON }); - return this; - }, - - /** - * Add a constraint that requires that a key's value not match a value in - * an object returned by a different Parse.Query. - * @param {String} key The key that contains the value that is being - * excluded. - * @param {String} queryKey The key in the objects returned by the query to - * match against. - * @param {Parse.Query} query The query to run. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - doesNotMatchKeyInQuery: function(key, queryKey, query) { - var queryJSON = query.toJSON(); - queryJSON.className = query.className; - this._addCondition(key, "$dontSelect", - { key: queryKey, query: queryJSON }); - return this; - }, - - /** - * Add constraint that at least one of the passed in queries matches. - * @param {Array} queries - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - _orQuery: function(queries) { - var queryJSON = _.map(queries, function(q) { - return q.toJSON().where; - }); - - this._where.$or = queryJSON; - return this; - }, - - /** - * Converts a string into a regex that matches it. - * Surrounding with \Q .. \E does this, we just need to escape \E's in - * the text separately. - */ - _quote: function(s) { - return "\\Q" + s.replace("\\E", "\\E\\\\E\\Q") + "\\E"; - }, - - /** - * Add a constraint for finding string values that contain a provided - * string. This may be slow for large datasets. - * @param {String} key The key that the string to match is stored in. - * @param {String} substring The substring that the value must contain. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - contains: function(key, value) { - this._addCondition(key, "$regex", this._quote(value)); - return this; - }, - - /** - * Add a constraint for finding string values that start with a provided - * string. This query will use the backend index, so it will be fast even - * for large datasets. - * @param {String} key The key that the string to match is stored in. - * @param {String} prefix The substring that the value must start with. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - startsWith: function(key, value) { - this._addCondition(key, "$regex", "^" + this._quote(value)); - return this; - }, - - /** - * Add a constraint for finding string values that end with a provided - * string. This will be slow for large datasets. - * @param {String} key The key that the string to match is stored in. - * @param {String} suffix The substring that the value must end with. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - endsWith: function(key, value) { - this._addCondition(key, "$regex", this._quote(value) + "$"); - return this; - }, - - /** - * Sorts the results in ascending order by the given key. - * - * @param {String} key The key to order by. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - ascending: function(key) { - this._order = key; - return this; - }, - - /** - * Sorts the results in descending order by the given key. - * - * @param {String} key The key to order by. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - descending: function(key) { - this._order = "-" + key; - return this; - }, - - /** - * Add a proximity based constraint for finding objects with key point - * values near the point given. - * @param {String} key The key that the Parse.GeoPoint is stored in. - * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - near: function(key, point) { - if (!(point instanceof Parse.GeoPoint)) { - // Try to cast it to a GeoPoint, so that near("loc", [20,30]) works. - point = new Parse.GeoPoint(point); - } - this._addCondition(key, "$nearSphere", point); - return this; - }, - - /** - * Add a proximity based constraint for finding objects with key point - * values near the point given and within the maximum distance given. - * @param {String} key The key that the Parse.GeoPoint is stored in. - * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. - * @param maxDistance Maximum distance (in radians) of results to return. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - withinRadians: function(key, point, distance) { - this.near(key, point); - this._addCondition(key, "$maxDistance", distance); - return this; - }, - - /** - * Add a proximity based constraint for finding objects with key point - * values near the point given and within the maximum distance given. - * Radius of earth used is 3958.8 miles. - * @param {String} key The key that the Parse.GeoPoint is stored in. - * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. - * @param {Number} maxDistance Maximum distance (in miles) of results to - * return. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - withinMiles: function(key, point, distance) { - return this.withinRadians(key, point, distance / 3958.8); - }, - - /** - * Add a proximity based constraint for finding objects with key point - * values near the point given and within the maximum distance given. - * Radius of earth used is 6371.0 kilometers. - * @param {String} key The key that the Parse.GeoPoint is stored in. - * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. - * @param {Number} maxDistance Maximum distance (in kilometers) of results - * to return. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - withinKilometers: function(key, point, distance) { - return this.withinRadians(key, point, distance / 6371.0); - }, - - /** - * Add a constraint to the query that requires a particular key's - * coordinates be contained within a given rectangular geographic bounding - * box. - * @param {String} key The key to be constrained. - * @param {Parse.GeoPoint} southwest - * The lower-left inclusive corner of the box. - * @param {Parse.GeoPoint} northeast - * The upper-right inclusive corner of the box. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - withinGeoBox: function(key, southwest, northeast) { - if (!(southwest instanceof Parse.GeoPoint)) { - southwest = new Parse.GeoPoint(southwest); - } - if (!(northeast instanceof Parse.GeoPoint)) { - northeast = new Parse.GeoPoint(northeast); - } - this._addCondition(key, '$within', { '$box': [southwest, northeast] }); - return this; - }, - - /** - * Include nested Parse.Objects for the provided key. You can use dot - * notation to specify which fields in the included object are also fetch. - * @param {String} key The name of the key to include. - * @return {Parse.Query} Returns the query, so you can chain this call. - */ - include: function(key) { - if (_.isArray(key)) { - this._include = this._include.concat(key); - } else { - this._include.push(key); - } - return this; - } - }; - - // Wrap an optional error callback with a fallback error event. - Parse.Query._wrapError = function(onError, options) { - return function(response) { - if (onError) { - var error = new Parse.Error(-1, response.responseText); - if (response.responseText) { - var errorJSON = JSON.parse(response.responseText); - if (errorJSON) { - error = new Parse.Error(errorJSON.code, errorJSON.error); - } - } - onError(error, options); - } - }; - }; -}(this)); - -/*global FB: false */ -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - var PUBLIC_KEY = "*"; - - var initialized = false; - var requestedPermissions; - var initOptions; - var provider = { - authenticate: function(options) { - var self = this; - FB.login(function(response) { - if (response.authResponse) { - if (options.success) { - options.success(self, { - id: response.authResponse.userID, - access_token: response.authResponse.accessToken, - expiration_date: new Date(response.authResponse.expiresIn * 1000 + - (new Date()).getTime()).toJSON() - }); - } - } else { - if (options.error) { - options.error(self, response); - } - } - }, { - scope: requestedPermissions - }); - }, - restoreAuthentication: function(authData) { - if (authData) { - var authResponse = { - userID: authData.id, - accessToken: authData.access_token, - expiresIn: (Parse._parseDate(authData.expiration_date).getTime() - - (new Date()).getTime()) / 1000 - }; - var newOptions = _.clone(initOptions); - newOptions.authResponse = authResponse; - FB.init(newOptions); - } - return true; - }, - getAuthType: function() { - return "facebook"; - }, - deauthenticate: function() { - this.restoreAuthentication(null); - FB.logout(); - } - }; - - /** - * Provides a set of utilities for using Parse with Facebook. - * @namespace - * Provides a set of utilities for using Parse with Facebook. - */ - Parse.FacebookUtils = { - /** - * Initializes Parse Facebook integration. Call this function after you - * have loaded the Facebook Javascript SDK with the same parameters - * as you would pass to - * - * FB.init(). Parse.FacebookUtils will invoke FB.init() for you - * with these arguments. - * - * @param {Object} options Facebook options argument as described here: - * - * FB.init() - */ - init: function(options) { - if (typeof(FB) === 'undefined') { - throw "The Javascript Facebook SDK must be loaded before calling init."; - } - initOptions = _.clone(options); - FB.init(initOptions); - Parse.User._registerAuthenticationProvider(provider); - initialized = true; - }, - - /** - * Gets whether the user has their account linked to Facebook. - * - * @param {Parse.User} user User to check for a facebook link. - * The user must be logged in on this device. - * @return {Boolean} true if the user has their account - * linked to Facebook. - */ - isLinked: function(user) { - return user._isLinked("facebook"); - }, - - /** - * Logs in a user using Facebook. This method delegates to the Facebook - * SDK to authenticate the user, and then automatically logs in (or - * creates, in the case where it is a new user) a Parse.User. - * - * @param {String, Object} permissions The permissions required for Facebook - * log in. This is a comma-separated string of permissions. - * Alternatively, supply a Facebook authData object as described in our - * REST API docs if you want to handle getting facebook auth tokens - * yourself. - * @param {Object} options Standard options object with success and error - * callbacks. - */ - logIn: function(permissions, options) { - if (!permissions || _.isString(permissions)) { - if (!initialized) { - throw "You must initialize FacebookUtils before calling logIn."; - } - requestedPermissions = permissions; - return Parse.User._logInWith("facebook", options); - } else { - var newOptions = _.clone(options); - newOptions.authData = permissions; - return Parse.User._logInWith("facebook", newOptions); - } - }, - - /** - * Links Facebook to an existing PFUser. This method delegates to the - * Facebook SDK to authenticate the user, and then automatically links - * the account to the Parse.User. - * - * @param {Parse.User} user User to link to Facebook. This must be the - * current user. - * @param {String, Object} permissions The permissions required for Facebook - * log in. This is a comma-separated string of permissions. - * Alternatively, supply a Facebook authData object as described in our - * REST API docs if you want to handle getting facebook auth tokens - * yourself. - * @param {Object} options Standard options object with success and error - * callbacks. - */ - link: function(user, permissions, options) { - if (!permissions || _.isString(permissions)) { - if (!initialized) { - throw "You must initialize FacebookUtils before calling link."; - } - requestedPermissions = permissions; - return user._linkWith("facebook", options); - } else { - var newOptions = _.clone(options); - newOptions.authData = permissions; - return user._linkWith("facebook", newOptions); - } - }, - - /** - * Unlinks the Parse.User from a Facebook account. - * - * @param {Parse.User} user User to unlink from Facebook. This must be the - * current user. - * @param {Object} options Standard options object with success and error - * callbacks. - */ - unlink: function(user, options) { - if (!initialized) { - throw "You must initialize FacebookUtils before calling unlink."; - } - return user._unlinkFrom("facebook", options); - } - }; - -}(this)); - -/*global _: false, document: false, window: false, navigator: false */ -(function(root) { - root.Parse = root.Parse || {}; - var Parse = root.Parse; - var _ = Parse._; - - /** - * History serves as a global router (per frame) to handle hashchange - * events or pushState, match the appropriate route, and trigger - * callbacks. You shouldn't ever have to create one of these yourself - * — you should use the reference to Parse.history - * that will be created for you automatically if you make use of - * Routers with routes. - * @class - * - *

        A fork of Backbone.History, provided for your convenience. If you - * use this class, you must also include jQuery, or another library - * that provides a jQuery-compatible $ function. For more information, - * see the - * Backbone documentation.

        - *

        Available in the client SDK only.

        - */ - Parse.History = function() { - this.handlers = []; - _.bindAll(this, 'checkUrl'); - }; - - // Cached regex for cleaning leading hashes and slashes . - var routeStripper = /^[#\/]/; - - // Cached regex for detecting MSIE. - var isExplorer = /msie [\w.]+/; - - // Has the history handling already been started? - Parse.History.started = false; - - // Set up all inheritable **Parse.History** properties and methods. - _.extend(Parse.History.prototype, Parse.Events, - /** @lends Parse.History.prototype */ { - - // The default interval to poll for hash changes, if necessary, is - // twenty times a second. - interval: 50, - - // Gets the true hash value. Cannot use location.hash directly due to bug - // in Firefox where location.hash will always be decoded. - getHash: function(windowOverride) { - var loc = windowOverride ? windowOverride.location : window.location; - var match = loc.href.match(/#(.*)$/); - return match ? match[1] : ''; - }, - - // Get the cross-browser normalized URL fragment, either from the URL, - // the hash, or the override. - getFragment: function(fragment, forcePushState) { - if (Parse._isNullOrUndefined(fragment)) { - if (this._hasPushState || forcePushState) { - fragment = window.location.pathname; - var search = window.location.search; - if (search) { - fragment += search; - } - } else { - fragment = this.getHash(); - } - } - if (!fragment.indexOf(this.options.root)) { - fragment = fragment.substr(this.options.root.length); - } - return fragment.replace(routeStripper, ''); - }, - - /** - * Start the hash change handling, returning `true` if the current - * URL matches an existing route, and `false` otherwise. - */ - start: function(options) { - if (Parse.History.started) { - throw new Error("Parse.history has already been started"); - } - Parse.History.started = true; - - // Figure out the initial configuration. Do we need an iframe? - // Is pushState desired ... is it available? - this.options = _.extend({}, {root: '/'}, this.options, options); - this._wantsHashChange = this.options.hashChange !== false; - this._wantsPushState = !!this.options.pushState; - this._hasPushState = !!(this.options.pushState && - window.history && - window.history.pushState); - var fragment = this.getFragment(); - var docMode = document.documentMode; - var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && - (!docMode || docMode <= 7)); - - if (oldIE) { - this.iframe = Parse.$('