├── .gitignore ├── test ├── controllers │ ├── TODO_CONTROLLERS_ARE_IN_EXAMPLES_FOLDER │ ├── arrayCtrl.js │ ├── modules.js │ └── options.js ├── benchmarks │ └── creation.js ├── karma.conf.js ├── karma.bench.conf.js └── unit │ ├── multipleControllersSpec.js │ ├── optionsSpec.js │ ├── todoCtrlSpec.js │ ├── todoAsCtrlSpec.js │ └── modulesSpec.js ├── index.js ├── examples └── todomvc │ ├── js │ ├── app.js │ ├── directives │ │ ├── todoBlur.js │ │ ├── todoEscape.js │ │ └── todoFocus.js │ ├── services │ │ └── todoStorage.js │ └── controllers │ │ ├── todoCtrl.js │ │ └── todoCtrl-asController.js │ ├── bower.json │ ├── readme.md │ ├── index.html │ └── index-as-controller.html ├── .travis.yml ├── src ├── register.js ├── bindDependencies.js ├── bindData.js ├── bindMethods.js ├── watch.js └── core.js ├── bower.json ├── package.json ├── readme.md ├── gulpfile.coffee ├── angular-classy.min.js ├── angular-classy.js └── angular-classy.min.js.map /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ -------------------------------------------------------------------------------- /test/controllers/TODO_CONTROLLERS_ARE_IN_EXAMPLES_FOLDER: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./angular-classy'); 2 | module.exports = 'classy'; -------------------------------------------------------------------------------- /examples/todomvc/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', ['classy']); 11 | -------------------------------------------------------------------------------- /examples/todomvc/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-angular-classy", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.3", 6 | "todomvc-common": "~0.1.4" 7 | }, 8 | "devDependencies": { 9 | "angular-mocks": "~1.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "4" 5 | before_install: 6 | - "export DISPLAY=:99.0" 7 | - "sh -e /etc/init.d/xvfb start" 8 | - "npm install -g bower" 9 | - "npm install -g gulp" 10 | - "bower install" 11 | 12 | # whitelist - only build these branches 13 | branches: 14 | only: 15 | - master 16 | - develop 17 | -------------------------------------------------------------------------------- /examples/todomvc/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 | -------------------------------------------------------------------------------- /src/register.js: -------------------------------------------------------------------------------- 1 | angular.module('classy.register', ['classy.core']).classy.plugin.controller({ 2 | options: { 3 | enabled: true, 4 | key: 'name' 5 | }, 6 | preInit: function(classConstructor, classObj, module) { 7 | if (this.options.enabled && angular.isString(classObj[this.options.key])) { 8 | module.controller(classObj[this.options.key], classConstructor); 9 | } 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /examples/todomvc/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'; 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 | -------------------------------------------------------------------------------- /examples/todomvc/js/directives/todoEscape.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that executes an expression when the element it is applied to gets 6 | * an `escape` keydown event. 7 | */ 8 | todomvc.directive('todoBlur', function () { 9 | var ESCAPE_KEY = 27; 10 | return function (scope, elem, attrs) { 11 | elem.bind('keydown', function (event) { 12 | if (event.keyCode === ESCAPE_KEY) { 13 | scope.$apply(attrs.todoEscape); 14 | } 15 | }); 16 | }; 17 | }); 18 | 19 | -------------------------------------------------------------------------------- /examples/todomvc/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 todoFocus($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 | -------------------------------------------------------------------------------- /test/benchmarks/creation.js: -------------------------------------------------------------------------------- 1 | suite('Classy controller simple creation', function() { 2 | 3 | var app = angular.module('app', ['classy']); 4 | 5 | benchmark('Classy', function() { 6 | app.classy.controller({ 7 | name: 'BenchmarkClassyController', 8 | inject: ['$scope'] 9 | }); 10 | }); 11 | 12 | benchmark('Vanilla', function() { 13 | var benchmarkVanillaController = function($scope) {}; 14 | app.controller('BenchmarkVanillaContoller', ['$scope', benchmarkVanillaController]); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | basePath: '../', 4 | frameworks: ['jasmine'], 5 | files: [ 6 | 'bower_components/angular/angular.js', 7 | 'bower_components/angular-mocks/angular-mocks.js', 8 | 'angular-classy.js', 9 | 'examples/todomvc/js/app.js', 10 | 'examples/todomvc/js/*/*.js', 11 | 'test/controllers/*.js', 12 | 'test/unit/*.js' 13 | ], 14 | autoWatch: false, 15 | singleRun: false, 16 | browsers: ['Firefox', 'PhantomJS'] 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /test/karma.bench.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | basePath: '../', 4 | frameworks: ['benchmark', 'jasmine'], 5 | reporters: ['benchmark'], 6 | files: [ 7 | 'bower_components/angular/angular.js', 8 | 'bower_components/angular-mocks/angular-mocks.js', 9 | 'angular-classy.js', 10 | 'examples/todomvc/js/app.js', 11 | 'examples/todomvc/js/*/*.js', 12 | 'test/benchmarks/creation.js', 13 | ], 14 | autoWatch: false, 15 | singleRun: true, 16 | browsers: ['Firefox'] 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /test/controllers/arrayCtrl.js: -------------------------------------------------------------------------------- 1 | /*global angular*/ 2 | 3 | var returnedClassyArrayModule = angular.module('array-classy', ['classy']).classy.controllers([{ 4 | name: 'hello', 5 | inject: ['$scope'], 6 | init: function() { 7 | this.$.foo = 'bar'; 8 | } 9 | }, { 10 | name: 'goodbye', 11 | inject: ['$scope'], 12 | init: function() { 13 | this.$.foo = 'baz'; 14 | } 15 | }]); 16 | 17 | var returnedClassyNormalModule = angular.module('normal-classy', ['classy']).classy.controller({ 18 | name: 'hello', 19 | inject: ['$scope'], 20 | init: function() { 21 | this.$.foo = 'bar'; 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-classy", 3 | "main": "angular-classy.js", 4 | "version": "1.2.4", 5 | "homepage": "http://davej.github.io/angular-classy/", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/davej/angular-classy.git" 9 | }, 10 | "authors": [ 11 | "Dave Jeffery " 12 | ], 13 | "description": "Cleaner class-based controllers for AngularJS", 14 | "keywords": [ 15 | "angular", 16 | "angularjs", 17 | "class", 18 | "classy", 19 | "coffeescript", 20 | "controller" 21 | ], 22 | "license": "MIT", 23 | "dependencies": { 24 | "angular": "^1.1" 25 | }, 26 | "devDependencies": { 27 | "angular-mocks": "^1.1" 28 | }, 29 | "ignore": [ 30 | "**/.*", 31 | "node_modules", 32 | "bower_components", 33 | "test", 34 | "tests", 35 | "examples", 36 | ".travis.yml", 37 | "gulpfile.coffee", 38 | "package.json" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /test/controllers/modules.js: -------------------------------------------------------------------------------- 1 | /*global angular*/ 2 | 'use strict'; 3 | var testFooVar; 4 | var testBarVar; 5 | (function () { 6 | 7 | angular.module('app2', ['classy']); 8 | angular.module('app1', []); 9 | angular.module('app3', ['classy.core']); 10 | angular.module('app4', ['app2']); 11 | 12 | angular.module('classy.foo', ['classy.core']).classy.plugin.controller({ 13 | name: 'foo', 14 | 15 | options: { 16 | foo: 'boo' 17 | }, 18 | 19 | preInit: function () { 20 | testFooVar = this.options.foo; 21 | } 22 | }); 23 | 24 | angular.module('classy.bar', ['classy.core']).classy.plugin.controller({ 25 | name: 'bar', 26 | 27 | options: { 28 | bar: 'baz' 29 | }, 30 | 31 | preInitBefore: function () { 32 | this.classyOptions.foo.foo = this.options.bar; 33 | testBarVar = this.options.bar; 34 | } 35 | }); 36 | 37 | angular.module('app5', ['classy.core', 'classy.foo', 'classy.bar']); 38 | 39 | angular.module('app6', ['app1', 'app2', 'classy.foo', 'classy.bar']); 40 | 41 | angular.module('app7', ['app1']); 42 | 43 | // This should not cause an error, see https://github.com/davej/angular-classy/issues/36 44 | angular.module('app8', ['thisModuleDoesNotExist']); 45 | 46 | }()); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-classy", 3 | "version": "1.2.4", 4 | "main": "index.js", 5 | "files": [ 6 | "index.js", 7 | "angular-classy.js", 8 | "angular-classy.min.js", 9 | "angular-classy.min.js.map" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/davej/angular-classy.git" 14 | }, 15 | "scripts": { 16 | "test": "gulp test", 17 | "start": "gulp" 18 | }, 19 | "author": "Dave Jeffery", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "coffee-script": "^1.7.1", 23 | "event-stream": "^3.1.5", 24 | "gulp": "^3.8.6", 25 | "gulp-coffee": "^2.1.1", 26 | "gulp-concat": "^2.3.3", 27 | "gulp-insert": "^0.4.0", 28 | "gulp-jsclosure": "0.0.1", 29 | "gulp-rename": "^1.2.0", 30 | "gulp-replace": "^0.5.4", 31 | "gulp-sourcemaps": "^1.6.0", 32 | "gulp-uglify": "^0.3.1", 33 | "gulp-util": "^3.0.0", 34 | "karma": "^0.12.17", 35 | "karma-benchmark": "^0.4.0", 36 | "karma-benchmark-reporter": "^0.1.1", 37 | "karma-firefox-launcher": "^0.1.3", 38 | "karma-jasmine": "^0.1.5", 39 | "karma-phantomjs-launcher": "^1.0.0", 40 | "node-karma-wrapper": "^0.2.2", 41 | "phantomjs-prebuilt": "^2.1.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/bindDependencies.js: -------------------------------------------------------------------------------- 1 | angular.module('classy.bindDependencies', ['classy.core']).classy.plugin.controller({ 2 | options: { 3 | enabled: true, 4 | scopeShortcut: '$' 5 | }, 6 | preInit: function(classConstructor, classObj, module) { 7 | var depNames = classObj.inject || []; 8 | if (angular.isArray(depNames)) { 9 | this.inject(classConstructor, depNames, module); 10 | } 11 | }, 12 | inject: function(classConstructor, depNames, module) { 13 | var pluginDepNames = []; 14 | for (var pluginName in module.classy.activePlugins) { 15 | var plugin = module.classy.activePlugins[pluginName]; 16 | if (angular.isArray(plugin.localInject)) { 17 | pluginDepNames = pluginDepNames.concat(plugin.localInject); 18 | } 19 | } 20 | pluginDepNames = pluginDepNames.concat(classFns.localInject); 21 | classConstructor.__classDepNames = angular.copy(depNames); 22 | classConstructor.$inject = depNames.concat(pluginDepNames); 23 | }, 24 | initBefore: function(klass, deps, module) { 25 | if (this.options.enabled) { 26 | var preDeps = klass.constructor.$inject; 27 | for (var i = 0; i < preDeps.length; ++i) { 28 | var key = preDeps[i]; 29 | klass[key] = deps[key]; 30 | if (key === '$scope' && this.options.scopeShortcut) { 31 | klass[this.options.scopeShortcut] = klass[key]; 32 | } 33 | } 34 | } 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /src/bindData.js: -------------------------------------------------------------------------------- 1 | angular.module('classy.bindData', ['classy.core']).classy.plugin.controller({ 2 | localInject: ['$parse'], 3 | options: { 4 | enabled: true, 5 | addToScope: true, 6 | addToClass: true, 7 | privatePrefix: '_', 8 | keyName: 'data' 9 | }, 10 | hasPrivatePrefix: function(string) { 11 | var prefix = this.options.privatePrefix; 12 | if (!prefix) { 13 | return false; 14 | } else { 15 | return string.slice(0, prefix.length) === prefix; 16 | } 17 | }, 18 | init: function(klass, deps, module) { 19 | // Adds objects returned by or set to the `$scope` 20 | var dataProp = klass.constructor.prototype[this.options.keyName]; 21 | if (this.options.enabled && dataProp) { 22 | var data = angular.copy(dataProp); 23 | if (angular.isFunction(data)) { 24 | data = data.call(klass); 25 | } else if (angular.isObject(data)) { 26 | for (var key in data) { 27 | var value = data[key]; 28 | if (angular.isString(value)) { 29 | var getter = this.$parse(value); 30 | data[key] = getter(klass); 31 | } else { 32 | data[key] = value; 33 | } 34 | } 35 | } 36 | for (var fnKey in data) { 37 | var fnValue = data[fnKey]; 38 | if (this.options.addToClass) { 39 | klass[fnKey] = fnValue; 40 | } 41 | if (this.options.addToScope && !this.hasPrivatePrefix(fnKey) && deps.$scope) { 42 | deps.$scope[fnKey] = fnValue; 43 | } 44 | } 45 | } 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /src/bindMethods.js: -------------------------------------------------------------------------------- 1 | angular.module('classy.bindMethods', ['classy.core']).classy.plugin.controller({ 2 | localInject: ['$parse'], 3 | options: { 4 | enabled: true, 5 | addToScope: true, 6 | addToClass: true, 7 | privatePrefix: '_', 8 | ignore: ['constructor', 'init'], 9 | keyName: 'methods' 10 | }, 11 | hasPrivatePrefix: function(string) { 12 | var prefix; 13 | prefix = this.options.privatePrefix; 14 | if (!prefix) { 15 | return false; 16 | } else { 17 | return string.slice(0, prefix.length) === prefix; 18 | } 19 | }, 20 | init: function(klass, deps, module) { 21 | // indexOf shim for IE <= 8 22 | var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 23 | 24 | if (this.options.enabled) { 25 | var methods = klass.constructor.prototype[this.options.keyName]; 26 | for (var key in methods) { 27 | var method = methods[key]; 28 | 29 | var boundMethod; 30 | if (angular.isFunction(method) && !(__indexOf.call(this.options.ignore, key) >= 0)) { 31 | boundMethod = angular.bind(klass, method); 32 | } else if (angular.isString(method)) { 33 | var getter = this.$parse(method); 34 | boundMethod = function() { 35 | return getter(klass); 36 | }; 37 | } 38 | if (angular.isFunction(boundMethod)) { 39 | if (this.options.addToClass) { 40 | klass[key] = boundMethod; 41 | } 42 | if (this.options.addToScope && !this.hasPrivatePrefix(key) && deps.$scope) { 43 | deps.$scope[key] = boundMethod; 44 | } 45 | } 46 | } 47 | } 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /examples/todomvc/readme.md: -------------------------------------------------------------------------------- 1 | # AngularJS TodoMVC Example 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.org](http://angularjs.org)_ 6 | 7 | 8 | ## Learning AngularJS 9 | The [AngularJS website](http://angularjs.org) 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 | -------------------------------------------------------------------------------- /test/unit/multipleControllersSpec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, beforeEach, inject, expect, module,returnedClassyArrayModule*/ 2 | (function () { 3 | 'use strict'; 4 | 5 | describe('Single Classy Controllers', function () { 6 | 7 | 8 | beforeEach(module('normal-classy')); 9 | 10 | describe('first controller', function() { 11 | var ctrl, scope; 12 | var ctrlName = 'hello'; 13 | 14 | beforeEach(inject(function ($controller, $rootScope) { 15 | scope = $rootScope.$new(); 16 | ctrl = $controller(ctrlName, { $scope: scope }); 17 | })); 18 | 19 | it('should have foo set to `bar`', function () { 20 | expect(scope.foo).toBe('bar'); 21 | }); 22 | }); 23 | 24 | 25 | describe('controllers array', function() { 26 | it('should return the module', function () { 27 | expect(angular.isFunction(returnedClassyNormalModule)).toBeTruthy(); 28 | }); 29 | }); 30 | 31 | }); 32 | 33 | describe('Multiple Classy Controllers using array', function () { 34 | 35 | 36 | beforeEach(module('array-classy')); 37 | 38 | describe('first controller', function() { 39 | var ctrl, scope; 40 | var ctrlName = 'hello'; 41 | 42 | beforeEach(inject(function ($controller, $rootScope) { 43 | scope = $rootScope.$new(); 44 | ctrl = $controller(ctrlName, { $scope: scope }); 45 | })); 46 | 47 | it('should have foo set to `bar`', function () { 48 | expect(scope.foo).toBe('bar'); 49 | }); 50 | }); 51 | 52 | describe('second controller', function() { 53 | var ctrl, scope; 54 | var ctrlName = 'goodbye'; 55 | 56 | beforeEach(inject(function ($controller, $rootScope) { 57 | scope = $rootScope.$new(); 58 | ctrl = $controller(ctrlName, { $scope: scope }); 59 | })); 60 | 61 | it('should have foo set to `baz`', function () { 62 | expect(scope.foo).toBe('baz'); 63 | }); 64 | }); 65 | 66 | describe('controllers array', function() { 67 | it('should return the module', function () { 68 | expect(angular.isFunction(returnedClassyArrayModule)).toBeFalsy(); 69 | expect(returnedClassyArrayModule.factory).toBeDefined(); 70 | }); 71 | }); 72 | 73 | }); 74 | }()); 75 | -------------------------------------------------------------------------------- /examples/todomvc/js/controllers/todoCtrl.js: -------------------------------------------------------------------------------- 1 | /*global todomvc, angular */ 2 | 'use strict'; 3 | 4 | todomvc.classy.controller({ 5 | name: 'TodoCtrl', 6 | inject: ['$scope', '$location', 'todoStorage'], 7 | 8 | data: { 9 | location: '$location', 10 | todos: 'todoStorage.get()', 11 | editedTodo: 'null' 12 | }, 13 | 14 | init: function() { 15 | if (this.$location.path() === '') { 16 | this.$location.path('/'); 17 | } 18 | }, 19 | 20 | watch: { 21 | 'location.path()': function(path) { 22 | this.$.statusFilter = (path === '/active') ? 23 | { completed: false } : (path === '/completed') ? 24 | { completed: true } : null; 25 | }, 26 | '{object}todos': '_onTodoChange' 27 | }, 28 | 29 | methods: { 30 | _getRemainingCount: '(todos | filter:{ completed: false }).length', 31 | 32 | _onTodoChange: function (newValue, oldValue) { 33 | this.$.remainingCount = this._getRemainingCount(); 34 | this.$.completedCount = this.$.todos.length - this.$.remainingCount; 35 | this.$.allChecked = !this.$.remainingCount; 36 | if (newValue !== oldValue) { // This prevents unneeded calls to the local storage 37 | this.todoStorage.put(this.$.todos); 38 | } 39 | }, 40 | 41 | addTodo: function () { 42 | var newTodo = this.$.newTodo.trim(); 43 | if (!newTodo.length) { 44 | return; 45 | } 46 | 47 | this.$.todos.push({ 48 | title: newTodo, 49 | completed: false 50 | }); 51 | 52 | this.$.newTodo = ''; 53 | }, 54 | 55 | editTodo: function (todo) { 56 | this.$.editedTodo = todo; 57 | // Clone the original todo to restore it on demand. 58 | this.$.originalTodo = angular.extend({}, todo); 59 | }, 60 | 61 | doneEditing: function (todo) { 62 | this.$.editedTodo = null; 63 | todo.title = todo.title.trim(); 64 | 65 | if (!todo.title) { 66 | this.$.removeTodo(todo); 67 | } 68 | }, 69 | 70 | revertEditing: function (todo) { 71 | this.$.todos[this.$.todos.indexOf(todo)] = this.$scope.originalTodo; 72 | this.$scope.doneEditing(this.$scope.originalTodo); 73 | }, 74 | 75 | removeTodo: function (todo) { 76 | this.$.todos.splice(this.$.todos.indexOf(todo), 1); 77 | }, 78 | 79 | clearCompletedTodos: function () { 80 | this.$.todos = this.$.todos.filter(function (val) { 81 | return !val.completed; 82 | }); 83 | }, 84 | 85 | markAll: function (completed) { 86 | this.$.todos.forEach(function (todo) { 87 | todo.completed = completed; 88 | }); 89 | } 90 | } 91 | 92 | }); 93 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Angular Classy [![Build Status](https://travis-ci.org/davej/angular-classy.svg?branch=master)](https://travis-ci.org/davej/angular-classy) 2 | 3 | [![Join the chat at https://gitter.im/davej/angular-classy](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/davej/angular-classy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | #### Cleaner class-based controllers for AngularJS 6 | 7 | Check out the [Angular Classy website](http://davej.github.io/angular-classy/) to see what it's all about. 8 | 9 | ## Building and testing Angular Classy 10 | 11 | If you want to edit any of the Classy source files or contribute some code then please build and test Angular Classy. 12 | 13 | 1. Install [NodeJS](http://nodejs.org/). 14 | 2. Install [gulp.js](http://gulpjs.com/) globally (`npm install -g gulp`). 15 | 3. Install [Bower](http://bower.io/) globally (`npm install -g bower`). 16 | 4. If you wish to run the tests then please also install [PhantomJS](http://phantomjs.org/) and [Firefox](https://www.mozilla.org/en-US/firefox/new/). 17 | 5. Run `npm install`. 18 | 6. Run `bower install`. 19 | 20 | To build Classy run `gulp`. To test Classy run `gulp test`. 21 | 22 | ## Thanks 23 | 24 | * [@elado](https://github.com/elado) and [this gist](https://gist.github.com/elado/8138516). 25 | * [@ahmednuaman](https://github.com/ahmednuaman) and his project [Radian](https://github.com/ahmednuaman/radian). 26 | * [@wuxiaoying](https://github.com/wuxiaoying) for contributions and [plugins](http://bower.io/search/?q=owner:wuxiaoying%20classy). 27 | 28 | ## License 29 | 30 | The MIT License 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 | -------------------------------------------------------------------------------- /test/controllers/options.js: -------------------------------------------------------------------------------- 1 | angular.module('options-classy', ['classy']).classy.options.controller = { 2 | bindData: { 3 | addToScope: false, 4 | addToClass: false 5 | }, 6 | bindMethods: { 7 | addToScope: false, 8 | addToClass: false 9 | } 10 | }; 11 | 12 | angular.module('options-classy').classy.controller({ 13 | name: 'optionsOne', 14 | inject: ['$scope'], 15 | 16 | data: { 17 | foo: '"foo"', 18 | bar: '"bar"' 19 | }, 20 | 21 | init: function() { 22 | this.baz = 'baz'; 23 | this.$.baz = 'baz'; 24 | }, 25 | 26 | methods: { 27 | fooMethod: function() { 28 | return; 29 | }, 30 | barMethod: function() { 31 | return; 32 | } 33 | } 34 | }); 35 | 36 | angular.module('options-classy').classy.controller({ 37 | name: 'optionsTwo', 38 | // Override module options 39 | __options: { 40 | 'bindData': { 41 | addToScope: true, 42 | addToClass: false 43 | }, 44 | 'bindMethods': { 45 | addToScope: false, 46 | addToClass: true 47 | } 48 | }, 49 | inject: ['$scope'], 50 | 51 | data: { 52 | foo: '"foo"', 53 | bar: '"bar"' 54 | }, 55 | 56 | init: function() { 57 | this.baz = 'baz'; 58 | this.$.baz = 'baz'; 59 | }, 60 | 61 | methods: { 62 | fooMethod: function() { 63 | return; 64 | }, 65 | barMethod: function() { 66 | return; 67 | } 68 | } 69 | }); 70 | 71 | 72 | angular.module('options-classy-shorthand', ['classy']).classy.options.controller = { 73 | addToScope: false, 74 | addToClass: false 75 | }; 76 | 77 | angular.module('options-classy-shorthand').classy.controller({ 78 | name: 'optionsOne', 79 | inject: ['$scope'], 80 | 81 | data: { 82 | foo: '"foo"', 83 | bar: '"bar"' 84 | }, 85 | 86 | init: function() { 87 | this.baz = 'baz'; 88 | this.$.baz = 'baz'; 89 | }, 90 | 91 | methods: { 92 | fooMethod: function() { 93 | return; 94 | }, 95 | barMethod: function() { 96 | return; 97 | } 98 | } 99 | }); 100 | 101 | angular.module('options-classy-shorthand').classy.controller({ 102 | name: 'optionsTwo', 103 | // Override module options 104 | __options: { 105 | addToScope: true, 106 | addToClass: false 107 | }, 108 | inject: ['$scope'], 109 | 110 | data: { 111 | foo: '"foo"', 112 | bar: '"bar"' 113 | }, 114 | 115 | init: function() { 116 | this.baz = 'baz'; 117 | this.$.baz = 'baz'; 118 | }, 119 | 120 | methods: { 121 | fooMethod: function() { 122 | return; 123 | }, 124 | barMethod: function() { 125 | return; 126 | } 127 | } 128 | }); 129 | -------------------------------------------------------------------------------- /examples/todomvc/js/controllers/todoCtrl-asController.js: -------------------------------------------------------------------------------- 1 | /*global todomvc, angular */ 2 | 'use strict'; 3 | 4 | /** 5 | * The main controller for the app. The controller: 6 | * - retrieves and persists the model via the todoStorage service 7 | * - exposes the model to the template and provides event handlers 8 | */ 9 | 10 | 11 | todomvc.classy.controller({ 12 | name: 'TodoAsControllerCtrl', 13 | inject: ['$scope', '$location', 'todoStorage'], 14 | __options: { 15 | bindWatchToClass: true, 16 | addToScope: false // shorthand for commented out code below 17 | 18 | // 'bindData': { 19 | // addToScope: false 20 | // }, 21 | // 'bindMethods': { 22 | // addToScope: false 23 | // } 24 | }, 25 | 26 | data: function() { 27 | return { 28 | todos: this.todoStorage.get(), 29 | newTodo: '', 30 | editedTodo: null, 31 | location: this.$location 32 | }; 33 | }, 34 | 35 | init: function() { 36 | if (this.location.path() === '') { 37 | this.location.path('/'); 38 | } 39 | }, 40 | 41 | watch: { 42 | 'location.path()': function(path) { 43 | this.statusFilter = (path === '/active') ? 44 | { completed: false } : (path === '/completed') ? 45 | { completed: true } : null; 46 | }, 47 | '{object}todos': '_onTodoChange' 48 | }, 49 | 50 | methods: { 51 | _getRemainingCount: '(todos | filter:{ completed: false }).length', 52 | 53 | _onTodoChange: function (newValue, oldValue) { 54 | this.remainingCount = this._getRemainingCount(); 55 | this.completedCount = this.todos.length - this.remainingCount; 56 | this.allChecked = !this.remainingCount; 57 | if (newValue !== oldValue) { // This prevents unneeded calls to the local storage 58 | this.todoStorage.put(this.todos); 59 | } 60 | }, 61 | 62 | addTodo: function () { 63 | var newTodo = this.newTodo.trim(); 64 | if (!newTodo.length) { 65 | return; 66 | } 67 | 68 | this.todos.push({ 69 | title: newTodo, 70 | completed: false 71 | }); 72 | 73 | this.newTodo = ''; 74 | }, 75 | 76 | editTodo: function (todo) { 77 | this.editedTodo = todo; 78 | // Clone the original todo to restore it on demand. 79 | this.originalTodo = angular.extend({}, todo); 80 | }, 81 | 82 | doneEditing: function (todo) { 83 | this.editedTodo = null; 84 | todo.title = todo.title.trim(); 85 | 86 | if (!todo.title) { 87 | this.removeTodo(todo); 88 | } 89 | }, 90 | 91 | revertEditing: function (todo) { 92 | this.todos[this.todos.indexOf(todo)] = this.originalTodo; 93 | this.doneEditing(this.originalTodo); 94 | }, 95 | 96 | removeTodo: function (todo) { 97 | this.todos.splice(this.todos.indexOf(todo), 1); 98 | }, 99 | 100 | clearCompletedTodos: function () { 101 | this.todos = this.todos.filter(function (val) { 102 | return !val.completed; 103 | }); 104 | }, 105 | 106 | markAll: function (completed) { 107 | this.todos.forEach(function (todo) { 108 | todo.completed = completed; 109 | }); 110 | } 111 | } 112 | 113 | }); 114 | -------------------------------------------------------------------------------- /gulpfile.coffee: -------------------------------------------------------------------------------- 1 | gulp = require("gulp") 2 | concat = require("gulp-concat") 3 | insert = require("gulp-insert") 4 | path = require("path") 5 | es = require("event-stream") 6 | coffee = require("gulp-coffee") 7 | gutil = require('gulp-util') 8 | uglify = require('gulp-uglify') 9 | rename = require('gulp-rename') 10 | closure = require('gulp-jsclosure') 11 | sourcemaps = require('gulp-sourcemaps') 12 | replace = require('gulp-replace') 13 | fs = require('fs') 14 | 15 | bower = fs.readFileSync('./bower.json') 16 | bowerJSON = JSON.parse(bower) 17 | version = bowerJSON.version 18 | 19 | ### 20 | `default` Action - Builds Angular Classy 21 | 22 | 1. Get's the names of all the classy plugins 23 | 2. Concatenates the core + all the plugins into one coffee file 24 | 3. Appends some code to register the plugins with classy 25 | 4. Outputs the coffee file (`angular-classy.coffee`) 26 | 5. Convert the CoffeeScript to a JS File (`angular-classy.js`) 27 | 6. Uglify (minify) the JS File (`angular-classy.min.js`) 28 | ### 29 | gulp.task "default", [ "minify" ] 30 | 31 | pluginNames = [] 32 | gulp.task "getPluginsNames", -> 33 | buildNames = (es) -> 34 | es.mapSync (file) -> 35 | pluginNames.push "classy.#{path.basename(file.path, ".js")}" 36 | file 37 | 38 | gulp.src "./src/*.js" 39 | .pipe buildNames(es) 40 | 41 | gulp.task "concatAndRegisterPlugins", [ "getPluginsNames" ], -> 42 | gulp.src ["./src/core.js", "./src/*.js"] 43 | .pipe concat("angular-classy.js") 44 | .pipe insert.append("\nangular.module('classy', " + JSON.stringify(pluginNames) + ");") 45 | .pipe closure() 46 | .pipe replace('%%VERSION%%', version) 47 | .pipe gulp.dest("./") 48 | 49 | gulp.task "minify", [ "concatAndRegisterPlugins" ], -> 50 | gulp.src "./angular-classy.js" 51 | .pipe sourcemaps.init() 52 | .pipe uglify() 53 | .pipe rename suffix: '.min' 54 | .pipe sourcemaps.write("./") 55 | .pipe gulp.dest("./") 56 | 57 | gulp.task "watch", ["default"], -> gulp.watch "./src/*.js", ['minify'] 58 | 59 | ### 60 | `test` Action - Uses Karma 61 | 62 | Runs tests in both Firefox and Phantom 63 | ### 64 | 65 | karma = require('node-karma-wrapper') 66 | 67 | karmaConfig = './test/karma.conf.js' 68 | karmaBenchConfig = './test/karma.bench.conf.js' 69 | 70 | karmaTest = karma(configFile: karmaConfig) 71 | karmaBench = karma(configFile: karmaBenchConfig) 72 | 73 | karmaPhantom = karma(configFile: karmaConfig, browsers: ['PhantomJS']) 74 | karmaFirefox = karma(configFile: karmaConfig, browsers: ['Firefox']) 75 | 76 | karmaBenchPhantom = karma(configFile: karmaBenchConfig, browsers: ['PhantomJS']) 77 | karmaBenchFirefox = karma(configFile: karmaBenchConfig, browsers: ['Firefox']) 78 | 79 | gulp.task "test", ["default"], (cb) -> karmaTest.simpleRun(cb) 80 | gulp.task "testFirefox", ["default"], (cb) -> karmaFirefox.simpleRun(cb) 81 | gulp.task "testPhantom", ["default"], (cb) -> karmaPhantom.simpleRun(cb) 82 | 83 | gulp.task "bench", ["default"], (cb) -> karmaBench.simpleRun(cb) 84 | gulp.task "benchFirefox", ["default"], (cb) -> karmaBenchPhantom.simpleRun(cb) 85 | gulp.task "benchPhantom", ["default"], (cb) -> karmaBenchFirefox.simpleRun(cb) 86 | -------------------------------------------------------------------------------- /src/watch.js: -------------------------------------------------------------------------------- 1 | angular.module('classy.watch', ['classy.core']).classy.plugin.controller({ 2 | localInject: ['$parse'], 3 | options: { 4 | enabled: true, 5 | bindWatchToClass: false, 6 | _watchKeywords: { 7 | normal: [], 8 | objectEquality: ['{object}', '{deep}'], 9 | collection: ['{collection}', '{shallow}'] 10 | } 11 | }, 12 | isActive: function(klass, deps) { 13 | if (this.options.enabled && angular.isObject(klass.watch)) { 14 | if (!deps.$scope) { 15 | throw new Error("You need to inject `$scope` to use the watch object"); 16 | return false; 17 | } 18 | return true; 19 | } 20 | }, 21 | watchFns: { 22 | normal: function(klass, expression, fn, deps) { 23 | return deps.$scope.$watch(expression, angular.bind(klass, fn)); 24 | }, 25 | objectEquality: function(klass, expression, fn, deps) { 26 | return deps.$scope.$watch(expression, angular.bind(klass, fn), true); 27 | }, 28 | collection: function(klass, expression, fn, deps) { 29 | return deps.$scope.$watchCollection(expression, angular.bind(klass, fn)); 30 | } 31 | }, 32 | convertToFunctionExpression: function(expression, context) { 33 | var parse = this.$parse; 34 | return function() { 35 | return parse(expression)(context); 36 | }; 37 | }, 38 | postInit: function(klass, deps, module) { 39 | if (!this.isActive(klass, deps)) { 40 | return; 41 | } 42 | var watchKeywords = this.options._watchKeywords; 43 | 44 | // for expression, fn of klass.watch 45 | for (var expression in klass.watch) { 46 | var fn = klass.watch[expression]; 47 | if (angular.isString(fn)) { 48 | fn = klass[fn]; 49 | } 50 | if (angular.isString(expression) && angular.isFunction(fn)) { 51 | var watchRegistered = false; 52 | 53 | // Search for keywords that identify it is a non-standard watch 54 | // for watchType, watchFn of @watchFns 55 | for (var watchType in this.watchFns) { 56 | var watchFn = this.watchFns[watchType]; 57 | if (watchRegistered) { 58 | break; 59 | } 60 | // for keyword in watchKeywords[watchType] 61 | var keywords = watchKeywords[watchType]; 62 | for (var i = 0; i < keywords.length; i++) { 63 | var keyword = keywords[i]; 64 | 65 | if (expression.indexOf(keyword) !== -1) { 66 | var normalizedExpression = expression.replace(keyword, ''); 67 | 68 | var exp = this.options.bindWatchToClass ? 69 | this.convertToFunctionExpression(normalizedExpression, klass) : 70 | normalizedExpression; 71 | 72 | watchFn(klass, exp, fn, deps); 73 | watchRegistered = true; 74 | break; 75 | } 76 | } 77 | } 78 | if (!watchRegistered) { 79 | // If no keywords have been found then register it as a normal watch 80 | var exp = this.options.bindWatchToClass ? 81 | this.convertToFunctionExpression(expression, klass) : 82 | expression; 83 | this.watchFns.normal(klass, exp, fn, deps); 84 | } 85 | } 86 | } 87 | } 88 | }); 89 | -------------------------------------------------------------------------------- /examples/todomvc/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 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /examples/todomvc/index-as-controller.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 | {{todoCtrl.remainingCount}} 36 | 37 | 38 | 49 | 50 |
51 |
52 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /test/unit/optionsSpec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, beforeEach, inject, expect, module,returnedClassyArrayModule*/ 2 | (function () { 3 | 'use strict'; 4 | 5 | describe('Classy Plugin Options', function () { 6 | 7 | beforeEach(module('options-classy')); 8 | 9 | describe('first controller', function() { 10 | var ctrl, scope; 11 | var ctrlName = 'optionsOne'; 12 | 13 | beforeEach(inject(function ($controller, $rootScope) { 14 | scope = $rootScope.$new(); 15 | ctrl = $controller(ctrlName, { $scope: scope }); 16 | })); 17 | 18 | it('should not have any properties from data', function () { 19 | expect(scope.foo).toBeUndefined(); 20 | expect(ctrl.foo).toBeUndefined(); 21 | 22 | expect(scope.bar).toBeUndefined(); 23 | expect(ctrl.bar).toBeUndefined(); 24 | }); 25 | 26 | it('should not have any properties from methods', function () { 27 | expect(scope.fooMethod).toBeUndefined(); 28 | expect(ctrl.fooMethod).toBeUndefined(); 29 | 30 | expect(scope.barMethod).toBeUndefined(); 31 | expect(ctrl.barMethod).toBeUndefined(); 32 | }); 33 | 34 | 35 | it('should have `baz` property on scope and class', function () { 36 | expect(scope.baz).toBeDefined(); 37 | expect(ctrl.baz).toBeDefined(); 38 | }); 39 | 40 | 41 | }); 42 | 43 | 44 | describe('second controller', function() { 45 | var ctrl, scope; 46 | var ctrlName = 'optionsTwo'; 47 | 48 | beforeEach(inject(function ($controller, $rootScope) { 49 | scope = $rootScope.$new(); 50 | ctrl = $controller(ctrlName, { $scope: scope }); 51 | })); 52 | 53 | it('should have `data` properties on scope but not directly on controller', function () { 54 | expect(scope.foo).toBeDefined(); 55 | expect(ctrl.foo).toBeUndefined(); 56 | 57 | expect(scope.bar).toBeDefined(); 58 | expect(ctrl.bar).toBeUndefined(); 59 | }); 60 | 61 | it('should not have methods on scope but does have methods directly on controller', function () { 62 | expect(scope.fooMethod).toBeUndefined(); 63 | expect(ctrl.fooMethod).toBeDefined(); 64 | 65 | expect(scope.barMethod).toBeUndefined(); 66 | expect(ctrl.barMethod).toBeDefined(); 67 | }); 68 | 69 | 70 | it('should have `baz` property on scope and class', function () { 71 | expect(scope.baz).toBeDefined(); 72 | expect(ctrl.baz).toBeDefined(); 73 | }); 74 | 75 | 76 | }); 77 | 78 | 79 | }); 80 | 81 | describe('Classy Plugin Options', function () { 82 | 83 | beforeEach(module('options-classy-shorthand')); 84 | 85 | describe('first controller', function() { 86 | var ctrl, scope; 87 | var ctrlName = 'optionsOne'; 88 | 89 | beforeEach(inject(function ($controller, $rootScope) { 90 | scope = $rootScope.$new(); 91 | ctrl = $controller(ctrlName, { $scope: scope }); 92 | })); 93 | 94 | it('should not have any properties from data', function () { 95 | expect(scope.foo).toBeUndefined(); 96 | expect(ctrl.foo).toBeUndefined(); 97 | 98 | expect(scope.bar).toBeUndefined(); 99 | expect(ctrl.bar).toBeUndefined(); 100 | }); 101 | 102 | it('should not have any properties from methods', function () { 103 | expect(scope.fooMethod).toBeUndefined(); 104 | expect(ctrl.fooMethod).toBeUndefined(); 105 | 106 | expect(scope.barMethod).toBeUndefined(); 107 | expect(ctrl.barMethod).toBeUndefined(); 108 | }); 109 | 110 | 111 | it('should have `baz` property on scope and class', function () { 112 | expect(scope.baz).toBeDefined(); 113 | expect(ctrl.baz).toBeDefined(); 114 | }); 115 | 116 | 117 | }); 118 | 119 | 120 | describe('second controller', function() { 121 | var ctrl, scope; 122 | var ctrlName = 'optionsTwo'; 123 | 124 | beforeEach(inject(function ($controller, $rootScope) { 125 | scope = $rootScope.$new(); 126 | ctrl = $controller(ctrlName, { $scope: scope }); 127 | })); 128 | 129 | it('should have `data` properties on scope but not directly on controller', function () { 130 | expect(scope.foo).toBeDefined(); 131 | expect(ctrl.foo).toBeUndefined(); 132 | 133 | expect(scope.bar).toBeDefined(); 134 | expect(ctrl.bar).toBeUndefined(); 135 | }); 136 | 137 | it('should have methods on scope but not directly on controller', function () { 138 | expect(scope.fooMethod).toBeDefined(); 139 | expect(ctrl.fooMethod).toBeUndefined(); 140 | 141 | expect(scope.barMethod).toBeDefined(); 142 | expect(ctrl.barMethod).toBeUndefined(); 143 | }); 144 | 145 | 146 | it('should have `baz` property on scope and class', function () { 147 | expect(scope.baz).toBeDefined(); 148 | expect(ctrl.baz).toBeDefined(); 149 | }); 150 | 151 | 152 | }); 153 | 154 | 155 | }); 156 | 157 | 158 | }()); 159 | -------------------------------------------------------------------------------- /test/unit/todoCtrlSpec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, beforeEach, inject, expect*/ 2 | (function () { 3 | 'use strict'; 4 | 5 | describe('Todo Javascript Classy Controller', function () { 6 | var ctrl, scope; 7 | var todoList; 8 | var todoStorage = { 9 | storage: {}, 10 | get: function () { 11 | return this.storage; 12 | }, 13 | put: function (value) { 14 | this.storage = value; 15 | } 16 | }; 17 | todoStorage.storage = []; 18 | var ctrlName = 'TodoCtrl'; 19 | 20 | // Load the module containing the app, only 'ng' is loaded by default. 21 | beforeEach(module('todomvc')); 22 | 23 | beforeEach(inject(function ($controller, $rootScope) { 24 | scope = $rootScope.$new(); 25 | ctrl = $controller(ctrlName, { $scope: scope, todoStorage: todoStorage }); 26 | })); 27 | 28 | it('should not have an edited Todo on start', function () { 29 | expect(scope.editedTodo).toBeNull(); 30 | }); 31 | 32 | it('should not have any Todos on start', function () { 33 | expect(scope.todos.length).toBe(0); 34 | }); 35 | 36 | it('should have all Todos completed', function () { 37 | scope.$digest(); 38 | expect(scope.allChecked).toBeTruthy(); 39 | }); 40 | 41 | describe('the path', function () { 42 | it('should default to "/"', function () { 43 | expect(scope.location.path()).toBe('/'); 44 | }); 45 | 46 | describe('being at /active', function () { 47 | it('should filter non-completed', inject(function ($controller) { 48 | ctrl = $controller(ctrlName, { 49 | $scope: scope, 50 | $location: { 51 | path: function () { return '/active'; } 52 | } 53 | }); 54 | scope.$digest(); 55 | expect(scope.statusFilter.completed).toBeFalsy(); 56 | })); 57 | }); 58 | 59 | describe('being at /completed', function () { 60 | it('should filter completed', inject(function ($controller) { 61 | ctrl = $controller(ctrlName, { 62 | $scope: scope, 63 | $location: { 64 | path: function () { return '/completed'; } 65 | } 66 | }); 67 | 68 | scope.$digest(); 69 | expect(scope.statusFilter.completed).toBeTruthy(); 70 | })); 71 | }); 72 | }); 73 | 74 | describe('having no Todos', function () { 75 | var ctrl; 76 | 77 | beforeEach(inject(function ($controller) { 78 | ctrl = $controller(ctrlName, { 79 | $scope: scope, 80 | todoStorage: todoStorage 81 | }); 82 | scope.$digest(); 83 | })); 84 | 85 | it('should not add empty Todos', function () { 86 | scope.newTodo = ''; 87 | scope.addTodo(); 88 | scope.$digest(); 89 | expect(scope.todos.length).toBe(0); 90 | }); 91 | 92 | it('should not add items consisting only of whitespaces', function () { 93 | scope.newTodo = ' '; 94 | scope.addTodo(); 95 | scope.$digest(); 96 | expect(scope.todos.length).toBe(0); 97 | }); 98 | 99 | 100 | it('should trim whitespace from new Todos', function () { 101 | scope.newTodo = ' buy some unicorns '; 102 | scope.addTodo(); 103 | scope.$digest(); 104 | expect(scope.todos.length).toBe(1); 105 | expect(scope.todos[0].title).toBe('buy some unicorns'); 106 | }); 107 | }); 108 | 109 | describe('having some saved Todos', function () { 110 | var ctrl; 111 | 112 | beforeEach(inject(function ($controller) { 113 | todoList = [{ 114 | 'title': 'Uncompleted Item 0', 115 | 'completed': false 116 | }, { 117 | 'title': 'Uncompleted Item 1', 118 | 'completed': false 119 | }, { 120 | 'title': 'Uncompleted Item 2', 121 | 'completed': false 122 | }, { 123 | 'title': 'Completed Item 0', 124 | 'completed': true 125 | }, { 126 | 'title': 'Completed Item 1', 127 | 'completed': true 128 | }]; 129 | 130 | todoStorage.storage = todoList; 131 | ctrl = $controller(ctrlName, { 132 | $scope: scope, 133 | todoStorage: todoStorage 134 | }); 135 | scope.$digest(); 136 | })); 137 | 138 | it('should count Todos correctly', function () { 139 | expect(scope.todos.length).toBe(5); 140 | expect(scope.remainingCount).toBe(3); 141 | expect(scope.completedCount).toBe(2); 142 | expect(scope.allChecked).toBeFalsy(); 143 | }); 144 | 145 | it('should save Todos to local storage', function () { 146 | expect(todoStorage.storage.length).toBe(5); 147 | }); 148 | 149 | it('should remove Todos w/o title on saving', function () { 150 | var todo = todoList[2]; 151 | todo.title = ''; 152 | 153 | scope.doneEditing(todo); 154 | expect(scope.todos.length).toBe(4); 155 | }); 156 | 157 | it('should trim Todos on saving', function () { 158 | var todo = todoList[0]; 159 | todo.title = ' buy moar unicorns '; 160 | 161 | scope.doneEditing(todo); 162 | expect(scope.todos[0].title).toBe('buy moar unicorns'); 163 | }); 164 | 165 | it('clearCompletedTodos() should clear completed Todos', function () { 166 | scope.clearCompletedTodos(); 167 | expect(scope.todos.length).toBe(3); 168 | }); 169 | 170 | it('markAll() should mark all Todos completed', function () { 171 | scope.markAll(); 172 | scope.$digest(); 173 | expect(scope.completedCount).toBe(5); 174 | }); 175 | 176 | it('revertTodo() get a Todo to its previous state', function () { 177 | var todo = todoList[0]; 178 | scope.editTodo(todo); 179 | todo.title = 'Unicorn sparkly skypuffles.'; 180 | scope.revertEditing(todo); 181 | scope.$digest(); 182 | expect(scope.todos[0].title).toBe('Uncompleted Item 0'); 183 | }); 184 | }); 185 | }); 186 | }()); 187 | -------------------------------------------------------------------------------- /test/unit/todoAsCtrlSpec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, beforeEach, inject, expect, module */ 2 | (function () { 3 | 'use strict'; 4 | 5 | describe('Todo Javascript Classy ControllerAs', function () { 6 | var ctrl, scope; 7 | var todoList; 8 | var todoStorage = { 9 | storage: {}, 10 | get: function () { 11 | return this.storage; 12 | }, 13 | put: function (value) { 14 | this.storage = value; 15 | } 16 | }; 17 | todoStorage.storage = []; 18 | var ctrlName = 'TodoAsControllerCtrl as todoCtrl'; 19 | 20 | // Load the module containing the app, only 'ng' is loaded by default. 21 | beforeEach(module('todomvc')); 22 | 23 | beforeEach(inject(function ($controller, $rootScope) { 24 | scope = $rootScope.$new(); 25 | ctrl = $controller(ctrlName, { $scope: scope, todoStorage: todoStorage }); 26 | })); 27 | 28 | 29 | describe('init', function() { 30 | it('should not have any controller methods placed on the scope', function () { 31 | expect(scope.todos).toBeUndefined(); 32 | expect(scope.editTodo).toBeUndefined(); 33 | }); 34 | 35 | it('should not have an edited Todo on start', function () { 36 | expect(ctrl.editedTodo).toBeNull(); 37 | }); 38 | 39 | it('should not have any Todos on start', function () { 40 | expect(ctrl.todos.length).toBe(0); 41 | }); 42 | 43 | it('should have all Todos completed', function () { 44 | scope.$digest(); 45 | expect(ctrl.allChecked).toBeTruthy(); 46 | }); 47 | }); 48 | 49 | 50 | describe('the path', function () { 51 | it('should default to "/"', function () { 52 | expect(ctrl.$location.path()).toBe('/'); 53 | }); 54 | 55 | describe('being at /active', function () { 56 | it('should filter non-completed', inject(function ($controller) { 57 | ctrl = $controller(ctrlName, { 58 | $scope: scope, 59 | $location: { 60 | path: function () { return '/active'; } 61 | } 62 | }); 63 | scope.$digest(); 64 | expect(ctrl.statusFilter.completed).toBeFalsy(); 65 | })); 66 | }); 67 | 68 | describe('being at /completed', function () { 69 | it('should filter completed', inject(function ($controller) { 70 | ctrl = $controller(ctrlName, { 71 | $scope: scope, 72 | $location: { 73 | path: function () { return '/completed'; } 74 | } 75 | }); 76 | 77 | scope.$digest(); 78 | expect(ctrl.statusFilter.completed).toBeTruthy(); 79 | })); 80 | }); 81 | }); 82 | 83 | describe('having no Todos', function () { 84 | var ctrl; 85 | 86 | beforeEach(inject(function ($controller) { 87 | ctrl = $controller(ctrlName, { 88 | $scope: scope, 89 | todoStorage: todoStorage 90 | }); 91 | scope.$digest(); 92 | })); 93 | 94 | it('should not add empty Todos', function () { 95 | ctrl.newTodo = ''; 96 | ctrl.addTodo(); 97 | scope.$digest(); 98 | expect(ctrl.todos.length).toBe(0); 99 | }); 100 | 101 | it('should not add items consisting only of whitespaces', function () { 102 | ctrl.newTodo = ' '; 103 | ctrl.addTodo(); 104 | scope.$digest(); 105 | expect(ctrl.todos.length).toBe(0); 106 | }); 107 | 108 | 109 | it('should trim whitespace from new Todos', function () { 110 | ctrl.newTodo = ' buy some unicorns '; 111 | ctrl.addTodo(); 112 | scope.$digest(); 113 | expect(ctrl.todos.length).toBe(1); 114 | expect(ctrl.todos[0].title).toBe('buy some unicorns'); 115 | }); 116 | }); 117 | 118 | describe('having some saved Todos', function () { 119 | var ctrl; 120 | 121 | beforeEach(inject(function ($controller) { 122 | todoList = [{ 123 | 'title': 'Uncompleted Item 0', 124 | 'completed': false 125 | }, { 126 | 'title': 'Uncompleted Item 1', 127 | 'completed': false 128 | }, { 129 | 'title': 'Uncompleted Item 2', 130 | 'completed': false 131 | }, { 132 | 'title': 'Completed Item 0', 133 | 'completed': true 134 | }, { 135 | 'title': 'Completed Item 1', 136 | 'completed': true 137 | }]; 138 | 139 | todoStorage.storage = todoList; 140 | ctrl = $controller(ctrlName, { 141 | $scope: scope, 142 | todoStorage: todoStorage 143 | }); 144 | scope.$digest(); 145 | })); 146 | 147 | it('should count Todos correctly', function () { 148 | expect(ctrl.todos.length).toBe(5); 149 | expect(ctrl.remainingCount).toBe(3); 150 | expect(ctrl.completedCount).toBe(2); 151 | expect(ctrl.allChecked).toBeFalsy(); 152 | }); 153 | 154 | it('should save Todos to local storage', function () { 155 | expect(todoStorage.storage.length).toBe(5); 156 | }); 157 | 158 | it('should remove Todos w/o title on saving', function () { 159 | var todo = todoList[2]; 160 | todo.title = ''; 161 | 162 | ctrl.doneEditing(todo); 163 | expect(ctrl.todos.length).toBe(4); 164 | }); 165 | 166 | it('should trim Todos on saving', function () { 167 | var todo = todoList[0]; 168 | todo.title = ' buy moar unicorns '; 169 | 170 | ctrl.doneEditing(todo); 171 | expect(ctrl.todos[0].title).toBe('buy moar unicorns'); 172 | }); 173 | 174 | it('clearCompletedTodos() should clear completed Todos', function () { 175 | ctrl.clearCompletedTodos(); 176 | expect(ctrl.todos.length).toBe(3); 177 | }); 178 | 179 | it('markAll() should mark all Todos completed', function () { 180 | ctrl.markAll(); 181 | scope.$digest(); 182 | expect(ctrl.completedCount).toBe(5); 183 | }); 184 | 185 | it('revertTodo() get a Todo to its previous state', function () { 186 | var todo = todoList[0]; 187 | ctrl.editTodo(todo); 188 | todo.title = 'Unicorn sparkly skypuffles.'; 189 | ctrl.revertEditing(todo); 190 | scope.$digest(); 191 | expect(ctrl.todos[0].title).toBe('Uncompleted Item 0'); 192 | }); 193 | }); 194 | }); 195 | }()); 196 | -------------------------------------------------------------------------------- /test/unit/modulesSpec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, angular, testFooVar, expect*/ 2 | (function () { 3 | 'use strict'; 4 | 5 | 6 | describe('app1 module', function () { 7 | 8 | it('should be defined', function() { 9 | expect(angular.module('app1')).toBeDefined(); 10 | }); 11 | 12 | it('should *not* be a classy-ful module', function() { 13 | expect(angular.module('app1').classy).toBeUndefined(); 14 | }); 15 | 16 | }); 17 | 18 | describe('app2 module', function () { 19 | 20 | it('should be defined', function() { 21 | expect(angular.module('app2')).toBeDefined(); 22 | }); 23 | 24 | it('should be a classy-ful module', function() { 25 | expect(angular.module('app2').classy).toBeDefined(); 26 | }); 27 | 28 | it('should contain the default classy plugins', function() { 29 | var app = angular.module('app2'); 30 | expect(app.classy.activePlugins['classy.bindData']).toBeDefined(); 31 | expect(app.classy.activePlugins['classy.bindDependencies']).toBeDefined(); 32 | expect(app.classy.activePlugins['classy.bindMethods']).toBeDefined(); 33 | expect(app.classy.activePlugins['classy.register']).toBeDefined(); 34 | expect(app.classy.activePlugins['classy.watch']).toBeDefined(); 35 | expect(app.classy.activePlugins['classy.core']).toBeDefined(); 36 | }); 37 | 38 | }); 39 | 40 | describe('app3 module', function () { 41 | 42 | it('should be defined', function() { 43 | expect(angular.module('app3')).toBeDefined(); 44 | }); 45 | 46 | it('should be a classy-ful module', function() { 47 | expect(angular.module('app3').classy).toBeDefined(); 48 | }); 49 | 50 | it('should only contain the classy-core module', function() { 51 | var app = angular.module('app3'); 52 | expect(app.classy.activePlugins['classy.bindData']).toBeUndefined(); 53 | expect(app.classy.activePlugins['classy.bindDependencies']).toBeUndefined(); 54 | expect(app.classy.activePlugins['classy.bindMethods']).toBeUndefined(); 55 | expect(app.classy.activePlugins['classy.register']).toBeUndefined(); 56 | expect(app.classy.activePlugins['classy.watch']).toBeUndefined(); 57 | expect(app.classy.activePlugins['classy.core']).toBeDefined(); 58 | }); 59 | 60 | }); 61 | 62 | describe('app4 module', function () { 63 | 64 | it('should be defined', function() { 65 | expect(angular.module('app4')).toBeDefined(); 66 | }); 67 | 68 | it('should be a classy-ful module', function() { 69 | expect(angular.module('app4').classy).toBeDefined(); 70 | }); 71 | 72 | it('should contain the default classy plugins', function() { 73 | var app = angular.module('app4'); 74 | expect(app.classy.activePlugins['classy.bindData']).toBeDefined(); 75 | expect(app.classy.activePlugins['classy.bindDependencies']).toBeDefined(); 76 | expect(app.classy.activePlugins['classy.bindMethods']).toBeDefined(); 77 | expect(app.classy.activePlugins['classy.register']).toBeDefined(); 78 | expect(app.classy.activePlugins['classy.watch']).toBeDefined(); 79 | expect(app.classy.activePlugins['classy.core']).toBeDefined(); 80 | }); 81 | 82 | }); 83 | 84 | describe('app5 module', function () { 85 | 86 | it('should be defined', function() { 87 | expect(angular.module('app5')).toBeDefined(); 88 | }); 89 | 90 | it('should be a classy-ful module', function() { 91 | expect(angular.module('app5').classy).toBeDefined(); 92 | }); 93 | 94 | it('should contain the classy-core module and foo + bar plugins', function() { 95 | var app = angular.module('app5'); 96 | expect(app.classy.activePlugins['classy.bindData']).toBeUndefined(); 97 | expect(app.classy.activePlugins['classy.bindDependencies']).toBeUndefined(); 98 | expect(app.classy.activePlugins['classy.bindMethods']).toBeUndefined(); 99 | expect(app.classy.activePlugins['classy.register']).toBeUndefined(); 100 | expect(app.classy.activePlugins['classy.watch']).toBeUndefined(); 101 | expect(app.classy.activePlugins['classy.core']).toBeDefined(); 102 | expect(app.classy.activePlugins['classy.foo']).toBeDefined(); 103 | expect(app.classy.activePlugins['classy.bar']).toBeDefined(); 104 | }); 105 | 106 | }); 107 | 108 | 109 | describe('app6 module', function () { 110 | 111 | it('should be defined', function() { 112 | expect(angular.module('app6')).toBeDefined(); 113 | }); 114 | 115 | it('should be a classy-ful module', function() { 116 | expect(angular.module('app6').classy).toBeDefined(); 117 | }); 118 | 119 | it('should contain the default classy plugins and foo + bar plugins', function() { 120 | var app = angular.module('app6'); 121 | expect(app.classy.activePlugins['classy.bindData']).toBeDefined(); 122 | expect(app.classy.activePlugins['classy.bindDependencies']).toBeDefined(); 123 | expect(app.classy.activePlugins['classy.bindMethods']).toBeDefined(); 124 | expect(app.classy.activePlugins['classy.register']).toBeDefined(); 125 | expect(app.classy.activePlugins['classy.watch']).toBeDefined(); 126 | expect(app.classy.activePlugins['classy.core']).toBeDefined(); 127 | expect(app.classy.activePlugins['classy.foo']).toBeDefined(); 128 | expect(app.classy.activePlugins['classy.bar']).toBeDefined(); 129 | }); 130 | 131 | it('should have correct options set when a controller is created', function(){ 132 | var app = angular.module('app6'); 133 | 134 | expect(testFooVar).toBeUndefined(); 135 | expect(testBarVar).toBeUndefined(); 136 | 137 | app.classy.controller({name: 'testPluginsController'}); 138 | 139 | expect(app.__classyDefaults.foo.foo).toBe('boo'); 140 | expect(testFooVar).toBe('baz'); 141 | expect(testBarVar).toBe('baz'); 142 | }); 143 | 144 | }); 145 | 146 | describe('app7 module', function () { 147 | 148 | it('should be defined', function() { 149 | expect(angular.module('app7')).toBeDefined(); 150 | }); 151 | 152 | it('should *not* be a classy-ful module', function() { 153 | expect(angular.module('app7').classy).toBeUndefined(); 154 | }); 155 | 156 | }); 157 | 158 | }()); 159 | 160 | -------------------------------------------------------------------------------- /angular-classy.min.js: -------------------------------------------------------------------------------- 1 | !function(){var n="1.2.3",t={},o={},a=function(n,a){var r=function(n){if(o[n])for(var s=angular.module(n),e=0;er;r++)if(o=arguments[r],o!==n)for(t in o)a=o[t],n[t]&&n[t].constructor&&n[t].constructor===Object?i(n[t],a):n[t]=angular.copy(a);return n},s=angular.module;angular.module=function(o,r,i){var c=s(o,r,i);if(r){"classy.core"===o&&(t[o]={});var l=a(o,c);l["classy.core"]&&(c.classy={version:n,plugin:{controller:function(n){t[o]=n}},options:{controller:{}},activePlugins:l,controller:function(n){function t(){e.init(this,arguments,c)}return e.preInit(t,n,c),t},controllers:function(n){for(var t=0;tt;t++)if(t in this&&this[t]===n)return t;return-1};if(this.options.enabled){var a=n.constructor.prototype[this.options.keyName];for(var r in a){var i,s=a[r];if(!angular.isFunction(s)||o.call(this.options.ignore,r)>=0){if(angular.isString(s)){var e=this.$parse(s);i=function(){return e(n)}}}else i=angular.bind(n,s);angular.isFunction(i)&&(this.options.addToClass&&(n[r]=i),this.options.addToScope&&!this.hasPrivatePrefix(r)&&t.$scope&&(t.$scope[r]=i))}}}}),angular.module("classy.register",["classy.core"]).classy.plugin.controller({options:{enabled:!0,key:"name"},preInit:function(n,t,o){this.options.enabled&&angular.isString(t[this.options.key])&&o.controller(t[this.options.key],n)}}),angular.module("classy.watch",["classy.core"]).classy.plugin.controller({localInject:["$parse"],options:{enabled:!0,bindWatchToClass:!1,_watchKeywords:{normal:[],objectEquality:["{object}","{deep}"],collection:["{collection}","{shallow}"]}},isActive:function(n,t){if(this.options.enabled&&angular.isObject(n.watch)){if(!t.$scope)throw new Error("You need to inject `$scope` to use the watch object");return!0}},watchFns:{normal:function(n,t,o,a){return a.$scope.$watch(t,angular.bind(n,o))},objectEquality:function(n,t,o,a){return a.$scope.$watch(t,angular.bind(n,o),!0)},collection:function(n,t,o,a){return a.$scope.$watchCollection(t,angular.bind(n,o))}},convertToFunctionExpression:function(n,t){var o=this.$parse;return function(){return o(n)(t)}},postInit:function(n,t){if(this.isActive(n,t)){var o=this.options._watchKeywords;for(var a in n.watch){var r=n.watch[a];if(angular.isString(r)&&(r=n[r]),angular.isString(a)&&angular.isFunction(r)){var i=!1;for(var s in this.watchFns){var e=this.watchFns[s];if(i)break;for(var c=o[s],l=0;l= 0)) { 400 | boundMethod = angular.bind(klass, method); 401 | } else if (angular.isString(method)) { 402 | var getter = this.$parse(method); 403 | boundMethod = function() { 404 | return getter(klass); 405 | }; 406 | } 407 | if (angular.isFunction(boundMethod)) { 408 | if (this.options.addToClass) { 409 | klass[key] = boundMethod; 410 | } 411 | if (this.options.addToScope && !this.hasPrivatePrefix(key) && deps.$scope) { 412 | deps.$scope[key] = boundMethod; 413 | } 414 | } 415 | } 416 | } 417 | } 418 | }); 419 | 420 | angular.module('classy.register', ['classy.core']).classy.plugin.controller({ 421 | options: { 422 | enabled: true, 423 | key: 'name' 424 | }, 425 | preInit: function(classConstructor, classObj, module) { 426 | if (this.options.enabled && angular.isString(classObj[this.options.key])) { 427 | module.controller(classObj[this.options.key], classConstructor); 428 | } 429 | } 430 | }); 431 | 432 | angular.module('classy.watch', ['classy.core']).classy.plugin.controller({ 433 | localInject: ['$parse'], 434 | options: { 435 | enabled: true, 436 | bindWatchToClass: false, 437 | _watchKeywords: { 438 | normal: [], 439 | objectEquality: ['{object}', '{deep}'], 440 | collection: ['{collection}', '{shallow}'] 441 | } 442 | }, 443 | isActive: function(klass, deps) { 444 | if (this.options.enabled && angular.isObject(klass.watch)) { 445 | if (!deps.$scope) { 446 | throw new Error("You need to inject `$scope` to use the watch object"); 447 | return false; 448 | } 449 | return true; 450 | } 451 | }, 452 | watchFns: { 453 | normal: function(klass, expression, fn, deps) { 454 | return deps.$scope.$watch(expression, angular.bind(klass, fn)); 455 | }, 456 | objectEquality: function(klass, expression, fn, deps) { 457 | return deps.$scope.$watch(expression, angular.bind(klass, fn), true); 458 | }, 459 | collection: function(klass, expression, fn, deps) { 460 | return deps.$scope.$watchCollection(expression, angular.bind(klass, fn)); 461 | } 462 | }, 463 | convertToFunctionExpression: function(expression, context) { 464 | var parse = this.$parse; 465 | return function() { 466 | return parse(expression)(context); 467 | }; 468 | }, 469 | postInit: function(klass, deps, module) { 470 | if (!this.isActive(klass, deps)) { 471 | return; 472 | } 473 | var watchKeywords = this.options._watchKeywords; 474 | 475 | // for expression, fn of klass.watch 476 | for (var expression in klass.watch) { 477 | var fn = klass.watch[expression]; 478 | if (angular.isString(fn)) { 479 | fn = klass[fn]; 480 | } 481 | if (angular.isString(expression) && angular.isFunction(fn)) { 482 | var watchRegistered = false; 483 | 484 | // Search for keywords that identify it is a non-standard watch 485 | // for watchType, watchFn of @watchFns 486 | for (var watchType in this.watchFns) { 487 | var watchFn = this.watchFns[watchType]; 488 | if (watchRegistered) { 489 | break; 490 | } 491 | // for keyword in watchKeywords[watchType] 492 | var keywords = watchKeywords[watchType]; 493 | for (var i = 0; i < keywords.length; i++) { 494 | var keyword = keywords[i]; 495 | 496 | if (expression.indexOf(keyword) !== -1) { 497 | var normalizedExpression = expression.replace(keyword, ''); 498 | 499 | var exp = this.options.bindWatchToClass ? 500 | this.convertToFunctionExpression(normalizedExpression, klass) : 501 | normalizedExpression; 502 | 503 | watchFn(klass, exp, fn, deps); 504 | watchRegistered = true; 505 | break; 506 | } 507 | } 508 | } 509 | if (!watchRegistered) { 510 | // If no keywords have been found then register it as a normal watch 511 | var exp = this.options.bindWatchToClass ? 512 | this.convertToFunctionExpression(expression, klass) : 513 | expression; 514 | this.watchFns.normal(klass, exp, fn, deps); 515 | } 516 | } 517 | } 518 | } 519 | }); 520 | 521 | angular.module('classy', ["classy.bindData","classy.bindDependencies","classy.bindMethods","classy.core","classy.register","classy.watch"]); 522 | })(); -------------------------------------------------------------------------------- /angular-classy.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"angular-classy.min.js","sources":["angular-classy.js"],"names":["classyVersion","availablePlugins","alreadyRegisteredModules","getActiveClassyPlugins","name","origModule","getNextRequires","module","angular","i","requires","length","pluginName","plugin","obj","replace","__classyDefaults","copy","options","pluginDo","methodName","params","callbacks","plugins","classy","activePlugins","__options","prototype","pluginKeys","Object","keys","classyOptions","isFunction","before","returnVal","apply","after","copyAndExtendDeep","dst","key","value","_i","_len","arguments","constructor","origModuleMethod","reqs","configFn","activeClassyPlugins","version","controller","classObj","classyController","classFns","init","this","preInit","controllers","controllerArray","cC","cCs","localInject","classConstructor","buildConstructor","buildOptions","classKeys","hasOwnProperty","option","optionName","shorthandOptions","optionNames","j","isObject","k","extend","klass","$inject","injectIndex","deps","__classDepNames","isArray","depName","pluginPromises","then","push","initClass","postInit","boundClass","bind","all","enabled","addToScope","addToClass","privatePrefix","keyName","hasPrivatePrefix","string","prefix","slice","dataProp","data","call","isString","getter","$parse","fnKey","fnValue","$scope","scopeShortcut","depNames","inject","pluginDepNames","concat","initBefore","preDeps","ignore","__indexOf","indexOf","item","l","methods","boundMethod","method","bindWatchToClass","_watchKeywords","normal","objectEquality","collection","isActive","watch","Error","watchFns","expression","fn","$watch","$watchCollection","convertToFunctionExpression","context","parse","watchKeywords","watchRegistered","watchType","watchFn","keywords","keyword","normalizedExpression","exp"],"mappings":"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","sourcesContent":[";(function() {\n/*\nAngular Classy 1.2.3\nDave Jeffery, @DaveJ\nLicense: MIT\n */\nvar classyVersion = '1.2.3';\n\n/* global angular */\nvar availablePlugins = {};\n\nvar alreadyRegisteredModules = {};\n\nvar getActiveClassyPlugins = function(name, origModule) {\n // TODO: Write a test to ensure that this method gets called the correct amount of times\n\n var getNextRequires = function(name) {\n if (alreadyRegisteredModules[name]) {\n var module = angular.module(name);\n\n // for pluginName in module.requires\n for (var i = 0; i < module.requires.length; i++) {\n var pluginName = module.requires[i];\n var plugin = availablePlugins[pluginName];\n if (plugin) {\n obj[pluginName] = plugin;\n if (!plugin.name) {\n plugin.name = pluginName.replace('classy.', '');\n }\n if (!origModule.__classyDefaults) {\n origModule.__classyDefaults = {};\n }\n origModule.__classyDefaults[plugin.name] = angular.copy(plugin.options || {});\n }\n getNextRequires(pluginName);\n }\n }\n };\n\n var obj = {};\n alreadyRegisteredModules[name] = true;\n getNextRequires(name);\n return obj;\n};\n\n\n/**\n* Runs a particular stage of the lifecycle for all plugins.\n* Also runs the `before` and `after` callbacks if specified.\n*/\nvar pluginDo = function(methodName, params, callbacks) {\n // params = [klass, deps, module];\n\n var plugins = params[2].classy.activePlugins;\n var options = params[0].__options || params[0].prototype.__options;\n\n // for plugin of plugins\n var pluginKeys = Object.keys(plugins);\n for (var i = 0; i < pluginKeys.length; i++) {\n var plugin = plugins[pluginKeys[i]];\n plugin.options = options[plugin.name] || {};\n plugin.classyOptions = options;\n\n if (callbacks && angular.isFunction(callbacks.before)) {\n callbacks.before(plugin);\n }\n\n var returnVal;\n if (plugin && methodName && angular.isFunction(plugin[methodName])) {\n returnVal = plugin[methodName].apply(plugin, params);\n }\n\n if (callbacks && angular.isFunction(callbacks.after)) {\n callbacks.after(plugin, returnVal);\n }\n }\n};\n\n\n/**\n* Utility method to take an object and extend it with other objects\n* It does this recursively (deep) on inner objects too.\n*/\nvar copyAndExtendDeep = function(dst) {\n var key, obj, value, _i, _len;\n for (_i = 0, _len = arguments.length; _i < _len; _i++) {\n obj = arguments[_i];\n if (obj !== dst) {\n for (key in obj) {\n value = obj[key];\n if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {\n copyAndExtendDeep(dst[key], value);\n } else {\n dst[key] = angular.copy(value);\n }\n }\n }\n }\n return dst;\n};\n\nvar origModuleMethod = angular.module;\n\nangular.module = function(name, reqs, configFn) {\n /*\n * We have to monkey-patch the `angular.module` method to see if 'classy' has been specified\n * as a requirement. We also need the module name to we can register our classy controllers.\n * Unfortunately there doesn't seem to be a more pretty/pluggable way to this.\n */\n\n var module = origModuleMethod(name, reqs, configFn);\n\n if (reqs) {\n if (name === 'classy.core') {\n availablePlugins[name] = {};\n }\n\n var activeClassyPlugins = getActiveClassyPlugins(name, module);\n if (activeClassyPlugins['classy.core']) {\n module.classy = {\n version: classyVersion,\n plugin: {\n controller: function(plugin) { availablePlugins[name] = plugin; }\n },\n options: {\n controller: {}\n },\n activePlugins: activeClassyPlugins,\n controller: function(classObj) {\n /*\n * `classyController` contains only a set of proxy functions for `classFns`,\n * this is because I suspect that performance is better this way.\n * TODO: Test performance to see if this is the most performant way to do it.\n */\n\n // Pre-initialisation (before instance is created)\n classFns.preInit(classyController, classObj, module);\n\n function classyController() {\n // Initialisation (after instance is created)\n classFns.init(this, arguments, module);\n }\n\n return classyController;\n },\n /**\n * Accepts an array of controllers and returns the module, e.g.:\n * `module.classy.controllers([xxx, xxx]).config(xxx).run(xxx)`\n * Requested in issue #29\n */\n controllers: function(controllerArray) {\n // for classObj in controllerArray\n for (var i = 0; i < controllerArray.length; i++) {\n this.controller(controllerArray[i]);\n }\n\n return module;\n }\n };\n module.cC = module.classy.controller;\n module.cCs = module.classy.controllers;\n }\n }\n return module;\n};\n\n\nvar classFns = {\n\n localInject: ['$q'],\n\n preInit: function(classConstructor, classObj, module) {\n this.buildConstructor(classConstructor, classObj);\n this.buildOptions(classConstructor, classObj, module);\n\n pluginDo('preInitBefore', [classConstructor, classObj, module]);\n pluginDo('preInit', [classConstructor, classObj, module]);\n pluginDo('preInitAfter', [classConstructor, classObj, module]);\n },\n\n /**\n * Add properties from class object onto the class constructor\n */\n buildConstructor: function(classConstructor, classObj) {\n // for key of classObj\n var classKeys = Object.keys(classObj);\n for (var i = 0; i < classKeys.length; i++) {\n var key = classKeys[i];\n if (!classObj.hasOwnProperty(key)) continue;\n classConstructor.prototype[key] = classObj[key];\n }\n },\n\n /**\n * Build options object for all classy plugins\n */\n buildOptions: function(classConstructor, classObj, module) {\n var options = copyAndExtendDeep({}, module.__classyDefaults, module.classy.options.controller, classObj.__options);\n var shorthandOptions = {};\n\n // Collect shorthand options\n var option, optionName;\n var optionNames = Object.keys(options);\n for (var j = 0; j < optionNames.length; j++) {\n optionName = optionNames[j];\n option = options[optionNames[j]];\n if (!angular.isObject(option)) {\n shorthandOptions[optionName] = option;\n }\n }\n\n // Apply shorthand options to plugin options\n if (Object.keys(shorthandOptions).length) {\n for (var k = 0; k < optionNames.length; k++) {\n optionName = optionNames[k];\n option = options[optionNames[k]];\n if (angular.isObject(option)) {\n angular.extend(option, shorthandOptions);\n }\n }\n }\n\n classConstructor.prototype.__options = options;\n },\n\n init: function(klass, $inject, module) {\n var injectIndex = 0;\n var deps = {};\n\n // for key in klass.constructor.__classDepNames\n for (var i = 0; i < klass.constructor.__classDepNames.length; i++) {\n var key = klass.constructor.__classDepNames[i];\n deps[key] = $inject[injectIndex];\n injectIndex++;\n }\n pluginDo(null, [klass, deps, module], {\n before: function(plugin) {\n if (angular.isArray(plugin.localInject)) {\n // for depName in plugin.localInject\n for (var j = 0; j < plugin.localInject.length; j++) {\n var depName = plugin.localInject[j];\n plugin[depName] = $inject[injectIndex];\n injectIndex++;\n }\n }\n }\n });\n\n pluginDo('initBefore', [klass, deps, module]);\n\n var pluginPromises = [];\n pluginDo('init', [klass, deps, module], {\n after: function(plugin, returnVal) {\n if (returnVal && returnVal.then) {\n // Naively assume this is a promise\n pluginPromises.push(returnVal);\n }\n }\n });\n\n var initClass = function() {\n if (angular.isFunction(klass.init)) {\n klass.init();\n }\n pluginDo('initAfter', [klass, deps, module]);\n this.postInit(klass, deps, module);\n };\n var boundClass = angular.bind(this, initClass);\n if (pluginPromises.length) {\n // Injected dependency below is $q\n $inject[injectIndex].all(pluginPromises).then(boundClass);\n } else {\n boundClass();\n }\n },\n postInit: function(klass, deps, module) {\n pluginDo('postInitBefore', [klass, deps, module]);\n pluginDo('postInit', [klass, deps, module]);\n pluginDo('postInitAfter', [klass, deps, module]);\n }\n};\n\nangular.module('classy.core', []);\n\nangular.module('classy.bindData', ['classy.core']).classy.plugin.controller({\n localInject: ['$parse'],\n options: {\n enabled: true,\n addToScope: true,\n addToClass: true,\n privatePrefix: '_',\n keyName: 'data'\n },\n hasPrivatePrefix: function(string) {\n var prefix = this.options.privatePrefix;\n if (!prefix) {\n return false;\n } else {\n return string.slice(0, prefix.length) === prefix;\n }\n },\n init: function(klass, deps, module) {\n // Adds objects returned by or set to the `$scope`\n var dataProp = klass.constructor.prototype[this.options.keyName];\n if (this.options.enabled && dataProp) {\n var data = angular.copy(dataProp);\n if (angular.isFunction(data)) {\n data = data.call(klass);\n } else if (angular.isObject(data)) {\n for (var key in data) {\n var value = data[key];\n if (angular.isString(value)) {\n var getter = this.$parse(value);\n data[key] = getter(klass);\n } else {\n data[key] = value;\n }\n }\n }\n for (var fnKey in data) {\n var fnValue = data[fnKey];\n if (this.options.addToClass) {\n klass[fnKey] = fnValue;\n }\n if (this.options.addToScope && !this.hasPrivatePrefix(fnKey) && deps.$scope) {\n deps.$scope[fnKey] = fnValue;\n }\n }\n }\n }\n});\n\nangular.module('classy.bindDependencies', ['classy.core']).classy.plugin.controller({\n options: {\n enabled: true,\n scopeShortcut: '$'\n },\n preInit: function(classConstructor, classObj, module) {\n var depNames = classObj.inject || [];\n if (angular.isArray(depNames)) {\n this.inject(classConstructor, depNames, module);\n }\n },\n inject: function(classConstructor, depNames, module) {\n var pluginDepNames = [];\n for (var pluginName in module.classy.activePlugins) {\n var plugin = module.classy.activePlugins[pluginName];\n if (angular.isArray(plugin.localInject)) {\n pluginDepNames = pluginDepNames.concat(plugin.localInject);\n }\n }\n pluginDepNames = pluginDepNames.concat(classFns.localInject);\n classConstructor.__classDepNames = angular.copy(depNames);\n classConstructor.$inject = depNames.concat(pluginDepNames);\n },\n initBefore: function(klass, deps, module) {\n if (this.options.enabled) {\n var preDeps = klass.constructor.$inject;\n for (var i = 0; i < preDeps.length; ++i) {\n var key = preDeps[i];\n klass[key] = deps[key];\n if (key === '$scope' && this.options.scopeShortcut) {\n klass[this.options.scopeShortcut] = klass[key];\n }\n }\n }\n }\n});\n\nangular.module('classy.bindMethods', ['classy.core']).classy.plugin.controller({\n localInject: ['$parse'],\n options: {\n enabled: true,\n addToScope: true,\n addToClass: true,\n privatePrefix: '_',\n ignore: ['constructor', 'init'],\n keyName: 'methods'\n },\n hasPrivatePrefix: function(string) {\n var prefix;\n prefix = this.options.privatePrefix;\n if (!prefix) {\n return false;\n } else {\n return string.slice(0, prefix.length) === prefix;\n }\n },\n init: function(klass, deps, module) {\n // indexOf shim for IE <= 8\n var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };\n\n if (this.options.enabled) {\n var methods = klass.constructor.prototype[this.options.keyName];\n for (var key in methods) {\n var method = methods[key];\n\n var boundMethod;\n if (angular.isFunction(method) && !(__indexOf.call(this.options.ignore, key) >= 0)) {\n boundMethod = angular.bind(klass, method);\n } else if (angular.isString(method)) {\n var getter = this.$parse(method);\n boundMethod = function() {\n return getter(klass);\n };\n }\n if (angular.isFunction(boundMethod)) {\n if (this.options.addToClass) {\n klass[key] = boundMethod;\n }\n if (this.options.addToScope && !this.hasPrivatePrefix(key) && deps.$scope) {\n deps.$scope[key] = boundMethod;\n }\n }\n }\n }\n }\n});\n\nangular.module('classy.register', ['classy.core']).classy.plugin.controller({\n options: {\n enabled: true,\n key: 'name'\n },\n preInit: function(classConstructor, classObj, module) {\n if (this.options.enabled && angular.isString(classObj[this.options.key])) {\n module.controller(classObj[this.options.key], classConstructor);\n }\n }\n});\n\nangular.module('classy.watch', ['classy.core']).classy.plugin.controller({\n localInject: ['$parse'],\n options: {\n enabled: true,\n bindWatchToClass: false,\n _watchKeywords: {\n normal: [],\n objectEquality: ['{object}', '{deep}'],\n collection: ['{collection}', '{shallow}']\n }\n },\n isActive: function(klass, deps) {\n if (this.options.enabled && angular.isObject(klass.watch)) {\n if (!deps.$scope) {\n throw new Error(\"You need to inject `$scope` to use the watch object\");\n return false;\n }\n return true;\n }\n },\n watchFns: {\n normal: function(klass, expression, fn, deps) {\n return deps.$scope.$watch(expression, angular.bind(klass, fn));\n },\n objectEquality: function(klass, expression, fn, deps) {\n return deps.$scope.$watch(expression, angular.bind(klass, fn), true);\n },\n collection: function(klass, expression, fn, deps) {\n return deps.$scope.$watchCollection(expression, angular.bind(klass, fn));\n }\n },\n convertToFunctionExpression: function(expression, context) {\n var parse = this.$parse;\n return function() {\n return parse(expression)(context);\n };\n },\n postInit: function(klass, deps, module) {\n if (!this.isActive(klass, deps)) {\n return;\n }\n var watchKeywords = this.options._watchKeywords;\n\n // for expression, fn of klass.watch\n for (var expression in klass.watch) {\n var fn = klass.watch[expression];\n if (angular.isString(fn)) {\n fn = klass[fn];\n }\n if (angular.isString(expression) && angular.isFunction(fn)) {\n var watchRegistered = false;\n\n // Search for keywords that identify it is a non-standard watch\n // for watchType, watchFn of @watchFns\n for (var watchType in this.watchFns) {\n var watchFn = this.watchFns[watchType];\n if (watchRegistered) {\n break;\n }\n // for keyword in watchKeywords[watchType]\n var keywords = watchKeywords[watchType];\n for (var i = 0; i < keywords.length; i++) {\n var keyword = keywords[i];\n\n if (expression.indexOf(keyword) !== -1) {\n var normalizedExpression = expression.replace(keyword, '');\n\n var exp = this.options.bindWatchToClass ?\n this.convertToFunctionExpression(normalizedExpression, klass) :\n normalizedExpression;\n\n watchFn(klass, exp, fn, deps);\n watchRegistered = true;\n break;\n }\n }\n }\n if (!watchRegistered) {\n // If no keywords have been found then register it as a normal watch\n var exp = this.options.bindWatchToClass ?\n this.convertToFunctionExpression(expression, klass) :\n expression;\n this.watchFns.normal(klass, exp, fn, deps);\n }\n }\n }\n }\n});\n\nangular.module('classy', [\"classy.bindData\",\"classy.bindDependencies\",\"classy.bindMethods\",\"classy.core\",\"classy.register\",\"classy.watch\"]);\n})();"],"sourceRoot":"/source/"} --------------------------------------------------------------------------------