├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── package.json ├── src └── angular-resource-x.js └── tests ├── configs └── karma.conf.js ├── coverage └── PhantomJS 1.9.8 (Mac OS X) │ ├── base.css │ ├── index.html │ ├── prettify.css │ ├── prettify.js │ ├── sort-arrow-sprite.png │ ├── sorter.js │ └── src │ ├── angular-resource-x.js.html │ ├── index.html │ ├── resource_.js.html │ └── resourcex.js.html ├── mocks ├── Departments.mocks.js └── people.mocks.js └── resourcex.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | bower_components 3 | tests/coverage 4 | node_modules 5 | dist 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | 5 | // Load grunt tasks automatically 6 | require('load-grunt-tasks')(grunt); 7 | 8 | // Time how long tasks take. Can help when optimizing build times 9 | require('time-grunt')(grunt); 10 | var pkg = require('./bower.json'); 11 | 12 | // Configurable paths for the application 13 | var appConfig = { 14 | app: pkg.appPath || 'src', 15 | dist: 'dist' 16 | }; 17 | 18 | // Define the configuration for all the tasks 19 | grunt.initConfig({ 20 | 21 | // Project settings 22 | resourceX: appConfig, 23 | 24 | // Watches files for changes and runs tasks based on the changed files 25 | watch: { 26 | js: { 27 | files: ['<%= resourceX.app %>/**/*.js'], 28 | tasks: ['newer:jshint:all'] 29 | }, 30 | gruntfile: { 31 | files: ['Gruntfile.js'] 32 | } 33 | }, 34 | 35 | bump: { 36 | options: { 37 | files: ['package.json'], 38 | updateConfigs: [], 39 | commit: true, 40 | commitMessage: 'Release v%VERSION%', 41 | commitFiles: ['package.json'], 42 | createTag: true, 43 | tagName: 'v%VERSION%', 44 | tagMessage: 'Version %VERSION%', 45 | push: true, 46 | pushTo: 'origin', 47 | gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d', 48 | globalReplace: false, 49 | prereleaseName: false, 50 | regExp: false 51 | } 52 | }, 53 | 54 | // Make sure code styles are up to par and there are no obvious mistakes 55 | jshint: { 56 | options: { 57 | jshintrc: '.jshintrc', 58 | reporter: require('jshint-stylish') 59 | }, 60 | all: { 61 | src: [ 62 | 'Gruntfile.js', 63 | '<%= resourceX.app %>/components/**/*.js' 64 | ] 65 | }, 66 | test: { 67 | options: { 68 | jshintrc: 'test/.jshintrc' 69 | }, 70 | src: ['test/spec/{,*/}*.js'] 71 | } 72 | }, 73 | 74 | uglify: { 75 | my_target: { 76 | options: { 77 | sourceMap: true, 78 | sourceMapName: 'dist/angular-resource-x.map' 79 | }, 80 | files: { 81 | 'dist/resource_.min.js': ['src/angular-resource-x.js'] 82 | } 83 | } 84 | }, 85 | 86 | // Empties folders to start fresh 87 | clean: { 88 | dist: { 89 | files: [{ 90 | dot: true, 91 | src: [ 92 | '.tmp', 93 | '<%= resourceX.dist %>/{,*/}*', 94 | '!<%= resourceX.dist %>/.git{,*/}*' 95 | ] 96 | }] 97 | }, 98 | server: '.tmp' 99 | }, 100 | 101 | ngAnnotate: { 102 | dist: { 103 | files: [{ 104 | expand: true, 105 | cwd: 'dist/', 106 | src: 'resource_.js', 107 | dest: 'dist/angular-resource-x.js' 108 | }] 109 | } 110 | }, 111 | 112 | // Test settings 113 | karma: { 114 | unit: { 115 | configFile: 'tests/configs/karma.conf.js', 116 | singleRun: true 117 | } 118 | } 119 | }); 120 | 121 | grunt.registerTask('test', [ 122 | 'karma' 123 | ]); 124 | 125 | grunt.registerTask('build', [ 126 | 'ngAnnotate', 127 | 'uglify' 128 | ]); 129 | 130 | grunt.registerTask('default', [ 131 | 'build', 132 | 'test' 133 | ]); 134 | }; 135 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Luciano Pereira 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `$resource_` (ng-resource-x) 2 | 3 | [![Join the chat at https://gitter.im/luciano7/ng-resource-x](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/luciano7/ng-resource-x?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/luciano7/angular-resource-x.svg)](https://travis-ci.org/luciano7/ng-resource-x) 4 | 5 | ## Get All You Need out of $resource! 6 | 7 | How do you use $resource_? Well, you can use it exactly the same as plain $resource. 8 | ```javascript 9 | var departments = $resource_('departments/:id/', {'id':'@id'}); 10 | ``` 11 | 12 | However the resource extension (`$resource_`) service wraps angular's `$resource` service to provide the following utilities: 13 | * **[Relationships](#resource-relationships)**: resources _always_ have relationships, $resource should be aware of them! 14 | * **[Methods](#adding-methods)**: No more creating a separate service to manage your $resource's business logic and validation! 15 | * **[One Call, Get All](#using-nested-resources)**: Don't pollute your ui-router resolve with 4 different calls to build one object, get all the relationships at once! 16 | 17 | ## Usage 18 | ```javascript 19 | //In this example, we have a Department resource which has employees as a sub-resource. 20 | var Department = $resource_('departments/:id/', {'id':'@department_id'}) 21 | //related resource 22 | .relate('employees', $resource_('people/:id/', {id:'@id', department:'^department_id'}); 23 | //instance methods 24 | .method('hire', hireMethod) 25 | //static methods 26 | .static('getManagers', getManagersMethod); 27 | 28 | ///////// 29 | // methods allow you to be more declarative to users of your service. They don't 30 | // need to know details of the data model, they can just descriptive methods 31 | // like: hire(person). 32 | function hireMethod(person, job){ 33 | this._employees.push(person); 34 | person.department = this.department_id; 35 | person.jobTitle = job; 36 | return person.$save(); 37 | } 38 | 39 | function getManagersMethod(){ 40 | return this.get({'jobTitle':'manager'}); 41 | } 42 | ``` 43 | Now that you've declared Departments and Employees, you can use them like so: 44 | 45 | ```javascript 46 | //by default, getFull will get all related resources 47 | var Sales = Department.getFull({'name':'Sales'}); 48 | //the above sends out 2 requests: GET /departments?name=Sales and GET /people?department_id=1 49 | Sales.$promise.then(example); 50 | 51 | function example(){ 52 | //by default we prefix related results with a '_' but it's configurable/removable 53 | Sales._people.hire(new Person({'name': 'Joey', 'jobTitle':'manager'})); 54 | //will output 'Joey says Hello!' 55 | console.log(Sales._people[0].name + ' says Hello!'); 56 | } 57 | ``` 58 | 59 | You don't have to use .getFull at all, all related resources are just added as an array to the `$resource_` as well: 60 | ```javascript 61 | //Say we want to get all the female workers who work in accounting 62 | var Accounting = Department.get({'name':'Accounting'}); 63 | var Accounting.$promise.then(example); 64 | 65 | function example(){ 66 | var Accountants = Accounting.$relationships['employees']; 67 | var FemaleAccountants = Accountants.query({'sex': 'female'}); 68 | //the above request will look like: GET /people?department_id=2&sex=female 69 | //the department_id is inferred from the parent 70 | } 71 | ``` 72 | 73 | ### Creating a `$resource_` 74 | `$resource_` takes the same arguments to create a resource as `$resource`. Any resource you can create with $resource can be created with `$resource_`. 75 | 76 | #### Resource Relationships 77 | The `^` as the first character in a param map stands in place of a `@` letting `$resource_` know to look for this parameter on its `$parent` property. You can go all sorts of crazy with '^' as they DO work through multiple levels (`^^^id` would get the ID of an object 3 levels above this one). Alternatively you can simply use the $parent variable like you would in a plain $resource call: 78 | ```javascript 79 | var People = $resource_('people/:id/', {id:'@id', department:'@$parent.department_id'}) 80 | ``` 81 | Either way, the People resource will look for a parent containing the department_id value and pass it on to all its calls. This way when its created through a parent department, all subsequent calls to People will attempt to filter by department_id. 82 | 83 | #### Using Related Resources 84 | You can then nest the declared resource using the `relate(name, function)` method. To pass in more than one relationship at a time, use `relationships({})`: 85 | ```javascript 86 | //declare both employees and computers nested $resource_s 87 | var Department = $resource_('departments/:id/', {'id':'@department_id'}) 88 | .relationships({ 89 | 'employees': $resource_('people/:id/', {'id':'@id', 'department':'^department_id'}), 90 | 'computers': $resource_('computers/:id/', {'id':'@comp_id', 'department':'^department_id'}) 91 | }); 92 | ``` 93 | Once related, resources can be accessed through the `$relationships` property on the `$resource_` or may automatically be loaded using the `getFull(params, resources)` method. 94 | ```javascript 95 | //... skipping the promises part for these examples 96 | var IT = Department.get({'name':'IT'}); 97 | var computers = IT.$relationships['computers'].query(); 98 | console.log(computers); //outputs the computers belonging to this department 99 | //OR 100 | var IT = Department.getFull({'name':'IT'}, ['computers']); 101 | console.log(IT._computers); //outputs the computers belonging to this department 102 | ``` 103 | 104 | #### Adding Methods 105 | There are 2 different kinds of methods you can attach to a `$resource_`: 106 | * method - Methods only attach to an instantiated `$resource_` (so the `$resource_` once you get it back from the database or create using new). 107 | * static - Statics (short for static methods) only attach to the `$resource_` and not its instances (so Department, but not IT). 108 | 109 | There's multiple ways to add methods: 110 | ```javascript 111 | //using func(name, method) syntax: 112 | var Department = $resource_('departments/:id/', {'id':'@department_id'}) 113 | // hires a person to work at a department. "this" is the specific instance of a department 114 | .method('hire', function(person){ 115 | this._employees.push(person); 116 | return this.$save(); 117 | }) 118 | // gets all departments whose income is above 9001 (are profitable ;) ) 119 | .static('getProfitable', function(){ 120 | return this.query({'income':9001}); 121 | }); 122 | 123 | //using func(obj) syntax: 124 | var methods = 125 | { 126 | 'hire': function(person) { 127 | this._employees.push(person); 128 | return this.$save(); 129 | }, 130 | 'addProfit': function(amount) { 131 | this.profits+=amount; 132 | return this.$save(); 133 | } 134 | } 135 | 136 | var statics = 137 | { 138 | 'getProfitable': function(person) { 139 | return this.query({'income':9001}); 140 | }, 141 | } 142 | 143 | var Department = $resource_('departments/:id/', {'id':'@department_id'}).method(methods).statics(statics); 144 | ``` 145 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-resource-x", 3 | "version": "0.4.0", 4 | "homepage": "https://github.com/luciano7/angular-resource-x", 5 | "authors": [ 6 | "Luciano Pereira ", 7 | "Dayan Moreno", 8 | "Mike Grischenko" 9 | ], 10 | "description": "Angular Resource Extension", 11 | "main": "src/angular-resource-x.js", 12 | "keywords": [ 13 | "angular", 14 | "angularjs", 15 | "ng-resource", 16 | "resource" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ], 26 | "devDependencies": { 27 | "angular": "1.5.x", 28 | "angular-mocks": "1.5.x", 29 | "angular-resource": "1.5.x" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-resource-x", 3 | "version": "0.4.1", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "grunt": "^0.4.5", 7 | "time-grunt": "^1.0.0", 8 | "grunt-karma": "^0.12.1", 9 | "grunt-contrib-uglify": "^0.7.0", 10 | "grunt-ng-annotate": "^0.9.2", 11 | "jasmine-core": "^2.2.0", 12 | "jshint-stylish": "^1.0.0", 13 | "karma": "^0.13.19", 14 | "karma-coverage": "^0.2.7", 15 | "karma-jasmine": "^0.3.5", 16 | "karma-jasmine-html-reporter": "^0.1.8", 17 | "karma-ng-html2js-preprocessor": "^0.1.2", 18 | "karma-phantomjs-launcher": "^1.0.0", 19 | "load-grunt-tasks": "^3.1.0", 20 | "phantomjs-prebuilt": "^2.1.1" 21 | }, 22 | "engines": { 23 | "node": ">=0.10.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/angular-resource-x.js: -------------------------------------------------------------------------------- 1 | (function(angular) { 2 | 3 | 'use strict'; 4 | 5 | angular 6 | .module('ngResourceX', ['ngResource']) 7 | .provider('$resource_', $resource_); 8 | 9 | var forEach = angular.forEach, 10 | extend = angular.extend, 11 | copy = angular.copy, 12 | isObject = angular.isObject, 13 | isString = angular.isString, 14 | isArray = angular.isArray; 15 | 16 | /** 17 | * $resource utilities extension. 18 | * Adds extensibility and related resources to the Angular $resource module. 19 | * @class 20 | */ 21 | function $resource_() { 22 | 23 | var provider = this; 24 | provider.$get = $get; 25 | 26 | //defaults holds all the configs 27 | provider.defaults = { 28 | baseUrl: null, 29 | relationPrefix: '_', 30 | statics: { 31 | relate: relate, 32 | relationships: relationships 33 | }, 34 | params: {}, 35 | actions: { 36 | update: { 37 | method: 'PATCH' 38 | }, 39 | get: { 40 | method: 'GET' 41 | }, 42 | query: { 43 | isArray: true, 44 | method: 'GET' 45 | }, 46 | save: { 47 | method: 'POST' 48 | }, 49 | options: { 50 | method: 'OPTIONS' 51 | } 52 | } 53 | }; 54 | 55 | $get.$inject = ['$resource', '$q']; 56 | /** 57 | * Provider $get function that returns the resource factory function. 58 | * @param $resource 59 | * @param $q 60 | * @returns {resourceFactory} 61 | */ 62 | function $get($resource, $q) { 63 | 64 | /** 65 | * Wrapper for $resource. Creates a $resource and adds default methods/statics 66 | * @param url {string} URL of this $resource 67 | * @param params {Object} hash of parameters, same as $resource but modified to support ^ to map to parent properties. 68 | * @param actions {Object} hash of actions, see $resource documentation 69 | * @param options {Object} hash of options, see $resource documentation 70 | * @public 71 | * @returns {Object} $resource 72 | */ 73 | function resourceFactory(url, params, actions, options) { 74 | 75 | var resource, 76 | resParams = extend({}, copy(provider.defaults.params), params), 77 | resActions = extend({}, copy(provider.defaults.actions), actions); 78 | 79 | url = (provider.defaults.baseUrl || '') + url; 80 | 81 | resource = $resource(url, resParams, resActions, options); 82 | 83 | //Exposed methods 84 | resource.method = method; 85 | resource.static = staticMethod; 86 | 87 | init(); 88 | 89 | return resource; 90 | 91 | //////////////////////////////////////////////////////////// 92 | 93 | function init() { 94 | 95 | //resolve our ^ parameters 96 | resolveParentParams(resParams, resource); 97 | addResponseTransformers(resActions, resource); 98 | 99 | //add default methods/statics 100 | forEach(provider.defaults.methods, function(func, key) { 101 | resource.method(key, func); 102 | }); 103 | forEach(provider.defaults.statics, function(func, key) { 104 | resource.static(key, func); 105 | }); 106 | 107 | resource.static('getFull', getFull); 108 | resource.$relationships = {}; 109 | } 110 | 111 | /** 112 | * Does a GET but also resolves sub-resources 113 | * @param getParams {Object} standard $http GET params 114 | * @param relatedResourcesNames {string[]} names of the related resources to query for 115 | * @public 116 | * @instance 117 | */ 118 | function getFull(getParams, relatedResourcesNames) { 119 | 120 | var deferred = $q.defer(), 121 | prefix = provider.defaults.relationPrefix, 122 | placeholder = extend(new this(), { 123 | '$promise': deferred.promise, 124 | '$ready': false 125 | }); 126 | 127 | if (!relatedResourcesNames) { 128 | relatedResourcesNames = Object.keys(resource.$relationships); 129 | } 130 | 131 | deferred.promise = this.get(getParams).$promise.then(gotParentResults); 132 | 133 | function gotParentResults(resource) { 134 | 135 | //if this $resource has related resources 136 | if (resource.$relationships) { 137 | 138 | //go through the related resources we were asked to grab 139 | $q.all(forEach(relatedResourcesNames, function(name) { 140 | 141 | //run their queries and assign the prefixed results to the parent 142 | return resource.$relationships[name].query().$promise 143 | .then(function gotRelatedResults(relatedResults) { 144 | resource[prefix + name] = relatedResults; 145 | return resource; 146 | }); 147 | })); 148 | } 149 | 150 | } 151 | 152 | return placeholder; 153 | } 154 | } 155 | 156 | return resourceFactory; 157 | } 158 | 159 | // DEFAULT EXTENSIONS 160 | //////////////////////////////////////////////////////////// 161 | 162 | /** 163 | * Adds a sub-resource to this $resource 164 | * @param name {string} 165 | * @param resource {Object} 166 | * @public 167 | * @static 168 | * @returns {$resource_.relate} 169 | */ 170 | function relate(name, resource) { 171 | this.$relationships[name] = resource; 172 | return this; 173 | } 174 | 175 | /** 176 | * Sets/Gets related resources 177 | * @param [relationships] {Object} hash of related resource factories 178 | * @public 179 | * @static 180 | * @return {*} 181 | */ 182 | function relationships(relationships) { 183 | this.$relationships = relationships; 184 | return this; 185 | } 186 | 187 | // EXTENDER METHODS 188 | //////////////////////////////////////////////////////////// 189 | 190 | /** 191 | * Adds a Method to this $resource. Methods operate on instantiated objects from your $resource. 192 | * @param arg1 {string|Object|Object[]} A key-value pair containing method name and definition OR an array of 193 | * key value pairs OR the method name (definition on the next param) 194 | * @param [func] {Function} if the first parameter is a string of the method name, this parameter will be used as 195 | * the definition 196 | * @public 197 | * @returns {_resource.method} 198 | */ 199 | function method(arg1, func) { 200 | extender(this.prototype, arg1, func); 201 | return this; 202 | } 203 | 204 | /** 205 | * Adds a "static" method to this $resource. Statics operate on the non-instantiated version of your $resource 206 | * @param arg1 {string|Object|Object[]} A key-value pair containing method name and definition OR an array of 207 | * key value pairs OR the method name (definition on the next param) 208 | * @param [func] {Function} if the first parameter is a string of the method name, this parameter will be used as 209 | * the definition 210 | * @public 211 | * @returns {*} 212 | */ 213 | function staticMethod(arg1, func) { 214 | extender(this, arg1, func); 215 | return this; 216 | } 217 | 218 | 219 | // PRIVATES 220 | //////////////////////////////////////////////////////////// 221 | 222 | /** 223 | * Helper function that extends a provided object with method arrays, hash or string/method value. 224 | * @param src Object to be extended 225 | * @param arg1 {string|Object|Object[]} A key-value pair containing method name and definition OR an array of 226 | * key value pairs OR the method name (definition on the next param) 227 | * @param [func] {Function} if @arg1 is a string of the method name, this parameter will be used as 228 | * the definition 229 | * @private 230 | */ 231 | function extender(src, arg1, func) { 232 | if (isString(arg1)) { 233 | src[arg1] = func; 234 | } 235 | else if (isArray(arg1)) { 236 | forEach(arg1, function(method) { 237 | extend(src, method); 238 | }); 239 | } 240 | else if (isObject(arg1)) { 241 | extend(src, arg1); 242 | } 243 | } 244 | 245 | /** 246 | * Special response transformer that first runs the user defined transformer and then our own to attach 247 | * our related resources. 248 | * @param actions {Object} actions hash to have response transformers added. 249 | * @param resource {Object} 250 | * @private 251 | * @returns {Object} actions hash with added response transformers. 252 | */ 253 | function addResponseTransformers(actions, resource) { 254 | forEach(actions, function(action) { 255 | action.transformResponse = responseTransforms(action.transformResponse, resource); 256 | }); 257 | 258 | return actions; 259 | } 260 | 261 | /** 262 | * Response transformer that first calls the user defined transform and then applies our own (which adds the 263 | * $relationships) 264 | * @param otherTransform {Function} 265 | * @param parentResource {Object} 266 | * @private 267 | * @returns {Function} 268 | */ 269 | function responseTransforms(otherTransform, parentResource) { 270 | 271 | return function transformResponse(response) { 272 | if (otherTransform) { 273 | response = otherTransform(response); 274 | } 275 | else { 276 | response = (response ? angular.fromJson(response) : {}); 277 | } 278 | 279 | if (isArray(response)) { 280 | forEach(response, function(entry) { 281 | attachRelations(entry, parentResource.$relationships); 282 | }); 283 | } 284 | else { 285 | attachRelations(response, parentResource.$relationships); 286 | } 287 | 288 | return response; 289 | }; 290 | 291 | } 292 | 293 | /** 294 | * Attaches related $resource_s to this instantiated $resource_. 295 | * @param entry {*} 296 | * @param relationships {Object} hash of related $resource_s to be added to this $resource_ 297 | * @private 298 | */ 299 | function attachRelations(entry, relationships) { 300 | if (!isObject(entry)) { 301 | return; 302 | } 303 | 304 | var prefix = provider.defaults.relationPrefix; 305 | 306 | if (!entry.$relationships) { 307 | entry.$relationships = {}; 308 | } 309 | 310 | forEach(relationships, function(relatedResource, name) { 311 | var params = {}; 312 | forEach(relatedResource.$$parentMap, function(map, key) { 313 | params[key] = lookupDottedPath(entry, map); 314 | }); 315 | entry.$relationships[name] = relatedResource.bind(params); 316 | entry[prefix + name] = entry.$relationships[name]; 317 | }); 318 | 319 | } 320 | 321 | /** 322 | * Finds parameters that need to map to a parent $resource and attaches a function that will fetch the parent's 323 | * value for that param. 324 | * @param params {Object} 325 | * @param resource {Object} 326 | * @private 327 | * @returns {Object} 328 | */ 329 | function resolveParentParams(params, resource) { 330 | 331 | var ret = {}; 332 | resource.$$parentMap = {}; 333 | 334 | forEach(params, function(param, key) { 335 | if (param && param.charAt && param.charAt(0) == '^') { 336 | resource.$$parentMap[key] = param.substr(1); 337 | } 338 | ret[key] = param; 339 | }); 340 | 341 | params = ret; 342 | return params; 343 | } 344 | 345 | 346 | //FROM ANGULAR 1.3 347 | //////////////////////////////////////////////////////////// 348 | var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/; 349 | 350 | function isValidDottedPath(path) { 351 | 352 | return (path != null && path !== '' && path !== 'hasOwnProperty' && 353 | MEMBER_NAME_REGEX.test('.' + path)); 354 | } 355 | 356 | function lookupDottedPath(obj, path) { 357 | if (!isValidDottedPath(path)) { 358 | throw new Error('Dotted member path is invalid.', path); 359 | } 360 | 361 | var keys = path.split('.'); 362 | for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) { 363 | var key = keys[i]; 364 | obj = (obj !== null) ? obj[key] : undefined; 365 | } 366 | return obj; 367 | } 368 | 369 | } 370 | 371 | 372 | })(angular); 373 | -------------------------------------------------------------------------------- /tests/configs/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | 3 | config.set({ 4 | frameworks: ['jasmine'], 5 | basePath: '../../', 6 | files: [ 7 | 'bower_components/angular/angular.js', 8 | 'bower_components/angular-mocks/angular-mocks.js', 9 | 'bower_components/angular-resource/angular-resource.js', 10 | 'src/**/*.js', 11 | 'tests/**/*.js' 12 | ], 13 | exclude: [], 14 | 'colors': true, 15 | port: 8080, 16 | reporters: ['coverage','progress'], 17 | preprocessors: { 18 | 'src/*.js': ['coverage'] 19 | }, 20 | coverageReporter: { 21 | type: "html", 22 | dir: 'tests/coverage/' 23 | }, 24 | browsers: ['PhantomJS'] 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /tests/coverage/PhantomJS 1.9.8 (Mac OS X)/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | } 4 | body { 5 | font-family: Helvetica Neue, Helvetica,Arial; 6 | font-size: 10pt; 7 | } 8 | div.header, div.footer { 9 | background: #eee; 10 | padding: 1em; 11 | } 12 | div.header { 13 | z-index: 100; 14 | position: fixed; 15 | top: 0; 16 | border-bottom: 1px solid #666; 17 | width: 100%; 18 | } 19 | div.footer { 20 | border-top: 1px solid #666; 21 | } 22 | div.body { 23 | margin-top: 10em; 24 | } 25 | div.meta { 26 | font-size: 90%; 27 | text-align: center; 28 | } 29 | h1, h2, h3 { 30 | font-weight: normal; 31 | } 32 | h1 { 33 | font-size: 12pt; 34 | } 35 | h2 { 36 | font-size: 10pt; 37 | } 38 | pre { 39 | font-family: Consolas, Menlo, Monaco, monospace; 40 | margin: 0; 41 | padding: 0; 42 | line-height: 14px; 43 | font-size: 14px; 44 | -moz-tab-size: 2; 45 | -o-tab-size: 2; 46 | tab-size: 2; 47 | } 48 | 49 | div.path { font-size: 110%; } 50 | div.path a:link, div.path a:visited { color: #000; } 51 | table.coverage { border-collapse: collapse; margin:0; padding: 0 } 52 | 53 | table.coverage td { 54 | margin: 0; 55 | padding: 0; 56 | color: #111; 57 | vertical-align: top; 58 | } 59 | table.coverage td.line-count { 60 | width: 50px; 61 | text-align: right; 62 | padding-right: 5px; 63 | } 64 | table.coverage td.line-coverage { 65 | color: #777 !important; 66 | text-align: right; 67 | border-left: 1px solid #666; 68 | border-right: 1px solid #666; 69 | } 70 | 71 | table.coverage td.text { 72 | } 73 | 74 | table.coverage td span.cline-any { 75 | display: inline-block; 76 | padding: 0 5px; 77 | width: 40px; 78 | } 79 | table.coverage td span.cline-neutral { 80 | background: #eee; 81 | } 82 | table.coverage td span.cline-yes { 83 | background: #b5d592; 84 | color: #999; 85 | } 86 | table.coverage td span.cline-no { 87 | background: #fc8c84; 88 | } 89 | 90 | .cstat-yes { color: #111; } 91 | .cstat-no { background: #fc8c84; color: #111; } 92 | .fstat-no { background: #ffc520; color: #111 !important; } 93 | .cbranch-no { background: yellow !important; color: #111; } 94 | 95 | .cstat-skip { background: #ddd; color: #111; } 96 | .fstat-skip { background: #ddd; color: #111 !important; } 97 | .cbranch-skip { background: #ddd !important; color: #111; } 98 | 99 | .missing-if-branch { 100 | display: inline-block; 101 | margin-right: 10px; 102 | position: relative; 103 | padding: 0 4px; 104 | background: black; 105 | color: yellow; 106 | } 107 | 108 | .skip-if-branch { 109 | display: none; 110 | margin-right: 10px; 111 | position: relative; 112 | padding: 0 4px; 113 | background: #ccc; 114 | color: white; 115 | } 116 | 117 | .missing-if-branch .typ, .skip-if-branch .typ { 118 | color: inherit !important; 119 | } 120 | 121 | .entity, .metric { font-weight: bold; } 122 | .metric { display: inline-block; border: 1px solid #333; padding: 0.3em; background: white; } 123 | .metric small { font-size: 80%; font-weight: normal; color: #666; } 124 | 125 | div.coverage-summary table { border-collapse: collapse; margin: 3em; font-size: 110%; } 126 | div.coverage-summary td, div.coverage-summary table th { margin: 0; padding: 0.25em 1em; border-top: 1px solid #666; border-bottom: 1px solid #666; } 127 | div.coverage-summary th { text-align: left; border: 1px solid #666; background: #eee; font-weight: normal; } 128 | div.coverage-summary th.file { border-right: none !important; } 129 | div.coverage-summary th.pic { border-left: none !important; text-align: right; } 130 | div.coverage-summary th.pct { border-right: none !important; } 131 | div.coverage-summary th.abs { border-left: none !important; text-align: right; } 132 | div.coverage-summary td.pct { text-align: right; border-left: 1px solid #666; } 133 | div.coverage-summary td.abs { text-align: right; font-size: 90%; color: #444; border-right: 1px solid #666; } 134 | div.coverage-summary td.file { text-align: right; border-left: 1px solid #666; white-space: nowrap; } 135 | div.coverage-summary td.pic { min-width: 120px !important; } 136 | div.coverage-summary a:link { text-decoration: none; color: #000; } 137 | div.coverage-summary a:visited { text-decoration: none; color: #333; } 138 | div.coverage-summary a:hover { text-decoration: underline; } 139 | div.coverage-summary tfoot td { border-top: 1px solid #666; } 140 | 141 | div.coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | div.coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | div.coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | 155 | .high { background: #b5d592 !important; } 156 | .medium { background: #ffe87c !important; } 157 | .low { background: #fc8c84 !important; } 158 | 159 | span.cover-fill, span.cover-empty { 160 | display:inline-block; 161 | border:1px solid #444; 162 | background: white; 163 | height: 12px; 164 | } 165 | span.cover-fill { 166 | background: #ccc; 167 | border-right: 1px solid #444; 168 | } 169 | span.cover-empty { 170 | background: white; 171 | border-left: none; 172 | } 173 | span.cover-full { 174 | border-right: none !important; 175 | } 176 | pre.prettyprint { 177 | border: none !important; 178 | padding: 0 !important; 179 | margin: 0 !important; 180 | } 181 | .com { color: #999 !important; } 182 | .ignore-none { color: #999; font-weight: normal; } -------------------------------------------------------------------------------- /tests/coverage/PhantomJS 1.9.8 (Mac OS X)/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 9 | 196 | 197 | 198 |
199 |

Code coverage report for All files

200 |

201 | 202 | Statements: 100% (99 / 99)      203 | 204 | 205 | Branches: 85.29% (29 / 34)      206 | 207 | 208 | Functions: 100% (30 / 30)      209 | 210 | 211 | Lines: 100% (99 / 99)      212 | 213 | Ignored: none      214 |

215 |
216 |
217 |
218 |
219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 |
FileStatementsBranchesFunctionsLines
src/100%(99 / 99)85.29%(29 / 34)100%(30 / 30)100%(99 / 99)
249 |
250 |
251 | 254 | 255 | 256 | 257 | 258 | 349 | 350 | 351 | -------------------------------------------------------------------------------- /tests/coverage/PhantomJS 1.9.8 (Mac OS X)/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /tests/coverage/PhantomJS 1.9.8 (Mac OS X)/prettify.js: -------------------------------------------------------------------------------- 1 | window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); 2 | -------------------------------------------------------------------------------- /tests/coverage/PhantomJS 1.9.8 (Mac OS X)/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellepereira/angular-resource-x/85d06fadb05a7189028428ff2b2082fa4a56ea57/tests/coverage/PhantomJS 1.9.8 (Mac OS X)/sort-arrow-sprite.png -------------------------------------------------------------------------------- /tests/coverage/PhantomJS 1.9.8 (Mac OS X)/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary table'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | el = getNthColumn(i).querySelector('.sorter'); 136 | if (el.addEventListener) { 137 | el.addEventListener('click', ithSorter(i)); 138 | } else { 139 | el.attachEvent('onclick', ithSorter(i)); 140 | } 141 | } 142 | } 143 | } 144 | // adds sorting functionality to the UI 145 | return function () { 146 | if (!getTable()) { 147 | return; 148 | } 149 | cols = loadColumns(); 150 | loadData(cols); 151 | addSortIndicators(); 152 | enableUI(); 153 | }; 154 | })(); 155 | 156 | window.addEventListener('load', addSorting); 157 | -------------------------------------------------------------------------------- /tests/coverage/PhantomJS 1.9.8 (Mac OS X)/src/angular-resource-x.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/angular-resource-x.js 5 | 6 | 7 | 8 | 9 | 196 | 197 | 198 |
199 |

Code coverage report for src/angular-resource-x.js

200 |

201 | 202 | Statements: 100% (99 / 99)      203 | 204 | 205 | Branches: 85.29% (29 / 34)      206 | 207 | 208 | Functions: 100% (30 / 30)      209 | 210 | 211 | Lines: 100% (99 / 99)      212 | 213 | Ignored: none      214 |

215 |
All files » src/ » angular-resource-x.js
216 |
217 |
218 |

 219 | 
1351 | 
1 220 | 2 221 | 3 222 | 4 223 | 5 224 | 6 225 | 7 226 | 8 227 | 9 228 | 10 229 | 11 230 | 12 231 | 13 232 | 14 233 | 15 234 | 16 235 | 17 236 | 18 237 | 19 238 | 20 239 | 21 240 | 22 241 | 23 242 | 24 243 | 25 244 | 26 245 | 27 246 | 28 247 | 29 248 | 30 249 | 31 250 | 32 251 | 33 252 | 34 253 | 35 254 | 36 255 | 37 256 | 38 257 | 39 258 | 40 259 | 41 260 | 42 261 | 43 262 | 44 263 | 45 264 | 46 265 | 47 266 | 48 267 | 49 268 | 50 269 | 51 270 | 52 271 | 53 272 | 54 273 | 55 274 | 56 275 | 57 276 | 58 277 | 59 278 | 60 279 | 61 280 | 62 281 | 63 282 | 64 283 | 65 284 | 66 285 | 67 286 | 68 287 | 69 288 | 70 289 | 71 290 | 72 291 | 73 292 | 74 293 | 75 294 | 76 295 | 77 296 | 78 297 | 79 298 | 80 299 | 81 300 | 82 301 | 83 302 | 84 303 | 85 304 | 86 305 | 87 306 | 88 307 | 89 308 | 90 309 | 91 310 | 92 311 | 93 312 | 94 313 | 95 314 | 96 315 | 97 316 | 98 317 | 99 318 | 100 319 | 101 320 | 102 321 | 103 322 | 104 323 | 105 324 | 106 325 | 107 326 | 108 327 | 109 328 | 110 329 | 111 330 | 112 331 | 113 332 | 114 333 | 115 334 | 116 335 | 117 336 | 118 337 | 119 338 | 120 339 | 121 340 | 122 341 | 123 342 | 124 343 | 125 344 | 126 345 | 127 346 | 128 347 | 129 348 | 130 349 | 131 350 | 132 351 | 133 352 | 134 353 | 135 354 | 136 355 | 137 356 | 138 357 | 139 358 | 140 359 | 141 360 | 142 361 | 143 362 | 144 363 | 145 364 | 146 365 | 147 366 | 148 367 | 149 368 | 150 369 | 151 370 | 152 371 | 153 372 | 154 373 | 155 374 | 156 375 | 157 376 | 158 377 | 159 378 | 160 379 | 161 380 | 162 381 | 163 382 | 164 383 | 165 384 | 166 385 | 167 386 | 168 387 | 169 388 | 170 389 | 171 390 | 172 391 | 173 392 | 174 393 | 175 394 | 176 395 | 177 396 | 178 397 | 179 398 | 180 399 | 181 400 | 182 401 | 183 402 | 184 403 | 185 404 | 186 405 | 187 406 | 188 407 | 189 408 | 190 409 | 191 410 | 192 411 | 193 412 | 194 413 | 195 414 | 196 415 | 197 416 | 198 417 | 199 418 | 200 419 | 201 420 | 202 421 | 203 422 | 204 423 | 205 424 | 206 425 | 207 426 | 208 427 | 209 428 | 210 429 | 211 430 | 212 431 | 213 432 | 214 433 | 215 434 | 216 435 | 217 436 | 218 437 | 219 438 | 220 439 | 221 440 | 222 441 | 223 442 | 224 443 | 225 444 | 226 445 | 227 446 | 228 447 | 229 448 | 230 449 | 231 450 | 232 451 | 233 452 | 234 453 | 235 454 | 236 455 | 237 456 | 238 457 | 239 458 | 240 459 | 241 460 | 242 461 | 243 462 | 244 463 | 245 464 | 246 465 | 247 466 | 248 467 | 249 468 | 250 469 | 251 470 | 252 471 | 253 472 | 254 473 | 255 474 | 256 475 | 257 476 | 258 477 | 259 478 | 260 479 | 261 480 | 262 481 | 263 482 | 264 483 | 265 484 | 266 485 | 267 486 | 268 487 | 269 488 | 270 489 | 271 490 | 272 491 | 273 492 | 274 493 | 275 494 | 276 495 | 277 496 | 278 497 | 279 498 | 280 499 | 281 500 | 282 501 | 283 502 | 284 503 | 285 504 | 286 505 | 287 506 | 288 507 | 289 508 | 290 509 | 291 510 | 292 511 | 293 512 | 294 513 | 295 514 | 296 515 | 297 516 | 298 517 | 299 518 | 300 519 | 301 520 | 302 521 | 303 522 | 304 523 | 305 524 | 306 525 | 307 526 | 308 527 | 309 528 | 310 529 | 311 530 | 312 531 | 313 532 | 314 533 | 315 534 | 316 535 | 317 536 | 318 537 | 319 538 | 320 539 | 321 540 | 322 541 | 323 542 | 324 543 | 325 544 | 326 545 | 327 546 | 328 547 | 329 548 | 330 549 | 331 550 | 332 551 | 333 552 | 334 553 | 335 554 | 336 555 | 337 556 | 338 557 | 339 558 | 340 559 | 341 560 | 342 561 | 343 562 | 344 563 | 345 564 | 346 565 | 347 566 | 348 567 | 349 568 | 350 569 | 351 570 | 352 571 | 353 572 | 354 573 | 355 574 | 356 575 | 357 576 | 358 577 | 359 578 | 360 579 | 361 580 | 362 581 | 363 582 | 364 583 | 365 584 | 366 585 | 367 586 | 368 587 | 369 588 | 370 589 | 371 590 | 372 591 | 373 592 | 374 593 | 375 594 | 376 595 | 377 596 | 3781 597 |   598 |   599 |   600 | 1 601 |   602 |   603 |   604 | 1 605 |   606 |   607 |   608 |   609 |   610 |   611 |   612 |   613 |   614 |   615 |   616 | 1 617 |   618 | 19 619 | 19 620 |   621 |   622 | 19 623 |   624 |   625 |   626 |   627 |   628 |   629 |   630 |   631 |   632 |   633 |   634 |   635 |   636 |   637 |   638 |   639 |   640 |   641 |   642 |   643 |   644 |   645 |   646 |   647 |   648 |   649 |   650 |   651 |   652 |   653 |   654 | 19 655 |   656 |   657 |   658 |   659 |   660 |   661 | 1 662 |   663 |   664 |   665 |   666 |   667 |   668 |   669 |   670 |   671 |   672 | 1 673 |   674 | 66 675 |   676 |   677 |   678 | 66 679 |   680 | 66 681 |   682 |   683 | 66 684 | 66 685 |   686 | 66 687 |   688 | 66 689 |   690 |   691 |   692 | 1 693 |   694 |   695 | 66 696 | 66 697 |   698 |   699 | 66 700 | 66 701 |   702 | 66 703 | 132 704 |   705 |   706 | 66 707 | 66 708 |   709 |   710 |   711 |   712 |   713 |   714 |   715 |   716 |   717 | 1 718 |   719 | 1 720 |   721 |   722 |   723 |   724 |   725 |   726 | 1 727 | 1 728 |   729 |   730 | 1 731 |   732 | 1 733 |   734 |   735 | 1 736 |   737 |   738 | 1 739 |   740 |   741 | 2 742 |   743 | 2 744 | 2 745 |   746 |   747 |   748 |   749 |   750 |   751 | 1 752 |   753 |   754 |   755 | 19 756 |   757 |   758 |   759 |   760 |   761 |   762 |   763 |   764 |   765 |   766 |   767 |   768 |   769 | 1 770 | 2 771 | 2 772 |   773 |   774 |   775 |   776 |   777 |   778 |   779 |   780 |   781 | 1 782 | 19 783 | 19 784 |   785 |   786 |   787 |   788 |   789 |   790 |   791 |   792 |   793 |   794 |   795 |   796 |   797 |   798 | 1 799 | 69 800 | 69 801 |   802 |   803 |   804 |   805 |   806 |   807 |   808 |   809 |   810 |   811 |   812 | 1 813 | 199 814 | 199 815 |   816 |   817 |   818 |   819 |   820 |   821 |   822 |   823 |   824 |   825 |   826 |   827 |   828 |   829 |   830 | 1 831 | 268 832 | 266 833 |   834 | 2 835 | 1 836 | 1 837 |   838 |   839 | 1 840 | 1 841 |   842 |   843 |   844 |   845 |   846 |   847 |   848 |   849 |   850 |   851 |   852 | 1 853 | 66 854 | 330 855 |   856 |   857 | 66 858 |   859 |   860 |   861 |   862 |   863 |   864 |   865 |   866 |   867 |   868 | 1 869 |   870 | 330 871 | 31 872 | 21 873 |   874 |   875 | 10 876 |   877 |   878 | 31 879 | 16 880 |   881 | 52 882 |   883 |   884 |   885 | 15 886 |   887 |   888 | 31 889 |   890 |   891 |   892 |   893 |   894 |   895 |   896 |   897 |   898 |   899 | 1 900 |   901 | 67 902 | 21 903 |   904 |   905 | 67 906 | 46 907 | 46 908 | 46 909 |   910 |   911 |   912 |   913 |   914 |   915 |   916 |   917 |   918 |   919 |   920 |   921 | 1 922 | 66 923 | 168 924 | 39 925 |   926 |   927 |   928 | 66 929 |   930 |   931 |   932 |   933 |   934 |   935 |   936 |   937 |   938 |   939 | 1 940 | 39 941 | 4 942 |   943 |   944 |   945 |   946 |   947 |   948 | 19 949 |   950 | 1 951 |   952 | 4 953 |   954 |   955 |   956 | 1 957 | 4 958 | 1 959 |   960 |   961 | 3 962 | 3 963 | 3 964 | 3 965 |   966 | 3 967 |   968 |   969 |   970 |   971 |   972 |   973 |  
(function (angular) {
 974 |  
 975 |   'use strict';
 976 |  
 977 |   angular
 978 |     .module('ngResourceX', ['ngResource'])
 979 |     .provider('$resource_', $resource_);
 980 |  
 981 |   var forEach = angular.forEach,
 982 |     extend = angular.extend,
 983 |     copy = angular.copy,
 984 |     isObject = angular.isObject,
 985 |     isString = angular.isString,
 986 |     isArray = angular.isArray;
 987 |  
 988 |   /**
 989 |    * $resource utilities extension.
 990 |    * Adds extensibility and related resources to the Angular $resource module.
 991 |    * @class
 992 |    */
 993 |   function $resource_() {
 994 |  
 995 |     var provider = this;
 996 |     provider.$get = $get;
 997 |  
 998 |     //defaults holds all the configs
 999 |     provider.defaults = {
1000 |       'baseUrl': null,
1001 |       'relationPrefix': '_',
1002 |       'methods': {
1003 |         //'relationships': relationships
1004 |       },
1005 |       'statics': {
1006 |         'relate': relate,
1007 |         //'getFull': getFull,
1008 |         'relationships': relationships
1009 |       },
1010 |       'params': {},
1011 |       'actions': {
1012 |         'update': {
1013 |           'method': 'PATCH'
1014 |         },
1015 |         'get': {
1016 |           'method': 'GET'
1017 |         },
1018 |         'query': {
1019 |           'isArray': true,
1020 |           'method': 'GET'
1021 |         },
1022 |         'save': {
1023 |           'method': 'POST'
1024 |         },
1025 |         'options': {
1026 |           'method': 'OPTIONS'
1027 |         }
1028 |       }
1029 |     };
1030 |  
1031 |     $get.$inject = ['$resource', '$q'];
1032 |     /**
1033 |      * Provider $get function that returns the resource factory function.
1034 |      * @param $resource
1035 |      * @param $q
1036 |      * @returns {resourceFactory}
1037 |      */
1038 |     function $get($resource, $q) {
1039 |  
1040 |       /**
1041 |        * Wrapper for $resource. Creates a $resource and adds default methods/statics
1042 |        * @param url {string} URL of this $resource
1043 |        * @param params {Object} hash of parameters, same as $resource but modified to support ^ to map to parent properties.
1044 |        * @param actions {Object} hash of actions, see $resource documentation
1045 |        * @param options {Object} hash of options, see $resource documentation
1046 |        * @public
1047 |        * @returns {Object} $resource
1048 |        */
1049 |       function resourceFactory(url, params, actions, options) {
1050 |  
1051 |         var resource,
1052 |           resParams = extend({}, provider.defaults.params, params),
1053 |           resActions = extend({}, provider.defaults.actions, actions);
1054 |  
1055 |         url = (provider.defaults.baseUrl || '') + url;
1056 |  
1057 |         resource = $resource(url, resParams, resActions, options);
1058 |  
1059 |         //Exposed methods
1060 |         resource.method = method;
1061 |         resource.static = staticMethod;
1062 |  
1063 |         init();
1064 |  
1065 |         return resource;
1066 |  
1067 |         ////////////////////////////////////////////////////////////
1068 |  
1069 |         function init() {
1070 |  
1071 |           //resolve our ^ parameters
1072 |           resolveParentParams(resParams, resource);
1073 |           addResponseTransformers(resActions, resource);
1074 |  
1075 |           //add default methods/statics
1076 |           forEach(provider.defaults.methods, function (func, key) {
1077 |             resource.method(key, func);
1078 |           });
1079 |           forEach(provider.defaults.statics, function (func, key) {
1080 |             resource.static(key, func);
1081 |           });
1082 |  
1083 |           resource.static('getFull', getFull);
1084 |           resource.$relationships = {};
1085 |         }
1086 |  
1087 |         /**
1088 |          * Does a GET but also resolves sub-resources
1089 |          * @param getParams {Object} standard $http GET params
1090 |          * @param relatedResourcesNames {string[]} names of the related resources to query for
1091 |          * @public
1092 |          * @instance
1093 |          */
1094 |         function getFull(getParams, relatedResourcesNames) {
1095 |  
1096 |           var deferred = $q.defer(),
1097 |             prefix = provider.defaults.relationPrefix,
1098 |             placeholder = extend(new this(), {
1099 |               '$promise': deferred.promise,
1100 |               '$ready': false
1101 |             });
1102 |  
1103 |           Eif(!relatedResourcesNames){
1104 |             relatedResourcesNames = Object.keys(resource.$relationships);
1105 |           }
1106 |  
1107 |           deferred.promise = this.get(getParams).$promise.then(gotParentResults);
1108 |  
1109 |           function gotParentResults(resource) {
1110 |  
1111 |             //if this $resource has related resources
1112 |             Eif (resource.$relationships) {
1113 |  
1114 |               //go through the related resources we were asked to grab
1115 |               $q.all(forEach(relatedResourcesNames, function (name) {
1116 |  
1117 |                 //run their queries and assign the prefixed results to the parent
1118 |                 return resource.$relationships[name].query().$promise
1119 |                   .then(function gotRelatedResults(relatedResults) {
1120 |                     resource[prefix + name] = relatedResults;
1121 |                     return resource;
1122 |                   });
1123 |               }));
1124 |             }
1125 |  
1126 |           }
1127 |  
1128 |           return placeholder;
1129 |         }
1130 |       }
1131 |  
1132 |       return resourceFactory;
1133 |     }
1134 |  
1135 |     // DEFAULT EXTENSIONS
1136 |     ////////////////////////////////////////////////////////////
1137 |  
1138 |     /**
1139 |      * Adds a sub-resource to this $resource
1140 |      * @param name {string}
1141 |      * @param resource {Object}
1142 |      * @public
1143 |      * @static
1144 |      * @returns {$resource_.relate}
1145 |      */
1146 |     function relate(name, resource) {
1147 |       this.$relationships[name] = resource;
1148 |       return this;
1149 |     }
1150 |  
1151 |     /**
1152 |      * Sets/Gets related resources
1153 |      * @param [relationships] {Object} hash of related resource factories
1154 |      * @public
1155 |      * @static
1156 |      * @return {*}
1157 |      */
1158 |     function relationships(relationships){
1159 |       this.$relationships = relationships;
1160 |       return this;
1161 |     }
1162 |  
1163 |     // EXTENDER METHODS
1164 |     ////////////////////////////////////////////////////////////
1165 |  
1166 |     /**
1167 |      * Adds a Method to this $resource. Methods operate on instantiated objects from your $resource.
1168 |      * @param arg1 {string|Object|Object[]} A key-value pair containing method name and definition OR an array of
1169 |      * key value pairs OR the method name (definition on the next param)
1170 |      * @param [func] {Function} if the first parameter is a string of the method name, this parameter will be used as
1171 |      * the definition
1172 |      * @public
1173 |      * @returns {_resource.method}
1174 |      */
1175 |     function method(arg1, func) {
1176 |       extender(this.prototype, arg1, func);
1177 |       return this;
1178 |     }
1179 |  
1180 |     /**
1181 |      * Adds a "static" method to this $resource. Statics operate on the non-instantiated version of your $resource
1182 |      * @param arg1 {string|Object|Object[]} A key-value pair containing method name and definition OR an array of
1183 |      * key value pairs OR the method name (definition on the next param)
1184 |      * @param [func] {Function} if the first parameter is a string of the method name, this parameter will be used as
1185 |      * the definition
1186 |      * @public
1187 |      * @returns {*}
1188 |      */
1189 |     function staticMethod(arg1, func) {
1190 |       extender(this, arg1, func);
1191 |       return this;
1192 |     }
1193 |  
1194 |  
1195 |     // PRIVATES
1196 |     ////////////////////////////////////////////////////////////
1197 |  
1198 |     /**
1199 |      * Helper function that extends a provided object with method arrays, hash or string/method value.
1200 |      * @param src Object to be extended
1201 |      * @param arg1 {string|Object|Object[]} A key-value pair containing method name and definition OR an array of
1202 |      * key value pairs OR the method name (definition on the next param)
1203 |      * @param [func] {Function} if @arg1 is a string of the method name, this parameter will be used as
1204 |      * the definition
1205 |      * @private
1206 |      */
1207 |     function extender(src, arg1, func) {
1208 |       if (isString(arg1)) {
1209 |         src[arg1] = func;
1210 |       }
1211 |       else if (isArray(arg1)) {
1212 |         forEach(arg1, function (method) {
1213 |           extend(src, method);
1214 |         });
1215 |       }
1216 |       else Eif (isObject(arg1)) {
1217 |         extend(src, arg1);
1218 |       }
1219 |     }
1220 |  
1221 |     /**
1222 |      * Special response transformer that first runs the user defined transformer and then our own to attach
1223 |      * our related resources.
1224 |      * @param actions {Object} actions hash to have response transformers added.
1225 |      * @param resource {Object}
1226 |      * @private
1227 |      * @returns {Object} actions hash with added response transformers.
1228 |      */
1229 |     function addResponseTransformers(actions, resource) {
1230 |       forEach(actions, function (action) {
1231 |         action.transformResponse = responseTransforms(action.transformResponse, resource);
1232 |       });
1233 |  
1234 |       return actions;
1235 |     }
1236 |  
1237 |     /**
1238 |      * Response transformer that first calls the user defined transform and then applies our own (which adds the
1239 |      * $relationships)
1240 |      * @param otherTransform {Function}
1241 |      * @param resource {Object}
1242 |      * @private
1243 |      * @returns {Function}
1244 |      */
1245 |     function responseTransforms(otherTransform, parentResource) {
1246 |  
1247 |       return function transformResponse(response) {
1248 |         if (otherTransform) {
1249 |           response = otherTransform(response);
1250 |         }
1251 |         else {
1252 |           response = (response ? angular.fromJson(response) : {});
1253 |         }
1254 |  
1255 |         if (isArray(response)) {
1256 |           forEach(response, function (entry) {
1257 |  
1258 |             attachRelations(entry, parentResource.$relationships);
1259 |           })
1260 |         }
1261 |         else {
1262 |           attachRelations(response, parentResource.$relationships);
1263 |         }
1264 |  
1265 |         return response;
1266 |       };
1267 |  
1268 |     }
1269 |  
1270 |     /**
1271 |      * Attaches related $resource_s to this instantiated $resource_.
1272 |      * @param entry {*}
1273 |      * @param relationships {Object} hash of related $resource_s to be added to this $resource_
1274 |      * @private
1275 |      */
1276 |     function attachRelations(entry, relationships) {
1277 |  
1278 |       if (!entry.$relationships) {
1279 |         entry.$relationships = {};
1280 |       }
1281 |  
1282 |       forEach(relationships, function (relatedResource, name) {
1283 |         var myResource = copy(relatedResource);
1284 |         myResource.prototype.$parent = entry;
1285 |         entry.$relationships[name] = myResource;
1286 |       });
1287 |  
1288 |     }
1289 |  
1290 |     /**
1291 |      * Finds parameters that need to map to a parent $resource and attaches a function that will fetch the parent's
1292 |      * value for that param.
1293 |      * @param params {Object}
1294 |      * @param resource {Object}
1295 |      * @private
1296 |      * @returns {Object}
1297 |      */
1298 |     function resolveParentParams(params, resource) {
1299 |       forEach(params, function (param, key) {
1300 |         if (param.charAt && param.charAt(0) == '^') {
1301 |           params[key] = parentParamFunc(resource, param.substr(1));
1302 |         }
1303 |       });
1304 |  
1305 |       return params;
1306 |     }
1307 |  
1308 |     /**
1309 |      * Function is run every time our $resource is used and attempts to fetch the value for our $parent if one of our
1310 |      * parameters requires so.
1311 |      * @param obj
1312 |      * @param param
1313 |      * @private
1314 |      * @returns {Function}
1315 |      */
1316 |     function parentParamFunc(obj, param) {
1317 |       return function () {
1318 |         return lookupDottedPath(obj.prototype.$parent, param);
1319 |       };
1320 |     }
1321 |  
1322 |  
1323 |     //FROM ANGULAR 1.3
1324 |     ////////////////////////////////////////////////////////////
1325 |     var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;
1326 |  
1327 |     function isValidDottedPath(path) {
1328 |  
1329 |       return (path != null && path !== '' && path !== 'hasOwnProperty' &&
1330 |       MEMBER_NAME_REGEX.test('.' + path));
1331 |     }
1332 |  
1333 |     function lookupDottedPath(obj, path) {
1334 |       if (!isValidDottedPath(path)) {
1335 |         throw new Error('Dotted member path is invalid.', path);
1336 |       }
1337 |  
1338 |       var keys = path.split('.');
1339 |       for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) {
1340 |         var key = keys[i];
1341 |         obj = (obj !== null) ? obj[key] : undefined;
1342 |       }
1343 |       return obj;
1344 |     }
1345 |  
1346 |   }
1347 |  
1348 |  
1349 | })(angular);
1350 |  
1352 | 1353 |
1354 | 1357 | 1358 | 1359 | 1360 | 1361 | 1452 | 1453 | 1454 | -------------------------------------------------------------------------------- /tests/coverage/PhantomJS 1.9.8 (Mac OS X)/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/ 5 | 6 | 7 | 8 | 9 | 196 | 197 | 198 |
199 |

Code coverage report for src/

200 |

201 | 202 | Statements: 100% (99 / 99)      203 | 204 | 205 | Branches: 85.29% (29 / 34)      206 | 207 | 208 | Functions: 100% (30 / 30)      209 | 210 | 211 | Lines: 100% (99 / 99)      212 | 213 | Ignored: none      214 |

215 |
All files » src/
216 |
217 |
218 |
219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 |
FileStatementsBranchesFunctionsLines
angular-resource-x.js100%(99 / 99)85.29%(29 / 34)100%(30 / 30)100%(99 / 99)
249 |
250 |
251 | 254 | 255 | 256 | 257 | 258 | 349 | 350 | 351 | -------------------------------------------------------------------------------- /tests/coverage/PhantomJS 1.9.8 (Mac OS X)/src/resource_.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/resource_.js 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |

Code coverage report for src/resource_.js

17 |

18 | Statements: 100% (99 / 99)      19 | Branches: 85.29% (29 / 34)      20 | Functions: 100% (30 / 30)      21 | Lines: 100% (99 / 99)      22 | Ignored: none      23 |

24 |
All files » src/ » resource_.js
25 |
26 |
27 |

  28 | 
1160 | 
1 29 | 2 30 | 3 31 | 4 32 | 5 33 | 6 34 | 7 35 | 8 36 | 9 37 | 10 38 | 11 39 | 12 40 | 13 41 | 14 42 | 15 43 | 16 44 | 17 45 | 18 46 | 19 47 | 20 48 | 21 49 | 22 50 | 23 51 | 24 52 | 25 53 | 26 54 | 27 55 | 28 56 | 29 57 | 30 58 | 31 59 | 32 60 | 33 61 | 34 62 | 35 63 | 36 64 | 37 65 | 38 66 | 39 67 | 40 68 | 41 69 | 42 70 | 43 71 | 44 72 | 45 73 | 46 74 | 47 75 | 48 76 | 49 77 | 50 78 | 51 79 | 52 80 | 53 81 | 54 82 | 55 83 | 56 84 | 57 85 | 58 86 | 59 87 | 60 88 | 61 89 | 62 90 | 63 91 | 64 92 | 65 93 | 66 94 | 67 95 | 68 96 | 69 97 | 70 98 | 71 99 | 72 100 | 73 101 | 74 102 | 75 103 | 76 104 | 77 105 | 78 106 | 79 107 | 80 108 | 81 109 | 82 110 | 83 111 | 84 112 | 85 113 | 86 114 | 87 115 | 88 116 | 89 117 | 90 118 | 91 119 | 92 120 | 93 121 | 94 122 | 95 123 | 96 124 | 97 125 | 98 126 | 99 127 | 100 128 | 101 129 | 102 130 | 103 131 | 104 132 | 105 133 | 106 134 | 107 135 | 108 136 | 109 137 | 110 138 | 111 139 | 112 140 | 113 141 | 114 142 | 115 143 | 116 144 | 117 145 | 118 146 | 119 147 | 120 148 | 121 149 | 122 150 | 123 151 | 124 152 | 125 153 | 126 154 | 127 155 | 128 156 | 129 157 | 130 158 | 131 159 | 132 160 | 133 161 | 134 162 | 135 163 | 136 164 | 137 165 | 138 166 | 139 167 | 140 168 | 141 169 | 142 170 | 143 171 | 144 172 | 145 173 | 146 174 | 147 175 | 148 176 | 149 177 | 150 178 | 151 179 | 152 180 | 153 181 | 154 182 | 155 183 | 156 184 | 157 185 | 158 186 | 159 187 | 160 188 | 161 189 | 162 190 | 163 191 | 164 192 | 165 193 | 166 194 | 167 195 | 168 196 | 169 197 | 170 198 | 171 199 | 172 200 | 173 201 | 174 202 | 175 203 | 176 204 | 177 205 | 178 206 | 179 207 | 180 208 | 181 209 | 182 210 | 183 211 | 184 212 | 185 213 | 186 214 | 187 215 | 188 216 | 189 217 | 190 218 | 191 219 | 192 220 | 193 221 | 194 222 | 195 223 | 196 224 | 197 225 | 198 226 | 199 227 | 200 228 | 201 229 | 202 230 | 203 231 | 204 232 | 205 233 | 206 234 | 207 235 | 208 236 | 209 237 | 210 238 | 211 239 | 212 240 | 213 241 | 214 242 | 215 243 | 216 244 | 217 245 | 218 246 | 219 247 | 220 248 | 221 249 | 222 250 | 223 251 | 224 252 | 225 253 | 226 254 | 227 255 | 228 256 | 229 257 | 230 258 | 231 259 | 232 260 | 233 261 | 234 262 | 235 263 | 236 264 | 237 265 | 238 266 | 239 267 | 240 268 | 241 269 | 242 270 | 243 271 | 244 272 | 245 273 | 246 274 | 247 275 | 248 276 | 249 277 | 250 278 | 251 279 | 252 280 | 253 281 | 254 282 | 255 283 | 256 284 | 257 285 | 258 286 | 259 287 | 260 288 | 261 289 | 262 290 | 263 291 | 264 292 | 265 293 | 266 294 | 267 295 | 268 296 | 269 297 | 270 298 | 271 299 | 272 300 | 273 301 | 274 302 | 275 303 | 276 304 | 277 305 | 278 306 | 279 307 | 280 308 | 281 309 | 282 310 | 283 311 | 284 312 | 285 313 | 286 314 | 287 315 | 288 316 | 289 317 | 290 318 | 291 319 | 292 320 | 293 321 | 294 322 | 295 323 | 296 324 | 297 325 | 298 326 | 299 327 | 300 328 | 301 329 | 302 330 | 303 331 | 304 332 | 305 333 | 306 334 | 307 335 | 308 336 | 309 337 | 310 338 | 311 339 | 312 340 | 313 341 | 314 342 | 315 343 | 316 344 | 317 345 | 318 346 | 319 347 | 320 348 | 321 349 | 322 350 | 323 351 | 324 352 | 325 353 | 326 354 | 327 355 | 328 356 | 329 357 | 330 358 | 331 359 | 332 360 | 333 361 | 334 362 | 335 363 | 336 364 | 337 365 | 338 366 | 339 367 | 340 368 | 341 369 | 342 370 | 343 371 | 344 372 | 345 373 | 346 374 | 347 375 | 348 376 | 349 377 | 350 378 | 351 379 | 352 380 | 353 381 | 354 382 | 355 383 | 356 384 | 357 385 | 358 386 | 359 387 | 360 388 | 361 389 | 362 390 | 363 391 | 364 392 | 365 393 | 366 394 | 367 395 | 368 396 | 369 397 | 370 398 | 371 399 | 372 400 | 373 401 | 374 402 | 375 403 | 376 404 | 377 405 | 3781 406 |   407 |   408 |   409 | 1 410 |   411 |   412 |   413 | 1 414 |   415 |   416 |   417 |   418 |   419 |   420 |   421 |   422 |   423 |   424 |   425 | 1 426 |   427 | 19 428 | 19 429 |   430 |   431 | 19 432 |   433 |   434 |   435 |   436 |   437 |   438 |   439 |   440 |   441 |   442 |   443 |   444 |   445 |   446 |   447 |   448 |   449 |   450 |   451 |   452 |   453 |   454 |   455 |   456 |   457 |   458 |   459 |   460 |   461 |   462 |   463 | 19 464 |   465 |   466 |   467 |   468 |   469 |   470 | 1 471 |   472 |   473 |   474 |   475 |   476 |   477 |   478 |   479 |   480 |   481 | 1 482 |   483 | 66 484 |   485 |   486 |   487 | 66 488 |   489 | 66 490 |   491 |   492 | 66 493 | 66 494 |   495 | 66 496 |   497 | 66 498 |   499 |   500 |   501 | 1 502 |   503 |   504 | 66 505 | 66 506 |   507 |   508 | 66 509 | 66 510 |   511 | 66 512 | 132 513 |   514 |   515 | 66 516 | 66 517 |   518 |   519 |   520 |   521 |   522 |   523 |   524 |   525 |   526 | 1 527 |   528 | 1 529 |   530 |   531 |   532 |   533 |   534 |   535 | 1 536 | 1 537 |   538 |   539 | 1 540 |   541 | 1 542 |   543 |   544 | 1 545 |   546 |   547 | 1 548 |   549 |   550 | 2 551 |   552 | 2 553 | 2 554 |   555 |   556 |   557 |   558 |   559 |   560 | 1 561 |   562 |   563 |   564 | 19 565 |   566 |   567 |   568 |   569 |   570 |   571 |   572 |   573 |   574 |   575 |   576 |   577 |   578 | 1 579 | 2 580 | 2 581 |   582 |   583 |   584 |   585 |   586 |   587 |   588 |   589 |   590 | 1 591 | 19 592 | 19 593 |   594 |   595 |   596 |   597 |   598 |   599 |   600 |   601 |   602 |   603 |   604 |   605 |   606 |   607 | 1 608 | 69 609 | 69 610 |   611 |   612 |   613 |   614 |   615 |   616 |   617 |   618 |   619 |   620 |   621 | 1 622 | 199 623 | 199 624 |   625 |   626 |   627 |   628 |   629 |   630 |   631 |   632 |   633 |   634 |   635 |   636 |   637 |   638 |   639 | 1 640 | 268 641 | 266 642 |   643 | 2 644 | 1 645 | 1 646 |   647 |   648 | 1 649 | 1 650 |   651 |   652 |   653 |   654 |   655 |   656 |   657 |   658 |   659 |   660 |   661 | 1 662 | 66 663 | 330 664 |   665 |   666 | 66 667 |   668 |   669 |   670 |   671 |   672 |   673 |   674 |   675 |   676 |   677 | 1 678 |   679 | 330 680 | 31 681 | 21 682 |   683 |   684 | 10 685 |   686 |   687 | 31 688 | 16 689 |   690 | 52 691 |   692 |   693 |   694 | 15 695 |   696 |   697 | 31 698 |   699 |   700 |   701 |   702 |   703 |   704 |   705 |   706 |   707 |   708 | 1 709 |   710 | 67 711 | 21 712 |   713 |   714 | 67 715 | 46 716 | 46 717 | 46 718 |   719 |   720 |   721 |   722 |   723 |   724 |   725 |   726 |   727 |   728 |   729 |   730 | 1 731 | 66 732 | 168 733 | 39 734 |   735 |   736 |   737 | 66 738 |   739 |   740 |   741 |   742 |   743 |   744 |   745 |   746 |   747 |   748 | 1 749 | 39 750 | 4 751 |   752 |   753 |   754 |   755 |   756 |   757 | 19 758 |   759 | 1 760 |   761 | 4 762 |   763 |   764 |   765 | 1 766 | 4 767 | 1 768 |   769 |   770 | 3 771 | 3 772 | 3 773 | 3 774 |   775 | 3 776 |   777 |   778 |   779 |   780 |   781 |   782 |  
(function (angular) {
 783 |  
 784 |   'use strict';
 785 |  
 786 |   angular
 787 |     .module('ngResourceX', ['ngResource'])
 788 |     .provider('$resource_', $resource_);
 789 |  
 790 |   var forEach = angular.forEach,
 791 |     extend = angular.extend,
 792 |     copy = angular.copy,
 793 |     isObject = angular.isObject,
 794 |     isString = angular.isString,
 795 |     isArray = angular.isArray;
 796 |  
 797 |   /**
 798 |    * $resource utilities extension.
 799 |    * Adds extensibility and related resources to the Angular $resource module.
 800 |    * @class
 801 |    */
 802 |   function $resource_() {
 803 |  
 804 |     var provider = this;
 805 |     provider.$get = $get;
 806 |  
 807 |     //defaults holds all the configs
 808 |     provider.defaults = {
 809 |       'baseUrl': null,
 810 |       'relationPrefix': '_',
 811 |       'methods': {
 812 |         //'relationships': relationships
 813 |       },
 814 |       'statics': {
 815 |         'relate': relate,
 816 |         //'getFull': getFull,
 817 |         'relationships': relationships
 818 |       },
 819 |       'params': {},
 820 |       'actions': {
 821 |         'update': {
 822 |           'method': 'PATCH'
 823 |         },
 824 |         'get': {
 825 |           'method': 'GET'
 826 |         },
 827 |         'query': {
 828 |           'isArray': true,
 829 |           'method': 'GET'
 830 |         },
 831 |         'save': {
 832 |           'method': 'POST'
 833 |         },
 834 |         'options': {
 835 |           'method': 'OPTIONS'
 836 |         }
 837 |       }
 838 |     };
 839 |  
 840 |     $get.$inject = ['$resource', '$q'];
 841 |     /**
 842 |      * Provider $get function that returns the resource factory function.
 843 |      * @param $resource
 844 |      * @param $q
 845 |      * @returns {resourceFactory}
 846 |      */
 847 |     function $get($resource, $q) {
 848 |  
 849 |       /**
 850 |        * Wrapper for $resource. Creates a $resource and adds default methods/statics
 851 |        * @param url {string} URL of this $resource
 852 |        * @param params {Object} hash of parameters, same as $resource but modified to support ^ to map to parent properties.
 853 |        * @param actions {Object} hash of actions, see $resource documentation
 854 |        * @param options {Object} hash of options, see $resource documentation
 855 |        * @public
 856 |        * @returns {Object} $resource
 857 |        */
 858 |       function resourceFactory(url, params, actions, options) {
 859 |  
 860 |         var resource,
 861 |           resParams = extend({}, provider.defaults.params, params),
 862 |           resActions = extend({}, provider.defaults.actions, actions);
 863 |  
 864 |         url = (provider.defaults.baseUrl || '') + url;
 865 |  
 866 |         resource = $resource(url, resParams, resActions, options);
 867 |  
 868 |         //Exposed methods
 869 |         resource.method = method;
 870 |         resource.static = staticMethod;
 871 |  
 872 |         init();
 873 |  
 874 |         return resource;
 875 |  
 876 |         ////////////////////////////////////////////////////////////
 877 |  
 878 |         function init() {
 879 |  
 880 |           //resolve our ^ parameters
 881 |           resolveParentParams(resParams, resource);
 882 |           addResponseTransformers(resActions, resource);
 883 |  
 884 |           //add default methods/statics
 885 |           forEach(provider.defaults.methods, function (func, key) {
 886 |             resource.method(key, func);
 887 |           });
 888 |           forEach(provider.defaults.statics, function (func, key) {
 889 |             resource.static(key, func);
 890 |           });
 891 |  
 892 |           resource.static('getFull', getFull);
 893 |           resource.$relationships = {};
 894 |         }
 895 |  
 896 |         /**
 897 |          * Does a GET but also resolves sub-resources
 898 |          * @param getParams {Object} standard $http GET params
 899 |          * @param relatedResourcesNames {string[]} names of the related resources to query for
 900 |          * @public
 901 |          * @instance
 902 |          */
 903 |         function getFull(getParams, relatedResourcesNames) {
 904 |  
 905 |           var deferred = $q.defer(),
 906 |             prefix = provider.defaults.relationPrefix,
 907 |             placeholder = extend(new this(), {
 908 |               '$promise': deferred.promise,
 909 |               '$ready': false
 910 |             });
 911 |  
 912 |           Eif(!relatedResourcesNames){
 913 |             relatedResourcesNames = Object.keys(resource.$relationships);
 914 |           }
 915 |  
 916 |           deferred.promise = this.get(getParams).$promise.then(gotParentResults);
 917 |  
 918 |           function gotParentResults(resource) {
 919 |  
 920 |             //if this $resource has related resources
 921 |             Eif (resource.$relationships) {
 922 |  
 923 |               //go through the related resources we were asked to grab
 924 |               $q.all(forEach(relatedResourcesNames, function (name) {
 925 |  
 926 |                 //run their queries and assign the prefixed results to the parent
 927 |                 return resource.$relationships[name].query().$promise
 928 |                   .then(function gotRelatedResults(relatedResults) {
 929 |                     resource[prefix + name] = relatedResults;
 930 |                     return resource;
 931 |                   });
 932 |               }));
 933 |             }
 934 |  
 935 |           }
 936 |  
 937 |           return placeholder;
 938 |         }
 939 |       }
 940 |  
 941 |       return resourceFactory;
 942 |     }
 943 |  
 944 |     // DEFAULT EXTENSIONS
 945 |     ////////////////////////////////////////////////////////////
 946 |  
 947 |     /**
 948 |      * Adds a sub-resource to this $resource
 949 |      * @param name {string}
 950 |      * @param resource {Object}
 951 |      * @public
 952 |      * @static
 953 |      * @returns {$resource_.relate}
 954 |      */
 955 |     function relate(name, resource) {
 956 |       this.$relationships[name] = resource;
 957 |       return this;
 958 |     }
 959 |  
 960 |     /**
 961 |      * Sets/Gets related resources
 962 |      * @param [relationships] {Object} hash of related resource factories
 963 |      * @public
 964 |      * @static
 965 |      * @return {*}
 966 |      */
 967 |     function relationships(relationships){
 968 |       this.$relationships = relationships;
 969 |       return this;
 970 |     }
 971 |  
 972 |     // EXTENDER METHODS
 973 |     ////////////////////////////////////////////////////////////
 974 |  
 975 |     /**
 976 |      * Adds a Method to this $resource. Methods operate on instantiated objects from your $resource.
 977 |      * @param arg1 {string|Object|Object[]} A key-value pair containing method name and definition OR an array of
 978 |      * key value pairs OR the method name (definition on the next param)
 979 |      * @param [func] {Function} if the first parameter is a string of the method name, this parameter will be used as
 980 |      * the definition
 981 |      * @public
 982 |      * @returns {_resource.method}
 983 |      */
 984 |     function method(arg1, func) {
 985 |       extender(this.prototype, arg1, func);
 986 |       return this;
 987 |     }
 988 |  
 989 |     /**
 990 |      * Adds a "static" method to this $resource. Statics operate on the non-instantiated version of your $resource
 991 |      * @param arg1 {string|Object|Object[]} A key-value pair containing method name and definition OR an array of
 992 |      * key value pairs OR the method name (definition on the next param)
 993 |      * @param [func] {Function} if the first parameter is a string of the method name, this parameter will be used as
 994 |      * the definition
 995 |      * @public
 996 |      * @returns {*}
 997 |      */
 998 |     function staticMethod(arg1, func) {
 999 |       extender(this, arg1, func);
1000 |       return this;
1001 |     }
1002 |  
1003 |  
1004 |     // PRIVATES
1005 |     ////////////////////////////////////////////////////////////
1006 |  
1007 |     /**
1008 |      * Helper function that extends a provided object with method arrays, hash or string/method value.
1009 |      * @param src Object to be extended
1010 |      * @param arg1 {string|Object|Object[]} A key-value pair containing method name and definition OR an array of
1011 |      * key value pairs OR the method name (definition on the next param)
1012 |      * @param [func] {Function} if @arg1 is a string of the method name, this parameter will be used as
1013 |      * the definition
1014 |      * @private
1015 |      */
1016 |     function extender(src, arg1, func) {
1017 |       if (isString(arg1)) {
1018 |         src[arg1] = func;
1019 |       }
1020 |       else if (isArray(arg1)) {
1021 |         forEach(arg1, function (method) {
1022 |           extend(src, method);
1023 |         });
1024 |       }
1025 |       else Eif (isObject(arg1)) {
1026 |         extend(src, arg1);
1027 |       }
1028 |     }
1029 |  
1030 |     /**
1031 |      * Special response transformer that first runs the user defined transformer and then our own to attach
1032 |      * our related resources.
1033 |      * @param actions {Object} actions hash to have response transformers added.
1034 |      * @param resource {Object}
1035 |      * @private
1036 |      * @returns {Object} actions hash with added response transformers.
1037 |      */
1038 |     function addResponseTransformers(actions, resource) {
1039 |       forEach(actions, function (action) {
1040 |         action.transformResponse = responseTransforms(action.transformResponse, resource);
1041 |       });
1042 |  
1043 |       return actions;
1044 |     }
1045 |  
1046 |     /**
1047 |      * Response transformer that first calls the user defined transform and then applies our own (which adds the
1048 |      * $relationships)
1049 |      * @param otherTransform {Function}
1050 |      * @param resource {Object}
1051 |      * @private
1052 |      * @returns {Function}
1053 |      */
1054 |     function responseTransforms(otherTransform, parentResource) {
1055 |  
1056 |       return function transformResponse(response) {
1057 |         if (otherTransform) {
1058 |           response = otherTransform(response);
1059 |         }
1060 |         else {
1061 |           response = (response ? angular.fromJson(response) : {});
1062 |         }
1063 |  
1064 |         if (isArray(response)) {
1065 |           forEach(response, function (entry) {
1066 |  
1067 |             attachRelations(entry, parentResource.$relationships);
1068 |           })
1069 |         }
1070 |         else {
1071 |           attachRelations(response, parentResource.$relationships);
1072 |         }
1073 |  
1074 |         return response;
1075 |       };
1076 |  
1077 |     }
1078 |  
1079 |     /**
1080 |      * Attaches related $resource_s to this instantiated $resource_.
1081 |      * @param entry {*}
1082 |      * @param relationships {Object} hash of related $resource_s to be added to this $resource_
1083 |      * @private
1084 |      */
1085 |     function attachRelations(entry, relationships) {
1086 |  
1087 |       if (!entry.$relationships) {
1088 |         entry.$relationships = {};
1089 |       }
1090 |  
1091 |       forEach(relationships, function (relatedResource, name) {
1092 |         var myResource = copy(relatedResource);
1093 |         myResource.prototype.$parent = entry;
1094 |         entry.$relationships[name] = myResource;
1095 |       });
1096 |  
1097 |     }
1098 |  
1099 |     /**
1100 |      * Finds parameters that need to map to a parent $resource and attaches a function that will fetch the parent's
1101 |      * value for that param.
1102 |      * @param params {Object}
1103 |      * @param resource {Object}
1104 |      * @private
1105 |      * @returns {Object}
1106 |      */
1107 |     function resolveParentParams(params, resource) {
1108 |       forEach(params, function (param, key) {
1109 |         if (param.charAt && param.charAt(0) == '^') {
1110 |           params[key] = parentParamFunc(resource, param.substr(1));
1111 |         }
1112 |       });
1113 |  
1114 |       return params;
1115 |     }
1116 |  
1117 |     /**
1118 |      * Function is run every time our $resource is used and attempts to fetch the value for our $parent if one of our
1119 |      * parameters requires so.
1120 |      * @param obj
1121 |      * @param param
1122 |      * @private
1123 |      * @returns {Function}
1124 |      */
1125 |     function parentParamFunc(obj, param) {
1126 |       return function () {
1127 |         return lookupDottedPath(obj.prototype.$parent, param);
1128 |       };
1129 |     }
1130 |  
1131 |  
1132 |     //FROM ANGULAR 1.3
1133 |     ////////////////////////////////////////////////////////////
1134 |     var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;
1135 |  
1136 |     function isValidDottedPath(path) {
1137 |  
1138 |       return (path != null && path !== '' && path !== 'hasOwnProperty' &&
1139 |       MEMBER_NAME_REGEX.test('.' + path));
1140 |     }
1141 |  
1142 |     function lookupDottedPath(obj, path) {
1143 |       if (!isValidDottedPath(path)) {
1144 |         throw new Error('Dotted member path is invalid.', path);
1145 |       }
1146 |  
1147 |       var keys = path.split('.');
1148 |       for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) {
1149 |         var key = keys[i];
1150 |         obj = (obj !== null) ? obj[key] : undefined;
1151 |       }
1152 |       return obj;
1153 |     }
1154 |  
1155 |   }
1156 |  
1157 |  
1158 | })(angular);
1159 |  
1161 | 1162 |
1163 | 1166 | 1167 | 1174 | 1175 | 1176 | 1177 | -------------------------------------------------------------------------------- /tests/mocks/Departments.mocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular. 4 | module('resourceMocks.departments', []) 5 | .value('Departments', [ 6 | { 7 | 'id': 1, 8 | 'url': 'http://api.com/departments/1', 9 | 'name': 'Sales', 10 | 'profits': 9002, 11 | 'manager': 'http://api.com/people/4' 12 | }, 13 | { 14 | 'id': 2, 15 | 'url': 'http://api.com/departments/2', 16 | 'name': 'Accounting', 17 | 'profits': 1000, 18 | 'manager': 'http://api.com/people/3' 19 | }, 20 | { 21 | 'id': 3, 22 | 'url': 'http://api.com/departments/3', 23 | 'name': 'IT', 24 | 'profits': 10000, 25 | 'manager': 'http://api.com/people/2' 26 | }, 27 | { 28 | 'id': 4, 29 | 'url': 'http://api.com/departments/4', 30 | 'name': 'Warehouse', 31 | 'profits': 3000, 32 | 'manager': 'http://api.com/people/1' 33 | }]); 34 | 35 | -------------------------------------------------------------------------------- /tests/mocks/people.mocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular. 4 | module('resourceMocks.people', []) 5 | .value('People', [ 6 | { 7 | 'id': 1, 8 | 'url': 'http://api.com/people/1', 9 | 'name': 'John Carver', 10 | 'jobTitle': 'manager', 11 | 'department': 'http://api.com/departments/4' 12 | }, 13 | { 14 | 'id': 2, 15 | 'url': 'http://api.com/people/2', 16 | 'name': 'Jack Bauer', 17 | 'jobTitle': 'Superhero', 18 | 'department': 'http://api.com/departments/3' 19 | }, 20 | { 21 | 'id': 3, 22 | 'url': 'http://api.com/people/3', 23 | 'name': 'Jesus Riviera', 24 | 'jobTitle': 'chief', 25 | 'department': 'http://api.com/departments/2' 26 | }, 27 | { 28 | 'id': 4, 29 | 'url': 'http://api.com/people/4', 30 | 'name': 'Jack White', 31 | 'jobTitle': 'star', 32 | 'department': 'http://api.com/departments/1' 33 | }]); 34 | 35 | -------------------------------------------------------------------------------- /tests/resourcex.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('$resource_', function () { 4 | 5 | //port over tests from ngResource 6 | describe('$resource tests', resourceTests); 7 | //test different configuration options 8 | describe('Configurations', configurationsTests); 9 | //test nesting resources 10 | describe('Nesting Resources', nestingTests); 11 | //test methods and extending 12 | describe('Methods and Statics', methodsTests); 13 | //anything else/edge cases here 14 | //describe('Edge cases', function(){}); 15 | 16 | /////////////////////////////////////////// 17 | //// shared variables 18 | var Departments, 19 | $resource_, 20 | resourceXProvider, 21 | $httpBackend, 22 | mocks = {}; 23 | 24 | /////////////////////////////////////////// 25 | //// mocks 26 | beforeEach(module('resourceMocks.people')); 27 | beforeEach(module('resourceMocks.departments')); 28 | 29 | /////////////////////////////////////////// 30 | //// initializations 31 | 32 | // does all other initializations, default 33 | function initAllDefault() { 34 | beforeEach(initResourceXWithRoles); 35 | beforeEach(initGlobalsDefault); 36 | beforeEach(initDepartmentWithChild); 37 | //afterAll(resetGlobalsDefault); 38 | } 39 | 40 | //module inits 41 | function initResourceXWithRoles() { 42 | module('ngResourceX', function ($provide, $resource_Provider) { 43 | 44 | $provide.value('Roles', { 45 | selectedRole: {'slug': 'test-role', 'url': 'http://api.com/member/36'}, 46 | person: {'url': 'http://api.com/member/36/'} 47 | }); 48 | 49 | $resource_Provider.defaults.params['user_role'] = 'test_role'; 50 | $resource_Provider.defaults.methods = { 51 | 'testMethod': angular.noop 52 | }; 53 | $resource_Provider.defaults.extensions = { 54 | 'testExtension': angular.noop 55 | }; 56 | 57 | resourceXProvider = $resource_Provider; 58 | 59 | }) 60 | } 61 | 62 | //resource inits 63 | function initDepartmentWithChild() { 64 | Departments = $resource_('departments/:id/', {'id': '@id'}) 65 | //.relate('employees', $resource_('people/:id/', {'id': '@id', 'department': '^id'})); 66 | .relationships({ 67 | 'employees': $resource_('people/:id/', {'id': '@id', 'department': '^id'}), 68 | 'computers': $resource_('computers/:id/', {'id':'@id', 'department':'^id'}) 69 | }); 70 | } 71 | 72 | //init our globals 73 | function initGlobalsDefault() { 74 | inject(function (_$httpBackend_, _$resource__, _People_, _Departments_) { 75 | $httpBackend = _$httpBackend_; 76 | $resource_ = _$resource__; 77 | mocks.departments = _Departments_; 78 | mocks.people = _People_; 79 | }) 80 | } 81 | 82 | //reset our globals 83 | function resetGlobalsDefault() { 84 | Departments = null; 85 | $resource_ = null; 86 | $httpBackend = null; 87 | mocks = {}; 88 | } 89 | 90 | /////////////////////////////////////////// 91 | //// tests 92 | function configurationsTests() { 93 | 94 | initAllDefault(); 95 | 96 | it('should have all default params', function () { 97 | expect(resourceXProvider.defaults.baseUrl).toBeDefined(); 98 | expect(resourceXProvider.defaults.relationPrefix).toBe('_'); 99 | expect(resourceXProvider.defaults.statics).toBeDefined(); 100 | //expect(resourceXProvider.defaults.statics.getFull).toBeDefined(); 101 | expect(resourceXProvider.defaults.statics.relate).toBeDefined(); 102 | expect(resourceXProvider.defaults.params).toBeDefined(); 103 | expect(resourceXProvider.defaults.methods).toBeDefined(); 104 | }); 105 | 106 | it('should have set baseURL default param', function () { 107 | expect(resourceXProvider.defaults).toBeDefined(); 108 | }); 109 | 110 | it('should have set user_role as a default request param', function () { 111 | expect(resourceXProvider.defaults.params.user_role).toBeDefined(); 112 | expect(resourceXProvider.defaults.params['user_role']).toBe('test_role'); 113 | }); 114 | 115 | it('new resources should have default statics', function(){ 116 | var R = $resource_('test/api'); 117 | expect(R.getFull).toBeDefined(); 118 | expect(R.relate).toBeDefined(); 119 | expect(R.relationships).toBeDefined(); 120 | }); 121 | 122 | it('new resources should have default methods', function(){ 123 | var R = $resource_('test/api'); 124 | var testR = new R(); 125 | expect(testR.testMethod).toBeDefined(); 126 | }); 127 | 128 | 129 | } 130 | 131 | function nestingTests() { 132 | 133 | initAllDefault(); 134 | 135 | it('the retrieved element will have the configured nested resources', function () { 136 | var departments = Departments.get({'id': '2'}); 137 | $httpBackend.expectGET(/departments\/2/).respond(mocks.departments[1]); 138 | $httpBackend.flush(); 139 | expect(departments.$relationships['employees']).toBeDefined(); 140 | }); 141 | 142 | it('can retrieve nested resources with a mapped parameter to the parent', function () { 143 | 144 | var department = Departments.get({'id': '1'}); 145 | $httpBackend.expectGET(/departments\/1/).respond(mocks.departments[0]); 146 | $httpBackend.flush(); 147 | $httpBackend.expectGET(/people\?department=1/).respond(200, mocks.people); 148 | department.$relationships['employees'].query(); 149 | $httpBackend.flush(); 150 | expect(department.$relationships['employees']).toBeDefined(); 151 | }); 152 | 153 | } 154 | 155 | function methodsTests() { 156 | 157 | initAllDefault(); 158 | 159 | it('Can create a new relationship to a $resource_', function () { 160 | var testResourceX = $resource_('testResource/:id', {'id':'@id'}); 161 | testResourceX.relate('testRelation', $resource_('test/:testId', {'testId':'@testId'})); 162 | expect(testResourceX.$relationships['testRelation']).toBeDefined(); 163 | }); 164 | 165 | 166 | it('getFull returns promise for resource with its related resources included', function () { 167 | Departments.getFull({'id':1}); 168 | $httpBackend.expectGET(/departments\/1/).respond(mocks.departments[0]); 169 | $httpBackend.expectGET(/people\?department=1/).respond(mocks.people); 170 | $httpBackend.expectGET(/computers\?department=1/).respond([]); 171 | $httpBackend.flush(); 172 | }); 173 | 174 | it('Static methods go into the resource factory', function () { 175 | 176 | Departments = $resource_('departments/:id/', {'id': '@id'}) 177 | .static('test', function () { 178 | return 'rest result'; 179 | }); 180 | 181 | var instanceDepartment = new Departments({'id': 1}); 182 | expect(Departments.test).toBeDefined(); 183 | expect(instanceDepartment.test).toBeUndefined(); 184 | 185 | }); 186 | 187 | it('Instance methods go into resource instance', function () { 188 | 189 | Departments = $resource_('departments/:id/', {'id': '@id'}) 190 | .method('test', function () { 191 | return this.id; 192 | }); 193 | 194 | var instanceDepartment = new Departments({'id': 1}); 195 | 196 | $httpBackend.expect('OPTIONS', /departments/).respond(200); 197 | Departments.options(); 198 | 199 | expect(Departments.test).toBeUndefined(); 200 | expect(instanceDepartment.test).toBeDefined(); 201 | expect(instanceDepartment.test()).toBe(1); 202 | 203 | }); 204 | 205 | it('Can extend with an hash of methods', function () { 206 | 207 | Departments = $resource_('departments/:id/', {'id': '@id'}) 208 | .method( 209 | { 210 | 'test': function () { 211 | return this.id; 212 | } 213 | } 214 | ); 215 | 216 | var instanceDepartment = new Departments({'id': 1}); 217 | 218 | expect(instanceDepartment.test).toBeDefined(); 219 | expect(instanceDepartment.test()).toBe(1); 220 | 221 | }); 222 | 223 | it('Can extend with an Array of methods', function () { 224 | 225 | Departments = $resource_('departments/:id/', {'id': '@id'}) 226 | .method([ 227 | { 228 | 'test': function () { 229 | return this.id; 230 | } 231 | } 232 | ]); 233 | 234 | var instanceDepartment = new Departments({'id': 1}); 235 | 236 | expect(instanceDepartment.test).toBeDefined(); 237 | expect(instanceDepartment.test()).toBe(1); 238 | 239 | }); 240 | } 241 | 242 | function resourceTests() { 243 | 244 | initAllDefault(); 245 | 246 | it('Can create a new $resource_', function () { 247 | var department = new Departments(); 248 | 249 | department.name = "Test"; 250 | department.$save(); 251 | $httpBackend.expectPOST(/departments/, {name: 'Test'}).respond({name: 'Test'}); 252 | $httpBackend.flush(); 253 | 254 | expect(department.name).toBe('Test'); 255 | 256 | }); 257 | 258 | it('Can modify an instance $resource_', function () { 259 | 260 | var department = Departments.get({'id': '2'}); 261 | $httpBackend.expectGET(/departments\/2/).respond(200).respond(mocks.departments[1]); 262 | $httpBackend.flush(); 263 | 264 | department.name = 'Patchy'; 265 | $httpBackend.expectPATCH(/departments\/2/).respond(200, {name: 'Patchy'}); 266 | department.$update(); 267 | $httpBackend.flush(); 268 | 269 | expect(department.name).toBe('Patchy'); 270 | }); 271 | 272 | it('Can get list of $resource_ instances using query', function () { 273 | var departments = Departments.query(); 274 | $httpBackend.expectGET(/departments/).respond(mocks.departments); 275 | $httpBackend.flush(); 276 | expect(departments.length).toBe(4); 277 | expect(departments[0].url).toBe("http://api.com/departments/1"); 278 | }); 279 | 280 | it('Each fetched $resource_ result is an instance of $resource_', function () { 281 | var departments = Departments.query(); 282 | $httpBackend.expectGET(/departments/).respond(mocks.departments); 283 | $httpBackend.flush(); 284 | expect(departments.length).toBe(4); 285 | expect(departments[0]).toBeDefined(); 286 | }); 287 | 288 | it('Can get a single instance of $resource_ using GET', function () { 289 | var departments = Departments.get({'id': '2'}); 290 | $httpBackend.expectGET(/departments\/2/).respond(mocks.departments[1]); 291 | $httpBackend.flush(); 292 | expect(departments.url).toBe("http://api.com/departments/2"); 293 | }); 294 | 295 | it('Catch invalid parameter', function () { 296 | Departments.relate('bad', $resource_('bad/:id/', {'id':'^'})); 297 | var departments = Departments.query(); 298 | $httpBackend.expectGET(/departments/).respond(mocks.departments); 299 | $httpBackend.expectGET(/bad/).respond(mocks.departments); 300 | expect( function(){ $httpBackend.flush() } ).toThrow(new Error("Dotted member path is invalid.")); 301 | }); 302 | } 303 | 304 | }); 305 | 306 | 307 | 308 | --------------------------------------------------------------------------------