├── .npmignore ├── .gitignore ├── .travis.yml ├── test ├── .jshintrc ├── spec │ ├── regression.spec.js │ ├── modelFactory.spec.js │ └── modelUsage.spec.js ├── karma.conf.js └── karma.conf-ci.js ├── bower.json ├── examples ├── plain │ └── index.html └── simple.js ├── LICENSE ├── package.json ├── Gruntfile.js ├── CONTRIBUTING.md ├── README.md └── src └── modelFactory.js /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | test/ 4 | src/ 5 | examples/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | jspm_packages/ 4 | .idea/* 5 | _site/ 6 | typings/ 7 | dist/ 8 | *.log 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | branches: 7 | only: 8 | - master 9 | notifications: 10 | email: false 11 | node_js: 12 | - '4.1' 13 | before_install: 14 | - npm i -g npm@^2.0.0 15 | - npm install -g grunt 16 | - npm install -g bower 17 | - npm install 18 | - bower install 19 | before_script: 20 | - npm prune 21 | after_success: 22 | - npm run semantic-release 23 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "after": false, 23 | "afterEach": false, 24 | "angular": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "browser": false, 28 | "describe": false, 29 | "expect": false, 30 | "inject": false, 31 | "it": false, 32 | "jasmine": false, 33 | "spyOn": false 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-model-factory", 3 | "authors": [ 4 | "Austin McDaniel ", 5 | "Juri Strumpflohner " 6 | ], 7 | "description": "modelFactory makes working with RESTful APIs in AngularJS easy", 8 | "license": "MIT", 9 | "homepage": "http://swimlane.github.io/angular-model-factory/", 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/swimlane/model-factory.git" 13 | }, 14 | "main": "./dist/angular-model-factory.js", 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "src", 20 | "examples" 21 | ], 22 | "dependencies": { 23 | "uri-templates": "~0.1.5", 24 | "deep-diff": "~0.2.0", 25 | "angular": "1.3.x" 26 | }, 27 | "devDependencies": { 28 | "angular-mocks": "1.x", 29 | "angular-scenario": "1.x" 30 | }, 31 | "resolutions": { 32 | "angular": "1.4.9" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/plain/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Model Factory demo

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 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 all 13 | 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 THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-model-factory", 3 | "author": "Austin McDaniel ", 4 | "authors": [ 5 | "Juri Strumpflohner " 6 | ], 7 | "description": "modelFactory makes working with RESTful APIs in AngularJS easy", 8 | "license": "MIT", 9 | "devDependencies": { 10 | "grunt": "~0.4.5", 11 | "grunt-bower": "*", 12 | "grunt-cli": ">= 0.1.7", 13 | "grunt-contrib-concat": "*", 14 | "grunt-contrib-jshint": "*", 15 | "grunt-contrib-uglify": "*", 16 | "grunt-karma": "~0.9.0", 17 | "jasmine-core": "~2.1.2", 18 | "karma": "~0.12.28", 19 | "karma-chrome-launcher": "~0.1.5", 20 | "karma-firefox-launcher": "~0.1.3", 21 | "karma-ie-launcher": "^0.2.0", 22 | "karma-jasmine": "~0.3.1", 23 | "karma-mocha-reporter": "~0.3.1", 24 | "karma-phantomjs-launcher": "~0.1.4", 25 | "ng-annotate": "^1.0.2", 26 | "publish-latest": "^1.1.2", 27 | "semantic-release": "^4.3.5" 28 | }, 29 | "main": "./dist/angular-model-factory.js", 30 | "scripts": { 31 | "build": "grunt build", 32 | "prepublish": "npm run build", 33 | "postpublish": "publish-latest", 34 | "test": "grunt karma:ci", 35 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/Swimlane/angular-model-factory.git" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/spec/regression.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | Regression tests that emerge from GitHub issues. Their main purpose is to identify 5 | a bug and make sure it won't be introduced any more 6 | */ 7 | 8 | describe('A person model defined using modelFactory', function() { 9 | var Department; 10 | var $httpBackend; 11 | 12 | beforeEach(angular.mock.module('modelFactory')); 13 | 14 | beforeEach(function() { 15 | angular.module('test-module', ['modelFactory']) 16 | .factory('Department', function($modelFactory) { 17 | var model = $modelFactory('department', { 18 | pk: 'ID', 19 | 20 | actions: { 21 | // base: { 22 | // override: true 23 | // }, 24 | 'lookup': { 25 | url: 'Lookups', 26 | cache: true, 27 | method: 'GET', 28 | params: { 29 | Range: 'All' 30 | } 31 | } 32 | } 33 | }); 34 | 35 | return model; 36 | }); 37 | }); 38 | 39 | beforeEach(angular.mock.module('test-module')); 40 | 41 | beforeEach(inject(function(_$httpBackend_, _Department_) { 42 | $httpBackend = _$httpBackend_; 43 | Department = _Department_; 44 | })); 45 | 46 | afterEach(function() { 47 | $httpBackend.verifyNoOutstandingExpectation(); 48 | $httpBackend.verifyNoOutstandingRequest(); 49 | }); 50 | 51 | it('should correctly override the defaults with the passed data', function(){ 52 | var customParam = 'test'; 53 | 54 | $httpBackend.expectGET('department/Lookups?Range=' + customParam).respond(200, { 55 | id: 1, 56 | name: 'Human resources' 57 | }); 58 | 59 | Department.lookup({ Range: customParam }); 60 | 61 | $httpBackend.flush(); 62 | }); 63 | 64 | }); -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.12/config/configuration-file.html 3 | // Generated on 2014-11-27 using 4 | // generator-karma 0.8.2 5 | 6 | module.exports = function(config) { 7 | config.set({ 8 | // enable / disable watching file and executing tests whenever any file changes 9 | autoWatch: true, 10 | 11 | // base path, that will be used to resolve files and exclude 12 | basePath: '../', 13 | 14 | // testing framework to use (jasmine/mocha/qunit/...) 15 | frameworks: ['jasmine'], 16 | 17 | // list of files / patterns to load in the browser 18 | files: [ 19 | 'bower_components/angular/angular.js', 20 | 'bower_components/angular-mocks/angular-mocks.js', 21 | // 'bower_components/angular-scenario/angular-scenario.js', 22 | 'bower_components/deep-diff/index.js', 23 | 'bower_components/uri-templates/uri-templates.js', 24 | // 'bower_components/angular-animate/angular-animate.js', 25 | // 'bower_components/angular-cookies/angular-cookies.js', 26 | // 'bower_components/angular-resource/angular-resource.js', 27 | // 'bower_components/angular-route/angular-route.js', 28 | // 'bower_components/angular-sanitize/angular-sanitize.js', 29 | // 'bower_components/angular-touch/angular-touch.js', 30 | 'src/**/*.js', 31 | 'test/mock/**/*.js', 32 | 'test/spec/**/*.js' 33 | ], 34 | 35 | // list of files / patterns to exclude 36 | exclude: [], 37 | 38 | // web server port 39 | port: 9876, 40 | 41 | // Start these browsers, currently available: 42 | // - Chrome 43 | // - ChromeCanary 44 | // - Firefox 45 | // - Opera 46 | // - Safari (only Mac) 47 | // - PhantomJS 48 | // - IE (only Windows) 49 | browsers: [ 50 | 'PhantomJS' 51 | ], 52 | 53 | // if the browser doesn't capture within the given ms, kill it 54 | captureTimeout: 60000, 55 | 56 | // Which plugins to enable 57 | // plugins: [ 58 | // 'karma-phantomjs-launcher', 59 | // 'karma-jasmine' 60 | // ], 61 | 62 | // Continuous Integration mode 63 | // if true, it capture browsers, run tests and exit 64 | singleRun: false, 65 | 66 | colors: true, 67 | 68 | // level of logging 69 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 70 | logLevel: config.LOG_INFO, 71 | 72 | // Uncomment the following lines if you are using grunt's server to run the tests 73 | // proxies: { 74 | // '/': 'http://localhost:9000/' 75 | // }, 76 | // URL root prevent conflicts with the site root 77 | // urlRoot: '_karma_' 78 | }); 79 | }; 80 | -------------------------------------------------------------------------------- /test/karma.conf-ci.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.12/config/configuration-file.html 3 | // Generated on 2014-11-27 using 4 | // generator-karma 0.8.2 5 | 6 | module.exports = function(config) { 7 | config.set({ 8 | // enable / disable watching file and executing tests whenever any file changes 9 | autoWatch: true, 10 | 11 | // base path, that will be used to resolve files and exclude 12 | basePath: '../', 13 | 14 | // testing framework to use (jasmine/mocha/qunit/...) 15 | frameworks: ['jasmine'], 16 | 17 | // list of files / patterns to load in the browser 18 | files: [ 19 | 'bower_components/angular/angular.js', 20 | 'bower_components/angular-mocks/angular-mocks.js', 21 | // 'bower_components/angular-scenario/angular-scenario.js', 22 | // 'bower_components/deep-diff/index.js', 23 | // 'bower_components/uri-templates/uri-templates.js', 24 | // 'bower_components/angular-animate/angular-animate.js', 25 | // 'bower_components/angular-cookies/angular-cookies.js', 26 | // 'bower_components/angular-resource/angular-resource.js', 27 | // 'bower_components/angular-route/angular-route.js', 28 | // 'bower_components/angular-sanitize/angular-sanitize.js', 29 | // 'bower_components/angular-touch/angular-touch.js', 30 | 'dist/angular-model-factory-bundle.min.js', 31 | 'test/mock/**/*.js', 32 | 'test/spec/**/*.js' 33 | ], 34 | 35 | // list of files / patterns to exclude 36 | exclude: [], 37 | 38 | // web server port 39 | port: 9876, 40 | 41 | // Start these browsers, currently available: 42 | // - Chrome 43 | // - ChromeCanary 44 | // - Firefox 45 | // - Opera 46 | // - Safari (only Mac) 47 | // - PhantomJS 48 | // - IE (only Windows) 49 | browsers: [ 50 | 'PhantomJS' 51 | ], 52 | 53 | // if the browser doesn't capture within the given ms, kill it 54 | captureTimeout: 60000, 55 | 56 | // Which plugins to enable 57 | // plugins: [ 58 | // 'karma-phantomjs-launcher', 59 | // 'karma-jasmine' 60 | // ], 61 | 62 | // Continuous Integration mode 63 | // if true, it capture browsers, run tests and exit 64 | singleRun: false, 65 | 66 | colors: true, 67 | 68 | // level of logging 69 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 70 | logLevel: config.LOG_INFO, 71 | 72 | // Uncomment the following lines if you are using grunt's server to run the tests 73 | // proxies: { 74 | // '/': 'http://localhost:9000/' 75 | // }, 76 | // URL root prevent conflicts with the site root 77 | // urlRoot: '_karma_' 78 | }); 79 | }; -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.loadNpmTasks('grunt-contrib-concat'); 4 | grunt.loadNpmTasks('grunt-contrib-uglify'); 5 | grunt.loadNpmTasks('grunt-karma'); 6 | 7 | var ngAnnotate = require("ng-annotate"); 8 | 9 | grunt.initConfig({ 10 | pkg: grunt.file.readJSON('bower.json'), 11 | npmpkg: grunt.file.readJSON('package.json'), 12 | 13 | meta: { 14 | banner: '/**\n' + 15 | ' * <%= pkg.description %>\n' + 16 | ' * @version v<%= npmpkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + 17 | ' * @link <%= pkg.homepage %>\n' + 18 | ' * @author <%= pkg.authors.join(", ") %>\n' + 19 | ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + 20 | ' */\n' 21 | }, 22 | bower: { 23 | install: {} 24 | }, 25 | concat: { 26 | options: { 27 | //banner: '<%= meta.banner %>\n(function(angular, undefined) {\n\'use strict\';\n', 28 | banner: '<%= meta.banner %>\n', 29 | footer: '', 30 | process: function(src, filepath) { 31 | var res = ngAnnotate(src, { 32 | add: true, 33 | }); 34 | 35 | if (res.errors) { 36 | // do something with this, res.errors is now an array of strings 37 | throw new Error(res.errors.join("\n")); 38 | } else { 39 | return res.src; 40 | } 41 | } 42 | }, 43 | dist: { 44 | files: { 45 | 'dist/<%= pkg.name %>.js': [ 46 | 'src/**/*.js' 47 | ], 48 | 'dist/<%= pkg.name %>-bundle.js': [ 49 | 'bower_components/deep-diff/releases/deep-diff-0.2.0.min.js', 50 | 'bower_components/uri-templates/uri-templates.js', 51 | 'src/**/*.js' 52 | ] 53 | } 54 | } 55 | }, 56 | uglify: { 57 | options: { 58 | mangle: true, 59 | banner: '<%= meta.banner %>' 60 | }, 61 | dist: { 62 | files: { 63 | 'dist/<%= pkg.name %>.min.js': 'dist/<%= pkg.name %>.js', 64 | 'dist/<%= pkg.name %>-bundle.min.js': 'dist/<%= pkg.name %>-bundle.js' 65 | } 66 | } 67 | }, 68 | karma: { 69 | dev: { 70 | configFile: 'test/karma.conf.js', 71 | singleRun: false, 72 | autoWatch: true, 73 | browsers: ['Chrome'], 74 | reporters: ['mocha'] 75 | }, 76 | ie: { 77 | configFile: 'test/karma.conf-ci.js', 78 | singleRun: true, 79 | autoWatch: false, 80 | browsers: ['IE'], 81 | reporters: ['mocha'] 82 | }, 83 | ci: { 84 | configFile: 'test/karma.conf-ci.js', 85 | singleRun: true, 86 | autoWatch: false, 87 | browsers: ['PhantomJS'], 88 | reporters: ['mocha'] 89 | } 90 | } 91 | }); 92 | 93 | grunt.registerTask('build', ['concat', 'uglify']); 94 | 95 | return grunt; 96 | }; -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | define(['angular', 'model-factory'], function (angular) { 2 | 3 | var module = angular.module('myapp', ['modelFactory']); 4 | 5 | module.factory('AnimalModel', function() { 6 | function Animal(val) { 7 | angular.extend(this, val); 8 | }; 9 | 10 | Animal.prototype.dateAdded = function(val) { 11 | return new Date(val); 12 | }; 13 | 14 | return Animal; 15 | }); 16 | 17 | module.factory('ZooModel', function($modelFactory, AnimalModel){ 18 | return $modelFactory('api/zoo', { 19 | defaults: { 20 | zooName: 'New Zoo' 21 | }, 22 | map: { 23 | animals: function(animal){ 24 | return animal.map(function(a){ return new AnimalModel(a); }) 25 | } 26 | }, 27 | actions: { 28 | query: { 29 | cache: true 30 | }, 31 | $copy: { 32 | method: 'POST', 33 | url: 'copy' 34 | } 35 | } 36 | }); 37 | }); 38 | 39 | module.config(function ($stateProvider) { 40 | $stateProvider.state('zoo', { 41 | url: '/zoo', 42 | templateUrl: 'zoo.tpl.html', 43 | controller: 'ZooCtrl', 44 | resolve: { 45 | zoos: function (ZooModel) { 46 | return ZooModel.query(); 47 | } 48 | } 49 | }); 50 | }); 51 | 52 | module.controller('ZooCtrl', function ($scope, ZooModel, zoos) { 53 | 54 | //-> zoos = [ ZooModel({ type: 'National', name: 'DC Zoo', id: '123' }) ] 55 | $scope.zoos = zoos; 56 | 57 | $scope.editZoo = function(zoo){ 58 | // UPDATE since we have an id 59 | zoo.$save().then(function(model){ 60 | 61 | // cache was invalidated 62 | alert('Zoo was updated'); 63 | }); 64 | }; 65 | 66 | $scope.deleteZoo = function(zoo){ 67 | zoo.$destroy().then(function(){ 68 | 69 | // cache was invalidated 70 | alert('Zoo was deleted'); 71 | 72 | // zoo was automatically removed from array 73 | //-> $scope.zoos = []; 74 | }); 75 | }; 76 | 77 | $scope.createZoo = function(name){ 78 | var newZoo = new ZooModel({ 79 | name: name 80 | }); 81 | 82 | // POST since no id 83 | zooModel.$save().then(function(model){ 84 | //-> ZooModel({ name: 'whatever', newAnimal: true }) 85 | 86 | // push our new zoo into the model 87 | // this will relate the zoo to the model 88 | // so if a user destorys this model it will 89 | // automatically be removed from the array too 90 | $scope.zoos.push(model); 91 | }); 92 | }; 93 | 94 | $scope.getZoo = function(id){ 95 | // get a zoo by id 96 | ZooModel.get(id).then(function(model){ 97 | $scope.newZoo = model; 98 | }); 99 | }; 100 | 101 | $scope.getPandas = function(){ 102 | // get a zoo by animalType of panda 103 | // GET api/zoo?animalType=panda 104 | ZooModel.query({ animalType: 'panda' }).then(function(models){ 105 | $scope.pandas = models; 106 | }); 107 | }; 108 | 109 | $scope.refreshZoos = function(){ 110 | ZooModel.query().then(function(models){ 111 | // models = [ ZooModel({ type: 'National', name: 'DC Zoo', id: '123' }) ] 112 | $scope.allZoos = models; 113 | }); 114 | }; 115 | 116 | }); 117 | 118 | return module; 119 | }); 120 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | We'd love to get contributions from your part...in the end that's the value behind sharing, right? :smile: 5 | However, for staying organized we'd like you to follow these simple guidelines: 6 | 7 | - [Issues](#issues) 8 | - [Commit Message Guidelines](#commit) 9 | - [Coding](#coding) 10 | 11 | ## Issues 12 | 13 | If you have a bug or enhancement request, please file an issue. 14 | 15 | When submitting an issue, please include context from your test and 16 | your application. If there's an error, please include the error text. 17 | 18 | The best would be to submit a PR with a failing test :smiley:. 19 | 20 | ## Commit Message Guidelines 21 | 22 | These guidelines have been taken and adapted from the [official Angular guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines). By following the rules also mentioned in [conventional-changelog](https://www.npmjs.com/package/conventional-changelog). This leads to much more readable and clearer commit messages. 23 | 24 | ### Commit Message Format 25 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special 26 | format that includes a **type**, a **scope** and a **subject**: 27 | 28 | ``` 29 | (): 30 | 31 | 32 | 33 |