From ec94caeb8e839112112fafa5279a548667e7042d Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Fri, 6 Sep 2013 01:55:00 +0200 Subject: [PATCH 001/255] update angular versions with 1.0.8 and 1.2.0-rc.2 --- index.html | 12 ++++++------ js/homepage.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index 187046443..1d31fde66 100644 --- a/index.html +++ b/index.html @@ -53,15 +53,15 @@ }); $script('google-code-prettify/prettify.min.js', bootstrap); $script('js/homepage.js', bootstrap); - $script('http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js', bootstrap); - $script('http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular-resource.min.js', bootstrap); + $script('http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js', bootstrap); + $script('http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular-resource.min.js', bootstrap); $script('https://cdn.firebase.com/v0/firebase.js', function() { $script('http://firebase.github.io/angularFire/angularFire.js', function() { - $script('http://code.angularjs.org/1.0.7/i18n/angular-locale_sk.js', function() { + $script('http://code.angularjs.org/1.0.8/i18n/angular-locale_sk.js', function() { angular.module('ngLocal.sk', [])._invokeQueue.push(angular.module('ngLocale')._invokeQueue[0]); bootstrap(); }); - $script('http://code.angularjs.org/1.0.7/i18n/angular-locale_en-us.js', function() { + $script('http://code.angularjs.org/1.0.8/i18n/angular-locale_en-us.js', function() { angular.module('ngLocal.us', [])._invokeQueue.push(angular.module('ngLocale')._invokeQueue[0]); bootstrap(); }); @@ -88,7 +88,7 @@ +  - - + +
> gitFetchSite.log`; ?> From e5a1fb24f0b7ec5fdbfe8f8f32f1f751e2876f2f Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Mon, 25 Nov 2013 15:38:37 -0800 Subject: [PATCH 009/255] feat(propagate): update propagate script to use http and targetpool API --- gitFetchSite.js | 84 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/gitFetchSite.js b/gitFetchSite.js index 0f6920429..f1225c687 100644 --- a/gitFetchSite.js +++ b/gitFetchSite.js @@ -1,39 +1,75 @@ -var exec = require('child_process').exec; +var exec = require('child_process').exec, + http = require('http'), + TARGET_POOL = 'ng-sites', + REGION = 'us-central1', + PROJECT = '435162472401', + PORT = '8000'; console.log('Beginning propagation to other instances'); -exec('gcutil listinstances --format=json --project=435162472401', function (err, result, code) { +exec('gcutil gettargetpool '+ TARGET_POOL +' --project='+ PROJECT +' --region='+ REGION +' --format=json', function (err, result, code) { var instanceIPs = [], - executed = 0; + executed = 0, + instances; if (err) { throw new Error(err); } - var regions = JSON.parse(result).items; - - Object.keys(regions).forEach(function (key) { - var instances = regions[key].instances; - if (Array.isArray(instances)) { - instances.forEach(function (instance) { - instance.networkInterfaces && instance.networkInterfaces.forEach(function (netInt) { - netInt.accessConfigs && - netInt.accessConfigs[0] && - parseInt(netInt.accessConfigs[0].natIP, 10) && - instanceIPs.push(netInt.accessConfigs[0].natIP); + try { + targetPool = JSON.parse(result); + instances = targetPool.instances; + } + catch (e) { + console.error('Could not parse target pool'); + return process.exit(1); + } + + + instances.forEach(function (instance) { + var name = /^.*\/([a-zA-Z\-0-9]*)$/.exec(instance)[1]; + + exec('gcutil getinstance '+ name +' --format=json --project='+ PROJECT, function (err, result, code) { + var instance, reqUrl, exitCode = 0; + + if (err) { + console.error(err); + return process.exit(code); + } + + try { + instance = JSON.parse(result); + console.log('instance', instance); + instance.networkInterfaces.forEach(function (netInt) { + if (reqUrl) return; + + console.log('netInt', netInt); + + netInt.accessConfigs.forEach(function (config) { + console.log('config', config, config.natIP); + if (config.natIP) reqUrl = 'http://'+ config.natIP +':'+ PORT +'/gitFetchSite.php?doNotPropagate=true'; + console.log('reqUrl', reqUrl); + }); }); - }); - } - }); + } + catch (e) { + console.error(e); + return process.exit(1); + } - instanceIPs.forEach(function (ip) { - var reqUrl = 'http://' + ip + ':8000/gitFetchSite.php?doNotPropagate=true'; - console.log('Updating remote instance: ', reqUrl); + console.log('Updating remote instance: ', reqUrl); - exec('curl ' + reqUrl, function (err, result, code) { - console.log('Finished executing', reqUrl); - executed++; - executed === instanceIPs.length && process.exit(code); + http.get(reqUrl, function (res) { + console.log('Finished executing', reqUrl); + executed++; + executed === instanceIPs.length && process.exit(exitCode); + }).on('error', function (err) { + console.error('Failed to update', reqUrl); + console.error(err); + executed++; + exitCode = 1; + executed === instanceIPs.length && process.exit(exitCode); + }); }); }); }); From 670977950cc3be055c38552b078d9ec8105b74aa Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Mon, 25 Nov 2013 15:41:48 -0800 Subject: [PATCH 010/255] rename(gitFetchSite.js): rename to propagateClusterUpdate.js --- gitFetchSite.php | 2 +- gitFetchSite.js => propagateClusterUpdate.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename gitFetchSite.js => propagateClusterUpdate.js (100%) diff --git a/gitFetchSite.php b/gitFetchSite.php index 6c0036cdf..cd8781311 100644 --- a/gitFetchSite.php +++ b/gitFetchSite.php @@ -24,7 +24,7 @@

Date: Mon, 25 Nov 2013 16:36:58 -0800
Subject: [PATCH 011/255] update(propagate): replace console.error with
 console.log

---
 propagateClusterUpdate.js | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/propagateClusterUpdate.js b/propagateClusterUpdate.js
index f1225c687..f3fd352e0 100644
--- a/propagateClusterUpdate.js
+++ b/propagateClusterUpdate.js
@@ -21,7 +21,7 @@ exec('gcutil gettargetpool '+ TARGET_POOL +' --project='+ PROJECT +' --region='+
     instances = targetPool.instances;
   }
   catch (e) {
-    console.error('Could not parse target pool');
+    console.log('Could not parse target pool');
     return process.exit(1);
   }
 
@@ -33,30 +33,29 @@ exec('gcutil gettargetpool '+ TARGET_POOL +' --project='+ PROJECT +' --region='+
       var instance, reqUrl, exitCode = 0;
 
       if (err) {
-        console.error(err);
+        console.log(err);
         return process.exit(code);
       }
 
       try {
         instance = JSON.parse(result);
-        console.log('instance', instance);
         instance.networkInterfaces.forEach(function (netInt) {
           if (reqUrl) return;
 
-          console.log('netInt', netInt);
-
           netInt.accessConfigs.forEach(function (config) {
-            console.log('config', config, config.natIP);
             if (config.natIP) reqUrl = 'http://'+ config.natIP +':'+ PORT +'/gitFetchSite.php?doNotPropagate=true';
-            console.log('reqUrl', reqUrl);
           });
         });
       }
       catch (e) {
-        console.error(e);
+        console.log(e);
         return process.exit(1);
       }
 
+      if (!reqUrl) {
+        return console.log('Could not find any URL for instance', instance);
+      }
+
       console.log('Updating remote instance: ', reqUrl);
 
       http.get(reqUrl, function (res) {
@@ -64,8 +63,8 @@ exec('gcutil gettargetpool '+ TARGET_POOL +' --project='+ PROJECT +' --region='+
         executed++;
         executed === instanceIPs.length && process.exit(exitCode);
       }).on('error', function (err) {
-        console.error('Failed to update', reqUrl);
-        console.error(err);
+        console.log('Failed to update', reqUrl);
+        console.log(err);
         executed++;
         exitCode = 1;
         executed === instanceIPs.length && process.exit(exitCode);

From 28b638a33fc8f8f7b1272294bf9d806246728bd4 Mon Sep 17 00:00:00 2001
From: Jeff Cross 
Date: Mon, 25 Nov 2013 17:04:51 -0800
Subject: [PATCH 012/255] fix(reqUrl): use private ip instead of public

---
 propagateClusterUpdate.js | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/propagateClusterUpdate.js b/propagateClusterUpdate.js
index f3fd352e0..bdd417c49 100644
--- a/propagateClusterUpdate.js
+++ b/propagateClusterUpdate.js
@@ -41,10 +41,7 @@ exec('gcutil gettargetpool '+ TARGET_POOL +' --project='+ PROJECT +' --region='+
         instance = JSON.parse(result);
         instance.networkInterfaces.forEach(function (netInt) {
           if (reqUrl) return;
-
-          netInt.accessConfigs.forEach(function (config) {
-            if (config.natIP) reqUrl = 'http://'+ config.natIP +':'+ PORT +'/gitFetchSite.php?doNotPropagate=true';
-          });
+          if (netInt.networkIP) reqUrl = netInt.networkIP;
         });
       }
       catch (e) {

From 78f0cd9e479d86d2821d28d8c610b5f4bb734f93 Mon Sep 17 00:00:00 2001
From: Jeff Cross 
Date: Mon, 25 Nov 2013 17:07:18 -0800
Subject: [PATCH 013/255] fix(reqUrl): add appropriate protocol and path

---
 propagateClusterUpdate.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/propagateClusterUpdate.js b/propagateClusterUpdate.js
index bdd417c49..caa2e3529 100644
--- a/propagateClusterUpdate.js
+++ b/propagateClusterUpdate.js
@@ -41,7 +41,7 @@ exec('gcutil gettargetpool '+ TARGET_POOL +' --project='+ PROJECT +' --region='+
         instance = JSON.parse(result);
         instance.networkInterfaces.forEach(function (netInt) {
           if (reqUrl) return;
-          if (netInt.networkIP) reqUrl = netInt.networkIP;
+          if (netInt.networkIP) reqUrl = 'http://'+ netInt.networkIP +':'+ PORT +'/gitFetchSite.php?doNotPropagate=true';
         });
       }
       catch (e) {

From 7240b6238af29ddef9e72cc270cbdf62df34fbb9 Mon Sep 17 00:00:00 2001
From: Jeff Cross 
Date: Mon, 25 Nov 2013 17:08:48 -0800
Subject: [PATCH 014/255] fix(propagate): remove references to instanceIPs

---
 propagateClusterUpdate.js | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/propagateClusterUpdate.js b/propagateClusterUpdate.js
index caa2e3529..a69f972e7 100644
--- a/propagateClusterUpdate.js
+++ b/propagateClusterUpdate.js
@@ -8,8 +8,7 @@ var exec = require('child_process').exec,
 console.log('Beginning propagation to other instances');
 
 exec('gcutil gettargetpool '+ TARGET_POOL +' --project='+ PROJECT +' --region='+ REGION +' --format=json', function (err, result, code) {
-  var instanceIPs = [],
-      executed = 0,
+  var executed = 0,
       instances;
 
   if (err) {
@@ -58,13 +57,13 @@ exec('gcutil gettargetpool '+ TARGET_POOL +' --project='+ PROJECT +' --region='+
       http.get(reqUrl, function (res) {
         console.log('Finished executing', reqUrl);
         executed++;
-        executed === instanceIPs.length && process.exit(exitCode);
+        executed === instances.length && process.exit(exitCode);
       }).on('error', function (err) {
         console.log('Failed to update', reqUrl);
         console.log(err);
         executed++;
         exitCode = 1;
-        executed === instanceIPs.length && process.exit(exitCode);
+        executed === instances.length && process.exit(exitCode);
       });
     });
   });

From 8237479dd72331e3ff61d9f51837a8e4100c9c12 Mon Sep 17 00:00:00 2001
From: Jeff Cross 
Date: Wed, 27 Nov 2013 09:33:54 -0800
Subject: [PATCH 015/255] update(version): update angular version to 1.2.3

---
 index.html     | 12 ++++++------
 js/homepage.js |  4 ++--
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/index.html b/index.html
index 7e8aeb3ed..1c7db6b22 100644
--- a/index.html
+++ b/index.html
@@ -19,7 +19,7 @@
   
   
 
-  
+  
 
   
     
     
-    
     
     

From 2fb09bec621236c4b6149b4e67c70bd1f5e4964c Mon Sep 17 00:00:00 2001
From: Pete Bacon Darwin 
Date: Thu, 28 Nov 2013 14:49:21 +0000
Subject: [PATCH 017/255] fix(index): update JSFiddle modules

---
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/index.html b/index.html
index 6919cd038..554be5a23 100644
--- a/index.html
+++ b/index.html
@@ -317,7 +317,7 @@ 

Localization

- +

Locale: US

From 1e6cc840b04c05c13999d8a14417a9ad39daea1e Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 28 Nov 2013 14:51:59 +0000 Subject: [PATCH 018/255] fix(index): correct annotation Closes #57 --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 554be5a23..84be9f7bf 100644 --- a/index.html +++ b/index.html @@ -476,7 +476,7 @@

Todo

{ "TodoCtrl": "The controller is the code behind the view. You can clearly see your application behavior because there is no DOM manipulation or framework specific boilerplate. Just simple, readable JavaScript." , "$scope": "$scope contains your model data. It is the glue between the controller and the view. The $scope is just one of the services that can be injected into the controller." , "todos": "We are creating the model with two initial todo items. Notice that a you simply assign your model to the $scope and AngularJS reflects the state in the UI automatically. The model data is a Plain-Old-JavaScript-Object no need to wrap it in proxy or accesses the property through special setter methods." - , "addTodo": "We are assigning the behavior into the $scope so that the ng-click can invoke it." + , "addTodo": "We are assigning the behavior into the $scope so that the ng-submit can invoke it." , "push": "This is unmodified Array.push method. Calling it updates the model, which then updates the view through data-binding. The ng–repeat is bound to this array. It automatically unrolls the array and adds the new DOM element into the view. (see ng–repeat in index.html tab.)" , "todoText": "Because of bi-directional data-binding, the model is always up to date. This means that we can simply read the state of the user input. No need for registering callbacks, event listeners or using framework dependent API." , "''": "Writing to the form controls is just as easy. The data-binding will clear the control for us." From 50bf8fc4e8d0a680170a86b1aad7f6214d0c3973 Mon Sep 17 00:00:00 2001 From: Mike Heitzke Date: Mon, 1 Jul 2013 22:27:35 -0500 Subject: [PATCH 019/255] docs(index): fix link to bower in download modal Bower URL was outdated Closes #63 --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 84be9f7bf..e12bb7ce7 100644 --- a/index.html +++ b/index.html @@ -860,7 +860,7 @@

Download AngularJS

- +
From 0e9908d0b64fd23596d2caa28847c2beb8a4caa8 Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Fri, 6 Dec 2013 12:18:29 -0800 Subject: [PATCH 020/255] update(version): update angular version to 1.2.4 --- index.html | 36 ++++++++++++++++++------------------ js/homepage.js | 4 ++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/index.html b/index.html index e12bb7ce7..1d7087606 100644 --- a/index.html +++ b/index.html @@ -19,7 +19,7 @@ - +  - - + + - + - + +  - + @@ -117,10 +118,10 @@

HTML enhanced for web apps!

- + Download - ({{CURRENT_STABLE_VERSION}} / {{CURRENT_UNSTABLE_VERSION}}) + ({{branch.version}}{{ !$last ? ' / ' : '' }}) @@ -230,7 +231,7 @@

The Basics

Watch as we build this app

-
+
@@ -273,7 +274,7 @@

Plain JavaScript

Watch as we build this app

-
+
@@ -416,7 +417,7 @@

Testable

- - - - - - - + diff --git a/js/angular-ui-bootstrap.js b/js/angular-ui-bootstrap.js new file mode 100644 index 000000000..60f57a8cc --- /dev/null +++ b/js/angular-ui-bootstrap.js @@ -0,0 +1,3584 @@ +angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); +angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/popup.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]); +angular.module('ui.bootstrap.transition', []) + +/** + * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. + * @param {DOMElement} element The DOMElement that will be animated. + * @param {string|object|function} trigger The thing that will cause the transition to start: + * - As a string, it represents the css class to be added to the element. + * - As an object, it represents a hash of style attributes to be applied to the element. + * - As a function, it represents a function to be called that will cause the transition to occur. + * @return {Promise} A promise that is resolved when the transition finishes. + */ +.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { + + var $transition = function(element, trigger, options) { + options = options || {}; + var deferred = $q.defer(); + var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"]; + + var transitionEndHandler = function(event) { + $rootScope.$apply(function() { + element.unbind(endEventName, transitionEndHandler); + deferred.resolve(element); + }); + }; + + if (endEventName) { + element.bind(endEventName, transitionEndHandler); + } + + // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur + $timeout(function() { + if ( angular.isString(trigger) ) { + element.addClass(trigger); + } else if ( angular.isFunction(trigger) ) { + trigger(element); + } else if ( angular.isObject(trigger) ) { + element.css(trigger); + } + //If browser does not support transitions, instantly resolve + if ( !endEventName ) { + deferred.resolve(element); + } + }); + + // Add our custom cancel function to the promise that is returned + // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, + // i.e. it will therefore never raise a transitionEnd event for that transition + deferred.promise.cancel = function() { + if ( endEventName ) { + element.unbind(endEventName, transitionEndHandler); + } + deferred.reject('Transition cancelled'); + }; + + return deferred.promise; + }; + + // Work out the name of the transitionEnd event + var transElement = document.createElement('trans'); + var transitionEndEventNames = { + 'WebkitTransition': 'webkitTransitionEnd', + 'MozTransition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'transition': 'transitionend' + }; + var animationEndEventNames = { + 'WebkitTransition': 'webkitAnimationEnd', + 'MozTransition': 'animationend', + 'OTransition': 'oAnimationEnd', + 'transition': 'animationend' + }; + function findEndEventName(endEventNames) { + for (var name in endEventNames){ + if (transElement.style[name] !== undefined) { + return endEventNames[name]; + } + } + } + $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); + $transition.animationEndEventName = findEndEventName(animationEndEventNames); + return $transition; +}]); + +angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition']) + +// The collapsible directive indicates a block of html that will expand and collapse +.directive('collapse', ['$transition', function($transition) { + // CSS transitions don't work with height: auto, so we have to manually change the height to a + // specific value and then once the animation completes, we can reset the height to auto. + // Unfortunately if you do this while the CSS transitions are specified (i.e. in the CSS class + // "collapse") then you trigger a change to height 0 in between. + // The fix is to remove the "collapse" CSS class while changing the height back to auto - phew! + var fixUpHeight = function(scope, element, height) { + // We remove the collapse CSS class to prevent a transition when we change to height: auto + element.removeClass('collapse'); + element.css({ height: height }); + // It appears that reading offsetWidth makes the browser realise that we have changed the + // height already :-/ + var x = element[0].offsetWidth; + element.addClass('collapse'); + }; + + return { + link: function(scope, element, attrs) { + + var isCollapsed; + var initialAnimSkip = true; + + scope.$watch(attrs.collapse, function(value) { + if (value) { + collapse(); + } else { + expand(); + } + }); + + + var currentTransition; + var doTransition = function(change) { + if ( currentTransition ) { + currentTransition.cancel(); + } + currentTransition = $transition(element,change); + currentTransition.then( + function() { currentTransition = undefined; }, + function() { currentTransition = undefined; } + ); + return currentTransition; + }; + + var expand = function () { + isCollapsed = false; + if (initialAnimSkip) { + initialAnimSkip = false; + expandDone(); + } else { + var targetElHeight = element[0].scrollHeight; + if (targetElHeight) { + doTransition({ height: targetElHeight + 'px' }).then(expandDone); + } else { + expandDone(); + } + } + }; + + function expandDone() { + if ( !isCollapsed ) { + fixUpHeight(scope, element, 'auto'); + element.addClass('in'); + } + } + + var collapse = function() { + isCollapsed = true; + element.removeClass('in'); + if (initialAnimSkip) { + initialAnimSkip = false; + fixUpHeight(scope, element, 0); + } else { + fixUpHeight(scope, element, element[0].scrollHeight + 'px'); + doTransition({'height':'0'}); + } + }; + } + }; +}]); + +angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) + +.constant('accordionConfig', { + closeOthers: true +}) + +.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { + + // This array keeps track of the accordion groups + this.groups = []; + + // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to + this.closeOthers = function(openGroup) { + var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; + if ( closeOthers ) { + angular.forEach(this.groups, function (group) { + if ( group !== openGroup ) { + group.isOpen = false; + } + }); + } + }; + + // This is called from the accordion-group directive to add itself to the accordion + this.addGroup = function(groupScope) { + var that = this; + this.groups.push(groupScope); + + groupScope.$on('$destroy', function (event) { + that.removeGroup(groupScope); + }); + }; + + // This is called from the accordion-group directive when to remove itself + this.removeGroup = function(group) { + var index = this.groups.indexOf(group); + if ( index !== -1 ) { + this.groups.splice(this.groups.indexOf(group), 1); + } + }; + +}]) + +// The accordion directive simply sets up the directive controller +// and adds an accordion CSS class to itself element. +.directive('accordion', function () { + return { + restrict:'EA', + controller:'AccordionController', + transclude: true, + replace: false, + templateUrl: 'template/accordion/accordion.html' + }; +}) + +// The accordion-group directive indicates a block of html that will expand and collapse in an accordion +.directive('accordionGroup', ['$parse', function($parse) { + return { + require:'^accordion', // We need this directive to be inside an accordion + restrict:'EA', + transclude:true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl:'template/accordion/accordion-group.html', + scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + var getIsOpen, setIsOpen; + + accordionCtrl.addGroup(scope); + + scope.isOpen = false; + + if ( attrs.isOpen ) { + getIsOpen = $parse(attrs.isOpen); + setIsOpen = getIsOpen.assign; + + scope.$parent.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + + scope.$watch('isOpen', function(value) { + if ( value ) { + accordionCtrl.closeOthers(scope); + } + if ( setIsOpen ) { + setIsOpen(scope.$parent, value); + } + }); + } + }; +}]) + +// Use accordion-heading below an accordion-group to provide a heading containing HTML +// +// Heading containing HTML - +// +.directive('accordionHeading', function() { + return { + restrict: 'EA', + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^accordionGroup', + compile: function(element, attr, transclude) { + return function link(scope, element, attr, accordionGroupCtrl) { + // Pass the heading to the accordion-group controller + // so that it can be transcluded into the right place in the template + // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] + accordionGroupCtrl.setHeading(transclude(scope, function() {})); + }; + } + }; +}) + +// Use in the accordion-group template to indicate where you want the heading to be transcluded +// You must provide the property on the accordion-group controller that will hold the transcluded element +//
+// +// ... +//
+.directive('accordionTransclude', function() { + return { + require: '^accordionGroup', + link: function(scope, element, attr, controller) { + scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { + if ( heading ) { + element.html(''); + element.append(heading); + } + }); + } + }; +}); + +angular.module("ui.bootstrap.alert", []) + +.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { + $scope.closeable = 'close' in $attrs; +}]) + +.directive('alert', function () { + return { + restrict:'EA', + controller:'AlertController', + templateUrl:'template/alert/alert.html', + transclude:true, + replace:true, + scope: { + type: '=', + close: '&' + } + }; +}); + +angular.module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function () { + return function (scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +angular.module('ui.bootstrap.buttons', []) + +.constant('buttonConfig', { + activeClass: 'active', + toggleEvent: 'click' +}) + +.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { + this.activeClass = buttonConfig.activeClass || 'active'; + this.toggleEvent = buttonConfig.toggleEvent || 'click'; +}]) + +.directive('btnRadio', function () { + return { + require: ['btnRadio', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + if (!element.hasClass(buttonsCtrl.activeClass)) { + scope.$apply(function () { + ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio)); + ngModelCtrl.$render(); + }); + } + }); + } + }; +}) + +.directive('btnCheckbox', function () { + return { + require: ['btnCheckbox', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + function getTrueValue() { + return getCheckboxValue(attrs.btnCheckboxTrue, true); + } + + function getFalseValue() { + return getCheckboxValue(attrs.btnCheckboxFalse, false); + } + + function getCheckboxValue(attributeValue, defaultValue) { + var val = scope.$eval(attributeValue); + return angular.isDefined(val) ? val : defaultValue; + } + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + scope.$apply(function () { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + } + }; +}); + +/** +* @ngdoc overview +* @name ui.bootstrap.carousel +* +* @description +* AngularJS version of an image carousel. +* +*/ +angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) +.controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) { + var self = this, + slides = self.slides = [], + currentIndex = -1, + currentTimeout, isPlaying; + self.currentSlide = null; + + var destroyed = false; + /* direction: "prev" or "next" */ + self.select = function(nextSlide, direction) { + var nextIndex = slides.indexOf(nextSlide); + //Decide direction if it's not given + if (direction === undefined) { + direction = nextIndex > currentIndex ? "next" : "prev"; + } + if (nextSlide && nextSlide !== self.currentSlide) { + if ($scope.$currentTransition) { + $scope.$currentTransition.cancel(); + //Timeout so ng-class in template has time to fix classes for finished slide + $timeout(goNext); + } else { + goNext(); + } + } + function goNext() { + // Scope has been destroyed, stop here. + if (destroyed) { return; } + //If we have a slide to transition from and we have a transition type and we're allowed, go + if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { + //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime + nextSlide.$element.addClass(direction); + var reflow = nextSlide.$element[0].offsetWidth; //force reflow + + //Set all other slides to stop doing their stuff for the new transition + angular.forEach(slides, function(slide) { + angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); + }); + angular.extend(nextSlide, {direction: direction, active: true, entering: true}); + angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); + + $scope.$currentTransition = $transition(nextSlide.$element, {}); + //We have to create new pointers inside a closure since next & current will change + (function(next,current) { + $scope.$currentTransition.then( + function(){ transitionDone(next, current); }, + function(){ transitionDone(next, current); } + ); + }(nextSlide, self.currentSlide)); + } else { + transitionDone(nextSlide, self.currentSlide); + } + self.currentSlide = nextSlide; + currentIndex = nextIndex; + //every time you change slides, reset the timer + restartTimer(); + } + function transitionDone(next, current) { + angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); + angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); + $scope.$currentTransition = null; + } + }; + $scope.$on('$destroy', function () { + destroyed = true; + }); + + /* Allow outside people to call indexOf on slides array */ + self.indexOfSlide = function(slide) { + return slides.indexOf(slide); + }; + + $scope.next = function() { + var newIndex = (currentIndex + 1) % slides.length; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'next'); + } + }; + + $scope.prev = function() { + var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'prev'); + } + }; + + $scope.select = function(slide) { + self.select(slide); + }; + + $scope.isActive = function(slide) { + return self.currentSlide === slide; + }; + + $scope.slides = function() { + return slides; + }; + + $scope.$watch('interval', restartTimer); + $scope.$on('$destroy', resetTimer); + + function restartTimer() { + resetTimer(); + var interval = +$scope.interval; + if (!isNaN(interval) && interval>=0) { + currentTimeout = $timeout(timerFn, interval); + } + } + + function resetTimer() { + if (currentTimeout) { + $timeout.cancel(currentTimeout); + currentTimeout = null; + } + } + + function timerFn() { + if (isPlaying) { + $scope.next(); + restartTimer(); + } else { + $scope.pause(); + } + } + + $scope.play = function() { + if (!isPlaying) { + isPlaying = true; + restartTimer(); + } + }; + $scope.pause = function() { + if (!$scope.noPause) { + isPlaying = false; + resetTimer(); + } + }; + + self.addSlide = function(slide, element) { + slide.$element = element; + slides.push(slide); + //if this is the first slide or the slide is set to active, select it + if(slides.length === 1 || slide.active) { + self.select(slides[slides.length-1]); + if (slides.length == 1) { + $scope.play(); + } + } else { + slide.active = false; + } + }; + + self.removeSlide = function(slide) { + //get the index of the slide inside the carousel + var index = slides.indexOf(slide); + slides.splice(index, 1); + if (slides.length > 0 && slide.active) { + if (index >= slides.length) { + self.select(slides[index-1]); + } else { + self.select(slides[index]); + } + } else if (currentIndex > index) { + currentIndex--; + } + }; + +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:carousel + * @restrict EA + * + * @description + * Carousel is the outer container for a set of image 'slides' to showcase. + * + * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. + * @param {boolean=} noTransition Whether to disable transitions on the carousel. + * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). + * + * @example + + + + + + + + + + + + + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + + + */ +.directive('carousel', [function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + controller: 'CarouselController', + require: 'carousel', + templateUrl: 'template/carousel/carousel.html', + scope: { + interval: '=', + noTransition: '=', + noPause: '=' + } + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:slide + * @restrict EA + * + * @description + * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. + * + * @param {boolean=} active Model binding, whether or not this slide is currently active. + * + * @example + + +
+ + + + + + +
+
+
    +
  • + + {{$index}}: {{slide.text}} +
  • +
+ Add Slide +
+
+ Interval, in milliseconds: +
Enter a negative number to stop the interval. +
+
+
+
+ +function CarouselDemoCtrl($scope) { + $scope.myInterval = 5000; + var slides = $scope.slides = []; + $scope.addSlide = function() { + var newWidth = 200 + ((slides.length + (25 * slides.length)) % 150); + slides.push({ + image: 'http://placekitten.com/' + newWidth + '/200', + text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' ' + ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4] + }); + }; + for (var i=0; i<4; i++) $scope.addSlide(); +} + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + +
+*/ + +.directive('slide', ['$parse', function($parse) { + return { + require: '^carousel', + restrict: 'EA', + transclude: true, + replace: true, + templateUrl: 'template/carousel/slide.html', + scope: { + }, + link: function (scope, element, attrs, carouselCtrl) { + //Set up optional 'active' = binding + if (attrs.active) { + var getActive = $parse(attrs.active); + var setActive = getActive.assign; + var lastValue = scope.active = getActive(scope.$parent); + scope.$watch(function parentActiveWatch() { + var parentActive = getActive(scope.$parent); + + if (parentActive !== scope.active) { + // we are out of sync and need to copy + if (parentActive !== lastValue) { + // parent changed and it has precedence + lastValue = scope.active = parentActive; + } else { + // if the parent can be assigned then do so + setActive(scope.$parent, parentActive = lastValue = scope.active); + } + } + return parentActive; + }); + } + + carouselCtrl.addSlide(scope, element); + //when the scope is destroyed then remove the slide from the current slides array + scope.$on('$destroy', function() { + carouselCtrl.removeSlide(scope); + }); + + scope.$watch('active', function(active) { + if (active) { + carouselCtrl.select(scope); + } + }); + } + }; +}]); + +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', ['$document', '$window', function ($document, $window) { + + function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, "position") || 'static' ) === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + var parentOffsetEl = function (element) { + var docDomEl = $document[0]; + var offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function (element) { + var elBCR = this.offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) + }; + } + }; + }]); + +angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position']) + +.constant('datepickerConfig', { + dayFormat: 'dd', + monthFormat: 'MMMM', + yearFormat: 'yyyy', + dayHeaderFormat: 'EEE', + dayTitleFormat: 'MMMM yyyy', + monthTitleFormat: 'yyyy', + showWeeks: true, + startingDay: 0, + yearRange: 20, + minDate: null, + maxDate: null +}) + +.controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) { + var format = { + day: getValue($attrs.dayFormat, dtConfig.dayFormat), + month: getValue($attrs.monthFormat, dtConfig.monthFormat), + year: getValue($attrs.yearFormat, dtConfig.yearFormat), + dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat), + dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat), + monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat) + }, + startingDay = getValue($attrs.startingDay, dtConfig.startingDay), + yearRange = getValue($attrs.yearRange, dtConfig.yearRange); + + this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null; + this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null; + + function getValue(value, defaultValue) { + return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue; + } + + function getDaysInMonth( year, month ) { + return new Date(year, month, 0).getDate(); + } + + function getDates(startDate, n) { + var dates = new Array(n); + var current = startDate, i = 0; + while (i < n) { + dates[i++] = new Date(current); + current.setDate( current.getDate() + 1 ); + } + return dates; + } + + function makeDate(date, format, isSelected, isSecondary) { + return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary }; + } + + this.modes = [ + { + name: 'day', + getVisibleDates: function(date, selected) { + var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1); + var difference = startingDay - firstDayOfMonth.getDay(), + numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, + firstDate = new Date(firstDayOfMonth), numDates = 0; + + if ( numDisplayedFromPreviousMonth > 0 ) { + firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); + numDates += numDisplayedFromPreviousMonth; // Previous + } + numDates += getDaysInMonth(year, month + 1); // Current + numDates += (7 - numDates % 7) % 7; // Next + + var days = getDates(firstDate, numDates), labels = new Array(7); + for (var i = 0; i < numDates; i ++) { + var dt = new Date(days[i]); + days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month); + } + for (var j = 0; j < 7; j++) { + labels[j] = dateFilter(days[j].date, format.dayHeader); + } + return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels }; + }, + compare: function(date1, date2) { + return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); + }, + split: 7, + step: { months: 1 } + }, + { + name: 'month', + getVisibleDates: function(date, selected) { + var months = new Array(12), year = date.getFullYear(); + for ( var i = 0; i < 12; i++ ) { + var dt = new Date(year, i, 1); + months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year)); + } + return { objects: months, title: dateFilter(date, format.monthTitle) }; + }, + compare: function(date1, date2) { + return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); + }, + split: 3, + step: { years: 1 } + }, + { + name: 'year', + getVisibleDates: function(date, selected) { + var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1; + for ( var i = 0; i < yearRange; i++ ) { + var dt = new Date(startYear + i, 0, 1); + years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear())); + } + return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') }; + }, + compare: function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }, + split: 5, + step: { years: yearRange } + } + ]; + + this.isDisabled = function(date, mode) { + var currentMode = this.modes[mode || 0]; + return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name}))); + }; +}]) + +.directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/datepicker.html', + scope: { + dateDisabled: '&' + }, + require: ['datepicker', '?^ngModel'], + controller: 'DatepickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], ngModel = ctrls[1]; + + if (!ngModel) { + return; // do nothing if no ng-model + } + + // Configuration parameters + var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks; + + if (attrs.showWeeks) { + scope.$parent.$watch($parse(attrs.showWeeks), function(value) { + showWeeks = !! value; + updateShowWeekNumbers(); + }); + } else { + updateShowWeekNumbers(); + } + + if (attrs.min) { + scope.$parent.$watch($parse(attrs.min), function(value) { + datepickerCtrl.minDate = value ? new Date(value) : null; + refill(); + }); + } + if (attrs.max) { + scope.$parent.$watch($parse(attrs.max), function(value) { + datepickerCtrl.maxDate = value ? new Date(value) : null; + refill(); + }); + } + + function updateShowWeekNumbers() { + scope.showWeekNumbers = mode === 0 && showWeeks; + } + + // Split array into smaller arrays + function split(arr, size) { + var arrays = []; + while (arr.length > 0) { + arrays.push(arr.splice(0, size)); + } + return arrays; + } + + function refill( updateSelected ) { + var date = null, valid = true; + + if ( ngModel.$modelValue ) { + date = new Date( ngModel.$modelValue ); + + if ( isNaN(date) ) { + valid = false; + $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } else if ( updateSelected ) { + selected = date; + } + } + ngModel.$setValidity('date', valid); + + var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date); + angular.forEach(data.objects, function(obj) { + obj.disabled = datepickerCtrl.isDisabled(obj.date, mode); + }); + + ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date))); + + scope.rows = split(data.objects, currentMode.split); + scope.labels = data.labels || []; + scope.title = data.title; + } + + function setMode(value) { + mode = value; + updateShowWeekNumbers(); + refill(); + } + + ngModel.$render = function() { + refill( true ); + }; + + scope.select = function( date ) { + if ( mode === 0 ) { + var dt = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); + ngModel.$setViewValue( dt ); + refill( true ); + } else { + selected = date; + setMode( mode - 1 ); + } + }; + scope.move = function(direction) { + var step = datepickerCtrl.modes[mode].step; + selected.setMonth( selected.getMonth() + direction * (step.months || 0) ); + selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) ); + refill(); + }; + scope.toggleMode = function() { + setMode( (mode + 1) % datepickerCtrl.modes.length ); + }; + scope.getWeekNumber = function(row) { + return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null; + }; + + function getISO8601WeekNumber(date) { + var checkDate = new Date(date); + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday + var time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + } + } + }; +}]) + +.constant('datepickerPopupConfig', { + dateFormat: 'yyyy-MM-dd', + currentText: 'Today', + toggleWeeksText: 'Weeks', + clearText: 'Clear', + closeText: 'Done', + closeOnDateSelection: true, + appendToBody: false, + showButtonBar: true +}) + +.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', 'datepickerConfig', +function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig, datepickerConfig) { + return { + restrict: 'EA', + require: 'ngModel', + link: function(originalScope, element, attrs, ngModel) { + var scope = originalScope.$new(), // create a child scope so we are not polluting original one + dateFormat, + closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? originalScope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, + appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? originalScope.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + + attrs.$observe('datepickerPopup', function(value) { + dateFormat = value || datepickerPopupConfig.dateFormat; + ngModel.$render(); + }); + + scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? originalScope.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; + + originalScope.$on('$destroy', function() { + $popup.remove(); + scope.$destroy(); + }); + + attrs.$observe('currentText', function(text) { + scope.currentText = angular.isDefined(text) ? text : datepickerPopupConfig.currentText; + }); + attrs.$observe('toggleWeeksText', function(text) { + scope.toggleWeeksText = angular.isDefined(text) ? text : datepickerPopupConfig.toggleWeeksText; + }); + attrs.$observe('clearText', function(text) { + scope.clearText = angular.isDefined(text) ? text : datepickerPopupConfig.clearText; + }); + attrs.$observe('closeText', function(text) { + scope.closeText = angular.isDefined(text) ? text : datepickerPopupConfig.closeText; + }); + + var getIsOpen, setIsOpen; + if ( attrs.isOpen ) { + getIsOpen = $parse(attrs.isOpen); + setIsOpen = getIsOpen.assign; + + originalScope.$watch(getIsOpen, function updateOpen(value) { + scope.isOpen = !! value; + }); + } + scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state + + function setOpen( value ) { + if (setIsOpen) { + setIsOpen(originalScope, !!value); + } else { + scope.isOpen = !!value; + } + } + + var documentClickBind = function(event) { + if (scope.isOpen && event.target !== element[0]) { + scope.$apply(function() { + setOpen(false); + }); + } + }; + + var elementFocusBind = function() { + scope.$apply(function() { + setOpen( true ); + }); + }; + + // popup element used to display calendar + var popupEl = angular.element('
'); + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection()' + }); + var datepickerEl = angular.element(popupEl.children()[0]); + if (attrs.datepickerOptions) { + datepickerEl.attr(angular.extend({}, originalScope.$eval(attrs.datepickerOptions))); + } + + // TODO: reverse from dateFilter string to Date object + function parseDate(viewValue) { + if (!viewValue) { + ngModel.$setValidity('date', true); + return null; + } else if (angular.isDate(viewValue)) { + ngModel.$setValidity('date', true); + return viewValue; + } else if (angular.isString(viewValue)) { + var date = new Date(viewValue); + if (isNaN(date)) { + ngModel.$setValidity('date', false); + return undefined; + } else { + ngModel.$setValidity('date', true); + return date; + } + } else { + ngModel.$setValidity('date', false); + return undefined; + } + } + ngModel.$parsers.unshift(parseDate); + + // Inner change + scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + scope.date = dt; + } + ngModel.$setViewValue(scope.date); + ngModel.$render(); + + if (closeOnDateSelection) { + setOpen( false ); + } + }; + + element.bind('input change keyup', function() { + scope.$apply(function() { + scope.date = ngModel.$modelValue; + }); + }); + + // Outter change + ngModel.$render = function() { + var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; + element.val(date); + scope.date = ngModel.$modelValue; + }; + + function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) { + if (attribute) { + originalScope.$watch($parse(attribute), function(value){ + scope[scopeProperty] = value; + }); + datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty); + } + } + addWatchableAttribute(attrs.min, 'min'); + addWatchableAttribute(attrs.max, 'max'); + if (attrs.showWeeks) { + addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks'); + } else { + scope.showWeeks = datepickerConfig.showWeeks; + datepickerEl.attr('show-weeks', 'showWeeks'); + } + if (attrs.dateDisabled) { + datepickerEl.attr('date-disabled', attrs.dateDisabled); + } + + function updatePosition() { + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top = scope.position.top + element.prop('offsetHeight'); + } + + var documentBindingInitialized = false, elementFocusInitialized = false; + scope.$watch('isOpen', function(value) { + if (value) { + updatePosition(); + $document.bind('click', documentClickBind); + if(elementFocusInitialized) { + element.unbind('focus', elementFocusBind); + } + element[0].focus(); + documentBindingInitialized = true; + } else { + if(documentBindingInitialized) { + $document.unbind('click', documentClickBind); + } + element.bind('focus', elementFocusBind); + elementFocusInitialized = true; + } + + if ( setIsOpen ) { + setIsOpen(originalScope, value); + } + }); + + scope.today = function() { + scope.dateSelection(new Date()); + }; + scope.clear = function() { + scope.dateSelection(null); + }; + + var $popup = $compile(popupEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + } + }; +}]) + +.directive('datepickerPopupWrap', function() { + return { + restrict:'EA', + replace: true, + transclude: true, + templateUrl: 'template/datepicker/popup.html', + link:function (scope, element, attrs) { + element.bind('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + }); + } + }; +}); + +/* + * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js + * @restrict class or attribute + * @example: + + */ + +angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) { + var openElement = null, + closeMenu = angular.noop; + return { + restrict: 'CA', + link: function(scope, element, attrs) { + scope.$watch('$location.path', function() { closeMenu(); }); + element.parent().bind('click', function() { closeMenu(); }); + element.bind('click', function (event) { + + var elementWasOpen = (element === openElement); + + event.preventDefault(); + event.stopPropagation(); + + if (!!openElement) { + closeMenu(); + } + + if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) { + element.parent().addClass('open'); + openElement = element; + closeMenu = function (event) { + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + $document.unbind('click', closeMenu); + element.parent().removeClass('open'); + closeMenu = angular.noop; + openElement = null; + }; + $document.bind('click', closeMenu); + } + }); + } + }; +}]); + +angular.module('ui.bootstrap.modal', []) + +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function () { + return { + createNew: function () { + var stack = []; + + return { + add: function (key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function (key) { + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function () { + return stack[stack.length - 1]; + }, + remove: function (key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function () { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function () { + return stack.length; + } + }; + } + }; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/modal/backdrop.html', + link: function (scope) { + + scope.animate = false; + + //trigger CSS transitions + $timeout(function () { + scope.animate = true; + }); + + scope.close = function (evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop != 'static') { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + } + }; + }]) + + .directive('modalWindow', ['$timeout', function ($timeout) { + return { + restrict: 'EA', + scope: { + index: '@' + }, + replace: true, + transclude: true, + templateUrl: 'template/modal/window.html', + link: function (scope, element, attrs) { + scope.windowClass = attrs.windowClass || ''; + + // focus a freshly-opened modal + element[0].focus(); + + $timeout(function () { + // trigger CSS transitions + scope.animate = true; + }); + } + }; + }]) + + .factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap', + function ($document, $compile, $rootScope, $$stackedMap) { + + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropjqLiteEl, backdropDomEl; + var backdropScope = $rootScope.$new(true); + var openedWindows = $$stackedMap.createNew(); + var $modalStack = {}; + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + backdropScope.index = newBackdropIndex; + }); + + function removeModalWindow(modalInstance) { + + var body = $document.find('body').eq(0); + var modalWindow = openedWindows.get(modalInstance).value; + + //clean up the stack + openedWindows.remove(modalInstance); + + //remove window DOM element + modalWindow.modalDomEl.remove(); + body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); + + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + backdropDomEl.remove(); + backdropDomEl = undefined; + } + + //destroy scope + modalWindow.modalScope.$destroy(); + } + + $document.bind('keydown', function (evt) { + var modal; + + if (evt.which === 27) { + modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + $rootScope.$apply(function () { + $modalStack.dismiss(modal.key); + }); + } + } + }); + + $modalStack.open = function (modalInstance, modal) { + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard + }); + + var body = $document.find('body').eq(0); + + if (backdropIndex() >= 0 && !backdropDomEl) { + backdropjqLiteEl = angular.element('
'); + backdropDomEl = $compile(backdropjqLiteEl)(backdropScope); + body.append(backdropDomEl); + } + + var angularDomEl = angular.element('
'); + angularDomEl.attr('window-class', modal.windowClass); + angularDomEl.attr('index', openedWindows.length() - 1); + angularDomEl.html(modal.content); + + var modalDomEl = $compile(angularDomEl)(modal.scope); + openedWindows.top().value.modalDomEl = modalDomEl; + body.append(modalDomEl); + body.addClass(OPENED_MODAL_CLASS); + }; + + $modalStack.close = function (modalInstance, result) { + var modal = openedWindows.get(modalInstance); + if (modal) { + modal.value.deferred.resolve(result); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismiss = function (modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance).value; + if (modalWindow) { + modalWindow.deferred.reject(reason); + removeModalWindow(modalInstance); + } + }; + + $modalStack.getTop = function () { + return openedWindows.top(); + }; + + return $modalStack; + }]) + + .provider('$modal', function () { + + var $modalProvider = { + options: { + backdrop: true, //can be also false or 'static' + keyboard: true + }, + $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', + function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { + + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) { + return result.data; + }); + } + + function getResolvePromises(resolves) { + var promisesArr = []; + angular.forEach(resolves, function (value, key) { + if (angular.isFunction(value) || angular.isArray(value)) { + promisesArr.push($q.when($injector.invoke(value))); + } + }); + return promisesArr; + } + + $modal.open = function (modalOptions) { + + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + close: function (result) { + $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + $modalStack.dismiss(modalInstance, reason); + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + + + templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$modalInstance = modalInstance; + angular.forEach(modalOptions.resolve, function (value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + content: tplAndVars[0], + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + windowClass: modalOptions.windowClass + }); + + }, function resolveError(reason) { + modalResultDeferred.reject(reason); + }); + + templateAndResolvePromise.then(function () { + modalOpenedDeferred.resolve(true); + }, function () { + modalOpenedDeferred.reject(false); + }); + + return modalInstance; + }; + + return $modal; + }] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.pagination', []) + +.controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) { + var self = this, + setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; + + this.init = function(defaultItemsPerPage) { + if ($attrs.itemsPerPage) { + $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { + self.itemsPerPage = parseInt(value, 10); + $scope.totalPages = self.calculateTotalPages(); + }); + } else { + this.itemsPerPage = defaultItemsPerPage; + } + }; + + this.noPrevious = function() { + return this.page === 1; + }; + this.noNext = function() { + return this.page === $scope.totalPages; + }; + + this.isActive = function(page) { + return this.page === page; + }; + + this.calculateTotalPages = function() { + var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + return Math.max(totalPages || 0, 1); + }; + + this.getAttributeValue = function(attribute, defaultValue, interpolate) { + return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue; + }; + + this.render = function() { + this.page = parseInt($scope.page, 10) || 1; + if (this.page > 0 && this.page <= $scope.totalPages) { + $scope.pages = this.getPages(this.page, $scope.totalPages); + } + }; + + $scope.selectPage = function(page) { + if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) { + $scope.page = page; + $scope.onSelectPage({ page: page }); + } + }; + + $scope.$watch('page', function() { + self.render(); + }); + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( self.page > value ) { + $scope.selectPage(value); + } else { + self.render(); + } + }); +}]) + +.constant('paginationConfig', { + itemsPerPage: 10, + boundaryLinks: false, + directionLinks: true, + firstText: 'First', + previousText: 'Previous', + nextText: 'Next', + lastText: 'Last', + rotate: true +}) + +.directive('pagination', ['$parse', 'paginationConfig', function($parse, config) { + return { + restrict: 'EA', + scope: { + page: '=', + totalItems: '=', + onSelectPage:' &' + }, + controller: 'PaginationController', + templateUrl: 'template/pagination/pagination.html', + replace: true, + link: function(scope, element, attrs, paginationCtrl) { + + // Setup configuration parameters + var maxSize, + boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ), + directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ), + firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true), + previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true), + nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true), + lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true), + rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate); + + paginationCtrl.init(config.itemsPerPage); + + if (attrs.maxSize) { + scope.$parent.$watch($parse(attrs.maxSize), function(value) { + maxSize = parseInt(value, 10); + paginationCtrl.render(); + }); + } + + // Create page object used in template + function makePage(number, text, isActive, isDisabled) { + return { + number: number, + text: text, + active: isActive, + disabled: isDisabled + }; + } + + paginationCtrl.getPages = function(currentPage, totalPages) { + var pages = []; + + // Default page limits + var startPage = 1, endPage = totalPages; + var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); + + // recompute if maxSize + if ( isMaxSized ) { + if ( rotate ) { + // Current page is displayed in the middle of the visible ones + startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); + endPage = startPage + maxSize - 1; + + // Adjust if limit is exceeded + if (endPage > totalPages) { + endPage = totalPages; + startPage = endPage - maxSize + 1; + } + } else { + // Visible pages are paginated with maxSize + startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; + + // Adjust last page if limit is exceeded + endPage = Math.min(startPage + maxSize - 1, totalPages); + } + } + + // Add page number links + for (var number = startPage; number <= endPage; number++) { + var page = makePage(number, number, paginationCtrl.isActive(number), false); + pages.push(page); + } + + // Add links to move between page sets + if ( isMaxSized && ! rotate ) { + if ( startPage > 1 ) { + var previousPageSet = makePage(startPage - 1, '...', false, false); + pages.unshift(previousPageSet); + } + + if ( endPage < totalPages ) { + var nextPageSet = makePage(endPage + 1, '...', false, false); + pages.push(nextPageSet); + } + } + + // Add previous & next links + if (directionLinks) { + var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious()); + pages.unshift(previousPage); + + var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext()); + pages.push(nextPage); + } + + // Add first & last links + if (boundaryLinks) { + var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious()); + pages.unshift(firstPage); + + var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext()); + pages.push(lastPage); + } + + return pages; + }; + } + }; +}]) + +.constant('pagerConfig', { + itemsPerPage: 10, + previousText: '« Previous', + nextText: 'Next »', + align: true +}) + +.directive('pager', ['pagerConfig', function(config) { + return { + restrict: 'EA', + scope: { + page: '=', + totalItems: '=', + onSelectPage:' &' + }, + controller: 'PaginationController', + templateUrl: 'template/pagination/pager.html', + replace: true, + link: function(scope, element, attrs, paginationCtrl) { + + // Setup configuration parameters + var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true), + nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true), + align = paginationCtrl.getAttributeValue(attrs.align, config.align); + + paginationCtrl.init(config.itemsPerPage); + + // Create page object used in template + function makePage(number, text, isDisabled, isPrevious, isNext) { + return { + number: number, + text: text, + disabled: isDisabled, + previous: ( align && isPrevious ), + next: ( align && isNext ) + }; + } + + paginationCtrl.getPages = function(currentPage) { + return [ + makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false), + makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true) + ]; + }; + } + }; +}]); + +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0 + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'focus': 'blur' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers ( triggers ) { + angular.extend( triggerMap, triggers ); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { + return function $tooltip ( type, prefix, defaultTriggerShow ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers ( trigger ) { + var show = trigger || options.trigger || defaultTriggerShow; + var hide = triggerMap[show] || show; + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case( type ); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
'+ + '
'; + + return { + restrict: 'EA', + scope: true, + link: function link ( scope, element, attrs ) { + var tooltip = $compile( template )( scope ); + var transitionTimeout; + var popupTimeout; + var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; + var triggers = getTriggers( undefined ); + var hasRegisteredTriggers = false; + var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; + + function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + return; + } + if ( scope.tt_popupDelay ) { + popupTimeout = $timeout( show, scope.tt_popupDelay ); + } else { + scope.$apply( show ); + } + } + + function hideTooltipBind () { + scope.$apply(function () { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + var position, + ttWidth, + ttHeight, + ttPosition; + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return; + } + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + } + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + if ( appendToBody ) { + $document.find( 'body' ).append( tooltip ); + } else { + element.after( tooltip ); + } + + // Get the position of the directive element. + position = appendToBody ? $position.offset( element ) : $position.position( element ); + + // Get the height and width of the tooltip so we can center it. + ttWidth = tooltip.prop( 'offsetWidth' ); + ttHeight = tooltip.prop( 'offsetHeight' ); + + // Calculate the tooltip's top and left coordinates to center it with + // this directive. + switch ( scope.tt_placement ) { + case 'right': + ttPosition = { + top: position.top + position.height / 2 - ttHeight / 2, + left: position.left + position.width + }; + break; + case 'bottom': + ttPosition = { + top: position.top + position.height, + left: position.left + position.width / 2 - ttWidth / 2 + }; + break; + case 'left': + ttPosition = { + top: position.top + position.height / 2 - ttHeight / 2, + left: position.left - ttWidth + }; + break; + default: + ttPosition = { + top: position.top - ttHeight, + left: position.left + position.width / 2 - ttWidth / 2 + }; + break; + } + + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + + // And show the tooltip. + scope.tt_isOpen = true; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + scope.tt_isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel( popupTimeout ); + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( scope.tt_animation ) { + transitionTimeout = $timeout(function () { + tooltip.remove(); + }, 500); + } else { + tooltip.remove(); + } + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + + if (!val && scope.tt_isOpen ) { + hide(); + } + }); + + attrs.$observe( prefix+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( prefix+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( prefix+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + + var unregisterTriggers = function() { + if (hasRegisteredTriggers) { + element.unbind( triggers.show, showTooltipBind ); + element.unbind( triggers.hide, hideTooltipBind ); + } + }; + + attrs.$observe( prefix+'Trigger', function ( val ) { + unregisterTriggers(); + + triggers = getTriggers( val ); + + if ( triggers.show === triggers.hide ) { + element.bind( triggers.show, toggleTooltipBind ); + } else { + element.bind( triggers.show, showTooltipBind ); + element.bind( triggers.hide, hideTooltipBind ); + } + + hasRegisteredTriggers = true; + }); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; + + attrs.$observe( prefix+'AppendToBody', function ( val ) { + appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; + }); + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if ( appendToBody ) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { + if ( scope.tt_isOpen ) { + hide(); + } + }); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel( transitionTimeout ); + $timeout.cancel( popupTimeout ); + unregisterTriggers(); + tooltip.remove(); + tooltip.unbind(); + tooltip = null; + }); + } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' + }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +}]); + +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html popovers, and selector delegatation. + */ +angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) +.directive( 'popoverPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html' + }; +}) +.directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) { + return $tooltip( 'popover', 'popover', 'click' ); +}]); + + +angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition']) + +.constant('progressConfig', { + animate: true, + max: 100 +}) + +.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', '$transition', function($scope, $attrs, progressConfig, $transition) { + var self = this, + bars = [], + max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; + + this.addBar = function(bar, element) { + var oldValue = 0, index = bar.$parent.$index; + if ( angular.isDefined(index) && bars[index] ) { + oldValue = bars[index].value; + } + bars.push(bar); + + this.update(element, bar.value, oldValue); + + bar.$watch('value', function(value, oldValue) { + if (value !== oldValue) { + self.update(element, value, oldValue); + } + }); + + bar.$on('$destroy', function() { + self.removeBar(bar); + }); + }; + + // Update bar element width + this.update = function(element, newValue, oldValue) { + var percent = this.getPercentage(newValue); + + if (animate) { + element.css('width', this.getPercentage(oldValue) + '%'); + $transition(element, {width: percent + '%'}); + } else { + element.css({'transition': 'none', 'width': percent + '%'}); + } + }; + + this.removeBar = function(bar) { + bars.splice(bars.indexOf(bar), 1); + }; + + this.getPercentage = function(value) { + return Math.round(100 * value / max); + }; +}]) + +.directive('progress', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + require: 'progress', + scope: {}, + template: '
' + //templateUrl: 'template/progressbar/progress.html' // Works in AngularJS 1.2 + }; +}) + +.directive('bar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + require: '^progress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, element); + } + }; +}) + +.directive('progressbar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, angular.element(element.children()[0])); + } + }; +}); +angular.module('ui.bootstrap.rating', []) + +.constant('ratingConfig', { + max: 5, + stateOn: null, + stateOff: null +}) + +.controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) { + + this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max; + this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; + this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; + + this.createRateObjects = function(states) { + var defaultOptions = { + stateOn: this.stateOn, + stateOff: this.stateOff + }; + + for (var i = 0, n = states.length; i < n; i++) { + states[i] = angular.extend({ index: i }, defaultOptions, states[i]); + } + return states; + }; + + // Get objects used in template + $scope.range = angular.isDefined($attrs.ratingStates) ? this.createRateObjects(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createRateObjects(new Array(this.maxRange)); + + $scope.rate = function(value) { + if ( $scope.readonly || $scope.value === value) { + return; + } + + $scope.value = value; + }; + + $scope.enter = function(value) { + if ( ! $scope.readonly ) { + $scope.val = value; + } + $scope.onHover({value: value}); + }; + + $scope.reset = function() { + $scope.val = angular.copy($scope.value); + $scope.onLeave(); + }; + + $scope.$watch('value', function(value) { + $scope.val = value; + }); + + $scope.readonly = false; + if ($attrs.readonly) { + $scope.$parent.$watch($parse($attrs.readonly), function(value) { + $scope.readonly = !!value; + }); + } +}]) + +.directive('rating', function() { + return { + restrict: 'EA', + scope: { + value: '=', + onHover: '&', + onLeave: '&' + }, + controller: 'RatingController', + templateUrl: 'template/rating/rating.html', + replace: true + }; +}); + +/** + * @ngdoc overview + * @name ui.bootstrap.tabs + * + * @description + * AngularJS version of the tabs directive. + */ + +angular.module('ui.bootstrap.tabs', []) + +.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { + var ctrl = this, + tabs = ctrl.tabs = $scope.tabs = []; + + ctrl.select = function(tab) { + angular.forEach(tabs, function(tab) { + tab.active = false; + }); + tab.active = true; + }; + + ctrl.addTab = function addTab(tab) { + tabs.push(tab); + if (tabs.length === 1 || tab.active) { + ctrl.select(tab); + } + }; + + ctrl.removeTab = function removeTab(tab) { + var index = tabs.indexOf(tab); + //Select a new tab if the tab to be removed is selected + if (tab.active && tabs.length > 1) { + //If this is the last tab, select the previous tab. else, the next tab. + var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; + ctrl.select(tabs[newActiveIndex]); + } + tabs.splice(index, 1); + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabset + * @restrict EA + * + * @description + * Tabset is the outer container for the tabs directive + * + * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. + * + * @example + + + + First Content! + Second Content! + +
+ + First Vertical Content! + Second Vertical Content! + +
+
+ */ +.directive('tabset', function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + scope: {}, + controller: 'TabsetController', + templateUrl: 'template/tabs/tabset.html', + link: function(scope, element, attrs) { + scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; + scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs'; + } + }; +}) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tab + * @restrict EA + * + * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. + * @param {string=} select An expression to evaluate when the tab is selected. + * @param {boolean=} active A binding, telling whether or not this tab is selected. + * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. + * + * @description + * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. + * + * @example + + +
+ + +
+ + First Tab + + Alert me! + Second Tab, with alert callback and html heading! + + + {{item.content}} + + +
+
+ + function TabsDemoCtrl($scope) { + $scope.items = [ + { title:"Dynamic Title 1", content:"Dynamic Item 0" }, + { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } + ]; + + $scope.alertMe = function() { + setTimeout(function() { + alert("You've selected the alert tab!"); + }); + }; + }; + +
+ */ + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabHeading + * @restrict EA + * + * @description + * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. + * + * @example + + + + + HTML in my titles?! + And some content, too! + + + Icon heading?!? + That's right. + + + + + */ +.directive('tab', ['$parse', function($parse) { + return { + require: '^tabset', + restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', + transclude: true, + scope: { + heading: '@', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + compile: function(elm, attrs, transclude) { + return function postLink(scope, elm, attrs, tabsetCtrl) { + var getActive, setActive; + if (attrs.active) { + getActive = $parse(attrs.active); + setActive = getActive.assign; + scope.$parent.$watch(getActive, function updateActive(value, oldVal) { + // Avoid re-initializing scope.active as it is already initialized + // below. (watcher is called async during init with value === + // oldVal) + if (value !== oldVal) { + scope.active = !!value; + } + }); + scope.active = getActive(scope.$parent); + } else { + setActive = getActive = angular.noop; + } + + scope.$watch('active', function(active) { + // Note this watcher also initializes and assigns scope.active to the + // attrs.active expression. + setActive(scope.$parent, active); + if (active) { + tabsetCtrl.select(scope); + scope.onSelect(); + } else { + scope.onDeselect(); + } + }); + + scope.disabled = false; + if ( attrs.disabled ) { + scope.$parent.$watch($parse(attrs.disabled), function(value) { + scope.disabled = !! value; + }); + } + + scope.select = function() { + if ( ! scope.disabled ) { + scope.active = true; + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + }; + } + }; +}]) + +.directive('tabHeadingTransclude', [function() { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm, attrs, tabCtrl) { + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; +}]) + +.directive('tabContentTransclude', function() { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs) { + var tab = scope.$eval(attrs.tabContentTransclude); + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } else { + elm.append(node); + } + }); + }); + } + }; + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('tab-heading') || + node.hasAttribute('data-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || + node.tagName.toLowerCase() === 'data-tab-heading' + ); + } +}) + +; + +angular.module('ui.bootstrap.timepicker', []) + +.constant('timepickerConfig', { + hourStep: 1, + minuteStep: 1, + showMeridian: true, + meridians: null, + readonlyInput: false, + mousewheel: true +}) + +.directive('timepicker', ['$parse', '$log', 'timepickerConfig', '$locale', function ($parse, $log, timepickerConfig, $locale) { + return { + restrict: 'EA', + require:'?^ngModel', + replace: true, + scope: {}, + templateUrl: 'template/timepicker/timepicker.html', + link: function(scope, element, attrs, ngModel) { + if ( !ngModel ) { + return; // do nothing if no ng-model + } + + var selected = new Date(), + meridians = angular.isDefined(attrs.meridians) ? scope.$parent.$eval(attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; + + var hourStep = timepickerConfig.hourStep; + if (attrs.hourStep) { + scope.$parent.$watch($parse(attrs.hourStep), function(value) { + hourStep = parseInt(value, 10); + }); + } + + var minuteStep = timepickerConfig.minuteStep; + if (attrs.minuteStep) { + scope.$parent.$watch($parse(attrs.minuteStep), function(value) { + minuteStep = parseInt(value, 10); + }); + } + + // 12H / 24H mode + scope.showMeridian = timepickerConfig.showMeridian; + if (attrs.showMeridian) { + scope.$parent.$watch($parse(attrs.showMeridian), function(value) { + scope.showMeridian = !!value; + + if ( ngModel.$error.time ) { + // Evaluate from template + var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); + if (angular.isDefined( hours ) && angular.isDefined( minutes )) { + selected.setHours( hours ); + refresh(); + } + } else { + updateTemplate(); + } + }); + } + + // Get scope.hours in 24H mode if valid + function getHoursFromTemplate ( ) { + var hours = parseInt( scope.hours, 10 ); + var valid = ( scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); + if ( !valid ) { + return undefined; + } + + if ( scope.showMeridian ) { + if ( hours === 12 ) { + hours = 0; + } + if ( scope.meridian === meridians[1] ) { + hours = hours + 12; + } + } + return hours; + } + + function getMinutesFromTemplate() { + var minutes = parseInt(scope.minutes, 10); + return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; + } + + function pad( value ) { + return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; + } + + // Input elements + var inputs = element.find('input'), hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1); + + // Respond on mousewheel spin + var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel; + if ( mousewheel ) { + + var isScrollingUp = function(e) { + if (e.originalEvent) { + e = e.originalEvent; + } + //pick correct delta variable depending on event + var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; + return (e.detail || delta > 0); + }; + + hoursInputEl.bind('mousewheel wheel', function(e) { + scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() ); + e.preventDefault(); + }); + + minutesInputEl.bind('mousewheel wheel', function(e) { + scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() ); + e.preventDefault(); + }); + } + + scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput; + if ( ! scope.readonlyInput ) { + + var invalidate = function(invalidHours, invalidMinutes) { + ngModel.$setViewValue( null ); + ngModel.$setValidity('time', false); + if (angular.isDefined(invalidHours)) { + scope.invalidHours = invalidHours; + } + if (angular.isDefined(invalidMinutes)) { + scope.invalidMinutes = invalidMinutes; + } + }; + + scope.updateHours = function() { + var hours = getHoursFromTemplate(); + + if ( angular.isDefined(hours) ) { + selected.setHours( hours ); + refresh( 'h' ); + } else { + invalidate(true); + } + }; + + hoursInputEl.bind('blur', function(e) { + if ( !scope.validHours && scope.hours < 10) { + scope.$apply( function() { + scope.hours = pad( scope.hours ); + }); + } + }); + + scope.updateMinutes = function() { + var minutes = getMinutesFromTemplate(); + + if ( angular.isDefined(minutes) ) { + selected.setMinutes( minutes ); + refresh( 'm' ); + } else { + invalidate(undefined, true); + } + }; + + minutesInputEl.bind('blur', function(e) { + if ( !scope.invalidMinutes && scope.minutes < 10 ) { + scope.$apply( function() { + scope.minutes = pad( scope.minutes ); + }); + } + }); + } else { + scope.updateHours = angular.noop; + scope.updateMinutes = angular.noop; + } + + ngModel.$render = function() { + var date = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : null; + + if ( isNaN(date) ) { + ngModel.$setValidity('time', false); + $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } else { + if ( date ) { + selected = date; + } + makeValid(); + updateTemplate(); + } + }; + + // Call internally when we know that model is valid. + function refresh( keyboardChange ) { + makeValid(); + ngModel.$setViewValue( new Date(selected) ); + updateTemplate( keyboardChange ); + } + + function makeValid() { + ngModel.$setValidity('time', true); + scope.invalidHours = false; + scope.invalidMinutes = false; + } + + function updateTemplate( keyboardChange ) { + var hours = selected.getHours(), minutes = selected.getMinutes(); + + if ( scope.showMeridian ) { + hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system + } + scope.hours = keyboardChange === 'h' ? hours : pad(hours); + scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); + scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + } + + function addMinutes( minutes ) { + var dt = new Date( selected.getTime() + minutes * 60000 ); + selected.setHours( dt.getHours(), dt.getMinutes() ); + refresh(); + } + + scope.incrementHours = function() { + addMinutes( hourStep * 60 ); + }; + scope.decrementHours = function() { + addMinutes( - hourStep * 60 ); + }; + scope.incrementMinutes = function() { + addMinutes( minuteStep ); + }; + scope.decrementMinutes = function() { + addMinutes( - minuteStep ); + }; + scope.toggleMeridian = function() { + addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); + }; + } + }; +}]); + +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) + +/** + * A helper service that can parse typeahead's syntax (string provided by users) + * Extracted to a separate service for ease of unit testing + */ + .factory('typeaheadParser', ['$parse', function ($parse) { + + // 00000111000000000000022200000000000000003333333333333330000000000044000 + var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/; + + return { + parse:function (input) { + + var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source; + if (!match) { + throw new Error( + "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" + + " but got '" + input + "'."); + } + + return { + itemName:match[3], + source:$parse(match[4]), + viewMapper:$parse(match[2] || match[1]), + modelMapper:$parse(match[1]) + }; + } + }; +}]) + + .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', + function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { + + var HOT_KEYS = [9, 13, 27, 38, 40]; + + return { + require:'ngModel', + link:function (originalScope, element, attrs, modelCtrl) { + + //SUPPORTED ATTRIBUTES (OPTIONS) + + //minimal no of characters that needs to be entered before typeahead kicks-in + var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; + + //minimal wait time after last character typed before typehead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; + + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + + //binding to a variable that indicates if matches are being retrieved asynchronously + var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + + //a callback executed when a match is selected + var onSelectCallback = $parse(attrs.typeaheadOnSelect); + + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; + + var appendToBody = attrs.typeaheadAppendToBody ? $parse(attrs.typeaheadAppendToBody) : false; + + //INTERNAL VARIABLES + + //model setter executed upon match selection + var $setModelValue = $parse(attrs.ngModel).assign; + + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); + + var hasFocus; + + //pop-up element used to display matches + var popUpEl = angular.element('
'); + popUpEl.attr({ + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + originalScope.$on('$destroy', function(){ + scope.$destroy(); + }); + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + }; + + var getMatchesAsync = function(inputValue) { + + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + if (inputValue === modelCtrl.$viewValue && hasFocus) { + if (matches.length > 0) { + + scope.activeIdx = 0; + scope.matches.length = 0; + + //transform labels + for(var i=0; i= minSearch) { + if (waitTime > 0) { + if (timeoutPromise) { + $timeout.cancel(timeoutPromise);//cancel previous timeout + } + timeoutPromise = $timeout(function () { + getMatchesAsync(inputValue); + }, waitTime); + } else { + getMatchesAsync(inputValue); + } + } else { + isLoadingSetter(originalScope, false); + resetMatches(); + } + + if (isEditable) { + return inputValue; + } else { + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return inputValue; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } + } + }); + + modelCtrl.$formatters.push(function (modelValue) { + + var candidateViewValue, emptyViewValue; + var locals = {}; + + if (inputFormatter) { + + locals['$model'] = modelValue; + return inputFormatter(originalScope, locals); + + } else { + + //it might happen that we don't have enough info to properly render input value + //we need to check for this situation and simply return model value if we can't apply custom formatting + locals[parserResult.itemName] = modelValue; + candidateViewValue = parserResult.viewMapper(originalScope, locals); + locals[parserResult.itemName] = undefined; + emptyViewValue = parserResult.viewMapper(originalScope, locals); + + return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; + } + }); + + scope.select = function (activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); + + resetMatches(); + + //return focus to the input element if a mach was selected via a mouse click event + element[0].focus(); + }; + + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function (evt) { + + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } + + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function (evt) { + hasFocus = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function (evt) { + if (element[0] !== evt.target) { + resetMatches(); + scope.$digest(); + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function(){ + $document.unbind('click', dismissClickHandler); + }); + + var $popup = $compile(popUpEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + } + }; + +}]) + + .directive('typeaheadPopup', function () { + return { + restrict:'EA', + scope:{ + matches:'=', + query:'=', + active:'=', + position:'=', + select:'&' + }, + replace:true, + templateUrl:'template/typeahead/typeahead-popup.html', + link:function (scope, element, attrs) { + + scope.templateUrl = attrs.templateUrl; + + scope.isOpen = function () { + return scope.matches.length > 0; + }; + + scope.isActive = function (matchIdx) { + return scope.active == matchIdx; + }; + + scope.selectActive = function (matchIdx) { + scope.active = matchIdx; + }; + + scope.selectMatch = function (activeIdx) { + scope.select({activeIdx:activeIdx}); + }; + } + }; + }) + + .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { + return { + restrict:'EA', + scope:{ + index:'=', + match:'=', + query:'=' + }, + link:function (scope, element, attrs) { + var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; + $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ + element.replaceWith($compile(tplContent.trim())(scope)); + }); + } + }; + }]) + + .filter('typeaheadHighlight', function() { + + function escapeRegexp(queryToEscape) { + return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); + } + + return function(matchItem, query) { + return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; + }; + }); +angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/accordion/accordion-group.html", + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
"); +}]); + +angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/accordion/accordion.html", + "
"); +}]); + +angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/alert/alert.html", + "
\n" + + " \n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/carousel/carousel.html", + "
\n" + + "
    1\">\n" + + "
  1. \n" + + "
\n" + + "
\n" + + " 1\">‹\n" + + " 1\">›\n" + + "
\n" + + ""); +}]); + +angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/carousel/slide.html", + "
\n" + + ""); +}]); + +angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/datepicker.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 0\">\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
#{{label}}
{{ getWeekNumber(row) }}\n" + + " \n" + + "
\n" + + ""); +}]); + +angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/popup.html", + "
    \n" + + "
  • \n" + + "
  • \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
  • \n" + + "
\n" + + ""); +}]); + +angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/backdrop.html", + "
"); +}]); + +angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/window.html", + "
"); +}]); + +angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/pagination/pager.html", + "
\n" + + " \n" + + "
\n" + + ""); +}]); + +angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/pagination/pagination.html", + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", + "
\n" + + "
\n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-popup.html", + "
\n" + + "
\n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/popover/popover.html", + "
\n" + + "
\n" + + "\n" + + "
\n" + + "

\n" + + "
\n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/bar.html", + "
"); +}]); + +angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/progress.html", + "
"); +}]); + +angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/progressbar.html", + "
"); +}]); + +angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/rating/rating.html", + "\n" + + " \n" + + ""); +}]); + +angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tab.html", + "
  • \n" + + " {{heading}}\n" + + "
  • \n" + + ""); +}]); + +angular.module("template/tabs/tabset-titles.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tabset-titles.html", + "
      \n" + + "
    \n" + + ""); +}]); + +angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tabset.html", + "\n" + + "
    \n" + + "
      \n" + + "
    \n" + + "
    \n" + + "
    \n" + + "
    \n" + + "
    \n" + + "
    \n" + + ""); +}]); + +angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/timepicker/timepicker.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
     
    :
     
    \n" + + ""); +}]); + +angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/typeahead/typeahead-match.html", + ""); +}]); + +angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/typeahead/typeahead-popup.html", + "
      \n" + + "
    • \n" + + "
      \n" + + "
    • \n" + + "
    "); +}]); \ No newline at end of file diff --git a/js/homepage.js b/js/homepage.js index 757bf33d4..e093eec0a 100644 --- a/js/homepage.js +++ b/js/homepage.js @@ -1,4 +1,4 @@ -angular.module('homepage', ['ngAnimate']) +angular.module('homepage', ['ngAnimate', 'ui.bootstrap']) .config(function($provide, $locationProvider) { var pulseElements = $(), @@ -76,7 +76,7 @@ angular.module('homepage', ['ngAnimate']) }) .value('escape', function(text) { - return text.replace(/\&/g, '&').replace(/\/g, '>').replace(/"/g, '"'); + return text.replace(/\&/g, '&').replace(//g, '>').replace(/"/g, '"'); }) .factory('script', function() { @@ -92,7 +92,7 @@ angular.module('homepage', ['ngAnimate']) .factory('fetchCode', function(indent) { return function get(id, spaces) { return indent(angular.element(document.getElementById(id)).html(), spaces); - } + }; }) .directive('code', function() { @@ -136,9 +136,10 @@ angular.module('homepage', ['ngAnimate']) }; }) - .directive('appSource', function(fetchCode, escape, script) { + .directive('appSource', function(fetchCode, escape, script, $compile) { return { terminal: true, + scope: true, link: function(scope, element, attrs) { var tabs = [], panes = [], @@ -206,10 +207,14 @@ angular.module('homepage', ['ngAnimate']) var regexp = new RegExp('(\\W|^)(' + key.replace(/([\W\-])/g, '\\$1') + ')(\\W|$)'); content = content.replace(regexp, function(_, before, token, after) { - var token = "__" + (counter++) + "__"; + token = "__" + (counter++) + "__"; popovers[token] = - '' + escape(key) + ''; + '' + escape(key) + + ''; return before + token + after; }); }); @@ -235,11 +240,14 @@ angular.module('homepage', ['ngAnimate']) '
    '); // element.find('[rel=popover]').popover().pulse(); - function id(id) { - return id.replace(/\W/g, '-'); + // Compile up the HTML to get the directives to kick-in + $compile(element.children())(scope); + + function id(i) { + return i.replace(/\W/g, '-'); } } - } + }; }) .directive('jsFiddle', function(fetchCode, escape, script) { @@ -304,9 +312,9 @@ angular.module('homepage', ['ngAnimate']) .directive('hint', function() { return { template: 'Hint: hover over ' + - 'me.' - } + 'me.' + }; }) .filter('byCategory', function() { @@ -319,148 +327,205 @@ angular.module('homepage', ['ngAnimate']) } }) - .controller('JumbotronCtrl', ['$scope', '$http', 'filterFilter', 'byCategoryFilter', + .controller('JumbotronCtrl', ['$scope', '$http', 'filterFilter', 'byCategoryFilter', function($scope, $http, filterFilter, byCategoryFilter) { - var defaultCategory = 'basics'; - $scope.category = defaultCategory; + var defaultCategory = 'basics'; + $scope.category = defaultCategory; - var allVideos; - $scope.loading = true; - $http.get('./featured-videos.json').success(function(results) { - $scope.loading = false; - allVideos = results; - $scope.filterByCategory($scope.category); + var allVideos; + $scope.loading = true; + $http.get('./featured-videos.json').success(function(results) { + $scope.loading = false; + allVideos = results; + $scope.filterByCategory($scope.category); + }); + + $scope.filterBySearch = function(q) { + $scope.search = q; + $scope.category = null; + $scope.videos = filterFilter(allVideos, q); + }; + + $scope.filterByCategory = function(category) { + $scope.search = null; + $scope.category = category; + $scope.videos = byCategoryFilter(allVideos, category); + }; + }]) + + .value('BRANCHES', [ + { branch: '1.2.*', version: '1.2.14', title: '1.2.x (legacy)', cssClass: 'bluePill' }, + { branch: '1.3.*', version: '1.3.0-beta.1', title: '1.3.x (latest)', cssClass: 'redPill' } + ]) + + .value('BUILDS', [ + { name: 'Minified' }, + { name: 'Uncompressed' }, + { name: 'Zip' } + ]) + + + .controller('AppCtrl', function($scope, $modal, BRANCHES) { + $scope.BRANCHES = BRANCHES; + + $scope.showDownloadModal = function() { + $modal.open({ + templateUrl: 'partials/download-modal.html', + windowClass: 'download-modal' }); + }; - $scope.filterBySearch = function(q) { - $scope.search = q; - $scope.category = null; - $scope.videos = filterFilter(allVideos, q); - }; - - $scope.filterByCategory = function(category) { - $scope.search = null; - $scope.category = category; - $scope.videos = byCategoryFilter(allVideos, category); - }; - }]) - - .controller('DownloadCtrl', function($scope, $location) { - $scope.CURRENT_STABLE_VERSION = '1.2.14'; - $scope.CURRENT_UNSTABLE_VERSION = '1.3.0-beta.1'; - var BASE_CODE_ANGULAR_URL = 'http://code.angularjs.org/'; - var BASE_CDN_URL = 'https://ajax.googleapis.com/ajax/libs/angularjs/'; - var getRelativeUrl = function(branch, build) { - var version = $scope.getVersion(branch); - if (build === 'minified') { - return version + '/angular.min.js'; - } else if (build === 'uncompressed') { - return version + '/angular.js'; - } else { - return version + '/angular-' + version + '.zip'; + $scope.showVideo = function(videoUrl) { + $modal.open({ + templateUrl: 'partials/video-modal.html', + windowClass: 'video-modal', + controller: 'VideoController', + resolve: { + videoUrl: function() { return videoUrl; } } - }; + }); + }; - var currentBranch = false; + }) - $scope.currentBranch = 'stable'; - $scope.currentBuild = 'minified'; + .controller('DownloadCtrl', function($scope, BRANCHES, BUILDS) { - $scope.selectType = function(type) { - if (type === false) { - return; - } - $scope.currentBranch = type || 'stable'; - $scope.updateCdnLink(); - }; - - $scope.getVersion = function(branch) { - return branch === 'stable' ? $scope.CURRENT_STABLE_VERSION : $scope.CURRENT_UNSTABLE_VERSION; - }; - - $scope.selectBuild = function(build) { - $scope.currentBuild = build; - $scope.updateCdnLink(); - }; - angular.forEach(['#extraInfoBranch', '#extraInfoBuild', '#extraInfoCDN'], function(id) { - $(id).popover({ - placement: 'left', - trigger: 'hover', - delay: {hide: '300'} - }); - }); - $scope.getPillClass = function(pill, actual) { - return pill === actual ? 'active' : ''; - }; - - window.onkeydown = function (ev) { - if (ev.keyCode === 27 && currentBranch) { - $scope.lightbox(false); - $scope.$apply(); - } - } + $scope.BRANCHES = BRANCHES; + $scope.BUILDS = BUILDS; - $scope.lightbox = function(arg) { - if (typeof arg !== 'undefined') { - currentBranch = arg; - $scope.selectType(currentBranch); - } - return currentBranch; - }; - - $scope.downloadLink = function() { - if ($scope.cdnURL && $scope.cdnURL.indexOf('http://') == 0) { - return $scope.cdnURL; - } else { - return BASE_CODE_ANGULAR_URL + getRelativeUrl($scope.currentBranch, $scope.currentBuild); - } - }; - $scope.updateCdnLink = function() { - if ($scope.currentBuild === 'zipped') { - $scope.cdnURL = 'Unavailable for zip archives'; - } else { - $scope.cdnURL = BASE_CDN_URL + getRelativeUrl($scope.currentBranch, $scope.currentBuild); - } - }; - }) + $scope.currentBranch = $scope.BRANCHES[0]; + $scope.currentBuild = $scope.BUILDS[0]; - .run(function($rootScope, startPulse){ - $rootScope.version = angular.version; - $rootScope.$evalAsync(function(){ - var videoURL; + $scope.setBranch = function(branch) { + $scope.currentBranch = branch; + }; - $('.video-img'). - bind('click', function() { - videoURL = $(this).data('video'); - }); + $scope.setBuild = function(build) { + $scope.currentBuild = build; + }; - $('#videoModal'). - modal({show:false}). - on('shown', function(event, a, b, c) { - var iframe = $(this).find('.modal-body').append(' \ No newline at end of file From 6515baeec93425c5567742ceb1c937882172ca68 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 13 Mar 2014 13:07:08 +0000 Subject: [PATCH 050/255] fix(examples-tabs): Implement angular-ui tabs --- js/homepage.js | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/js/homepage.js b/js/homepage.js index e093eec0a..e3cb7597a 100644 --- a/js/homepage.js +++ b/js/homepage.js @@ -142,7 +142,6 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap']) scope: true, link: function(scope, element, attrs) { var tabs = [], - panes = [], annotation = attrs.annotate && angular.fromJson(fetchCode(attrs.annotate)) || {}, TEMPLATE = { 'index.html': @@ -166,19 +165,14 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap']) angular.forEach(attrs.appSource.split(' '), function(filename, index) { var content; - tabs.push( - '
  • ' + - '' + (index ? filename : 'index.html') + '' + - '
  • '); - - if (index == 0) { + if (index === 0) { var head = []; angular.forEach(attrs.appSource.split(' '), function(tab, index) { var filename = tab.split(':')[0], fileType = filename.split(/\./)[1]; - if (index == 0) return; + if (index === 0) return; if (fileType == 'js') { head.push(' \n'); } else if (fileType == 'css') { @@ -223,21 +217,17 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap']) content = content.replace(token, text); }); - panes.push( - '
    ' + - '
    ' + content +'
    ' + - '
    '); + tabs.push( + '\n' + + '
    ' + content +'
    \n' + + '
    \n' + ); }); element.html( - '
    ' + - '' + - '
    ' + - panes.join('') + - '
    ' + - '
    '); + ''); // element.find('[rel=popover]').popover().pulse(); // Compile up the HTML to get the directives to kick-in From 357a30c22b6e8436407eac5e09f1c9bbe06f824a Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 14 Mar 2014 10:16:09 +0000 Subject: [PATCH 051/255] refact(download-data): move download-data into its own file Putting the download data into its own file, makes it easier to automatically update the data when we do a release. --- index.html | 1 + js/download-data.js | 54 ++++++++++++++++++++++++ js/homepage.js | 79 ++++++++---------------------------- partials/download-modal.html | 3 +- 4 files changed, 75 insertions(+), 62 deletions(-) create mode 100644 js/download-data.js diff --git a/index.html b/index.html index 660702995..cf330e978 100644 --- a/index.html +++ b/index.html @@ -855,6 +855,7 @@

    JavaScript Projects

    + diff --git a/js/download-data.js b/js/download-data.js new file mode 100644 index 000000000..ae4cf37dc --- /dev/null +++ b/js/download-data.js @@ -0,0 +1,54 @@ +angular.module('download-data', []) + +.value('BRANCHES', [ + { + branch: '1.2.*', version: '1.2.14', + title: '1.2.x (legacy)', + cssClass: 'bluePill' + }, + { + branch: '1.3.*', version: '1.3.0-beta.1', + title: '1.3.x (latest)', + cssClass: 'redPill' + } +]) + +.value('BUILDS', [ + { name: 'Minified' }, + { name: 'Uncompressed' }, + { name: 'Zip' } +]) + +.value('DOWNLOAD_INFO', { + branchesInfo: + "
    "+ + "
    Legacy 1.2.x
    "+ + "
    The Release has been well tested, and the API for this version will not undergo any further change.
    "+ + "
    Latest 1.3.x
    "+ + "
    This version is still being worked on, and API's are subject to change without any prior notice. Use only if you want to remain on the most cutting edge...
    "+ + "
    ", + + buildsInfo: + "
    "+ + "
    Minified
    "+ + "
    Minified and obfuscated version of the AngularJS base code. Use this in your deployed application (but only if you can't use Google's CDN)
    "+ + "
    Uncompressed
    "+ + "
    The main AngularJS source code, as is. Useful for debugging and development purpose, but should ideally not be used in your deployed application
    "+ + "
    Zipped
    "+ + "
    The zipped version of the Angular Build, which contains both the builds of AngularJS, as well as documentation and other extras
    "+ + "
    ", + + cdnInfo: + "While downloading and using the AngularJS source code is great for development, "+ + "we recommend that you source the script from Google's CDN (Content Delivery Network) in your deployed, customer facing app whenever possible. "+ + "You get the following advantages for doing so:"+ + "
      "+ + "
    • Better Caching : If you host AngularJS yourself, your users will have to download the source code atleast once. But if the browser sees that you are referring to Google CDN's version of AngularJS, and your user has visited another app which uses AngularJS, then he can avail the benefits of caching, and thus reduce one download, speeding up his overall experience!
    • "+ + "
    • Decreased Latency : Google's CDN distributes your static content across the globe, in various diverse, physical locations. It increases the odds that the user gets a version of AngularJS served from a location near him, thus reducing overall latency.
    • "+ + "
    • Increased Parallelism : Using Google's CDN reduces one request to your domain. Depending on the browser, the number of parallel requests it can make to a domain is restricted (as low as 2 in IE 7). So it can make a gigantic difference in loading times for users of those browsers.
    • "+ + "
    ", + + bowerInfo: + "Bower is a package manager for client-side JavaScript components.

    "+ + "For more info please see: https://github.com/bower/bower" +}); diff --git a/js/homepage.js b/js/homepage.js index e3cb7597a..adeb08680 100644 --- a/js/homepage.js +++ b/js/homepage.js @@ -1,4 +1,4 @@ -angular.module('homepage', ['ngAnimate', 'ui.bootstrap']) +angular.module('homepage', ['ngAnimate', 'ui.bootstrap', 'download-data']) .config(function($provide, $locationProvider) { var pulseElements = $(), @@ -344,16 +344,6 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap']) }; }]) - .value('BRANCHES', [ - { branch: '1.2.*', version: '1.2.14', title: '1.2.x (legacy)', cssClass: 'bluePill' }, - { branch: '1.3.*', version: '1.3.0-beta.1', title: '1.3.x (latest)', cssClass: 'redPill' } - ]) - - .value('BUILDS', [ - { name: 'Minified' }, - { name: 'Uncompressed' }, - { name: 'Zip' } - ]) .controller('AppCtrl', function($scope, $modal, BRANCHES) { @@ -379,10 +369,22 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap']) }) - .controller('DownloadCtrl', function($scope, BRANCHES, BUILDS) { + .controller('DownloadCtrl', function($scope, BRANCHES, BUILDS, DOWNLOAD_INFO) { + + function getRelativeUrl(branch, build) { + switch (build.name) { + case 'Minified': + return branch.version + '/angular.min.js'; + case 'Uncompressed': + return branch.version + '/angular.js'; + case 'Zip': + return branch.version + '/angular-' + branch.version + '.zip'; + } + } $scope.BRANCHES = BRANCHES; $scope.BUILDS = BUILDS; + $scope.DOWNLOAD_INFO = DOWNLOAD_INFO; $scope.currentBranch = $scope.BRANCHES[0]; $scope.currentBuild = $scope.BUILDS[0]; @@ -395,48 +397,10 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap']) $scope.currentBuild = build; }; - $scope.branchesInfo = - "
    "+ - "
    Legacy 1.2.x
    "+ - "
    The Release has been well tested, and the API for this version will not undergo any further change.
    "+ - "
    Latest 1.3.x
    "+ - "
    This version is still being worked on, and API's are subject to change without any prior notice. Use only if you want to remain on the most cutting edge...
    "+ - "
    "; - - $scope.buildsInfo = - "
    "+ - "
    Minified
    "+ - "
    Minified and obfuscated version of the AngularJS base code. Use this in your deployed application (but only if you can't use Google's CDN)
    "+ - "
    Uncompressed
    "+ - "
    The main AngularJS source code, as is. Useful for debugging and development purpose, but should ideally not be used in your deployed application
    "+ - "
    Zipped
    "+ - "
    The zipped version of the Angular Build, which contains both the builds of AngularJS, as well as documentation and other extras
    "+ - "
    "; - - $scope.cdnInfo = - "While downloading and using the AngularJS source code is great for development, "+ - "we recommend that you source the script from Google's CDN (Content Delivery Network) in your deployed, customer facing app whenever possible. "+ - "You get the following advantages for doing so:"+ - "
      "+ - "
    • Better Caching : If you host AngularJS yourself, your users will have to download the source code atleast once. But if the browser sees that you are referring to Google CDN's version of AngularJS, and your user has visited another app which uses AngularJS, then he can avail the benefits of caching, and thus reduce one download, speeding up his overall experience!
    • "+ - "
    • Decreased Latency : Google's CDN distributes your static content across the globe, in various diverse, physical locations. It increases the odds that the user gets a version of AngularJS served from a location near him, thus reducing overall latency.
    • "+ - "
    • Increased Parallelism : Using Google's CDN reduces one request to your domain. Depending on the browser, the number of parallel requests it can make to a domain is restricted (as low as 2 in IE 7). So it can make a gigantic difference in loading times for users of those browsers.
    • "+ - "
    "; - - $scope.bowerInfo = - "Bower is a package manager for client-side JavaScript components.

    "+ - "For more info please see: https://github.com/bower/bower"; - - - - var getRelativeUrl = function(branch, build) { - switch (build.name) { - case 'Minified': - return branch.version + '/angular.min.js'; - case 'Uncompressed': - return branch.version + '/angular.js'; - case 'Zip': - return branch.version + '/angular-' + branch.version + '.zip'; + var BASE_CDN_URL = 'https://ajax.googleapis.com/ajax/libs/angularjs/'; + $scope.cdnUrl = function() { + if ($scope.currentBuild.name !== 'Zip') { + return BASE_CDN_URL + getRelativeUrl($scope.currentBranch, $scope.currentBuild); } }; @@ -447,13 +411,6 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap']) }; - var BASE_CDN_URL = 'https://ajax.googleapis.com/ajax/libs/angularjs/'; - $scope.cdnUrl = function() { - if ($scope.currentBuild.name !== 'Zip') { - return BASE_CDN_URL + getRelativeUrl($scope.currentBranch, $scope.currentBuild); - } - }; - }) .controller('VideoController', function($scope, $timeout, $sce, videoUrl) { diff --git a/partials/download-modal.html b/partials/download-modal.html index 8c47210bd..abaa23657 100644 --- a/partials/download-modal.html +++ b/partials/download-modal.html @@ -32,7 +32,8 @@

    Download AngularJS

    From 25539137d56ba0f04e53717c64753f09a1644b35 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 14 Mar 2014 14:28:56 +0000 Subject: [PATCH 052/255] fix(index): cloak the downloadable versions till they arrive --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index cf330e978..4bb1efd81 100644 --- a/index.html +++ b/index.html @@ -120,7 +120,7 @@

    HTML enhanced for web apps!

    Download - + ({{branch.version}}{{ !$last ? ' / ' : '' }}) From b294a73a2f594d186c480e178a27538f1c623e7d Mon Sep 17 00:00:00 2001 From: jenkins Date: Fri, 14 Mar 2014 17:40:27 -0700 Subject: [PATCH 053/255] update(version): update angular version to 1.3.0-beta.2 --- index.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 4bb1efd81..e10fe530f 100644 --- a/index.html +++ b/index.html @@ -20,8 +20,8 @@ - - + +  - - - - - - - - AngularJS — Complimentary Libraries, Tools, and Techniques - - - - - - - - -  - - - - - - -
    - -
    - -
    - -

    Complimentary Libraries, Tools, and Techniques

    - -
    -

    - Below are several tools, libraries, tutorials, guides, etc. that we've found useful in developing AngularJS - applications. We can't guarantee the usefulness of any of these for your particular project, but we've - worked with each of them to varying degrees and they all look worth investigating. -

    -

    - Do you have something we've missed and should list here? Please tell us via Google+ or Twitter (+angularjs and - @angularjs, respectively). -

    -
    - -

    Development

    -
    -
    -

    Testacular

    -

    - Testacular runs your unit tests faster than any other tool we know. The 1K+ AngularJS tests run in ~3 seconds. - It runs tests every time you save. It runs tests on real browsers including Android and iOS. It integrates - with WebStorm's debugger. It works with any test framework, but comes with specific support for Jasmine and - Mocha, and AngularJS' scenario runner. It will change your life. No, we can't believe the name either. - Testacular on GitHub -

    -
    -
    -

    Batarang

    -

    - Want to see which elements have data bindings, the inheritance tree for your scopes, which $watch expressions - need optimization and a dependency graph for your app, all in the Chrome development tools pane? Well then, - the AngularJS debugger extension could be for you. You can learn more about it in this blog post and install - it from the - Chrome Web Store. -

    -
    -
    -

    Yeoman

    -

    - Yeoman is the swiss-army knife of command-line tools you'll need to build a production-ready web application. - The list of features is extensive and includes scaffolding, compiling (CoffeeScript, Compass), linting, - package management, and the list goes on. We've worked with the Yeoman team to create Angular-specific - support. Check out our blog post and - video for using Yeoman with Angular and the main - project page at yeoman.io. -

    -
    -
    -
    -
    -

    WebStorm

    -

    - WebStorm offers an Angular syntax-aware plugin for their nifty editor. Get it at their plugin download site - or directly within WebStorm's preferences. There is also a separate set of LiveTemplates for Angular on - this GitHub repo. -

    -
    -
    -

    Sublime Text

    -

    - Use CoffeeScript and the Sublime Text editor? Then check out this extensive set of key-completion snippets as - a Sublime keymap. -

    -
    -
    - - - - -

    Deployment

    -
    -
    -

    PhoneGap

    -

    - Want to build installable mobile apps for Android, iOS and others? Okay, so there's no specific support for - Angular in PhoneGap, but they work just great together. More at phonegap.com -

    -

    - Oh, but if you do want slick animations between views on mobile, you might check out the - Angular Mobile Nav library. -

    -

    -
    -
    -

    Chrome Packaged Apps

    -

    - Packaged apps in Chrome let you create desktop-like applications that get delivered like web pages. They - can have native-like features like accessing the file system, hardware devices, etc. In short, the sky's - the limit in what they can do. There's a - guide to getting started building packaged apps with Angular and an - Angular code lab to expand on these basic - concepts. -

    -
    -
    -

    UglifyJS

    -

    - AngularJS offers minified versions of all of its libraries, but what about your code? If you want your code - to load quickly, this may be the tool for you! UglifyJS -

    -
    -
    - - - - -

    Complimentary Libraries

    -
    -
    -

    Angular UI

    -

    - Though not affiliated with the AngularJS project, Angular UI is the largest known collection of open source - Angular UI components. A great source of examples and many useful techniques. - Angular UI -

    -

    -
    -
    -

    Bootstrap

    -

    - Okay, another one with no specific linkage to Angular, but we use Bootstrap extensively on this site as do - many other developers in getting a leg up on page layout and starting with sensible defaults for all critical - CSS bits. Bootstrap -

    -
    -
    -

    BreezeJS

    -

    - For folks who want client-side data management, BreezeJS provides and extensive set of client-side features - like filtering, ordering, and paging, and much more to let you mirror your server-side model manipulation on - in the web client. They've put together some good examples on how to get started combining with Angular on - the BreezeJS site. -

    -
    -
    -

    jQuery Mobile

    -

    - This project provides a bridge between Angular and the jQuery Mobile allowing you to use all the widgets - and many utilities from the jQuery Mobile library. - jQuery Mobile Adapter - -

    -
    -
    -

    Wijmo

    -

    - Wijmo is a complete kit of over 40 UI widgets with everything from interactive menus to rich charts. Built - with HTML5, jQuery, CSS3, and SVG, Wijmo widgets make your applications suitable for Today's Web. Wijmo also - has an AngularJS Integration Library that exposes all of its widgets as AngularJS components. - - wijmo.com -

    -
    -
    - - - - -
    - -
    - - - - - - - - diff --git a/.htaccess b/src/.htaccess similarity index 100% rename from .htaccess rename to src/.htaccess diff --git a/css/bootstrap-responsive.css b/src/css/bootstrap-responsive.css similarity index 100% rename from css/bootstrap-responsive.css rename to src/css/bootstrap-responsive.css diff --git a/css/bootstrap-responsive.min.css b/src/css/bootstrap-responsive.min.css similarity index 100% rename from css/bootstrap-responsive.min.css rename to src/css/bootstrap-responsive.min.css diff --git a/css/bootstrap.css b/src/css/bootstrap.css similarity index 100% rename from css/bootstrap.css rename to src/css/bootstrap.css diff --git a/css/bootstrap.min.css b/src/css/bootstrap.min.css similarity index 100% rename from css/bootstrap.min.css rename to src/css/bootstrap.min.css diff --git a/css/docs.css b/src/css/docs.css similarity index 100% rename from css/docs.css rename to src/css/docs.css diff --git a/css/font-awesome.css b/src/css/font-awesome.css similarity index 100% rename from css/font-awesome.css rename to src/css/font-awesome.css diff --git a/favicon.ico b/src/favicon.ico similarity index 100% rename from favicon.ico rename to src/favicon.ico diff --git a/featured-videos.json b/src/featured-videos.json similarity index 100% rename from featured-videos.json rename to src/featured-videos.json diff --git a/font/fontawesome-webfont.eot b/src/font/fontawesome-webfont.eot similarity index 100% rename from font/fontawesome-webfont.eot rename to src/font/fontawesome-webfont.eot diff --git a/font/fontawesome-webfont.svg b/src/font/fontawesome-webfont.svg similarity index 100% rename from font/fontawesome-webfont.svg rename to src/font/fontawesome-webfont.svg diff --git a/font/fontawesome-webfont.svgz b/src/font/fontawesome-webfont.svgz similarity index 100% rename from font/fontawesome-webfont.svgz rename to src/font/fontawesome-webfont.svgz diff --git a/font/fontawesome-webfont.ttf b/src/font/fontawesome-webfont.ttf similarity index 100% rename from font/fontawesome-webfont.ttf rename to src/font/fontawesome-webfont.ttf diff --git a/font/fontawesome-webfont.woff b/src/font/fontawesome-webfont.woff similarity index 100% rename from font/fontawesome-webfont.woff rename to src/font/fontawesome-webfont.woff diff --git a/generatePassword.php b/src/generatePassword.php similarity index 100% rename from generatePassword.php rename to src/generatePassword.php diff --git a/gitFetchSite.php b/src/gitFetchSite.php similarity index 100% rename from gitFetchSite.php rename to src/gitFetchSite.php diff --git a/google-code-prettify/lang-apollo.js b/src/google-code-prettify/lang-apollo.js similarity index 100% rename from google-code-prettify/lang-apollo.js rename to src/google-code-prettify/lang-apollo.js diff --git a/google-code-prettify/lang-clj.js b/src/google-code-prettify/lang-clj.js similarity index 100% rename from google-code-prettify/lang-clj.js rename to src/google-code-prettify/lang-clj.js diff --git a/google-code-prettify/lang-css.js b/src/google-code-prettify/lang-css.js similarity index 100% rename from google-code-prettify/lang-css.js rename to src/google-code-prettify/lang-css.js diff --git a/google-code-prettify/lang-go.js b/src/google-code-prettify/lang-go.js similarity index 100% rename from google-code-prettify/lang-go.js rename to src/google-code-prettify/lang-go.js diff --git a/google-code-prettify/lang-hs.js b/src/google-code-prettify/lang-hs.js similarity index 100% rename from google-code-prettify/lang-hs.js rename to src/google-code-prettify/lang-hs.js diff --git a/google-code-prettify/lang-lisp.js b/src/google-code-prettify/lang-lisp.js similarity index 100% rename from google-code-prettify/lang-lisp.js rename to src/google-code-prettify/lang-lisp.js diff --git a/google-code-prettify/lang-lua.js b/src/google-code-prettify/lang-lua.js similarity index 100% rename from google-code-prettify/lang-lua.js rename to src/google-code-prettify/lang-lua.js diff --git a/google-code-prettify/lang-ml.js b/src/google-code-prettify/lang-ml.js similarity index 100% rename from google-code-prettify/lang-ml.js rename to src/google-code-prettify/lang-ml.js diff --git a/google-code-prettify/lang-n.js b/src/google-code-prettify/lang-n.js similarity index 100% rename from google-code-prettify/lang-n.js rename to src/google-code-prettify/lang-n.js diff --git a/google-code-prettify/lang-proto.js b/src/google-code-prettify/lang-proto.js similarity index 100% rename from google-code-prettify/lang-proto.js rename to src/google-code-prettify/lang-proto.js diff --git a/google-code-prettify/lang-scala.js b/src/google-code-prettify/lang-scala.js similarity index 100% rename from google-code-prettify/lang-scala.js rename to src/google-code-prettify/lang-scala.js diff --git a/google-code-prettify/lang-sql.js b/src/google-code-prettify/lang-sql.js similarity index 100% rename from google-code-prettify/lang-sql.js rename to src/google-code-prettify/lang-sql.js diff --git a/google-code-prettify/lang-tex.js b/src/google-code-prettify/lang-tex.js similarity index 100% rename from google-code-prettify/lang-tex.js rename to src/google-code-prettify/lang-tex.js diff --git a/google-code-prettify/lang-vb.js b/src/google-code-prettify/lang-vb.js similarity index 100% rename from google-code-prettify/lang-vb.js rename to src/google-code-prettify/lang-vb.js diff --git a/google-code-prettify/lang-vhdl.js b/src/google-code-prettify/lang-vhdl.js similarity index 100% rename from google-code-prettify/lang-vhdl.js rename to src/google-code-prettify/lang-vhdl.js diff --git a/google-code-prettify/lang-wiki.js b/src/google-code-prettify/lang-wiki.js similarity index 100% rename from google-code-prettify/lang-wiki.js rename to src/google-code-prettify/lang-wiki.js diff --git a/google-code-prettify/lang-xq.js b/src/google-code-prettify/lang-xq.js similarity index 100% rename from google-code-prettify/lang-xq.js rename to src/google-code-prettify/lang-xq.js diff --git a/google-code-prettify/lang-yaml.js b/src/google-code-prettify/lang-yaml.js similarity index 100% rename from google-code-prettify/lang-yaml.js rename to src/google-code-prettify/lang-yaml.js diff --git a/google-code-prettify/prettify.css b/src/google-code-prettify/prettify.css similarity index 100% rename from google-code-prettify/prettify.css rename to src/google-code-prettify/prettify.css diff --git a/google-code-prettify/prettify.js b/src/google-code-prettify/prettify.js similarity index 100% rename from google-code-prettify/prettify.js rename to src/google-code-prettify/prettify.js diff --git a/google-code-prettify/prettify.min.js b/src/google-code-prettify/prettify.min.js similarity index 100% rename from google-code-prettify/prettify.min.js rename to src/google-code-prettify/prettify.min.js diff --git a/greet.php b/src/greet.php similarity index 100% rename from greet.php rename to src/greet.php diff --git a/img/AngularJS-large.png b/src/img/AngularJS-large.png similarity index 100% rename from img/AngularJS-large.png rename to src/img/AngularJS-large.png diff --git a/img/AngularJS-small.png b/src/img/AngularJS-small.png similarity index 100% rename from img/AngularJS-small.png rename to src/img/AngularJS-small.png diff --git a/img/glyphicons-halflings-white.png b/src/img/glyphicons-halflings-white.png similarity index 100% rename from img/glyphicons-halflings-white.png rename to src/img/glyphicons-halflings-white.png diff --git a/img/glyphicons-halflings.png b/src/img/glyphicons-halflings.png similarity index 100% rename from img/glyphicons-halflings.png rename to src/img/glyphicons-halflings.png diff --git a/img/google-black.png b/src/img/google-black.png similarity index 100% rename from img/google-black.png rename to src/img/google-black.png diff --git a/img/google.png b/src/img/google.png similarity index 100% rename from img/google.png rename to src/img/google.png diff --git a/img/video-over.png b/src/img/video-over.png similarity index 100% rename from img/video-over.png rename to src/img/video-over.png diff --git a/img/video.png b/src/img/video.png similarity index 100% rename from img/video.png rename to src/img/video.png diff --git a/index.html b/src/index.html similarity index 100% rename from index.html rename to src/index.html diff --git a/js/angular-ui-bootstrap.js b/src/js/angular-ui-bootstrap.js similarity index 100% rename from js/angular-ui-bootstrap.js rename to src/js/angular-ui-bootstrap.js diff --git a/js/bootstrap.js b/src/js/bootstrap.js similarity index 100% rename from js/bootstrap.js rename to src/js/bootstrap.js diff --git a/js/bootstrap.min.js b/src/js/bootstrap.min.js similarity index 100% rename from js/bootstrap.min.js rename to src/js/bootstrap.min.js diff --git a/js/download-data.js b/src/js/download-data.js similarity index 100% rename from js/download-data.js rename to src/js/download-data.js diff --git a/js/homepage.js b/src/js/homepage.js similarity index 100% rename from js/homepage.js rename to src/js/homepage.js diff --git a/misc/package.json b/src/misc/package.json similarity index 100% rename from misc/package.json rename to src/misc/package.json diff --git a/misc/resetProjects.js b/src/misc/resetProjects.js similarity index 100% rename from misc/resetProjects.js rename to src/misc/resetProjects.js diff --git a/partials/download-modal.html b/src/partials/download-modal.html similarity index 100% rename from partials/download-modal.html rename to src/partials/download-modal.html diff --git a/partials/video-modal.html b/src/partials/video-modal.html similarity index 100% rename from partials/video-modal.html rename to src/partials/video-modal.html diff --git a/propagateClusterUpdate.js b/src/propagateClusterUpdate.js similarity index 100% rename from propagateClusterUpdate.js rename to src/propagateClusterUpdate.js From 0ea487b0c253989ea650f865cbb86badd7137ba0 Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Tue, 25 Mar 2014 15:20:27 -0700 Subject: [PATCH 070/255] add script id to index for testing --- src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index b94bedb21..aef49d5a9 100644 --- a/src/index.html +++ b/src/index.html @@ -20,7 +20,7 @@ - +  - + October 22nd-23rd, 2014

    - Join us in Paris, France in October for the first official - AngularJS European conference. + The first official European AngularJS conference in Paris, France was awesome.

    +

    If you missed it the talks are available on YouTube.

    From 6e850152c68bed3be788fa16e62afde1f8f9c967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Thu, 30 Oct 2014 19:10:25 -0400 Subject: [PATCH 106/255] update(index): remove the ng-europe banner --- src/index.html | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/index.html b/src/index.html index 1de3c53cb..917c9f6a8 100644 --- a/src/index.html +++ b/src/index.html @@ -152,24 +152,6 @@

    HTML enhanced for web apps!

    -
    - -
    -

    - October 22nd-23rd, 2014 -

    -

    - The first official European AngularJS conference in Paris, France was awesome. -

    -

    If you missed it the talks are available on YouTube.

    - -
    -
    -

    Why AngularJS?

    From 304f7b4c56d4e3c424e56203367bf5ffe0affbe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 4 Nov 2014 00:02:15 -0500 Subject: [PATCH 107/255] update(index): add videos from ng-europe and improve the video panel --- src/css/docs.css | 209 ++++++++++++++++++++++----------- src/featured-videos.json | 229 ++++++++++++++----------------------- src/img/ng-conf-logo.png | Bin 0 -> 16137 bytes src/img/ng-europe-logo.png | Bin 0 -> 18774 bytes src/img/videos-bg.png | Bin 0 -> 33702 bytes src/index.html | 61 +++++----- src/js/homepage.js | 41 +++---- 7 files changed, 275 insertions(+), 265 deletions(-) create mode 100644 src/img/ng-conf-logo.png create mode 100755 src/img/ng-europe-logo.png create mode 100644 src/img/videos-bg.png diff --git a/src/css/docs.css b/src/css/docs.css index d87a4ee57..0378f3910 100644 --- a/src/css/docs.css +++ b/src/css/docs.css @@ -299,7 +299,7 @@ div.modal-body button { .jumbotron { box-shadow:inset 0 0 100px #000; padding:0; - background:#444; + background:#444 url(../img/videos-bg.png); overflow:auto; text-align:center; } @@ -314,18 +314,98 @@ div.modal-body button { margin-top:0; } +.jumbotron-sections a { + display:inline-block; + padding:10px 20px; + overflow:auto; + color:white; +} + +.jumbotron-sections a.active { + background:black; + border-radius:10px; +} + +.jumbotron-sections h2 { + padding:0 20px; + overflow:auto; + display:inline-block; + vertical-align:middle; +} + +.jumbotron-logo { + height:70px; + vertical-align:middle; +} + .gallery-item { + box-sizing:border-box; display:inline-block; vertical-align:top; - width:280px; + width:340px; +} + +.gallery-item img { + box-sizing:border-box; +} + +.video-item-heading { + box-sizing:border-box; + font-weight:bold; + line-height:25px; + padding:20px 10px 0; + font-size:20px; + font-family:helvetica; } .video-item { padding:10px 5px; + margin:20px 20px 30px; + text-decoration:none!important; +} + +.video-item-image { + border-radius:10px; + position:relative; + height:170px; + overflow:hidden; + border:5px solid rgba(0,0,0,0.4); +} + +.video-item:hover .video-item-image { + transition:0.2s linear all; + -webkit-transition:0.2s linear all; +} + +.ng-europe .video-item:hover .video-item-image { + border-color:#006FCC; +} + +.ng-europe .jumbotron-sections a.active, +.ng-europe .video-item:hover .video-item-image:after { + color:#006FCC; +} + +.ng-conf .video-item:hover .video-item-image { + border-color:#D32C25; +} + +.ng-conf .jumbotron-sections a.active, +.ng-conf .video-item:hover .video-item-image:after { + color:#D32C25; +} + +.ng-conf .video-item:hover .video-item-image:after { + color:#D32C25; } -.video-item img { - height:200px; +.video-item-image img { + margin-top:-40px; + max-height:250px; +} + +.video-item-image { + position:relative; } .video-item:hover img { @@ -333,6 +413,30 @@ div.modal-body button { cursor:pointer; } +.video-item:hover .video-item-image:before { + position:absolute; + top:0; + left:0; + bottom:0; + right:0; + background:rgba(0,0,0,0.4); + content:""; +} + +.video-item:hover .video-item-image:after { + position:absolute; + top:50%; + left:50%; + width:100px; + height:100px; + margin-top:-50px; + margin-left:-50px; + font-family: "FontAwesome"; + line-height:100px; + font-size:80px; + content: "\f04b"; +} + .jumbotron-header, .jumbotron-actions { font-family: 'Helvetica Neue', Arial, sans-serif; @@ -349,6 +453,7 @@ div.modal-body button { .jumbotron-body { padding-bottom:30px; + padding-top:20px; } .jumbotron .btn { @@ -359,84 +464,52 @@ div.modal-body button { font-weight:normal; } -.video-item-heading { - margin-top:5px; -} - -.jumbotron-tabs { - margin:30px 0 20px; -} - -.jumbotron-tabs-container { - display:table; - width:90%; - margin:0 auto; +.jumbotron-buttons { + padding:0 0 20px; } -.jumbotron-tab { - vertical-align:middle; - height:50px; - font-size:20px; - display:table-cell; - width:25%; - margin:0 10px; - color:white; - text-decoration:none; - border-bottom:2px solid transparent; +.jumbotron-buttons .btn { + font-size:30px; + margin:0 20px 20px; } -.jumbotron-tabs .search-input { - margin:2px 0 0; - background:transparent; - border:0; - height:auto; - font-size:20px; - color:white; - border:0; - box-shadow:none; -} +.animated-item.ng-enter-stagger { + -webkit-animation-delay:150ms; + animation-delay:150ms; -.jumbotron-tab:hover { - color:white; - text-decoration:none; - background:rgba(0,0,0,0.15); + -webkit-animation-duration:0; + animation-duration:0; } -.jumbotron-tab.active { +.animated-item.ng-enter { position:relative; - background:#222; - color:#006FCC; + backface-visibility: visible !important; + animation: 0.4s fadeInDown; + -webkit-animation: 0.8s fadeInDown; } -.jumbotron-tab.active:after { - border-width:8px; - border-color:#222 transparent transparent; - border-style:solid; - position:absolute; - bottom:-16px; - left:50%; - margin-left:-8px; - content:""; -} - -.animated-item.ng-enter-stagger { - -webkit-transition-delay:0.15s; - transition-delay:0.15s; +@keyframes fadeInDown { + 0% { + opacity: 0; + transform: translate3d(0, -30px, 0); + } - -webkit-transition-duration:0; - transition-duration:0; + 100% { + opacity: 1; + transform: none; + } } -.animated-item.ng-enter { - transition:0.5s linear all; - position:relative; - left:-20px; - opacity:0; -} +@-webkit-keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -30px, 0); + } -.animated-item.ng-enter.ng-enter-active { - left:0; - opacity:1; + 100% { + opacity: 1; + -webkit-transform: none; + } } .jumbotron-message { diff --git a/src/featured-videos.json b/src/featured-videos.json index 3774bdfac..55c84a6e7 100644 --- a/src/featured-videos.json +++ b/src/featured-videos.json @@ -1,170 +1,113 @@ -[ +{"ng-conf": [ { - "category":"basics", "imageUrl":"https://i.ytimg.com/vi/r1A1VR0ibIQ/hqdefault.jpg", "title":"Miško Hevery and Brad Green - Keynote", "url":"https://www.youtube.com/watch?v=r1A1VR0ibIQ" }, { - "category":"basics", - "imageUrl":"https://i.ytimg.com/vi/tnXO-i7944M/hqdefault.jpg", - "title":"Dan Wahlin - AngularJS in 20ish Minutes", - "url":"https://www.youtube.com/watch?v=tnXO-i7944M" - }, - { - "category":"beyond", - "imageUrl":"https://i.ytimg.com/vi/e4yUTkva_FM/hqdefault.jpg", - "title":"Anant Narayanan - Building Realtime Apps With Firebase and Angular", - "url":"https://www.youtube.com/watch?v=e4yUTkva_FM" - }, - { - "category":"advanced", - "imageUrl":"https://i.ytimg.com/vi/TQoV0Jt3IJg/hqdefault.jpg", - "title":"Burke Holland - Angular Directives that Scale", - "url":"https://www.youtube.com/watch?v=TQoV0Jt3IJg" - }, - { - "category":"advanced", - "imageUrl":"https://i.ytimg.com/vi/aQipuiTcn3U/hqdefault.jpg", - "title":"Julie Ralph - End to End Angular Testing with Protractor", - "url":"https://www.youtube.com/watch?v=aQipuiTcn3U" - }, - { - "category":"advanced", - "imageUrl":"https://i.ytimg.com/vi/62RvRQuMVyg/hqdefault.jpg", - "title":"Writing a Massive Angular App at Google NG Conf", - "url":"https://www.youtube.com/watch?v=62RvRQuMVyg" - }, - { - "category":"advanced", - "imageUrl":"https://i.ytimg.com/vi/UMkd0nYmLzY/hqdefault.jpg", - "title":"Dave Smith - Deep Dive into Custom Directives", - "url":"https://www.youtube.com/watch?v=UMkd0nYmLzY" - }, - { - "category":"basics", "imageUrl":"https://i.ytimg.com/vi/h-SQvre_6qU/hqdefault.jpg", "title":"Igor Minar - Angular === Community (Keynote)", "url":"https://www.youtube.com/watch?v=h-SQvre_6qU" }, { - "category":"beyond", - "imageUrl":"https://i.ytimg.com/vi/wVntVkRLR3M/hqdefault.jpg", - "title":"Daniel Zen - Using AngularJS to create iPhone & Android applications with PhoneGap", - "url":"https://www.youtube.com/watch?v=wVntVkRLR3M" - }, - { - "category":"beyond", - "imageUrl":"https://i.ytimg.com/vi/MhVgGE-pgEY/hqdefault.jpg", - "title":"Ari Lerner - Robotics powering interfaces with AngularJS to the Arduino", - "url":"https://www.youtube.com/watch?v=MhVgGE-pgEY" - }, - { - "category":"beyond", - "imageUrl":"https://i.ytimg.com/vi/u6TeBM_SC8w/hqdefault.jpg", - "title":"Sean Hess - How to use Typescript on your Angular Application and Be Happy", - "url":"https://www.youtube.com/watch?v=u6TeBM_SC8w" - }, - { - "category":"advanced", - "imageUrl":"https://i.ytimg.com/vi/f62k7b753-Y/hqdefault.jpg", - "title":"Tom Valletta and Gabe Dayley - Angular Weapon Defense", - "url":"https://www.youtube.com/watch?v=f62k7b753-Y" + "imageUrl":"https://i.ytimg.com/vi/aQipuiTcn3U/hqdefault.jpg", + "title":"Julie Ralph - End to End Angular Testing with Protractor", + "url":"https://www.youtube.com/watch?v=aQipuiTcn3U" }, { - "category":"basics", "imageUrl":"https://i.ytimg.com/vi/srt3OBP2kGc/hqdefault.jpg", "title":"Angular Team Panel", "url":"https://www.youtube.com/watch?v=srt3OBP2kGc" }, { - "category":"advanced", - "imageUrl":"https://i.ytimg.com/vi/JfykD-0tpjI/hqdefault.jpg", - "title":"Ben Teese - Rich Data Models & Angular", - "url":"https://www.youtube.com/watch?v=JfykD-0tpjI" - }, - { - "category":"basics", - "imageUrl":"https://i.ytimg.com/vi/L4FJ_kuO9Rc/hqdefault.jpg", - "title":"Sharon DiOrio - Filters Beyond OrderBy and LimitTo", - "url":"https://www.youtube.com/watch?v=L4FJ_kuO9Rc" - }, - { - "category":"beyond", - "imageUrl":"https://i.ytimg.com/vi/3IqtmUscE_U/hqdefault.jpg", - "title":"Brian Ford - Zones", - "url":"https://www.youtube.com/watch?v=3IqtmUscE_U" - }, - { - "category":"advanced", - "imageUrl":"https://i.ytimg.com/vi/zyYpHIOrk_Y/hqdefault.jpg", - "title":"Karl Seamon - Angular Performance", - "url":"https://www.youtube.com/watch?v=zyYpHIOrk_Y" - }, - { - "category":"basics", "imageUrl":"https://i.ytimg.com/vi/hC0MpgUoui4/hqdefault.jpg", "title":"Lukas Rubbelke & Matias Niemela - Awesome Interfaces with AngularJS Animations", "url":"https://www.youtube.com/watch?v=hC0MpgUoui4" }, { - "category":"basics", "imageUrl":"https://i.ytimg.com/vi/_OGGsf1ZXMs/hqdefault.jpg", "title":"Vojta Jina - Dependency Injection", "url":"https://www.youtube.com/watch?v=_OGGsf1ZXMs" - }, - { - "category":"beyond", - "imageUrl":"https://i.ytimg.com/vi/0V8fQoqQLLA/hqdefault.jpg", - "title":"Jeff Cross - Rapid Prototyping with Angular & Deployd - NGConf", - "url":"https://www.youtube.com/watch?v=0V8fQoqQLLA" - }, - { - "category":"beyond", - "imageUrl":"https://i.ytimg.com/vi/4yulGISBF8w/hqdefault.jpg", - "title":"Thomas Burleson Angular and RequireJS", - "url":"https://www.youtube.com/watch?v=4yulGISBF8w" - }, - { - "category":"beyond", - "imageUrl":"https://i.ytimg.com/vi/NTPutZ99XWY/hqdefault.jpg", - "title":"Ben Clinkinbeard - Angular with Browserify", - "url":"https://www.youtube.com/watch?v=NTPutZ99XWY" - }, - { - "category":"advanced", - "imageUrl":"https://i.ytimg.com/vi/JLij19xbefI/hqdefault.jpg", - "title":"John Papa - Progressive Saving", - "url":"https://www.youtube.com/watch?v=JLij19xbefI" - }, - { - "category":"beyond", - "imageUrl":"https://i.ytimg.com/vi/RqKUTGB-CxA/hqdefault.jpg", - "title":"James Deboer - Dart, it's Angular Too", - "url":"https://www.youtube.com/watch?v=RqKUTGB-CxA" - }, - { - "category":"beyond", - "imageUrl":"https://i.ytimg.com/vi/Iw-3qgG_ipU/hqdefault.jpg", - "title":"Dean Sofer - AngularJS ORM", - "url":"https://www.youtube.com/watch?v=Iw-3qgG_ipU" - }, - { - "category":"advanced", - "imageUrl":"https://i.ytimg.com/vi/I-TvWfXVR08/hqdefault.jpg", - "title":"Silvano Luciani - PhotoHunt", - "url":"https://www.youtube.com/watch?v=I-TvWfXVR08" - }, - { - "category":"advanced", - "imageUrl":"https://i.ytimg.com/vi/XcRdO5QVlqE/hqdefault.jpg", - "title":"Christian Lilley - Going Postal with Angular in Promises", - "url":"https://www.youtube.com/watch?v=XcRdO5QVlqE" - }, - { - "category":"advanced", - "imageUrl":"https://i.ytimg.com/vi/jVzymluqmg4/hqdefault.jpg", - "title":"Jason Aden - Using ngModelController to Make Sexy Custom Components", - "url":"https://www.youtube.com/watch?v=jVzymluqmg4" } -] +], "ng-europe": [ + { + "description": "Slides: http://goo.gl/70sEsr", + "duration": "2094", + "id": "c5HSqDLfpW0", + "imageUrl": "https://i.ytimg.com/vi/c5HSqDLfpW0/hqdefault.jpg", + "url": "https://www.youtube.com/watch?v=c5HSqDLfpW0", + "title": "Keynote on the state of Angular by Igor Minar, Brad Green and Judy Tuan" + }, + { + "description": "In this keynote, Mi\u0161ko introduces AtScript to the world and talks about the future and past of AngularJS. Slides: http://goo.gl/pwk6Pb.", + "duration": "2136", + "id": "lGdnh8QSPPk", + "imageUrl": "https://i.ytimg.com/vi/lGdnh8QSPPk/hqdefault.jpg", + "url": "https://www.youtube.com/watch?v=lGdnh8QSPPk", + "title": "Mi\u0161ko Hevery - Keynote on AtScript" + }, + { + "description": "Igor Minar & Tobias Bosch present on Angular 2.0 Core by comparing it with Angular 1.3. Slides: http://goo.gl/ZyUU3Q.", + "duration": "1815", + "id": "gNmWybAyBHI", + "imageUrl": "https://i.ytimg.com/vi/gNmWybAyBHI/hqdefault.jpg", + "url": "https://www.youtube.com/watch?v=gNmWybAyBHI", + "title": "Angular 2.0 Core by Igor Minar & Tobias Bosch" + }, + { + "description": "Brian and Jeff tell you everything you need to know about Angular 1.3. Slides: http://goo.gl/pNmhAa.", + "duration": "1158", + "id": "ojMy6m_fcxc", + "imageUrl": "https://i.ytimg.com/vi/ojMy6m_fcxc/hqdefault.jpg", + "url": "https://www.youtube.com/watch?v=ojMy6m_fcxc", + "title": "Angular 1.3 by Jeff Cross & Brian Ford" + }, + { + "description": "slides: http://goo.gl/Htbhuw docs and demos: https://material.angularjs.org/", + "duration": "1527", + "id": "2qiyhkQVyxE", + "imageUrl": "https://i.ytimg.com/vi/2qiyhkQVyxE/hqdefault.jpg", + "url": "https://www.youtube.com/watch?v=2qiyhkQVyxE", + "title": "Material Design by Thomas Burleson & Max Lynch" + }, + { + "description": "Animations (sequencer, web animations) by Matias Niemel\u00e4 ak yearofmoo at ngeurope 2014. Slides: http://goo.gl/dknq6x.", + "duration": "1577", + "id": "3hktBbxFxSM", + "imageUrl": "https://i.ytimg.com/vi/3hktBbxFxSM/hqdefault.jpg", + "url": "https://www.youtube.com/watch?v=3hktBbxFxSM", + "title": "Animations (sequencer, web animations) by Matias Niemel\u00e4 aka yearofmoo at ngeurope 2014" + }, + { + "duration": "1543", + "description": "Slides: http://goo.gl/yWqIzW", + "id": "XgmUkCISabc", + "title":"Protractor and the Testability API by Julie Ralph & Chirayu Krishnappa", + "url": "https://www.youtube.com/watch?v=XgmUkCISabc", + "imageUrl":"https://i.ytimg.com/vi/XgmUkCISabc/hqdefault.jpg" + }, + { + "description": "Rob tells all about the new router in AngularJS. Slides: http://goo.gl/33Jlb6.", + "duration": "1554", + "id": "h1P_Vh4gSQY", + "imageUrl": "https://i.ytimg.com/vi/h1P_Vh4gSQY/hqdefault.jpg", + "url": "https://www.youtube.com/watch?v=h1P_Vh4gSQY", + "title": "The new Router for AngularJS by Rob Eisenberg" + }, + { + "description": "There's a tendency for engineers to pick up a web development framework and then suddenly forget everything they've ever learned about software engineering. This talk will start by examining...", + "duration": "1768", + "id": "dmYDggEgU-s", + "imageUrl": "https://i.ytimg.com/vi/dmYDggEgU-s/hqdefault.jpg", + "url": "https://www.youtube.com/watch?v=dmYDggEgU-s", + "title": "AngularJS Accessibility by Marcy Sutton" + }, + { + "description": "Brian talks about the latest tooling in Angular and the new Batarang. Slides: http://goo.gl/Jb8V46.", + "duration": "896", + "id": "x8IWsjoCy-M", + "imageUrl": "https://i.ytimg.com/vi/x8IWsjoCy-M/hqdefault.jpg", + "url": "https://www.youtube.com/watch?v=x8IWsjoCy-M", + "title": "Tooling by Brian Ford" + } +]} diff --git a/src/img/ng-conf-logo.png b/src/img/ng-conf-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..710aac123651192680ec8e74b4de098a8b0ca2a0 GIT binary patch literal 16137 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 b9P$4F@T-!HX;+Z$00000NkvXXu0mjfFC;tl literal 0 HcmV?d00001 diff --git a/src/img/ng-europe-logo.png b/src/img/ng-europe-logo.png new file mode 100755 index 0000000000000000000000000000000000000000..a4cc258b3f308481b246605b53110b0d7d337ae2 GIT binary patch literal 18774 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 literal 0 HcmV?d00001 diff --git a/src/img/videos-bg.png b/src/img/videos-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..8b1d392408147834387766c9212cffbf549fb0d4 GIT binary patch literal 33702 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 literal 0 HcmV?d00001 diff --git a/src/index.html b/src/index.html index 917c9f6a8..f51c9e04b 100644 --- a/src/index.html +++ b/src/index.html @@ -184,48 +184,55 @@

    Extensibility

    -
    +
    - - -
    +

    Loading...

    - diff --git a/src/js/homepage.js b/src/js/homepage.js index 1d36b31d7..9485f3f45 100644 --- a/src/js/homepage.js +++ b/src/js/homepage.js @@ -315,38 +315,25 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap', 'download-data']) .controller('JumbotronCtrl', ['$scope', '$http', 'filterFilter', function($scope, $http, filterFilter) { - function byCategoryFilter(array, category) { - var results = []; - angular.forEach(array, function(video) { - if ( video.category == category ) { - results.push(video); - } - }); - return results; - } + var self = this; + var allVideos; + var defaultSection = 'ng-europe'; + self.section = defaultSection; - var defaultCategory = 'basics'; - $scope.category = defaultCategory; + self.setSection = function(section) { + self.section = section; + if (!self.loading) { + self.videos = allVideos[self.section]; + } + }; - var allVideos; - $scope.loading = true; + self.loading = true; $http.get('./featured-videos.json').success(function(results) { - $scope.loading = false; + self.loading = false; + allVideos = results; - $scope.filterByCategory($scope.category); + self.videos = results[self.section]; }); - - $scope.filterBySearch = function(q) { - $scope.search = q; - $scope.category = null; - $scope.videos = filterFilter(allVideos, q); - }; - - $scope.filterByCategory = function(category) { - $scope.search = null; - $scope.category = category; - $scope.videos = byCategoryFilter(allVideos, category); - }; }]) From 65ca322b4b52664e64dee8e81b0902b26ebed8a0 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Tue, 4 Nov 2014 13:02:16 +0100 Subject: [PATCH 108/255] docs(index): correct link to the www.ngeurope.org site Closes #139 Closes https://github.com/angular/angular.js/issues/9901 --- src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index f51c9e04b..b37eb9c90 100644 --- a/src/index.html +++ b/src/index.html @@ -220,7 +220,7 @@

    {{ video.title }}

    view all videos - + www.ngeurope.org
    From 878225be5b43bce2c4c1106c6fdf1b601708fe96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Wed, 5 Nov 2014 15:12:53 -0500 Subject: [PATCH 109/255] update(index.html): add video of the team-panel discussion from ng-europe --- src/featured-videos.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/featured-videos.json b/src/featured-videos.json index 55c84a6e7..854b88815 100644 --- a/src/featured-videos.json +++ b/src/featured-videos.json @@ -62,6 +62,14 @@ "url": "https://www.youtube.com/watch?v=ojMy6m_fcxc", "title": "Angular 1.3 by Jeff Cross & Brian Ford" }, + { + "description": "The angular team answers questions from the crowd and Google Moderator on Angular 2.0, AtScript, Angular Material, the new router, ARIA, the path to migration and more.", + "duration": "3234", + "id": "g-x1QKriY90", + "imageUrl": "https://i.ytimg.com/vi/g-x1QKriY90/hqdefault.jpg", + "url": "https://www.youtube.com/watch?v=g-x1QKriY90", + "title": "Q&A with Angular team on 2.0, AtScript & more" + }, { "description": "slides: http://goo.gl/Htbhuw docs and demos: https://material.angularjs.org/", "duration": "1527", From 3c88bda5a4a510bc5f5e015a1a30451430f4fcf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Wed, 5 Nov 2014 15:18:18 -0500 Subject: [PATCH 110/255] chore(index.html): remove trailing text from video title --- src/featured-videos.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/featured-videos.json b/src/featured-videos.json index 854b88815..edac73ba5 100644 --- a/src/featured-videos.json +++ b/src/featured-videos.json @@ -84,7 +84,7 @@ "id": "3hktBbxFxSM", "imageUrl": "https://i.ytimg.com/vi/3hktBbxFxSM/hqdefault.jpg", "url": "https://www.youtube.com/watch?v=3hktBbxFxSM", - "title": "Animations (sequencer, web animations) by Matias Niemel\u00e4 aka yearofmoo at ngeurope 2014" + "title": "Animations (sequencer, web animations) by Matias Niemel\u00e4 aka yearofmoo" }, { "duration": "1543", From 432171388dd420823ee0daf4449628083bd284da Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Mon, 10 Nov 2014 09:02:27 -0800 Subject: [PATCH 111/255] chore(plusone): remove plus one from page The API call was occasionally hanging, causing tests to timeout. --- src/index.html | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/index.html b/src/index.html index b37eb9c90..86b586803 100644 --- a/src/index.html +++ b/src/index.html @@ -129,9 +129,6 @@

    HTML enhanced for web apps!

    Follow +AngularJS on -
  • - -
  • @@ -217,16 +214,16 @@

    {{ video.title }}

    - +
    - + view all videos @@ -893,8 +890,7 @@

    JavaScript Projects

    angular.bootstrap(document, ['ngRoute', 'homepage', 'ngLocal.us']); - - + - - + + \n', resource: '\n', route: '\n', - firebase: '\n \n' + firebase: '\n \n' }; }) From 3a23cfb4b878ae4a5fdf37be353c14c8bfa9c939 Mon Sep 17 00:00:00 2001 From: aarongray Date: Fri, 21 Nov 2014 16:48:06 -0600 Subject: [PATCH 114/255] fix(favicon): provide retina-friendly favicon The docs favicon is very low resolution. This remedies the issue. Closes #143 --- src/favicon.ico | Bin 1150 -> 32038 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/favicon.ico b/src/favicon.ico index fe24a63a6ba4c4b4fb0e960abb60e51dc9097d43..3bb79096115a22b0977fd55805c29feb9562cf1e 100644 GIT binary patch literal 32038 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 Date: Thu, 4 Dec 2014 16:58:01 -0800 Subject: [PATCH 115/255] chore: update text for 1.3 branch Closes #144 --- src/js/download-data.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/download-data.js b/src/js/download-data.js index b2e337c78..a89ba4733 100644 --- a/src/js/download-data.js +++ b/src/js/download-data.js @@ -24,8 +24,8 @@ angular.module('download-data', []) "
    "+ "
    Legacy 1.2.x
    "+ "
    This branch is in maintenance mode. It is stable and the API will not undergo any further changes. New releases will only contain bug-fixes.
    "+ - "
    Latest 1.3.x
    "+ - "
    This branch is being actively developed. The API's are subject to change without any prior notice. Use if you want to have access to the most recent features.
    "+ + "
    Stable 1.3.x
    "+ + "
    This is the latest stable branch, with regular bug fixes, performance improvements and small features added.
    "+ "
    ", buildsInfo: From 326332f01f08e4d42c575d92b582f758e1253a5a Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Mon, 29 Dec 2014 15:51:19 -0800 Subject: [PATCH 116/255] chore(protractor): upgrade protractor version And add a test for project list --- package.json | 2 +- protractorConf.js | 2 +- test/angularjs.org.spec.js | 72 ++++++++++++++++++++++---------------- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index 7351e579b..123cdd58b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "devDependencies": { "http-server": "*", - "protractor": "~0.24", + "protractor": "~1.5", "selenium-webdriver": "~2.40.0" } } \ No newline at end of file diff --git a/protractorConf.js b/protractorConf.js index e785d88c8..37f0ae4cb 100644 --- a/protractorConf.js +++ b/protractorConf.js @@ -1,5 +1,5 @@ exports.config = { - seleniumServerJar: './node_modules/protractor/selenium/selenium-server-standalone-2.42.0.jar', + seleniumServerJar: './node_modules/protractor/selenium/selenium-server-standalone-2.44.0.jar', seleniumArgs: [], baseUrl: process.env.ANGULAR_HOME_HOST || 'http://angularjs.org', capabilities: { diff --git a/test/angularjs.org.spec.js b/test/angularjs.org.spec.js index b77cce42c..c6d6a80c6 100644 --- a/test/angularjs.org.spec.js +++ b/test/angularjs.org.spec.js @@ -1,26 +1,25 @@ describe('Angularjs.org', function () { var protractor = require('protractor') - , tractor = protractor.getInstance() , protractorConfig = require('../protractorConf.js') , webdriver = require('selenium-webdriver'); describe('App', function () { beforeEach(function () { - tractor.get(''); + browser.get(''); }); it('should have the correct version of angularjs loaded', function() { //This only runs if an environment variable tells us to check if (process.env.CHECK_SCRIPT_TAG !== 'true') return; - var scriptTag = tractor.findElement(protractor.By.css('script#angularScript')); + var scriptTag = browser.findElement(protractor.By.css('script#angularScript')); expect(scriptTag.getAttribute('src')). toContain(process.env.ANGULAR_VERSION); }) it('should load the web page', function () { - var body = tractor.findElement(protractor.By.css('body')); + var body = browser.findElement(protractor.By.css('body')); expect(body.getAttribute('ng-controller')).toEqual('AppCtrl'); }); @@ -30,10 +29,10 @@ describe('Angularjs.org', function () { downloadVersions = process.env.ANGULAR_DOWNLOAD_VERSIONS.split(' '); beforeEach(function () { - var downloadBtn = tractor.findElement(protractor.By.css('.hero-unit .btn-primary')), done; + var downloadBtn = browser.findElement(protractor.By.css('.hero-unit .btn-primary')), done; downloadBtn.click(); - tractor.driver.sleep(500); - cdnInput = tractor.findElement(protractor.By.css('#cdnURL')); + browser.driver.sleep(500); + cdnInput = browser.findElement(protractor.By.css('#cdnURL')); cdnInput.getAttribute('value') cdnInput.getText().then(function (text) { stableVersion = text.toString().split('/').splice(-2,1)[0]; @@ -41,15 +40,15 @@ describe('Angularjs.org', function () { }); it('should open a modal prompting for download configuration', function () { - var downloadModal = tractor.findElement(protractor.By.css('.download-modal')) + var downloadModal = browser.findElement(protractor.By.css('.download-modal')) expect(downloadModal.getCssValue('display')).toEqual('block'); }); it('should change the CDN url based on user selection of stable or unstable', function () { var okay; - var unstableButton = tractor.findElement(protractor.By.css(".branch-btns button:nth-child(1)")); - tractor.driver.sleep(500); + var unstableButton = browser.findElement(protractor.By.css(".branch-btns button:nth-child(1)")); + browser.driver.sleep(500); unstableButton.click(); cdnInput.getAttribute('value').then(function (val) { var unstableVersion = val.split('/').splice(-2,1)[0]; @@ -71,7 +70,7 @@ describe('Angularjs.org', function () { replace(/\./g, '-'). replace(/\*/g, 'x'), - branchBtn = tractor.findElement( + branchBtn = browser.findElement( protractor.By.css(branchBtnSelector)); branchBtn.click(); @@ -83,7 +82,7 @@ describe('Angularjs.org', function () { it('should allow downloading uncompressed angular', function () { - var uncompressedBtn = tractor.findElement( + var uncompressedBtn = browser.findElement( protractor.By.css( '.download-modal .modal-body > dl button.uncompressed')); uncompressedBtn.click() @@ -94,23 +93,23 @@ describe('Angularjs.org', function () { describe('The Basics', function () { it('should show the code example', function () { - var hello = tractor.findElement(protractor.By.css('[app-source="hello.html"]')); + var hello = browser.findElement(protractor.By.css('[app-source="hello.html"]')); expect(hello.getText()).toContain('{{yourName}}'); }); it('should have a hoverable region called ng-app', function () { - var noCode = tractor.findElement(protractor.By.css('[popover-title="ng-app"]')) + var noCode = browser.findElement(protractor.By.css('[popover-title="ng-app"]')) expect(noCode.getText()).toEqual('ng-app'); }); it('should update the Hello text after entering a name', function () { - var el = tractor.findElement(protractor.By.model('yourName')); + var el = browser.findElement(protractor.By.model('yourName')); el.click() el.sendKeys('Jeff') - var bound = tractor.findElement(protractor.By.css('[app-run="hello.html"] h1')); + var bound = browser.findElement(protractor.By.css('[app-run="hello.html"] h1')); expect(bound.getText()).toEqual('Hello Jeff!'); }); }); @@ -118,28 +117,28 @@ describe('Angularjs.org', function () { describe('Add Some Control', function () { it('should strike out a todo when clicked', function () { - var el = tractor.findElement(protractor.By.css('[ng-controller="TodoController"] ul >li:nth-child(2) input')); + var el = browser.findElement(protractor.By.css('[ng-controller="TodoController"] ul >li:nth-child(2) input')); el.click(); expect(el.getAttribute('value')).toBe('on'); }); it('should add a new todo when added through text field', function () { - var el = tractor.findElement(protractor.By.model('todoText')); + var el = browser.findElement(protractor.By.model('todoText')); el.click(); el.sendKeys('Write tests!'); el.sendKeys(webdriver.Key.RETURN); - var lastTodo = tractor.findElement(protractor.By.css('[ng-repeat="todo in todos"]:nth-child(3) span')); + var lastTodo = browser.findElement(protractor.By.css('[ng-repeat="todo in todos"]:nth-child(3) span')); expect(lastTodo.getText()).toEqual('Write tests!'); }); it('should show a secondary tab when selected', function () { - var todoJsTab = tractor.findElement(protractor.By.css('[annotate="todo.annotation"] ul.nav-tabs li:nth-child(2) a')); + var todoJsTab = browser.findElement(protractor.By.css('[annotate="todo.annotation"] ul.nav-tabs li:nth-child(2) a')); todoJsTab.click() - tractor.driver.sleep(500); - var todojs = tractor.findElement(protractor.By.css('[annotate="todo.annotation"] .tab-pane:nth-child(2)')); + browser.driver.sleep(500); + var todojs = browser.findElement(protractor.By.css('[annotate="todo.annotation"] .tab-pane:nth-child(2)')); expect(todojs.getCssValue('display')).toEqual('block'); }); }); @@ -147,18 +146,29 @@ describe('Angularjs.org', function () { describe('Wire up a Backend', function () { it('should show a secondary tab when selected', function () { - var listBtn = tractor.findElement(protractor.By.css('[annotate="project.annotation"] ul.nav-tabs li:nth-child(2) a')); + var listBtn = browser.findElement(protractor.By.css('[annotate="project.annotation"] ul.nav-tabs li:nth-child(2) a')); listBtn.click(); - tractor.driver.sleep(500); - var listTab = tractor.findElement(protractor.By.css('[module="project"] .tab-pane:nth-child(2)')); + browser.driver.sleep(500); + var listTab = browser.findElement(protractor.By.css('[module="project"] .tab-pane:nth-child(2)')); expect(listTab.getCssValue('display')).toEqual('block'); }); + + + it('should search the list of projects', function() { + browser.driver.sleep(2000); + var list = element.all(by.repeater('project in projects')); + element(by.id('projects_search')).sendKeys('Ang'); + browser.driver.sleep(50); + expect(list.count()).toBe(1); + expect(list.get(0).getText()).toContain('AngularJS'); + browser.driver.sleep(5000); + }); }); describe('Create Components', function () { it('should show the US localization of date', function () { - var dateText = tractor.findElement(protractor.By.css('[module="app-us"] .tab-content > .tab-pane > span:first-child')); + var dateText = browser.findElement(protractor.By.css('[module="app-us"] .tab-content > .tab-pane > span:first-child')); var text = dateText.getText(); expect(text).toMatch(/^Date: [A-Za-z]*, [A-Za-z]+ [0-9]{1,2}, [0-9]{4}$/); @@ -166,19 +176,19 @@ describe('Angularjs.org', function () { /*it('should show the US pluralization of beer', function () { - var pluralTabLink = tractor.findElement(protractor.By.css('[module="app-us"] .nav-tabs > li:nth-child(2) a')); + var pluralTabLink = browser.findElement(protractor.By.css('[module="app-us"] .nav-tabs > li:nth-child(2) a')); pluralTabLink.click() - var pluralTab = tractor.findElement(protractor.By.css('[module="app-us"] [ng-controller="BeerCounter"] > div > ng-pluralize')); + var pluralTab = browser.findElement(protractor.By.css('[module="app-us"] [ng-controller="BeerCounter"] > div > ng-pluralize')); expect(pluralTab.getText()).toEqual('no beers'); }); it('should show the Slovak pluralization of beer', function () { - var pluralTabLink = tractor.findElement(protractor.By.css('[module="app-sk"] .nav-tabs > li:nth-child(2) a')); + var pluralTabLink = browser.findElement(protractor.By.css('[module="app-sk"] .nav-tabs > li:nth-child(2) a')); pluralTabLink.click(); - var pluralTab = tractor.findElement(protractor.By.css('[module="app-sk"] [ng-controller="BeerCounter"] > div > ng-pluralize')); + var pluralTab = browser.findElement(protractor.By.css('[module="app-sk"] [ng-controller="BeerCounter"] > div > ng-pluralize')); expect(pluralTab.getText()).toEqual('žiadne pivo'); });*/ }); @@ -186,7 +196,7 @@ describe('Angularjs.org', function () { describe('Embed and Inject', function () { it('should have some content under and "Embeddable" heading', function () { - var embedAndInject = tractor.findElement(protractor.By.css('#embed-and-inject')) + var embedAndInject = browser.findElement(protractor.By.css('#embed-and-inject')) expect(embedAndInject.getText()).toEqual('Embed and Inject'); }); }); From 551685c5aa6fab64937c4489217c543070490723 Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Mon, 29 Dec 2014 15:53:07 -0800 Subject: [PATCH 117/255] fix(projects): sandbox projects list backend --- src/index.html | 105 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 96 insertions(+), 9 deletions(-) diff --git a/src/index.html b/src/index.html index 7068da930..fbf0dc726 100644 --- a/src/index.html +++ b/src/index.html @@ -558,20 +558,107 @@

    Todo

    padding: 0 .25em; } + - + @@ -780,7 +780,7 @@

    JavaScript Projects

    , "$asArray": "A method that returns data from Firebase in the form of a synchronized array." , "config": "You use config() to configure existing services. Here, we’re configuring the $routeProvider responsible for mapping URL paths to partials." , "controller": "Define a controller function that can be attached to the DOM using ng-controller or to a view template by specifying it in the route configuration." - , "'/'": "When the URL is / it will load list.html into the view and attach the ListCtrl controller. You can instantly get an overview of an app's structure by reading the route definitions." + , "'/'": "When the URL is / it will load list.html into the view and attach the ProjectListController controller. You can instantly get an overview of an app's structure by reading the route definitions." , "/edit/:projectId": "This route definition has a colon ':' in it. You use colons to make a component of the URL available to your controller. So now, EditCtrl can refer to the projectId property which tells it which project to edit." , "otherwise": "The otherwise route specifies which view to display when the URL doesn’t match any of the explicit routes. It’s the default." , "Projects": "Projects is an instance of $firebase, and is defined in the projects module. It exposes method to add, remove and update projects in the collection. Its purpose is to abstract the server communication. This lets the controller focus on the behavior rather than the complexities of server access." @@ -801,7 +801,7 @@

    JavaScript Projects

    , "ng-repeat": "Use ng-repeat to unroll a collection. Here, for every project in projects, AngularJS will create new copy of the <tr> element." , "filter": "The filter uses the search to return only a subset of items in the projects array. As you enter text into the search box, the filter will narrow down the list according to your criteria. ng-repeat will then add or remove items from the table." , "orderBy": "Returns the project list ordered by name property." - , "#/edit/{{project._id.$oid}}": "Creates individual edit links, by embedding the project id into the URL. The embedded project id serves the purpose of deep-linking, back button, as well as a way to communicate to EditCtrl which project should be edited." + , "#/edit/{{project._id.$oid}}": "Creates individual edit links, by embedding the project id into the URL. The embedded project id serves the purpose of deep-linking, back button, as well as a way to communicate to EditProjectController which project should be edited." } , "detail.html": { "myForm": "Create a form named myForm. We will declare form validation rules here which we'll use to show input errors and disable buttons." @@ -858,8 +858,8 @@

    JavaScript Projects

    restrict: 'E', transclude: true, scope: { title: '@' }, - link: function(scope, element, attrs, tabsCtrl) { - tabsCtrl.addPane(scope); + link: function(scope, element, attrs, tabsController) { + tabsController.addPane(scope); }, template: '
    ' + diff --git a/src/js/homepage.js b/src/js/homepage.js index 48aed5967..0feaa2a2a 100644 --- a/src/js/homepage.js +++ b/src/js/homepage.js @@ -313,7 +313,7 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap', 'download-data']) }; }) - .controller('AppCtrl', function($scope, $modal, BRANCHES) { + .controller('AppController', function($scope, $modal, BRANCHES) { $scope.BRANCHES = BRANCHES; $scope.showDownloadModal = function() { @@ -336,7 +336,7 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap', 'download-data']) }) - .controller('DownloadCtrl', function($scope, BRANCHES, BUILDS, DOWNLOAD_INFO) { + .controller('DownloadController', function($scope, BRANCHES, BUILDS, DOWNLOAD_INFO) { function getRelativeUrl(branch, build) { switch (build.name) { @@ -442,7 +442,7 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap', 'download-data']) angular.module('Group', ['ngResource']); -function GroupCtrl($scope, $resource) +function GroupController($scope, $resource) { $scope.featuredGroups = $resource('groups/index/getfeatured'); $scope.featuredGroups.get(); diff --git a/src/partials/download-modal.html b/src/partials/download-modal.html index 5c93ec7de..f35f41e49 100644 --- a/src/partials/download-modal.html +++ b/src/partials/download-modal.html @@ -1,4 +1,4 @@ -
    +
    + - - -

    Embed and Inject

    +

    Testability Built-in

    -
    -

    Embeddable

    -

    - AngularJS works great with other technologies. Add as much or as little of AngularJS to - an existing page as you like. Many other frameworks require full commitment. This page - has multiple AngularJS applications embedded in it. Because AngularJS has no global - state multiple apps can run on a single page without the use of iframes. We - encourage you to view-source and look around. -

    -
    -
    +

    Injectable

    The dependency injection in AngularJS allows you to declaratively describe how your @@ -405,7 +369,7 @@

    Injectable

    replaced.

    -
    +

    Testable

    AngularJS was designed from ground up to be testable. It encourages behavior-view @@ -418,6 +382,51 @@

    Testable

    + + +

    Back to top

    From 785566f6c68ed42a833d224d5330d092f965f9e4 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Mon, 29 Jun 2015 21:59:05 +0100 Subject: [PATCH 139/255] fix(index): update id and tests for new testability content --- src/index.html | 2 +- test/angularjs.org.spec.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/index.html b/src/index.html index e53ca2d49..554d03f43 100644 --- a/src/index.html +++ b/src/index.html @@ -357,7 +357,7 @@

    Locale: SK

    -

    Testability Built-in

    +

    Testability Built-in

    Injectable

    diff --git a/test/angularjs.org.spec.js b/test/angularjs.org.spec.js index b36520755..0b8e8c3eb 100644 --- a/test/angularjs.org.spec.js +++ b/test/angularjs.org.spec.js @@ -193,10 +193,10 @@ describe('Angularjs.org', function () { }); - describe('Embed and Inject', function () { - it('should have some content under and "Embeddable" heading', function () { - var embedAndInject = browser.findElement(protractor.By.css('#embed-and-inject')) - expect(embedAndInject.getText()).toEqual('Embed and Inject'); + describe('Testability Built-in', function () { + it('should have some content under and "Testability Built-in" heading', function () { + var testability = browser.findElement(protractor.By.css('#testability')) + expect(testability.getText()).toEqual('Testability Built-in'); }); }); }); From 7b95f75916087f2dd8508df1ed6fc8b8fe232449 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 2 Jul 2015 12:49:44 +0100 Subject: [PATCH 140/255] chore(version): update to Angular 1.4 Upgraded the website to run off latest Angular 1.4 release rather than 1.3; Changed the download modal to reference 1.4 (latest) and 1.2 (legacy). This will encourage people to move away from 1.3. --- build.sh | 8 ++++---- src/index.html | 12 ++++++------ src/js/download-data.js | 18 +++++++++--------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/build.sh b/build.sh index 7bb2f70ab..20899516c 100755 --- a/build.sh +++ b/build.sh @@ -21,14 +21,14 @@ function replaceInFile { } function getCdnVersions { - CDN_VERSION_1_3=$(./get-cdn-version.sh 1.3) + CDN_VERSION_1_2=$(./get-cdn-version.sh 1.2) CDN_VERSION_1_4=$(./get-cdn-version.sh 1.4) } function replaceCdnVersionInFiles { for FILE in "${CDN_REPLACE_FILES[@]}" do - replaceInFile $FILE '${CDN_VERSION_1_3}' $CDN_VERSION_1_3 + replaceInFile $FILE '${CDN_VERSION_1_2}' $CDN_VERSION_1_2 replaceInFile $FILE '${CDN_VERSION_1_4}' $CDN_VERSION_1_4 done } @@ -37,8 +37,8 @@ function replaceCdnVersionInFiles { function testBuildResult { export ANGULAR_HOME_HOST='http://localhost:8100'; - export ANGULAR_DOWNLOAD_VERSIONS="$CDN_VERSION_1_3:1.3.x $CDN_VERSION_1_4:1.4.x" - export ANGULAR_VERSION="$CDN_VERSION_1_3" + export ANGULAR_DOWNLOAD_VERSIONS="$CDN_VERSION_1_2:1.2.x $CDN_VERSION_1_4:1.4.x" + export ANGULAR_VERSION="$CDN_VERSION_1_4" export CHECK_SCRIPT_TAG="true" function killServer () { diff --git a/src/index.html b/src/index.html index 554d03f43..8be9cbf86 100644 --- a/src/index.html +++ b/src/index.html @@ -20,8 +20,8 @@ - - + + \n' + - ' \n'; - } + bootstrapStylesheet = 'http://netdna.bootstrapcdn.com/twitter-bootstrap/2.0.4/css/bootstrap-combined.min.css', + plnkrFiles = []; + + angular.forEach(ctrl.files.split(' '), function(filename, index) { + var content; + + if (index === 0) { + var head = templateBuilder.createLocalDependencies(ctrl.files); + + head.push(' \n'); + content = templateBuilder.getIndexTemplate({resource: ctrl.resource, route: ctrl.route, firebase: ctrl.firebase}); + content = content. + replace('__MODULE__', ctrl.module ? '="' + ctrl.module + '"' : ''). + replace('__HEAD__', head.join('')). + replace('__BODY__', fetchCode(filename, 4)); } else { - fields[fileType] += fetchCode(file) + '\n'; + content = fetchCode(filename); } + + plnkrFiles.push({ + name: index === 0 ? 'index.html' : filename, // plnkr expects an index.html + content: content + }); }); - fields.html += '
    \n'; - fields.html += - '

    \n' + - 'Copyright 2016 Google Inc. All Rights Reserved.
    \n' + - 'Use of this source code is governed by an MIT-style license that can be found in the LICENSE file at http://angular.io/license\n' + - '

    '; - element.html( - '
    ' + - hiddenField('title', 'AngularJS Example: ' + name) + - hiddenField('css', ' \n' + - stylesheet + - script.angular + - (attr.resource ? script.resource : '') + - (attr.route ? script.route : '') + - (attr.firebase ? script.firebase : '') + - ' - + - - + diff --git a/src/js/homepage.js b/src/js/homepage.js index f84a5979e..5e4ae8e09 100644 --- a/src/js/homepage.js +++ b/src/js/homepage.js @@ -216,10 +216,10 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap', 'download-data']) }; }) - .directive('appSource', function(fetchCode, escape, $compile, $timeout, templateBuilder) { + .directive('appSource', function(fetchCode, escape, $compile, $timeout, templateBuilder, $sce) { return { terminal: true, - scope: true, + scope: {}, link: function(scope, element, attrs) { var tabs = [], annotation = attrs.annotate && angular.fromJson(fetchCode(attrs.annotate)) || {}; @@ -251,19 +251,22 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap', 'download-data']) counter = 0; angular.forEach(annotation[filename], function(text, key) { + counter++; text = text.replace('{{', '{{').replace('}}', '}}'); var regexp = new RegExp('(\\W|^)(' + key.replace(/([\W\-])/g, '\\$1') + ')(\\W|$)'); + scope['popover' + index + counter] = $sce.trustAsHtml(text); + content = content.replace(regexp, function(_, before, token, after) { - token = "__" + (counter++) + "__"; + token = "__" + (counter) + "__"; popovers[token] = '' + escape(key) + '' + + ' uib-popover-html="popover' + index + counter + '">' + escape(key) + '' + ''; return before + token + after; }); @@ -274,22 +277,21 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap', 'download-data']) }); tabs.push( - '\n' + + '\n' + '
    ' + content +'
    \n' + - '
    \n' + '\n' ); }); element.html( - '' + + '' + tabs.join('') + - ''); - // element.find('[rel=popover]').popover().pulse(); + ''); // Compile up the HTML to get the directives to kick-in $compile(element.children())(scope); $timeout(function() { - var annotationElements = element.find('span[popover-html-unsafe]'); + var annotationElements = element.find('span[uib-popover-html]'); $compile(annotationElements)(scope); }, 0); } @@ -368,23 +370,23 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap', 'download-data']) .directive('hint', function() { return { template: 'Hint: Click ' + - 'me.' + 'me.' }; }) - .controller('AppController', function($scope, $modal, BRANCHES) { + .controller('AppController', function($scope, $uibModal, BRANCHES) { $scope.BRANCHES = BRANCHES; $scope.showDownloadModal = function() { - $modal.open({ + $uibModal.open({ templateUrl: 'partials/download-modal.html', windowClass: 'download-modal' }); }; $scope.showVideo = function(videoUrl) { - $modal.open({ + $uibModal.open({ templateUrl: 'partials/video-modal.html', windowClass: 'video-modal', controller: 'VideoController', @@ -458,36 +460,6 @@ angular.module('homepage', ['ngAnimate', 'ui.bootstrap', 'download-data']) }) - -// Angular UI Bootstrap provide some excellent directives, but the popover didn't allow for HTML content -// The popoverHtmlUnsafe and popoverHtmlUnsafePopup implement this on top of the AngularUI Bootstrap's $tooltip service -.directive( 'popoverHtmlUnsafePopup', function ($templateCache) { - - $templateCache.put("template/popover/popover-html-unsafe-popup.html", - "
    \n" + - "
    \n" + - "\n" + - "
    \n" + - "

    \n" + - "
    \n" + - "
    \n" + - "
    \n" + - ""); - - return { - restrict: 'EA', - replace: true, - scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/popover/popover-html-unsafe-popup.html' - }; -}) - -.directive( 'popoverHtmlUnsafe', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) { - return $tooltip( 'popoverHtmlUnsafe', 'popover', 'click' ); -}]) - - - .run(function($rootScope, startPulse){ $rootScope.version = angular.version; From ea0ae6e0f829ad5f285bc450fc3b0a8915f3c164 Mon Sep 17 00:00:00 2001 From: Jacques Crocker Date: Mon, 29 Feb 2016 14:02:23 -0800 Subject: [PATCH 171/255] feat(index): add angularattack 2016 Closes #192 --- src/img/angularattack-logo.png | Bin 0 -> 17002 bytes src/index.html | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/img/angularattack-logo.png diff --git a/src/img/angularattack-logo.png b/src/img/angularattack-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..103b3aa60067d3cfa46098ca9e0860d35fbadbb0 GIT binary patch literal 17002 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%0AJJGU^8cS6hjKM)`Uv`MJeq{ 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 N002ovPDHLkV1nrhtM>o^ literal 0 HcmV?d00001 diff --git a/src/index.html b/src/index.html index a05283aea..124e768f7 100644 --- a/src/index.html +++ b/src/index.html @@ -155,6 +155,26 @@

    HTML enhanced for web apps!

    + + +
    From c2d33ef7443c988b4d12bd73a7218cffae978d21 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 3 Mar 2016 09:54:36 +0000 Subject: [PATCH 172/255] feat(index): add angularattack 2016 (Fix the link) Closes #192 --- src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index 124e768f7..6ce25d9a6 100644 --- a/src/index.html +++ b/src/index.html @@ -159,7 +159,7 @@

    HTML enhanced for web apps!

    - + From 8aa40a2bb80d554041a532c275b78433967354c6 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 9 Nov 2016 10:00:01 +0000 Subject: [PATCH 186/255] chore(build): fix protractor version --- package.json | 4 ++-- protractorConf.js | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f4114a823..c7f0a15c7 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "build": "node scripts/build" }, "devDependencies": { + "eslint": "^3.9.1", "http-server": "*", - "protractor": "~2.5", - "selenium-webdriver": "~2.47.0" + "protractor": "^4.0.10" } } diff --git a/protractorConf.js b/protractorConf.js index 4ba3093dd..03c9730dc 100644 --- a/protractorConf.js +++ b/protractorConf.js @@ -1,10 +1,9 @@ exports.config = { - seleniumServerJar: './node_modules/protractor/selenium/selenium-server-standalone-2.47.1.jar', - seleniumArgs: [], baseUrl: process.env.ANGULAR_HOME_HOST || 'http://angularjs.org', capabilities: { 'browserName': 'chrome' }, + directConnect: true, specs: [ 'test/angularjs.org.spec.js', ], From e360d7c54b71c96cab149d77ba83de7126d4b33a Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 9 Nov 2016 10:00:40 +0000 Subject: [PATCH 187/255] chore(nvm): set expected node version to 6 --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..0e2c60cef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +6.6 \ No newline at end of file From 78f5c8b2325cdd85ec24ec08a18ce67730602f6a Mon Sep 17 00:00:00 2001 From: Alex Wachira Date: Thu, 3 Nov 2016 18:27:40 -0500 Subject: [PATCH 188/255] docs(index): fix typos and grammar for better readability Closes #208 --- src/index.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/index.html b/src/index.html index fada538c9..e47b35442 100644 --- a/src/index.html +++ b/src/index.html @@ -262,16 +262,16 @@

    Wire up a Backend

    Deep Linking

    - A deep link reflects where the user is in the app, this is useful so users can bookmark - and email links to locations within apps. Round trip apps get this automatically, but - AJAX apps by their nature do not. AngularJS combines the benefits of deep link with + A deep link reflects where the user is in the app. This is useful so users can bookmark + and email links to locations within the app. Round trip apps get this automatically, but + AJAX apps by their nature do not. AngularJS combines the benefits of deep linking with desktop app-like behavior.

    Form Validation

    - Client-side form validation is an important part of great user experience. + Client-side form validation is an important part of a great user experience. AngularJS lets you declare the validation rules of the form without having to write JavaScript code. Write less code, go have beer sooner.

    @@ -309,7 +309,7 @@

    Create Components

    Directives

    - Directives is a unique and powerful feature available only in Angular. Directives let + Directives are a unique and powerful feature available only in Angular. Directives let you invent new HTML syntax, specific to your application.

    From edc7f077cee6774fce1653e7965f6839ce63bb07 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 23 Nov 2016 19:05:45 +0000 Subject: [PATCH 189/255] chore(version): update to Angular 1.6 Changed the download modal to reference 1.6 (release-candidate). --- scripts/build.js | 2 +- src/js/download-data.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/build.js b/scripts/build.js index f602e72fe..92cf927d4 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -12,7 +12,7 @@ const utils = require('./utils'); const ROOT_DIR = '.'; const DST_DIR = 'build'; const SRC_DIR = 'src'; -const CDN_VERSIONS = ['1.2', '1.5']; +const CDN_VERSIONS = ['1.2', '1.5', '1.6']; const CDN_REPLACE_FILES = ['index.html', 'js/download-data.js']; const GIT_BRANCH_DIST = 'dist'; const PTOR_CONF = 'protractorConf.js'; diff --git a/src/js/download-data.js b/src/js/download-data.js index 1f96b477e..78764c86c 100644 --- a/src/js/download-data.js +++ b/src/js/download-data.js @@ -1,6 +1,12 @@ angular.module('download-data', []) .value('BRANCHES', [ + { + branch: '1.6.*', version: '${CDN_VERSION_1_6}', + title: '1.6.x (release candidate)', + cssClass: 'branch-1-6-x', + showOnButton: true + }, { branch: '1.5.*', version: '${CDN_VERSION_1_5}', title: '1.5.x (stable)', From f31012d7cf89f99af5dd1bd896d01563ee650842 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 23 Nov 2016 19:32:13 +0000 Subject: [PATCH 190/255] chore(version): update to run off 1.6.0-rc.0 --- src/index.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/index.html b/src/index.html index e47b35442..84663d1cb 100644 --- a/src/index.html +++ b/src/index.html @@ -21,8 +21,8 @@ - - + +  - +
    - +
    -
    -
    - -
    -
    -

    HTML enhanced for web apps!

    - - Download AngularJS
    -
    - - ({{branch.version}}{{ !$last ? ' / ' : '' }}) - -
      - - Try the new Angular
    - -
    +
    +

    AngularJSBy Google

    -
    - +
    + +
    +
    -

    Why AngularJS?

    -

    +

    Why AngularJS?

    +

    HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application. The resulting environment is extraordinarily expressive, readable, and quick to develop.

    +
    -

    Alternatives

    -

    +

    Alternatives

    +

    Other frameworks deal with HTML’s shortcomings by either abstracting away HTML, CSS, and/or JavaScript or by providing an imperative way for manipulating the DOM. Neither of these address the root problem that HTML was not designed for dynamic views.

    +
    -

    Extensibility

    -

    +

    Extensibility

    +

    AngularJS is a toolset for building the framework most suited to your application development. It is fully extensible and works well with other libraries. Every feature can be modified or replaced to suit your unique development workflow and feature needs. @@ -184,12 +170,18 @@

    Extensibility

    +
    + + + Shaping up with Angular.js + Learn Angular in your browser for free! +
    -

    The Basics

    +

    The Basics

    @@ -206,27 +198,27 @@

    Watch as we build this app

    -

    Add Some Control

    +

    Add Some Control

    -

    Data Binding

    -

    +

    Data Binding

    +

    Data-binding is an automatic way of updating the view whenever the model changes, as well as updating the model whenever the view changes. This is awesome because it eliminates DOM manipulation from the list of things you have to worry about.

    -

    Controller

    -

    +

    Controller

    +

    Controllers are the behavior behind the DOM elements. AngularJS lets you express the behavior in a clean readable form without the usual boilerplate of updating the DOM, registering callbacks or watching model changes.

    -

    Plain JavaScript

    -

    +

    Plain JavaScript

    +

    Unlike other frameworks, there is no need to inherit from proprietary types in order to wrap the model in accessors methods. AngularJS models are plain old JavaScript objects. This makes your code easy to test, maintain, reuse, and again free from boilerplate. @@ -240,7 +232,7 @@

    Plain JavaScript

    -

    Watch as we build this app

    +

    Watch as we build this app

    @@ -250,18 +242,18 @@

    Watch as we build this app

    -

    Wire up a Backend

    +

    Wire up a Backend

    -

    Deep Linking

    -

    +

    Deep Linking

    +

    A deep link reflects where the user is in the app. This is useful so users can bookmark and email links to locations within the app. Round trip apps get this automatically, but AJAX apps by their nature do not. AngularJS combines the benefits of deep linking with @@ -269,16 +261,16 @@

    Deep Linking

    -

    Form Validation

    -

    +

    Form Validation

    +

    Client-side form validation is an important part of a great user experience. AngularJS lets you declare the validation rules of the form without having to write JavaScript code. Write less code, go have beer sooner.

    -

    Server Communication

    -

    +

    Server Communication

    +

    AngularJS provides built-in services on top of XHR as well as various other backends using third party libraries. Promises further simplify your code by handling asynchronous return of data. In this example, we use the AngularFire @@ -304,26 +296,26 @@

    Server Communication

    -

    Create Components

    +

    Create Components

    -

    Directives

    -

    +

    Directives

    +

    Directives are a unique and powerful feature available in AngularJS. Directives let you invent new HTML syntax, specific to your application.

    -

    Reusable Components

    -

    +

    Reusable Components

    +

    We use directives to create reusable components. A component allows you to hide complex DOM structure, CSS, and behavior. This lets you focus either on what the application does or how the application looks separately.

    -

    Localization

    -

    +

    Localization

    +

    An important part of serious apps is localization. AngularJS's locale aware filters and stemming directives give you building blocks to make your application available in all locales. @@ -344,11 +336,11 @@

    Locale: SK

    -

    Testability Built-in

    +

    Testability Built-in

    -

    Injectable

    -

    +

    Injectable

    +

    The dependency injection in AngularJS allows you to declaratively describe how your application is wired. This means that your application needs no main() method which is usually an unmaintainable mess. Dependency injection is also a core to @@ -357,8 +349,8 @@

    Injectable

    -

    Testable

    -

    +

    Testable

    +

    AngularJS was designed from ground up to be testable. It encourages behavior-view separation, comes pre-bundled with mocks, and takes full advantage of dependency injection. It also comes with end-to-end scenario runner which eliminates test flakiness @@ -371,7 +363,7 @@

    Testable

    -

    Previous Conferences

    +

    Previous Conferences