├── .gitignore ├── README.md ├── admin.html ├── css └── leaderboard.css ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf └── glyphicons-halflings-regular.woff ├── index.html ├── js ├── leader-board-static.js └── leader-board.js ├── libs ├── angular.js ├── angular.min.js ├── angular.min.js.map ├── angularfire.min.js ├── bootstrap.js ├── bootstrap.min.js ├── firebase.js ├── jquery.min.js ├── modernizr.min.js └── sugar.custom.js └── remote.html /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Angularjs-Leaderboard-Firebase 2 | ============================== 3 | 4 | Welcome to a realtime leaderboard built with [AngularJS](https://angularjs.org/) and [Firebase](https://www.firebase.com/)! This app is simple to set up so let's get started. 5 | 6 | Prerequisites 7 | ------------- 8 | - [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git) 9 | - [Node.js and NPM](https://nodejs.org/) 10 | - Serve NPM package (`npm install -g serve`) 11 | 12 | Getting Started 13 | --------------- 14 | First, make sure you have Git and Node.js installed (see "Prerequisites"). Then open your terminal/command prompt and run the following commands: 15 | ``` 16 | git clone https://github.com/simpulton/angularjs-leaderboard-firebase.git 17 | cd angularjs-leaderboard-firebase 18 | ``` 19 | Now open up the code in your favorite text editor. 20 | 21 | Setting up Firebase 22 | ------------------- 23 | To run the app, you will need to set up an account with Firebase. Don't worry, it's free AND easy! 24 | 25 | #### Get a Firebase Account 26 | First, navigate to https://www.firebase.com/. Then you can do one of two things: you can sign up for an account with your email or, if you have a Github account, you can log in with those credentials. 27 | 28 | #### Create an App 29 | After you have logged in/signed up, you will be taken to your dashboard. Go ahead and create a new app (call it whatever you want). After you click `CREATE NEW APP`, your app will appear in your dashboard. 30 | 31 | #### Get your Firebase URL 32 | Click `Manage App` on your newly minted Firebase app. Once you have transitioned to the new page, copy the URL from the address bar. It should look like `https://.firebaseio.com/`. 33 | 34 | #### Put the URL in the app 35 | Now open up your code editor, navigate to `js/leader-board.js`, and replace the `FIREBASE_URI` constant ([line 3](https://github.com/simpulton/angularjs-leaderboard-firebase/blob/master/js/leader-board.js#L3) as of this writing) with your Firebase URL. Firebase is now ready to go! 36 | 37 | Running the App 38 | --------------- 39 | Now go back to your terminal, make sure you are in the `angularjs-leaderboard-firebase` directory, and then run `serve`. In your browser, navigate to `http://localhost:3000`. Boom! The app is now running. Read on for a tour of the app. 40 | 41 | Tour de App 42 | ----------- 43 | Let's take a quick look at what the app does. 44 | 45 | #### The Index View 46 | The index page is where we show all of our data. 47 | ![leader-index](https://cloud.githubusercontent.com/assets/590361/7207118/ff576d66-e4eb-11e4-9483-dfabc901e837.png) 48 | 49 | #### The Admin View 50 | The admin page is where we can CRUD (Create Read Update Delete) our data. 51 | ![leader-admin](https://cloud.githubusercontent.com/assets/590361/7207146/28802502-e4ec-11e4-855e-14a6066a79c1.png) 52 | 53 | #### The Remote View 54 | The remote page is where the realtime component becomes important. Here, we can choose a contestant and then update their score. [Click here](http://www.screencast.com/t/2BZGTSH3g8) to see a realtime demo. 55 | ![leader-remote](https://cloud.githubusercontent.com/assets/590361/7207461/7729aa8c-e4ee-11e4-8507-8ab60cd83620.png) 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AngularJS Leader Board 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Race to Awesome

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 |
LaneContestantScore
29 | 30 |
34 | 35 |
36 |

Add Contestant

37 | 38 |
39 |
40 | 41 |
42 |
43 | 44 |
45 |
46 | 47 |
48 | 49 |
50 |
51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /css/leaderboard.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-top: 50px; 3 | } 4 | 5 | .remote { 6 | margin: 10px; 7 | padding: 10px; 8 | } 9 | 10 | .remote .row { 11 | margin-bottom: 10px; 12 | padding-left: 20px; 13 | padding-right: 20px; 14 | } 15 | 16 | .remote select { 17 | margin-top: 20px; 18 | } 19 | 20 | .edit input[type=text] { 21 | border: 1px solid #ffffff; 22 | width: 100%; 23 | resize: none; 24 | background-color: #ffffff; 25 | -webkit-box-shadow: none; 26 | -moz-box-shadow: none; 27 | box-shadow: none; 28 | } 29 | 30 | .edit input[type=text]:hover, 31 | .edit input[type=text]:focus { 32 | border: 1px solid #d7cebf; 33 | outline: none 34 | } 35 | 36 | .xlarge { 37 | font-size: 36px; 38 | font-weight: bold; 39 | color: inherit; 40 | } 41 | 42 | .large { 43 | font-size: 30px; 44 | font-weight: bold; 45 | color: inherit; 46 | } 47 | 48 | .medium { 49 | font-size: 24px; 50 | font-weight: bold; 51 | color: inherit; 52 | } 53 | 54 | -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simpulton/angularjs-leaderboard-firebase/2b1c2c50233ec03f7dc9bc8c3d251f94ced3f802/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simpulton/angularjs-leaderboard-firebase/2b1c2c50233ec03f7dc9bc8c3d251f94ced3f802/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simpulton/angularjs-leaderboard-firebase/2b1c2c50233ec03f7dc9bc8c3d251f94ced3f802/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AngularJS Leader Board 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Race to Awesome

13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
LaneContestantScore
{{contestant.lane}}{{contestant.name}}{{contestant.score}}
30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /js/leader-board-static.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('leaderboard', []); 2 | 3 | app.controller('MainCtrl', function (ContestantsService) { 4 | var main = this; 5 | main.newContestant = {lane: '', name: '', score: ''}; 6 | main.currentContestant = null; 7 | main.contestants = ContestantsService.getContestants(); 8 | 9 | main.addContestant = function () { 10 | ContestantsService.addContestant(angular.copy(main.newContestant)); 11 | main.newContestant = {lane: '', name: '', score: ''}; 12 | }; 13 | 14 | main.updateContestant = function (contestant) { 15 | ContestantsService.updateContestant(contestant); 16 | }; 17 | 18 | main.removeContestant = function (contestant) { 19 | ContestantsService.removeContestant(contestant); 20 | }; 21 | 22 | main.incrementScore = function () { 23 | main.currentContestant.score = parseInt(main.currentContestant.score, 10) + 1; 24 | main.updateContestant(main.currentContestant); 25 | }; 26 | 27 | main.decrementScore = function () { 28 | main.currentContestant.score = parseInt(main.currentContestant.score, 10) - 1; 29 | main.updateContestant(main.currentContestant); 30 | }; 31 | }); 32 | 33 | app.service('ContestantsService', function () { 34 | var service = this; 35 | var contestants = [ 36 | {id: 1, lane: 1, name: 'Contestant 01', score: '10'}, 37 | {id: 2, lane: 2, name: 'Contestant 02', score: '15'}, 38 | {id: 3, lane: 3, name: 'Contestant 03', score: '20'} 39 | ]; 40 | 41 | service.getContestants = function () { 42 | return contestants; 43 | }; 44 | 45 | service.addContestant = function (contestant) { 46 | contestant.id = new Date().getTime(); 47 | contestants.push(contestant); 48 | }; 49 | 50 | service.updateContestant = function (contestant) { 51 | // Already in memory 52 | }; 53 | 54 | service.removeContestant = function (contestant) { 55 | contestants.remove(function(c) { 56 | return c.id === contestant.id; 57 | }); 58 | }; 59 | }); -------------------------------------------------------------------------------- /js/leader-board.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('leaderboard', ['firebase']); 2 | 3 | app.constant('FIREBASE_URI', 'https://ng-fb-leaderboard.firebaseio.com/'); 4 | 5 | app.controller('MainCtrl', function (ContestantsService) { 6 | var main = this; 7 | main.newContestant = {lane: '', name: '', score: ''}; 8 | main.currentContestant = null; 9 | main.contestants = ContestantsService.getContestants(); 10 | 11 | main.addContestant = function () { 12 | ContestantsService.addContestant(angular.copy(main.newContestant)); 13 | main.newContestant = {lane: '', name: '', score: ''}; 14 | }; 15 | 16 | main.updateContestant = function (contestant) { 17 | ContestantsService.updateContestant(contestant); 18 | }; 19 | 20 | main.removeContestant = function (contestant) { 21 | ContestantsService.removeContestant(contestant); 22 | }; 23 | 24 | main.incrementScore = function () { 25 | main.currentContestant.score = parseInt(main.currentContestant.score, 10) + 1; 26 | main.updateContestant(main.currentContestant); 27 | }; 28 | 29 | main.decrementScore = function () { 30 | main.currentContestant.score = parseInt(main.currentContestant.score, 10) - 1; 31 | main.updateContestant(main.currentContestant); 32 | }; 33 | }); 34 | 35 | app.service('ContestantsService', function ($firebaseArray, FIREBASE_URI) { 36 | var service = this; 37 | var ref = new Firebase(FIREBASE_URI); 38 | var contestants = $firebaseArray(ref); 39 | 40 | service.getContestants = function () { 41 | return contestants; 42 | }; 43 | 44 | service.addContestant = function (contestant) { 45 | contestants.$add(contestant); 46 | }; 47 | 48 | service.updateContestant = function (contestant) { 49 | contestants.$save(contestant); 50 | }; 51 | 52 | service.removeContestant = function (contestant) { 53 | contestants.$remove(contestant); 54 | }; 55 | }); 56 | -------------------------------------------------------------------------------- /libs/angularfire.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * AngularFire is the officially supported AngularJS binding for Firebase. Firebase 3 | * is a full backend so you don't need servers to build your Angular app. AngularFire 4 | * provides you with the $firebase service which allows you to easily keep your $scope 5 | * variables in sync with your Firebase backend. 6 | * 7 | * AngularFire 1.0.0 8 | * https://github.com/firebase/angularfire/ 9 | * Date: 03/04/2015 10 | * License: MIT 11 | */ 12 | !function(a){"use strict";angular.module("firebase",[]).value("Firebase",a.Firebase).value("firebaseBatchDelay",50)}(window),function(){"use strict";angular.module("firebase").factory("$firebaseArray",["$log","$firebaseUtils",function(a,b){function c(a){if(!(this instanceof c))return new c(a);var e=this;return this._observers=[],this.$list=[],this._ref=a,this._sync=new d(this),b.assertValidRef(a,"Must pass a valid Firebase reference to $firebaseArray (not a string or URL)"),this._indexCache={},b.getPublicMethods(e,function(a,b){e.$list[b]=a.bind(e)}),this._sync.init(this.$list),this.$list}function d(c){function d(a){if(!p.isDestroyed){p.isDestroyed=!0;var b=c.$ref();b.off("child_added",i),b.off("child_moved",k),b.off("child_changed",j),b.off("child_removed",l),c=null,o(a||"destroyed")}}function e(b){var d=c.$ref();d.on("child_added",i,n),d.on("child_moved",k,n),d.on("child_changed",j,n),d.on("child_removed",l,n),d.once("value",function(c){angular.isArray(c.val())&&a.warn("Storing data using array indices in Firebase can result in unexpected behavior. See https://www.firebase.com/docs/web/guide/understanding-data.html#section-arrays-in-firebase for more information."),o(null,b)},o)}function f(a,b){m||(m=!0,a?g.reject(a):g.resolve(b))}var g=b.defer(),h=b.batch(),i=h(function(a,b){var d=c.$$added(a,b);d&&c.$$process("child_added",d,b)}),j=h(function(a){var d=c.$getRecord(b.getKey(a));if(d){var e=c.$$updated(a);e&&c.$$process("child_changed",d)}}),k=h(function(a,d){var e=c.$getRecord(b.getKey(a));if(e){var f=c.$$moved(a,d);f&&c.$$process("child_moved",e,d)}}),l=h(function(a){var d=c.$getRecord(b.getKey(a));if(d){var e=c.$$removed(a);e&&c.$$process("child_removed",d)}}),m=!1,n=h(function(a){f(a),c.$$error(a)}),o=h(f),p={destroy:d,isDestroyed:!1,init:e,ready:function(){return g.promise}};return p}return c.prototype={$add:function(a){this._assertNotDestroyed("$add");var c=b.defer(),d=this.$ref().ref().push();return d.set(b.toJSON(a),b.makeNodeResolver(c)),c.promise.then(function(){return d})},$save:function(a){this._assertNotDestroyed("$save");var c=this,d=c._resolveItem(a),e=c.$keyAt(d);if(null!==e){var f=c.$ref().ref().child(e),g=b.toJSON(d);return b.doSet(f,g).then(function(){return c.$$notify("child_changed",e),f})}return b.reject("Invalid record; could determine key for "+a)},$remove:function(a){this._assertNotDestroyed("$remove");var c=this.$keyAt(a);if(null!==c){var d=this.$ref().ref().child(c);return b.doRemove(d).then(function(){return d})}return b.reject("Invalid record; could not determine key for "+a)},$keyAt:function(a){var b=this._resolveItem(a);return this.$$getKey(b)},$indexFor:function(a){var b=this,c=b._indexCache;if(!c.hasOwnProperty(a)||b.$keyAt(c[a])!==a){var d=b.$list.findIndex(function(c){return b.$$getKey(c)===a});-1!==d&&(c[a]=d)}return c.hasOwnProperty(a)?c[a]:-1},$loaded:function(a,b){var c=this._sync.ready();return arguments.length&&(c=c.then.call(c,a,b)),c},$ref:function(){return this._ref},$watch:function(a,b){var c=this._observers;return c.push([a,b]),function(){var d=c.findIndex(function(c){return c[0]===a&&c[1]===b});d>-1&&c.splice(d,1)}},$destroy:function(b){this._isDestroyed||(this._isDestroyed=!0,this._sync.destroy(b),this.$list.length=0,a.debug("destroy called for FirebaseArray: "+this.$ref().ref().toString()))},$getRecord:function(a){var b=this.$indexFor(a);return b>-1?this.$list[b]:null},$$added:function(a){var c=this.$indexFor(b.getKey(a));if(-1===c){var d=a.val();return angular.isObject(d)||(d={$value:d}),d.$id=b.getKey(a),d.$priority=a.getPriority(),b.applyDefaults(d,this.$$defaults),d}return!1},$$removed:function(a){return this.$indexFor(b.getKey(a))>-1},$$updated:function(a){var c=!1,d=this.$getRecord(b.getKey(a));return angular.isObject(d)&&(c=b.updateRec(d,a),b.applyDefaults(d,this.$$defaults)),c},$$moved:function(a){var c=this.$getRecord(b.getKey(a));return angular.isObject(c)?(c.$priority=a.getPriority(),!0):!1},$$error:function(b){a.error(b),this.$destroy(b)},$$getKey:function(a){return angular.isObject(a)?a.$id:null},$$process:function(a,b,c){var d,e=this.$$getKey(b),f=!1;switch(a){case"child_added":d=this.$indexFor(e);break;case"child_moved":d=this.$indexFor(e),this._spliceOut(e);break;case"child_removed":f=null!==this._spliceOut(e);break;case"child_changed":f=!0;break;default:throw new Error("Invalid event type: "+a)}return angular.isDefined(d)&&(f=this._addAfter(b,c)!==d),f&&this.$$notify(a,e,c),f},$$notify:function(a,b,c){var d={event:a,key:b};angular.isDefined(c)&&(d.prevChild=c),angular.forEach(this._observers,function(a){a[0].call(a[1],d)})},_addAfter:function(a,b){var c;return null===b?c=0:(c=this.$indexFor(b)+1,0===c&&(c=this.$list.length)),this.$list.splice(c,0,a),this._indexCache[this.$$getKey(a)]=c,c},_spliceOut:function(a){var b=this.$indexFor(a);return b>-1?(delete this._indexCache[a],this.$list.splice(b,1)[0]):null},_resolveItem:function(a){var b=this.$list;if(angular.isNumber(a)&&a>=0&&b.length>=a)return b[a];if(angular.isObject(a)){var c=this.$$getKey(a),d=this.$getRecord(c);return d===a?d:null}return null},_assertNotDestroyed:function(a){if(this._isDestroyed)throw new Error("Cannot call "+a+" method on a destroyed $firebaseArray object")}},c.$extend=function(a,d){return 1===arguments.length&&angular.isObject(a)&&(d=a,a=function(){return c.apply(this,arguments)}),b.inherit(a,c,d)},c}]),angular.module("firebase").factory("$FirebaseArray",["$log","$firebaseArray",function(a,b){return function(){return a.warn("$FirebaseArray has been renamed. Use $firebaseArray instead."),b.apply(null,arguments)}}])}(),function(){"use strict";var a;angular.module("firebase").factory("$firebaseAuth",["$q","$firebaseUtils","$log",function(b,c,d){return function(e){var f=new a(b,c,d,e);return f.construct()}}]),a=function(a,b,c,d){if(this._q=a,this._utils=b,this._log=c,"string"==typeof d)throw new Error("Please provide a Firebase reference instead of a URL when creating a `$firebaseAuth` object.");this._ref=d},a.prototype={construct:function(){return this._object={$authWithCustomToken:this.authWithCustomToken.bind(this),$authAnonymously:this.authAnonymously.bind(this),$authWithPassword:this.authWithPassword.bind(this),$authWithOAuthPopup:this.authWithOAuthPopup.bind(this),$authWithOAuthRedirect:this.authWithOAuthRedirect.bind(this),$authWithOAuthToken:this.authWithOAuthToken.bind(this),$unauth:this.unauth.bind(this),$onAuth:this.onAuth.bind(this),$getAuth:this.getAuth.bind(this),$requireAuth:this.requireAuth.bind(this),$waitForAuth:this.waitForAuth.bind(this),$createUser:this.createUser.bind(this),$changePassword:this.changePassword.bind(this),$changeEmail:this.changeEmail.bind(this),$removeUser:this.removeUser.bind(this),$resetPassword:this.resetPassword.bind(this)},this._object},authWithCustomToken:function(a,b){var c=this._q.defer();try{this._ref.authWithCustomToken(a,this._utils.makeNodeResolver(c),b)}catch(d){c.reject(d)}return c.promise},authAnonymously:function(a){var b=this._q.defer();try{this._ref.authAnonymously(this._utils.makeNodeResolver(b),a)}catch(c){b.reject(c)}return b.promise},authWithPassword:function(a,b){var c=this._q.defer();try{this._ref.authWithPassword(a,this._utils.makeNodeResolver(c),b)}catch(d){c.reject(d)}return c.promise},authWithOAuthPopup:function(a,b){var c=this._q.defer();try{this._ref.authWithOAuthPopup(a,this._utils.makeNodeResolver(c),b)}catch(d){c.reject(d)}return c.promise},authWithOAuthRedirect:function(a,b){var c=this._q.defer();try{this._ref.authWithOAuthRedirect(a,this._utils.makeNodeResolver(c),b)}catch(d){c.reject(d)}return c.promise},authWithOAuthToken:function(a,b,c){var d=this._q.defer();try{this._ref.authWithOAuthToken(a,b,this._utils.makeNodeResolver(d),c)}catch(e){d.reject(e)}return d.promise},unauth:function(){null!==this.getAuth()&&this._ref.unauth()},onAuth:function(a,b){var c=this,d=this._utils.debounce(a,b,0);return this._ref.onAuth(d),function(){c._ref.offAuth(d)}},getAuth:function(){return this._ref.getAuth()},_routerMethodOnAuthPromise:function(a){var b=this._ref;return this._utils.promise(function(c,d){function e(f){return b.offAuth(e),null!==f?void c(f):a?void d("AUTH_REQUIRED"):void c(null)}b.onAuth(e)})},requireAuth:function(){return this._routerMethodOnAuthPromise(!0)},waitForAuth:function(){return this._routerMethodOnAuthPromise(!1)},createUser:function(a){var b=this._q.defer();if("string"==typeof a)throw new Error("$createUser() expects an object containing 'email' and 'password', but got a string.");try{this._ref.createUser(a,this._utils.makeNodeResolver(b))}catch(c){b.reject(c)}return b.promise},changePassword:function(a){var b=this._q.defer();if("string"==typeof a)throw new Error("$changePassword() expects an object containing 'email', 'oldPassword', and 'newPassword', but got a string.");try{this._ref.changePassword(a,this._utils.makeNodeResolver(b))}catch(c){b.reject(c)}return b.promise},changeEmail:function(a){if("function"!=typeof this._ref.changeEmail)throw new Error("$changeEmail() expects an object containing 'oldEmail', 'newEmail', and 'password', but got a string.");var b=this._q.defer();try{this._ref.changeEmail(a,this._utils.makeNodeResolver(b))}catch(c){b.reject(c)}return b.promise},removeUser:function(a){var b=this._q.defer();if("string"==typeof a)throw new Error("$removeUser() expects an object containing 'email' and 'password', but got a string.");try{this._ref.removeUser(a,this._utils.makeNodeResolver(b))}catch(c){b.reject(c)}return b.promise},resetPassword:function(a){var b=this._q.defer();if("string"==typeof a)throw new Error("$resetPassword() expects an object containing 'email', but got a string.");try{this._ref.resetPassword(a,this._utils.makeNodeResolver(b))}catch(c){b.reject(c)}return b.promise}}}(),function(){"use strict";angular.module("firebase").factory("$firebaseObject",["$parse","$firebaseUtils","$log",function(a,b,c){function d(a){return this instanceof d?(this.$$conf={sync:new f(this,a),ref:a,binding:new e(this),listeners:[]},Object.defineProperty(this,"$$conf",{value:this.$$conf}),this.$id=b.getKey(a.ref()),this.$priority=null,b.applyDefaults(this,this.$$defaults),void this.$$conf.sync.init()):new d(a)}function e(a){this.subs=[],this.scope=null,this.key=null,this.rec=a}function f(a,d){function e(b){n.isDestroyed||(n.isDestroyed=!0,d.off("value",k),a=null,m(b||"destroyed"))}function f(){d.on("value",k,l),d.once("value",function(a){angular.isArray(a.val())&&c.warn("Storing data using array indices in Firebase can result in unexpected behavior. See https://www.firebase.com/docs/web/guide/understanding-data.html#section-arrays-in-firebase for more information. Also note that you probably wanted $firebaseArray and not $firebaseObject."),m(null)},m)}function g(b){h||(h=!0,b?i.reject(b):i.resolve(a))}var h=!1,i=b.defer(),j=b.batch(),k=j(function(b){var c=a.$$updated(b);c&&a.$$notify()}),l=j(a.$$error,a),m=j(g),n={isDestroyed:!1,destroy:e,init:f,ready:function(){return i.promise}};return n}return d.prototype={$save:function(){var a=this,c=a.$ref(),d=b.toJSON(a);return b.doSet(c,d).then(function(){return a.$$notify(),a.$ref()})},$remove:function(){var a=this;return b.trimKeys(a,{}),a.$value=null,b.doRemove(a.$ref()).then(function(){return a.$$notify(),a.$ref()})},$loaded:function(a,b){var c=this.$$conf.sync.ready();return arguments.length&&(c=c.then.call(c,a,b)),c},$ref:function(){return this.$$conf.ref},$bindTo:function(a,b){var c=this;return c.$loaded().then(function(){return c.$$conf.binding.bindTo(a,b)})},$watch:function(a,b){var c=this.$$conf.listeners;return c.push([a,b]),function(){var d=c.findIndex(function(c){return c[0]===a&&c[1]===b});d>-1&&c.splice(d,1)}},$destroy:function(a){var c=this;c.$isDestroyed||(c.$isDestroyed=!0,c.$$conf.sync.destroy(a),c.$$conf.binding.destroy(),b.each(c,function(a,b){delete c[b]}))},$$updated:function(a){var c=b.updateRec(this,a);return b.applyDefaults(this,this.$$defaults),c},$$error:function(a){c.error(a),this.$destroy(a)},$$scopeUpdated:function(a){var c=b.defer();return this.$ref().set(b.toJSON(a),b.makeNodeResolver(c)),c.promise},$$notify:function(){var a=this,b=this.$$conf.listeners.slice();angular.forEach(b,function(b){b[0].call(b[1],{event:"value",key:a.$id})})},forEach:function(a,c){return b.each(this,a,c)}},d.$extend=function(a,c){return 1===arguments.length&&angular.isObject(a)&&(c=a,a=function(){d.apply(this,arguments)}),b.inherit(a,d,c)},e.prototype={assertNotBound:function(a){if(this.scope){var d="Cannot bind to "+a+" because this instance is already bound to "+this.key+"; one binding per instance (call unbind method or create another FirebaseObject instance)";return c.error(d),b.reject(d)}},bindTo:function(c,d){function e(e){function f(a){return angular.equals(a,k)&&a.$priority===k.$priority&&a.$value===k.$value}function g(a){j.assign(c,b.scopeData(a))}function h(){var a=j(c);return[a,a.$priority,a.$value]}var i=!1,j=a(d),k=e.rec;e.scope=c,e.varName=d;var l=b.debounce(function(a){var d=b.scopeData(a);k.$$scopeUpdated(d)["finally"](function(){i=!1,d.hasOwnProperty("$value")||(delete k.$value,delete j(c).$value)})},50,500),m=function(a){a=a[0],f(a)||(i=!0,l(a))},n=function(){i||f(j(c))||g(k)};return g(k),e.subs.push(c.$on("$destroy",e.unbind.bind(e))),e.subs.push(c.$watch(h,m,!0)),e.subs.push(k.$watch(n)),e.unbind.bind(e)}return this.assertNotBound(d)||e(this)},unbind:function(){this.scope&&(angular.forEach(this.subs,function(a){a()}),this.subs=[],this.scope=null,this.key=null)},destroy:function(){this.unbind(),this.rec=null}},d}]),angular.module("firebase").factory("$FirebaseObject",["$log","$firebaseObject",function(a,b){return function(){return a.warn("$FirebaseObject has been renamed. Use $firebaseObject instead."),b.apply(null,arguments)}}])}(),function(){"use strict";angular.module("firebase").factory("$firebase",function(){return function(){throw new Error("$firebase has been removed. You may instantiate $firebaseArray and $firebaseObject directly now. For simple write operations, just use the Firebase ref directly. See the AngularFire 1.0.0 changelog for details: https://www.firebase.com/docs/web/libraries/angular/changelog.html")}})}(),Array.prototype.indexOf||(Array.prototype.indexOf=function(a,b){if(void 0===this||null===this)throw new TypeError("'this' is null or not defined");var c=this.length>>>0;for(b=+b||0,1/0===Math.abs(b)&&(b=0),0>b&&(b+=c,0>b&&(b=0));c>b;b++)if(this[b]===a)return b;return-1}),Function.prototype.bind||(Function.prototype.bind=function(a){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var b=Array.prototype.slice.call(arguments,1),c=this,d=function(){},e=function(){return c.apply(this instanceof d&&a?this:a,b.concat(Array.prototype.slice.call(arguments)))};return d.prototype=this.prototype,e.prototype=new d,e}),Array.prototype.findIndex||Object.defineProperty(Array.prototype,"findIndex",{enumerable:!1,configurable:!0,writable:!0,value:function(a){if(null==this)throw new TypeError("Array.prototype.find called on null or undefined");if("function"!=typeof a)throw new TypeError("predicate must be a function");for(var b,c=Object(this),d=c.length>>>0,e=arguments[1],f=0;d>f;f++)if(f in c&&(b=c[f],a.call(e,b,f,c)))return f;return-1}}),"function"!=typeof Object.create&&!function(){var a=function(){};Object.create=function(b){if(arguments.length>1)throw new Error("Second argument not supported");if(null===b)throw new Error("Cannot set a null [[Prototype]]");if("object"!=typeof b)throw new TypeError("Argument must be an object");return a.prototype=b,new a}}(),Object.keys||(Object.keys=function(){"use strict";var a=Object.prototype.hasOwnProperty,b=!{toString:null}.propertyIsEnumerable("toString"),c=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],d=c.length;return function(e){if("object"!=typeof e&&("function"!=typeof e||null===e))throw new TypeError("Object.keys called on non-object");var f,g,h=[];for(f in e)a.call(e,f)&&h.push(f);if(b)for(g=0;d>g;g++)a.call(e,c[g])&&h.push(c[g]);return h}}()),"function"!=typeof Object.getPrototypeOf&&(Object.getPrototypeOf="object"==typeof"test".__proto__?function(a){return a.__proto__}:function(a){return a.constructor.prototype}),function(){"use strict";function a(b){if(!angular.isObject(b))return b;var c=angular.isArray(b)?[]:{};return angular.forEach(b,function(b,d){("string"!=typeof d||"$"!==d.charAt(0))&&(c[d]=a(b))}),c}angular.module("firebase").factory("$firebaseConfig",["$firebaseArray","$firebaseObject","$injector",function(a,b,c){return function(d){var e=angular.extend({},d);return"string"==typeof e.objectFactory&&(e.objectFactory=c.get(e.objectFactory)),"string"==typeof e.arrayFactory&&(e.arrayFactory=c.get(e.arrayFactory)),angular.extend({arrayFactory:a,objectFactory:b},e)}}]).factory("$firebaseUtils",["$q","$timeout","firebaseBatchDelay",function(b,c,d){function e(a){function c(a){e.resolve(a)}function d(a){e.reject(a)}if(!angular.isFunction(a))throw new Error("missing resolver function");var e=b.defer();return a(c,d),e.promise}var f={batch:function(a,b){function c(a,b){if("function"!=typeof a)throw new Error("Must provide a function to be batched. Got "+a);return function(){var c=Array.prototype.slice.call(arguments,0);k.push([a,b,c]),e()}}function e(){i&&(i(),i=null),h&&Date.now()-h>b?j||(j=!0,f.compile(g)):(h||(h=Date.now()),i=f.wait(g,a))}function g(){i=null,h=null,j=!1;var a=k.slice(0);k=[],angular.forEach(a,function(a){a[0].apply(a[1],a[2])})}a=d,b||(b=10*a||100);var h,i,j,k=[];return c},debounce:function(a,b,c,d){function e(){j&&(j(),j=null),i&&Date.now()-i>d?l||(l=!0,f.compile(g)):(i||(i=Date.now()),j=f.wait(g,c))}function g(){j=null,i=null,l=!1,a.apply(b,k)}function h(){k=Array.prototype.slice.call(arguments,0),e()}var i,j,k,l;if("number"==typeof b&&(d=c,c=b,b=null),"number"!=typeof c)throw new Error("Must provide a valid integer for wait. Try 0 for a default");if("function"!=typeof a)throw new Error("Must provide a valid function to debounce");return d||(d=10*c||100),h.running=function(){return i>0},h},assertValidRef:function(a,b){if(!angular.isObject(a)||"function"!=typeof a.ref||"function"!=typeof a.ref().transaction)throw new Error(b||"Invalid Firebase reference")},inherit:function(a,b,c){var d=a.prototype;return a.prototype=Object.create(b.prototype),a.prototype.constructor=a,angular.forEach(Object.keys(d),function(b){a.prototype[b]=d[b]}),angular.isObject(c)&&angular.extend(a.prototype,c),a},getPrototypeMethods:function(a,b,c){for(var d={},e=Object.getPrototypeOf({}),f=angular.isFunction(a)&&angular.isObject(a.prototype)?a.prototype:Object.getPrototypeOf(a);f&&f!==e;){for(var g in f)f.hasOwnProperty(g)&&!d.hasOwnProperty(g)&&(d[g]=!0,b.call(c,f[g],g,f));f=Object.getPrototypeOf(f)}},getPublicMethods:function(a,b,c){f.getPrototypeMethods(a,function(a,d){"function"==typeof a&&"_"!==d.charAt(0)&&b.call(c,a,d)})},defer:b.defer,reject:b.reject,resolve:b.when,promise:angular.isFunction(b)?b:e,makeNodeResolver:function(a){return function(b,c){null===b?(arguments.length>2&&(c=Array.prototype.slice.call(arguments,1)),a.resolve(c)):a.reject(b)}},wait:function(a,b){var d=c(a,b||0);return function(){d&&(c.cancel(d),d=null)}},compile:function(a){return c(a||function(){})},deepCopy:function(a){if(!angular.isObject(a))return a;var b=angular.isArray(a)?a.slice():angular.extend({},a);for(var c in b)b.hasOwnProperty(c)&&angular.isObject(b[c])&&(b[c]=f.deepCopy(b[c]));return b},trimKeys:function(a,b){f.each(a,function(c,d){b.hasOwnProperty(d)||delete a[d]})},scopeData:function(a){var b={$id:a.$id,$priority:a.$priority},c=!1;return f.each(a,function(a,d){c=!0,b[d]=f.deepCopy(a)}),!c&&a.hasOwnProperty("$value")&&(b.$value=a.$value),b},updateRec:function(a,b){var c=b.val(),d=angular.extend({},a);return angular.isObject(c)?delete a.$value:(a.$value=c,c={}),f.trimKeys(a,c),angular.extend(a,c),a.$priority=b.getPriority(),!angular.equals(d,a)||d.$value!==a.$value||d.$priority!==a.$priority},applyDefaults:function(a,b){return angular.isObject(b)&&angular.forEach(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)}),a},dataKeys:function(a){var b=[];return f.each(a,function(a,c){b.push(c)}),b},each:function(a,b,c){if(angular.isObject(a)){for(var d in a)if(a.hasOwnProperty(d)){var e=d.charAt(0);"_"!==e&&"$"!==e&&"."!==e&&b.call(c,a[d],d,a)}}else if(angular.isArray(a))for(var f=0,g=a.length;g>f;f++)b.call(c,a[f],f,a);return a},getKey:function(a){return"function"==typeof a.key?a.key():a.name()},toJSON:function(b){var c;return angular.isObject(b)||(b={$value:b}),angular.isFunction(b.toJSON)?c=b.toJSON():(c={},f.each(b,function(b,d){c[d]=a(b)})),angular.isDefined(b.$value)&&0===Object.keys(c).length&&null!==b.$value&&(c[".value"]=b.$value),angular.isDefined(b.$priority)&&Object.keys(c).length>0&&null!==b.$priority&&(c[".priority"]=b.$priority),angular.forEach(c,function(a,b){if(b.match(/[.$\[\]#\/]/)&&".value"!==b&&".priority"!==b)throw new Error("Invalid key "+b+" (cannot contain .$[]#)");if(angular.isUndefined(a))throw new Error("Key "+b+" was undefined. Cannot pass undefined in JSON. Use null instead.")}),c},doSet:function(a,b){var c=f.defer();if(angular.isFunction(a.set)||!angular.isObject(b))a.set(b,f.makeNodeResolver(c));else{var d=angular.extend({},b);a.once("value",function(b){b.forEach(function(a){d.hasOwnProperty(f.getKey(a))||(d[f.getKey(a)]=null)}),a.ref().update(d,f.makeNodeResolver(c))},function(a){c.reject(a)})}return c.promise},doRemove:function(a){var b=f.defer();return angular.isFunction(a.remove)?a.remove(f.makeNodeResolver(b)):a.once("value",function(c){var d=[];c.forEach(function(a){var c=f.defer();d.push(c.promise),a.ref().remove(f.makeNodeResolver(b))}),f.allPromises(d).then(function(){b.resolve(a)},function(a){b.reject(a)})},function(a){b.reject(a)}),b.promise},VERSION:"1.0.0",batchDelay:d,allPromises:b.all.bind(b)};return f}])}(); -------------------------------------------------------------------------------- /libs/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootstrap.js v3.0.0 by @fat and @mdo 3 | * Copyright 2013 Twitter Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0 5 | */ 6 | if (!jQuery) { throw new Error("Bootstrap requires jQuery") } 7 | 8 | /* ======================================================================== 9 | * Bootstrap: transition.js v3.0.0 10 | * http://twbs.github.com/bootstrap/javascript.html#transitions 11 | * ======================================================================== 12 | * Copyright 2013 Twitter, Inc. 13 | * 14 | * Licensed under the Apache License, Version 2.0 (the "License"); 15 | * you may not use this file except in compliance with the License. 16 | * You may obtain a copy of the License at 17 | * 18 | * http://www.apache.org/licenses/LICENSE-2.0 19 | * 20 | * Unless required by applicable law or agreed to in writing, software 21 | * distributed under the License is distributed on an "AS IS" BASIS, 22 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | * See the License for the specific language governing permissions and 24 | * limitations under the License. 25 | * ======================================================================== */ 26 | 27 | 28 | +function ($) { "use strict"; 29 | 30 | // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) 31 | // ============================================================ 32 | 33 | function transitionEnd() { 34 | var el = document.createElement('bootstrap') 35 | 36 | var transEndEventNames = { 37 | 'WebkitTransition' : 'webkitTransitionEnd' 38 | , 'MozTransition' : 'transitionend' 39 | , 'OTransition' : 'oTransitionEnd otransitionend' 40 | , 'transition' : 'transitionend' 41 | } 42 | 43 | for (var name in transEndEventNames) { 44 | if (el.style[name] !== undefined) { 45 | return { end: transEndEventNames[name] } 46 | } 47 | } 48 | } 49 | 50 | // http://blog.alexmaccaw.com/css-transitions 51 | $.fn.emulateTransitionEnd = function (duration) { 52 | var called = false, $el = this 53 | $(this).one($.support.transition.end, function () { called = true }) 54 | var callback = function () { if (!called) $($el).trigger($.support.transition.end) } 55 | setTimeout(callback, duration) 56 | return this 57 | } 58 | 59 | $(function () { 60 | $.support.transition = transitionEnd() 61 | }) 62 | 63 | }(window.jQuery); 64 | 65 | /* ======================================================================== 66 | * Bootstrap: alert.js v3.0.0 67 | * http://twbs.github.com/bootstrap/javascript.html#alerts 68 | * ======================================================================== 69 | * Copyright 2013 Twitter, Inc. 70 | * 71 | * Licensed under the Apache License, Version 2.0 (the "License"); 72 | * you may not use this file except in compliance with the License. 73 | * You may obtain a copy of the License at 74 | * 75 | * http://www.apache.org/licenses/LICENSE-2.0 76 | * 77 | * Unless required by applicable law or agreed to in writing, software 78 | * distributed under the License is distributed on an "AS IS" BASIS, 79 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 80 | * See the License for the specific language governing permissions and 81 | * limitations under the License. 82 | * ======================================================================== */ 83 | 84 | 85 | +function ($) { "use strict"; 86 | 87 | // ALERT CLASS DEFINITION 88 | // ====================== 89 | 90 | var dismiss = '[data-dismiss="alert"]' 91 | var Alert = function (el) { 92 | $(el).on('click', dismiss, this.close) 93 | } 94 | 95 | Alert.prototype.close = function (e) { 96 | var $this = $(this) 97 | var selector = $this.attr('data-target') 98 | 99 | if (!selector) { 100 | selector = $this.attr('href') 101 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 102 | } 103 | 104 | var $parent = $(selector) 105 | 106 | if (e) e.preventDefault() 107 | 108 | if (!$parent.length) { 109 | $parent = $this.hasClass('alert') ? $this : $this.parent() 110 | } 111 | 112 | $parent.trigger(e = $.Event('close.bs.alert')) 113 | 114 | if (e.isDefaultPrevented()) return 115 | 116 | $parent.removeClass('in') 117 | 118 | function removeElement() { 119 | $parent.trigger('closed.bs.alert').remove() 120 | } 121 | 122 | $.support.transition && $parent.hasClass('fade') ? 123 | $parent 124 | .one($.support.transition.end, removeElement) 125 | .emulateTransitionEnd(150) : 126 | removeElement() 127 | } 128 | 129 | 130 | // ALERT PLUGIN DEFINITION 131 | // ======================= 132 | 133 | var old = $.fn.alert 134 | 135 | $.fn.alert = function (option) { 136 | return this.each(function () { 137 | var $this = $(this) 138 | var data = $this.data('bs.alert') 139 | 140 | if (!data) $this.data('bs.alert', (data = new Alert(this))) 141 | if (typeof option == 'string') data[option].call($this) 142 | }) 143 | } 144 | 145 | $.fn.alert.Constructor = Alert 146 | 147 | 148 | // ALERT NO CONFLICT 149 | // ================= 150 | 151 | $.fn.alert.noConflict = function () { 152 | $.fn.alert = old 153 | return this 154 | } 155 | 156 | 157 | // ALERT DATA-API 158 | // ============== 159 | 160 | $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) 161 | 162 | }(window.jQuery); 163 | 164 | /* ======================================================================== 165 | * Bootstrap: button.js v3.0.0 166 | * http://twbs.github.com/bootstrap/javascript.html#buttons 167 | * ======================================================================== 168 | * Copyright 2013 Twitter, Inc. 169 | * 170 | * Licensed under the Apache License, Version 2.0 (the "License"); 171 | * you may not use this file except in compliance with the License. 172 | * You may obtain a copy of the License at 173 | * 174 | * http://www.apache.org/licenses/LICENSE-2.0 175 | * 176 | * Unless required by applicable law or agreed to in writing, software 177 | * distributed under the License is distributed on an "AS IS" BASIS, 178 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 179 | * See the License for the specific language governing permissions and 180 | * limitations under the License. 181 | * ======================================================================== */ 182 | 183 | 184 | +function ($) { "use strict"; 185 | 186 | // BUTTON PUBLIC CLASS DEFINITION 187 | // ============================== 188 | 189 | var Button = function (element, options) { 190 | this.$element = $(element) 191 | this.options = $.extend({}, Button.DEFAULTS, options) 192 | } 193 | 194 | Button.DEFAULTS = { 195 | loadingText: 'loading...' 196 | } 197 | 198 | Button.prototype.setState = function (state) { 199 | var d = 'disabled' 200 | var $el = this.$element 201 | var val = $el.is('input') ? 'val' : 'html' 202 | var data = $el.data() 203 | 204 | state = state + 'Text' 205 | 206 | if (!data.resetText) $el.data('resetText', $el[val]()) 207 | 208 | $el[val](data[state] || this.options[state]) 209 | 210 | // push to event loop to allow forms to submit 211 | setTimeout(function () { 212 | state == 'loadingText' ? 213 | $el.addClass(d).attr(d, d) : 214 | $el.removeClass(d).removeAttr(d); 215 | }, 0) 216 | } 217 | 218 | Button.prototype.toggle = function () { 219 | var $parent = this.$element.closest('[data-toggle="buttons"]') 220 | 221 | if ($parent.length) { 222 | var $input = this.$element.find('input') 223 | .prop('checked', !this.$element.hasClass('active')) 224 | .trigger('change') 225 | if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active') 226 | } 227 | 228 | this.$element.toggleClass('active') 229 | } 230 | 231 | 232 | // BUTTON PLUGIN DEFINITION 233 | // ======================== 234 | 235 | var old = $.fn.button 236 | 237 | $.fn.button = function (option) { 238 | return this.each(function () { 239 | var $this = $(this) 240 | var data = $this.data('bs.button') 241 | var options = typeof option == 'object' && option 242 | 243 | if (!data) $this.data('bs.button', (data = new Button(this, options))) 244 | 245 | if (option == 'toggle') data.toggle() 246 | else if (option) data.setState(option) 247 | }) 248 | } 249 | 250 | $.fn.button.Constructor = Button 251 | 252 | 253 | // BUTTON NO CONFLICT 254 | // ================== 255 | 256 | $.fn.button.noConflict = function () { 257 | $.fn.button = old 258 | return this 259 | } 260 | 261 | 262 | // BUTTON DATA-API 263 | // =============== 264 | 265 | $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) { 266 | var $btn = $(e.target) 267 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 268 | $btn.button('toggle') 269 | e.preventDefault() 270 | }) 271 | 272 | }(window.jQuery); 273 | 274 | /* ======================================================================== 275 | * Bootstrap: carousel.js v3.0.0 276 | * http://twbs.github.com/bootstrap/javascript.html#carousel 277 | * ======================================================================== 278 | * Copyright 2012 Twitter, Inc. 279 | * 280 | * Licensed under the Apache License, Version 2.0 (the "License"); 281 | * you may not use this file except in compliance with the License. 282 | * You may obtain a copy of the License at 283 | * 284 | * http://www.apache.org/licenses/LICENSE-2.0 285 | * 286 | * Unless required by applicable law or agreed to in writing, software 287 | * distributed under the License is distributed on an "AS IS" BASIS, 288 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 289 | * See the License for the specific language governing permissions and 290 | * limitations under the License. 291 | * ======================================================================== */ 292 | 293 | 294 | +function ($) { "use strict"; 295 | 296 | // CAROUSEL CLASS DEFINITION 297 | // ========================= 298 | 299 | var Carousel = function (element, options) { 300 | this.$element = $(element) 301 | this.$indicators = this.$element.find('.carousel-indicators') 302 | this.options = options 303 | this.paused = 304 | this.sliding = 305 | this.interval = 306 | this.$active = 307 | this.$items = null 308 | 309 | this.options.pause == 'hover' && this.$element 310 | .on('mouseenter', $.proxy(this.pause, this)) 311 | .on('mouseleave', $.proxy(this.cycle, this)) 312 | } 313 | 314 | Carousel.DEFAULTS = { 315 | interval: 5000 316 | , pause: 'hover' 317 | , wrap: true 318 | } 319 | 320 | Carousel.prototype.cycle = function (e) { 321 | e || (this.paused = false) 322 | 323 | this.interval && clearInterval(this.interval) 324 | 325 | this.options.interval 326 | && !this.paused 327 | && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) 328 | 329 | return this 330 | } 331 | 332 | Carousel.prototype.getActiveIndex = function () { 333 | this.$active = this.$element.find('.item.active') 334 | this.$items = this.$active.parent().children() 335 | 336 | return this.$items.index(this.$active) 337 | } 338 | 339 | Carousel.prototype.to = function (pos) { 340 | var that = this 341 | var activeIndex = this.getActiveIndex() 342 | 343 | if (pos > (this.$items.length - 1) || pos < 0) return 344 | 345 | if (this.sliding) return this.$element.one('slid', function () { that.to(pos) }) 346 | if (activeIndex == pos) return this.pause().cycle() 347 | 348 | return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) 349 | } 350 | 351 | Carousel.prototype.pause = function (e) { 352 | e || (this.paused = true) 353 | 354 | if (this.$element.find('.next, .prev').length && $.support.transition.end) { 355 | this.$element.trigger($.support.transition.end) 356 | this.cycle(true) 357 | } 358 | 359 | this.interval = clearInterval(this.interval) 360 | 361 | return this 362 | } 363 | 364 | Carousel.prototype.next = function () { 365 | if (this.sliding) return 366 | return this.slide('next') 367 | } 368 | 369 | Carousel.prototype.prev = function () { 370 | if (this.sliding) return 371 | return this.slide('prev') 372 | } 373 | 374 | Carousel.prototype.slide = function (type, next) { 375 | var $active = this.$element.find('.item.active') 376 | var $next = next || $active[type]() 377 | var isCycling = this.interval 378 | var direction = type == 'next' ? 'left' : 'right' 379 | var fallback = type == 'next' ? 'first' : 'last' 380 | var that = this 381 | 382 | if (!$next.length) { 383 | if (!this.options.wrap) return 384 | $next = this.$element.find('.item')[fallback]() 385 | } 386 | 387 | this.sliding = true 388 | 389 | isCycling && this.pause() 390 | 391 | var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction }) 392 | 393 | if ($next.hasClass('active')) return 394 | 395 | if (this.$indicators.length) { 396 | this.$indicators.find('.active').removeClass('active') 397 | this.$element.one('slid', function () { 398 | var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) 399 | $nextIndicator && $nextIndicator.addClass('active') 400 | }) 401 | } 402 | 403 | if ($.support.transition && this.$element.hasClass('slide')) { 404 | this.$element.trigger(e) 405 | if (e.isDefaultPrevented()) return 406 | $next.addClass(type) 407 | $next[0].offsetWidth // force reflow 408 | $active.addClass(direction) 409 | $next.addClass(direction) 410 | $active 411 | .one($.support.transition.end, function () { 412 | $next.removeClass([type, direction].join(' ')).addClass('active') 413 | $active.removeClass(['active', direction].join(' ')) 414 | that.sliding = false 415 | setTimeout(function () { that.$element.trigger('slid') }, 0) 416 | }) 417 | .emulateTransitionEnd(600) 418 | } else { 419 | this.$element.trigger(e) 420 | if (e.isDefaultPrevented()) return 421 | $active.removeClass('active') 422 | $next.addClass('active') 423 | this.sliding = false 424 | this.$element.trigger('slid') 425 | } 426 | 427 | isCycling && this.cycle() 428 | 429 | return this 430 | } 431 | 432 | 433 | // CAROUSEL PLUGIN DEFINITION 434 | // ========================== 435 | 436 | var old = $.fn.carousel 437 | 438 | $.fn.carousel = function (option) { 439 | return this.each(function () { 440 | var $this = $(this) 441 | var data = $this.data('bs.carousel') 442 | var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) 443 | var action = typeof option == 'string' ? option : options.slide 444 | 445 | if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) 446 | if (typeof option == 'number') data.to(option) 447 | else if (action) data[action]() 448 | else if (options.interval) data.pause().cycle() 449 | }) 450 | } 451 | 452 | $.fn.carousel.Constructor = Carousel 453 | 454 | 455 | // CAROUSEL NO CONFLICT 456 | // ==================== 457 | 458 | $.fn.carousel.noConflict = function () { 459 | $.fn.carousel = old 460 | return this 461 | } 462 | 463 | 464 | // CAROUSEL DATA-API 465 | // ================= 466 | 467 | $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { 468 | var $this = $(this), href 469 | var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 470 | var options = $.extend({}, $target.data(), $this.data()) 471 | var slideIndex = $this.attr('data-slide-to') 472 | if (slideIndex) options.interval = false 473 | 474 | $target.carousel(options) 475 | 476 | if (slideIndex = $this.attr('data-slide-to')) { 477 | $target.data('bs.carousel').to(slideIndex) 478 | } 479 | 480 | e.preventDefault() 481 | }) 482 | 483 | $(window).on('load', function () { 484 | $('[data-ride="carousel"]').each(function () { 485 | var $carousel = $(this) 486 | $carousel.carousel($carousel.data()) 487 | }) 488 | }) 489 | 490 | }(window.jQuery); 491 | 492 | /* ======================================================================== 493 | * Bootstrap: collapse.js v3.0.0 494 | * http://twbs.github.com/bootstrap/javascript.html#collapse 495 | * ======================================================================== 496 | * Copyright 2012 Twitter, Inc. 497 | * 498 | * Licensed under the Apache License, Version 2.0 (the "License"); 499 | * you may not use this file except in compliance with the License. 500 | * You may obtain a copy of the License at 501 | * 502 | * http://www.apache.org/licenses/LICENSE-2.0 503 | * 504 | * Unless required by applicable law or agreed to in writing, software 505 | * distributed under the License is distributed on an "AS IS" BASIS, 506 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 507 | * See the License for the specific language governing permissions and 508 | * limitations under the License. 509 | * ======================================================================== */ 510 | 511 | 512 | +function ($) { "use strict"; 513 | 514 | // COLLAPSE PUBLIC CLASS DEFINITION 515 | // ================================ 516 | 517 | var Collapse = function (element, options) { 518 | this.$element = $(element) 519 | this.options = $.extend({}, Collapse.DEFAULTS, options) 520 | this.transitioning = null 521 | 522 | if (this.options.parent) this.$parent = $(this.options.parent) 523 | if (this.options.toggle) this.toggle() 524 | } 525 | 526 | Collapse.DEFAULTS = { 527 | toggle: true 528 | } 529 | 530 | Collapse.prototype.dimension = function () { 531 | var hasWidth = this.$element.hasClass('width') 532 | return hasWidth ? 'width' : 'height' 533 | } 534 | 535 | Collapse.prototype.show = function () { 536 | if (this.transitioning || this.$element.hasClass('in')) return 537 | 538 | var startEvent = $.Event('show.bs.collapse') 539 | this.$element.trigger(startEvent) 540 | if (startEvent.isDefaultPrevented()) return 541 | 542 | var actives = this.$parent && this.$parent.find('> .panel > .in') 543 | 544 | if (actives && actives.length) { 545 | var hasData = actives.data('bs.collapse') 546 | if (hasData && hasData.transitioning) return 547 | actives.collapse('hide') 548 | hasData || actives.data('bs.collapse', null) 549 | } 550 | 551 | var dimension = this.dimension() 552 | 553 | this.$element 554 | .removeClass('collapse') 555 | .addClass('collapsing') 556 | [dimension](0) 557 | 558 | this.transitioning = 1 559 | 560 | var complete = function () { 561 | this.$element 562 | .removeClass('collapsing') 563 | .addClass('in') 564 | [dimension]('auto') 565 | this.transitioning = 0 566 | this.$element.trigger('shown.bs.collapse') 567 | } 568 | 569 | if (!$.support.transition) return complete.call(this) 570 | 571 | var scrollSize = $.camelCase(['scroll', dimension].join('-')) 572 | 573 | this.$element 574 | .one($.support.transition.end, $.proxy(complete, this)) 575 | .emulateTransitionEnd(350) 576 | [dimension](this.$element[0][scrollSize]) 577 | } 578 | 579 | Collapse.prototype.hide = function () { 580 | if (this.transitioning || !this.$element.hasClass('in')) return 581 | 582 | var startEvent = $.Event('hide.bs.collapse') 583 | this.$element.trigger(startEvent) 584 | if (startEvent.isDefaultPrevented()) return 585 | 586 | var dimension = this.dimension() 587 | 588 | this.$element 589 | [dimension](this.$element[dimension]()) 590 | [0].offsetHeight 591 | 592 | this.$element 593 | .addClass('collapsing') 594 | .removeClass('collapse') 595 | .removeClass('in') 596 | 597 | this.transitioning = 1 598 | 599 | var complete = function () { 600 | this.transitioning = 0 601 | this.$element 602 | .trigger('hidden.bs.collapse') 603 | .removeClass('collapsing') 604 | .addClass('collapse') 605 | } 606 | 607 | if (!$.support.transition) return complete.call(this) 608 | 609 | this.$element 610 | [dimension](0) 611 | .one($.support.transition.end, $.proxy(complete, this)) 612 | .emulateTransitionEnd(350) 613 | } 614 | 615 | Collapse.prototype.toggle = function () { 616 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 617 | } 618 | 619 | 620 | // COLLAPSE PLUGIN DEFINITION 621 | // ========================== 622 | 623 | var old = $.fn.collapse 624 | 625 | $.fn.collapse = function (option) { 626 | return this.each(function () { 627 | var $this = $(this) 628 | var data = $this.data('bs.collapse') 629 | var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) 630 | 631 | if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) 632 | if (typeof option == 'string') data[option]() 633 | }) 634 | } 635 | 636 | $.fn.collapse.Constructor = Collapse 637 | 638 | 639 | // COLLAPSE NO CONFLICT 640 | // ==================== 641 | 642 | $.fn.collapse.noConflict = function () { 643 | $.fn.collapse = old 644 | return this 645 | } 646 | 647 | 648 | // COLLAPSE DATA-API 649 | // ================= 650 | 651 | $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) { 652 | var $this = $(this), href 653 | var target = $this.attr('data-target') 654 | || e.preventDefault() 655 | || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 656 | var $target = $(target) 657 | var data = $target.data('bs.collapse') 658 | var option = data ? 'toggle' : $this.data() 659 | var parent = $this.attr('data-parent') 660 | var $parent = parent && $(parent) 661 | 662 | if (!data || !data.transitioning) { 663 | if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed') 664 | $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed') 665 | } 666 | 667 | $target.collapse(option) 668 | }) 669 | 670 | }(window.jQuery); 671 | 672 | /* ======================================================================== 673 | * Bootstrap: dropdown.js v3.0.0 674 | * http://twbs.github.com/bootstrap/javascript.html#dropdowns 675 | * ======================================================================== 676 | * Copyright 2012 Twitter, Inc. 677 | * 678 | * Licensed under the Apache License, Version 2.0 (the "License"); 679 | * you may not use this file except in compliance with the License. 680 | * You may obtain a copy of the License at 681 | * 682 | * http://www.apache.org/licenses/LICENSE-2.0 683 | * 684 | * Unless required by applicable law or agreed to in writing, software 685 | * distributed under the License is distributed on an "AS IS" BASIS, 686 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 687 | * See the License for the specific language governing permissions and 688 | * limitations under the License. 689 | * ======================================================================== */ 690 | 691 | 692 | +function ($) { "use strict"; 693 | 694 | // DROPDOWN CLASS DEFINITION 695 | // ========================= 696 | 697 | var backdrop = '.dropdown-backdrop' 698 | var toggle = '[data-toggle=dropdown]' 699 | var Dropdown = function (element) { 700 | var $el = $(element).on('click.bs.dropdown', this.toggle) 701 | } 702 | 703 | Dropdown.prototype.toggle = function (e) { 704 | var $this = $(this) 705 | 706 | if ($this.is('.disabled, :disabled')) return 707 | 708 | var $parent = getParent($this) 709 | var isActive = $parent.hasClass('open') 710 | 711 | clearMenus() 712 | 713 | if (!isActive) { 714 | if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { 715 | // if mobile we we use a backdrop because click events don't delegate 716 | $('