├── .editorconfig ├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── README.md ├── bower.json ├── demo └── demo.html ├── karma.conf.js ├── lib ├── angular-mocks.js ├── angular.min.js └── jquery-1.8.2.min.js ├── package.json ├── src └── ngmodel.format.js └── test └── ngmodel.format.spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # Howto with your editor: http://editorconfig.org/#download 4 | # Sublime: https://github.com/sindresorhus/editorconfig-sublime 5 | 6 | # top-most EditorConfig file 7 | root = true 8 | 9 | # Unix-style newlines with a newline ending every file 10 | [**] 11 | end_of_line = lf 12 | insert_final_newline = true 13 | 14 | # Standard at: https://github.com/felixge/node-style-guide 15 | [**.js, **.json] 16 | trim_trailing_whitespace = true 17 | indent_style = space 18 | indent_size = 2 19 | quote_type = single 20 | curly_bracket_next_line = false 21 | spaces_around_operators = true 22 | space_after_control_statements = true 23 | space_after_anonymous_functions = true 24 | spaces_in_brackets = false 25 | 26 | # No Standard. Please document a standard if different from .js 27 | [**.yml, **.html, **.css] 28 | trim_trailing_whitespace = true 29 | indent_style = tab 30 | 31 | # No standard. Please document a standard if different from .js 32 | [**.md] 33 | indent_style = tab 34 | 35 | # Standard at: 36 | [Makefile] 37 | indent_style = tab 38 | 39 | # The indentation in package.json will always need to be 2 spaces 40 | # https://github.com/npm/npm/issues/4718 41 | [package.json, bower.json] 42 | indent_style = space 43 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | node_modules 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_script: 5 | - 'npm install -g grunt-cli' 6 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | grunt.loadNpmTasks('grunt-karma'); 5 | 6 | grunt.initConfig({ 7 | karma: { 8 | unit: { 9 | configFile: 'karma.conf.js', 10 | singleRun: true, 11 | browsers: ["PhantomJS"] 12 | } 13 | } 14 | }); 15 | 16 | grunt.registerTask('test', [ 17 | 'karma' 18 | ]); 19 | 20 | grunt.registerTask('default', [ 21 | 'test' 22 | ]); 23 | }; 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ngmodel.format 3 | ================== 4 | 5 | [![Build Status](https://travis-ci.org/greengerong/ngmodel-format.png)](https://travis-ci.org/greengerong/ngmodel-format) 6 | 7 | 8 | * angular format for input/select ... ng-model stuff, the model value will be not same as view value. 9 | 10 | * there are some default format: currency, digit,int,float. 11 | 12 | * You can easy to change it, just inject modelFormatConfig constant into you module config for all of you application. and you also can just set default formatter on element attribute to change this format. 13 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngmodel-format", 3 | "version": "0.0.1", 4 | "authors": [ 5 | ], 6 | "description": "angualr format for input/select", 7 | "main": "./src/ngmodel.format.js", 8 | "keywords": [ 9 | "angular", 10 | "angularjs", 11 | "currency", 12 | "field", 13 | "format" 14 | ], 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "test", 20 | "tests", 21 | "lib", 22 | "demo", 23 | "karma.conf.js" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | demo 6 | 7 | 8 | 59 | 60 | 61 | 62 |
63 |
64 | 65 | 66 | currency: default config 67 | 68 | 69 |
70 |
{{test | json}}
71 |
72 | 73 | 74 | currency: customer config for formatter 75 | 76 | 77 |
78 |
{{test1 | json}}
79 |
80 | 81 | 82 | 83 | currency: customer config for key down 84 | 85 | 86 |
87 |
{{test2 | json}}
88 |
89 | 90 | 91 | 92 | digit 93 | 94 | 95 |
96 |
{{test3 | json}}
97 |
98 | 99 | 100 | 101 | int 102 | 103 | 104 |
105 |
{{test4 | json}}
106 |
107 | 108 | 109 | 110 | float 111 | 112 | 113 |
114 |
{{test5 | json}}
115 |
116 | 117 | 118 | 119 | Check box value to a Array 120 | 121 |
122 | 123 | 124 |
125 |
126 |
{{testBoolean | json}}
127 |
128 | 129 | 130 | 131 | Check box value to a Array 132 | 133 |
134 | 135 | 136 |
137 |
138 |
{{testToArray | json}}
139 |
140 | 141 | 142 | 143 | customer key: number(include negative numbers) 144 | 145 | 146 |
147 |
{{testNumber | json}}
148 |
149 |
150 |
151 |
152 |
153 |
154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | // base path, that will be used to resolve files and exclude 4 | basePath: './', 5 | 6 | frameworks: ['jasmine'], 7 | 8 | // list of files / patterns to load in the browser 9 | files: [ 10 | 'lib/jquery-1.8.2.min.js', 11 | 'lib/angular.min.js', 12 | 'lib/angular-mocks.js', 13 | 'src/*.js', 14 | 'test/*.js' 15 | ], 16 | 17 | 18 | // web server port 19 | // CLI --port 9876 20 | port: 9876, 21 | 22 | // enable / disable colors in the output (reporters and logs) 23 | // CLI --colors --no-colors 24 | colors: true, 25 | 26 | // level of logging 27 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 28 | // CLI --log-level debug 29 | logLevel: config.LOG_INFO, 30 | 31 | // enable / disable watching file and executing tests whenever any file changes 32 | // CLI --auto-watch --no-auto-watch 33 | autoWatch: false, 34 | 35 | // Start these browsers, currently available: 36 | // - Chrome 37 | // - ChromeCanary 38 | // - Firefox 39 | // - Opera 40 | // - Safari (only Mac) 41 | // - PhantomJS 42 | // - IE (only Windows) 43 | // CLI --browsers Chrome,Firefox,Safari 44 | browsers: ['Chrome'],//['PhantomJS'], 45 | 46 | // If browser does not capture in given timeout [ms], kill it 47 | // CLI --capture-timeout 5000 48 | captureTimeout: 20000, 49 | 50 | // Auto run tests on start (when browsers are captured) and exit 51 | // CLI --single-run --no-single-run 52 | singleRun: false, 53 | 54 | 55 | plugins: [ 56 | 'karma-jasmine', 57 | 'karma-chrome-launcher', 58 | 'karma-phantomjs-launcher' 59 | ] 60 | }); 61 | }; 62 | -------------------------------------------------------------------------------- /lib/angular-mocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.8 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | * 6 | * TODO(vojta): wrap whole file into closure during build 7 | */ 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name angular.mock 12 | * @description 13 | * 14 | * Namespace from 'angular-mocks.js' which contains testing related code. 15 | */ 16 | angular.mock = {}; 17 | 18 | /** 19 | * ! This is a private undocumented service ! 20 | * 21 | * @name ngMock.$browser 22 | * 23 | * @description 24 | * This service is a mock implementation of {@link ng.$browser}. It provides fake 25 | * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, 26 | * cookies, etc... 27 | * 28 | * The api of this service is the same as that of the real {@link ng.$browser $browser}, except 29 | * that there are several helper methods available which can be used in tests. 30 | */ 31 | angular.mock.$BrowserProvider = function() { 32 | this.$get = function() { 33 | return new angular.mock.$Browser(); 34 | }; 35 | }; 36 | 37 | angular.mock.$Browser = function() { 38 | var self = this; 39 | 40 | this.isMock = true; 41 | self.$$url = "http://server/"; 42 | self.$$lastUrl = self.$$url; // used by url polling fn 43 | self.pollFns = []; 44 | 45 | // TODO(vojta): remove this temporary api 46 | self.$$completeOutstandingRequest = angular.noop; 47 | self.$$incOutstandingRequestCount = angular.noop; 48 | 49 | 50 | // register url polling fn 51 | 52 | self.onUrlChange = function(listener) { 53 | self.pollFns.push( 54 | function() { 55 | if (self.$$lastUrl != self.$$url) { 56 | self.$$lastUrl = self.$$url; 57 | listener(self.$$url); 58 | } 59 | } 60 | ); 61 | 62 | return listener; 63 | }; 64 | 65 | self.cookieHash = {}; 66 | self.lastCookieHash = {}; 67 | self.deferredFns = []; 68 | self.deferredNextId = 0; 69 | 70 | self.defer = function(fn, delay) { 71 | delay = delay || 0; 72 | self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); 73 | self.deferredFns.sort(function(a,b){ return a.time - b.time;}); 74 | return self.deferredNextId++; 75 | }; 76 | 77 | 78 | self.defer.now = 0; 79 | 80 | 81 | self.defer.cancel = function(deferId) { 82 | var fnIndex; 83 | 84 | angular.forEach(self.deferredFns, function(fn, index) { 85 | if (fn.id === deferId) fnIndex = index; 86 | }); 87 | 88 | if (fnIndex !== undefined) { 89 | self.deferredFns.splice(fnIndex, 1); 90 | return true; 91 | } 92 | 93 | return false; 94 | }; 95 | 96 | 97 | /** 98 | * @name ngMock.$browser#defer.flush 99 | * @methodOf ngMock.$browser 100 | * 101 | * @description 102 | * Flushes all pending requests and executes the defer callbacks. 103 | * 104 | * @param {number=} number of milliseconds to flush. See {@link #defer.now} 105 | */ 106 | self.defer.flush = function(delay) { 107 | if (angular.isDefined(delay)) { 108 | self.defer.now += delay; 109 | } else { 110 | if (self.deferredFns.length) { 111 | self.defer.now = self.deferredFns[self.deferredFns.length-1].time; 112 | } else { 113 | throw Error('No deferred tasks to be flushed'); 114 | } 115 | } 116 | 117 | while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { 118 | self.deferredFns.shift().fn(); 119 | } 120 | }; 121 | /** 122 | * @name ngMock.$browser#defer.now 123 | * @propertyOf ngMock.$browser 124 | * 125 | * @description 126 | * Current milliseconds mock time. 127 | */ 128 | 129 | self.$$baseHref = ''; 130 | self.baseHref = function() { 131 | return this.$$baseHref; 132 | }; 133 | }; 134 | angular.mock.$Browser.prototype = { 135 | 136 | /** 137 | * @name ngMock.$browser#poll 138 | * @methodOf ngMock.$browser 139 | * 140 | * @description 141 | * run all fns in pollFns 142 | */ 143 | poll: function poll() { 144 | angular.forEach(this.pollFns, function(pollFn){ 145 | pollFn(); 146 | }); 147 | }, 148 | 149 | addPollFn: function(pollFn) { 150 | this.pollFns.push(pollFn); 151 | return pollFn; 152 | }, 153 | 154 | url: function(url, replace) { 155 | if (url) { 156 | this.$$url = url; 157 | return this; 158 | } 159 | 160 | return this.$$url; 161 | }, 162 | 163 | cookies: function(name, value) { 164 | if (name) { 165 | if (value == undefined) { 166 | delete this.cookieHash[name]; 167 | } else { 168 | if (angular.isString(value) && //strings only 169 | value.length <= 4096) { //strict cookie storage limits 170 | this.cookieHash[name] = value; 171 | } 172 | } 173 | } else { 174 | if (!angular.equals(this.cookieHash, this.lastCookieHash)) { 175 | this.lastCookieHash = angular.copy(this.cookieHash); 176 | this.cookieHash = angular.copy(this.cookieHash); 177 | } 178 | return this.cookieHash; 179 | } 180 | }, 181 | 182 | notifyWhenNoOutstandingRequests: function(fn) { 183 | fn(); 184 | } 185 | }; 186 | 187 | 188 | /** 189 | * @ngdoc object 190 | * @name ngMock.$exceptionHandlerProvider 191 | * 192 | * @description 193 | * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed 194 | * into the `$exceptionHandler`. 195 | */ 196 | 197 | /** 198 | * @ngdoc object 199 | * @name ngMock.$exceptionHandler 200 | * 201 | * @description 202 | * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed 203 | * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration 204 | * information. 205 | * 206 | * 207 | *
 208 |  *   describe('$exceptionHandlerProvider', function() {
 209 |  *
 210 |  *     it('should capture log messages and exceptions', function() {
 211 |  *
 212 |  *       module(function($exceptionHandlerProvider) {
 213 |  *         $exceptionHandlerProvider.mode('log');
 214 |  *       });
 215 |  *
 216 |  *       inject(function($log, $exceptionHandler, $timeout) {
 217 |  *         $timeout(function() { $log.log(1); });
 218 |  *         $timeout(function() { $log.log(2); throw 'banana peel'; });
 219 |  *         $timeout(function() { $log.log(3); });
 220 |  *         expect($exceptionHandler.errors).toEqual([]);
 221 |  *         expect($log.assertEmpty());
 222 |  *         $timeout.flush();
 223 |  *         expect($exceptionHandler.errors).toEqual(['banana peel']);
 224 |  *         expect($log.log.logs).toEqual([[1], [2], [3]]);
 225 |  *       });
 226 |  *     });
 227 |  *   });
 228 |  * 
229 | */ 230 | 231 | angular.mock.$ExceptionHandlerProvider = function() { 232 | var handler; 233 | 234 | /** 235 | * @ngdoc method 236 | * @name ngMock.$exceptionHandlerProvider#mode 237 | * @methodOf ngMock.$exceptionHandlerProvider 238 | * 239 | * @description 240 | * Sets the logging mode. 241 | * 242 | * @param {string} mode Mode of operation, defaults to `rethrow`. 243 | * 244 | * - `rethrow`: If any errors are passed into the handler in tests, it typically 245 | * means that there is a bug in the application or test, so this mock will 246 | * make these tests fail. 247 | * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an 248 | * array of errors in `$exceptionHandler.errors`, to allow later assertion of them. 249 | * See {@link ngMock.$log#assertEmpty assertEmpty()} and 250 | * {@link ngMock.$log#reset reset()} 251 | */ 252 | this.mode = function(mode) { 253 | switch(mode) { 254 | case 'rethrow': 255 | handler = function(e) { 256 | throw e; 257 | }; 258 | break; 259 | case 'log': 260 | var errors = []; 261 | 262 | handler = function(e) { 263 | if (arguments.length == 1) { 264 | errors.push(e); 265 | } else { 266 | errors.push([].slice.call(arguments, 0)); 267 | } 268 | }; 269 | 270 | handler.errors = errors; 271 | break; 272 | default: 273 | throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); 274 | } 275 | }; 276 | 277 | this.$get = function() { 278 | return handler; 279 | }; 280 | 281 | this.mode('rethrow'); 282 | }; 283 | 284 | 285 | /** 286 | * @ngdoc service 287 | * @name ngMock.$log 288 | * 289 | * @description 290 | * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays 291 | * (one array per logging level). These arrays are exposed as `logs` property of each of the 292 | * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. 293 | * 294 | */ 295 | angular.mock.$LogProvider = function() { 296 | 297 | function concat(array1, array2, index) { 298 | return array1.concat(Array.prototype.slice.call(array2, index)); 299 | } 300 | 301 | 302 | this.$get = function () { 303 | var $log = { 304 | log: function() { $log.log.logs.push(concat([], arguments, 0)); }, 305 | warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, 306 | info: function() { $log.info.logs.push(concat([], arguments, 0)); }, 307 | error: function() { $log.error.logs.push(concat([], arguments, 0)); } 308 | }; 309 | 310 | /** 311 | * @ngdoc method 312 | * @name ngMock.$log#reset 313 | * @methodOf ngMock.$log 314 | * 315 | * @description 316 | * Reset all of the logging arrays to empty. 317 | */ 318 | $log.reset = function () { 319 | /** 320 | * @ngdoc property 321 | * @name ngMock.$log#log.logs 322 | * @propertyOf ngMock.$log 323 | * 324 | * @description 325 | * Array of messages logged using {@link ngMock.$log#log}. 326 | * 327 | * @example 328 | *
 329 |        * $log.log('Some Log');
 330 |        * var first = $log.log.logs.unshift();
 331 |        * 
332 | */ 333 | $log.log.logs = []; 334 | /** 335 | * @ngdoc property 336 | * @name ngMock.$log#warn.logs 337 | * @propertyOf ngMock.$log 338 | * 339 | * @description 340 | * Array of messages logged using {@link ngMock.$log#warn}. 341 | * 342 | * @example 343 | *
 344 |        * $log.warn('Some Warning');
 345 |        * var first = $log.warn.logs.unshift();
 346 |        * 
347 | */ 348 | $log.warn.logs = []; 349 | /** 350 | * @ngdoc property 351 | * @name ngMock.$log#info.logs 352 | * @propertyOf ngMock.$log 353 | * 354 | * @description 355 | * Array of messages logged using {@link ngMock.$log#info}. 356 | * 357 | * @example 358 | *
 359 |        * $log.info('Some Info');
 360 |        * var first = $log.info.logs.unshift();
 361 |        * 
362 | */ 363 | $log.info.logs = []; 364 | /** 365 | * @ngdoc property 366 | * @name ngMock.$log#error.logs 367 | * @propertyOf ngMock.$log 368 | * 369 | * @description 370 | * Array of messages logged using {@link ngMock.$log#error}. 371 | * 372 | * @example 373 | *
 374 |        * $log.log('Some Error');
 375 |        * var first = $log.error.logs.unshift();
 376 |        * 
377 | */ 378 | $log.error.logs = []; 379 | }; 380 | 381 | /** 382 | * @ngdoc method 383 | * @name ngMock.$log#assertEmpty 384 | * @methodOf ngMock.$log 385 | * 386 | * @description 387 | * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown. 388 | */ 389 | $log.assertEmpty = function() { 390 | var errors = []; 391 | angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) { 392 | angular.forEach($log[logLevel].logs, function(log) { 393 | angular.forEach(log, function (logItem) { 394 | errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); 395 | }); 396 | }); 397 | }); 398 | if (errors.length) { 399 | errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + 400 | "log message was not checked and removed:"); 401 | errors.push(''); 402 | throw new Error(errors.join('\n---------\n')); 403 | } 404 | }; 405 | 406 | $log.reset(); 407 | return $log; 408 | }; 409 | }; 410 | 411 | 412 | (function() { 413 | var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; 414 | 415 | function jsonStringToDate(string) { 416 | var match; 417 | if (match = string.match(R_ISO8061_STR)) { 418 | var date = new Date(0), 419 | tzHour = 0, 420 | tzMin = 0; 421 | if (match[9]) { 422 | tzHour = int(match[9] + match[10]); 423 | tzMin = int(match[9] + match[11]); 424 | } 425 | date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); 426 | date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); 427 | return date; 428 | } 429 | return string; 430 | } 431 | 432 | function int(str) { 433 | return parseInt(str, 10); 434 | } 435 | 436 | function padNumber(num, digits, trim) { 437 | var neg = ''; 438 | if (num < 0) { 439 | neg = '-'; 440 | num = -num; 441 | } 442 | num = '' + num; 443 | while(num.length < digits) num = '0' + num; 444 | if (trim) 445 | num = num.substr(num.length - digits); 446 | return neg + num; 447 | } 448 | 449 | 450 | /** 451 | * @ngdoc object 452 | * @name angular.mock.TzDate 453 | * @description 454 | * 455 | * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. 456 | * 457 | * Mock of the Date type which has its timezone specified via constructor arg. 458 | * 459 | * The main purpose is to create Date-like instances with timezone fixed to the specified timezone 460 | * offset, so that we can test code that depends on local timezone settings without dependency on 461 | * the time zone settings of the machine where the code is running. 462 | * 463 | * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) 464 | * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* 465 | * 466 | * @example 467 | * !!!! WARNING !!!!! 468 | * This is not a complete Date object so only methods that were implemented can be called safely. 469 | * To make matters worse, TzDate instances inherit stuff from Date via a prototype. 470 | * 471 | * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is 472 | * incomplete we might be missing some non-standard methods. This can result in errors like: 473 | * "Date.prototype.foo called on incompatible Object". 474 | * 475 | *
 476 |    * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
 477 |    * newYearInBratislava.getTimezoneOffset() => -60;
 478 |    * newYearInBratislava.getFullYear() => 2010;
 479 |    * newYearInBratislava.getMonth() => 0;
 480 |    * newYearInBratislava.getDate() => 1;
 481 |    * newYearInBratislava.getHours() => 0;
 482 |    * newYearInBratislava.getMinutes() => 0;
 483 |    * 
484 | * 485 | */ 486 | angular.mock.TzDate = function (offset, timestamp) { 487 | var self = new Date(0); 488 | if (angular.isString(timestamp)) { 489 | var tsStr = timestamp; 490 | 491 | self.origDate = jsonStringToDate(timestamp); 492 | 493 | timestamp = self.origDate.getTime(); 494 | if (isNaN(timestamp)) 495 | throw { 496 | name: "Illegal Argument", 497 | message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" 498 | }; 499 | } else { 500 | self.origDate = new Date(timestamp); 501 | } 502 | 503 | var localOffset = new Date(timestamp).getTimezoneOffset(); 504 | self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; 505 | self.date = new Date(timestamp + self.offsetDiff); 506 | 507 | self.getTime = function() { 508 | return self.date.getTime() - self.offsetDiff; 509 | }; 510 | 511 | self.toLocaleDateString = function() { 512 | return self.date.toLocaleDateString(); 513 | }; 514 | 515 | self.getFullYear = function() { 516 | return self.date.getFullYear(); 517 | }; 518 | 519 | self.getMonth = function() { 520 | return self.date.getMonth(); 521 | }; 522 | 523 | self.getDate = function() { 524 | return self.date.getDate(); 525 | }; 526 | 527 | self.getHours = function() { 528 | return self.date.getHours(); 529 | }; 530 | 531 | self.getMinutes = function() { 532 | return self.date.getMinutes(); 533 | }; 534 | 535 | self.getSeconds = function() { 536 | return self.date.getSeconds(); 537 | }; 538 | 539 | self.getTimezoneOffset = function() { 540 | return offset * 60; 541 | }; 542 | 543 | self.getUTCFullYear = function() { 544 | return self.origDate.getUTCFullYear(); 545 | }; 546 | 547 | self.getUTCMonth = function() { 548 | return self.origDate.getUTCMonth(); 549 | }; 550 | 551 | self.getUTCDate = function() { 552 | return self.origDate.getUTCDate(); 553 | }; 554 | 555 | self.getUTCHours = function() { 556 | return self.origDate.getUTCHours(); 557 | }; 558 | 559 | self.getUTCMinutes = function() { 560 | return self.origDate.getUTCMinutes(); 561 | }; 562 | 563 | self.getUTCSeconds = function() { 564 | return self.origDate.getUTCSeconds(); 565 | }; 566 | 567 | self.getUTCMilliseconds = function() { 568 | return self.origDate.getUTCMilliseconds(); 569 | }; 570 | 571 | self.getDay = function() { 572 | return self.date.getDay(); 573 | }; 574 | 575 | // provide this method only on browsers that already have it 576 | if (self.toISOString) { 577 | self.toISOString = function() { 578 | return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + 579 | padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + 580 | padNumber(self.origDate.getUTCDate(), 2) + 'T' + 581 | padNumber(self.origDate.getUTCHours(), 2) + ':' + 582 | padNumber(self.origDate.getUTCMinutes(), 2) + ':' + 583 | padNumber(self.origDate.getUTCSeconds(), 2) + '.' + 584 | padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' 585 | } 586 | } 587 | 588 | //hide all methods not implemented in this mock that the Date prototype exposes 589 | var unimplementedMethods = ['getMilliseconds', 'getUTCDay', 590 | 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 591 | 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', 592 | 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', 593 | 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', 594 | 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; 595 | 596 | angular.forEach(unimplementedMethods, function(methodName) { 597 | self[methodName] = function() { 598 | throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); 599 | }; 600 | }); 601 | 602 | return self; 603 | }; 604 | 605 | //make "tzDateInstance instanceof Date" return true 606 | angular.mock.TzDate.prototype = Date.prototype; 607 | })(); 608 | 609 | 610 | /** 611 | * @ngdoc function 612 | * @name angular.mock.dump 613 | * @description 614 | * 615 | * *NOTE*: this is not an injectable instance, just a globally available function. 616 | * 617 | * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging. 618 | * 619 | * This method is also available on window, where it can be used to display objects on debug console. 620 | * 621 | * @param {*} object - any object to turn into string. 622 | * @return {string} a serialized string of the argument 623 | */ 624 | angular.mock.dump = function(object) { 625 | return serialize(object); 626 | 627 | function serialize(object) { 628 | var out; 629 | 630 | if (angular.isElement(object)) { 631 | object = angular.element(object); 632 | out = angular.element('
'); 633 | angular.forEach(object, function(element) { 634 | out.append(angular.element(element).clone()); 635 | }); 636 | out = out.html(); 637 | } else if (angular.isArray(object)) { 638 | out = []; 639 | angular.forEach(object, function(o) { 640 | out.push(serialize(o)); 641 | }); 642 | out = '[ ' + out.join(', ') + ' ]'; 643 | } else if (angular.isObject(object)) { 644 | if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { 645 | out = serializeScope(object); 646 | } else if (object instanceof Error) { 647 | out = object.stack || ('' + object.name + ': ' + object.message); 648 | } else { 649 | out = angular.toJson(object, true); 650 | } 651 | } else { 652 | out = String(object); 653 | } 654 | 655 | return out; 656 | } 657 | 658 | function serializeScope(scope, offset) { 659 | offset = offset || ' '; 660 | var log = [offset + 'Scope(' + scope.$id + '): {']; 661 | for ( var key in scope ) { 662 | if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { 663 | log.push(' ' + key + ': ' + angular.toJson(scope[key])); 664 | } 665 | } 666 | var child = scope.$$childHead; 667 | while(child) { 668 | log.push(serializeScope(child, offset + ' ')); 669 | child = child.$$nextSibling; 670 | } 671 | log.push('}'); 672 | return log.join('\n' + offset); 673 | } 674 | }; 675 | 676 | /** 677 | * @ngdoc object 678 | * @name ngMock.$httpBackend 679 | * @description 680 | * Fake HTTP backend implementation suitable for unit testing applications that use the 681 | * {@link ng.$http $http service}. 682 | * 683 | * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less 684 | * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. 685 | * 686 | * During unit testing, we want our unit tests to run quickly and have no external dependencies so 687 | * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or 688 | * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is 689 | * to verify whether a certain request has been sent or not, or alternatively just let the 690 | * application make requests, respond with pre-trained responses and assert that the end result is 691 | * what we expect it to be. 692 | * 693 | * This mock implementation can be used to respond with static or dynamic responses via the 694 | * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). 695 | * 696 | * When an Angular application needs some data from a server, it calls the $http service, which 697 | * sends the request to a real server using $httpBackend service. With dependency injection, it is 698 | * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify 699 | * the requests and respond with some testing data without sending a request to real server. 700 | * 701 | * There are two ways to specify what test data should be returned as http responses by the mock 702 | * backend when the code under test makes http requests: 703 | * 704 | * - `$httpBackend.expect` - specifies a request expectation 705 | * - `$httpBackend.when` - specifies a backend definition 706 | * 707 | * 708 | * # Request Expectations vs Backend Definitions 709 | * 710 | * Request expectations provide a way to make assertions about requests made by the application and 711 | * to define responses for those requests. The test will fail if the expected requests are not made 712 | * or they are made in the wrong order. 713 | * 714 | * Backend definitions allow you to define a fake backend for your application which doesn't assert 715 | * if a particular request was made or not, it just returns a trained response if a request is made. 716 | * The test will pass whether or not the request gets made during testing. 717 | * 718 | * 719 | * 720 | * 721 | * 722 | * 723 | * 724 | * 725 | * 726 | * 727 | * 728 | * 729 | * 730 | * 731 | * 732 | * 733 | * 734 | * 735 | * 736 | * 737 | * 738 | * 739 | * 740 | * 741 | * 742 | * 743 | * 744 | * 745 | * 746 | * 747 | * 748 | * 749 | * 750 | * 751 | *
Request expectationsBackend definitions
Syntax.expect(...).respond(...).when(...).respond(...)
Typical usagestrict unit testsloose (black-box) unit testing
Fulfills multiple requestsNOYES
Order of requests mattersYESNO
Request requiredYESNO
Response requiredoptional (see below)YES
752 | * 753 | * In cases where both backend definitions and request expectations are specified during unit 754 | * testing, the request expectations are evaluated first. 755 | * 756 | * If a request expectation has no response specified, the algorithm will search your backend 757 | * definitions for an appropriate response. 758 | * 759 | * If a request didn't match any expectation or if the expectation doesn't have the response 760 | * defined, the backend definitions are evaluated in sequential order to see if any of them match 761 | * the request. The response from the first matched definition is returned. 762 | * 763 | * 764 | * # Flushing HTTP requests 765 | * 766 | * The $httpBackend used in production, always responds to requests with responses asynchronously. 767 | * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are 768 | * hard to write, follow and maintain. At the same time the testing mock, can't respond 769 | * synchronously because that would change the execution of the code under test. For this reason the 770 | * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending 771 | * requests and thus preserving the async api of the backend, while allowing the test to execute 772 | * synchronously. 773 | * 774 | * 775 | * # Unit testing with mock $httpBackend 776 | * The following code shows how to setup and use the mock backend in unit testing a controller. 777 | * First we create the controller under test 778 | * 779 |
 780 |   // The controller code
 781 |   function MyController($scope, $http) {
 782 |     var authToken;
 783 | 
 784 |     $http.get('/auth.py').success(function(data, status, headers) {
 785 |       authToken = headers('A-Token');
 786 |       $scope.user = data;
 787 |     });
 788 | 
 789 |     $scope.saveMessage = function(message) {
 790 |       var headers = { 'Authorization': authToken };
 791 |       $scope.status = 'Saving...';
 792 | 
 793 |       $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
 794 |         $scope.status = '';
 795 |       }).error(function() {
 796 |         $scope.status = 'ERROR!';
 797 |       });
 798 |     };
 799 |   }
 800 |   
801 | * 802 | * Now we setup the mock backend and create the test specs. 803 | * 804 |
 805 |     // testing controller
 806 |     describe('MyController', function() {
 807 |        var $httpBackend, $rootScope, createController;
 808 | 
 809 |        beforeEach(inject(function($injector) {
 810 |          // Set up the mock http service responses
 811 |          $httpBackend = $injector.get('$httpBackend');
 812 |          // backend definition common for all tests
 813 |          $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
 814 | 
 815 |          // Get hold of a scope (i.e. the root scope)
 816 |          $rootScope = $injector.get('$rootScope');
 817 |          // The $controller service is used to create instances of controllers
 818 |          var $controller = $injector.get('$controller');
 819 | 
 820 |          createController = function() {
 821 |            return $controller('MyController', {'$scope' : $rootScope });
 822 |          };
 823 |        }));
 824 | 
 825 | 
 826 |        afterEach(function() {
 827 |          $httpBackend.verifyNoOutstandingExpectation();
 828 |          $httpBackend.verifyNoOutstandingRequest();
 829 |        });
 830 | 
 831 | 
 832 |        it('should fetch authentication token', function() {
 833 |          $httpBackend.expectGET('/auth.py');
 834 |          var controller = createController();
 835 |          $httpBackend.flush();
 836 |        });
 837 | 
 838 | 
 839 |        it('should send msg to server', function() {
 840 |          var controller = createController();
 841 |          $httpBackend.flush();
 842 | 
 843 |          // now you don’t care about the authentication, but
 844 |          // the controller will still send the request and
 845 |          // $httpBackend will respond without you having to
 846 |          // specify the expectation and response for this request
 847 | 
 848 |          $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
 849 |          $rootScope.saveMessage('message content');
 850 |          expect($rootScope.status).toBe('Saving...');
 851 |          $httpBackend.flush();
 852 |          expect($rootScope.status).toBe('');
 853 |        });
 854 | 
 855 | 
 856 |        it('should send auth header', function() {
 857 |          var controller = createController();
 858 |          $httpBackend.flush();
 859 | 
 860 |          $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
 861 |            // check if the header was send, if it wasn't the expectation won't
 862 |            // match the request and the test will fail
 863 |            return headers['Authorization'] == 'xxx';
 864 |          }).respond(201, '');
 865 | 
 866 |          $rootScope.saveMessage('whatever');
 867 |          $httpBackend.flush();
 868 |        });
 869 |     });
 870 |    
871 | */ 872 | angular.mock.$HttpBackendProvider = function() { 873 | this.$get = [createHttpBackendMock]; 874 | }; 875 | 876 | /** 877 | * General factory function for $httpBackend mock. 878 | * Returns instance for unit testing (when no arguments specified): 879 | * - passing through is disabled 880 | * - auto flushing is disabled 881 | * 882 | * Returns instance for e2e testing (when `$delegate` and `$browser` specified): 883 | * - passing through (delegating request to real backend) is enabled 884 | * - auto flushing is enabled 885 | * 886 | * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) 887 | * @param {Object=} $browser Auto-flushing enabled if specified 888 | * @return {Object} Instance of $httpBackend mock 889 | */ 890 | function createHttpBackendMock($delegate, $browser) { 891 | var definitions = [], 892 | expectations = [], 893 | responses = [], 894 | responsesPush = angular.bind(responses, responses.push); 895 | 896 | function createResponse(status, data, headers) { 897 | if (angular.isFunction(status)) return status; 898 | 899 | return function() { 900 | return angular.isNumber(status) 901 | ? [status, data, headers] 902 | : [200, status, data]; 903 | }; 904 | } 905 | 906 | // TODO(vojta): change params to: method, url, data, headers, callback 907 | function $httpBackend(method, url, data, callback, headers) { 908 | var xhr = new MockXhr(), 909 | expectation = expectations[0], 910 | wasExpected = false; 911 | 912 | function prettyPrint(data) { 913 | return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) 914 | ? data 915 | : angular.toJson(data); 916 | } 917 | 918 | if (expectation && expectation.match(method, url)) { 919 | if (!expectation.matchData(data)) 920 | throw Error('Expected ' + expectation + ' with different data\n' + 921 | 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); 922 | 923 | if (!expectation.matchHeaders(headers)) 924 | throw Error('Expected ' + expectation + ' with different headers\n' + 925 | 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + 926 | prettyPrint(headers)); 927 | 928 | expectations.shift(); 929 | 930 | if (expectation.response) { 931 | responses.push(function() { 932 | var response = expectation.response(method, url, data, headers); 933 | xhr.$$respHeaders = response[2]; 934 | callback(response[0], response[1], xhr.getAllResponseHeaders()); 935 | }); 936 | return; 937 | } 938 | wasExpected = true; 939 | } 940 | 941 | var i = -1, definition; 942 | while ((definition = definitions[++i])) { 943 | if (definition.match(method, url, data, headers || {})) { 944 | if (definition.response) { 945 | // if $browser specified, we do auto flush all requests 946 | ($browser ? $browser.defer : responsesPush)(function() { 947 | var response = definition.response(method, url, data, headers); 948 | xhr.$$respHeaders = response[2]; 949 | callback(response[0], response[1], xhr.getAllResponseHeaders()); 950 | }); 951 | } else if (definition.passThrough) { 952 | $delegate(method, url, data, callback, headers); 953 | } else throw Error('No response defined !'); 954 | return; 955 | } 956 | } 957 | throw wasExpected ? 958 | Error('No response defined !') : 959 | Error('Unexpected request: ' + method + ' ' + url + '\n' + 960 | (expectation ? 'Expected ' + expectation : 'No more request expected')); 961 | } 962 | 963 | /** 964 | * @ngdoc method 965 | * @name ngMock.$httpBackend#when 966 | * @methodOf ngMock.$httpBackend 967 | * @description 968 | * Creates a new backend definition. 969 | * 970 | * @param {string} method HTTP method. 971 | * @param {string|RegExp} url HTTP url. 972 | * @param {(string|RegExp)=} data HTTP request body. 973 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 974 | * object and returns true if the headers match the current definition. 975 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 976 | * request is handled. 977 | * 978 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 979 | * – The respond method takes a set of static data to be returned or a function that can return 980 | * an array containing response status (number), response data (string) and response headers 981 | * (Object). 982 | */ 983 | $httpBackend.when = function(method, url, data, headers) { 984 | var definition = new MockHttpExpectation(method, url, data, headers), 985 | chain = { 986 | respond: function(status, data, headers) { 987 | definition.response = createResponse(status, data, headers); 988 | } 989 | }; 990 | 991 | if ($browser) { 992 | chain.passThrough = function() { 993 | definition.passThrough = true; 994 | }; 995 | } 996 | 997 | definitions.push(definition); 998 | return chain; 999 | }; 1000 | 1001 | /** 1002 | * @ngdoc method 1003 | * @name ngMock.$httpBackend#whenGET 1004 | * @methodOf ngMock.$httpBackend 1005 | * @description 1006 | * Creates a new backend definition for GET requests. For more info see `when()`. 1007 | * 1008 | * @param {string|RegExp} url HTTP url. 1009 | * @param {(Object|function(Object))=} headers HTTP headers. 1010 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1011 | * request is handled. 1012 | */ 1013 | 1014 | /** 1015 | * @ngdoc method 1016 | * @name ngMock.$httpBackend#whenHEAD 1017 | * @methodOf ngMock.$httpBackend 1018 | * @description 1019 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 1020 | * 1021 | * @param {string|RegExp} url HTTP url. 1022 | * @param {(Object|function(Object))=} headers HTTP headers. 1023 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1024 | * request is handled. 1025 | */ 1026 | 1027 | /** 1028 | * @ngdoc method 1029 | * @name ngMock.$httpBackend#whenDELETE 1030 | * @methodOf ngMock.$httpBackend 1031 | * @description 1032 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1033 | * 1034 | * @param {string|RegExp} url HTTP url. 1035 | * @param {(Object|function(Object))=} headers HTTP headers. 1036 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1037 | * request is handled. 1038 | */ 1039 | 1040 | /** 1041 | * @ngdoc method 1042 | * @name ngMock.$httpBackend#whenPOST 1043 | * @methodOf ngMock.$httpBackend 1044 | * @description 1045 | * Creates a new backend definition for POST requests. For more info see `when()`. 1046 | * 1047 | * @param {string|RegExp} url HTTP url. 1048 | * @param {(string|RegExp)=} data HTTP request body. 1049 | * @param {(Object|function(Object))=} headers HTTP headers. 1050 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1051 | * request is handled. 1052 | */ 1053 | 1054 | /** 1055 | * @ngdoc method 1056 | * @name ngMock.$httpBackend#whenPUT 1057 | * @methodOf ngMock.$httpBackend 1058 | * @description 1059 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1060 | * 1061 | * @param {string|RegExp} url HTTP url. 1062 | * @param {(string|RegExp)=} data HTTP request body. 1063 | * @param {(Object|function(Object))=} headers HTTP headers. 1064 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1065 | * request is handled. 1066 | */ 1067 | 1068 | /** 1069 | * @ngdoc method 1070 | * @name ngMock.$httpBackend#whenJSONP 1071 | * @methodOf ngMock.$httpBackend 1072 | * @description 1073 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1074 | * 1075 | * @param {string|RegExp} url HTTP url. 1076 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1077 | * request is handled. 1078 | */ 1079 | createShortMethods('when'); 1080 | 1081 | 1082 | /** 1083 | * @ngdoc method 1084 | * @name ngMock.$httpBackend#expect 1085 | * @methodOf ngMock.$httpBackend 1086 | * @description 1087 | * Creates a new request expectation. 1088 | * 1089 | * @param {string} method HTTP method. 1090 | * @param {string|RegExp} url HTTP url. 1091 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1092 | * receives data string and returns true if the data is as expected, or Object if request body 1093 | * is in JSON format. 1094 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1095 | * object and returns true if the headers match the current expectation. 1096 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1097 | * request is handled. 1098 | * 1099 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 1100 | * – The respond method takes a set of static data to be returned or a function that can return 1101 | * an array containing response status (number), response data (string) and response headers 1102 | * (Object). 1103 | */ 1104 | $httpBackend.expect = function(method, url, data, headers) { 1105 | var expectation = new MockHttpExpectation(method, url, data, headers); 1106 | expectations.push(expectation); 1107 | return { 1108 | respond: function(status, data, headers) { 1109 | expectation.response = createResponse(status, data, headers); 1110 | } 1111 | }; 1112 | }; 1113 | 1114 | 1115 | /** 1116 | * @ngdoc method 1117 | * @name ngMock.$httpBackend#expectGET 1118 | * @methodOf ngMock.$httpBackend 1119 | * @description 1120 | * Creates a new request expectation for GET requests. For more info see `expect()`. 1121 | * 1122 | * @param {string|RegExp} url HTTP url. 1123 | * @param {Object=} headers HTTP headers. 1124 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1125 | * request is handled. See #expect for more info. 1126 | */ 1127 | 1128 | /** 1129 | * @ngdoc method 1130 | * @name ngMock.$httpBackend#expectHEAD 1131 | * @methodOf ngMock.$httpBackend 1132 | * @description 1133 | * Creates a new request expectation for HEAD requests. For more info see `expect()`. 1134 | * 1135 | * @param {string|RegExp} url HTTP url. 1136 | * @param {Object=} headers HTTP headers. 1137 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1138 | * request is handled. 1139 | */ 1140 | 1141 | /** 1142 | * @ngdoc method 1143 | * @name ngMock.$httpBackend#expectDELETE 1144 | * @methodOf ngMock.$httpBackend 1145 | * @description 1146 | * Creates a new request expectation for DELETE requests. For more info see `expect()`. 1147 | * 1148 | * @param {string|RegExp} url HTTP url. 1149 | * @param {Object=} headers HTTP headers. 1150 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1151 | * request is handled. 1152 | */ 1153 | 1154 | /** 1155 | * @ngdoc method 1156 | * @name ngMock.$httpBackend#expectPOST 1157 | * @methodOf ngMock.$httpBackend 1158 | * @description 1159 | * Creates a new request expectation for POST requests. For more info see `expect()`. 1160 | * 1161 | * @param {string|RegExp} url HTTP url. 1162 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1163 | * receives data string and returns true if the data is as expected, or Object if request body 1164 | * is in JSON format. 1165 | * @param {Object=} headers HTTP headers. 1166 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1167 | * request is handled. 1168 | */ 1169 | 1170 | /** 1171 | * @ngdoc method 1172 | * @name ngMock.$httpBackend#expectPUT 1173 | * @methodOf ngMock.$httpBackend 1174 | * @description 1175 | * Creates a new request expectation for PUT requests. For more info see `expect()`. 1176 | * 1177 | * @param {string|RegExp} url HTTP url. 1178 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1179 | * receives data string and returns true if the data is as expected, or Object if request body 1180 | * is in JSON format. 1181 | * @param {Object=} headers HTTP headers. 1182 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1183 | * request is handled. 1184 | */ 1185 | 1186 | /** 1187 | * @ngdoc method 1188 | * @name ngMock.$httpBackend#expectPATCH 1189 | * @methodOf ngMock.$httpBackend 1190 | * @description 1191 | * Creates a new request expectation for PATCH requests. For more info see `expect()`. 1192 | * 1193 | * @param {string|RegExp} url HTTP url. 1194 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1195 | * receives data string and returns true if the data is as expected, or Object if request body 1196 | * is in JSON format. 1197 | * @param {Object=} headers HTTP headers. 1198 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1199 | * request is handled. 1200 | */ 1201 | 1202 | /** 1203 | * @ngdoc method 1204 | * @name ngMock.$httpBackend#expectJSONP 1205 | * @methodOf ngMock.$httpBackend 1206 | * @description 1207 | * Creates a new request expectation for JSONP requests. For more info see `expect()`. 1208 | * 1209 | * @param {string|RegExp} url HTTP url. 1210 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1211 | * request is handled. 1212 | */ 1213 | createShortMethods('expect'); 1214 | 1215 | 1216 | /** 1217 | * @ngdoc method 1218 | * @name ngMock.$httpBackend#flush 1219 | * @methodOf ngMock.$httpBackend 1220 | * @description 1221 | * Flushes all pending requests using the trained responses. 1222 | * 1223 | * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, 1224 | * all pending requests will be flushed. If there are no pending requests when the flush method 1225 | * is called an exception is thrown (as this typically a sign of programming error). 1226 | */ 1227 | $httpBackend.flush = function(count) { 1228 | if (!responses.length) throw Error('No pending request to flush !'); 1229 | 1230 | if (angular.isDefined(count)) { 1231 | while (count--) { 1232 | if (!responses.length) throw Error('No more pending request to flush !'); 1233 | responses.shift()(); 1234 | } 1235 | } else { 1236 | while (responses.length) { 1237 | responses.shift()(); 1238 | } 1239 | } 1240 | $httpBackend.verifyNoOutstandingExpectation(); 1241 | }; 1242 | 1243 | 1244 | /** 1245 | * @ngdoc method 1246 | * @name ngMock.$httpBackend#verifyNoOutstandingExpectation 1247 | * @methodOf ngMock.$httpBackend 1248 | * @description 1249 | * Verifies that all of the requests defined via the `expect` api were made. If any of the 1250 | * requests were not made, verifyNoOutstandingExpectation throws an exception. 1251 | * 1252 | * Typically, you would call this method following each test case that asserts requests using an 1253 | * "afterEach" clause. 1254 | * 1255 | *
1256 |    *   afterEach($httpBackend.verifyNoOutstandingExpectation);
1257 |    * 
1258 | */ 1259 | $httpBackend.verifyNoOutstandingExpectation = function() { 1260 | if (expectations.length) { 1261 | throw Error('Unsatisfied requests: ' + expectations.join(', ')); 1262 | } 1263 | }; 1264 | 1265 | 1266 | /** 1267 | * @ngdoc method 1268 | * @name ngMock.$httpBackend#verifyNoOutstandingRequest 1269 | * @methodOf ngMock.$httpBackend 1270 | * @description 1271 | * Verifies that there are no outstanding requests that need to be flushed. 1272 | * 1273 | * Typically, you would call this method following each test case that asserts requests using an 1274 | * "afterEach" clause. 1275 | * 1276 | *
1277 |    *   afterEach($httpBackend.verifyNoOutstandingRequest);
1278 |    * 
1279 | */ 1280 | $httpBackend.verifyNoOutstandingRequest = function() { 1281 | if (responses.length) { 1282 | throw Error('Unflushed requests: ' + responses.length); 1283 | } 1284 | }; 1285 | 1286 | 1287 | /** 1288 | * @ngdoc method 1289 | * @name ngMock.$httpBackend#resetExpectations 1290 | * @methodOf ngMock.$httpBackend 1291 | * @description 1292 | * Resets all request expectations, but preserves all backend definitions. Typically, you would 1293 | * call resetExpectations during a multiple-phase test when you want to reuse the same instance of 1294 | * $httpBackend mock. 1295 | */ 1296 | $httpBackend.resetExpectations = function() { 1297 | expectations.length = 0; 1298 | responses.length = 0; 1299 | }; 1300 | 1301 | return $httpBackend; 1302 | 1303 | 1304 | function createShortMethods(prefix) { 1305 | angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { 1306 | $httpBackend[prefix + method] = function(url, headers) { 1307 | return $httpBackend[prefix](method, url, undefined, headers) 1308 | } 1309 | }); 1310 | 1311 | angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { 1312 | $httpBackend[prefix + method] = function(url, data, headers) { 1313 | return $httpBackend[prefix](method, url, data, headers) 1314 | } 1315 | }); 1316 | } 1317 | } 1318 | 1319 | function MockHttpExpectation(method, url, data, headers) { 1320 | 1321 | this.data = data; 1322 | this.headers = headers; 1323 | 1324 | this.match = function(m, u, d, h) { 1325 | if (method != m) return false; 1326 | if (!this.matchUrl(u)) return false; 1327 | if (angular.isDefined(d) && !this.matchData(d)) return false; 1328 | if (angular.isDefined(h) && !this.matchHeaders(h)) return false; 1329 | return true; 1330 | }; 1331 | 1332 | this.matchUrl = function(u) { 1333 | if (!url) return true; 1334 | if (angular.isFunction(url.test)) return url.test(u); 1335 | return url == u; 1336 | }; 1337 | 1338 | this.matchHeaders = function(h) { 1339 | if (angular.isUndefined(headers)) return true; 1340 | if (angular.isFunction(headers)) return headers(h); 1341 | return angular.equals(headers, h); 1342 | }; 1343 | 1344 | this.matchData = function(d) { 1345 | if (angular.isUndefined(data)) return true; 1346 | if (data && angular.isFunction(data.test)) return data.test(d); 1347 | if (data && !angular.isString(data)) return angular.toJson(data) == d; 1348 | return data == d; 1349 | }; 1350 | 1351 | this.toString = function() { 1352 | return method + ' ' + url; 1353 | }; 1354 | } 1355 | 1356 | function MockXhr() { 1357 | 1358 | // hack for testing $http, $httpBackend 1359 | MockXhr.$$lastInstance = this; 1360 | 1361 | this.open = function(method, url, async) { 1362 | this.$$method = method; 1363 | this.$$url = url; 1364 | this.$$async = async; 1365 | this.$$reqHeaders = {}; 1366 | this.$$respHeaders = {}; 1367 | }; 1368 | 1369 | this.send = function(data) { 1370 | this.$$data = data; 1371 | }; 1372 | 1373 | this.setRequestHeader = function(key, value) { 1374 | this.$$reqHeaders[key] = value; 1375 | }; 1376 | 1377 | this.getResponseHeader = function(name) { 1378 | // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last 1379 | var header = this.$$respHeaders[name]; 1380 | if (header) return header; 1381 | 1382 | name = angular.lowercase(name); 1383 | header = this.$$respHeaders[name]; 1384 | if (header) return header; 1385 | 1386 | header = undefined; 1387 | angular.forEach(this.$$respHeaders, function(headerVal, headerName) { 1388 | if (!header && angular.lowercase(headerName) == name) header = headerVal; 1389 | }); 1390 | return header; 1391 | }; 1392 | 1393 | this.getAllResponseHeaders = function() { 1394 | var lines = []; 1395 | 1396 | angular.forEach(this.$$respHeaders, function(value, key) { 1397 | lines.push(key + ': ' + value); 1398 | }); 1399 | return lines.join('\n'); 1400 | }; 1401 | 1402 | this.abort = angular.noop; 1403 | } 1404 | 1405 | 1406 | /** 1407 | * @ngdoc function 1408 | * @name ngMock.$timeout 1409 | * @description 1410 | * 1411 | * This service is just a simple decorator for {@link ng.$timeout $timeout} service 1412 | * that adds a "flush" method. 1413 | */ 1414 | 1415 | /** 1416 | * @ngdoc method 1417 | * @name ngMock.$timeout#flush 1418 | * @methodOf ngMock.$timeout 1419 | * @description 1420 | * 1421 | * Flushes the queue of pending tasks. 1422 | */ 1423 | 1424 | /** 1425 | * 1426 | */ 1427 | angular.mock.$RootElementProvider = function() { 1428 | this.$get = function() { 1429 | return angular.element('
'); 1430 | } 1431 | }; 1432 | 1433 | /** 1434 | * @ngdoc overview 1435 | * @name ngMock 1436 | * @description 1437 | * 1438 | * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful 1439 | * mocks to the {@link AUTO.$injector $injector}. 1440 | */ 1441 | angular.module('ngMock', ['ng']).provider({ 1442 | $browser: angular.mock.$BrowserProvider, 1443 | $exceptionHandler: angular.mock.$ExceptionHandlerProvider, 1444 | $log: angular.mock.$LogProvider, 1445 | $httpBackend: angular.mock.$HttpBackendProvider, 1446 | $rootElement: angular.mock.$RootElementProvider 1447 | }).config(function($provide) { 1448 | $provide.decorator('$timeout', function($delegate, $browser) { 1449 | $delegate.flush = function(delay) { 1450 | $browser.defer.flush(delay); 1451 | }; 1452 | return $delegate; 1453 | }); 1454 | }); 1455 | 1456 | 1457 | /** 1458 | * @ngdoc overview 1459 | * @name ngMockE2E 1460 | * @description 1461 | * 1462 | * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. 1463 | * Currently there is only one mock present in this module - 1464 | * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. 1465 | */ 1466 | angular.module('ngMockE2E', ['ng']).config(function($provide) { 1467 | $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); 1468 | }); 1469 | 1470 | /** 1471 | * @ngdoc object 1472 | * @name ngMockE2E.$httpBackend 1473 | * @description 1474 | * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of 1475 | * applications that use the {@link ng.$http $http service}. 1476 | * 1477 | * *Note*: For fake http backend implementation suitable for unit testing please see 1478 | * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. 1479 | * 1480 | * This implementation can be used to respond with static or dynamic responses via the `when` api 1481 | * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the 1482 | * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch 1483 | * templates from a webserver). 1484 | * 1485 | * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application 1486 | * is being developed with the real backend api replaced with a mock, it is often desirable for 1487 | * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch 1488 | * templates or static files from the webserver). To configure the backend with this behavior 1489 | * use the `passThrough` request handler of `when` instead of `respond`. 1490 | * 1491 | * Additionally, we don't want to manually have to flush mocked out requests like we do during unit 1492 | * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests 1493 | * automatically, closely simulating the behavior of the XMLHttpRequest object. 1494 | * 1495 | * To setup the application to run with this http backend, you have to create a module that depends 1496 | * on the `ngMockE2E` and your application modules and defines the fake backend: 1497 | * 1498 | *
1499 |  *   myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
1500 |  *   myAppDev.run(function($httpBackend) {
1501 |  *     phones = [{name: 'phone1'}, {name: 'phone2'}];
1502 |  *
1503 |  *     // returns the current list of phones
1504 |  *     $httpBackend.whenGET('/phones').respond(phones);
1505 |  *
1506 |  *     // adds a new phone to the phones array
1507 |  *     $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
1508 |  *       phones.push(angular.fromJson(data));
1509 |  *     });
1510 |  *     $httpBackend.whenGET(/^\/templates\//).passThrough();
1511 |  *     //...
1512 |  *   });
1513 |  * 
1514 | * 1515 | * Afterwards, bootstrap your app with this new module. 1516 | */ 1517 | 1518 | /** 1519 | * @ngdoc method 1520 | * @name ngMockE2E.$httpBackend#when 1521 | * @methodOf ngMockE2E.$httpBackend 1522 | * @description 1523 | * Creates a new backend definition. 1524 | * 1525 | * @param {string} method HTTP method. 1526 | * @param {string|RegExp} url HTTP url. 1527 | * @param {(string|RegExp)=} data HTTP request body. 1528 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1529 | * object and returns true if the headers match the current definition. 1530 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1531 | * control how a matched request is handled. 1532 | * 1533 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 1534 | * – The respond method takes a set of static data to be returned or a function that can return 1535 | * an array containing response status (number), response data (string) and response headers 1536 | * (Object). 1537 | * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` 1538 | * handler, will be pass through to the real backend (an XHR request will be made to the 1539 | * server. 1540 | */ 1541 | 1542 | /** 1543 | * @ngdoc method 1544 | * @name ngMockE2E.$httpBackend#whenGET 1545 | * @methodOf ngMockE2E.$httpBackend 1546 | * @description 1547 | * Creates a new backend definition for GET requests. For more info see `when()`. 1548 | * 1549 | * @param {string|RegExp} url HTTP url. 1550 | * @param {(Object|function(Object))=} headers HTTP headers. 1551 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1552 | * control how a matched request is handled. 1553 | */ 1554 | 1555 | /** 1556 | * @ngdoc method 1557 | * @name ngMockE2E.$httpBackend#whenHEAD 1558 | * @methodOf ngMockE2E.$httpBackend 1559 | * @description 1560 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 1561 | * 1562 | * @param {string|RegExp} url HTTP url. 1563 | * @param {(Object|function(Object))=} headers HTTP headers. 1564 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1565 | * control how a matched request is handled. 1566 | */ 1567 | 1568 | /** 1569 | * @ngdoc method 1570 | * @name ngMockE2E.$httpBackend#whenDELETE 1571 | * @methodOf ngMockE2E.$httpBackend 1572 | * @description 1573 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1574 | * 1575 | * @param {string|RegExp} url HTTP url. 1576 | * @param {(Object|function(Object))=} headers HTTP headers. 1577 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1578 | * control how a matched request is handled. 1579 | */ 1580 | 1581 | /** 1582 | * @ngdoc method 1583 | * @name ngMockE2E.$httpBackend#whenPOST 1584 | * @methodOf ngMockE2E.$httpBackend 1585 | * @description 1586 | * Creates a new backend definition for POST requests. For more info see `when()`. 1587 | * 1588 | * @param {string|RegExp} url HTTP url. 1589 | * @param {(string|RegExp)=} data HTTP request body. 1590 | * @param {(Object|function(Object))=} headers HTTP headers. 1591 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1592 | * control how a matched request is handled. 1593 | */ 1594 | 1595 | /** 1596 | * @ngdoc method 1597 | * @name ngMockE2E.$httpBackend#whenPUT 1598 | * @methodOf ngMockE2E.$httpBackend 1599 | * @description 1600 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1601 | * 1602 | * @param {string|RegExp} url HTTP url. 1603 | * @param {(string|RegExp)=} data HTTP request body. 1604 | * @param {(Object|function(Object))=} headers HTTP headers. 1605 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1606 | * control how a matched request is handled. 1607 | */ 1608 | 1609 | /** 1610 | * @ngdoc method 1611 | * @name ngMockE2E.$httpBackend#whenPATCH 1612 | * @methodOf ngMockE2E.$httpBackend 1613 | * @description 1614 | * Creates a new backend definition for PATCH requests. For more info see `when()`. 1615 | * 1616 | * @param {string|RegExp} url HTTP url. 1617 | * @param {(string|RegExp)=} data HTTP request body. 1618 | * @param {(Object|function(Object))=} headers HTTP headers. 1619 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1620 | * control how a matched request is handled. 1621 | */ 1622 | 1623 | /** 1624 | * @ngdoc method 1625 | * @name ngMockE2E.$httpBackend#whenJSONP 1626 | * @methodOf ngMockE2E.$httpBackend 1627 | * @description 1628 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1629 | * 1630 | * @param {string|RegExp} url HTTP url. 1631 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1632 | * control how a matched request is handled. 1633 | */ 1634 | angular.mock.e2e = {}; 1635 | angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$browser', createHttpBackendMock]; 1636 | 1637 | 1638 | angular.mock.clearDataCache = function() { 1639 | var key, 1640 | cache = angular.element.cache; 1641 | 1642 | for(key in cache) { 1643 | if (cache.hasOwnProperty(key)) { 1644 | var handle = cache[key].handle; 1645 | 1646 | handle && angular.element(handle.elem).unbind(); 1647 | delete cache[key]; 1648 | } 1649 | } 1650 | }; 1651 | 1652 | 1653 | 1654 | window.jasmine && (function(window) { 1655 | 1656 | afterEach(function() { 1657 | var spec = getCurrentSpec(); 1658 | var injector = spec.$injector; 1659 | 1660 | spec.$injector = null; 1661 | spec.$modules = null; 1662 | 1663 | if (injector) { 1664 | injector.get('$rootElement').unbind(); 1665 | injector.get('$browser').pollFns.length = 0; 1666 | } 1667 | 1668 | angular.mock.clearDataCache(); 1669 | 1670 | // clean up jquery's fragment cache 1671 | angular.forEach(angular.element.fragments, function(val, key) { 1672 | delete angular.element.fragments[key]; 1673 | }); 1674 | 1675 | MockXhr.$$lastInstance = null; 1676 | 1677 | angular.forEach(angular.callbacks, function(val, key) { 1678 | delete angular.callbacks[key]; 1679 | }); 1680 | angular.callbacks.counter = 0; 1681 | }); 1682 | 1683 | function getCurrentSpec() { 1684 | return jasmine.getEnv().currentSpec; 1685 | } 1686 | 1687 | function isSpecRunning() { 1688 | var spec = getCurrentSpec(); 1689 | return spec && spec.queue.running; 1690 | } 1691 | 1692 | /** 1693 | * @ngdoc function 1694 | * @name angular.mock.module 1695 | * @description 1696 | * 1697 | * *NOTE*: This function is also published on window for easy access.
1698 | * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. 1699 | * 1700 | * This function registers a module configuration code. It collects the configuration information 1701 | * which will be used when the injector is created by {@link angular.mock.inject inject}. 1702 | * 1703 | * See {@link angular.mock.inject inject} for usage example 1704 | * 1705 | * @param {...(string|Function)} fns any number of modules which are represented as string 1706 | * aliases or as anonymous module initialization functions. The modules are used to 1707 | * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. 1708 | */ 1709 | window.module = angular.mock.module = function() { 1710 | var moduleFns = Array.prototype.slice.call(arguments, 0); 1711 | return isSpecRunning() ? workFn() : workFn; 1712 | ///////////////////// 1713 | function workFn() { 1714 | var spec = getCurrentSpec(); 1715 | if (spec.$injector) { 1716 | throw Error('Injector already created, can not register a module!'); 1717 | } else { 1718 | var modules = spec.$modules || (spec.$modules = []); 1719 | angular.forEach(moduleFns, function(module) { 1720 | modules.push(module); 1721 | }); 1722 | } 1723 | } 1724 | }; 1725 | 1726 | /** 1727 | * @ngdoc function 1728 | * @name angular.mock.inject 1729 | * @description 1730 | * 1731 | * *NOTE*: This function is also published on window for easy access.
1732 | * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. 1733 | * 1734 | * The inject function wraps a function into an injectable function. The inject() creates new 1735 | * instance of {@link AUTO.$injector $injector} per test, which is then used for 1736 | * resolving references. 1737 | * 1738 | * See also {@link angular.mock.module module} 1739 | * 1740 | * Example of what a typical jasmine tests looks like with the inject method. 1741 | *
1742 |    *
1743 |    *   angular.module('myApplicationModule', [])
1744 |    *       .value('mode', 'app')
1745 |    *       .value('version', 'v1.0.1');
1746 |    *
1747 |    *
1748 |    *   describe('MyApp', function() {
1749 |    *
1750 |    *     // You need to load modules that you want to test,
1751 |    *     // it loads only the "ng" module by default.
1752 |    *     beforeEach(module('myApplicationModule'));
1753 |    *
1754 |    *
1755 |    *     // inject() is used to inject arguments of all given functions
1756 |    *     it('should provide a version', inject(function(mode, version) {
1757 |    *       expect(version).toEqual('v1.0.1');
1758 |    *       expect(mode).toEqual('app');
1759 |    *     }));
1760 |    *
1761 |    *
1762 |    *     // The inject and module method can also be used inside of the it or beforeEach
1763 |    *     it('should override a version and test the new version is injected', function() {
1764 |    *       // module() takes functions or strings (module aliases)
1765 |    *       module(function($provide) {
1766 |    *         $provide.value('version', 'overridden'); // override version here
1767 |    *       });
1768 |    *
1769 |    *       inject(function(version) {
1770 |    *         expect(version).toEqual('overridden');
1771 |    *       });
1772 |    *     ));
1773 |    *   });
1774 |    *
1775 |    * 
1776 | * 1777 | * @param {...Function} fns any number of functions which will be injected using the injector. 1778 | */ 1779 | window.inject = angular.mock.inject = function() { 1780 | var blockFns = Array.prototype.slice.call(arguments, 0); 1781 | var errorForStack = new Error('Declaration Location'); 1782 | return isSpecRunning() ? workFn() : workFn; 1783 | ///////////////////// 1784 | function workFn() { 1785 | var spec = getCurrentSpec(); 1786 | var modules = spec.$modules || []; 1787 | modules.unshift('ngMock'); 1788 | modules.unshift('ng'); 1789 | var injector = spec.$injector; 1790 | if (!injector) { 1791 | injector = spec.$injector = angular.injector(modules); 1792 | } 1793 | for(var i = 0, ii = blockFns.length; i < ii; i++) { 1794 | try { 1795 | injector.invoke(blockFns[i] || angular.noop, this); 1796 | } catch (e) { 1797 | if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; 1798 | throw e; 1799 | } finally { 1800 | errorForStack = null; 1801 | } 1802 | } 1803 | } 1804 | }; 1805 | })(window); 1806 | -------------------------------------------------------------------------------- /lib/angular.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.7 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(P,T,q){'use strict';function m(b,a,c){var d;if(b)if(H(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==m)b.forEach(a,c);else if(!b||typeof b.length!=="number"?0:typeof b.hasOwnProperty!="function"&&typeof b.constructor!="function"||b instanceof K||ca&&b instanceof ca||wa.call(b)!=="[object Object]"||typeof b.callee==="function")for(d=0;d=0&&b.splice(c,1);return a}function U(b,a){if(oa(b)||b&&b.$evalAsync&&b.$watch)throw Error("Can't copy Window or Scope");if(a){if(b===a)throw Error("Can't copy equivalent objects or arrays");if(E(b))for(var c=a.length=0;c2?ha.call(arguments,2):[];return H(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ha.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function ic(b,a){var c=a;/^\$+/.test(b)?c=q:oa(a)?c="$WINDOW":a&&T===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function da(b,a){return JSON.stringify(b, 13 | ic,a?" ":null)}function pb(b){return B(b)?JSON.parse(b):b}function Ua(b){b&&b.length!==0?(b=z(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;return b}function pa(b){b=u(b).clone();try{b.html("")}catch(a){}var c=u("
").append(b).html();try{return b[0].nodeType===3?z(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+z(b)})}catch(d){return z(c)}}function Va(b){var a={},c,d;m((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]), 14 | a[d]=y(c[1])?decodeURIComponent(c[1]):!0)});return a}function qb(b){var a=[];m(b,function(b,d){a.push(Wa(d,!0)+(b===!0?"":"="+Wa(b,!0)))});return a.length?a.join("&"):""}function Xa(b){return Wa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function jc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,h=["ng:app","ng-app","x-ng-app", 15 | "data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;m(h,function(a){h[a]=!0;c(T.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(m(b.querySelectorAll("."+a),c),m(b.querySelectorAll("."+a+"\\:"),c),m(b.querySelectorAll("["+a+"]"),c))});m(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):m(a.attributes,function(b){if(!e&&h[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}function rb(b,a){var c=function(){b=u(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement", 16 | b)}]);a.unshift("ng");var c=sb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(P&&!d.test(P.name))return c();P.name=P.name.replace(d,"");Ya.resumeBootstrap=function(b){m(b,function(b){a.push(b)});c()}}function Za(b,a){a=a||"_";return b.replace(kc,function(b,d){return(d?a:"")+b.toLowerCase()})}function $a(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"required")); 17 | return b}function qa(b,a,c){c&&E(b)&&(b=b[b.length-1]);$a(H(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function lc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,d,e){return function(){b[e||"push"]([c,d,arguments]);return k}}if(!e)throw Error("No module: "+d);var b=[],c=[],j=a("$injector", 18 | "invoke"),k={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:j,run:function(a){c.push(a);return this}};g&&j(g);return k})}})}function tb(b){return b.replace(mc,function(a,b,d,e){return e?d.toUpperCase(): 19 | d}).replace(nc,"Moz$1")}function ab(b,a){function c(){var e;for(var b=[this],c=a,h,f,i,j,k,l;b.length;){h=b.shift();f=0;for(i=h.length;f-1}function xb(b,a){a&&m(a.split(" "),function(a){b.className=Q((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+Q(a)+" "," "))})} 22 | function yb(b,a){a&&m(a.split(" "),function(a){if(!Ca(b,a))b.className=Q(b.className+" "+Q(a))})}function bb(b,a){if(a)for(var a=!a.nodeName&&y(a.length)&&!oa(a)?a:[a],c=0;c4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!")}else{if(i.cookie!==$){$=i.cookie;d=$.split("; ");r={};for(f=0;f0&&(a=unescape(e.substring(0,j)),r[a]===q&&(r[a]=unescape(e.substring(j+1))))}return r}};f.defer=function(a,b){var c; 34 | p++;c=l(function(){delete o[c];e(a)},b||0);o[c]=!0;return c};f.defer.cancel=function(a){return o[a]?(delete o[a],n(a),e(C),!0):!1}}function wc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new vc(b,d,a,c)}]}function xc(){this.$get=function(){function b(b,d){function e(a){if(a!=l){if(n){if(n==a)n=a.n}else n=a;g(a.n,a.p);g(a,l);l=a;l.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error("cacheId "+b+" taken");var h=0,f=v({},d,{id:b}),i={},j=d&& 35 | d.capacity||Number.MAX_VALUE,k={},l=null,n=null;return a[b]={put:function(a,b){var c=k[a]||(k[a]={key:a});e(c);w(b)||(a in i||h++,i[a]=b,h>j&&this.remove(n.key))},get:function(a){var b=k[a];if(b)return e(b),i[a]},remove:function(a){var b=k[a];if(b){if(b==l)l=b.p;if(b==n)n=b.n;g(b.n,b.p);delete k[a];delete i[a];h--}},removeAll:function(){i={};h=0;k={};l=n=null},destroy:function(){k=f=i=null;delete a[b]},info:function(){return v({},f,{size:h})}}}var a={};b.info=function(){var b={};m(a,function(a,e){b[e]= 36 | a.info()});return b};b.get=function(b){return a[b]};return b}}function yc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Db(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ",h=/^\s*(https?|ftp|mailto|file):/;this.directive=function i(d,e){B(d)?($a(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];m(a[d], 37 | function(a){try{var g=b.invoke(a);if(H(g))g={compile:I(g)};else if(!g.compile&&g.link)g.compile=I(g.link);g.priority=g.priority||0;g.name=g.name||d;g.require=g.require||g.controller&&g.name;g.restrict=g.restrict||"A";e.push(g)}catch(h){c(h)}});return e}])),a[d].push(e)):m(d,nb(i));return this};this.urlSanitizationWhitelist=function(a){return y(a)?(h=a,this):h};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document",function(b, 38 | j,k,l,n,o,p,s,t){function x(a,b,c){a instanceof u||(a=u(a));m(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\S+/)&&(a[c]=u(b).wrap("").parent()[0])});var d=A(a,b,a,c);return function(b,c){$a(b,"scope");for(var e=c?ua.clone.call(a):a,j=0,g=e.length;jr.priority)break;if(Y=r.scope)ta("isolated scope",J,r,D),L(Y)&&(M(D,"ng-isolate-scope"),J=r),M(D,"ng-scope"),s=s||r;F=r.name;if(Y=r.controller)y=y||{},ta("'"+F+"' controller",y[F],r,D),y[F]=r;if(Y=r.transclude)ta("transclusion",ja,r,D),ja=r,l=r.priority,Y=="element"?(W=u(b),D=c.$$element=u(T.createComment(" "+ 45 | F+": "+c[F]+" ")),b=D[0],C(e,u(W[0]),b),V=x(W,d,l)):(W=u(cb(b)).contents(),D.html(""),V=x(W,d));if(Y=r.template)if(ta("template",A,r,D),A=r,Y=Fb(Y),r.replace){W=u("
"+Q(Y)+"
").contents();b=W[0];if(W.length!=1||b.nodeType!==1)throw Error(g+Y);C(e,D,b);F={$attr:{}};a=a.concat(N(b,a.splice(v+1,a.length-(v+1)),F));$(c,F);z=a.length}else D.html(Y);if(r.templateUrl)ta("template",A,r,D),A=r,i=R(a.splice(v,a.length-v),i,D,c,e,r.replace,V),z=a.length;else if(r.compile)try{w=r.compile(D,c,V),H(w)? 46 | j(null,w):w&&j(w.pre,w.post)}catch(G){k(G,pa(D))}if(r.terminal)i.terminal=!0,l=Math.max(l,r.priority)}i.scope=s&&s.scope;i.transclude=ja&&V;return i}function r(d,e,g,j){var h=!1;if(a.hasOwnProperty(e))for(var o,e=b.get(e+c),l=0,p=e.length;lo.priority)&&o.restrict.indexOf(g)!=-1)d.push(o),h=!0}catch(n){k(n)}return h}function $(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b, 47 | function(b,g){g=="class"?(M(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):g=="style"?e.attr("style",e.attr("style")+";"+b):g.charAt(0)!="$"&&!a.hasOwnProperty(g)&&(a[g]=b,d[g]=c[g])})}function R(a,b,c,d,e,j,h){var i=[],k,o,p=c[0],t=a.shift(),s=v({},t,{controller:null,templateUrl:null,transclude:null,scope:null});c.html("");l.get(t.templateUrl,{cache:n}).success(function(l){var n,t,l=Fb(l);if(j){t=u("
"+Q(l)+"
").contents();n=t[0];if(t.length!=1||n.nodeType!==1)throw Error(g+l);l={$attr:{}}; 48 | C(e,c,n);N(n,a,l);$(d,l)}else n=p,c.html(l);a.unshift(s);k=J(a,n,d,h);for(o=A(c[0].childNodes,h);i.length;){var r=i.pop(),l=i.pop();t=i.pop();var ia=i.pop(),D=n;t!==p&&(D=cb(n),C(l,u(t),D));k(function(){b(o,ia,D,e,r)},ia,D,e,r)}i=null}).error(function(a,b,c,d){throw Error("Failed to load template: "+d.url);});return function(a,c,d,e,g){i?(i.push(c),i.push(d),i.push(e),i.push(g)):k(function(){b(o,c,d,e,g)},c,d,e,g)}}function F(a,b){return b.priority-a.priority}function ta(a,b,c,d){if(b)throw Error("Multiple directives ["+ 49 | b.name+", "+c.name+"] asking for "+a+" on: "+pa(d));}function y(a,b){var c=j(b,!0);c&&a.push({priority:0,compile:I(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);M(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function V(a,b,c,d){var e=j(c,!0);e&&b.push({priority:100,compile:I(function(a,b,c){b=c.$$observers||(c.$$observers={});d==="class"&&(e=j(c[d],!0));c[d]=q;(b[d]||(b[d]=[])).$$inter=!0;(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e, 50 | function(a){c.$set(d,a)})})})}function C(a,b,c){var d=b[0],e=d.parentNode,g,j;if(a){g=0;for(j=a.length;g 68 | 0){var e=R[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b,c,d,f){return(b=h(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),R.shift(),b):!1}function i(a){f(a)||e("is unexpected, expecting ["+a+"]",h())}function j(a,b){return function(c,d){return a(c,d,b)}}function k(a,b,c){return function(d,e){return b(d,e,a,c)}}function l(){for(var a=[];;)if(R.length>0&&!h("}",")",";","]")&&a.push(w()),!f(";"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e","<=",">="))a=k(a,b.fn,t());return a}function x(){for(var a=m(),b;b=f("*","/","%");)a=k(a,b.fn,m());return a}function m(){var a;return f("+")?A():(a=f("-"))?k(r,a.fn,m()):(a=f("!"))?j(a.fn,m()):A()}function A(){var a;if(f("("))a=w(),i(")");else if(f("["))a=N();else if(f("{"))a=J();else{var b=f();(a=b.fn)||e("not a primary expression",b)}for(var c;b=f("(","[",".");)b.text==="("?(a=y(a,c),c=null):b.text==="["?(c=a,a=V(a)):b.text==="."?(c=a,a=u(a)):e("IMPOSSIBLE");return a}function N(){var a= 71 | [];if(g().text!="]"){do a.push(F());while(f(","))}i("]");return function(b,c){for(var d=[],e=0;e1;d++){var e=a.shift(),g=b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]= 74 | c}function gb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,h=0;h7),hasEvent:function(c){if(c=="input"&&Z==9)return!1;if(w(a[c])){var e=b.document.createElement("div");a[c]="on"+c in e}return a[c]},csp:!1}}]}function Vc(){this.$get=I(P)}function Ob(b){var a={},c,d,e;if(!b)return a;m(b.split("\n"),function(b){e=b.indexOf(":");c=z(Q(b.substr(0, 92 | e)));d=Q(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Pb(b){var a=L(b)?b:q;return function(c){a||(a=Ob(b));return c?a[z(c)]||null:a}}function Qb(b,a,c){if(H(c))return c(b,a);m(c,function(c){b=c(b,a)});return b}function Wc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){B(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=pb(d,!0)));return d}],transformRequest:[function(a){return L(a)&&wa.apply(a)!=="[object File]"?da(a):a}], 93 | headers:{common:{Accept:"application/json, text/plain, */*","X-Requested-With":"XMLHttpRequest"},post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}}},e=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,i,j,k){function l(a){function c(a){var b=v({},a,{data:Qb(a.data,a.headers,f)});return 200<=a.status&&a.status<300?b:j.reject(b)}a.method=la(a.method);var e=a.transformRequest|| 94 | d.transformRequest,f=a.transformResponse||d.transformResponse,g=d.headers,g=v({"X-XSRF-TOKEN":b.cookies()["XSRF-TOKEN"]},g.common,g[z(a.method)],a.headers),e=Qb(a.data,Pb(g),e),i;w(a.data)&&delete g["Content-Type"];i=n(a,e,g);i=i.then(c,c);m(s,function(a){i=a(i)});i.success=function(b){i.then(function(c){b(c.data,c.status,c.headers,a)});return i};i.error=function(b){i.then(null,function(c){b(c.data,c.status,c.headers,a)});return i};return i}function n(b,c,d){function e(a,b,c){m&&(200<=a&&a<300?m.put(q, 95 | [a,b,Ob(c)]):m.remove(q));f(b,a,c);i.$apply()}function f(a,c,d){c=Math.max(c,0);(200<=c&&c<300?k.resolve:k.reject)({data:a,status:c,headers:Pb(d),config:b})}function h(){var a=za(l.pendingRequests,b);a!==-1&&l.pendingRequests.splice(a,1)}var k=j.defer(),n=k.promise,m,s,q=o(b.url,b.params);l.pendingRequests.push(b);n.then(h,h);b.cache&&b.method=="GET"&&(m=L(b.cache)?b.cache:p);if(m)if(s=m.get(q))if(s.then)return s.then(h,h),s;else E(s)?f(s[1],s[0],U(s[2])):f(s,200,{});else m.put(q,n);s||a(b.method, 96 | q,c,e,d,b.timeout,b.withCredentials);return n}function o(a,b){if(!b)return a;var c=[];fc(b,function(a,b){a==null||a==q||(L(a)&&(a=da(a)),c.push(encodeURIComponent(b)+"="+encodeURIComponent(a)))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var p=c("$http"),s=[];m(e,function(a){s.push(B(a)?k.get(a):k.invoke(a))});l.pendingRequests=[];(function(a){m(arguments,function(a){l[a]=function(b,c){return l(v(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){m(arguments,function(a){l[a]= 97 | function(b,c,d){return l(v(d||{},{method:a,url:b,data:c}))}})})("post","put");l.defaults=d;return l}]}function Xc(){this.$get=["$browser","$window","$document",function(b,a,c){return Yc(b,Zc,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]}function Yc(b,a,c,d,e,g){function h(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;Z?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror= 98 | d;e.body.appendChild(c)}return function(e,i,j,k,l,n,o){function p(a,c,d,e){c=(i.match(Hb)||["",g])[1]=="file"?d?200:404:c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(C)}b.$$incOutstandingRequestCount();i=i||b.url();if(z(e)=="jsonp"){var s="_"+(d.counter++).toString(36);d[s]=function(a){d[s].data=a};h(i.replace("JSON_CALLBACK","angular.callbacks."+s),function(){d[s].data?p(k,200,d[s].data):p(k,-2);delete d[s]})}else{var t=new a;t.open(e,i,!0);m(l,function(a,b){a&&t.setRequestHeader(b,a)}); 99 | var q;t.onreadystatechange=function(){if(t.readyState==4){var a=t.getAllResponseHeaders(),b=["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified","Pragma"];a||(a="",m(b,function(b){var c=t.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));p(k,q||t.status,t.responseText,a)}};if(o)t.withCredentials=!0;t.send(j||"");n>0&&c(function(){q=-1;t.abort()},n)}}}function $c(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0, 100 | maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","), 101 | AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function ad(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,i){var j=c.defer(),k=j.promise,l=y(i)&&!i,f=a.defer(function(){try{j.resolve(e())}catch(a){j.reject(a),d(a)}l||b.$apply()},f),i=function(){delete g[k.$$timeoutId]}; 102 | k.$$timeoutId=f;g[f]=j;k.then(i,i);return k}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Rb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Sb);a("date",Tb);a("filter",bd);a("json",cd);a("limitTo",dd);a("lowercase",ed);a("number",Ub);a("orderBy",Vb);a("uppercase",fd)}function bd(){return function(b, 103 | a){if(!E(b))return b;var c=[];c.check=function(a){for(var b=0;b-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;ce+1?h="0":(f=h,j=!0)}if(!j){h=(h.split(Xb)[1]||"").length;w(e)&&(e=Math.min(Math.max(a.minFrac,h),a.maxFrac));var h=Math.pow(10,e),b=Math.round(b*h)/h,b=(""+b).split(Xb),h=b[0],b=b[1]||"",j=0,k=a.lgSize, 106 | l=a.gSize;if(h.length>=k+l)for(var j=h.length-k,n=0;n0||e> 107 | -c)e+=c;e===0&&c==-12&&(e=12);return jb(e,a,d)}}function Ja(b,a){return function(c,d){var e=c["get"+b](),g=la(a?"SHORT"+b:b);return d[g][e]}}function Tb(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,h=0;b[9]&&(g=G(b[9]+b[10]),h=G(b[9]+b[11]));a.setUTCFullYear(G(b[1]),G(b[2])-1,G(b[3]));a.setUTCHours(G(b[4]||0)-g,G(b[5]||0)-h,G(b[6]||0),G(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c, 108 | e){var g="",h=[],f,i,e=e||"mediumDate",e=b.DATETIME_FORMATS[e]||e;B(c)&&(c=gd.test(c)?G(c):a(c));Qa(c)&&(c=new Date(c));if(!na(c))return c;for(;e;)(i=hd.exec(e))?(h=h.concat(ha.call(i,1)),e=h.pop()):(h.push(e),e=null);m(h,function(a){f=id[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function cd(){return function(b){return da(b,!0)}}function dd(){return function(b,a){if(!(b instanceof Array))return b;var a=G(a),c=[],d,e;if(!b||!(b instanceof Array))return c; 109 | a>b.length?a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;dn?(d.$setValidity("maxlength",!1),q):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(c);d.$formatters.push(c)}}function kb(b,a){b="ngClass"+b;return S(function(c,d,e){function g(b){if(a===!0||c.$index%2===a)i&&!fa(b,i)&&h(i),f(b);i=U(b)}function h(a){L(a)&& 115 | !E(a)&&(a=Ra(a,function(a,b){if(a)return b}));d.removeClass(E(a)?a.join(" "):a)}function f(a){L(a)&&!E(a)&&(a=Ra(a,function(a,b){if(a)return b}));a&&d.addClass(E(a)?a.join(" "):a)}var i=q;c.$watch(e[b],g,!0);e.$observe("class",function(){var a=c.$eval(e[b]);g(a,a)});b!=="ngClass"&&c.$watch("$index",function(d,g){var i=d&1;i!==g&1&&(i===a?f(c.$eval(e[b])):h(c.$eval(e[b])))})})}var z=function(b){return B(b)?b.toLowerCase():b},la=function(b){return B(b)?b.toUpperCase():b},Z=G((/msie (\d+)/.exec(z(navigator.userAgent))|| 116 | [])[1]),u,ca,ha=[].slice,Pa=[].push,wa=Object.prototype.toString,Ya=P.angular||(P.angular={}),sa,fb,aa=["0","0","0"];C.$inject=[];ma.$inject=[];fb=Z<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?la(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var kc=/[A-Z]/g,jd={full:"1.0.7",major:1,minor:0,dot:7,codeName:"monochromatic-rainbow"},Ba=K.cache={},Aa=K.expando="ng-"+(new Date).getTime(),oc=1,$b=P.document.addEventListener? 117 | function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},db=P.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},mc=/([\:\-\_]+(.))/g,nc=/^moz([A-Z])/,ua=K.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;this.bind("DOMContentLoaded",a);K(P).bind("load",a)},toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?u(this[b]):u(this[this.length+ 118 | b])},length:0,push:Pa,sort:[].sort,splice:[].splice},Ea={};m("multiple,selected,checked,disabled,readOnly,required".split(","),function(b){Ea[z(b)]=b});var Bb={};m("input,select,option,textarea,button,form".split(","),function(b){Bb[la(b)]=!0});m({data:wb,inheritedData:Da,scope:function(b){return Da(b,"$scope")},controller:zb,injector:function(b){return Da(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ca,css:function(b,a,c){a=tb(a);if(y(c))b.style[a]=c;else{var d;Z<=8&&(d= 119 | b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];Z<=8&&(d=d===""?q:d);return d}},attr:function(b,a,c){var d=z(a);if(Ea[d])if(y(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||C).specified?d:q;else if(y(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?q:b},prop:function(b,a,c){if(y(c))b[a]=c;else return b[a]},text:v(Z<9?function(b,a){if(b.nodeType==1){if(w(a))return b.innerText; 120 | b.innerText=a}else{if(w(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(w(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(w(a))return b.value;b.value=a},html:function(b,a){if(w(a))return b.innerHTML;for(var c=0,d=b.childNodes;c":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Mc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},ib={},Zc=P.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw Error("This browser does not support XMLHttpRequest."); 130 | };Rb.$inject=["$provide"];Sb.$inject=["$locale"];Ub.$inject=["$locale"];var Xb=".",id={yyyy:O("FullYear",4),yy:O("FullYear",2,0,!0),y:O("FullYear",1),MMMM:Ja("Month"),MMM:Ja("Month",!0),MM:O("Month",2,1),M:O("Month",1,1),dd:O("Date",2),d:O("Date",1),HH:O("Hours",2),H:O("Hours",1),hh:O("Hours",2,-12),h:O("Hours",1,-12),mm:O("Minutes",2),m:O("Minutes",1),ss:O("Seconds",2),s:O("Seconds",1),EEEE:Ja("Day"),EEE:Ja("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){var a= 131 | -1*a.getTimezoneOffset(),c=a>=0?"+":"";c+=jb(Math[a>0?"floor":"ceil"](a/60),2)+jb(Math.abs(a%60),2);return c}},hd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,gd=/^\d+$/;Tb.$inject=["$locale"];var ed=I(z),fd=I(la);Vb.$inject=["$parse"];var kd=I({restrict:"E",compile:function(a,c){Z<=8&&(!c.href&&!c.name&&c.$set("href",""),a.append(T.createComment("IE fix")));return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),lb={};m(Ea,function(a, 132 | c){var d=ea("ng-"+c);lb[d]=function(){return{priority:100,compile:function(){return function(a,g,h){a.$watch(h[d],function(a){h.$set(c,!!a)})}}}}});m(["src","href"],function(a){var c=ea("ng-"+a);lb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),Z&&e.prop(a,g[a]))})}}}});var Ma={$addControl:C,$removeControl:C,$setValidity:C,$setDirty:C};Yb.$inject=["$element","$attrs","$scope"];var Pa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E", 133 | controller:Yb,compile:function(){return{pre:function(a,d,h,f){if(!h.action){var i=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};$b(d[0],"submit",i);d.bind("$destroy",function(){c(function(){db(d[0],"submit",i)},0,!1)})}var j=d.parent().controller("form"),k=h.name||h.ngForm;k&&(a[k]=f);j&&d.bind("$destroy",function(){j.$removeControl(f);k&&(a[k]=q);v(f,Ma)})}}}};return a?v(U(d),{restrict:"EAC"}):d}]},ld=Pa(),md=Pa(!0),nd=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/, 134 | od=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,pd=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,bc={text:Oa,number:function(a,c,d,e,g,h){Oa(a,c,d,e,g,h);e.$parsers.push(function(a){var c=X(a);return c||pd.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),q)});e.$formatters.push(function(a){return X(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!X(a)&&ai?(e.$setValidity("max",!1),q):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return X(a)||Qa(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),q)})},url:function(a,c,d,e,g,h){Oa(a,c,d,e,g,h);a=function(a){return X(a)||nd.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a, 136 | c,d,e,g,h){Oa(a,c,d,e,g,h);a=function(a){return X(a)||od.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){w(d.name)&&c.attr("name",xa());c.bind("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,h=d.ngFalseValue;B(g)||(g=!0);B(h)||(h=!1);c.bind("click", 137 | function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:h})},hidden:C,button:C,submit:C,reset:C},cc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,h){h&&(bc[z(g.type)]||bc.text)(d,e,g,h,c,a)}}}],La="ng-valid",Ka="ng-invalid",Na="ng-pristine",Zb="ng-dirty",qd=["$scope","$exceptionHandler","$attrs","$element","$parse", 138 | function(a,c,d,e,g){function h(a,c){c=c?"-"+Za(c,"-"):"";e.removeClass((a?Ka:La)+c).addClass((a?La:Ka)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),i=f.assign;if(!i)throw Error(Eb+d.ngModel+" ("+pa(e)+")");this.$render=C;var j=e.inheritedData("$formController")||Ma,k=0,l=this.$error={};e.addClass(Na);h(!0);this.$setValidity=function(a, 139 | c){if(l[a]!==!c){if(c){if(l[a]&&k--,!k)h(!0),this.$valid=!0,this.$invalid=!1}else h(!1),this.$invalid=!0,this.$valid=!1,k++;l[a]=!c;h(c,a);j.$setValidity(a,c,this)}};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(Na).addClass(Zb),j.$setDirty();m(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,i(a,d),m(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};var n=this;a.$watch(function(){var c= 140 | f(a);if(n.$modelValue!==c){var d=n.$formatters,e=d.length;for(n.$modelValue=c;e--;)c=d[e](c);if(n.$viewValue!==c)n.$viewValue=c,n.$render()}})}],rd=function(){return{require:["ngModel","^?form"],controller:qd,link:function(a,c,d,e){var g=e[0],h=e[1]||Ma;h.$addControl(g);c.bind("$destroy",function(){h.$removeControl(g)})}}},sd=I({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),dc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required= 141 | !0;var g=function(a){if(d.required&&(X(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},td=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&m(a.split(g),function(a){a&&c.push(Q(a))});return c});e.$formatters.push(function(a){return E(a)?a.join(", "): 142 | q})}}},ud=/^(true|false|\d+)$/,vd=function(){return{priority:100,compile:function(a,c){return ud.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},wd=S(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==q?"":a)})}),xd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate", 143 | function(a){d.text(a)})}}],yd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],zd=kb("",!0),Ad=kb("Odd",0),Bd=kb("Even",1),Cd=S({compile:function(a,c){c.$set("ngCloak",q);a.removeClass("ng-cloak")}}),Dd=[function(){return{scope:!0,controller:"@"}}],Ed=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],ec={};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave".split(" "), 144 | function(a){var c=ea("ng-"+a);ec[c]=["$parse",function(d){return function(e,g,h){var f=d(h[c]);g.bind(z(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Fd=S(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),Gd=["$http","$templateCache","$anchorScroll","$compile",function(a,c,d,e){return{restrict:"ECA",terminal:!0,compile:function(g,h){var f=h.ngInclude||h.src,i=h.onload||"",j=h.autoscroll;return function(g,h){var n=0,o,p=function(){o&&(o.$destroy(),o=null);h.html("")}; 145 | g.$watch(f,function(f){var m=++n;f?a.get(f,{cache:c}).success(function(a){m===n&&(o&&o.$destroy(),o=g.$new(),h.html(a),e(h.contents())(o),y(j)&&(!j||g.$eval(j))&&d(),o.$emit("$includeContentLoaded"),g.$eval(i))}).error(function(){m===n&&p()}):p()})}}}}],Hd=S({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Id=S({terminal:!0,priority:1E3}),Jd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,h){var f=h.count,i=g.attr(h.$attr.when),j=h.offset|| 146 | 0,k=e.$eval(i),l={},n=c.startSymbol(),o=c.endSymbol();m(k,function(a,e){l[e]=c(a.replace(d,n+f+"-"+j+o))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(c in k||(c=a.pluralCat(c-j)),l[c](e,g,!0))},function(a){g.text(a)})}}}],Kd=S({transclude:"element",priority:1E3,terminal:!0,compile:function(a,c,d){return function(a,c,h){var f=h.ngRepeat,h=f.match(/^\s*(.+)\s+in\s+(.*)\s*$/),i,j,k;if(!h)throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '"+f+"'.");f= 147 | h[1];i=h[2];h=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!h)throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '"+f+"'.");j=h[3]||h[1];k=h[2];var l=new eb;a.$watch(function(a){var e,f,h=a.$eval(i),m=c,q=new eb,y,A,u,w,r,v;if(E(h))r=h||[];else{r=[];for(u in h)h.hasOwnProperty(u)&&u.charAt(0)!="$"&&r.push(u);r.sort()}y=r.length-1;e=0;for(f=r.length;ez;)u.pop().element.remove()}for(;r.length> 157 | x;)r.pop()[0].element.remove()}var i;if(!(i=s.match(d)))throw Error("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+s+"'.");var j=c(i[2]||i[1]),k=i[4]||i[6],l=i[5],m=c(i[3]||""),n=c(i[2]?i[1]:k),o=c(i[7]),r=[[{element:f,label:""}]];t&&(a(t)(e),t.removeClass("ng-scope"),t.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=o(e)||[],d={},h,i,j,m,s,t;if(p){i=[];m=0;for(t=r.length;m@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}'); 164 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngmodel-format", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "vital sign", 6 | "scripts": { 7 | "test": "grunt" 8 | }, 9 | "engines": { 10 | "node": ">=0.8.0" 11 | }, 12 | "devDependencies": { 13 | "grunt": "~0.4.1", 14 | "grunt-karma": "~0.6.2" 15 | }, 16 | "main": "app.js", 17 | "repository": { 18 | "type":"git", 19 | "url": "https://github.com/greengerong/ngmodel-format" 20 | }, 21 | 22 | "author": "", 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /src/ngmodel.format.js: -------------------------------------------------------------------------------- 1 | /****************************************** 2 | * * 3 | * Auth: green gerong * 4 | * Date: 2012 * 5 | * blog: http://greengerong.github.io/ * 6 | * github: https://github.com/greengerong * 7 | * * 8 | ******************************************/ 9 | 10 | 'use strict'; 11 | 12 | (function(gobal, angular) { 13 | angular.module('ngmodel.format', []) 14 | .constant("modelFormatConfig", { 15 | "currency": { 16 | "formatter": function(args) { 17 | var modelValue = args.$modelValue, 18 | filter = args.$filter, 19 | attrs = args.$attrs, 20 | $eval = args.$eval; 21 | 22 | var val = filter("currency")(modelValue); 23 | return attrs.prefixed && $eval(attrs.prefixed) ? val : val.substr(1); 24 | }, 25 | "parser": function(args) { 26 | var viewValue = args.$viewValue; 27 | var num = viewValue.replace(/[^0-9.]/g, ''); 28 | var result = parseFloat(num, 10); 29 | return isNaN(result) ? undefined : parseFloat(result.toFixed(2)); 30 | }, 31 | "isEmpty": function(value) { 32 | return !value.$modelValue; 33 | }, 34 | "keyDown": function(args) { 35 | var event = args.$event, 36 | viewValue = args.$viewValue, 37 | modelValue = args.$modelValue; 38 | 39 | if (!(gobal.keyHelper.smallKeyBoard(event) || gobal.keyHelper.numberKeyBpoard(event) || gobal.keyHelper.functionKeyBoard(event) || gobal.keyHelper.currencyKeyBoard(event, viewValue) || gobal.keyHelper.floatKeyBoard(event, viewValue))) { 40 | event.stopPropagation(); 41 | event.preventDefault(); 42 | } 43 | } 44 | }, 45 | "digit": { 46 | "formatter": function(args) { 47 | return args.$modelValue; 48 | }, 49 | "parser": function(args) { 50 | return args.$viewValue ? args.$viewValue.replace(/[^0-9]/g, '') : undefined; 51 | }, 52 | "isEmpty": function(value) { 53 | return !value.$modelValue; 54 | }, 55 | "keyDown": function(args) { 56 | var event = args.$event; 57 | 58 | if (!(gobal.keyHelper.smallKeyBoard(event) || gobal.keyHelper.numberKeyBpoard(event) || gobal.keyHelper.functionKeyBoard(event))) { 59 | event.stopPropagation(); 60 | event.preventDefault(); 61 | } 62 | } 63 | }, 64 | "int": { 65 | "formatter": function(args) { 66 | var modelValue = args.$modelValue, 67 | filter = args.$filter; 68 | return filter("number")(modelValue); 69 | }, 70 | "parser": function(args) { 71 | var val = parseInt(args.$viewValue.replace(/[^0-9]/g, ''), 10); 72 | return isNaN(val) ? undefined : val; 73 | }, 74 | "isEmpty": function(value) { 75 | return !value.$modelValue; 76 | }, 77 | "keyDown": function(args) { 78 | var event = args.$event; 79 | 80 | if (!(gobal.keyHelper.smallKeyBoard(event) || gobal.keyHelper.numberKeyBpoard(event) || gobal.keyHelper.functionKeyBoard(event))) { 81 | event.stopPropagation(); 82 | event.preventDefault(); 83 | } 84 | } 85 | }, 86 | "float": { 87 | "formatter": function(args) { 88 | var modelValue = args.$modelValue, 89 | filter = args.$filter; 90 | return filter("number")(modelValue); 91 | }, 92 | "parser": function(args) { 93 | var val = parseFloat(args.$viewValue.replace(/[^0-9.]/g, '')), 94 | ENOB = 3, 95 | tempNum = Math.pow(10, ENOB); 96 | return isNaN(val) ? undefined : Math.round(val * tempNum) / tempNum; 97 | }, 98 | "isEmpty": function(value) { 99 | return !value.$modelValue; 100 | }, 101 | "keyDown": function(args) { 102 | var event = args.$event, 103 | viewValue = args.$viewValue; 104 | 105 | if (!(gobal.keyHelper.smallKeyBoard(event) || gobal.keyHelper.numberKeyBpoard(event) || gobal.keyHelper.functionKeyBoard(event) || gobal.keyHelper.floatKeyBoard(event, viewValue))) { 106 | event.stopPropagation(); 107 | event.preventDefault(); 108 | } 109 | 110 | } 111 | }, 112 | "boolean": { 113 | "formatter": function(args) { 114 | var modelValue = args.$modelValue; 115 | if (!angular.isUndefined(modelValue)) { 116 | return modelValue.toString(); 117 | } 118 | }, 119 | "parser": function(args) { 120 | var viewValue = args.$viewValue; 121 | if (!angular.isUndefined(viewValue)) { 122 | return viewValue.trim() === "true"; 123 | } 124 | }, 125 | "isEmpty": function(value) { 126 | return angular.isUndefined(value); 127 | } 128 | } 129 | }) 130 | .directive("modelFormat", ["modelFormatConfig", "$filter", "$parse", 131 | function(modelFormatConfig, $filter, $parse) { 132 | return { 133 | require: 'ngModel', 134 | link: function(scope, element, attrs, ctrl) { 135 | var config = modelFormatConfig[attrs.modelFormat] || {}; 136 | 137 | 138 | var parseFuction = function(funKey) { 139 | if (attrs[funKey]) { 140 | var func = $parse(attrs[funKey]); 141 | return (function(args) { 142 | return func(scope, args); 143 | }); 144 | } 145 | return config[funKey]; 146 | }; 147 | 148 | var formatter = parseFuction("formatter"); 149 | var parser = parseFuction("parser"); 150 | var isEmpty = parseFuction("isEmpty"); 151 | var keyDown = parseFuction("keyDown"); 152 | var getModelValue = function() { 153 | return $parse(attrs.ngModel)(scope); 154 | }; 155 | 156 | if (keyDown) { 157 | element.bind("blur", function() { 158 | element.val(formatter({ 159 | "$modelValue": getModelValue(), 160 | "$filter": $filter, 161 | "$attrs": attrs, 162 | "$eval": scope.$eval 163 | })); 164 | }).bind("keydown", function(event) { 165 | keyDown({ 166 | "$event": event, 167 | "$viewValue": element.val(), 168 | "$modelValue": getModelValue(), 169 | "$attrs": attrs, 170 | "$eval": scope.$eval, 171 | "$ngModelCtrl": ctrl 172 | }); 173 | }); 174 | } 175 | 176 | 177 | ctrl.$parsers.push(function(viewValue) { 178 | return parser({ 179 | "$viewValue": viewValue, 180 | "$attrs": attrs, 181 | "$eval": scope.$eval 182 | }); 183 | }); 184 | 185 | ctrl.$formatters.push(function(value) { 186 | return formatter({ 187 | "$modelValue": value, 188 | "$filter": $filter, 189 | "$attrs": attrs, 190 | "$eval": scope.$eval 191 | }); 192 | }); 193 | 194 | ctrl.$isEmpty = function(value) { 195 | return isEmpty({ 196 | "$modelValue": value, 197 | "$attrs": attrs, 198 | "$eval": scope.$eval 199 | }); 200 | }; 201 | } 202 | }; 203 | } 204 | ]) 205 | .directive("checkBoxToArray", [ 206 | 207 | function() { 208 | return { 209 | restrict: "A", 210 | require: "ngModel", 211 | link: function(scope, element, attrs, ctrl) { 212 | var value = scope.$eval(attrs.checkBoxToArray); 213 | ctrl.$parsers.push(function(viewValue) { 214 | var modelValue = ctrl.$modelValue ? angular.copy(ctrl.$modelValue) : []; 215 | if (viewValue === true && modelValue.indexOf(value) === -1) { 216 | modelValue.push(value); 217 | } 218 | 219 | if (viewValue !== true && modelValue.indexOf(value) != -1) { 220 | modelValue.splice(modelValue.indexOf(value), 1); 221 | } 222 | 223 | return modelValue.sort(); 224 | }); 225 | 226 | ctrl.$formatters.push(function(modelValue) { 227 | return modelValue && modelValue.indexOf(value) != -1; 228 | }); 229 | 230 | ctrl.$isEmpty = function($modelValue) { 231 | return !$modelValue || $modelValue.length === 0; 232 | }; 233 | } 234 | } 235 | } 236 | ]); 237 | 238 | 239 | var smallKeyBoard = function(event) { 240 | var which = event.which; 241 | return (which >= 96 && which <= 105); 242 | }; 243 | 244 | var numberKeyBpoard = function(event) { 245 | var which = event.which; 246 | return (which >= 48 && which <= 57) && !event.shiftKey; 247 | }; 248 | 249 | var functionKeyBoard = function(event) { 250 | var which = event.which; 251 | return (which <= 40) || (navigator.platform.indexOf("Mac") > -1 && event.metaKey) || (navigator.platform.indexOf("Win") > -1 && event.ctrlKey); 252 | }; 253 | 254 | var currencyKeyBoard = function(event, viewValue) { 255 | var which = event.which; 256 | return (viewValue.toString().indexOf('$') === -1 && which === 52 && event.shiftKey); 257 | }; 258 | 259 | var floatKeyBoard = function(event, viewValue) { 260 | var which = event.which; 261 | return [188].indexOf(which) != -1 || (which === 190 || which === 110) && viewValue.toString().indexOf('.') === -1; 262 | } 263 | 264 | gobal.keyHelper = { 265 | smallKeyBoard: smallKeyBoard, 266 | numberKeyBpoard: numberKeyBpoard, 267 | functionKeyBoard: functionKeyBoard, 268 | currencyKeyBoard: currencyKeyBoard, 269 | floatKeyBoard: floatKeyBoard 270 | }; 271 | 272 | })(this, angular); 273 | -------------------------------------------------------------------------------- /test/ngmodel.format.spec.js: -------------------------------------------------------------------------------- 1 | describe('ngmodel.format', function () { 2 | 3 | beforeEach(module('ngmodel.format')); 4 | 5 | var $scope, $compile, elm; 6 | 7 | beforeEach(inject(function ($rootScope, _$compile_) { 8 | $scope = $rootScope; 9 | $compile = _$compile_; 10 | })); 11 | 12 | describe('currency format', function () { 13 | 14 | beforeEach(function () { 15 | var html = ''; 16 | elm = $compile(angular.element(html))($scope); 17 | $scope.$digest(); 18 | }); 19 | 20 | it('should be format when blur', function () { 21 | elm.val("12345.123").trigger("input").trigger("blur"); 22 | expect(elm.val()).toEqual("$12,345.12"); 23 | expect($scope.test).toEqual(12345.12); 24 | }); 25 | 26 | it('should be reject non-number', function () { 27 | elm.val("aa1.123").trigger("input").trigger("blur"); 28 | expect(elm.val()).toEqual("$1.12"); 29 | expect($scope.test).toEqual(1.12); 30 | }); 31 | }); 32 | 33 | describe('digit format', function () { 34 | 35 | beforeEach(function () { 36 | var html = ''; 37 | elm = $compile(angular.element(html))($scope); 38 | $scope.$digest(); 39 | }); 40 | 41 | it('should be format when blur', function () { 42 | elm.val("12345123").trigger("input").trigger("blur"); 43 | expect(elm.val()).toEqual("12345123"); 44 | expect($scope.test).toEqual("12345123"); 45 | }); 46 | 47 | it('should be reject non-number', function () { 48 | elm.val("aa1.123").trigger("input").trigger("blur"); 49 | expect(elm.val()).toEqual("1123"); 50 | expect($scope.test).toEqual("1123"); 51 | }); 52 | }); 53 | 54 | describe('int format', function () { 55 | 56 | describe(" default", function () { 57 | beforeEach(function () { 58 | var html = ''; 59 | elm = $compile(angular.element(html))($scope); 60 | $scope.$digest(); 61 | }); 62 | 63 | it('should be format when blur', function () { 64 | elm.val("12345123").trigger("input").trigger("blur"); 65 | expect(elm.val()).toEqual("12,345,123"); 66 | expect($scope.test).toEqual(12345123); 67 | }); 68 | 69 | it('should be reject non-number', function () { 70 | elm.val("aa1.123").trigger("input").trigger("blur"); 71 | expect(elm.val()).toEqual("1,123"); 72 | expect($scope.test).toEqual(1123); 73 | }); 74 | }); 75 | 76 | describe("customerl", function () { 77 | beforeEach(function () { 78 | var html = ''; 79 | $scope.formatter = function () { 80 | return "$0.00"; 81 | }; 82 | elm = $compile(angular.element(html))($scope); 83 | $scope.$digest(); 84 | }); 85 | 86 | it('should be format when blur by customer fomatter', function () { 87 | elm.val("12345123").trigger("input").trigger("blur"); 88 | expect(elm.val()).toEqual("$0.00"); 89 | expect($scope.test).toEqual(12345123); 90 | }); 91 | 92 | }); 93 | }); 94 | 95 | describe('float format', function () { 96 | 97 | beforeEach(function () { 98 | var html = ''; 99 | elm = $compile(angular.element(html))($scope); 100 | $scope.$digest(); 101 | }); 102 | 103 | it('should be format when blur', function () { 104 | elm.val("12345.123").trigger("input").trigger("blur"); 105 | expect(elm.val()).toEqual("12,345.123"); 106 | expect($scope.test).toEqual(12345.123); 107 | }); 108 | 109 | it('should be reject non-number', function () { 110 | elm.val("aa1.123").trigger("input").trigger("blur"); 111 | expect(elm.val()).toEqual("1.123"); 112 | expect($scope.test).toEqual(1.123); 113 | }); 114 | }); 115 | 116 | describe('boolean format', function () { 117 | 118 | beforeEach(function () { 119 | var html = '
' + 120 | '
'; 121 | elm = $compile(angular.element(html))($scope); 122 | $scope.$digest(); 123 | }); 124 | 125 | it('should get a boolean', function () { 126 | elm.find("input:eq(0)").attr("checked", true).trigger("click"); 127 | $scope.$digest(); 128 | expect($scope.test).toBeTruthy(); 129 | 130 | elm.find("input:eq(1)").attr("checked", true).trigger("click"); 131 | $scope.$digest(); 132 | expect($scope.test).toBeFalsy(); 133 | }); 134 | 135 | it('should be render to view', function () { 136 | $scope.test = true; 137 | $scope.$digest(); 138 | expect(elm.find("input:eq(0)").is(":checked")).toBeTruthy(); 139 | 140 | $scope.test = false; 141 | $scope.$digest(); 142 | expect(elm.find("input:eq(1)").is(":checked")).toBeTruthy(); 143 | 144 | $scope.test = undefined; 145 | $scope.$digest(); 146 | expect(elm.find("input:eq(0)").is(":checked")).toBeFalsy(); 147 | expect(elm.find("input:eq(1)").is(":checked")).toBeFalsy(); 148 | }); 149 | }); 150 | 151 | describe('check box to array', function () { 152 | 153 | beforeEach(function () { 154 | var html = '
' + 155 | '
'; 156 | elm = $compile(angular.element(html))($scope); 157 | $scope.$digest(); 158 | }); 159 | 160 | it('should get a boolean', function () { 161 | elm.find("input:eq(0)").attr("checked", true).trigger("click"); 162 | $scope.$digest(); 163 | expect($scope.test).toEqual([1]); 164 | 165 | elm.find("input:eq(1)").attr("checked", true).trigger("click"); 166 | $scope.$digest(); 167 | expect($scope.test).toEqual([1, 2]); 168 | 169 | elm.find("input").removeAttr("checked").trigger("click"); 170 | $scope.$digest(); 171 | expect($scope.test).toEqual([]); 172 | }); 173 | 174 | it('should be render to view', function () { 175 | $scope.test = [1, 2]; 176 | $scope.$apply(); 177 | expect(elm.find("input:eq(0)").is(":checked")).toBeTruthy(); 178 | expect(elm.find("input:eq(1)").is(":checked")).toBeTruthy(); 179 | 180 | $scope.test = [1]; 181 | $scope.$digest(); 182 | expect(elm.find("input:eq(0)").is(":checked")).toBeTruthy(); 183 | expect(elm.find("input:eq(1)").is(":checked")).toBeFalsy(); 184 | 185 | $scope.test = undefined; 186 | $scope.$digest(); 187 | expect(elm.find("input:eq(0)").is(":checked")).toBeFalsy(); 188 | expect(elm.find("input:eq(1)").is(":checked")).toBeFalsy(); 189 | }); 190 | }); 191 | }); 192 | --------------------------------------------------------------------------------