├── .gitignore ├── modules ├── filters │ ├── highlight │ │ ├── highlight.less │ │ ├── highlight.js │ │ └── test │ │ │ └── highlightSpec.js │ ├── format │ │ ├── test │ │ │ └── formatSpec.js │ │ └── format.js │ ├── unique │ │ ├── unique.js │ │ └── test │ │ │ └── uniqueSpec.js │ └── inflector │ │ ├── test │ │ └── inflectorSpec.js │ │ └── inflector.js └── directives │ ├── date │ ├── dependencies.json │ ├── README.md │ └── date.js │ ├── codemirror │ ├── dependencies.json │ ├── codemirror.js │ └── test │ │ └── codemirrorSpec.js │ ├── select2 │ ├── dependencies.json │ ├── select2.js │ └── test │ │ └── select2Spec.js │ ├── tinymce │ ├── dependencies.json │ ├── tinymce.js │ └── test │ │ └── tinymceSpec.js │ ├── calendar │ ├── dependencies.json │ ├── calendar.js │ └── test │ │ └── calendarSpec.js │ ├── currency │ ├── stylesheets │ │ └── currency.less │ ├── currency.js │ ├── README.md │ └── test │ │ └── currencySpec.js │ ├── reset │ ├── stylesheets │ │ └── reset.less │ ├── reset.js │ └── test │ │ └── resetSpec.js │ ├── event │ ├── event.js │ └── test │ │ └── eventSpec.js │ ├── if │ ├── if.js │ └── test │ │ └── ifSpec.js │ ├── sortable │ ├── REDME.md │ ├── test │ │ └── sortableSpec.js │ └── sortable.js │ ├── animate │ ├── animate.js │ └── test │ │ └── animateSpec.js │ ├── scrollfix │ ├── scrollfix.js │ └── test │ │ └── scrollfixSpec.js │ ├── showhide │ ├── showhide.js │ └── test │ │ └── showhideSpec.js │ ├── keypress │ ├── test │ │ ├── keyupSpec.js │ │ ├── keydownSpec.js │ │ └── keypressSpec.js │ └── keypress.js │ ├── route │ ├── route.js │ └── test │ │ └── routeSpec.js │ ├── validate │ ├── validate.js │ └── test │ │ └── validateSpec.js │ ├── jq │ ├── jq.js │ ├── test │ │ └── jqSpec.js │ └── README.md │ ├── mask │ ├── demo │ │ └── index.html │ └── test │ │ └── maskSpec.js │ └── map │ ├── test │ └── mapSpec.js │ └── map.js ├── templates ├── stylesheets │ └── template.less ├── dependencies.json ├── template.js ├── README.md └── test │ └── templateSpec.js ├── CONTRIBUTING.md ├── .travis.yml ├── common ├── module.js ├── stylesheets │ ├── angular-ui.less │ └── mixins.less └── ieshiv │ ├── README.md │ └── ieshiv.js ├── component.json ├── package.json ├── LICENSE ├── test ├── test-config.js └── lib │ ├── tinymce │ └── jquery.tinymce.js │ ├── googlemaps │ └── googlemaps.js │ └── bootstrap │ └── bootstrap-modal.js ├── README.md ├── CHANGELOG.md └── grunt.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea -------------------------------------------------------------------------------- /modules/filters/highlight/highlight.less: -------------------------------------------------------------------------------- 1 | 2 | /* highlight */ 3 | .ui-match { 4 | background: yellow; 5 | } 6 | -------------------------------------------------------------------------------- /templates/stylesheets/template.less: -------------------------------------------------------------------------------- 1 | /* default styles (if any) for your directive implementations */ 2 | 3 | -------------------------------------------------------------------------------- /modules/directives/date/dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": [ "jquery", "jquery-ui" ], 3 | "internal": [], 4 | "external": [] 5 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | CONTRIBUTING 2 | ============ 3 | 4 | ### AngularUI is currently undergoing a [project restructure](http://angular-ui.github.io/). 5 | 6 | Please relocate all your issues to the relevant repo, thanks! 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | 5 | before_install: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | - npm install -g grunt@0.3.x testacular@0.4.x 9 | 10 | script: "grunt" -------------------------------------------------------------------------------- /modules/directives/codemirror/dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": [ "jquery" ], 3 | "internal": [], 4 | "external": [ 5 | "http://codemirror.net/lib/codemirror.js", 6 | "http://codemirror.net/lib/codemirror.css", 7 | ] 8 | } -------------------------------------------------------------------------------- /common/module.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('ui.config', []).value('ui.config', {}); 3 | angular.module('ui.filters', ['ui.config']); 4 | angular.module('ui.directives', ['ui.config']); 5 | angular.module('ui', ['ui.filters', 'ui.directives', 'ui.config']); 6 | -------------------------------------------------------------------------------- /modules/directives/select2/dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": [ "jquery" ], 3 | "internal": [], 4 | "external": [ 5 | "http://ivaynberg.github.com/select2/select2-3.2/select2.js", 6 | "http://ivaynberg.github.com/select2/select2-3.2/select2.css", 7 | ] 8 | } -------------------------------------------------------------------------------- /modules/directives/tinymce/dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": [ "jquery" ], 3 | "internal": [], 4 | "external": [ 5 | "http://fiddle.tinymce.com/tinymce/3.5.8/tiny_mce_jquery_src.js", 6 | "http://fiddle.tinymce.com/tinymce/3.5.8/jquery.tinymce.js", 7 | ] 8 | } -------------------------------------------------------------------------------- /modules/directives/calendar/dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": [ "jquery" ], 3 | "internal": [], 4 | "external": [ 5 | "http://arshaw.com/js/fullcalendar-1.5.4/fullcalendar/fullcalendar.css", 6 | "http://arshaw.com/js/fullcalendar-1.5.4/fullcalendar/fullcalendar.js" 7 | ] 8 | } -------------------------------------------------------------------------------- /common/stylesheets/angular-ui.less: -------------------------------------------------------------------------------- 1 | /** 2 | * import components to builds angular-ui.css 3 | */ 4 | @import "mixins.less"; 5 | @import "modules/directives/reset/stylesheets/reset.less"; 6 | @import "modules/directives/currency/stylesheets/currency.less"; 7 | @import "modules/filters/highlight/highlight.less"; -------------------------------------------------------------------------------- /modules/directives/currency/stylesheets/currency.less: -------------------------------------------------------------------------------- 1 | 2 | /* ui-currency */ 3 | .ui-currency-pos { 4 | color: green; 5 | } 6 | .ui-currency-neg { 7 | color: red; 8 | } 9 | .ui-currency-zero { 10 | color: blue; 11 | } 12 | .ui-currency-pos.ui-bignum, .ui-currency-neg.ui-smallnum { 13 | font-size: 110%; 14 | } 15 | -------------------------------------------------------------------------------- /common/stylesheets/mixins.less: -------------------------------------------------------------------------------- 1 | .border-radius(@radius: 5px) { 2 | -webkit-border-radius: @radius; 3 | -moz-border-radius: @radius; 4 | border-radius: @radius; 5 | } 6 | 7 | .box-shadow(@shadow: 0 1px 3px rgba(0,0,0,.25)) { 8 | -webkit-box-shadow: @shadow; 9 | -moz-box-shadow: @shadow; 10 | box-shadow: @shadow; 11 | } -------------------------------------------------------------------------------- /templates/dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": [ "jquery", "jquery-ui", "bootstrap" ], 3 | "internal": [ "directives/showHide", "filters/highlight" ], 4 | "external": [ 5 | "http://ivaynberg.github.com/select2/select2-3.2/select2.js", 6 | "http://ivaynberg.github.com/select2/select2-3.2/select2.css", 7 | ] 8 | } -------------------------------------------------------------------------------- /modules/directives/reset/stylesheets/reset.less: -------------------------------------------------------------------------------- 1 | 2 | /* ui-reset */ 3 | .ui-resetwrap { 4 | display: inline-block; 5 | position: relative; 6 | } 7 | .ui-reset { 8 | display: none; 9 | position: absolute; 10 | cursor: pointer; 11 | top: 0; 12 | right: 0; 13 | z-index: 2; 14 | height: 100%; 15 | } 16 | .ui-resetwrap:hover .ui-reset { 17 | display: block; 18 | } 19 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "AngularUI Team", 3 | "name": "angular-ui", 4 | "description": "AngularUI - Companion Suite for AngularJS", 5 | "version": "0.4.1", 6 | "homepage": "http://angular-ui.github.com", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/angular-ui/angular-ui.git" 10 | }, 11 | "main": "./build/angular-ui.min.js", 12 | "dependencies": { 13 | "jquery": ">= 1.8.0", 14 | "angular": ">= 1.0.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "https://github.com/angular-ui/angular-ui/graphs/contributors", 3 | "name": "angular-ui", 4 | "description": "AngularUI - The companion suite for AngularJS", 5 | "version": "0.4.0", 6 | "homepage": "http://angular-ui.github.com", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/angular-ui/angular-ui.git" 10 | }, 11 | "engines": { 12 | "node": ">= 0.8.4" 13 | }, 14 | "dependencies": {}, 15 | "devDependencies": { 16 | "grunt-recess": "~0.1.3", 17 | "async": "0.1.x", 18 | "testacular": "~0.5.x" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/template.js: -------------------------------------------------------------------------------- 1 | angular.module('ui.directives').directive('uiTemplate', ['ui.config', function (uiConfig) { 2 | var options = uiConfig.uiTemplate || {}; 3 | return { 4 | restrict: 'EAC', // supports using directive as element, attribute and class 5 | link: function (iScope, iElement, iAttrs, controller) { 6 | var opts; 7 | 8 | // opts is link element-specific options merged on top of global defaults. If you only extend the global default, then all instances would override each other 9 | opts = angular.extend({}, options, iAttrs.uiTemplate); 10 | 11 | // your logic goes here 12 | } 13 | }; 14 | }]); 15 | 16 | 17 | angular.module('ui.filters').filter('filterTmpl', ['ui.config', function (uiConfig) { 18 | return function (value) { 19 | return value; 20 | }; 21 | }]); -------------------------------------------------------------------------------- /modules/filters/highlight/highlight.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wraps the 3 | * @param text {string} haystack to search through 4 | * @param search {string} needle to search for 5 | * @param [caseSensitive] {boolean} optional boolean to use case-sensitive searching 6 | */ 7 | angular.module('ui.filters').filter('highlight', function () { 8 | return function (text, search, caseSensitive) { 9 | if (search || angular.isNumber(search)) { 10 | text = text.toString(); 11 | search = search.toString(); 12 | if (caseSensitive) { 13 | return text.split(search).join('' + search + ''); 14 | } else { 15 | return text.replace(new RegExp(search, 'gi'), '$&'); 16 | } 17 | } else { 18 | return text; 19 | } 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /modules/directives/reset/reset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Add a clear button to form inputs to reset their value 3 | */ 4 | angular.module('ui.directives').directive('uiReset', ['ui.config', function (uiConfig) { 5 | var resetValue = null; 6 | if (uiConfig.reset !== undefined) 7 | resetValue = uiConfig.reset; 8 | return { 9 | require: 'ngModel', 10 | link: function (scope, elm, attrs, ctrl) { 11 | var aElement; 12 | aElement = angular.element(''); 13 | elm.wrap('').after(aElement); 14 | aElement.bind('click', function (e) { 15 | e.preventDefault(); 16 | scope.$apply(function () { 17 | if (attrs.uiReset) 18 | ctrl.$setViewValue(scope.$eval(attrs.uiReset)); 19 | else 20 | ctrl.$setViewValue(resetValue); 21 | ctrl.$render(); 22 | }); 23 | }); 24 | } 25 | }; 26 | }]); 27 | -------------------------------------------------------------------------------- /modules/filters/format/test/formatSpec.js: -------------------------------------------------------------------------------- 1 | describe('format', function() { 2 | var formatFilter; 3 | 4 | beforeEach(module('ui.filters')); 5 | beforeEach(inject(function($filter) { 6 | formatFilter = $filter('format'); 7 | })); 8 | 9 | it('should replace all instances of $0 if string token is passed', function() { 10 | expect(formatFilter('First $0, then $0, finally $0', 'bob')).toEqual('First bob, then bob, finally bob'); 11 | }); 12 | it('should replace all instances of $n based on order of token array', function() { 13 | expect(formatFilter('First is $0, then $1, finally $2', ['bob','frank','dianne'])).toEqual('First is bob, then frank, finally dianne'); 14 | }); 15 | it('should replace all instances :tokens based on keys of token object', function() { 16 | expect(formatFilter('First is :first, next is :second, finally there is :third', {first:'bob',second:'frank',third:'dianne'})).toEqual('First is bob, next is frank, finally there is dianne'); 17 | }); 18 | it('should do nothing if tokens are undefined', function() { 19 | expect(formatFilter('Hello There')).toEqual('Hello There'); 20 | }); 21 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 the AngularUI Team, http://angular-ui.github.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /modules/directives/event/event.js: -------------------------------------------------------------------------------- 1 | /** 2 | * General-purpose Event binding. Bind any event not natively supported by Angular 3 | * Pass an object with keynames for events to ui-event 4 | * Allows $event object and $params object to be passed 5 | * 6 | * @example 7 | * @example 8 | * 9 | * @param ui-event {string|object literal} The event to bind to as a string or a hash of events with their callbacks 10 | */ 11 | angular.module('ui.directives').directive('uiEvent', ['$parse', 12 | function ($parse) { 13 | return function (scope, elm, attrs) { 14 | var events = scope.$eval(attrs.uiEvent); 15 | angular.forEach(events, function (uiEvent, eventName) { 16 | var fn = $parse(uiEvent); 17 | elm.bind(eventName, function (evt) { 18 | var params = Array.prototype.slice.call(arguments); 19 | //Take out first paramater (event object); 20 | params = params.splice(1); 21 | scope.$apply(function () { 22 | fn(scope, {$event: evt, $params: params}); 23 | }); 24 | }); 25 | }); 26 | }; 27 | }]); 28 | -------------------------------------------------------------------------------- /modules/directives/if/if.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Defines the ui-if tag. This removes/adds an element from the dom depending on a condition 3 | * Originally created by @tigbro, for the @jquery-mobile-angular-adapter 4 | * https://github.com/tigbro/jquery-mobile-angular-adapter 5 | */ 6 | angular.module('ui.directives').directive('uiIf', [function () { 7 | return { 8 | transclude: 'element', 9 | priority: 1000, 10 | terminal: true, 11 | restrict: 'A', 12 | compile: function (element, attr, transclude) { 13 | return function (scope, element, attr) { 14 | 15 | var childElement; 16 | var childScope; 17 | 18 | scope.$watch(attr['uiIf'], function (newValue) { 19 | if (childElement) { 20 | childElement.remove(); 21 | childElement = undefined; 22 | } 23 | if (childScope) { 24 | childScope.$destroy(); 25 | childScope = undefined; 26 | } 27 | 28 | if (newValue) { 29 | childScope = scope.$new(); 30 | transclude(childScope, function (clone) { 31 | childElement = clone; 32 | element.after(clone); 33 | }); 34 | } 35 | }); 36 | }; 37 | } 38 | }; 39 | }]); -------------------------------------------------------------------------------- /modules/directives/sortable/REDME.md: -------------------------------------------------------------------------------- 1 | # ui-sortable directive 2 | 3 | This directive allows you to sort array with drag & drop. 4 | 5 | ## Requirements 6 | 7 | - JQuery 8 | - JQueryUI 9 | 10 | ## Usage 11 | 12 | Load the script file: sortable.js in your application: 13 | 14 | ```html 15 | 16 | ``` 17 | 18 | Add the sortable module as a dependency to your application module: 19 | 20 | ```js 21 | var myAppModule = angular.module('MyApp', ['ui.directives.sortable']) 22 | ``` 23 | 24 | Apply the directive to your form elements: 25 | 26 | ```html 27 | 30 | ``` 31 | 32 | ### Options 33 | 34 | All the jQueryUI Sortable options can be passed through the directive. 35 | 36 | 37 | ```js 38 | myAppModule.controller('MyController', function($scope) { 39 | $scope.items = ["One", "Two", "Three"]; 40 | 41 | $scope.sortableOptions = { 42 | update: function(e, ui) { ... }, 43 | axis: 'x' 44 | }; 45 | }); 46 | ``` 47 | 48 | ```html 49 | 52 | ``` 53 | 54 | 55 | -------------------------------------------------------------------------------- /modules/filters/format/format.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * A replacement utility for internationalization very similar to sprintf. 4 | * 5 | * @param replace {mixed} The tokens to replace depends on type 6 | * string: all instances of $0 will be replaced 7 | * array: each instance of $0, $1, $2 etc. will be placed with each array item in corresponding order 8 | * object: all attributes will be iterated through, with :key being replaced with its corresponding value 9 | * @return string 10 | * 11 | * @example: 'Hello :name, how are you :day'.format({ name:'John', day:'Today' }) 12 | * @example: 'Records $0 to $1 out of $2 total'.format(['10', '20', '3000']) 13 | * @example: '$0 agrees to all mentions $0 makes in the event that $0 hits a tree while $0 is driving drunk'.format('Bob') 14 | */ 15 | angular.module('ui.filters').filter('format', function(){ 16 | return function(value, replace) { 17 | if (!value) { 18 | return value; 19 | } 20 | var target = value.toString(), token; 21 | if (replace === undefined) { 22 | return target; 23 | } 24 | if (!angular.isArray(replace) && !angular.isObject(replace)) { 25 | return target.split('$0').join(replace); 26 | } 27 | token = angular.isArray(replace) && '$' || ':'; 28 | 29 | angular.forEach(replace, function(value, key){ 30 | target = target.split(token+key).join(value); 31 | }); 32 | return target; 33 | }; 34 | }); 35 | -------------------------------------------------------------------------------- /modules/directives/currency/currency.js: -------------------------------------------------------------------------------- 1 | /* 2 | Gives the ability to style currency based on its sign. 3 | */ 4 | angular.module('ui.directives').directive('uiCurrency', ['ui.config', 'currencyFilter' , function (uiConfig, currencyFilter) { 5 | var options = { 6 | pos: 'ui-currency-pos', 7 | neg: 'ui-currency-neg', 8 | zero: 'ui-currency-zero' 9 | }; 10 | if (uiConfig.currency) { 11 | angular.extend(options, uiConfig.currency); 12 | } 13 | return { 14 | restrict: 'EAC', 15 | require: 'ngModel', 16 | link: function (scope, element, attrs, controller) { 17 | var opts, // instance-specific options 18 | renderview, 19 | value; 20 | 21 | opts = angular.extend({}, options, scope.$eval(attrs.uiCurrency)); 22 | 23 | renderview = function (viewvalue) { 24 | var num; 25 | num = viewvalue * 1; 26 | element.toggleClass(opts.pos, (num > 0) ); 27 | element.toggleClass(opts.neg, (num < 0) ); 28 | element.toggleClass(opts.zero, (num === 0) ); 29 | if (viewvalue === '') { 30 | element.text(''); 31 | } else { 32 | element.text(currencyFilter(num, opts.symbol)); 33 | } 34 | return true; 35 | }; 36 | 37 | controller.$render = function () { 38 | value = controller.$viewValue; 39 | element.val(value); 40 | renderview(value); 41 | }; 42 | 43 | } 44 | }; 45 | }]); 46 | -------------------------------------------------------------------------------- /modules/filters/unique/unique.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Filters out all duplicate items from an array by checking the specified key 3 | * @param [key] {string} the name of the attribute of each object to compare for uniqueness 4 | if the key is empty, the entire object will be compared 5 | if the key === false then no filtering will be performed 6 | * @return {array} 7 | */ 8 | angular.module('ui.filters').filter('unique', function () { 9 | 10 | return function (items, filterOn) { 11 | 12 | if (filterOn === false) { 13 | return items; 14 | } 15 | 16 | if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) { 17 | var hashCheck = {}, newItems = []; 18 | 19 | var extractValueToCompare = function (item) { 20 | if (angular.isObject(item) && angular.isString(filterOn)) { 21 | return item[filterOn]; 22 | } else { 23 | return item; 24 | } 25 | }; 26 | 27 | angular.forEach(items, function (item) { 28 | var valueToCheck, isDuplicate = false; 29 | 30 | for (var i = 0; i < newItems.length; i++) { 31 | if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) { 32 | isDuplicate = true; 33 | break; 34 | } 35 | } 36 | if (!isDuplicate) { 37 | newItems.push(item); 38 | } 39 | 40 | }); 41 | items = newItems; 42 | } 43 | return items; 44 | }; 45 | }); 46 | -------------------------------------------------------------------------------- /modules/directives/sortable/test/sortableSpec.js: -------------------------------------------------------------------------------- 1 | describe('uiSortable', function() { 2 | 3 | // Ensure the sortable angular module is loaded 4 | beforeEach(module('ui.directives')); 5 | 6 | describe('simple use', function() { 7 | 8 | it('should have a ui-sortable class', function() { 9 | inject(function($compile, $rootScope) { 10 | var element; 11 | element = $compile("")($rootScope); 12 | expect(element.hasClass("ui-sortable")).toBeTruthy(); 13 | }); 14 | }); 15 | 16 | it('should update model when order changes', function() { 17 | inject(function($compile, $rootScope) { 18 | var element; 19 | element = $compile('')($rootScope); 20 | $rootScope.$apply(function() { 21 | return $rootScope.items = ["One", "Two", "Three"]; 22 | }); 23 | 24 | element.find('li:eq(1)').insertAfter(element.find('li:eq(2)')); 25 | 26 | // None of this work, one way is to use .bind("sortupdate") 27 | // and then use .trigger("sortupdate", e, ui) but I have no idea how to 28 | // construct ui object 29 | 30 | // element.sortable('refresh') 31 | // element.sortable('refreshPositions') 32 | // element.trigger('sortupdate') 33 | 34 | // expect($rootScope.items).toEqual(["One", "Three", "Two"]) 35 | }); 36 | }); 37 | 38 | }); 39 | 40 | }); -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | # ui-template directives 2 | 3 | #### NOTE: This template is going to be replaced in very near future ### 4 | 5 | These directives are boilerplates for creating your own directives. 6 | 7 | ## Usage 8 | 9 | Add the template module as a dependency to your application module: 10 | 11 | var myAppModule = angular.module('MyApp', ['ui.directives.template']) 12 | 13 | Apply the directive to your html elements: 14 | 15 | 16 | 17 | Default styles are in angular-ui.css and are pretty boring, you could just override these in your 18 | stylesheet and make things more interesting 19 | 20 | ### Options 21 | 22 | All the options can be passed through the directive or set on the html element. 23 | NOTE: attributes override controller options 24 | 25 | myAppModule.controller('MyController', function($scope) { 26 | $scope.SomeNumber = 123; 27 | $scope.uiTemplateOptions = { 28 | 29 | }; 30 | }); 31 | 32 | // two-way binding with default for scoped.options uiTemplateOptions 33 | 34 | 35 | // one way binding with your own name for scoped options 36 | 37 | 38 | 39 | ### Notes 40 | 41 | ui-template 42 | - one-way binding unless you have in an ng-repeat 43 | - does not currently work with ng-model. 44 | - is supported only for attribute style elements 45 | 46 | ### Todo 47 | - support ng-model 48 | -------------------------------------------------------------------------------- /modules/directives/animate/animate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Animates the injection of new DOM elements by simply creating the DOM with a class and then immediately removing it 3 | * Animations must be done using CSS3 transitions, but provide excellent flexibility 4 | * 5 | * @todo Add proper support for animating out 6 | * @param [options] {mixed} Can be an object with multiple options, or a string with the animation class 7 | * class {string} the CSS class(es) to use. For example, 'ui-hide' might be an excellent alternative class. 8 | * @example
  • {{item}}
  • 9 | */ 10 | angular.module('ui.directives').directive('uiAnimate', ['ui.config', '$timeout', function (uiConfig, $timeout) { 11 | var options = {}; 12 | if (angular.isString(uiConfig.animate)) { 13 | options['class'] = uiConfig.animate; 14 | } else if (uiConfig.animate) { 15 | options = uiConfig.animate; 16 | } 17 | return { 18 | restrict: 'A', // supports using directive as element, attribute and class 19 | link: function ($scope, element, attrs) { 20 | var opts = {}; 21 | if (attrs.uiAnimate) { 22 | opts = $scope.$eval(attrs.uiAnimate); 23 | if (angular.isString(opts)) { 24 | opts = {'class': opts}; 25 | } 26 | } 27 | opts = angular.extend({'class': 'ui-animate'}, options, opts); 28 | 29 | element.addClass(opts['class']); 30 | $timeout(function () { 31 | element.removeClass(opts['class']); 32 | }, 20, false); 33 | } 34 | }; 35 | }]); 36 | 37 | -------------------------------------------------------------------------------- /modules/filters/inflector/test/inflectorSpec.js: -------------------------------------------------------------------------------- 1 | describe('inflector', function () { 2 | var inflectorFilter, testPhrase = 'here isMy_phone_number'; 3 | 4 | beforeEach(module('ui.filters')); 5 | beforeEach(inject(function ($filter) { 6 | inflectorFilter = $filter('inflector'); 7 | })); 8 | 9 | describe('default', function () { 10 | it('should default to humanize', function () { 11 | expect(inflectorFilter(testPhrase)).toEqual('Here Is My Phone Number'); 12 | }); 13 | it('should fail gracefully for invalid input', function () { 14 | expect(inflectorFilter(undefined)).toBeUndefined(); 15 | }); 16 | it('should do nothing for empty input', function () { 17 | expect(inflectorFilter('')).toEqual(''); 18 | }); 19 | }); 20 | 21 | describe('humanize', function () { 22 | it('should uppercase first letter and separate words with a space', function () { 23 | expect(inflectorFilter(testPhrase, 'humanize')).toEqual('Here Is My Phone Number'); 24 | }); 25 | }); 26 | describe('underscore', function () { 27 | it('should lowercase everything and separate words with an underscore', function () { 28 | expect(inflectorFilter(testPhrase, 'underscore')).toEqual('here_is_my_phone_number'); 29 | }); 30 | }); 31 | describe('variable', function () { 32 | it('should remove all separators and camelHump the phrase', function () { 33 | expect(inflectorFilter(testPhrase, 'variable')).toEqual('hereIsMyPhoneNumber'); 34 | }); 35 | it('should do nothing if already formatted properly', function () { 36 | expect(inflectorFilter("hereIsMyPhoneNumber", 'variable')).toEqual('hereIsMyPhoneNumber'); 37 | }); 38 | }); 39 | }); -------------------------------------------------------------------------------- /modules/filters/inflector/inflector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts variable-esque naming conventions to something presentational, capitalized words separated by space. 3 | * @param {String} value The value to be parsed and prettified. 4 | * @param {String} [inflector] The inflector to use. Default: humanize. 5 | * @return {String} 6 | * @example {{ 'Here Is my_phoneNumber' | inflector:'humanize' }} => Here Is My Phone Number 7 | * {{ 'Here Is my_phoneNumber' | inflector:'underscore' }} => here_is_my_phone_number 8 | * {{ 'Here Is my_phoneNumber' | inflector:'variable' }} => hereIsMyPhoneNumber 9 | */ 10 | angular.module('ui.filters').filter('inflector', function () { 11 | function ucwords(text) { 12 | return text.replace(/^([a-z])|\s+([a-z])/g, function ($1) { 13 | return $1.toUpperCase(); 14 | }); 15 | } 16 | 17 | function breakup(text, separator) { 18 | return text.replace(/[A-Z]/g, function (match) { 19 | return separator + match; 20 | }); 21 | } 22 | 23 | var inflectors = { 24 | humanize: function (value) { 25 | return ucwords(breakup(value, ' ').split('_').join(' ')); 26 | }, 27 | underscore: function (value) { 28 | return value.substr(0, 1).toLowerCase() + breakup(value.substr(1), '_').toLowerCase().split(' ').join('_'); 29 | }, 30 | variable: function (value) { 31 | value = value.substr(0, 1).toLowerCase() + ucwords(value.split('_').join(' ')).substr(1).split(' ').join(''); 32 | return value; 33 | } 34 | }; 35 | 36 | return function (text, inflector, separator) { 37 | if (inflector !== false && angular.isString(text)) { 38 | inflector = inflector || 'humanize'; 39 | return inflectors[inflector](text); 40 | } else { 41 | return text; 42 | } 43 | }; 44 | }); 45 | -------------------------------------------------------------------------------- /modules/directives/scrollfix/scrollfix.js: -------------------------------------------------------------------------------- 1 | /*global angular, $, document*/ 2 | /** 3 | * Adds a 'ui-scrollfix' class to the element when the page scrolls past it's position. 4 | * @param [offset] {int} optional Y-offset to override the detected offset. 5 | * Takes 300 (absolute) or -300 or +300 (relative to detected) 6 | */ 7 | angular.module('ui.directives').directive('uiScrollfix', ['$window', function ($window) { 8 | 'use strict'; 9 | return { 10 | link: function (scope, elm, attrs) { 11 | var top = elm.offset().top; 12 | if (!attrs.uiScrollfix) { 13 | attrs.uiScrollfix = top; 14 | } else { 15 | // chartAt is generally faster than indexOf: http://jsperf.com/indexof-vs-chartat 16 | if (attrs.uiScrollfix.charAt(0) === '-') { 17 | attrs.uiScrollfix = top - attrs.uiScrollfix.substr(1); 18 | } else if (attrs.uiScrollfix.charAt(0) === '+') { 19 | attrs.uiScrollfix = top + parseFloat(attrs.uiScrollfix.substr(1)); 20 | } 21 | } 22 | angular.element($window).on('scroll.ui-scrollfix', function () { 23 | // if pageYOffset is defined use it, otherwise use other crap for IE 24 | var offset; 25 | if (angular.isDefined($window.pageYOffset)) { 26 | offset = $window.pageYOffset; 27 | } else { 28 | var iebody = (document.compatMode && document.compatMode !== "BackCompat") ? document.documentElement : document.body; 29 | offset = iebody.scrollTop; 30 | } 31 | if (!elm.hasClass('ui-scrollfix') && offset > attrs.uiScrollfix) { 32 | elm.addClass('ui-scrollfix'); 33 | } else if (elm.hasClass('ui-scrollfix') && offset < attrs.uiScrollfix) { 34 | elm.removeClass('ui-scrollfix'); 35 | } 36 | }); 37 | } 38 | }; 39 | }]); 40 | -------------------------------------------------------------------------------- /modules/directives/tinymce/tinymce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Binds a TinyMCE widget to ')(scope); 26 | }); 27 | waits(1); 28 | } 29 | 30 | describe('compiling this directive', function () { 31 | 32 | it('should include the passed options', function () { 33 | spyOn($.fn, 'tinymce'); 34 | compile(); 35 | runs(function () { 36 | expect($.fn.tinymce).toHaveBeenCalled(); 37 | expect($.fn.tinymce.mostRecentCall.args[0].foo).toEqual('bar'); 38 | }); 39 | }); 40 | 41 | it('should include the default options', function () { 42 | spyOn($.fn, 'tinymce'); 43 | compile(); 44 | runs(function () { 45 | expect($.fn.tinymce).toHaveBeenCalled(); 46 | expect($.fn.tinymce.mostRecentCall.args[0].bar).toEqual('baz'); 47 | }); 48 | }); 49 | }); 50 | /* 51 | describe('setting a value to the model', function () { 52 | it('should update the editor', function() { 53 | compile(); 54 | runs(function () { 55 | scope.$apply(function() { 56 | scope.foo = text; 57 | }); 58 | expect(element.find('textarea').tinymce().getContent()).toEqual(text); 59 | }); 60 | }); 61 | it('should handle undefined gracefully', function() { 62 | compile(); 63 | runs(function () { 64 | scope.$apply(function() { 65 | scope.foo = undefined; 66 | }); 67 | expect(element.find('textarea').tinymce().getContent()).toEqual(''); 68 | }); 69 | }); 70 | it('should handle null gracefully', function() { 71 | compile(); 72 | runs(function () { 73 | scope.$apply(function() { 74 | scope.foo = null; 75 | }); 76 | expect(element.find('textarea').tinymce().getContent()).toEqual(''); 77 | }); 78 | }); 79 | }); 80 | describe('using the editor', function () { 81 | it('should update the model', function() { 82 | compile(); 83 | runs(function () { 84 | element.find('textarea').tinymce().setContent(text); 85 | expect($rootScope.x).toEqual(text); 86 | }); 87 | }); 88 | }); 89 | */ 90 | }); -------------------------------------------------------------------------------- /modules/directives/calendar/calendar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * AngularJs Fullcalendar Wrapper for the JQuery FullCalendar 3 | * API @ http://arshaw.com/fullcalendar/ 4 | * 5 | * Angular Calendar Directive that takes in the [eventSources] nested array object as the ng-model and watches (eventSources.length + eventSources[i].length) for changes. 6 | * Can also take in multiple event urls as a source object(s) and feed the events per view. 7 | * The calendar will watch any eventSource array and update itself when a delta is created 8 | * An equalsTracker attrs has been added for use cases that would render the overall length tracker the same even though the events have changed to force updates. 9 | * 10 | */ 11 | 12 | angular.module('ui.directives').directive('uiCalendar',['ui.config', '$parse', function (uiConfig,$parse) { 13 | uiConfig.uiCalendar = uiConfig.uiCalendar || {}; 14 | //returns calendar 15 | return { 16 | require: 'ngModel', 17 | restrict: 'A', 18 | link: function(scope, elm, attrs, $timeout) { 19 | var sources = scope.$eval(attrs.ngModel); 20 | var tracker = 0; 21 | /* returns the length of all source arrays plus the length of eventSource itself */ 22 | var getSources = function () { 23 | var equalsTracker = scope.$eval(attrs.equalsTracker); 24 | tracker = 0; 25 | angular.forEach(sources,function(value,key){ 26 | if(angular.isArray(value)){ 27 | tracker += value.length; 28 | } 29 | }); 30 | if(angular.isNumber(equalsTracker)){ 31 | return tracker + sources.length + equalsTracker; 32 | }else{ 33 | return tracker + sources.length; 34 | } 35 | }; 36 | /* update the calendar with the correct options */ 37 | function update() { 38 | //calendar object exposed on scope 39 | scope.calendar = elm.html(''); 40 | var view = scope.calendar.fullCalendar('getView'); 41 | if(view){ 42 | view = view.name; //setting the default view to be whatever the current view is. This can be overwritten. 43 | } 44 | /* If the calendar has options added then render them */ 45 | var expression, 46 | options = { 47 | defaultView : view, 48 | eventSources: sources 49 | }; 50 | if (attrs.uiCalendar) { 51 | expression = scope.$eval(attrs.uiCalendar); 52 | } else { 53 | expression = {}; 54 | } 55 | angular.extend(options, uiConfig.uiCalendar, expression); 56 | scope.calendar.fullCalendar(options); 57 | } 58 | update(); 59 | /* watches all eventSources */ 60 | scope.$watch(getSources, function( newVal, oldVal ) 61 | { 62 | update(); 63 | }); 64 | } 65 | }; 66 | }]); -------------------------------------------------------------------------------- /test/lib/tinymce/jquery.tinymce.js: -------------------------------------------------------------------------------- 1 | (function(c){var b,e,a=[],d=window;c.fn.tinymce=function(j){var p=this,g,k,h,m,i,l="",n="";if(!p.length){return p}if(!j){return tinyMCE.get(p[0].id)}p.css("visibility","hidden");function o(){var r=[],q=0;if(f){f();f=null}p.each(function(t,u){var s,w=u.id,v=j.oninit;if(!w){u.id=w=tinymce.DOM.uniqueId()}s=new tinymce.Editor(w,j);r.push(s);s.onInit.add(function(){var x,y=v;p.css("visibility","");if(v){if(++q==r.length){if(tinymce.is(y,"string")){x=(y.indexOf(".")===-1)?null:tinymce.resolve(y.replace(/\.\w+$/,""));y=tinymce.resolve(y)}y.apply(x||tinymce,r)}}})});c.each(r,function(t,s){s.render()})}if(!d.tinymce&&!e&&(g=j.script_url)){e=1;h=g.substring(0,g.lastIndexOf("/"));if(/_(src|dev)\.js/g.test(g)){n="_src"}m=g.lastIndexOf("?");if(m!=-1){l=g.substring(m+1)}d.tinyMCEPreInit=d.tinyMCEPreInit||{base:h,suffix:n,query:l};if(g.indexOf("gzip")!=-1){i=j.language||"en";g=g+(/\?/.test(g)?"&":"?")+"js=true&core=true&suffix="+escape(n)+"&themes="+escape(j.theme)+"&plugins="+escape(j.plugins)+"&languages="+i;if(!d.tinyMCE_GZ){tinyMCE_GZ={start:function(){tinymce.suffix=n;function q(r){tinymce.ScriptLoader.markDone(tinyMCE.baseURI.toAbsolute(r))}q("langs/"+i+".js");q("themes/"+j.theme+"/editor_template"+n+".js");q("themes/"+j.theme+"/langs/"+i+".js");c.each(j.plugins.split(","),function(s,r){if(r){q("plugins/"+r+"/editor_plugin"+n+".js");q("plugins/"+r+"/langs/"+i+".js")}})},end:function(){}}}}c.ajax({type:"GET",url:g,dataType:"script",cache:true,success:function(){tinymce.dom.Event.domLoaded=1;e=2;if(j.script_loaded){j.script_loaded()}o();c.each(a,function(q,r){r()})}})}else{if(e===1){a.push(o)}else{o()}}return p};c.extend(c.expr[":"],{tinymce:function(g){return !!(g.id&&"tinyMCE" in window&&tinyMCE.get(g.id))}});function f(){function i(l){if(l==="remove"){this.each(function(n,o){var m=h(o);if(m){m.remove()}})}this.find("span.mceEditor,div.mceEditor").each(function(n,o){var m=tinyMCE.get(o.id.replace(/_parent$/,""));if(m){m.remove()}})}function k(n){var m=this,l;if(n!==b){i.call(m);m.each(function(p,q){var o;if(o=tinyMCE.get(q.id)){o.setContent(n)}})}else{if(m.length>0){if(l=tinyMCE.get(m[0].id)){return l.getContent()}}}}function h(m){var l=null;(m)&&(m.id)&&(d.tinymce)&&(l=tinyMCE.get(m.id));return l}function g(l){return !!((l)&&(l.length)&&(d.tinymce)&&(l.is(":tinymce")))}var j={};c.each(["text","html","val"],function(n,l){var o=j[l]=c.fn[l],m=(l==="text");c.fn[l]=function(s){var p=this;if(!g(p)){return o.apply(p,arguments)}if(s!==b){k.call(p.filter(":tinymce"),s);o.apply(p.not(":tinymce"),arguments);return p}else{var r="";var q=arguments;(m?p:p.eq(0)).each(function(u,v){var t=h(v);r+=t?(m?t.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):t.getContent({save:true})):o.apply(c(v),q)});return r}}});c.each(["append","prepend"],function(n,m){var o=j[m]=c.fn[m],l=(m==="prepend");c.fn[m]=function(q){var p=this;if(!g(p)){return o.apply(p,arguments)}if(q!==b){p.filter(":tinymce").each(function(s,t){var r=h(t);r&&r.setContent(l?q+r.getContent():r.getContent()+q)});o.apply(p.not(":tinymce"),arguments);return p}}});c.each(["remove","replaceWith","replaceAll","empty"],function(m,l){var n=j[l]=c.fn[l];c.fn[l]=function(){i.call(this,l);return n.apply(this,arguments)}});j.attr=c.fn.attr;c.fn.attr=function(o,q){var m=this,n=arguments;if((!o)||(o!=="value")||(!g(m))){if(q!==b){return j.attr.apply(m,n)}else{return j.attr.apply(m,n)}}if(q!==b){k.call(m.filter(":tinymce"),q);j.attr.apply(m.not(":tinymce"),n);return m}else{var p=m[0],l=h(p);return l?l.getContent({save:true}):j.attr.apply(c(p),n)}}}})(jQuery); -------------------------------------------------------------------------------- /modules/directives/route/test/routeSpec.js: -------------------------------------------------------------------------------- 1 | /*global describe, beforeEach, module, inject, it, spyOn, expect, $ */ 2 | describe('uiRoute', function () { 3 | 'use strict'; 4 | 5 | var scope, $compile, $location; 6 | beforeEach(module('ui.directives')); 7 | beforeEach(inject(function (_$rootScope_, _$compile_, _$window_, _$location_) { 8 | scope = _$rootScope_.$new(); 9 | $compile = _$compile_; 10 | $location = _$location_; 11 | })); 12 | 13 | function setPath(path) { 14 | $location.path(path); 15 | scope.$broadcast('$routeChangeSuccess'); 16 | scope.$apply(); 17 | } 18 | 19 | describe('model is null', function() { 20 | runTests(); 21 | }); 22 | describe('model is set', function() { 23 | runTests('pizza'); 24 | }); 25 | 26 | function runTests(routeModel) { 27 | var modelProp = routeModel || '$uiRoute', elm = angular.noop; 28 | function compileRoute(template) { 29 | elm = $(template); 30 | if (routeModel) elm.attr('ng-model', routeModel); 31 | return $compile(elm[0])(scope); 32 | } 33 | 34 | describe('with uiRoute defined', function(){ 35 | it('should use the uiRoute property', function(){ 36 | compileRoute('
    '); 37 | }); 38 | it('should update model on $observe', function(){ 39 | setPath('/bar'); 40 | scope.$apply('foobar = "foo"'); 41 | compileRoute('
    '); 42 | expect(elm.scope()[modelProp]).toBeFalsy(); 43 | scope.$apply('foobar = "bar"'); 44 | expect(elm.scope()[modelProp]).toBe(true); 45 | scope.$apply('foobar = "foo"'); 46 | expect(elm.scope()[modelProp]).toBe(false); 47 | }); 48 | it('should support regular expression', function(){ 49 | setPath('/foo/123'); 50 | compileRoute('
    '); 51 | expect(elm.scope()[modelProp]).toBe(true); 52 | }); 53 | }); 54 | 55 | describe('with ngHref defined', function(){ 56 | 57 | it('should use the ngHref property', function(){ 58 | setPath('/foo'); 59 | compileRoute(''); 60 | expect(elm.scope()[modelProp]).toBe(true); 61 | }); 62 | it('should update model on $observe', function(){ 63 | setPath('/bar'); 64 | scope.$apply('foobar = "foo"'); 65 | compileRoute(''); 66 | expect(elm.scope()[modelProp]).toBeFalsy(); 67 | scope.$apply('foobar = "bar"'); 68 | expect(elm.scope()[modelProp]).toBe(true); 69 | scope.$apply('foobar = "foo"'); 70 | expect(elm.scope()[modelProp]).toBe(false); 71 | }); 72 | }); 73 | 74 | describe('with href defined', function(){ 75 | 76 | it('should use the href property', function(){ 77 | setPath('/foo'); 78 | compileRoute(''); 79 | expect(elm.scope()[modelProp]).toBe(true); 80 | }); 81 | }); 82 | 83 | it('should throw an error if no route property available', function(){ 84 | expect(function(){ 85 | compileRoute('
    '); 86 | }).toThrow(); 87 | }); 88 | 89 | it('should update model on route change', function(){ 90 | setPath('/bar'); 91 | compileRoute('
    '); 92 | expect(elm.scope()[modelProp]).toBeFalsy(); 93 | setPath('/foo'); 94 | expect(elm.scope()[modelProp]).toBe(true); 95 | setPath('/bar'); 96 | expect(elm.scope()[modelProp]).toBe(false); 97 | }); 98 | } 99 | }); 100 | -------------------------------------------------------------------------------- /modules/directives/map/test/mapSpec.js: -------------------------------------------------------------------------------- 1 | xdescribe('uiMap', function () { 2 | var scope, $rootScope, $compile; 3 | 4 | beforeEach(module('ui.directives')); 5 | beforeEach(inject(function (_$compile_, _$rootScope_) { 6 | $rootScope = _$rootScope_; 7 | $compile = _$compile_; 8 | })); 9 | 10 | function createMap(options, events) { 11 | scope.gmapOptions = options || {}; 12 | scope.gmapEvents = events || {}; 13 | $compile("
    ")(scope); 15 | } 16 | 17 | function createWindow(options, events, inner) { 18 | scope.gOptions = options || {}; 19 | scope.gEvents = events || {}; 20 | inner = inner || angular.element(''); 21 | var elm = angular.element("
    "); 23 | elm.append(inner); 24 | $compile(elm)(scope); 25 | } 26 | 27 | describe('test', function () { 28 | beforeEach(function () { 29 | scope = $rootScope.$new(); 30 | }); 31 | 32 | it('should bind google map object to scope', function () { 33 | createMap(); 34 | expect(scope.gmap).toBeTruthy(); 35 | }); 36 | 37 | it('should create google map with given options', function () { 38 | var center = new google.maps.LatLng(40, 40); 39 | createMap({center: center}); 40 | expect(scope.gmap.getCenter()).toBe(center); 41 | }); 42 | 43 | it('should pass events to the element as "map-eventname"', function () { 44 | scope.zoomy = false; 45 | scope.county = 0; 46 | createMap({}, { 47 | 'map-zoom_changed': 'zoomy = true', 48 | 'map-dblclick map-dragend': 'county = county + 1' 49 | }); 50 | google.maps.event.trigger(scope.gmap, 'zoom_changed'); 51 | expect(scope.zoomy).toBeTruthy(); 52 | google.maps.event.trigger(scope.gmap, 'dblclick'); 53 | expect(scope.county).toBe(1); 54 | google.maps.event.trigger(scope.gmap, 'dragend'); 55 | expect(scope.county).toBe(2); 56 | }); 57 | }); 58 | 59 | describe('test infoWindow', function () { 60 | beforeEach(function () { 61 | scope = $rootScope.$new(); 62 | }); 63 | 64 | it('should bind info window to scope', function () { 65 | createWindow(); 66 | expect(scope.ginfo).toBeTruthy(); 67 | }); 68 | 69 | it('should create info window with given options & content', function () { 70 | var content = $('

    Hi

    '); 71 | createWindow({ zIndex: 5 }, {}, content); 72 | expect(scope.ginfo.getZIndex()).toBe(5); 73 | expect(scope.ginfo.getContent().innerHTML) 74 | .toBe($('
    ').append(content).html()); 75 | }); 76 | 77 | it('should $compile content and recognize scope changes', function () { 78 | var inner = $(''); 79 | createWindow({}, {}, inner); 80 | createMap(); 81 | scope.$apply(function () { 82 | scope.myVal = 'initial'; 83 | }); 84 | scope.ginfo.open(scope.gmap, scope.gmap.getCenter()); 85 | expect(inner.val()).toBe('initial'); 86 | scope.$apply(function () { 87 | scope.myVal = 'final'; 88 | }); 89 | expect(inner.val()).toBe('final'); 90 | }); 91 | 92 | it('should recognize infowindow events in ui-event as "map-eventname"', function () { 93 | createWindow({}, { 94 | 'map-closeclick': 'closed = true' 95 | }); 96 | createMap(); 97 | google.maps.event.trigger(scope.ginfo, 'closeclick'); 98 | expect(scope.closed).toBe(true); 99 | }); 100 | }); 101 | 102 | }); -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [Cha Cha Cha Changes](http://www.youtube.com/watch?v=pl3vxEudif8&t=0m53s) 2 | 3 | ## Master 4 | 5 | ## v0.4.0 6 | * **Validate directive** has been upgraded 7 | * **API BREAKING CHANGE!** now takes expressions instead of function references 8 | * You must explicitly specify the $value variable, but you no longer need to create a function 9 | * **NEW FEATURE** uiValidateWatch allows you to re-fire a validation rule (or all rules) when a related model changes (confirm_password) 10 | * **CodeMirror directive** has been updated 11 | * Now works with v3.02 12 | * **NEW FEATURE** uiRefresh lets you specify an expression to watch for changes to refresh codemirror (useful for modals) 13 | * **Mask directive** has many new fixes 14 | * Fixes for **uiDate** 15 | * **DateFormat directive** can now be declared in **uiConfig** 16 | * **uiJq Passthru directive** has upgrades to support a wider variety of directives 17 | * Now fires asyncronously post-angular-rendering of the view (**uiDefer** option is now always true) 18 | * New **uiRefresh** lets you specify an expression to watch to re-fire the plugin (call $(elm).focus() when a modal opens) 19 | * **Select2 directive** now adds support for setting the selected item by specifying a simple ID 20 | * FINALLY have unit-tests for Select2! 21 | * **IEShiv** has been simplified and stripped of browser-sniffing code (just use conditional comments) 22 | * **Calendar directive** now performs better watching of events data 23 | * Added optional equalsTracker attr (increment to force update from scope) 24 | * **Sortable directive** now properly supports connectWith option 25 | * New **route directive** that sets a boolean based on a pattern match of the current route (useful for tabs/navigation) 26 | * Refactored **If directive** to be tidier 27 | * **API BREAKING CHANGE!** **Modal directive** has been completely removed (if you still need it, grab the files from v0.3.x) 28 | 29 | ## v0.3.0 30 | * New **format** filter 31 | * Lots of cleanup! Consistent indentation, linting 32 | * Custom builds via grunt (soon to be leveraged via builder) 33 | * uiDate now watches options 34 | * Rewrote ui-keypress (API is not backwards-compatible) 35 | * **ui-**keypress has been expanded into **ui-keyup**, **ui-keydown** and **ui-keypress** 36 | * The **ui-keypress** can now be used to `$event.preventDefault()` as expected 37 | * Multiple combinations are separated by spaces, while multi-key combos are separated by dashes: `'enter alt-space 13-shift':'whatever()'` 38 | * The string-notation (__a and be or c and d__) has been dropped completely 39 | * Can now pass (or globally define) the value uiReset resets to 40 | 41 | ## v0.2.0 42 | * Unit tests. Unit tests. Unit tests. 43 | * New **inflector** filter (previously named **prettifier**) 44 | * Added 2 alternative modes, now contains: humanize, underscore and variable 45 | * **Passthrough directive** (uiJq) now fixes common ngModel problems due to trigger(change). Can optionally be disabled 46 | * Removed **Length Filter** (you can instead do {{ ( myArray | filter: { gender:'m' } ).length }}) 47 | * Added **validate directive**, allows you to pass validation functions 48 | * **Sortable directive** 49 | * Fixed **unique filter** 50 | * **Highlight filter** has had bug fixes 51 | * **Event directive** has been refactored / improved 52 | * **Keypress directive** has been refactored / improved 53 | * New **if-directive** instead of **remove directive** (removed) 54 | * New **google maps directive** 55 | * New **animate directive** that transitions the injection of new DOM elements (transitioning the removal of DOM is still not supported yet) 56 | * Improvements to **scrollfix directive** 57 | 58 | ## v0.1.0 59 | * New folder structure 60 | * Too many to list 61 | -------------------------------------------------------------------------------- /modules/directives/currency/test/currencySpec.js: -------------------------------------------------------------------------------- 1 | describe('uiCurrency', function () { 2 | var scope; 3 | beforeEach(module('ui')); 4 | beforeEach(inject(function ($rootScope) { 5 | scope = $rootScope.$new(); 6 | })); 7 | describe('use on a div element with two-way binding', function () { 8 | it('should have ui-currency-pos style non-zero positive model number ', function () { 9 | inject(function ($compile) { 10 | var element; 11 | element = $compile("
    ")(scope); 12 | scope.$apply(function () { 13 | scope.aNum = 0.5123; 14 | }); 15 | expect(element.text()).toEqual('$0.51'); 16 | expect(element.hasClass('ui-currency-pos')).toBeTruthy(); 17 | expect(element.hasClass('ui-currency-neg')).toBeFalsy(); 18 | expect(element.hasClass('ui-currency-zero')).toBeFalsy(); 19 | }); 20 | }); 21 | it('should have ui-currency-neg style when negative model number', function () { 22 | inject(function ($compile) { 23 | var element; 24 | element = $compile("
    ")(scope); 25 | scope.$apply(function () { 26 | scope.aNum = -123; 27 | }); 28 | expect(element.text()).toEqual('($123.00)'); 29 | expect(element.hasClass('ui-currency-pos')).toBeFalsy(); 30 | expect(element.hasClass('ui-currency-neg')).toBeTruthy(); 31 | }); 32 | }); 33 | it('should have ui-currency-zero style when zero model number', function () { 34 | inject(function ($compile) { 35 | var element; 36 | element = $compile("
    ")(scope); 37 | scope.$apply(function () { 38 | scope.aNum = 0; 39 | }); 40 | expect(element.text()).toEqual('$0.00'); 41 | expect(element.hasClass('ui-currency-pos')).toBeFalsy(); 42 | expect(element.hasClass('ui-currency-neg')).toBeFalsy(); 43 | expect(element.hasClass('ui-currency-zero')).toBeTruthy(); 44 | }); 45 | }); 46 | it('should not have any ui-currency styles or a value at all when missing scope model value', function () { 47 | inject(function ($compile) { 48 | var element; 49 | element = $compile("
    ")(scope); 50 | expect(element.text()).toEqual(''); 51 | expect(element.hasClass('ui-currency-pos')).toBeFalsy(); 52 | expect(element.hasClass('ui-currency-neg')).toBeFalsy(); 53 | expect(element.hasClass('ui-currency-zero')).toBeFalsy(); 54 | }); 55 | }); 56 | it('should not have any ui-currency styles or a value at all when provided a non-numeric model value', function () { 57 | inject(function ($compile) { 58 | var element; 59 | element = $compile("
    ")(scope); 60 | scope.$apply(function () { 61 | scope.aBadNum = 'bad'; 62 | }); 63 | expect(element.text()).toEqual(''); 64 | expect(element.hasClass('ui-currency-pos')).toBeFalsy(); 65 | expect(element.hasClass('ui-currency-neg')).toBeFalsy(); 66 | expect(element.hasClass('ui-currency-zero')).toBeFalsy(); 67 | }); 68 | }); 69 | 70 | it('should have user-defined positive style when provided in uiCurrency attr', function () { 71 | inject(function ($compile) { 72 | var element; 73 | element = $compile("
    ")(scope); 74 | scope.$apply(function () { 75 | scope.aNum = 1; 76 | }); 77 | expect(element.hasClass('pstyle')).toBeTruthy(); 78 | }); 79 | }); 80 | // Presumption is if above works then no need to test other cases, given the coverage in previous describe 81 | }); 82 | describe('use on a tag element', function () { 83 | it('should have a defined element', function () { 84 | inject(function ($compile) { 85 | var element; 86 | element = $compile("")(scope); 87 | scope.$apply(function () { 88 | scope.aNum = 1; 89 | }); 90 | expect(element).toBeDefined(); 91 | expect(element.text()).toEqual('$1.00'); 92 | }); 93 | }); 94 | }); 95 | }); -------------------------------------------------------------------------------- /modules/directives/date/date.js: -------------------------------------------------------------------------------- 1 | /*global angular */ 2 | /* 3 | jQuery UI Datepicker plugin wrapper 4 | 5 | @note If ≤ IE8 make sure you have a polyfill for Date.toISOString() 6 | @param [ui-date] {object} Options to pass to $.fn.datepicker() merged onto ui.config 7 | */ 8 | 9 | angular.module('ui.directives') 10 | 11 | .directive('uiDate', ['ui.config', function (uiConfig) { 12 | 'use strict'; 13 | var options; 14 | options = {}; 15 | if (angular.isObject(uiConfig.date)) { 16 | angular.extend(options, uiConfig.date); 17 | } 18 | return { 19 | require:'?ngModel', 20 | link:function (scope, element, attrs, controller) { 21 | var getOptions = function () { 22 | return angular.extend({}, uiConfig.date, scope.$eval(attrs.uiDate)); 23 | }; 24 | var initDateWidget = function () { 25 | var opts = getOptions(); 26 | 27 | // If we have a controller (i.e. ngModelController) then wire it up 28 | if (controller) { 29 | var updateModel = function () { 30 | scope.$apply(function () { 31 | var date = element.datepicker("getDate"); 32 | element.datepicker("setDate", element.val()); 33 | controller.$setViewValue(date); 34 | element.blur(); 35 | }); 36 | }; 37 | if (opts.onSelect) { 38 | // Caller has specified onSelect, so call this as well as updating the model 39 | var userHandler = opts.onSelect; 40 | opts.onSelect = function (value, picker) { 41 | updateModel(); 42 | scope.$apply(function() { 43 | userHandler(value, picker); 44 | }); 45 | }; 46 | } else { 47 | // No onSelect already specified so just update the model 48 | opts.onSelect = updateModel; 49 | } 50 | // In case the user changes the text directly in the input box 51 | element.bind('change', updateModel); 52 | 53 | // Update the date picker when the model changes 54 | controller.$render = function () { 55 | var date = controller.$viewValue; 56 | if ( angular.isDefined(date) && date !== null && !angular.isDate(date) ) { 57 | throw new Error('ng-Model value must be a Date object - currently it is a ' + typeof date + ' - use ui-date-format to convert it from a string'); 58 | } 59 | element.datepicker("setDate", date); 60 | }; 61 | } 62 | // If we don't destroy the old one it doesn't update properly when the config changes 63 | element.datepicker('destroy'); 64 | // Create the new datepicker widget 65 | element.datepicker(opts); 66 | if ( controller ) { 67 | // Force a render to override whatever is in the input text box 68 | controller.$render(); 69 | } 70 | }; 71 | // Watch for changes to the directives options 72 | scope.$watch(getOptions, initDateWidget, true); 73 | } 74 | }; 75 | } 76 | ]) 77 | 78 | .directive('uiDateFormat', ['ui.config', function(uiConfig) { 79 | var directive = { 80 | require:'ngModel', 81 | link: function(scope, element, attrs, modelCtrl) { 82 | var dateFormat = attrs.uiDateFormat || uiConfig.dateFormat; 83 | if ( dateFormat ) { 84 | // Use the datepicker with the attribute value as the dateFormat string to convert to and from a string 85 | modelCtrl.$formatters.push(function(value) { 86 | if (angular.isString(value) ) { 87 | return $.datepicker.parseDate(dateFormat, value); 88 | } 89 | }); 90 | modelCtrl.$parsers.push(function(value){ 91 | if (value) { 92 | return $.datepicker.formatDate(dateFormat, value); 93 | } 94 | }); 95 | } else { 96 | // Default to ISO formatting 97 | modelCtrl.$formatters.push(function(value) { 98 | if (angular.isString(value) ) { 99 | return new Date(value); 100 | } 101 | }); 102 | modelCtrl.$parsers.push(function(value){ 103 | if (value) { 104 | return value.toISOString(); 105 | } 106 | }); 107 | } 108 | } 109 | }; 110 | return directive; 111 | }]); 112 | -------------------------------------------------------------------------------- /modules/directives/jq/test/jqSpec.js: -------------------------------------------------------------------------------- 1 | describe('uiJq', function () { 2 | var scope, compile, timeout; 3 | scope = null; 4 | beforeEach(module('ui.directives')); 5 | beforeEach(function () { 6 | jQuery.fn.foo = function () {}; 7 | module(function ($provide) { 8 | $provide.value('ui.config', { 9 | jq: {foo: {}} 10 | }); 11 | }); 12 | }); 13 | beforeEach(inject(function ($rootScope, $compile, $timeout) { 14 | scope = $rootScope.$new(); 15 | compile = $compile; 16 | timeout = $timeout; 17 | })); 18 | describe('function or plugin isn\'t found', function () { 19 | it('should throw an error', function () { 20 | expect(function () { 21 | compile("
    ")(scope); 22 | }).toThrow(); 23 | }); 24 | }); 25 | describe('calling a jQuery element function', function () { 26 | it('should just like, sort of work and junk', function () { 27 | spyOn(jQuery.fn, 'foo'); 28 | compile("
    ")(scope); 29 | timeout.flush(); 30 | expect(jQuery.fn.foo).toHaveBeenCalled(); 31 | }); 32 | it('should fire after the view has rendered', function() { 33 | var length; 34 | jQuery.fn.bar = function() { 35 | length = $(this).children().length; 36 | console.log(length); 37 | }; 38 | scope.$apply('items=[1, 2]'); 39 | compile("
    ")(scope); 40 | scope.$apply(); 41 | timeout.flush(); 42 | expect(length).toBe(2); 43 | }); 44 | }); 45 | describe('calling a jQuery element function with options', function() { 46 | it('should not copy options.pizza to global', function() { 47 | spyOn(jQuery.fn, 'foo'); 48 | compile('
    ')(scope); 49 | timeout.flush(); 50 | expect(jQuery.fn.foo.calls[0].args).toEqual([{pizza: true}]); 51 | expect(jQuery.fn.foo.calls[1].args).toEqual([{}]); 52 | }); 53 | }); 54 | describe('using ui-refresh', function() { 55 | it('should execute exactly once if the expression is never set', function() { 56 | spyOn(jQuery.fn, 'foo'); 57 | compile('
    ')(scope); 58 | timeout.flush(); 59 | expect(jQuery.fn.foo.callCount).toBe(1); 60 | }); 61 | it('should execute exactly once if the expression is set at initialization', function() { 62 | spyOn(jQuery.fn, 'foo'); 63 | scope.$apply('bar = true'); 64 | compile('
    ')(scope); 65 | timeout.flush(); 66 | expect(jQuery.fn.foo.callCount).toBe(1); 67 | }); 68 | it('should execute once for each time the expression changes', function() { 69 | spyOn(jQuery.fn, 'foo'); 70 | scope.$apply('bar = 1'); 71 | compile('
    ')(scope); 72 | timeout.flush(); 73 | expect(jQuery.fn.foo.callCount).toBe(1); 74 | scope.$apply('bar = bar+1'); 75 | timeout.flush(); 76 | expect(jQuery.fn.foo.callCount).toBe(2); 77 | scope.$apply('bar = bar+1'); 78 | timeout.flush(); 79 | expect(jQuery.fn.foo.callCount).toBe(3); 80 | }); 81 | }); 82 | describe('change events', function() { 83 | it('should trigger an `input` event', function() { 84 | var bar = false; 85 | var element = compile('')(scope); 86 | element.bind('input', function(){ 87 | bar = true; 88 | }); 89 | element.trigger('change'); 90 | expect(bar).toBe(true); 91 | }); 92 | it('should ignore controls without ngModel attribute', function() { 93 | var bar = false; 94 | var element = compile('')(scope); 95 | element.bind('input', function(){ 96 | bar = true; 97 | }); 98 | element.trigger('change'); 99 | expect(bar).toBe(false); 100 | }); 101 | it('should ignore non-form controls', function() { 102 | var bar = false; 103 | var element = compile('
    ')(scope); 104 | element.bind('input', function(){ 105 | bar = true; 106 | }); 107 | element.trigger('change'); 108 | expect(bar).toBe(false); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /modules/directives/sortable/sortable.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery UI Sortable plugin wrapper 3 | 4 | @param [ui-sortable] {object} Options to pass to $.fn.sortable() merged onto ui.config 5 | */ 6 | angular.module('ui.directives').directive('uiSortable', [ 7 | 'ui.config', function(uiConfig) { 8 | return { 9 | require: '?ngModel', 10 | link: function(scope, element, attrs, ngModel) { 11 | var onReceive, onRemove, onStart, onUpdate, opts; 12 | 13 | opts = angular.extend({}, uiConfig.sortable, scope.$eval(attrs.uiSortable)); 14 | 15 | if (ngModel) { 16 | 17 | ngModel.$render = function() { 18 | element.sortable( "refresh" ); 19 | }; 20 | 21 | onStart = function(e, ui) { 22 | // Save position of dragged item 23 | ui.item.sortable = { index: ui.item.index() }; 24 | }; 25 | 26 | onUpdate = function(e, ui) { 27 | // For some reason the reference to ngModel in stop() is wrong 28 | ui.item.sortable.resort = ngModel; 29 | }; 30 | 31 | onReceive = function(e, ui) { 32 | ui.item.sortable.relocate = true; 33 | // added item to array into correct position and set up flag 34 | ngModel.$modelValue.splice(ui.item.index(), 0, ui.item.sortable.moved); 35 | }; 36 | 37 | onRemove = function(e, ui) { 38 | // copy data into item 39 | if (ngModel.$modelValue.length === 1) { 40 | ui.item.sortable.moved = ngModel.$modelValue.splice(0, 1)[0]; 41 | } else { 42 | ui.item.sortable.moved = ngModel.$modelValue.splice(ui.item.sortable.index, 1)[0]; 43 | } 44 | }; 45 | 46 | onStop = function(e, ui) { 47 | // digest all prepared changes 48 | if (ui.item.sortable.resort && !ui.item.sortable.relocate) { 49 | 50 | // Fetch saved and current position of dropped element 51 | var end, start; 52 | start = ui.item.sortable.index; 53 | end = ui.item.index(); 54 | 55 | // Reorder array and apply change to scope 56 | ui.item.sortable.resort.$modelValue.splice(end, 0, ui.item.sortable.resort.$modelValue.splice(start, 1)[0]); 57 | 58 | } 59 | if (ui.item.sortable.resort || ui.item.sortable.relocate) { 60 | scope.$apply(); 61 | } 62 | }; 63 | 64 | // If user provided 'start' callback compose it with onStart function 65 | opts.start = (function(_start){ 66 | return function(e, ui) { 67 | onStart(e, ui); 68 | if (typeof _start === "function") 69 | _start(e, ui); 70 | } 71 | })(opts.start); 72 | 73 | // If user provided 'start' callback compose it with onStart function 74 | opts.stop = (function(_stop){ 75 | return function(e, ui) { 76 | onStop(e, ui); 77 | if (typeof _stop === "function") 78 | _stop(e, ui); 79 | } 80 | })(opts.stop); 81 | 82 | // If user provided 'update' callback compose it with onUpdate function 83 | opts.update = (function(_update){ 84 | return function(e, ui) { 85 | onUpdate(e, ui); 86 | if (typeof _update === "function") 87 | _update(e, ui); 88 | } 89 | })(opts.update); 90 | 91 | // If user provided 'receive' callback compose it with onReceive function 92 | opts.receive = (function(_receive){ 93 | return function(e, ui) { 94 | onReceive(e, ui); 95 | if (typeof _receive === "function") 96 | _receive(e, ui); 97 | } 98 | })(opts.receive); 99 | 100 | // If user provided 'remove' callback compose it with onRemove function 101 | opts.remove = (function(_remove){ 102 | return function(e, ui) { 103 | onRemove(e, ui); 104 | if (typeof _remove === "function") 105 | _remove(e, ui); 106 | }; 107 | })(opts.remove); 108 | } 109 | 110 | // Create sortable 111 | element.sortable(opts); 112 | } 113 | }; 114 | } 115 | ]); 116 | -------------------------------------------------------------------------------- /modules/directives/showhide/test/showhideSpec.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, describe, it, inject, expect, module, spyOn*/ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | describe('uiShow', function () { 7 | 8 | var scope, $compile; 9 | beforeEach(module('ui.directives')); 10 | beforeEach(inject(function (_$rootScope_, _$compile_) { 11 | scope = _$rootScope_.$new(); 12 | $compile = _$compile_; 13 | })); 14 | 15 | describe('linking the directive', function () { 16 | it('should call scope.$watch', function () { 17 | spyOn(scope, '$watch'); 18 | $compile('
    ')(scope); 19 | expect(scope.$watch).toHaveBeenCalled(); 20 | }); 21 | }); 22 | 23 | describe('executing the watcher', function () { 24 | it('should add the ui-show class if true', function () { 25 | var element = $compile('
    ')(scope); 26 | scope.foo = true; 27 | scope.$apply(); 28 | expect(element.hasClass('ui-show')).toBe(true); 29 | }); 30 | it('should remove the ui-show class if false', function () { 31 | var element = $compile('
    ')(scope); 32 | scope.foo = false; 33 | scope.$apply(); 34 | expect(element.hasClass('ui-show')).toBe(false); 35 | }); 36 | }); 37 | }); 38 | 39 | describe('uiHide', function () { 40 | 41 | var scope, $compile; 42 | beforeEach(module('ui.directives')); 43 | beforeEach(inject(function (_$rootScope_, _$compile_) { 44 | scope = _$rootScope_.$new(); 45 | $compile = _$compile_; 46 | })); 47 | 48 | describe('when the directive is linked', function () { 49 | it('should call scope.$watch', function () { 50 | spyOn(scope, '$watch'); 51 | $compile('
    ')(scope); 52 | expect(scope.$watch).toHaveBeenCalled(); 53 | }); 54 | }); 55 | 56 | describe('executing the watcher', function () { 57 | it('should add the ui-hide class if true', function () { 58 | var element = $compile('
    ')(scope); 59 | scope.foo = true; 60 | scope.$apply(); 61 | expect(element.hasClass('ui-hide')).toBe(true); 62 | }); 63 | it('should remove the ui-hide class if false', function () { 64 | var element = $compile('
    ')(scope); 65 | scope.foo = false; 66 | scope.$apply(); 67 | expect(element.hasClass('ui-hide')).toBe(false); 68 | }); 69 | }); 70 | }); 71 | 72 | describe('uiToggle', function () { 73 | 74 | var scope, $compile; 75 | beforeEach(module('ui.directives')); 76 | beforeEach(inject(function (_$rootScope_, _$compile_) { 77 | scope = _$rootScope_.$new(); 78 | $compile = _$compile_; 79 | })); 80 | 81 | describe('when the directive is linked', function () { 82 | it('should call scope.$watch', function () { 83 | spyOn(scope, '$watch'); 84 | $compile('
    ')(scope); 85 | expect(scope.$watch).toHaveBeenCalled(); 86 | }); 87 | }); 88 | 89 | describe('executing the watcher', function () { 90 | it('should remove the ui-hide class and add the ui-show class if true', function () { 91 | var element = $compile('
    ')(scope); 92 | scope.foo = true; 93 | scope.$apply(); 94 | expect(element.hasClass('ui-show') && !element.hasClass('ui-hide')).toBe(true); 95 | }); 96 | it('should remove the ui-hide class and add the ui-show class if false', function () { 97 | var element = $compile('
    ')(scope); 98 | scope.foo = false; 99 | scope.$apply(); 100 | expect(!element.hasClass('ui-show') && element.hasClass('ui-hide')).toBe(true); 101 | }); 102 | }); 103 | }); 104 | })(); 105 | -------------------------------------------------------------------------------- /modules/directives/keypress/keypress.js: -------------------------------------------------------------------------------- 1 | angular.module('ui.directives').factory('keypressHelper', ['$parse', function keypress($parse){ 2 | var keysByCode = { 3 | 8: 'backspace', 4 | 9: 'tab', 5 | 13: 'enter', 6 | 27: 'esc', 7 | 32: 'space', 8 | 33: 'pageup', 9 | 34: 'pagedown', 10 | 35: 'end', 11 | 36: 'home', 12 | 37: 'left', 13 | 38: 'up', 14 | 39: 'right', 15 | 40: 'down', 16 | 45: 'insert', 17 | 46: 'delete' 18 | }; 19 | 20 | var capitaliseFirstLetter = function (string) { 21 | return string.charAt(0).toUpperCase() + string.slice(1); 22 | }; 23 | 24 | return function(mode, scope, elm, attrs) { 25 | var params, combinations = []; 26 | params = scope.$eval(attrs['ui'+capitaliseFirstLetter(mode)]); 27 | 28 | // Prepare combinations for simple checking 29 | angular.forEach(params, function (v, k) { 30 | var combination, expression; 31 | expression = $parse(v); 32 | 33 | angular.forEach(k.split(' '), function(variation) { 34 | combination = { 35 | expression: expression, 36 | keys: {} 37 | }; 38 | angular.forEach(variation.split('-'), function (value) { 39 | combination.keys[value] = true; 40 | }); 41 | combinations.push(combination); 42 | }); 43 | }); 44 | 45 | // Check only matching of pressed keys one of the conditions 46 | elm.bind(mode, function (event) { 47 | // No need to do that inside the cycle 48 | var altPressed = !!(event.metaKey || event.altKey); 49 | var ctrlPressed = !!event.ctrlKey; 50 | var shiftPressed = !!event.shiftKey; 51 | var keyCode = event.keyCode; 52 | 53 | // normalize keycodes 54 | if (mode === 'keypress' && !shiftPressed && keyCode >= 97 && keyCode <= 122) { 55 | keyCode = keyCode - 32; 56 | } 57 | 58 | // Iterate over prepared combinations 59 | angular.forEach(combinations, function (combination) { 60 | 61 | var mainKeyPressed = combination.keys[keysByCode[event.keyCode]] || combination.keys[event.keyCode.toString()]; 62 | 63 | var altRequired = !!combination.keys.alt; 64 | var ctrlRequired = !!combination.keys.ctrl; 65 | var shiftRequired = !!combination.keys.shift; 66 | 67 | if ( 68 | mainKeyPressed && 69 | ( altRequired == altPressed ) && 70 | ( ctrlRequired == ctrlPressed ) && 71 | ( shiftRequired == shiftPressed ) 72 | ) { 73 | // Run the function 74 | scope.$apply(function () { 75 | combination.expression(scope, { '$event': event }); 76 | }); 77 | } 78 | }); 79 | }); 80 | }; 81 | }]); 82 | 83 | /** 84 | * Bind one or more handlers to particular keys or their combination 85 | * @param hash {mixed} keyBindings Can be an object or string where keybinding expression of keys or keys combinations and AngularJS Exspressions are set. Object syntax: "{ keys1: expression1 [, keys2: expression2 [ , ... ]]}". String syntax: ""expression1 on keys1 [ and expression2 on keys2 [ and ... ]]"". Expression is an AngularJS Expression, and key(s) are dash-separated combinations of keys and modifiers (one or many, if any. Order does not matter). Supported modifiers are 'ctrl', 'shift', 'alt' and key can be used either via its keyCode (13 for Return) or name. Named keys are 'backspace', 'tab', 'enter', 'esc', 'space', 'pageup', 'pagedown', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'. 86 | * @example 87 | **/ 88 | angular.module('ui.directives').directive('uiKeydown', ['keypressHelper', function(keypressHelper){ 89 | return { 90 | link: function (scope, elm, attrs) { 91 | keypressHelper('keydown', scope, elm, attrs); 92 | } 93 | }; 94 | }]); 95 | 96 | angular.module('ui.directives').directive('uiKeypress', ['keypressHelper', function(keypressHelper){ 97 | return { 98 | link: function (scope, elm, attrs) { 99 | keypressHelper('keypress', scope, elm, attrs); 100 | } 101 | }; 102 | }]); 103 | 104 | angular.module('ui.directives').directive('uiKeyup', ['keypressHelper', function(keypressHelper){ 105 | return { 106 | link: function (scope, elm, attrs) { 107 | keypressHelper('keyup', scope, elm, attrs); 108 | } 109 | }; 110 | }]); -------------------------------------------------------------------------------- /test/lib/googlemaps/googlemaps.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | window.google = window.google || {}; 4 | google.maps = google.maps || {}; 5 | (function() { 6 | 7 | function getScript(src) { 8 | document.write('<' + 'script src="' + src + '"' + 9 | ' type="text/javascript"><' + '/script>'); 10 | } 11 | 12 | var modules = google.maps.modules = {}; 13 | google.maps.__gjsload__ = function(name, text) { 14 | modules[name] = text; 15 | }; 16 | 17 | google.maps.Load = function(apiLoad) { 18 | delete google.maps.Load; 19 | apiLoad([null,[[["http://mt0.googleapis.com/vt?lyrs=m@180000000\u0026src=api\u0026hl=en-US\u0026","http://mt1.googleapis.com/vt?lyrs=m@180000000\u0026src=api\u0026hl=en-US\u0026"],null,null,null,null,"m@180000000"],[["http://khm0.googleapis.com/kh?v=115\u0026hl=en-US\u0026","http://khm1.googleapis.com/kh?v=115\u0026hl=en-US\u0026"],null,null,null,1,"115"],[["http://mt0.googleapis.com/vt?lyrs=h@180000000\u0026src=api\u0026hl=en-US\u0026","http://mt1.googleapis.com/vt?lyrs=h@180000000\u0026src=api\u0026hl=en-US\u0026"],null,null,"imgtp=png32\u0026",null,"h@180000000"],[["http://mt0.googleapis.com/vt?lyrs=t@129,r@180000000\u0026src=api\u0026hl=en-US\u0026","http://mt1.googleapis.com/vt?lyrs=t@129,r@180000000\u0026src=api\u0026hl=en-US\u0026"],null,null,null,null,"t@129,r@180000000"],null,[[null,0,7,7,[[[330000000,1246050000],[386200000,1293600000]],[[366500000,1297000000],[386200000,1320034790]]],["http://mt0.gmaptiles.co.kr/mt?v=kr1.16\u0026hl=en-US\u0026","http://mt1.gmaptiles.co.kr/mt?v=kr1.16\u0026hl=en-US\u0026"]],[null,0,8,8,[[[330000000,1246050000],[386200000,1279600000]],[[345000000,1279600000],[386200000,1286700000]],[[354690000,1286700000],[386200000,1320035000]]],["http://mt0.gmaptiles.co.kr/mt?v=kr1.16\u0026hl=en-US\u0026","http://mt1.gmaptiles.co.kr/mt?v=kr1.16\u0026hl=en-US\u0026"]],[null,0,9,9,[[[330000000,1246050000],[386200000,1279600000]],[[340000000,1279600000],[386200000,1286700000]],[[348900000,1286700000],[386200000,1302000000]],[[368300000,1302000000],[386200000,1320035000]]],["http://mt0.gmaptiles.co.kr/mt?v=kr1.16\u0026hl=en-US\u0026","http://mt1.gmaptiles.co.kr/mt?v=kr1.16\u0026hl=en-US\u0026"]],[null,0,10,19,[[[329890840,1246055600],[386930130,1284960940]],[[344646740,1284960940],[386930130,1288476560]],[[350277470,1288476560],[386930130,1310531620]],[[370277730,1310531620],[386930130,1320034790]]],["http://mt0.gmaptiles.co.kr/mt?v=kr1.16\u0026hl=en-US\u0026","http://mt1.gmaptiles.co.kr/mt?v=kr1.16\u0026hl=en-US\u0026"]],[null,3,7,7,[[[330000000,1246050000],[386200000,1293600000]],[[366500000,1297000000],[386200000,1320034790]]],["http://mt0.gmaptiles.co.kr/mt?v=kr1p.16\u0026hl=en-US\u0026","http://mt1.gmaptiles.co.kr/mt?v=kr1p.16\u0026hl=en-US\u0026"]],[null,3,8,8,[[[330000000,1246050000],[386200000,1279600000]],[[345000000,1279600000],[386200000,1286700000]],[[354690000,1286700000],[386200000,1320035000]]],["http://mt0.gmaptiles.co.kr/mt?v=kr1p.16\u0026hl=en-US\u0026","http://mt1.gmaptiles.co.kr/mt?v=kr1p.16\u0026hl=en-US\u0026"]],[null,3,9,9,[[[330000000,1246050000],[386200000,1279600000]],[[340000000,1279600000],[386200000,1286700000]],[[348900000,1286700000],[386200000,1302000000]],[[368300000,1302000000],[386200000,1320035000]]],["http://mt0.gmaptiles.co.kr/mt?v=kr1p.16\u0026hl=en-US\u0026","http://mt1.gmaptiles.co.kr/mt?v=kr1p.16\u0026hl=en-US\u0026"]],[null,3,10,null,[[[329890840,1246055600],[386930130,1284960940]],[[344646740,1284960940],[386930130,1288476560]],[[350277470,1288476560],[386930130,1310531620]],[[370277730,1310531620],[386930130,1320034790]]],["http://mt0.gmaptiles.co.kr/mt?v=kr1p.16\u0026hl=en-US\u0026","http://mt1.gmaptiles.co.kr/mt?v=kr1p.16\u0026hl=en-US\u0026"]]],[["http://cbk0.googleapis.com/cbk?","http://cbk1.googleapis.com/cbk?"]],[["http://khm0.googleapis.com/kh?v=60\u0026hl=en-US\u0026","http://khm1.googleapis.com/kh?v=60\u0026hl=en-US\u0026"],null,null,null,null,"60"],[["http://mt0.googleapis.com/mapslt?hl=en-US\u0026","http://mt1.googleapis.com/mapslt?hl=en-US\u0026"]],[["http://mt0.googleapis.com/mapslt/ft?hl=en-US\u0026","http://mt1.googleapis.com/mapslt/ft?hl=en-US\u0026"]],[["http://mt0.googleapis.com/vt?hl=en-US\u0026","http://mt1.googleapis.com/vt?hl=en-US\u0026"]]],["en-US","US",null,0,null,null,"http://maps.gstatic.com/mapfiles/","http://csi.gstatic.com","https://maps.googleapis.com","http://maps.googleapis.com"],["http://maps.gstatic.com/intl/en_us/mapfiles/api-3/9/12","3.9.12"],[4027454879],1.0,null,null,null,null,0,"",null,null,0,"http://khm.googleapis.com/mz?v=115\u0026",null,"https://earthbuilder.google.com","https://earthbuilder.googleapis.com"], loadScriptTime); 20 | }; 21 | var loadScriptTime = (new Date).getTime(); 22 | getScript("http://maps.gstatic.com/intl/en_us/mapfiles/api-3/9/12/main.js"); 23 | })(); -------------------------------------------------------------------------------- /grunt.js: -------------------------------------------------------------------------------- 1 | var testacular = require('testacular'); 2 | 3 | /*global module:false*/ 4 | module.exports = function (grunt) { 5 | 6 | grunt.loadNpmTasks('grunt-recess'); 7 | 8 | // Project configuration. 9 | grunt.initConfig({ 10 | dist: 'build', 11 | pkg: '', 12 | meta: { 13 | banner: ['/**', 14 | ' * <%= pkg.description %>', 15 | ' * @version v<%= pkg.version %> - ', 16 | '<%= grunt.template.today("yyyy-mm-dd") %>', 17 | ' * @link <%= pkg.homepage %>', 18 | ' * @license MIT License, http://www.opensource.org/licenses/MIT', 19 | ' */'].join('\n') 20 | }, 21 | concat: { 22 | build: { 23 | src: ['', 'common/*.js'], 24 | dest: '<%= dist %>/<%= pkg.name %>.js' 25 | }, 26 | ieshiv: { 27 | src: ['', 'common/ieshiv/*.js'], 28 | dest: '<%= dist %>/<%= pkg.name %>-ieshiv.js' 29 | } 30 | }, 31 | min: { 32 | build: { 33 | src: ['', ''], 34 | dest: '<%= dist %>/<%= pkg.name %>.min.js' 35 | }, 36 | ieshiv: { 37 | src: ['', ''], 38 | dest: '<%= dist %>/<%= pkg.name %>-ieshiv.min.js' 39 | } 40 | }, 41 | recess: { 42 | build: { 43 | src: ['common/**/*.less'], 44 | dest: '<%= dist %>/<%= pkg.name %>.css', 45 | options: { 46 | compile: true 47 | } 48 | }, 49 | min: { 50 | src: '', 51 | dest: '<%= dist %>/<%= pkg.name %>.min.css', 52 | options: { 53 | compress: true 54 | } 55 | } 56 | }, 57 | lint: { 58 | files: ['grunt.js', 'common/**/*.js', 'modules/**/*.js'] 59 | }, 60 | watch: { 61 | files: ['modules/**/*.js', 'common/**/*.js', 'templates/**/*.js'], 62 | tasks: 'build test' 63 | } 64 | }); 65 | 66 | // Default task. 67 | grunt.registerTask('default', 'build test'); 68 | 69 | grunt.registerTask('build', 'build all or some of the angular-ui modules', function () { 70 | 71 | var jsBuildFiles = grunt.config('concat.build.src'); 72 | var lessBuildFiles = []; 73 | 74 | if (this.args.length > 0) { 75 | 76 | this.args.forEach(function(moduleName) { 77 | var modulejs = grunt.file.expandFiles('modules/*/' + moduleName + '/*.js'); 78 | var moduleless = grunt.file.expandFiles('modules/*/' + moduleName + '/stylesheets/*.less', 'modules/*/' + moduleName + '/*.less'); 79 | 80 | jsBuildFiles = jsBuildFiles.concat(modulejs); 81 | lessBuildFiles = lessBuildFiles.concat(moduleless); 82 | }); 83 | 84 | grunt.config('concat.build.src', jsBuildFiles); 85 | grunt.config('recess.build.src', lessBuildFiles); 86 | 87 | } else { 88 | grunt.config('concat.build.src', jsBuildFiles.concat(['modules/*/*/*.js'])); 89 | grunt.config('recess.build.src', lessBuildFiles.concat(grunt.config('recess.build.src'))); 90 | } 91 | 92 | grunt.task.run('concat min recess:build recess:min'); 93 | }); 94 | 95 | grunt.registerTask('dist', 'change dist location', function() { 96 | var dir = this.args[0]; 97 | if (dir) { grunt.config('dist', dir); } 98 | }); 99 | 100 | grunt.registerTask('test', 'run tests on single-run server', function() { 101 | var options = ['--single-run', '--no-auto-watch', '--log-level=warn']; 102 | if (process.env.TRAVIS) { 103 | options = options.concat(['--browsers=Firefox']); 104 | } else { 105 | //Can augment options with command line arguments 106 | options = options.concat(this.args); 107 | } 108 | runTestacular('start', options); 109 | }); 110 | 111 | grunt.registerTask('server', 'start testacular server', function() { 112 | var options = ['--no-single-run', '--no-auto-watch'].concat(this.args); 113 | runTestacular('start', options); 114 | }); 115 | 116 | grunt.registerTask('test-run', 'run tests against continuous testacular server', function() { 117 | var options = ['--single-run', '--no-auto-watch'].concat(this.args); 118 | runTestacular('run', options); 119 | }); 120 | 121 | grunt.registerTask('test-watch', 'start testacular server, watch & execute tests', function() { 122 | var options = ['--no-single-run', '--auto-watch'].concat(this.args); 123 | runTestacular('start', options); 124 | }); 125 | 126 | function runTestacular(command, options) { 127 | var testacularCmd = process.platform === 'win32' ? 'testacular.cmd' : 'testacular'; 128 | var args = [command, 'test/test-config.js'].concat(options); 129 | var done = grunt.task.current.async(); 130 | var child = grunt.utils.spawn({ 131 | cmd: testacularCmd, 132 | args: args 133 | }, function(err, result, code) { 134 | if (code) { 135 | done(false); 136 | } else { 137 | done(); 138 | } 139 | }); 140 | child.stdout.pipe(process.stdout); 141 | child.stderr.pipe(process.stderr); 142 | } 143 | }; 144 | -------------------------------------------------------------------------------- /modules/directives/select2/select2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Enhanced Select2 Dropmenus 3 | * 4 | * @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2 5 | * This change is so that you do not have to do an additional query yourself on top of Select2's own query 6 | * @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation 7 | */ 8 | angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$timeout', function (uiConfig, $timeout) { 9 | var options = {}; 10 | if (uiConfig.select2) { 11 | angular.extend(options, uiConfig.select2); 12 | } 13 | return { 14 | require: '?ngModel', 15 | compile: function (tElm, tAttrs) { 16 | var watch, 17 | repeatOption, 18 | repeatAttr, 19 | isSelect = tElm.is('select'), 20 | isMultiple = (tAttrs.multiple !== undefined); 21 | 22 | // Enable watching of the options dataset if in use 23 | if (tElm.is('select')) { 24 | repeatOption = tElm.find('option[ng-repeat], option[data-ng-repeat]'); 25 | 26 | if (repeatOption.length) { 27 | repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat'); 28 | watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop(); 29 | } 30 | } 31 | 32 | return function (scope, elm, attrs, controller) { 33 | // instance-specific options 34 | var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2)); 35 | 36 | if (isSelect) { 37 | // Use "; 4 | var inputHtml = ""; 5 | var compileElement, scope; 6 | 7 | beforeEach(module('ui.directives')); 8 | beforeEach(inject(function ($rootScope, $compile) { 9 | c = console.log; 10 | scope = $rootScope; 11 | compileElement = function(html) { 12 | return $compile(html)(scope); 13 | }; 14 | })); 15 | 16 | describe('initialization', function () { 17 | 18 | it("should not not happen if the mask is undefined or invalid", function() { 19 | var input = compileElement(inputHtml); 20 | scope.$apply("x = 'abc123'"); 21 | expect(input.val()).toBe('abc123'); 22 | scope.$apply("mask = '()_abc123'"); 23 | expect(input.val()).toBe('abc123'); 24 | }); 25 | 26 | it("should mask the value only if it's valid", function() { 27 | var input = compileElement(inputHtml); 28 | scope.$apply("x = 'abc123'"); 29 | scope.$apply("mask = '(A) * 9'"); 30 | expect(input.val()).toBe('(a) b 1'); 31 | scope.$apply("mask = '(A) * 9 A'"); 32 | expect(input.val()).toBe(''); 33 | }); 34 | 35 | it("should not dirty or invalidate the input", function() { 36 | var input = compileElement(inputHtml); 37 | scope.$apply("x = 'abc123'"); 38 | scope.$apply("mask = '(9) * A'"); 39 | expect(input.hasClass('ng-pristine ng-valid')).toBeTruthy(); 40 | scope.$apply("mask = '(9) * A 9'"); 41 | expect(input.hasClass('ng-pristine ng-valid')).toBeTruthy(); 42 | }); 43 | 44 | it("should not change the model value", function() { 45 | var input = compileElement(inputHtml); 46 | scope.$apply("x = 'abc123'"); 47 | scope.$apply("mask = '(A) * 9'"); 48 | expect(scope.x).toBe('abc123'); 49 | scope.$apply("mask = '(A) * 9 A'"); 50 | expect(scope.x).toBe('abc123'); 51 | }); 52 | 53 | it("should set ngModelController.$viewValue to match input value", function() { 54 | var form = compileElement(formHtml); 55 | var input = form.find('input'); 56 | scope.$apply("x = 'abc123'"); 57 | scope.$apply("mask = '(A) * 9'"); 58 | expect(scope.test.input.$viewValue).toBe('(a) b 1'); 59 | scope.$apply("mask = '(A) * 9 A'"); 60 | expect(scope.test.input.$viewValue).toBe(''); 61 | }); 62 | 63 | }); 64 | 65 | describe('user input', function () { 66 | it("should mask-as-you-type", function() { 67 | var form = compileElement(formHtml); 68 | var input = form.find('input'); 69 | scope.$apply("x = ''"); 70 | scope.$apply("mask = '(A) * 9'"); 71 | input.val('a').triggerHandler('input'); 72 | expect(input.val()).toBe('(a) _ _'); 73 | input.val('ab').triggerHandler('input'); 74 | expect(input.val()).toBe('(a) b _'); 75 | input.val('ab1').triggerHandler('input'); 76 | expect(input.val()).toBe('(a) b 1'); 77 | }); 78 | 79 | it("should set ngModelController.$viewValue to match input value", function() { 80 | var form = compileElement(formHtml); 81 | var input = form.find('input'); 82 | scope.$apply("x = ''"); 83 | scope.$apply("mask = '(A) * 9'"); 84 | input.val('a').triggerHandler('input'); 85 | input.triggerHandler('change'); // Because IE8 and below are terrible 86 | expect(scope.test.input.$viewValue).toBe('(a) _ _'); 87 | }); 88 | 89 | it("should parse unmasked value to model", function() { 90 | var form = compileElement(formHtml); 91 | var input = form.find('input'); 92 | scope.$apply("x = ''"); 93 | scope.$apply("mask = '(A) * 9'"); 94 | input.val('abc123').triggerHandler('input'); 95 | input.triggerHandler('change'); // Because IE8 and below are terrible 96 | expect(scope.x).toBe('ab1'); 97 | }); 98 | 99 | it("should set model to undefined if masked value is invalid", function() { 100 | var form = compileElement(formHtml); 101 | var input = form.find('input'); 102 | scope.$apply("x = ''"); 103 | scope.$apply("mask = '(A) * 9'"); 104 | input.val('a').triggerHandler('input'); 105 | input.triggerHandler('change'); // Because IE8 and below are terrible 106 | expect(scope.x).toBeUndefined(); 107 | }); 108 | 109 | it("should not set model to an empty mask", function() { 110 | var form = compileElement(formHtml); 111 | var input = form.find('input'); 112 | scope.$apply("x = ''"); 113 | scope.$apply("mask = '(A) * 9'"); 114 | input.triggerHandler('input'); 115 | expect(scope.x).toBe(''); 116 | }); 117 | }); 118 | 119 | describe('blurring', function () { 120 | it("should clear an invalid value from the input", function() { 121 | var input = compileElement(inputHtml); 122 | scope.$apply("x = ''"); 123 | scope.$apply("mask = '(9) * A'"); 124 | input.val('a').triggerHandler('input'); 125 | input.triggerHandler('blur'); 126 | expect(input.val()).toBe(''); 127 | }); 128 | 129 | it("should clear an invalid value from the ngModelController.$viewValue", function() { 130 | var form = compileElement(formHtml); 131 | var input = form.find('input'); 132 | scope.$apply("x = ''"); 133 | scope.$apply("mask = '(A) * 9'"); 134 | input.val('a').triggerHandler('input'); 135 | input.triggerHandler('blur'); 136 | expect(scope.test.input.$viewValue).toBe(''); 137 | }); 138 | }); 139 | 140 | }); -------------------------------------------------------------------------------- /modules/directives/codemirror/test/codemirrorSpec.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, describe, it, inject, expect, module, spyOn, CodeMirror, angular, $*/ 2 | /** 3 | * TODO Test all the CodeMirror events : cursorActivity viewportChange gutterClick focus blur scroll update. 4 | * with ')(scope); 42 | } 43 | 44 | expect(compile).not.toThrow(); 45 | }); 46 | 47 | it('should throw an error when no ngModel attribute defined', function () { 48 | function compile() { 49 | $compile('')(scope); 50 | } 51 | 52 | expect(compile).toThrow(); 53 | }); 54 | 55 | it('should watch the uiCodemirror attribute', function () { 56 | spyOn(scope, '$watch'); 57 | $compile('')(scope); 58 | $timeout.flush(); 59 | expect(scope.$watch).toHaveBeenCalled(); 60 | }); 61 | 62 | }); 63 | 64 | describe('while spying on the CodeMirror instance', function () { 65 | 66 | var codemirror; 67 | 68 | beforeEach(function () { 69 | var fromTextArea = CodeMirror.fromTextArea; 70 | spyOn(CodeMirror, 'fromTextArea').andCallFake(function () { 71 | codemirror = fromTextArea.apply(this, arguments); 72 | return codemirror; 73 | }); 74 | }); 75 | 76 | describe('verify the directive options', function () { 77 | it('should include the passed options', function () { 78 | $compile('')(scope); 79 | $timeout.flush(); 80 | expect(CodeMirror.fromTextArea.mostRecentCall.args[1].oof).toEqual("baar"); 81 | }); 82 | 83 | it('should include the default options', function () { 84 | $compile('')(scope); 85 | $timeout.flush(); 86 | expect(CodeMirror.fromTextArea.mostRecentCall.args[1].bar).toEqual('baz'); 87 | }); 88 | }); 89 | 90 | describe('when uiRefresh is added', function () { 91 | it('should trigger the CodeMirror.refresh() method', function () { 92 | $compile('')(scope); 93 | $timeout.flush(); 94 | spyOn(codemirror, 'refresh'); 95 | scope.$apply('bar = true'); 96 | $timeout.flush(); 97 | expect(codemirror.refresh).toHaveBeenCalled(); 98 | }); 99 | }); 100 | 101 | 102 | describe('when the IDE changes', function () { 103 | it('should update the model', function () { 104 | $compile('')(scope); 105 | scope.$apply("foo = 'bar'"); 106 | $timeout.flush(); 107 | var value = 'baz'; 108 | codemirror.setValue(value); 109 | expect(scope.foo).toBe(value); 110 | }); 111 | }); 112 | 113 | describe('when the model changes', function () { 114 | it('should update the IDE', function () { 115 | var element = $compile('')(scope); 116 | scope.foo = 'bar'; 117 | scope.$apply(); 118 | $timeout.flush(); 119 | expect(codemirror.getValue()).toBe(scope.foo); 120 | }); 121 | }); 122 | 123 | describe('when the model is undefined/null', function () { 124 | it('should update the IDE with an empty string', function () { 125 | var element = $compile('')(scope); 126 | scope.$apply(); 127 | $timeout.flush(); 128 | expect(scope.foo).toBe(undefined); 129 | expect(codemirror.getValue()).toBe(''); 130 | scope.$apply('foo = "bar"'); 131 | expect(scope.foo).toBe('bar'); 132 | expect(codemirror.getValue()).toBe('bar'); 133 | scope.$apply('foo = null'); 134 | expect(scope.foo).toBe(null); 135 | expect(codemirror.getValue()).toBe(''); 136 | }); 137 | }); 138 | }); 139 | 140 | describe('when the model is an object or an array', function () { 141 | it('should throw an error', function () { 142 | function compileWithObject() { 143 | $compile('')(scope); 144 | $timeout.flush(); 145 | scope.foo = {}; 146 | scope.$apply(); 147 | } 148 | 149 | function compileWithArray() { 150 | $compile('')(scope); 151 | $timeout.flush(); 152 | scope.foo = []; 153 | scope.$apply(); 154 | } 155 | 156 | expect(compileWithObject).toThrow(); 157 | expect(compileWithArray).toThrow(); 158 | }); 159 | }); 160 | }); -------------------------------------------------------------------------------- /test/lib/bootstrap/bootstrap-modal.js: -------------------------------------------------------------------------------- 1 | 2 | /* ========================================================= 3 | * bootstrap-modal.js v2.0.4 4 | * http://twitter.github.com/bootstrap/javascript.html#modals 5 | * ========================================================= 6 | * Copyright 2012 Twitter, Inc. 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ========================================================= */ 20 | 21 | 22 | !function ($) { 23 | 24 | "use strict"; // jshint ;_; 25 | 26 | 27 | /* MODAL CLASS DEFINITION 28 | * ====================== */ 29 | 30 | var Modal = function (content, options) { 31 | this.options = options; 32 | this.$element = $(content) 33 | .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) 34 | }; 35 | 36 | Modal.prototype = { 37 | 38 | constructor: Modal, toggle: function () { 39 | return this[!this.isShown ? 'show' : 'hide']() 40 | }, show: function () { 41 | var that = this 42 | , e = $.Event('show') 43 | 44 | this.$element.trigger(e) 45 | 46 | if (this.isShown || e.isDefaultPrevented()) return 47 | 48 | $('body').addClass('modal-open') 49 | 50 | this.isShown = true 51 | 52 | escape.call(this) 53 | backdrop.call(this, function () { 54 | var transition = $.support.transition && that.$element.hasClass('fade') 55 | 56 | if (!that.$element.parent().length) { 57 | that.$element.appendTo(document.body) //don't move modals dom position 58 | } 59 | 60 | that.$element 61 | .show() 62 | 63 | if (transition) { 64 | that.$element[0].offsetWidth // force reflow 65 | } 66 | 67 | that.$element.addClass('in') 68 | 69 | transition ? 70 | that.$element.one($.support.transition.end, function () { 71 | that.$element.trigger('shown') 72 | }) : 73 | that.$element.trigger('shown') 74 | 75 | }) 76 | }, hide: function (e) { 77 | e && e.preventDefault() 78 | 79 | var that = this 80 | 81 | e = $.Event('hide') 82 | 83 | this.$element.trigger(e) 84 | 85 | if (!this.isShown || e.isDefaultPrevented()) return 86 | 87 | this.isShown = false 88 | 89 | $('body').removeClass('modal-open') 90 | 91 | escape.call(this) 92 | 93 | this.$element.removeClass('in') 94 | 95 | $.support.transition && this.$element.hasClass('fade') ? 96 | hideWithTransition.call(this) : 97 | hideModal.call(this) 98 | } 99 | 100 | } 101 | 102 | 103 | /* MODAL PRIVATE METHODS 104 | * ===================== */ 105 | 106 | function hideWithTransition() { 107 | var that = this 108 | , timeout = setTimeout(function () { 109 | that.$element.off($.support.transition.end) 110 | hideModal.call(that) 111 | }, 500) 112 | 113 | this.$element.one($.support.transition.end, function () { 114 | clearTimeout(timeout) 115 | hideModal.call(that) 116 | }) 117 | } 118 | 119 | function hideModal(that) { 120 | this.$element 121 | .hide() 122 | .trigger('hidden') 123 | 124 | backdrop.call(this) 125 | } 126 | 127 | function backdrop(callback) { 128 | var that = this 129 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 130 | 131 | if (this.isShown && this.options.backdrop) { 132 | var doAnimate = $.support.transition && animate 133 | 134 | this.$backdrop = $('