├── .gitignore ├── .bowerrc ├── demo ├── bower_components │ ├── todomvc-common │ │ ├── bower.json │ │ ├── bg.png │ │ ├── readme.md │ │ ├── .bower.json │ │ ├── base.js │ │ └── base.css │ ├── angular │ │ ├── angular.min.js.gzip │ │ ├── bower.json │ │ ├── .bower.json │ │ ├── angular-csp.css │ │ └── README.md │ └── angular-route │ │ ├── bower.json │ │ ├── .bower.json │ │ ├── README.md │ │ ├── angular-route.min.js │ │ ├── angular-route.min.js.map │ │ └── angular-route.js ├── bower.json ├── js │ ├── app.js │ ├── directives │ │ ├── todoBlur.js │ │ └── todoFocus.js │ ├── services │ │ └── todoStorage.js │ └── controllers │ │ └── todoCtrl.js ├── readme.md └── index.html ├── .travis.yml ├── test ├── test.conf.js └── unit.spec.js ├── bower.json ├── package.json ├── LICENSE ├── Gruntfile.js ├── CHANGELOG.md ├── angular-adaptive-speech.min.js ├── README.md ├── api.md ├── src └── angular-adaptive-speech.js └── angular-adaptive-speech.js /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /demo/bower_components/todomvc-common/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-common", 3 | "version": "0.1.9" 4 | } 5 | -------------------------------------------------------------------------------- /demo/bower_components/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janantala/angular-adaptive-speech/HEAD/demo/bower_components/todomvc-common/bg.png -------------------------------------------------------------------------------- /demo/bower_components/angular/angular.min.js.gzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janantala/angular-adaptive-speech/HEAD/demo/bower_components/angular/angular.min.js.gzip -------------------------------------------------------------------------------- /demo/bower_components/todomvc-common/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-common 2 | 3 | > Bower component for some common utilities we use in every app 4 | 5 | 6 | ## License 7 | 8 | MIT 9 | -------------------------------------------------------------------------------- /demo/bower_components/angular/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.2.6-build.2014+sha.277a5ea", 4 | "main": "./angular.js", 5 | "dependencies": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demo/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-angular-perf", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.2.5", 6 | "angular-route": "~1.2.5", 7 | "todomvc-common": "~0.1.4" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /demo/bower_components/angular-route/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-route", 3 | "version": "1.2.6-build.2014+sha.277a5ea", 4 | "main": "./angular-route.js", 5 | "dependencies": { 6 | "angular": "1.2.6-build.2014+sha.277a5ea" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demo/js/app.js: -------------------------------------------------------------------------------- 1 | /*global angular */ 2 | /*jshint unused:false */ 3 | 'use strict'; 4 | 5 | /** 6 | * The main TodoMVC app module 7 | * 8 | * @type {angular.Module} 9 | */ 10 | var todomvc = angular.module('todomvc', ['ngRoute', 'adaptive.speech']); 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | before_install: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | - npm install -g bower grunt-cli 9 | - npm install 10 | - bower install 11 | 12 | script: "grunt" 13 | -------------------------------------------------------------------------------- /demo/js/directives/todoBlur.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that executes an expression when the element it is applied to loses focus 6 | */ 7 | todomvc.directive('todoBlur', function () { 8 | return function (scope, elem, attrs) { 9 | elem.bind('blur', function () { 10 | scope.$apply(attrs.todoBlur); 11 | }); 12 | }; 13 | }); 14 | -------------------------------------------------------------------------------- /test/test.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | basePath: '..', 4 | files: [ 5 | 'bower_components/angular/angular.js', 6 | 'bower_components/angular-mocks/angular-mocks.js', 7 | 'src/angular-adaptive-speech.js', 8 | 'test/*.spec.js' 9 | ], 10 | frameworks: ['jasmine'], 11 | singleRun: true, 12 | browsers: [ 'Chrome' ] 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /demo/bower_components/todomvc-common/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-common", 3 | "version": "0.1.9", 4 | "homepage": "https://github.com/tastejs/todomvc-common", 5 | "_release": "0.1.9", 6 | "_resolution": { 7 | "type": "version", 8 | "tag": "v0.1.9", 9 | "commit": "7dd61b0ebf56c020e719a69444442cc7ae7242ff" 10 | }, 11 | "_source": "git://github.com/tastejs/todomvc-common.git", 12 | "_target": "~0.1.4" 13 | } -------------------------------------------------------------------------------- /demo/js/directives/todoFocus.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that places focus on the element it is applied to when the expression it binds to evaluates to true 6 | */ 7 | todomvc.directive('todoFocus', function ($timeout) { 8 | return function (scope, elem, attrs) { 9 | scope.$watch(attrs.todoFocus, function (newVal) { 10 | if (newVal) { 11 | $timeout(function () { 12 | elem[0].focus(); 13 | }, 0, false); 14 | } 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /demo/js/services/todoStorage.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Services that persists and retrieves TODOs from localStorage 6 | */ 7 | todomvc.factory('todoStorage', function () { 8 | var STORAGE_ID = 'todos-angularjs-perf'; 9 | 10 | return { 11 | get: function () { 12 | return JSON.parse(localStorage.getItem(STORAGE_ID) || '[]'); 13 | }, 14 | 15 | put: function (todos) { 16 | localStorage.setItem(STORAGE_ID, JSON.stringify(todos)); 17 | } 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /demo/bower_components/angular/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.2.6-build.2014", 4 | "main": "./angular.js", 5 | "dependencies": {}, 6 | "homepage": "https://github.com/angular/bower-angular", 7 | "_release": "1.2.6-build.2014", 8 | "_resolution": { 9 | "type": "version", 10 | "tag": "v1.2.6-build.2014+sha.277a5ea", 11 | "commit": "47f5df78de1d5037bb44060bdd388e5ec90414b7" 12 | }, 13 | "_source": "git://github.com/angular/bower-angular.git", 14 | "_target": "~1.2.5" 15 | } -------------------------------------------------------------------------------- /demo/bower_components/angular-route/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-route", 3 | "version": "1.2.6-build.2014", 4 | "main": "./angular-route.js", 5 | "dependencies": { 6 | "angular": "1.2.6-build.2014+sha.277a5ea" 7 | }, 8 | "homepage": "https://github.com/angular/bower-angular-route", 9 | "_release": "1.2.6-build.2014", 10 | "_resolution": { 11 | "type": "version", 12 | "tag": "v1.2.6-build.2014+sha.277a5ea", 13 | "commit": "637f000ab5d73c52f6d72c9d4f880decb73b35ee" 14 | }, 15 | "_source": "git://github.com/angular/bower-angular-route.git", 16 | "_target": "~1.2.5" 17 | } -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-adaptive-speech", 3 | "version": "0.3.0", 4 | "description": "This module allows you to control web app using speech commands.", 5 | "author": "https://github.com/angular-adaptive/adaptive-speech/graphs/contributors", 6 | "license": "MIT", 7 | "keywords": [ 8 | "angular", 9 | "adaptive", 10 | "speech" 11 | ], 12 | "homepage": "http://angular-adaptive.github.io", 13 | "main": "./src/angular-adaptive-speech.js", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/angular-adaptive/adaptive-speech.git" 17 | }, 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "test*", 23 | "demo*", 24 | "Gruntfile.js", 25 | "package.json" 26 | ], 27 | "dependencies": { 28 | "angular": "~1.2.10" 29 | }, 30 | "devDependencies": { 31 | "angular-mocks": "~1.2.10" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /demo/bower_components/angular/angular-csp.css: -------------------------------------------------------------------------------- 1 | /* Include this file in your html if you are using the CSP mode. */ 2 | 3 | @charset "UTF-8"; 4 | 5 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], 6 | .ng-cloak, .x-ng-cloak, 7 | .ng-hide { 8 | display: none !important; 9 | } 10 | 11 | ng\:form { 12 | display: block; 13 | } 14 | 15 | /* The styles below ensure that the CSS transition will ALWAYS 16 | * animate and close. A nasty bug occurs with CSS transitions where 17 | * when the active class isn't set, or if the active class doesn't 18 | * contain any styles to transition to, then, if ngAnimate is used, 19 | * it will appear as if the webpage is broken due to the forever hanging 20 | * animations. The border-spacing (!ie) and zoom (ie) CSS properties are 21 | * used below since they trigger a transition without making the browser 22 | * animate anything and they're both highly underused CSS properties */ 23 | .ng-animate-start { border-spacing:1px 1px; -ms-zoom:1.0001; } 24 | .ng-animate-active { border-spacing:0px 0px; -ms-zoom:1; } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-adaptive-speech", 3 | "version": "0.3.0", 4 | "description": "This module allows you to control web app using speech commands.", 5 | "author": "https://github.com/angular-adaptive/adaptive-speech/graphs/contributors", 6 | "license": "MIT", 7 | "keywords": [ 8 | "angular", 9 | "adaptive", 10 | "speech" 11 | ], 12 | "homepage": "http://angular-adaptive.github.io", 13 | "main": "./src/angular-adaptive-speech.js", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/angular-adaptive/adaptive-speech.git" 17 | }, 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "grunt": "~0.4.2", 21 | "karma": "~0.11.x", 22 | "grunt-karma": "~0.7.x", 23 | "karma-jasmine": "~0.1.5", 24 | "karma-firefox-launcher": "~0.1.x", 25 | "grunt-contrib-jshint": "~0.4.x", 26 | "grunt-conventional-changelog": "~1.0.x", 27 | "grunt-contrib-uglify": "~0.2.2", 28 | "grunt-contrib-concat": "~0.3.x", 29 | "load-grunt-tasks" : "~0.2.0" 30 | }, 31 | "scripts": {} 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 the Jan Antala, https://github.com/janantala 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /demo/bower_components/angular/README.md: -------------------------------------------------------------------------------- 1 | # bower-angular 2 | 3 | This repo is for distribution on `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | Install with `bower`: 10 | 11 | ```shell 12 | bower install angular 13 | ``` 14 | 15 | Add a ` 19 | ``` 20 | 21 | ## Documentation 22 | 23 | Documentation is available on the 24 | [AngularJS docs site](http://docs.angularjs.org/). 25 | 26 | ## License 27 | 28 | The MIT License 29 | 30 | Copyright (c) 2010-2012 Google, Inc. http://angularjs.org 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in 40 | all copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 48 | THE SOFTWARE. 49 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | require('load-grunt-tasks')(grunt); 3 | 4 | // Default task. 5 | grunt.registerTask('default', ['karma', 'jshint']); 6 | grunt.registerTask('build', ['karma', 'jshint', 'concat', 'uglify']); 7 | 8 | var karmaConfig = function(configFile, customOptions) { 9 | var options = { configFile: configFile, keepalive: true }; 10 | var travisOptions = process.env.TRAVIS && { browsers: ['Firefox'], reporters: 'dots' }; 11 | return grunt.util._.extend(options, customOptions, travisOptions); 12 | }; 13 | 14 | // Project configuration. 15 | grunt.initConfig({ 16 | pkg: grunt.file.readJSON('bower.json'), 17 | meta: { 18 | banner: '/*!\n' + 19 | ' * <%= pkg.name %> v<%= pkg.version %>\n' + 20 | ' * The MIT License\n' + 21 | ' * Copyright (c) 2014 Jan Antala\n' + 22 | ' */' 23 | }, 24 | uglify: { 25 | options: { 26 | preserveComments: 'some' 27 | }, 28 | dist: { 29 | src: '<%= pkg.name %>.js', 30 | dest: '<%= pkg.name %>.min.js' 31 | } 32 | }, 33 | concat: { 34 | options: { 35 | process: true, 36 | banner: '<%= meta.banner %>\n\n' 37 | }, 38 | dist: { 39 | src: 'src/<%= pkg.name %>.js', 40 | dest: '<%= pkg.name %>.js' 41 | } 42 | }, 43 | karma: { 44 | unit: { 45 | options: karmaConfig('test/test.conf.js') 46 | } 47 | }, 48 | jshint:{ 49 | files:['src/**/*.js', 'test/**/*.js'], 50 | options: { 51 | curly:true, 52 | eqeqeq:true, 53 | immed:true, 54 | latedef:true, 55 | newcap:true, 56 | noarg:true, 57 | sub:true, 58 | boss:true, 59 | eqnull:true, 60 | devel:true, 61 | globals:{} 62 | } 63 | }, 64 | changelog: { 65 | options: { 66 | dest: 'CHANGELOG.md' 67 | } 68 | }, 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /demo/bower_components/angular-route/README.md: -------------------------------------------------------------------------------- 1 | # bower-angular-route 2 | 3 | This repo is for distribution on `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngRoute). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | Install with `bower`: 10 | 11 | ```shell 12 | bower install angular-route 13 | ``` 14 | 15 | Add a ` 19 | ``` 20 | 21 | And add `ngRoute` as a dependency for your app: 22 | 23 | ```javascript 24 | angular.module('myApp', ['ngRoute']); 25 | ``` 26 | 27 | ## Documentation 28 | 29 | Documentation is available on the 30 | [AngularJS docs site](http://docs.angularjs.org/api/ngRoute). 31 | 32 | ## License 33 | 34 | The MIT License 35 | 36 | Copyright (c) 2010-2012 Google, Inc. http://angularjs.org 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a copy 39 | of this software and associated documentation files (the "Software"), to deal 40 | in the Software without restriction, including without limitation the rights 41 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 42 | copies of the Software, and to permit persons to whom the Software is 43 | furnished to do so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be included in 46 | all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 49 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 50 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 51 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 52 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 53 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 54 | THE SOFTWARE. 55 | -------------------------------------------------------------------------------- /demo/readme.md: -------------------------------------------------------------------------------- 1 | # AngularJS (Performance Optimized) TodoMVC Example using speech commands 2 | 3 | > HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application. The resulting environment is extraordinarily expressive, readable, and quick to develop. 4 | 5 | > _[AngularJS - angularjs.com](http://angularjs.com)_ 6 | 7 | 8 | ## Learning AngularJS 9 | The [AngularJS website](http://angularjs.com) is a great resource for getting started. 10 | 11 | Here are some links you may find helpful: 12 | 13 | * [Tutorial](http://docs.angularjs.org/tutorial) 14 | * [API Reference](http://docs.angularjs.org/api) 15 | * [Developer Guide](http://docs.angularjs.org/guide) 16 | * [Applications built with AngularJS](http://builtwith.angularjs.org) 17 | * [Blog](http://blog.angularjs.org) 18 | * [FAQ](http://docs.angularjs.org/misc/faq) 19 | * [AngularJS Meetups](http://www.youtube.com/angularjs) 20 | 21 | Articles and guides from the community: 22 | 23 | * [Code School AngularJS course](http://www.codeschool.com/code_tv/angularjs-part-1) 24 | * [5 Awesome AngularJS Features](http://net.tutsplus.com/tutorials/javascript-ajax/5-awesome-angularjs-features) 25 | * [Using Yeoman with AngularJS](http://briantford.com/blog/angular-yeoman.html) 26 | * [me&ngular - an introduction to MVW](http://stephenplusplus.github.io/meangular) 27 | 28 | Get help from other AngularJS users: 29 | 30 | * [Walkthroughs and Tutorials on YouTube](http://www.youtube.com/playlist?list=PL1w1q3fL4pmgqpzb-XhG7Clgi67d_OHXz) 31 | * [Google Groups mailing list](https://groups.google.com/forum/?fromgroups#!forum/angular) 32 | * [angularjs on Stack Overflow](http://stackoverflow.com/questions/tagged/angularjs) 33 | * [AngularJS on Twitter](https://twitter.com/angularjs) 34 | * [AngularjS on Google +](https://plus.google.com/+AngularJS/posts) 35 | 36 | _If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ 37 | 38 | 39 | ## Implementation 40 | The normal AngularJS TodoMVC implemetation performs deep watching of the todos array object. This means that it keeps an in-memory copy of the complete array that is used for dirty checking in order to detect model mutations. For smaller applications such as TodoMVC, this is completely fine as one trades off a little memory and performance for the sake of simplicity. 41 | 42 | In larger more complex applications however, where one might be working with 100s or 1000s of large objects one definitely should avoid using this approach. This implementation of the AngularJS app demonstrates the correct way to approach this problem when working in larger apps. 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## v0.3.0 (2014-01-31) 3 | 4 | 5 | #### Bug Fixes 6 | 7 | * **build:** 8 | * add karma-firefox-launcher as a dependency ([eb4e648a](https://github.com/angular-adaptive/adaptive-speech/commit/eb4e648a2d58ed35d30fe6d82012adaa8499ba98)) 9 | * add karma ~0.11.x as a dependecy ([a9fb88de](https://github.com/angular-adaptive/adaptive-speech/commit/a9fb88de4af076f9c9ad74dbe763cbd5caddfd84)) 10 | * add karma-jasmine as a dependecy ([9f1f89a6](https://github.com/angular-adaptive/adaptive-speech/commit/9f1f89a67d3b11a1289239e546dbbb00d32b1cd9)) 11 | 12 | 13 | #### Features 14 | 15 | * use localStorage for correctionMap ([b849be4e](https://github.com/angular-adaptive/adaptive-speech/commit/b849be4eb8da1fb3a9793e8f2f31f266ed38c378)) 16 | * add command method ([7d3fbab8](https://github.com/angular-adaptive/adaptive-speech/commit/7d3fbab8f3291400a0826e02e10c88f438bc6b21)) 17 | * payAttention has default true value ([df05af37](https://github.com/angular-adaptive/adaptive-speech/commit/df05af3716990ccc8f6f115ab76ff169e6917e1d)) 18 | * use $rootScope.$emit instead of $rootScope.$broadcast ([fe47e8bb](https://github.com/angular-adaptive/adaptive-speech/commit/fe47e8bbd960ea53c41d58ea7614f19bb6ddb1fc)) 19 | * **api:** create api.md ([15b11629](https://github.com/angular-adaptive/adaptive-speech/commit/15b1162929ac2d9e9a8959ef84a0096a44d4d977)) 20 | * **demo:** add correct utterance form ([5fa9f5eb](https://github.com/angular-adaptive/adaptive-speech/commit/5fa9f5eb70f7c5b33d6285cc7c25c28700bc8236)) 21 | * **speechCorrection:** create $speechCorrection provider ([7e7c7474](https://github.com/angular-adaptive/adaptive-speech/commit/7e7c7474a2e09946711e3a9c487c73a033aa2d1d)) 22 | * **speechRecognitionProvider:** 23 | * add method onUtterance ([7eb4e3a5](https://github.com/angular-adaptive/adaptive-speech/commit/7eb4e3a53dc7ea9ef42eb0fee3823817d6131a0f)) 24 | * add method setLang ([18d7309d](https://github.com/angular-adaptive/adaptive-speech/commit/18d7309d770827a37f7098e20093d9ea77e08922)) 25 | * **speechrecognition:** unbind listener on element destroy ([68110f9a](https://github.com/angular-adaptive/adaptive-speech/commit/68110f9a3d6f60d201d8fb5ab624e426be4b98e4)) 26 | 27 | 28 | ## v0.2.0 (2013-11-12) 29 | 30 | 31 | #### Features 32 | 33 | * **GruntFile:** add concated and uglified version ([4cbb131b](https://github.com/angular-adaptive/adaptive-speech/commit/4cbb131bc78ac202f4f962c015c726c2939efe7c)) 34 | * **grunt-conventional-changelog:** adds grunt-conventional-changelog ([ee1f72dd](https://github.com/angular-adaptive/adaptive-speech/commit/ee1f72dd941ed7cbb0d9140872afe0d29f21253a)) 35 | 36 | 37 | #### Breaking Changes 38 | 39 | * src/adaptive-speech.js is now renamed into src/angular-adaptive-speech.js 40 | ([4cbb131b](https://github.com/angular-adaptive/adaptive-speech/commit/4cbb131bc78ac202f4f962c015c726c2939efe7c)) 41 | 42 | 43 | -------------------------------------------------------------------------------- /angular-adaptive-speech.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * angular-adaptive-speech v0.3.0 3 | * The MIT License 4 | * Copyright (c) 2014 Jan Antala 5 | */ 6 | !function(){var a=function(a,b,c,d){d=d||".+";var e=[];angular.isArray(a)?e=a:e.push(a),e.forEach(function(a){if(a.lang!==b)return!1;var e=a.regex||null;c.match(e)&&c.match(new RegExp(d,"ig"))&&a.call(c)})},b=angular.module("adaptive.speech",[]);b.provider("$speechCorrection",function(){this.STORAGE_ID="adaptive:speech:correction",this.$get=function(){var a=this.STORAGE_ID,b=JSON.parse(localStorage.getItem(a)||"{}"),c=function(a,b){localStorage.setItem(a,JSON.stringify(b))},d=function(a,c,d){b[d]=b[d]||{},b[d][a]=c},e=function(a){delete b.lang[a]},f=function(a,c){b[a]=b[a]||{},b[a]=c},g=function(a){delete b[a]},h=function(){return b},i=function(a){return b[a]},j=function(a,c){return b[c]&&b[c][a]||a};return{addUtterance:function(e,f,g){d(e,f,g),c(a,b)},removeUtterance:function(d,f){e(d,f),c(a,b)},addLangMap:function(d,e){f(d,e),c(a,b)},clearLangMap:function(d){g(d),c(a,b)},getCorrectionMap:function(){return h()},getLangMap:function(a){return i(a)},getCorrection:function(a,b){return j(a,b)}}}}),b.provider("$speechSynthetis",function(){this.corsProxyServer="http://www.corsproxy.com/",this.$get=function(){var a=this.corsProxyServer,b=!1,c=function(c,d){if(!c)return!1;var e=[a,"translate.google.com/translate_tts?ie=UTF-8&q=",c,"&tl=",d].join(""),f=new Audio;f.addEventListener("play",function(){},!1),f.addEventListener("ended",function(){b=!0},!1),f.addEventListener("error",function(){},!1),f.autoplay=!0,f.src=e};return{speak:function(a,b){c(a,b)},justSpoke:function(){return b},recognised:function(){b=!1}}}}),b.provider("$speechRecognition",function(){this.DEST_LANG="en-US",this.setLang=function(a){this.DEST_LANG=a},this.$get=["$rootScope","$speechSynthetis","$speechCorrection",function(b,c,d){var e=this.DEST_LANG,f=function(){this.start=function(){this.onerror({code:0,error:"speech recognition is not supported"})}.bind(this),this.stop=function(){this.onerror({code:0,error:"speech recognition is not supported"})}.bind(this)};window.SpeechRecognition=window.SpeechRecognition||window.webkitSpeechRecognition||f;var g,h=function(){g=new window.SpeechRecognition,g.continuous=!0,g.interimResults=!0,g.maxAlternatives=3,g.onresult=function(a){r&&r(a)},g.onstart=function(a){i&&i(a)},g.onend=function(a){q&&q(a)},g.onerror=function(a){j&&j(a)}};h();var i,j,k=!0,l=!1,m=function(){l||(h(),g.start()),l=!0},n=function(){l&&g.stop(),l=!1},o=function(a){a=d.getCorrection(a,e),b.$emit("adaptive.speech:utterance",{lang:e,utterance:a})},p=function(a){e=a,g.lang=a},q=function(){k=!1,g=null},r=function(a){if(a.results.length){var b=a.results[a.resultIndex];if(b.isFinal){if(c.justSpoke())return c.recognised(),!1;var d=b[0].transcript.trim();k&&o(d)}}},s=function(c){return b.$on("adaptive.speech:utterance",function(b,d){var f=d.utterance;a(c,e,f)})};return{onstart:function(a){i=a},onerror:function(a){j=a},onUtterance:function(a){var c=b.$on("adaptive.speech:utterance",function(b,c){a(c.utterance)});b.$on("destroy",c)},setLang:function(a){p(a)},getLang:function(){return e},payAttention:function(){k=!0},ignore:function(){k=!1},listen:function(){m()},stopListening:function(){n()},command:function(a){o(a)},listenUtterance:function(a){return s(a)}}}]}),b.directive("speechrecognition",["$rootScope","$speechRecognition",function(b,c){return{restrict:"A",link:function(d,e,f){var g=function(){return angular.extend({},d.$eval(f.speechrecognition))},h=g(),i=b.$on("adaptive.speech:utterance",function(b,d){var e=c.getLang(),f=d.utterance;a(h.tasks,e,f,h.reference)});e.bind("$destroy",function(){i()})}}}])}(); -------------------------------------------------------------------------------- /demo/bower_components/angular-route/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.6-build.2014+sha.277a5ea 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(h,e,A){'use strict';function u(w,q,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,n){function y(){l&&(l.$destroy(),l=null);g&&(k.leave(g),g=null)}function v(){var b=w.current&&w.current.locals;if(b&&b.$template){var b=a.$new(),f=w.current;g=n(b,function(d){k.enter(d,null,g||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||q()});y()});l=f.scope=b;l.$emit("$viewContentLoaded");l.$eval(h)}else y()}var l,g,t=b.autoscroll,h=b.onload||"";a.$on("$routeChangeSuccess", 7 | v);v()}}}function z(e,h,k){return{restrict:"ECA",priority:-400,link:function(a,c){var b=k.current,f=b.locals;c.html(f.$template);var n=e(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));n(a)}}}h=e.module("ngRoute",["ng"]).provider("$route",function(){function h(a,c){return e.extend(new (e.extend(function(){},{prototype:a})),c)}function q(a,e){var b=e.caseInsensitiveMatch, 8 | f={originalPath:a,regexp:a},h=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?|\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var k={};this.when=function(a,c){k[a]=e.extend({reloadOnSearch:!0},c,a&&q(a,c));if(a){var b="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";k[b]=e.extend({redirectTo:a}, 9 | q(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,n,q,v,l){function g(){var d=t(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!x)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)x=!1,a.$broadcast("$routeChangeStart",d,m),(r.current=d)&&d.redirectTo&&(e.isString(d.redirectTo)? 10 | c.path(u(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?n.get(d):n.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=l.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl=b,c=q.get(b,{cache:v}).then(function(a){return a.data}))); 11 | e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function t(){var a,b;e.forEach(k,function(f,k){var p;if(p=!b){var s=c.path();p=f.keys;var l={};if(f.regexp)if(s=f.regexp.exec(s)){for(var g=1,q=s.length;g 41 | 42 | ``` 43 | 44 | Add the adaptive.speech module as a dependency to your application module: 45 | 46 | ```js 47 | var myAppModule = angular.module('MyApp', ['adaptive.speech']); 48 | ``` 49 | 50 | and include $speechRecognition, $speechSynthetis, $speechCorrection service as a dependency to your controller: 51 | 52 | ```js 53 | angular.module('MyApp').controller('MainCtrl', function ['$scope', '$speechRecognition, $speechSynthetis', ($scope, $speechRecognition, $speechSynthetis) { 54 | 55 | }]); 56 | ``` 57 | 58 | To start speech recognition run from controller: 59 | 60 | ```js 61 | $speechRecognition.onstart(function(){ 62 | $speechSynthetis.speak('Yes? How can I help you?', 'en-UK'); 63 | }); 64 | $speechRecognition.setLang('en-UK'); // Default value is en-US 65 | $speechRecognition.listen(); 66 | ``` 67 | 68 | Apply the directive to your elements where *reference* is keyword reference: 69 | 70 | ```html 71 | 76 | ``` 77 | Or run recognition directly from controller: 78 | 79 | ```js 80 | $speechRecognition.listenUtterance($scope.recognition['en-US']['addToList']); 81 | ``` 82 | 83 | ## Options 84 | 85 | All the speechRecognition options can be set up in your controller. 86 | 87 | ```js 88 | myAppModule.controller('MyController', function($scope) { 89 | $scope.recognition = {}; 90 | $scope.recognition['en-US'] = { 91 | 'addToList': { 92 | 'regex': /^to do .+/gi, 93 | 'lang': 'en-US', 94 | 'call': function(e){ 95 | $scope.addToList(e); 96 | } 97 | }, 98 | 'listTasks': [{ 99 | 'regex': /^complete .+/gi, 100 | 'lang': 'en-US', 101 | 'call': function(e){ 102 | $scope.completeTask(e); 103 | } 104 | },{ 105 | 'regex': /^remove .+/gi, 106 | 'lang': 'en-US', 107 | 'call': function(e){ 108 | $scope.removeTask(e); 109 | } 110 | }] 111 | }; 112 | }); 113 | ``` 114 | 115 | # APIs 116 | 117 | Check out [API docs](api.md). 118 | 119 | # Testing 120 | 121 | We use karma and jshint to ensure the quality of the code. The easiest way to run these checks is to use grunt: 122 | 123 | npm install -g grunt-cli 124 | npm install 125 | bower install 126 | grunt 127 | 128 | The karma task will try to open Chrome as a browser in which to run the tests. Make sure this is available or change the configuration in `test/test.config.js` 129 | 130 | # Contributing 131 | 132 | Pull requests are welcome. 133 | 134 | Make a PR against canary branch and don't bump any versions. 135 | 136 | Please respect the code style in place. 137 | 138 | # License 139 | 140 | The MIT License 141 | 142 | Copyright (c) 2014 [Jan Antala](http://www.janantala.com) 143 | -------------------------------------------------------------------------------- /api.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | ## $speechRecognition 4 | 5 | ### onstart(fn) 6 | On start event. 7 | ```js 8 | $speechRecognition.onstart(function(e){ 9 | // onstart 10 | }); 11 | ``` 12 | 13 | ### onerror(fn) 14 | On error event. 15 | ```js 16 | $speechRecognition.error(function(e){ 17 | // onerror 18 | }); 19 | ``` 20 | 21 | ### onUtterance(cb) 22 | On recognised utterance callback. 23 | ```js 24 | $speechRecognition.onUtterance(function(utterance){ 25 | console.log(utterance); // buy a milk 26 | }); 27 | ``` 28 | 29 | ### setLang(lang) 30 | Set recognition language. 31 | ```js 32 | $speechRecognition.setLang('en-US'); 33 | ``` 34 | 35 | To change language when recognition is already running you need to also restart recognizer: 36 | ```js 37 | $speechRecognition.stopListening(); 38 | $speechRecognition.listen(); 39 | ``` 40 | 41 | ### getLang() 42 | Get recognition language. 43 | ```js 44 | $speechRecognition.getLang(); // 'en-US' 45 | ``` 46 | ### payAttention() 47 | Continue speech recognition after pause caused by `ignore()`. You don't need user permision again. 48 | 49 | ```js 50 | $speechRecognition.payAttention(); 51 | ``` 52 | 53 | ### ignore() 54 | Pause speech recognition. 55 | ```js 56 | $speechRecognition.ignore(); 57 | ``` 58 | 59 | ### listen() 60 | Start speech recognition. User permission is required. 61 | ```js 62 | $speechRecognition.listen(); 63 | ``` 64 | 65 | ### stopListening() 66 | Stop speech recognition. 67 | ```js 68 | $speechRecognition.stopListening(); 69 | ``` 70 | 71 | ### command(utterance) 72 | Call uterance manually. 73 | ```js 74 | $speechRecognition.command('do something'); 75 | ``` 76 | 77 | ### listenUtterance(tasks) 78 | Add listener for task 79 | ```js 80 | var task = { 81 | 'regex': /^do .+/gi, 82 | 'lang': 'en-US', 83 | 'call': function(utterance){ 84 | // do something with utterance 'do something' 85 | } 86 | }; 87 | $speechRecognition.listenUtterance(task); 88 | ``` 89 | 90 | ## $speechSynthetis 91 | 92 | ### speak(text, lang) 93 | Speak utterance. 94 | 95 | ```js 96 | $speechSynthetis.speak('Hello there!', 'en-US'); 97 | ``` 98 | ### justSpoke() 99 | Return true after `speak()` has been called. 100 | ```js 101 | $speechSynthetis.justSpoke(); // true or false 102 | ``` 103 | 104 | ### recognised() 105 | Manualy mark speechSynthetis voice as recognised. justSpoke will be `true`. 106 | ```js 107 | $speechSynthetis.recognised(); 108 | ``` 109 | 110 | ## $speechCorrection 111 | Correct speech recognition. After incorrect recognition utterance will be corrected. 112 | 113 | ### addUtterance(utterance, correction, lang) 114 | Create a key - value pair with incorret recognition, correction and language. 115 | ```js 116 | $speechCorrection.addUtterance('to something', 'do something', 'en-US'); 117 | ``` 118 | 119 | ### removeUtterance(utterance, lang) 120 | Remove utterance correction. 121 | ```js 122 | $speechCorrection.removeUtterance('to something', 'en-US'); 123 | ``` 124 | 125 | ### addLangMap(lang, map) 126 | Add complete correction map for a language. 127 | ```js 128 | var map = { 129 | 'to something': 'do something', 130 | 'pseudo make me a sandwich': 'sudo make me a sandwich' 131 | }; 132 | $speechCorrection.addUtterance('en-US', map); 133 | ``` 134 | 135 | ### clearLangMap(lang) 136 | Remove language map. 137 | ```js 138 | $speechCorrection.clearLangMap('en-US'); 139 | ``` 140 | 141 | ### getCorrectionMap() 142 | Get correction map for all languages. 143 | ```js 144 | $speechCorrection.getCorrectionMap(); 145 | // { 146 | // 'en-US: { 147 | // 'to something': 'do something', 148 | // 'pseudo make me a sandwich': 'sudo make me a sandwich' 149 | // } 150 | // } 151 | ``` 152 | 153 | ### getLangMap(lang) 154 | Get correction map for a language. 155 | ```js 156 | $speechCorrection.getCorrectionMap('en-US'); 157 | // { 158 | // 'to something': 'do something', 159 | // 'pseudo make me a sandwich': 'sudo make me a sandwich' 160 | // } 161 | ``` 162 | 163 | ### getCorrection(utterance, lang) 164 | Get a single utterance correction. 165 | ```js 166 | $speechCorrection.getCorrection('pseudo make me a sandwich', 'en-US'); // 'sudo make me a sandwich' 167 | ``` 168 | 169 | ## speechrecognition directive 170 | Add listener to html element. 171 | - tasks: configuration object (*remove something*) 172 | - reference: element reference name (*something*) 173 | 174 | ```html 175 |
  • 176 | {{todo}} 177 |
  • 178 | ``` 179 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AngularJS • TodoMVC 7 | 8 | 9 | 10 | 11 |
    12 | 18 |
    19 | 20 | 21 |
      22 |
    • 23 |
      24 | 25 | 26 | 27 |
      28 |
      29 | 30 |
      31 |
    • 32 |
    33 |
    34 |
    35 | {{remainingCount}} 36 | 37 | 38 | 49 | 50 |
    51 |
    52 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /demo/bower_components/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | // Underscore's Template Module 5 | // Courtesy of underscorejs.org 6 | var _ = (function (_) { 7 | _.defaults = function (object) { 8 | if (!object) { 9 | return object; 10 | } 11 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 12 | var iterable = arguments[argsIndex]; 13 | if (iterable) { 14 | for (var key in iterable) { 15 | if (object[key] == null) { 16 | object[key] = iterable[key]; 17 | } 18 | } 19 | } 20 | } 21 | return object; 22 | } 23 | 24 | // By default, Underscore uses ERB-style template delimiters, change the 25 | // following template settings to use alternative delimiters. 26 | _.templateSettings = { 27 | evaluate : /<%([\s\S]+?)%>/g, 28 | interpolate : /<%=([\s\S]+?)%>/g, 29 | escape : /<%-([\s\S]+?)%>/g 30 | }; 31 | 32 | // When customizing `templateSettings`, if you don't want to define an 33 | // interpolation, evaluation or escaping regex, we need one that is 34 | // guaranteed not to match. 35 | var noMatch = /(.)^/; 36 | 37 | // Certain characters need to be escaped so that they can be put into a 38 | // string literal. 39 | var escapes = { 40 | "'": "'", 41 | '\\': '\\', 42 | '\r': 'r', 43 | '\n': 'n', 44 | '\t': 't', 45 | '\u2028': 'u2028', 46 | '\u2029': 'u2029' 47 | }; 48 | 49 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 50 | 51 | // JavaScript micro-templating, similar to John Resig's implementation. 52 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 53 | // and correctly escapes quotes within interpolated code. 54 | _.template = function(text, data, settings) { 55 | var render; 56 | settings = _.defaults({}, settings, _.templateSettings); 57 | 58 | // Combine delimiters into one regular expression via alternation. 59 | var matcher = new RegExp([ 60 | (settings.escape || noMatch).source, 61 | (settings.interpolate || noMatch).source, 62 | (settings.evaluate || noMatch).source 63 | ].join('|') + '|$', 'g'); 64 | 65 | // Compile the template source, escaping string literals appropriately. 66 | var index = 0; 67 | var source = "__p+='"; 68 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 69 | source += text.slice(index, offset) 70 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 71 | 72 | if (escape) { 73 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 74 | } 75 | if (interpolate) { 76 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 77 | } 78 | if (evaluate) { 79 | source += "';\n" + evaluate + "\n__p+='"; 80 | } 81 | index = offset + match.length; 82 | return match; 83 | }); 84 | source += "';\n"; 85 | 86 | // If a variable is not specified, place data values in local scope. 87 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 88 | 89 | source = "var __t,__p='',__j=Array.prototype.join," + 90 | "print=function(){__p+=__j.call(arguments,'');};\n" + 91 | source + "return __p;\n"; 92 | 93 | try { 94 | render = new Function(settings.variable || 'obj', '_', source); 95 | } catch (e) { 96 | e.source = source; 97 | throw e; 98 | } 99 | 100 | if (data) return render(data, _); 101 | var template = function(data) { 102 | return render.call(this, data, _); 103 | }; 104 | 105 | // Provide the compiled function source as a convenience for precompilation. 106 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 107 | 108 | return template; 109 | }; 110 | 111 | return _; 112 | })({}); 113 | 114 | if (location.hostname === 'todomvc.com') { 115 | window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); 116 | } 117 | 118 | function redirect() { 119 | if (location.hostname === 'tastejs.github.io') { 120 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 121 | } 122 | } 123 | 124 | function findRoot() { 125 | var base; 126 | 127 | [/labs/, /\w*-examples/].forEach(function (href) { 128 | var match = location.href.match(href); 129 | 130 | if (!base && match) { 131 | base = location.href.indexOf(match); 132 | } 133 | }); 134 | 135 | return location.href.substr(0, base); 136 | } 137 | 138 | function getFile(file, callback) { 139 | if (!location.host) { 140 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 141 | } 142 | 143 | var xhr = new XMLHttpRequest(); 144 | 145 | xhr.open('GET', findRoot() + file, true); 146 | xhr.send(); 147 | 148 | xhr.onload = function () { 149 | if (xhr.status === 200 && callback) { 150 | callback(xhr.responseText); 151 | } 152 | }; 153 | } 154 | 155 | function Learn(learnJSON, config) { 156 | if (!(this instanceof Learn)) { 157 | return new Learn(learnJSON, config); 158 | } 159 | 160 | var template, framework; 161 | 162 | if (typeof learnJSON !== 'object') { 163 | try { 164 | learnJSON = JSON.parse(learnJSON); 165 | } catch (e) { 166 | return; 167 | } 168 | } 169 | 170 | if (config) { 171 | template = config.template; 172 | framework = config.framework; 173 | } 174 | 175 | if (!template && learnJSON.templates) { 176 | template = learnJSON.templates.todomvc; 177 | } 178 | 179 | if (!framework && document.querySelector('[data-framework]')) { 180 | framework = document.querySelector('[data-framework]').getAttribute('data-framework'); 181 | } 182 | 183 | 184 | if (template && learnJSON[framework]) { 185 | this.frameworkJSON = learnJSON[framework]; 186 | this.template = template; 187 | 188 | this.append(); 189 | } 190 | } 191 | 192 | Learn.prototype.append = function () { 193 | var aside = document.createElement('aside'); 194 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 195 | aside.className = 'learn'; 196 | 197 | // Localize demo links 198 | var demoLinks = aside.querySelectorAll('.demo-link'); 199 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 200 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 201 | }); 202 | 203 | document.body.className = (document.body.className + ' learn-bar').trim(); 204 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 205 | }; 206 | 207 | redirect(); 208 | getFile('learn.json', Learn); 209 | })(); 210 | -------------------------------------------------------------------------------- /demo/js/controllers/todoCtrl.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /** 6 | * The main controller for the app. The controller: 7 | * - retrieves and persists the model via the todoStorage service 8 | * - exposes the model to the template and provides event handlers 9 | */ 10 | todomvc.controller('TodoCtrl', function TodoCtrl($scope, $location, todoStorage, filterFilter, $speechSynthetis, $speechRecognition, $speechCorrection) { 11 | var todos = $scope.todos = todoStorage.get(); 12 | 13 | $scope.newTodo = ''; 14 | $scope.remainingCount = filterFilter(todos, {completed: false}).length; 15 | $scope.editedTodo = null; 16 | 17 | if ($location.path() === '') { 18 | $location.path('/'); 19 | } 20 | 21 | $scope.location = $location; 22 | 23 | $scope.$watch('location.path()', function (path) { 24 | $scope.statusFilter = (path === '/active') ? 25 | { completed: false } : (path === '/completed') ? 26 | { completed: true } : null; 27 | }); 28 | 29 | $scope.$watch('remainingCount == 0', function (val) { 30 | $scope.allChecked = val; 31 | }); 32 | 33 | $scope.safeApply = function(fn) { 34 | var phase = this.$root.$$phase; 35 | if (phase == '$apply' || phase == '$digest') { 36 | if(fn && (typeof(fn) === 'function')) { 37 | fn(); 38 | } 39 | } else { 40 | this.$apply(fn); 41 | } 42 | }; 43 | 44 | $scope.addTodo = function () { 45 | var newTodo = $scope.newTodo.trim(); 46 | console.log(newTodo); 47 | if (newTodo.length === 0) { 48 | return; 49 | } 50 | 51 | todos.push({ 52 | title: newTodo, 53 | completed: false 54 | }); 55 | todoStorage.put(todos); 56 | console.log(todos); 57 | $scope.newTodo = ''; 58 | $scope.remainingCount++; 59 | }; 60 | 61 | $scope.editTodo = function (todo) { 62 | $scope.editedTodo = todo; 63 | }; 64 | 65 | $scope.doneEditing = function (todo) { 66 | $scope.editedTodo = null; 67 | todo.title = todo.title.trim(); 68 | 69 | if (!todo.title) { 70 | $scope.removeTodo(todo); 71 | } 72 | 73 | todoStorage.put(todos); 74 | }; 75 | 76 | $scope.removeTodo = function (todo) { 77 | $scope.remainingCount -= todo.completed ? 0 : 1; 78 | todos.splice(todos.indexOf(todo), 1); 79 | todoStorage.put(todos); 80 | }; 81 | 82 | $scope.todoCompleted = function (todo) { 83 | if (todo.completed) { 84 | $scope.remainingCount--; 85 | } else { 86 | $scope.remainingCount++; 87 | } 88 | todoStorage.put(todos); 89 | }; 90 | 91 | $scope.clearCompletedTodos = function () { 92 | $scope.todos = todos = todos.filter(function (val) { 93 | return !val.completed; 94 | }); 95 | todoStorage.put(todos); 96 | }; 97 | 98 | $scope.markAll = function (completed) { 99 | todos.forEach(function (todo) { 100 | todo.completed = completed; 101 | }); 102 | $scope.remainingCount = completed ? 0 : todos.length; 103 | todoStorage.put(todos); 104 | }; 105 | 106 | /** 107 | * Need to be added for speech recognition 108 | */ 109 | 110 | var findTodo = function(title){ 111 | for (var i=0; i 1) { 169 | $scope.newTodo = parts.slice(1).join(' '); 170 | $scope.addTodo(); 171 | $scope.safeApply(); 172 | } 173 | } 174 | }, 175 | 'show-all': { 176 | 'regex': /show.*all/gi, 177 | 'lang': 'en-US', 178 | 'call': function(utterance){ 179 | $location.path('/'); 180 | $scope.safeApply(); 181 | } 182 | }, 183 | 'show-active': { 184 | 'regex': /show.*active/gi, 185 | 'lang': 'en-US', 186 | 'call': function(utterance){ 187 | $location.path('/active'); 188 | $scope.safeApply(); 189 | } 190 | }, 191 | 'show-completed': { 192 | 'regex': /show.*complete/gi, 193 | 'lang': 'en-US', 194 | 'call': function(utterance){ 195 | $location.path('/completed'); 196 | $scope.safeApply(); 197 | } 198 | }, 199 | 'mark-all': { 200 | 'regex': /^mark/gi, 201 | 'lang': 'en-US', 202 | 'call': function(utterance){ 203 | $scope.markAll(1); 204 | $scope.safeApply(); 205 | } 206 | }, 207 | 'unmark-all': { 208 | 'regex': /^unmark/gi, 209 | 'lang': 'en-US', 210 | 'call': function(utterance){ 211 | $scope.markAll(1); 212 | $scope.safeApply(); 213 | } 214 | }, 215 | 'clear-completed': { 216 | 'regex': /clear.*/gi, 217 | 'lang': 'en-US', 218 | 'call': function(utterance){ 219 | $scope.clearCompletedTodos(); 220 | $scope.safeApply(); 221 | } 222 | }, 223 | 'listTasks': [{ 224 | 'regex': /^complete .+/gi, 225 | 'lang': 'en-US', 226 | 'call': function(utterance){ 227 | var parts = utterance.split(' '); 228 | if (parts.length > 1) { 229 | completeTodo(parts.slice(1).join(' ')); 230 | } 231 | } 232 | },{ 233 | 'regex': /^remove .+/gi, 234 | 'lang': 'en-US', 235 | 'call': function(utterance){ 236 | var parts = utterance.split(' '); 237 | if (parts.length > 1) { 238 | var todo = findTodo(parts.slice(1).join(' ')); 239 | console.log(todo); 240 | if (todo) { 241 | $scope.removeTodo(todo); 242 | $scope.safeApply(); 243 | } 244 | } 245 | } 246 | }] 247 | }; 248 | 249 | var ignoreUtterance = {}; 250 | ignoreUtterance['addToList'] = $speechRecognition.listenUtterance($scope.recognition['en-US']['addToList']); 251 | ignoreUtterance['show-all'] = $speechRecognition.listenUtterance($scope.recognition['en-US']['show-all']); 252 | ignoreUtterance['show-active'] = $speechRecognition.listenUtterance($scope.recognition['en-US']['show-active']); 253 | ignoreUtterance['show-completed'] = $speechRecognition.listenUtterance($scope.recognition['en-US']['show-completed']); 254 | ignoreUtterance['mark-all'] = $speechRecognition.listenUtterance($scope.recognition['en-US']['mark-all']); 255 | ignoreUtterance['unmark-all'] = $speechRecognition.listenUtterance($scope.recognition['en-US']['unmark-all']); 256 | ignoreUtterance['clear-completed'] = $speechRecognition.listenUtterance($scope.recognition['en-US']['clear-completed']); 257 | 258 | /* 259 | to ignore listener call returned function 260 | */ 261 | // ignoreUtterance['addToList'](); 262 | }); 263 | 264 | }()); 265 | -------------------------------------------------------------------------------- /demo/bower_components/angular-route/angular-route.min.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version":3, 3 | "file":"angular-route.min.js", 4 | "lineCount":13, 5 | "mappings":"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", 6 | "sources":["angular-route.js"], 7 | "names":["window","angular","undefined","ngViewFactory","$route","$anchorScroll","$animate","link","scope","$element","attr","ctrl","$transclude","cleanupLastView","currentScope","$destroy","currentElement","leave","update","locals","current","$template","newScope","$new","clone","enter","onNgViewEnter","isDefined","autoScrollExp","$eval","$emit","onloadExp","autoscroll","onload","$on","ngViewFillContentFactory","$compile","$controller","html","contents","controller","$scope","controllerAs","data","children","ngRouteModule","module","provider","$RouteProvider","inherit","parent","extra","extend","pathRegExp","path","opts","insensitive","caseInsensitiveMatch","ret","keys","replace","_","slash","key","option","optional","star","push","regexp","RegExp","routes","when","this.when","route","redirectPath","length","substr","otherwise","this.otherwise","params","$get","$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce","updateRoute","next","parseRoute","last","$$route","equals","pathParams","reloadOnSearch","forceReload","copy","$broadcast","redirectTo","isString","interpolate","search","url","then","resolve","template","templateUrl","forEach","value","get","invoke","isFunction","getTrustedResourceUrl","loadedTemplateUrl","response","all","error","match","m","exec","on","i","len","val","decodeURIComponent","name","string","result","split","segment","segmentMatch","join","reload","$evalAsync","$RouteParamsProvider","this.$get","directive","$inject"] 8 | } 9 | -------------------------------------------------------------------------------- /demo/bower_components/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | -ms-appearance: none; 18 | -o-appearance: none; 19 | appearance: none; 20 | } 21 | 22 | body { 23 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 24 | line-height: 1.4em; 25 | background: #eaeaea url('bg.png'); 26 | color: #4d4d4d; 27 | width: 550px; 28 | margin: 0 auto; 29 | -webkit-font-smoothing: antialiased; 30 | -moz-font-smoothing: antialiased; 31 | -ms-font-smoothing: antialiased; 32 | -o-font-smoothing: antialiased; 33 | font-smoothing: antialiased; 34 | } 35 | 36 | button, 37 | input[type="checkbox"] { 38 | outline: none; 39 | } 40 | 41 | #todoapp { 42 | background: #fff; 43 | background: rgba(255, 255, 255, 0.9); 44 | margin: 130px 0 40px 0; 45 | border: 1px solid #ccc; 46 | position: relative; 47 | border-top-left-radius: 2px; 48 | border-top-right-radius: 2px; 49 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 50 | 0 25px 50px 0 rgba(0, 0, 0, 0.15); 51 | } 52 | 53 | #todoapp:before { 54 | content: ''; 55 | border-left: 1px solid #f5d6d6; 56 | border-right: 1px solid #f5d6d6; 57 | width: 2px; 58 | position: absolute; 59 | top: 0; 60 | left: 40px; 61 | height: 100%; 62 | } 63 | 64 | #todoapp input::-webkit-input-placeholder { 65 | font-style: italic; 66 | } 67 | 68 | #todoapp input::-moz-placeholder { 69 | font-style: italic; 70 | color: #a9a9a9; 71 | } 72 | 73 | #todoapp h1 { 74 | position: absolute; 75 | top: -120px; 76 | width: 100%; 77 | font-size: 70px; 78 | font-weight: bold; 79 | text-align: center; 80 | color: #b3b3b3; 81 | color: rgba(255, 255, 255, 0.3); 82 | text-shadow: -1px -1px rgba(0, 0, 0, 0.2); 83 | -webkit-text-rendering: optimizeLegibility; 84 | -moz-text-rendering: optimizeLegibility; 85 | -ms-text-rendering: optimizeLegibility; 86 | -o-text-rendering: optimizeLegibility; 87 | text-rendering: optimizeLegibility; 88 | } 89 | 90 | #header { 91 | padding-top: 15px; 92 | border-radius: inherit; 93 | } 94 | 95 | #header:before { 96 | content: ''; 97 | position: absolute; 98 | top: 0; 99 | right: 0; 100 | left: 0; 101 | height: 15px; 102 | z-index: 2; 103 | border-bottom: 1px solid #6c615c; 104 | background: #8d7d77; 105 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); 106 | background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 107 | background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 108 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); 109 | border-top-left-radius: 1px; 110 | border-top-right-radius: 1px; 111 | } 112 | 113 | #new-todo, 114 | .edit { 115 | position: relative; 116 | margin: 0; 117 | width: 100%; 118 | font-size: 24px; 119 | font-family: inherit; 120 | line-height: 1.4em; 121 | border: 0; 122 | outline: none; 123 | color: inherit; 124 | padding: 6px; 125 | border: 1px solid #999; 126 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 127 | -moz-box-sizing: border-box; 128 | -ms-box-sizing: border-box; 129 | -o-box-sizing: border-box; 130 | box-sizing: border-box; 131 | -webkit-font-smoothing: antialiased; 132 | -moz-font-smoothing: antialiased; 133 | -ms-font-smoothing: antialiased; 134 | -o-font-smoothing: antialiased; 135 | font-smoothing: antialiased; 136 | } 137 | 138 | #new-todo { 139 | padding: 16px 16px 16px 60px; 140 | border: none; 141 | background: rgba(0, 0, 0, 0.02); 142 | z-index: 2; 143 | box-shadow: none; 144 | } 145 | 146 | #main { 147 | position: relative; 148 | z-index: 2; 149 | border-top: 1px dotted #adadad; 150 | } 151 | 152 | label[for='toggle-all'] { 153 | display: none; 154 | } 155 | 156 | #toggle-all { 157 | position: absolute; 158 | top: -42px; 159 | left: -4px; 160 | width: 40px; 161 | text-align: center; 162 | /* Mobile Safari */ 163 | border: none; 164 | } 165 | 166 | #toggle-all:before { 167 | content: '»'; 168 | font-size: 28px; 169 | color: #d9d9d9; 170 | padding: 0 25px 7px; 171 | } 172 | 173 | #toggle-all:checked:before { 174 | color: #737373; 175 | } 176 | 177 | #todo-list { 178 | margin: 0; 179 | padding: 0; 180 | list-style: none; 181 | } 182 | 183 | #todo-list li { 184 | position: relative; 185 | font-size: 24px; 186 | border-bottom: 1px dotted #ccc; 187 | } 188 | 189 | #todo-list li:last-child { 190 | border-bottom: none; 191 | } 192 | 193 | #todo-list li.editing { 194 | border-bottom: none; 195 | padding: 0; 196 | } 197 | 198 | #todo-list li.editing .edit { 199 | display: block; 200 | width: 506px; 201 | padding: 13px 17px 12px 17px; 202 | margin: 0 0 0 43px; 203 | } 204 | 205 | #todo-list li.editing .view { 206 | display: none; 207 | } 208 | 209 | #todo-list li .toggle { 210 | text-align: center; 211 | width: 40px; 212 | /* auto, since non-WebKit browsers doesn't support input styling */ 213 | height: auto; 214 | position: absolute; 215 | top: 0; 216 | bottom: 0; 217 | margin: auto 0; 218 | /* Mobile Safari */ 219 | border: none; 220 | -webkit-appearance: none; 221 | -ms-appearance: none; 222 | -o-appearance: none; 223 | appearance: none; 224 | } 225 | 226 | #todo-list li .toggle:after { 227 | content: '✔'; 228 | /* 40 + a couple of pixels visual adjustment */ 229 | line-height: 43px; 230 | font-size: 20px; 231 | color: #d9d9d9; 232 | text-shadow: 0 -1px 0 #bfbfbf; 233 | } 234 | 235 | #todo-list li .toggle:checked:after { 236 | color: #85ada7; 237 | text-shadow: 0 1px 0 #669991; 238 | bottom: 1px; 239 | position: relative; 240 | } 241 | 242 | #todo-list li label { 243 | white-space: pre; 244 | word-break: break-word; 245 | padding: 15px 60px 15px 15px; 246 | margin-left: 45px; 247 | display: block; 248 | line-height: 1.2; 249 | -webkit-transition: color 0.4s; 250 | transition: color 0.4s; 251 | } 252 | 253 | #todo-list li.completed label { 254 | color: #a9a9a9; 255 | text-decoration: line-through; 256 | } 257 | 258 | #todo-list li .destroy { 259 | display: none; 260 | position: absolute; 261 | top: 0; 262 | right: 10px; 263 | bottom: 0; 264 | width: 40px; 265 | height: 40px; 266 | margin: auto 0; 267 | font-size: 22px; 268 | color: #a88a8a; 269 | -webkit-transition: all 0.2s; 270 | transition: all 0.2s; 271 | } 272 | 273 | #todo-list li .destroy:hover { 274 | text-shadow: 0 0 1px #000, 275 | 0 0 10px rgba(199, 107, 107, 0.8); 276 | -webkit-transform: scale(1.3); 277 | -ms-transform: scale(1.3); 278 | transform: scale(1.3); 279 | } 280 | 281 | #todo-list li .destroy:after { 282 | content: '✖'; 283 | } 284 | 285 | #todo-list li:hover .destroy { 286 | display: block; 287 | } 288 | 289 | #todo-list li .edit { 290 | display: none; 291 | } 292 | 293 | #todo-list li.editing:last-child { 294 | margin-bottom: -1px; 295 | } 296 | 297 | #footer { 298 | color: #777; 299 | padding: 0 15px; 300 | position: absolute; 301 | right: 0; 302 | bottom: -31px; 303 | left: 0; 304 | height: 20px; 305 | z-index: 1; 306 | text-align: center; 307 | } 308 | 309 | #footer:before { 310 | content: ''; 311 | position: absolute; 312 | right: 0; 313 | bottom: 31px; 314 | left: 0; 315 | height: 50px; 316 | z-index: -1; 317 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 318 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 319 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 320 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 321 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 322 | } 323 | 324 | #todo-count { 325 | float: left; 326 | text-align: left; 327 | } 328 | 329 | #filters { 330 | margin: 0; 331 | padding: 0; 332 | list-style: none; 333 | position: absolute; 334 | right: 0; 335 | left: 0; 336 | } 337 | 338 | #filters li { 339 | display: inline; 340 | } 341 | 342 | #filters li a { 343 | color: #83756f; 344 | margin: 2px; 345 | text-decoration: none; 346 | } 347 | 348 | #filters li a.selected { 349 | font-weight: bold; 350 | } 351 | 352 | #clear-completed { 353 | float: right; 354 | position: relative; 355 | line-height: 20px; 356 | text-decoration: none; 357 | background: rgba(0, 0, 0, 0.1); 358 | font-size: 11px; 359 | padding: 0 10px; 360 | border-radius: 3px; 361 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 362 | } 363 | 364 | #clear-completed:hover { 365 | background: rgba(0, 0, 0, 0.15); 366 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 367 | } 368 | 369 | #info { 370 | margin: 65px auto 0; 371 | color: #a6a6a6; 372 | font-size: 12px; 373 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 374 | text-align: center; 375 | } 376 | 377 | #info a { 378 | color: inherit; 379 | } 380 | 381 | /* 382 | Hack to remove background from Mobile Safari. 383 | Can't use it globally since it destroys checkboxes in Firefox and Opera 384 | */ 385 | 386 | @media screen and (-webkit-min-device-pixel-ratio:0) { 387 | #toggle-all, 388 | #todo-list li .toggle { 389 | background: none; 390 | } 391 | 392 | #todo-list li .toggle { 393 | height: 40px; 394 | } 395 | 396 | #toggle-all { 397 | top: -56px; 398 | left: -15px; 399 | width: 65px; 400 | height: 41px; 401 | -webkit-transform: rotate(90deg); 402 | -ms-transform: rotate(90deg); 403 | transform: rotate(90deg); 404 | -webkit-appearance: none; 405 | appearance: none; 406 | } 407 | } 408 | 409 | .hidden { 410 | display: none; 411 | } 412 | 413 | hr { 414 | margin: 20px 0; 415 | border: 0; 416 | border-top: 1px dashed #C5C5C5; 417 | border-bottom: 1px dashed #F7F7F7; 418 | } 419 | 420 | .learn a { 421 | font-weight: normal; 422 | text-decoration: none; 423 | color: #b83f45; 424 | } 425 | 426 | .learn a:hover { 427 | text-decoration: underline; 428 | color: #787e7e; 429 | } 430 | 431 | .learn h3, 432 | .learn h4, 433 | .learn h5 { 434 | margin: 10px 0; 435 | font-weight: 500; 436 | line-height: 1.2; 437 | color: #000; 438 | } 439 | 440 | .learn h3 { 441 | font-size: 24px; 442 | } 443 | 444 | .learn h4 { 445 | font-size: 18px; 446 | } 447 | 448 | .learn h5 { 449 | margin-bottom: 0; 450 | font-size: 14px; 451 | } 452 | 453 | .learn ul { 454 | padding: 0; 455 | margin: 0 0 30px 25px; 456 | } 457 | 458 | .learn li { 459 | line-height: 20px; 460 | } 461 | 462 | .learn p { 463 | font-size: 15px; 464 | font-weight: 300; 465 | line-height: 1.3; 466 | margin-top: 0; 467 | margin-bottom: 0; 468 | } 469 | 470 | .quote { 471 | border: none; 472 | margin: 20px 0 60px 0; 473 | } 474 | 475 | .quote p { 476 | font-style: italic; 477 | } 478 | 479 | .quote p:before { 480 | content: '“'; 481 | font-size: 50px; 482 | opacity: .15; 483 | position: absolute; 484 | top: -20px; 485 | left: 3px; 486 | } 487 | 488 | .quote p:after { 489 | content: '”'; 490 | font-size: 50px; 491 | opacity: .15; 492 | position: absolute; 493 | bottom: -42px; 494 | right: 3px; 495 | } 496 | 497 | .quote footer { 498 | position: absolute; 499 | bottom: -40px; 500 | right: 0; 501 | } 502 | 503 | .quote footer img { 504 | border-radius: 3px; 505 | } 506 | 507 | .quote footer a { 508 | margin-left: 5px; 509 | vertical-align: middle; 510 | } 511 | 512 | .speech-bubble { 513 | position: relative; 514 | padding: 10px; 515 | background: rgba(0, 0, 0, .04); 516 | border-radius: 5px; 517 | } 518 | 519 | .speech-bubble:after { 520 | content: ''; 521 | position: absolute; 522 | top: 100%; 523 | right: 30px; 524 | border: 13px solid transparent; 525 | border-top-color: rgba(0, 0, 0, .04); 526 | } 527 | 528 | .learn-bar > .learn { 529 | position: absolute; 530 | width: 272px; 531 | top: 8px; 532 | left: -300px; 533 | padding: 10px; 534 | border-radius: 5px; 535 | background-color: rgba(255, 255, 255, .6); 536 | -webkit-transition-property: left; 537 | transition-property: left; 538 | -webkit-transition-duration: 500ms; 539 | transition-duration: 500ms; 540 | } 541 | 542 | @media (min-width: 899px) { 543 | .learn-bar { 544 | width: auto; 545 | margin: 0 0 0 300px; 546 | } 547 | 548 | .learn-bar > .learn { 549 | left: 8px; 550 | } 551 | 552 | .learn-bar #todoapp { 553 | width: 550px; 554 | margin: 130px auto 40px auto; 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /test/unit.spec.js: -------------------------------------------------------------------------------- 1 | describe('adaptive.speech', function() { 2 | 3 | describe('$speechSynthetisProvider', function() { 4 | var speechSynthetis; 5 | 6 | beforeEach(module('adaptive.speech', function($speechSynthetisProvider) { 7 | speechSynthetis = $speechSynthetisProvider; 8 | })); 9 | 10 | it('should be defined', inject(function() { 11 | expect(speechSynthetis).toBeDefined(); 12 | })); 13 | 14 | it('should have corsProxyServer variable', inject(function() { 15 | expect(speechSynthetis.corsProxyServer).toBeDefined(); 16 | expect(typeof speechSynthetis.corsProxyServer).toBe('string'); 17 | })); 18 | 19 | it('should have corsProxyServer string to match server url', inject(function() { 20 | expect(speechSynthetis.corsProxyServer).toMatch(/http(s)?:\/\/.+\//); 21 | })); 22 | 23 | it('should have $get method', inject(function() { 24 | expect(speechSynthetis.$get).toBeDefined(); 25 | expect(typeof speechSynthetis.$get).toBe('function'); 26 | })); 27 | }); 28 | 29 | 30 | describe('$speechSynthetis service', function() { 31 | 32 | beforeEach(module('adaptive.speech', function($speechSynthetisProvider) { 33 | // config provider 34 | })); 35 | 36 | it('should be defined', inject(function($speechSynthetis) { 37 | expect($speechSynthetis).toBeDefined(); 38 | })); 39 | 40 | it('should have public methods', inject(function($speechSynthetis) { 41 | expect($speechSynthetis.speak).toBeDefined(); 42 | expect($speechSynthetis.justSpoke).toBeDefined(); 43 | expect($speechSynthetis.recognised).toBeDefined(); 44 | 45 | expect(typeof $speechSynthetis.speak).toBe('function'); 46 | expect(typeof $speechSynthetis.justSpoke).toBe('function'); 47 | expect(typeof $speechSynthetis.recognised).toBe('function'); 48 | })); 49 | 50 | it('should not to speak at the start', inject(function($speechSynthetis) { 51 | expect($speechSynthetis.justSpoke()).toBe(false); 52 | })); 53 | }); 54 | 55 | 56 | describe('$speechRecognitionProvider', function() { 57 | var speechRecognition; 58 | 59 | beforeEach(module('adaptive.speech', function($speechRecognitionProvider) { 60 | speechRecognition = $speechRecognitionProvider; 61 | })); 62 | 63 | it('should be defined', inject(function() { 64 | expect(speechRecognition).toBeDefined(); 65 | })); 66 | 67 | it('should have DEST_LANG variable', inject(function() { 68 | expect(speechRecognition.DEST_LANG).toBeDefined(); 69 | expect(typeof speechRecognition.DEST_LANG).toBe('string'); 70 | expect(speechRecognition.DEST_LANG).toBe('en-US'); 71 | })); 72 | 73 | it('should have default value en-US', inject(function() { 74 | expect(speechRecognition.DEST_LANG).toBe('en-US'); 75 | })); 76 | 77 | it('should have setLang method', inject(function() { 78 | expect(speechRecognition.setLang).toBeDefined(); 79 | expect(typeof speechRecognition.setLang).toBe('function'); 80 | })); 81 | 82 | it('should change language', inject(function() { 83 | speechRecognition.setLang('sk-SK'); 84 | expect(speechRecognition.DEST_LANG).toBe('sk-SK'); 85 | })); 86 | 87 | it('should have $get method', inject(function() { 88 | expect(speechRecognition.$get).toBeDefined(); 89 | expect(typeof speechRecognition.$get).toBe('object'); 90 | })); 91 | }); 92 | 93 | 94 | describe('$speechRecognition service', function() { 95 | 96 | beforeEach(module('adaptive.speech', function($speechRecognitionProvider) { 97 | // config provider 98 | })); 99 | 100 | it('should be defined', inject(function($speechRecognition) { 101 | expect($speechRecognition).toBeDefined(); 102 | })); 103 | 104 | it('should have public methods', inject(function($speechRecognition) { 105 | expect($speechRecognition.onstart).toBeDefined(); 106 | expect($speechRecognition.onerror).toBeDefined(); 107 | expect($speechRecognition.onUtterance).toBeDefined(); 108 | expect($speechRecognition.setLang).toBeDefined(); 109 | expect($speechRecognition.getLang).toBeDefined(); 110 | expect($speechRecognition.payAttention).toBeDefined(); 111 | expect($speechRecognition.listen).toBeDefined(); 112 | expect($speechRecognition.stopListening).toBeDefined(); 113 | expect($speechRecognition.command).toBeDefined(); 114 | expect($speechRecognition.listenUtterance).toBeDefined(); 115 | 116 | expect(typeof $speechRecognition.onstart).toBe('function'); 117 | expect(typeof $speechRecognition.onerror).toBe('function'); 118 | expect(typeof $speechRecognition.onUtterance).toBe('function'); 119 | expect(typeof $speechRecognition.setLang).toBe('function'); 120 | expect(typeof $speechRecognition.getLang).toBe('function'); 121 | expect(typeof $speechRecognition.payAttention).toBe('function'); 122 | expect(typeof $speechRecognition.listen).toBe('function'); 123 | expect(typeof $speechRecognition.stopListening).toBe('function'); 124 | expect(typeof $speechRecognition.command).toBe('function'); 125 | expect(typeof $speechRecognition.listenUtterance).toBe('function'); 126 | })); 127 | 128 | it('should call functions', inject(function($speechRecognition) { 129 | spyOn($speechRecognition, 'onstart').andCallThrough(); 130 | spyOn($speechRecognition, 'onerror').andCallThrough(); 131 | spyOn($speechRecognition, 'onUtterance').andCallThrough(); 132 | spyOn($speechRecognition, 'payAttention').andCallThrough(); 133 | spyOn($speechRecognition, 'listen').andCallThrough(); 134 | spyOn($speechRecognition, 'stopListening').andCallThrough(); 135 | 136 | $speechRecognition.onstart(); 137 | expect($speechRecognition.onstart).toHaveBeenCalled(); 138 | $speechRecognition.onerror(); 139 | expect($speechRecognition.onerror).toHaveBeenCalled(); 140 | $speechRecognition.onUtterance(); 141 | expect($speechRecognition.onUtterance).toHaveBeenCalled(); 142 | $speechRecognition.payAttention(); 143 | expect($speechRecognition.payAttention).toHaveBeenCalled(); 144 | $speechRecognition.listen(); 145 | expect($speechRecognition.listen).toHaveBeenCalled(); 146 | $speechRecognition.stopListening(); 147 | expect($speechRecognition.stopListening).toHaveBeenCalled(); 148 | })); 149 | 150 | it('should change a language', inject(function($speechRecognition) { 151 | expect($speechRecognition.getLang()).toEqual('en-US'); 152 | $speechRecognition.setLang('sk-SK'); 153 | expect($speechRecognition.getLang()).toEqual('sk-SK'); 154 | })); 155 | 156 | it('should have called rootScope.$on', inject(function($speechRecognition, $rootScope) { 157 | spyOn($rootScope, '$on').andCallThrough(); 158 | 159 | $speechRecognition.listenUtterance(); 160 | expect($rootScope.$on).toHaveBeenCalled(); 161 | })); 162 | 163 | it('should have called rootScope.$emit', inject(function($speechRecognition, $rootScope) { 164 | spyOn($rootScope, '$emit').andCallThrough(); 165 | $speechRecognition.command('do something'); 166 | expect($rootScope.$emit).toHaveBeenCalled(); 167 | })); 168 | 169 | it('should call a function after recognition - object', inject(function($speechRecognition, $rootScope) { 170 | var calledCount = 0; 171 | var mockUtterance = {'lang': 'en-US', 'utterance': 'do something'}; 172 | var mockObject = { 173 | 'regex': /^do .+/gi, 174 | 'lang': 'en-US', 175 | 'call': function(utterance){ 176 | calledCount += 1; 177 | } 178 | }; 179 | 180 | $speechRecognition.listenUtterance(mockObject); 181 | expect(calledCount).toEqual(0); 182 | 183 | $rootScope.$emit('adaptive.speech:utterance', mockUtterance); 184 | expect(calledCount).toEqual(1); 185 | })); 186 | 187 | it('should call a function after recognition - array', inject(function($speechRecognition, $rootScope) { 188 | var calledCount = 0; 189 | var mockUtterance1 = {'lang': 'en-US', 'utterance': 'complete something'}; 190 | var mockUtterance2 = {'lang': 'en-US', 'utterance': 'clear'}; 191 | var mockArray = [{ 192 | 'regex': /^complete .+/gi, 193 | 'lang': 'en-US', 194 | 'call': function(utterance){ 195 | calledCount += 1; 196 | } 197 | },{ 198 | 'regex': /clear.*/gi, 199 | 'lang': 'en-US', 200 | 'call': function(utterance){ 201 | calledCount += 1; 202 | } 203 | }]; 204 | 205 | $speechRecognition.listenUtterance(mockArray); 206 | expect(calledCount).toEqual(0); 207 | 208 | $rootScope.$emit('adaptive.speech:utterance', mockUtterance1); 209 | expect(calledCount).toEqual(1); 210 | 211 | $rootScope.$emit('adaptive.speech:utterance', mockUtterance2); 212 | expect(calledCount).toEqual(2); 213 | })); 214 | 215 | }); 216 | 217 | 218 | 219 | describe('speechrecognition directive - object', function() { 220 | var elm, scope, calledCount; 221 | var rootscope; 222 | 223 | beforeEach(module('adaptive.speech')); 224 | 225 | beforeEach(inject(function($rootScope) { 226 | rootScope = $rootScope; 227 | spyOn(rootScope, '$on').andCallThrough(); 228 | spyOn(rootScope, '$emit').andCallThrough(); 229 | })); 230 | 231 | beforeEach(inject(function($rootScope, $compile) { 232 | 233 | elm = angular.element( 234 | '
  • ' + 235 | '
    ' + 236 | '' + 237 | '' + 238 | '' + 239 | '
    ' + 240 | '
    ' + 241 | '' + 242 | '
    ' + 243 | '
  • ' 244 | ); 245 | 246 | scope = $rootScope; 247 | 248 | scope.todo = { 249 | title: 'something', 250 | completed: false 251 | }; 252 | 253 | scope.mockObject = { 254 | 'regex': /^complete .+/gi, 255 | 'lang': 'en-US', 256 | 'call': function(utterance){ 257 | calledCount += 1; 258 | } 259 | }; 260 | 261 | $compile(elm)(scope); 262 | scope.$digest(); 263 | })); 264 | 265 | it('should have called rootScope.$on', function(){ 266 | expect(rootScope.$on).toHaveBeenCalled(); 267 | }); 268 | 269 | it('should call a function after recognition', function() { 270 | calledCount = 0; 271 | var mockUtterance = {'lang': 'en-US', 'utterance': 'complete something'}; 272 | 273 | rootScope.$emit('adaptive.speech:utterance', mockUtterance); 274 | expect(calledCount).toEqual(1); 275 | }); 276 | 277 | }); 278 | 279 | 280 | describe('speechrecognition directive - array', function() { 281 | var elm, scope, calledCount; 282 | var rootscope; 283 | 284 | beforeEach(module('adaptive.speech')); 285 | 286 | beforeEach(inject(function($rootScope) { 287 | rootScope = $rootScope; 288 | spyOn(rootScope, '$on').andCallThrough(); 289 | spyOn(rootScope, '$emit').andCallThrough(); 290 | })); 291 | 292 | beforeEach(inject(function($rootScope, $compile) { 293 | elm = angular.element( 294 | '
  • ' + 295 | '
    ' + 296 | '' + 297 | '' + 298 | '' + 299 | '
    ' + 300 | '
    ' + 301 | '' + 302 | '
    ' + 303 | '
  • ' 304 | ); 305 | 306 | scope = $rootScope; 307 | 308 | scope.todo = { 309 | title: 'something', 310 | completed: false 311 | }; 312 | 313 | scope.mockArray = [{ 314 | 'regex': /^complete .+/gi, 315 | 'lang': 'en-US', 316 | 'call': function(utterance){ 317 | calledCount += 1; 318 | } 319 | },{ 320 | 'regex': /^remove .+/gi, 321 | 'lang': 'en-US', 322 | 'call': function(utterance){ 323 | calledCount += 1; 324 | } 325 | }]; 326 | 327 | $compile(elm)(scope); 328 | scope.$digest(); 329 | })); 330 | 331 | it('should have called rootScope.$on', function(){ 332 | expect(rootScope.$on).toHaveBeenCalled(); 333 | }); 334 | 335 | it('should call a function after recognition', function() { 336 | calledCount = 0; 337 | var mockUtterance1 = {'lang': 'en-US', 'utterance': 'complete something'}; 338 | var mockUtterance2 = {'lang': 'en-US', 'utterance': 'remove something'}; 339 | 340 | rootScope.$emit('adaptive.speech:utterance', mockUtterance1); 341 | expect(calledCount).toEqual(1); 342 | 343 | rootScope.$emit('adaptive.speech:utterance', mockUtterance2); 344 | expect(calledCount).toEqual(2); 345 | }); 346 | 347 | }); 348 | }); 349 | -------------------------------------------------------------------------------- /src/angular-adaptive-speech.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var callCommands = function(tasks, DEST_LANG, utterance, reference){ 4 | reference = reference || '.+'; 5 | var commands = []; 6 | 7 | if (angular.isArray(tasks)) { 8 | commands = tasks; 9 | } 10 | else { 11 | commands.push(tasks); 12 | } 13 | 14 | commands.forEach(function(command){ 15 | if (command.lang !== DEST_LANG) { 16 | return false; 17 | } 18 | 19 | var regex = command.regex || null; 20 | 21 | if (utterance.match(regex)) { 22 | if (utterance.match(new RegExp(reference, 'ig'))) { 23 | command.call(utterance); 24 | } 25 | } 26 | }); 27 | }; 28 | 29 | /** 30 | * @ngdoc overview 31 | * @name adaptive.speech 32 | * 33 | * @description 34 | * `adaptive.speech` is an Angular module which provides you with speech recognition 35 | * API's. Use its service to control your web app using speech commands. It's based 36 | * on Chrome's speech recognition API. 37 | */ 38 | var adaptive = angular.module('adaptive.speech', []); 39 | 40 | adaptive.provider('$speechCorrection', function() { 41 | 42 | this.STORAGE_ID = 'adaptive:speech:correction'; 43 | this.$get = function() { 44 | 45 | var STORAGE_ID = this.STORAGE_ID; 46 | var correctionMap = JSON.parse(localStorage.getItem(STORAGE_ID) || '{}'); 47 | 48 | var save = function(STORAGE_ID, correctionMap){ 49 | localStorage.setItem(STORAGE_ID, JSON.stringify(correctionMap)); 50 | }; 51 | 52 | var addUtterance = function(utterance, correction, lang){ 53 | correctionMap[lang] = correctionMap[lang] || {}; 54 | correctionMap[lang][utterance] = correction; 55 | }; 56 | 57 | var removeUtterance = function(utterance, lang){ 58 | delete correctionMap.lang[utterance]; 59 | }; 60 | 61 | var addLangMap = function(lang, map){ 62 | correctionMap[lang] = correctionMap[lang] || {}; 63 | correctionMap[lang] = map; 64 | }; 65 | 66 | var clearLangMap = function(lang){ 67 | delete correctionMap[lang]; 68 | }; 69 | 70 | var getCorrectionMap = function(){ 71 | return correctionMap; 72 | }; 73 | 74 | var getLangMap = function(lang){ 75 | return correctionMap[lang]; 76 | }; 77 | 78 | var getCorrection = function(utterance, lang){ 79 | return ((correctionMap[lang] && correctionMap[lang][utterance]) || utterance); 80 | }; 81 | 82 | return { 83 | addUtterance: function(utterance, correction, lang){ 84 | addUtterance(utterance, correction, lang); 85 | save(STORAGE_ID, correctionMap); 86 | }, 87 | 88 | removeUtterance: function(utterance, lang){ 89 | removeUtterance(utterance, lang); 90 | save(STORAGE_ID, correctionMap); 91 | }, 92 | 93 | addLangMap: function(lang, map){ 94 | addLangMap(lang, map); 95 | save(STORAGE_ID, correctionMap); 96 | }, 97 | 98 | clearLangMap: function(lang){ 99 | clearLangMap(lang); 100 | save(STORAGE_ID, correctionMap); 101 | }, 102 | 103 | getCorrectionMap: function(){ 104 | return getCorrectionMap(); 105 | }, 106 | 107 | getLangMap: function(lang){ 108 | return getLangMap(lang); 109 | }, 110 | 111 | getCorrection: function(utterance, lang){ 112 | return getCorrection(utterance, lang); 113 | } 114 | }; 115 | 116 | }; 117 | }); 118 | 119 | adaptive.provider('$speechSynthetis', function() { 120 | 121 | this.corsProxyServer = 'http://www.corsproxy.com/'; 122 | 123 | this.$get = function() { 124 | 125 | var corsProxyServer = this.corsProxyServer; 126 | var justSpoke = false; 127 | 128 | /** 129 | * @ngdoc function 130 | * @name adaptive.speech.$speechSynthetis#speak 131 | * @methodOf apdative.speech.$speechSynthetis 132 | * 133 | * @description 134 | * Let's your computer speak to you. Simply pass a string with a text 135 | * you want your computer to say. 136 | * 137 | * @param {string} text Text 138 | * @param {string} lang Language 139 | */ 140 | var speak = function(text, lang){ 141 | if (!text) { 142 | return false; 143 | } 144 | 145 | var audioURL = [corsProxyServer, 'translate.google.com/translate_tts?ie=UTF-8&q=', text , '&tl=', lang].join(''); 146 | var audio = new Audio(); 147 | 148 | audio.addEventListener('play', function() { 149 | }, false); 150 | 151 | audio.addEventListener('ended', function() { 152 | justSpoke = true; 153 | }, false); 154 | 155 | audio.addEventListener('error', function() { 156 | }, false); 157 | 158 | audio.autoplay = true; 159 | audio.src = audioURL; 160 | }; 161 | 162 | return { 163 | speak: function(text, lang){ 164 | speak(text, lang); 165 | }, 166 | 167 | justSpoke: function(){ 168 | return justSpoke; 169 | }, 170 | 171 | recognised: function(){ 172 | justSpoke = false; 173 | } 174 | }; 175 | }; 176 | }); 177 | 178 | /** 179 | * @ngdoc object 180 | * @name adaptive.speech.$speechRecognitionProvider 181 | * 182 | * @description 183 | * The `$speechRecognitionProvider` provides an interface to configure `$speechRecognition 184 | * service for runtime. 185 | */ 186 | 187 | adaptive.provider('$speechRecognition', function() { 188 | 189 | this.DEST_LANG = 'en-US'; 190 | 191 | this.setLang = function(lang){ 192 | this.DEST_LANG = lang; 193 | }; 194 | 195 | /** 196 | * @ngdoc object 197 | * @name adaptive.speech.$speechRecognition 198 | * @requires $rootScope 199 | * 200 | * @description 201 | * The `$speechRecognition` service is your interface to communicate with underlying 202 | * native speech recognition implementations by the browser. It provides several methods 203 | * to for example paying attention and listening to what the user says, or it can 204 | * react on specific callbacks. 205 | */ 206 | this.$get = ['$rootScope', '$speechSynthetis', '$speechCorrection', function($rootScope, $speechSynthetis, $speechCorrection) { 207 | 208 | var DEST_LANG = this.DEST_LANG; 209 | 210 | var SpeechRecognitionMock = function(){ 211 | this.start = function() { this.onerror({'code': 0, 'error': 'speech recognition is not supported'}); }.bind(this); 212 | this.stop = function() { this.onerror({'code': 0, 'error': 'speech recognition is not supported'}); }.bind(this); 213 | }; 214 | 215 | window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition || SpeechRecognitionMock; 216 | 217 | var recognizer; 218 | 219 | var init = function(){ 220 | recognizer = new window.SpeechRecognition(); 221 | recognizer.continuous = true; 222 | recognizer.interimResults = true; 223 | recognizer.maxAlternatives = 3; 224 | 225 | recognizer.onresult = function(e) { 226 | if (onresult) { 227 | onresult(e); 228 | } 229 | }; 230 | 231 | recognizer.onstart = function(e) { 232 | if (onstart) { 233 | onstart(e); 234 | } 235 | }; 236 | 237 | recognizer.onend = function(e) { 238 | if (onend) { 239 | onend(e); 240 | } 241 | }; 242 | 243 | recognizer.onerror = function(e) { 244 | if (onerror) { 245 | onerror(e); 246 | } 247 | }; 248 | }; 249 | 250 | init(); 251 | 252 | var payingAttention = true; 253 | var isListening = false; 254 | 255 | /** 256 | * @ngdoc function 257 | * @name adaptive.speech.$speechRecognition#listen 258 | * @methodOf adaptive.speech.$speechRecognition 259 | * 260 | * @description 261 | * Starts the speech recognizer and listens for speech input. 262 | */ 263 | var listen = function(){ 264 | if (!isListening) { 265 | init(); 266 | recognizer.start(); 267 | } 268 | isListening = true; 269 | }; 270 | 271 | var stopListening = function(){ 272 | if (isListening) { 273 | recognizer.stop(); 274 | } 275 | isListening = false; 276 | }; 277 | 278 | var command = function(utterance){ 279 | utterance = $speechCorrection.getCorrection(utterance, DEST_LANG); 280 | $rootScope.$emit('adaptive.speech:utterance', {'lang': DEST_LANG, 'utterance': utterance}); 281 | }; 282 | 283 | /** 284 | * @ngdoc function 285 | * @name adaptive.speech.$speechRecognition#setLang 286 | * @methodOf adaptive.speech.$speechRecognition 287 | * 288 | * @description 289 | * Configures speech recognizer to use given language when trying to recognize 290 | * speech input. Default is `en-US`. 291 | * 292 | * @ 293 | */ 294 | var setLang = function(lang){ 295 | DEST_LANG = lang; 296 | recognizer.lang = lang; 297 | }; 298 | 299 | var onstart, onerror; 300 | 301 | var onend = function(e){ 302 | payingAttention = false; 303 | recognizer = null; 304 | }; 305 | 306 | var onresult = function(e){ 307 | if (e.results.length) { 308 | var result = e.results[e.resultIndex]; 309 | if (result.isFinal) { 310 | 311 | if ($speechSynthetis.justSpoke()) { 312 | $speechSynthetis.recognised(); 313 | return false; 314 | } 315 | 316 | var utterance = result[0].transcript.trim(); 317 | 318 | if (payingAttention) { 319 | command(utterance); 320 | } 321 | } 322 | } 323 | }; 324 | 325 | /** 326 | * @ngdoc function 327 | * @name adaptive.speech.$speechRecognition#listenUtterance 328 | * @methodOf adaptive.speech.$speechRecognition 329 | * 330 | * @description 331 | * With `$speechRecognition.listenUtterance()` you're able to setup several tasks 332 | * for the speech recognizer within your controller. `listenUtterance()` expects a 333 | * task description object, that holds defined tasks. A task needs an identifier, 334 | * a regex for the speech recognizer, as well as the language in which the speech 335 | * recognizer should interpret it. 336 | * 337 | * In addition one has to provide a function that will be called once the speech 338 | * recognizer recognizes the given pattern. 339 | * 340 | *
    341 |     * var app = angular.module('myApp', ['adaptive.speech']);
    342 |     *
    343 |     * app.controller('Ctrl', function ($speechRecognition) {
    344 |     *  
    345 |     *     $scope.recognition = {};
    346 |     *     $scope.recognition['en-US'] = {
    347 |     *       'addToList': {
    348 |     *           'regex': /^to do .+/gi,
    349 |     *           'lang': 'en-US',
    350 |     *           'call': function(e){
    351 |     *               $scope.addToList(e);
    352 |     *           }
    353 |     *       },
    354 |     *       'listTasks': [{
    355 |     *           'regex': /^complete .+/gi,
    356 |     *           'lang': 'en-US',
    357 |     *           'call': function(e){
    358 |     *               $scope.completeTask(e);
    359 |     *           }
    360 |     *       },{
    361 |     *           'regex': /^remove .+/gi,
    362 |     *           'lang': 'en-US',
    363 |     *           'call': function(e){
    364 |     *               $scope.removeTask(e);
    365 |     *           }
    366 |     *       }]
    367 |     *     };
    368 |     * });
    369 |     * 
    370 | * 371 | * @param {object} tasks Task definition object 372 | */ 373 | var listenUtterance = function(tasks){ 374 | return $rootScope.$on('adaptive.speech:utterance', function(e, data){ 375 | var utterance = data.utterance; 376 | callCommands(tasks, DEST_LANG, utterance); 377 | }); 378 | }; 379 | 380 | return { 381 | /** 382 | * @ngdoc function 383 | * @name adaptive.speech.$speechRecognition#onstart 384 | * @method adaptive.speech.$speechRecognition 385 | * 386 | * @description 387 | * Exepts a function which gets executed once `$speechRecognition` service 388 | * starts listening to speech input. 389 | * 390 | *
    391 |       * var app = angular.module('myApp', ['adaptive.speech']);
    392 |       *
    393 |       * app.controller('Ctrl', function ($speechRecognition, $speechSynthetis) {
    394 |       *   $speechRecognition.onstart(function() {
    395 |       *      $speechSynthetis.speak('Yes?, How can I help you?);
    396 |       *   });
    397 |       * });
    398 |       * 
    399 | * 400 | * @param {object} onstartFn Function callback 401 | */ 402 | onstart: function(fn){ 403 | onstart = fn; 404 | }, 405 | 406 | onerror: function(fn){ 407 | onerror = fn; 408 | }, 409 | 410 | onUtterance: function(cb){ 411 | var unbind = $rootScope.$on('adaptive.speech:utterance', function(e, data){ 412 | cb(data.utterance); 413 | }); 414 | 415 | $rootScope.$on('destroy', unbind); 416 | }, 417 | 418 | setLang: function(lang){ 419 | setLang(lang); 420 | }, 421 | 422 | /** 423 | * @ngdoc function 424 | * @name adaptive.speech.$speechRecognition#getLang 425 | * @methodOf adaptive.speech.$speechRecognition 426 | * 427 | * @description 428 | * Returns configured language that is used by speech recognizer. 429 | * 430 | * @return {string} lang Language key 431 | */ 432 | getLang: function(){ 433 | return DEST_LANG; 434 | }, 435 | 436 | payAttention: function(){ 437 | payingAttention = true; 438 | }, 439 | 440 | ignore: function(){ 441 | payingAttention = false; 442 | }, 443 | 444 | listen: function(){ 445 | listen(); 446 | }, 447 | 448 | stopListening: function(){ 449 | stopListening(); 450 | }, 451 | 452 | command: function(utterance){ 453 | command(utterance); 454 | }, 455 | 456 | listenUtterance: function(tasks){ 457 | return listenUtterance(tasks); 458 | } 459 | }; 460 | }]; 461 | }); 462 | 463 | /** 464 | * @ngdoc object 465 | * @name adaptive.speech.directive:speechrecognition 466 | * @requires $rootScope 467 | * @restrict A 468 | * 469 | * @description 470 | * `adaptive.speech` provides an alternative way to define tasks, the speech 471 | * recognizer should listen to, to the `$speechRecognition.listenUtterance()` method. 472 | * All you have to do is to apply the `speechrecognition` directive to any element 473 | * and declare a object literal expression just as you would when using 474 | * `$speechRecognition.listenUtterance()`. 475 | */ 476 | adaptive.directive('speechrecognition', ['$rootScope', '$speechRecognition', function ($rootScope, $speechRecognition) { 477 | return { 478 | restrict: 'A', 479 | link: function (scope, element, attrs) { 480 | var getOptions = function() { 481 | return angular.extend({}, scope.$eval(attrs.speechrecognition)); 482 | }; 483 | var opts = getOptions(); 484 | var unbind = $rootScope.$on('adaptive.speech:utterance', function(e, data){ 485 | 486 | var DEST_LANG = $speechRecognition.getLang(); 487 | var utterance = data.utterance; 488 | callCommands(opts.tasks, DEST_LANG, utterance, opts.reference); 489 | }); 490 | 491 | element.bind('$destroy', function() { 492 | unbind(); 493 | }); 494 | 495 | } 496 | }; 497 | }]); 498 | 499 | })(); 500 | -------------------------------------------------------------------------------- /angular-adaptive-speech.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * angular-adaptive-speech v0.3.0 3 | * The MIT License 4 | * Copyright (c) 2014 Jan Antala 5 | */ 6 | 7 | (function() { 8 | 9 | var callCommands = function(tasks, DEST_LANG, utterance, reference){ 10 | reference = reference || '.+'; 11 | var commands = []; 12 | 13 | if (angular.isArray(tasks)) { 14 | commands = tasks; 15 | } 16 | else { 17 | commands.push(tasks); 18 | } 19 | 20 | commands.forEach(function(command){ 21 | if (command.lang !== DEST_LANG) { 22 | return false; 23 | } 24 | 25 | var regex = command.regex || null; 26 | 27 | if (utterance.match(regex)) { 28 | if (utterance.match(new RegExp(reference, 'ig'))) { 29 | command.call(utterance); 30 | } 31 | } 32 | }); 33 | }; 34 | 35 | /** 36 | * @ngdoc overview 37 | * @name adaptive.speech 38 | * 39 | * @description 40 | * `adaptive.speech` is an Angular module which provides you with speech recognition 41 | * API's. Use its service to control your web app using speech commands. It's based 42 | * on Chrome's speech recognition API. 43 | */ 44 | var adaptive = angular.module('adaptive.speech', []); 45 | 46 | adaptive.provider('$speechCorrection', function() { 47 | 48 | this.STORAGE_ID = 'adaptive:speech:correction'; 49 | this.$get = function() { 50 | 51 | var STORAGE_ID = this.STORAGE_ID; 52 | var correctionMap = JSON.parse(localStorage.getItem(STORAGE_ID) || '{}'); 53 | 54 | var save = function(STORAGE_ID, correctionMap){ 55 | localStorage.setItem(STORAGE_ID, JSON.stringify(correctionMap)); 56 | }; 57 | 58 | var addUtterance = function(utterance, correction, lang){ 59 | correctionMap[lang] = correctionMap[lang] || {}; 60 | correctionMap[lang][utterance] = correction; 61 | }; 62 | 63 | var removeUtterance = function(utterance, lang){ 64 | delete correctionMap.lang[utterance]; 65 | }; 66 | 67 | var addLangMap = function(lang, map){ 68 | correctionMap[lang] = correctionMap[lang] || {}; 69 | correctionMap[lang] = map; 70 | }; 71 | 72 | var clearLangMap = function(lang){ 73 | delete correctionMap[lang]; 74 | }; 75 | 76 | var getCorrectionMap = function(){ 77 | return correctionMap; 78 | }; 79 | 80 | var getLangMap = function(lang){ 81 | return correctionMap[lang]; 82 | }; 83 | 84 | var getCorrection = function(utterance, lang){ 85 | return ((correctionMap[lang] && correctionMap[lang][utterance]) || utterance); 86 | }; 87 | 88 | return { 89 | addUtterance: function(utterance, correction, lang){ 90 | addUtterance(utterance, correction, lang); 91 | save(STORAGE_ID, correctionMap); 92 | }, 93 | 94 | removeUtterance: function(utterance, lang){ 95 | removeUtterance(utterance, lang); 96 | save(STORAGE_ID, correctionMap); 97 | }, 98 | 99 | addLangMap: function(lang, map){ 100 | addLangMap(lang, map); 101 | save(STORAGE_ID, correctionMap); 102 | }, 103 | 104 | clearLangMap: function(lang){ 105 | clearLangMap(lang); 106 | save(STORAGE_ID, correctionMap); 107 | }, 108 | 109 | getCorrectionMap: function(){ 110 | return getCorrectionMap(); 111 | }, 112 | 113 | getLangMap: function(lang){ 114 | return getLangMap(lang); 115 | }, 116 | 117 | getCorrection: function(utterance, lang){ 118 | return getCorrection(utterance, lang); 119 | } 120 | }; 121 | 122 | }; 123 | }); 124 | 125 | adaptive.provider('$speechSynthetis', function() { 126 | 127 | this.corsProxyServer = 'http://www.corsproxy.com/'; 128 | 129 | this.$get = function() { 130 | 131 | var corsProxyServer = this.corsProxyServer; 132 | var justSpoke = false; 133 | 134 | /** 135 | * @ngdoc function 136 | * @name adaptive.speech.$speechSynthetis#speak 137 | * @methodOf apdative.speech.$speechSynthetis 138 | * 139 | * @description 140 | * Let's your computer speak to you. Simply pass a string with a text 141 | * you want your computer to say. 142 | * 143 | * @param {string} text Text 144 | * @param {string} lang Language 145 | */ 146 | var speak = function(text, lang){ 147 | if (!text) { 148 | return false; 149 | } 150 | 151 | var audioURL = [corsProxyServer, 'translate.google.com/translate_tts?ie=UTF-8&q=', text , '&tl=', lang].join(''); 152 | var audio = new Audio(); 153 | 154 | audio.addEventListener('play', function() { 155 | }, false); 156 | 157 | audio.addEventListener('ended', function() { 158 | justSpoke = true; 159 | }, false); 160 | 161 | audio.addEventListener('error', function() { 162 | }, false); 163 | 164 | audio.autoplay = true; 165 | audio.src = audioURL; 166 | }; 167 | 168 | return { 169 | speak: function(text, lang){ 170 | speak(text, lang); 171 | }, 172 | 173 | justSpoke: function(){ 174 | return justSpoke; 175 | }, 176 | 177 | recognised: function(){ 178 | justSpoke = false; 179 | } 180 | }; 181 | }; 182 | }); 183 | 184 | /** 185 | * @ngdoc object 186 | * @name adaptive.speech.$speechRecognitionProvider 187 | * 188 | * @description 189 | * The `$speechRecognitionProvider` provides an interface to configure `$speechRecognition 190 | * service for runtime. 191 | */ 192 | 193 | adaptive.provider('$speechRecognition', function() { 194 | 195 | this.DEST_LANG = 'en-US'; 196 | 197 | this.setLang = function(lang){ 198 | this.DEST_LANG = lang; 199 | }; 200 | 201 | /** 202 | * @ngdoc object 203 | * @name adaptive.speech.$speechRecognition 204 | * @requires $rootScope 205 | * 206 | * @description 207 | * The `$speechRecognition` service is your interface to communicate with underlying 208 | * native speech recognition implementations by the browser. It provides several methods 209 | * to for example paying attention and listening to what the user says, or it can 210 | * react on specific callbacks. 211 | */ 212 | this.$get = ['$rootScope', '$speechSynthetis', '$speechCorrection', function($rootScope, $speechSynthetis, $speechCorrection) { 213 | 214 | var DEST_LANG = this.DEST_LANG; 215 | 216 | var SpeechRecognitionMock = function(){ 217 | this.start = function() { this.onerror({'code': 0, 'error': 'speech recognition is not supported'}); }.bind(this); 218 | this.stop = function() { this.onerror({'code': 0, 'error': 'speech recognition is not supported'}); }.bind(this); 219 | }; 220 | 221 | window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition || SpeechRecognitionMock; 222 | 223 | var recognizer; 224 | 225 | var init = function(){ 226 | recognizer = new window.SpeechRecognition(); 227 | recognizer.continuous = true; 228 | recognizer.interimResults = true; 229 | recognizer.maxAlternatives = 3; 230 | 231 | recognizer.onresult = function(e) { 232 | if (onresult) { 233 | onresult(e); 234 | } 235 | }; 236 | 237 | recognizer.onstart = function(e) { 238 | if (onstart) { 239 | onstart(e); 240 | } 241 | }; 242 | 243 | recognizer.onend = function(e) { 244 | if (onend) { 245 | onend(e); 246 | } 247 | }; 248 | 249 | recognizer.onerror = function(e) { 250 | if (onerror) { 251 | onerror(e); 252 | } 253 | }; 254 | }; 255 | 256 | init(); 257 | 258 | var payingAttention = true; 259 | var isListening = false; 260 | 261 | /** 262 | * @ngdoc function 263 | * @name adaptive.speech.$speechRecognition#listen 264 | * @methodOf adaptive.speech.$speechRecognition 265 | * 266 | * @description 267 | * Starts the speech recognizer and listens for speech input. 268 | */ 269 | var listen = function(){ 270 | if (!isListening) { 271 | init(); 272 | recognizer.start(); 273 | } 274 | isListening = true; 275 | }; 276 | 277 | var stopListening = function(){ 278 | if (isListening) { 279 | recognizer.stop(); 280 | } 281 | isListening = false; 282 | }; 283 | 284 | var command = function(utterance){ 285 | utterance = $speechCorrection.getCorrection(utterance, DEST_LANG); 286 | $rootScope.$emit('adaptive.speech:utterance', {'lang': DEST_LANG, 'utterance': utterance}); 287 | }; 288 | 289 | /** 290 | * @ngdoc function 291 | * @name adaptive.speech.$speechRecognition#setLang 292 | * @methodOf adaptive.speech.$speechRecognition 293 | * 294 | * @description 295 | * Configures speech recognizer to use given language when trying to recognize 296 | * speech input. Default is `en-US`. 297 | * 298 | * @ 299 | */ 300 | var setLang = function(lang){ 301 | DEST_LANG = lang; 302 | recognizer.lang = lang; 303 | }; 304 | 305 | var onstart, onerror; 306 | 307 | var onend = function(e){ 308 | payingAttention = false; 309 | recognizer = null; 310 | }; 311 | 312 | var onresult = function(e){ 313 | if (e.results.length) { 314 | var result = e.results[e.resultIndex]; 315 | if (result.isFinal) { 316 | 317 | if ($speechSynthetis.justSpoke()) { 318 | $speechSynthetis.recognised(); 319 | return false; 320 | } 321 | 322 | var utterance = result[0].transcript.trim(); 323 | 324 | if (payingAttention) { 325 | command(utterance); 326 | } 327 | } 328 | } 329 | }; 330 | 331 | /** 332 | * @ngdoc function 333 | * @name adaptive.speech.$speechRecognition#listenUtterance 334 | * @methodOf adaptive.speech.$speechRecognition 335 | * 336 | * @description 337 | * With `$speechRecognition.listenUtterance()` you're able to setup several tasks 338 | * for the speech recognizer within your controller. `listenUtterance()` expects a 339 | * task description object, that holds defined tasks. A task needs an identifier, 340 | * a regex for the speech recognizer, as well as the language in which the speech 341 | * recognizer should interpret it. 342 | * 343 | * In addition one has to provide a function that will be called once the speech 344 | * recognizer recognizes the given pattern. 345 | * 346 | *
    347 |     * var app = angular.module('myApp', ['adaptive.speech']);
    348 |     *
    349 |     * app.controller('Ctrl', function ($speechRecognition) {
    350 |     *  
    351 |     *     $scope.recognition = {};
    352 |     *     $scope.recognition['en-US'] = {
    353 |     *       'addToList': {
    354 |     *           'regex': /^to do .+/gi,
    355 |     *           'lang': 'en-US',
    356 |     *           'call': function(e){
    357 |     *               $scope.addToList(e);
    358 |     *           }
    359 |     *       },
    360 |     *       'listTasks': [{
    361 |     *           'regex': /^complete .+/gi,
    362 |     *           'lang': 'en-US',
    363 |     *           'call': function(e){
    364 |     *               $scope.completeTask(e);
    365 |     *           }
    366 |     *       },{
    367 |     *           'regex': /^remove .+/gi,
    368 |     *           'lang': 'en-US',
    369 |     *           'call': function(e){
    370 |     *               $scope.removeTask(e);
    371 |     *           }
    372 |     *       }]
    373 |     *     };
    374 |     * });
    375 |     * 
    376 | * 377 | * @param {object} tasks Task definition object 378 | */ 379 | var listenUtterance = function(tasks){ 380 | return $rootScope.$on('adaptive.speech:utterance', function(e, data){ 381 | var utterance = data.utterance; 382 | callCommands(tasks, DEST_LANG, utterance); 383 | }); 384 | }; 385 | 386 | return { 387 | /** 388 | * @ngdoc function 389 | * @name adaptive.speech.$speechRecognition#onstart 390 | * @method adaptive.speech.$speechRecognition 391 | * 392 | * @description 393 | * Exepts a function which gets executed once `$speechRecognition` service 394 | * starts listening to speech input. 395 | * 396 | *
    397 |       * var app = angular.module('myApp', ['adaptive.speech']);
    398 |       *
    399 |       * app.controller('Ctrl', function ($speechRecognition, $speechSynthetis) {
    400 |       *   $speechRecognition.onstart(function() {
    401 |       *      $speechSynthetis.speak('Yes?, How can I help you?);
    402 |       *   });
    403 |       * });
    404 |       * 
    405 | * 406 | * @param {object} onstartFn Function callback 407 | */ 408 | onstart: function(fn){ 409 | onstart = fn; 410 | }, 411 | 412 | onerror: function(fn){ 413 | onerror = fn; 414 | }, 415 | 416 | onUtterance: function(cb){ 417 | var unbind = $rootScope.$on('adaptive.speech:utterance', function(e, data){ 418 | cb(data.utterance); 419 | }); 420 | 421 | $rootScope.$on('destroy', unbind); 422 | }, 423 | 424 | setLang: function(lang){ 425 | setLang(lang); 426 | }, 427 | 428 | /** 429 | * @ngdoc function 430 | * @name adaptive.speech.$speechRecognition#getLang 431 | * @methodOf adaptive.speech.$speechRecognition 432 | * 433 | * @description 434 | * Returns configured language that is used by speech recognizer. 435 | * 436 | * @return {string} lang Language key 437 | */ 438 | getLang: function(){ 439 | return DEST_LANG; 440 | }, 441 | 442 | payAttention: function(){ 443 | payingAttention = true; 444 | }, 445 | 446 | ignore: function(){ 447 | payingAttention = false; 448 | }, 449 | 450 | listen: function(){ 451 | listen(); 452 | }, 453 | 454 | stopListening: function(){ 455 | stopListening(); 456 | }, 457 | 458 | command: function(utterance){ 459 | command(utterance); 460 | }, 461 | 462 | listenUtterance: function(tasks){ 463 | return listenUtterance(tasks); 464 | } 465 | }; 466 | }]; 467 | }); 468 | 469 | /** 470 | * @ngdoc object 471 | * @name adaptive.speech.directive:speechrecognition 472 | * @requires $rootScope 473 | * @restrict A 474 | * 475 | * @description 476 | * `adaptive.speech` provides an alternative way to define tasks, the speech 477 | * recognizer should listen to, to the `$speechRecognition.listenUtterance()` method. 478 | * All you have to do is to apply the `speechrecognition` directive to any element 479 | * and declare a object literal expression just as you would when using 480 | * `$speechRecognition.listenUtterance()`. 481 | */ 482 | adaptive.directive('speechrecognition', ['$rootScope', '$speechRecognition', function ($rootScope, $speechRecognition) { 483 | return { 484 | restrict: 'A', 485 | link: function (scope, element, attrs) { 486 | var getOptions = function() { 487 | return angular.extend({}, scope.$eval(attrs.speechrecognition)); 488 | }; 489 | var opts = getOptions(); 490 | var unbind = $rootScope.$on('adaptive.speech:utterance', function(e, data){ 491 | 492 | var DEST_LANG = $speechRecognition.getLang(); 493 | var utterance = data.utterance; 494 | callCommands(opts.tasks, DEST_LANG, utterance, opts.reference); 495 | }); 496 | 497 | element.bind('$destroy', function() { 498 | unbind(); 499 | }); 500 | 501 | } 502 | }; 503 | }]); 504 | 505 | })(); 506 | -------------------------------------------------------------------------------- /demo/bower_components/angular-route/angular-route.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.2.6-build.2014+sha.277a5ea 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | /** 9 | * @ngdoc overview 10 | * @name ngRoute 11 | * @description 12 | * 13 | * # ngRoute 14 | * 15 | * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. 16 | * 17 | * ## Example 18 | * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 19 | * 20 | * {@installModule route} 21 | * 22 | *
    23 | */ 24 | /* global -ngRouteModule */ 25 | var ngRouteModule = angular.module('ngRoute', ['ng']). 26 | provider('$route', $RouteProvider); 27 | 28 | /** 29 | * @ngdoc object 30 | * @name ngRoute.$routeProvider 31 | * @function 32 | * 33 | * @description 34 | * 35 | * Used for configuring routes. 36 | * 37 | * ## Example 38 | * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 39 | * 40 | * ## Dependencies 41 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 42 | */ 43 | function $RouteProvider(){ 44 | function inherit(parent, extra) { 45 | return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); 46 | } 47 | 48 | var routes = {}; 49 | 50 | /** 51 | * @ngdoc method 52 | * @name ngRoute.$routeProvider#when 53 | * @methodOf ngRoute.$routeProvider 54 | * 55 | * @param {string} path Route path (matched against `$location.path`). If `$location.path` 56 | * contains redundant trailing slash or is missing one, the route will still match and the 57 | * `$location.path` will be updated to add or drop the trailing slash to exactly match the 58 | * route definition. 59 | * 60 | * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up 61 | * to the next slash are matched and stored in `$routeParams` under the given `name` 62 | * when the route matches. 63 | * * `path` can contain named groups starting with a colon and ending with a star: 64 | * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` 65 | * when the route matches. 66 | * * `path` can contain optional named groups with a question mark: e.g.`:name?`. 67 | * 68 | * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match 69 | * `/color/brown/largecode/code/with/slashs/edit` and extract: 70 | * 71 | * * `color: brown` 72 | * * `largecode: code/with/slashs`. 73 | * 74 | * 75 | * @param {Object} route Mapping information to be assigned to `$route.current` on route 76 | * match. 77 | * 78 | * Object properties: 79 | * 80 | * - `controller` – `{(string|function()=}` – Controller fn that should be associated with 81 | * newly created scope or the name of a {@link angular.Module#controller registered 82 | * controller} if passed as a string. 83 | * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be 84 | * published to scope under the `controllerAs` name. 85 | * - `template` – `{string=|function()=}` – html template as a string or a function that 86 | * returns an html template as a string which should be used by {@link 87 | * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. 88 | * This property takes precedence over `templateUrl`. 89 | * 90 | * If `template` is a function, it will be called with the following parameters: 91 | * 92 | * - `{Array.}` - route parameters extracted from the current 93 | * `$location.path()` by applying the current route 94 | * 95 | * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html 96 | * template that should be used by {@link ngRoute.directive:ngView ngView}. 97 | * 98 | * If `templateUrl` is a function, it will be called with the following parameters: 99 | * 100 | * - `{Array.}` - route parameters extracted from the current 101 | * `$location.path()` by applying the current route 102 | * 103 | * - `resolve` - `{Object.=}` - An optional map of dependencies which should 104 | * be injected into the controller. If any of these dependencies are promises, the router 105 | * will wait for them all to be resolved or one to be rejected before the controller is 106 | * instantiated. 107 | * If all the promises are resolved successfully, the values of the resolved promises are 108 | * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is 109 | * fired. If any of the promises are rejected the 110 | * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object 111 | * is: 112 | * 113 | * - `key` – `{string}`: a name of a dependency to be injected into the controller. 114 | * - `factory` - `{string|function}`: If `string` then it is an alias for a service. 115 | * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} 116 | * and the return value is treated as the dependency. If the result is a promise, it is 117 | * resolved before its value is injected into the controller. Be aware that 118 | * `ngRoute.$routeParams` will still refer to the previous route within these resolve 119 | * functions. Use `$route.current.params` to access the new route parameters, instead. 120 | * 121 | * - `redirectTo` – {(string|function())=} – value to update 122 | * {@link ng.$location $location} path with and trigger route redirection. 123 | * 124 | * If `redirectTo` is a function, it will be called with the following parameters: 125 | * 126 | * - `{Object.}` - route parameters extracted from the current 127 | * `$location.path()` by applying the current route templateUrl. 128 | * - `{string}` - current `$location.path()` 129 | * - `{Object}` - current `$location.search()` 130 | * 131 | * The custom `redirectTo` function is expected to return a string which will be used 132 | * to update `$location.path()` and `$location.search()`. 133 | * 134 | * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` 135 | * or `$location.hash()` changes. 136 | * 137 | * If the option is set to `false` and url in the browser changes, then 138 | * `$routeUpdate` event is broadcasted on the root scope. 139 | * 140 | * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive 141 | * 142 | * If the option is set to `true`, then the particular route can be matched without being 143 | * case sensitive 144 | * 145 | * @returns {Object} self 146 | * 147 | * @description 148 | * Adds a new route definition to the `$route` service. 149 | */ 150 | this.when = function(path, route) { 151 | routes[path] = angular.extend( 152 | {reloadOnSearch: true}, 153 | route, 154 | path && pathRegExp(path, route) 155 | ); 156 | 157 | // create redirection for trailing slashes 158 | if (path) { 159 | var redirectPath = (path[path.length-1] == '/') 160 | ? path.substr(0, path.length-1) 161 | : path +'/'; 162 | 163 | routes[redirectPath] = angular.extend( 164 | {redirectTo: path}, 165 | pathRegExp(redirectPath, route) 166 | ); 167 | } 168 | 169 | return this; 170 | }; 171 | 172 | /** 173 | * @param path {string} path 174 | * @param opts {Object} options 175 | * @return {?Object} 176 | * 177 | * @description 178 | * Normalizes the given path, returning a regular expression 179 | * and the original path. 180 | * 181 | * Inspired by pathRexp in visionmedia/express/lib/utils.js. 182 | */ 183 | function pathRegExp(path, opts) { 184 | var insensitive = opts.caseInsensitiveMatch, 185 | ret = { 186 | originalPath: path, 187 | regexp: path 188 | }, 189 | keys = ret.keys = []; 190 | 191 | path = path 192 | .replace(/([().])/g, '\\$1') 193 | .replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){ 194 | var optional = option === '?' ? option : null; 195 | var star = option === '*' ? option : null; 196 | keys.push({ name: key, optional: !!optional }); 197 | slash = slash || ''; 198 | return '' 199 | + (optional ? '' : slash) 200 | + '(?:' 201 | + (optional ? slash : '') 202 | + (star && '(.+?)' || '([^/]+)') 203 | + (optional || '') 204 | + ')' 205 | + (optional || ''); 206 | }) 207 | .replace(/([\/$\*])/g, '\\$1'); 208 | 209 | ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); 210 | return ret; 211 | } 212 | 213 | /** 214 | * @ngdoc method 215 | * @name ngRoute.$routeProvider#otherwise 216 | * @methodOf ngRoute.$routeProvider 217 | * 218 | * @description 219 | * Sets route definition that will be used on route change when no other route definition 220 | * is matched. 221 | * 222 | * @param {Object} params Mapping information to be assigned to `$route.current`. 223 | * @returns {Object} self 224 | */ 225 | this.otherwise = function(params) { 226 | this.when(null, params); 227 | return this; 228 | }; 229 | 230 | 231 | this.$get = ['$rootScope', 232 | '$location', 233 | '$routeParams', 234 | '$q', 235 | '$injector', 236 | '$http', 237 | '$templateCache', 238 | '$sce', 239 | function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { 240 | 241 | /** 242 | * @ngdoc object 243 | * @name ngRoute.$route 244 | * @requires $location 245 | * @requires $routeParams 246 | * 247 | * @property {Object} current Reference to the current route definition. 248 | * The route definition contains: 249 | * 250 | * - `controller`: The controller constructor as define in route definition. 251 | * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for 252 | * controller instantiation. The `locals` contain 253 | * the resolved values of the `resolve` map. Additionally the `locals` also contain: 254 | * 255 | * - `$scope` - The current route scope. 256 | * - `$template` - The current route template HTML. 257 | * 258 | * @property {Array.} routes Array of all configured routes. 259 | * 260 | * @description 261 | * `$route` is used for deep-linking URLs to controllers and views (HTML partials). 262 | * It watches `$location.url()` and tries to map the path to an existing route definition. 263 | * 264 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 265 | * 266 | * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. 267 | * 268 | * The `$route` service is typically used in conjunction with the 269 | * {@link ngRoute.directive:ngView `ngView`} directive and the 270 | * {@link ngRoute.$routeParams `$routeParams`} service. 271 | * 272 | * @example 273 | This example shows how changing the URL hash causes the `$route` to match a route against the 274 | URL, and the `ngView` pulls in the partial. 275 | 276 | Note that this example is using {@link ng.directive:script inlined templates} 277 | to get it working on jsfiddle as well. 278 | 279 | 280 | 281 |
    282 | Choose: 283 | Moby | 284 | Moby: Ch1 | 285 | Gatsby | 286 | Gatsby: Ch4 | 287 | Scarlet Letter
    288 | 289 |
    290 |
    291 | 292 |
    $location.path() = {{$location.path()}}
    293 |
    $route.current.templateUrl = {{$route.current.templateUrl}}
    294 |
    $route.current.params = {{$route.current.params}}
    295 |
    $route.current.scope.name = {{$route.current.scope.name}}
    296 |
    $routeParams = {{$routeParams}}
    297 |
    298 |
    299 | 300 | 301 | controller: {{name}}
    302 | Book Id: {{params.bookId}}
    303 |
    304 | 305 | 306 | controller: {{name}}
    307 | Book Id: {{params.bookId}}
    308 | Chapter Id: {{params.chapterId}} 309 |
    310 | 311 | 312 | angular.module('ngViewExample', ['ngRoute']) 313 | 314 | .config(function($routeProvider, $locationProvider) { 315 | $routeProvider.when('/Book/:bookId', { 316 | templateUrl: 'book.html', 317 | controller: BookCntl, 318 | resolve: { 319 | // I will cause a 1 second delay 320 | delay: function($q, $timeout) { 321 | var delay = $q.defer(); 322 | $timeout(delay.resolve, 1000); 323 | return delay.promise; 324 | } 325 | } 326 | }); 327 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 328 | templateUrl: 'chapter.html', 329 | controller: ChapterCntl 330 | }); 331 | 332 | // configure html5 to get links working on jsfiddle 333 | $locationProvider.html5Mode(true); 334 | }); 335 | 336 | function MainCntl($scope, $route, $routeParams, $location) { 337 | $scope.$route = $route; 338 | $scope.$location = $location; 339 | $scope.$routeParams = $routeParams; 340 | } 341 | 342 | function BookCntl($scope, $routeParams) { 343 | $scope.name = "BookCntl"; 344 | $scope.params = $routeParams; 345 | } 346 | 347 | function ChapterCntl($scope, $routeParams) { 348 | $scope.name = "ChapterCntl"; 349 | $scope.params = $routeParams; 350 | } 351 | 352 | 353 | 354 | it('should load and compile correct template', function() { 355 | element('a:contains("Moby: Ch1")').click(); 356 | var content = element('.doc-example-live [ng-view]').text(); 357 | expect(content).toMatch(/controller\: ChapterCntl/); 358 | expect(content).toMatch(/Book Id\: Moby/); 359 | expect(content).toMatch(/Chapter Id\: 1/); 360 | 361 | element('a:contains("Scarlet")').click(); 362 | sleep(2); // promises are not part of scenario waiting 363 | content = element('.doc-example-live [ng-view]').text(); 364 | expect(content).toMatch(/controller\: BookCntl/); 365 | expect(content).toMatch(/Book Id\: Scarlet/); 366 | }); 367 | 368 |
    369 | */ 370 | 371 | /** 372 | * @ngdoc event 373 | * @name ngRoute.$route#$routeChangeStart 374 | * @eventOf ngRoute.$route 375 | * @eventType broadcast on root scope 376 | * @description 377 | * Broadcasted before a route change. At this point the route services starts 378 | * resolving all of the dependencies needed for the route change to occurs. 379 | * Typically this involves fetching the view template as well as any dependencies 380 | * defined in `resolve` route property. Once all of the dependencies are resolved 381 | * `$routeChangeSuccess` is fired. 382 | * 383 | * @param {Object} angularEvent Synthetic event object. 384 | * @param {Route} next Future route information. 385 | * @param {Route} current Current route information. 386 | */ 387 | 388 | /** 389 | * @ngdoc event 390 | * @name ngRoute.$route#$routeChangeSuccess 391 | * @eventOf ngRoute.$route 392 | * @eventType broadcast on root scope 393 | * @description 394 | * Broadcasted after a route dependencies are resolved. 395 | * {@link ngRoute.directive:ngView ngView} listens for the directive 396 | * to instantiate the controller and render the view. 397 | * 398 | * @param {Object} angularEvent Synthetic event object. 399 | * @param {Route} current Current route information. 400 | * @param {Route|Undefined} previous Previous route information, or undefined if current is 401 | * first route entered. 402 | */ 403 | 404 | /** 405 | * @ngdoc event 406 | * @name ngRoute.$route#$routeChangeError 407 | * @eventOf ngRoute.$route 408 | * @eventType broadcast on root scope 409 | * @description 410 | * Broadcasted if any of the resolve promises are rejected. 411 | * 412 | * @param {Object} angularEvent Synthetic event object 413 | * @param {Route} current Current route information. 414 | * @param {Route} previous Previous route information. 415 | * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. 416 | */ 417 | 418 | /** 419 | * @ngdoc event 420 | * @name ngRoute.$route#$routeUpdate 421 | * @eventOf ngRoute.$route 422 | * @eventType broadcast on root scope 423 | * @description 424 | * 425 | * The `reloadOnSearch` property has been set to false, and we are reusing the same 426 | * instance of the Controller. 427 | */ 428 | 429 | var forceReload = false, 430 | $route = { 431 | routes: routes, 432 | 433 | /** 434 | * @ngdoc method 435 | * @name ngRoute.$route#reload 436 | * @methodOf ngRoute.$route 437 | * 438 | * @description 439 | * Causes `$route` service to reload the current route even if 440 | * {@link ng.$location $location} hasn't changed. 441 | * 442 | * As a result of that, {@link ngRoute.directive:ngView ngView} 443 | * creates new scope, reinstantiates the controller. 444 | */ 445 | reload: function() { 446 | forceReload = true; 447 | $rootScope.$evalAsync(updateRoute); 448 | } 449 | }; 450 | 451 | $rootScope.$on('$locationChangeSuccess', updateRoute); 452 | 453 | return $route; 454 | 455 | ///////////////////////////////////////////////////// 456 | 457 | /** 458 | * @param on {string} current url 459 | * @param route {Object} route regexp to match the url against 460 | * @return {?Object} 461 | * 462 | * @description 463 | * Check if the route matches the current url. 464 | * 465 | * Inspired by match in 466 | * visionmedia/express/lib/router/router.js. 467 | */ 468 | function switchRouteMatcher(on, route) { 469 | var keys = route.keys, 470 | params = {}; 471 | 472 | if (!route.regexp) return null; 473 | 474 | var m = route.regexp.exec(on); 475 | if (!m) return null; 476 | 477 | for (var i = 1, len = m.length; i < len; ++i) { 478 | var key = keys[i - 1]; 479 | 480 | var val = 'string' == typeof m[i] 481 | ? decodeURIComponent(m[i]) 482 | : m[i]; 483 | 484 | if (key && val) { 485 | params[key.name] = val; 486 | } 487 | } 488 | return params; 489 | } 490 | 491 | function updateRoute() { 492 | var next = parseRoute(), 493 | last = $route.current; 494 | 495 | if (next && last && next.$$route === last.$$route 496 | && angular.equals(next.pathParams, last.pathParams) 497 | && !next.reloadOnSearch && !forceReload) { 498 | last.params = next.params; 499 | angular.copy(last.params, $routeParams); 500 | $rootScope.$broadcast('$routeUpdate', last); 501 | } else if (next || last) { 502 | forceReload = false; 503 | $rootScope.$broadcast('$routeChangeStart', next, last); 504 | $route.current = next; 505 | if (next) { 506 | if (next.redirectTo) { 507 | if (angular.isString(next.redirectTo)) { 508 | $location.path(interpolate(next.redirectTo, next.params)).search(next.params) 509 | .replace(); 510 | } else { 511 | $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) 512 | .replace(); 513 | } 514 | } 515 | } 516 | 517 | $q.when(next). 518 | then(function() { 519 | if (next) { 520 | var locals = angular.extend({}, next.resolve), 521 | template, templateUrl; 522 | 523 | angular.forEach(locals, function(value, key) { 524 | locals[key] = angular.isString(value) ? 525 | $injector.get(value) : $injector.invoke(value); 526 | }); 527 | 528 | if (angular.isDefined(template = next.template)) { 529 | if (angular.isFunction(template)) { 530 | template = template(next.params); 531 | } 532 | } else if (angular.isDefined(templateUrl = next.templateUrl)) { 533 | if (angular.isFunction(templateUrl)) { 534 | templateUrl = templateUrl(next.params); 535 | } 536 | templateUrl = $sce.getTrustedResourceUrl(templateUrl); 537 | if (angular.isDefined(templateUrl)) { 538 | next.loadedTemplateUrl = templateUrl; 539 | template = $http.get(templateUrl, {cache: $templateCache}). 540 | then(function(response) { return response.data; }); 541 | } 542 | } 543 | if (angular.isDefined(template)) { 544 | locals['$template'] = template; 545 | } 546 | return $q.all(locals); 547 | } 548 | }). 549 | // after route change 550 | then(function(locals) { 551 | if (next == $route.current) { 552 | if (next) { 553 | next.locals = locals; 554 | angular.copy(next.params, $routeParams); 555 | } 556 | $rootScope.$broadcast('$routeChangeSuccess', next, last); 557 | } 558 | }, function(error) { 559 | if (next == $route.current) { 560 | $rootScope.$broadcast('$routeChangeError', next, last, error); 561 | } 562 | }); 563 | } 564 | } 565 | 566 | 567 | /** 568 | * @returns the current active route, by matching it against the URL 569 | */ 570 | function parseRoute() { 571 | // Match a route 572 | var params, match; 573 | angular.forEach(routes, function(route, path) { 574 | if (!match && (params = switchRouteMatcher($location.path(), route))) { 575 | match = inherit(route, { 576 | params: angular.extend({}, $location.search(), params), 577 | pathParams: params}); 578 | match.$$route = route; 579 | } 580 | }); 581 | // No route matched; fallback to "otherwise" route 582 | return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); 583 | } 584 | 585 | /** 586 | * @returns interpolation of the redirect path with the parameters 587 | */ 588 | function interpolate(string, params) { 589 | var result = []; 590 | angular.forEach((string||'').split(':'), function(segment, i) { 591 | if (i === 0) { 592 | result.push(segment); 593 | } else { 594 | var segmentMatch = segment.match(/(\w+)(.*)/); 595 | var key = segmentMatch[1]; 596 | result.push(params[key]); 597 | result.push(segmentMatch[2] || ''); 598 | delete params[key]; 599 | } 600 | }); 601 | return result.join(''); 602 | } 603 | }]; 604 | } 605 | 606 | ngRouteModule.provider('$routeParams', $RouteParamsProvider); 607 | 608 | 609 | /** 610 | * @ngdoc object 611 | * @name ngRoute.$routeParams 612 | * @requires $route 613 | * 614 | * @description 615 | * The `$routeParams` service allows you to retrieve the current set of route parameters. 616 | * 617 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 618 | * 619 | * The route parameters are a combination of {@link ng.$location `$location`}'s 620 | * {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}. 621 | * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. 622 | * 623 | * In case of parameter name collision, `path` params take precedence over `search` params. 624 | * 625 | * The service guarantees that the identity of the `$routeParams` object will remain unchanged 626 | * (but its properties will likely change) even when a route change occurs. 627 | * 628 | * Note that the `$routeParams` are only updated *after* a route change completes successfully. 629 | * This means that you cannot rely on `$routeParams` being correct in route resolve functions. 630 | * Instead you can use `$route.current.params` to access the new route's parameters. 631 | * 632 | * @example 633 | *
    634 |  *  // Given:
    635 |  *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
    636 |  *  // Route: /Chapter/:chapterId/Section/:sectionId
    637 |  *  //
    638 |  *  // Then
    639 |  *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
    640 |  * 
    641 | */ 642 | function $RouteParamsProvider() { 643 | this.$get = function() { return {}; }; 644 | } 645 | 646 | ngRouteModule.directive('ngView', ngViewFactory); 647 | ngRouteModule.directive('ngView', ngViewFillContentFactory); 648 | 649 | 650 | /** 651 | * @ngdoc directive 652 | * @name ngRoute.directive:ngView 653 | * @restrict ECA 654 | * 655 | * @description 656 | * # Overview 657 | * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by 658 | * including the rendered template of the current route into the main layout (`index.html`) file. 659 | * Every time the current route changes, the included view changes with it according to the 660 | * configuration of the `$route` service. 661 | * 662 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 663 | * 664 | * @animations 665 | * enter - animation is used to bring new content into the browser. 666 | * leave - animation is used to animate existing content away. 667 | * 668 | * The enter and leave animation occur concurrently. 669 | * 670 | * @scope 671 | * @priority 400 672 | * @example 673 | 674 | 675 |
    676 | Choose: 677 | Moby | 678 | Moby: Ch1 | 679 | Gatsby | 680 | Gatsby: Ch4 | 681 | Scarlet Letter
    682 | 683 |
    684 |
    685 |
    686 |
    687 | 688 |
    $location.path() = {{main.$location.path()}}
    689 |
    $route.current.templateUrl = {{main.$route.current.templateUrl}}
    690 |
    $route.current.params = {{main.$route.current.params}}
    691 |
    $route.current.scope.name = {{main.$route.current.scope.name}}
    692 |
    $routeParams = {{main.$routeParams}}
    693 |
    694 |
    695 | 696 | 697 |
    698 | controller: {{book.name}}
    699 | Book Id: {{book.params.bookId}}
    700 |
    701 |
    702 | 703 | 704 |
    705 | controller: {{chapter.name}}
    706 | Book Id: {{chapter.params.bookId}}
    707 | Chapter Id: {{chapter.params.chapterId}} 708 |
    709 |
    710 | 711 | 712 | .view-animate-container { 713 | position:relative; 714 | height:100px!important; 715 | position:relative; 716 | background:white; 717 | border:1px solid black; 718 | height:40px; 719 | overflow:hidden; 720 | } 721 | 722 | .view-animate { 723 | padding:10px; 724 | } 725 | 726 | .view-animate.ng-enter, .view-animate.ng-leave { 727 | -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 728 | transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 729 | 730 | display:block; 731 | width:100%; 732 | border-left:1px solid black; 733 | 734 | position:absolute; 735 | top:0; 736 | left:0; 737 | right:0; 738 | bottom:0; 739 | padding:10px; 740 | } 741 | 742 | .view-animate.ng-enter { 743 | left:100%; 744 | } 745 | .view-animate.ng-enter.ng-enter-active { 746 | left:0; 747 | } 748 | .view-animate.ng-leave.ng-leave-active { 749 | left:-100%; 750 | } 751 | 752 | 753 | 754 | angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], 755 | function($routeProvider, $locationProvider) { 756 | $routeProvider.when('/Book/:bookId', { 757 | templateUrl: 'book.html', 758 | controller: BookCntl, 759 | controllerAs: 'book' 760 | }); 761 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 762 | templateUrl: 'chapter.html', 763 | controller: ChapterCntl, 764 | controllerAs: 'chapter' 765 | }); 766 | 767 | // configure html5 to get links working on jsfiddle 768 | $locationProvider.html5Mode(true); 769 | }); 770 | 771 | function MainCntl($route, $routeParams, $location) { 772 | this.$route = $route; 773 | this.$location = $location; 774 | this.$routeParams = $routeParams; 775 | } 776 | 777 | function BookCntl($routeParams) { 778 | this.name = "BookCntl"; 779 | this.params = $routeParams; 780 | } 781 | 782 | function ChapterCntl($routeParams) { 783 | this.name = "ChapterCntl"; 784 | this.params = $routeParams; 785 | } 786 | 787 | 788 | 789 | it('should load and compile correct template', function() { 790 | element('a:contains("Moby: Ch1")').click(); 791 | var content = element('.doc-example-live [ng-view]').text(); 792 | expect(content).toMatch(/controller\: ChapterCntl/); 793 | expect(content).toMatch(/Book Id\: Moby/); 794 | expect(content).toMatch(/Chapter Id\: 1/); 795 | 796 | element('a:contains("Scarlet")').click(); 797 | content = element('.doc-example-live [ng-view]').text(); 798 | expect(content).toMatch(/controller\: BookCntl/); 799 | expect(content).toMatch(/Book Id\: Scarlet/); 800 | }); 801 | 802 |
    803 | */ 804 | 805 | 806 | /** 807 | * @ngdoc event 808 | * @name ngRoute.directive:ngView#$viewContentLoaded 809 | * @eventOf ngRoute.directive:ngView 810 | * @eventType emit on the current ngView scope 811 | * @description 812 | * Emitted every time the ngView content is reloaded. 813 | */ 814 | ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; 815 | function ngViewFactory( $route, $anchorScroll, $animate) { 816 | return { 817 | restrict: 'ECA', 818 | terminal: true, 819 | priority: 400, 820 | transclude: 'element', 821 | link: function(scope, $element, attr, ctrl, $transclude) { 822 | var currentScope, 823 | currentElement, 824 | autoScrollExp = attr.autoscroll, 825 | onloadExp = attr.onload || ''; 826 | 827 | scope.$on('$routeChangeSuccess', update); 828 | update(); 829 | 830 | function cleanupLastView() { 831 | if (currentScope) { 832 | currentScope.$destroy(); 833 | currentScope = null; 834 | } 835 | if(currentElement) { 836 | $animate.leave(currentElement); 837 | currentElement = null; 838 | } 839 | } 840 | 841 | function update() { 842 | var locals = $route.current && $route.current.locals, 843 | template = locals && locals.$template; 844 | 845 | if (template) { 846 | var newScope = scope.$new(); 847 | var current = $route.current; 848 | 849 | // Note: This will also link all children of ng-view that were contained in the original 850 | // html. If that content contains controllers, ... they could pollute/change the scope. 851 | // However, using ng-view on an element with additional content does not make sense... 852 | // Note: We can't remove them in the cloneAttchFn of $transclude as that 853 | // function is called before linking the content, which would apply child 854 | // directives to non existing elements. 855 | var clone = $transclude(newScope, function(clone) { 856 | $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { 857 | if (angular.isDefined(autoScrollExp) 858 | && (!autoScrollExp || scope.$eval(autoScrollExp))) { 859 | $anchorScroll(); 860 | } 861 | }); 862 | cleanupLastView(); 863 | }); 864 | 865 | currentElement = clone; 866 | currentScope = current.scope = newScope; 867 | currentScope.$emit('$viewContentLoaded'); 868 | currentScope.$eval(onloadExp); 869 | } else { 870 | cleanupLastView(); 871 | } 872 | } 873 | } 874 | }; 875 | } 876 | 877 | // This directive is called during the $transclude call of the first `ngView` directive. 878 | // It will replace and compile the content of the element with the loaded template. 879 | // We need this directive so that the element content is already filled when 880 | // the link function of another directive on the same element as ngView 881 | // is called. 882 | ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; 883 | function ngViewFillContentFactory($compile, $controller, $route) { 884 | return { 885 | restrict: 'ECA', 886 | priority: -400, 887 | link: function(scope, $element) { 888 | var current = $route.current, 889 | locals = current.locals; 890 | 891 | $element.html(locals.$template); 892 | 893 | var link = $compile($element.contents()); 894 | 895 | if (current.controller) { 896 | locals.$scope = scope; 897 | var controller = $controller(current.controller, locals); 898 | if (current.controllerAs) { 899 | scope[current.controllerAs] = controller; 900 | } 901 | $element.data('$ngControllerController', controller); 902 | $element.children().data('$ngControllerController', controller); 903 | } 904 | 905 | link(scope); 906 | } 907 | }; 908 | } 909 | 910 | 911 | })(window, window.angular); 912 | --------------------------------------------------------------------------------