├── .gitattributes ├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── api ├── bower.json ├── dist ├── angular-cog.js └── angular-cog.min.js ├── example ├── index.html └── main.controller.js ├── karma.conf.js ├── package.json ├── src └── angular-cog.js ├── tests └── angular-cog.tests.js └── why.jpg /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tmp 3 | .sass-cache 4 | bower_components 5 | coverage 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | before_script: "npm install grunt-cli -g && grunt" 5 | notifications: 6 | email: false 7 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | ngmin: { 5 | directives: { 6 | src: ['src/*.js'], 7 | dest: 'build/directives.js' 8 | } 9 | }, 10 | pkg: grunt.file.readJSON('package.json'), 11 | concat: { 12 | dist: { 13 | src: ['build/*.js'], 14 | dest: 'dist/angular-cog.js' 15 | } 16 | }, 17 | uglify: { 18 | options: { 19 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> */' 20 | }, 21 | dist: { 22 | src: 'dist/angular-cog.js', 23 | dest: 'dist/angular-cog.min.js' 24 | } 25 | }, 26 | clean: ["build"], 27 | watch: { 28 | scripts: { 29 | files: ['src/**/*.js', 'tests/**/*.js'], 30 | tasks: ['ngmin', 'uglify', 'clean', 'karma:unit:run'], 31 | options: { 32 | debounceDelay: 250, 33 | }, 34 | } 35 | }, 36 | karma: { 37 | unit: { 38 | configFile: 'karma.conf.js', 39 | // run karma in the background 40 | background: true, 41 | // which browsers to run the tests on 42 | browsers: ['Chrome'] 43 | } 44 | } 45 | }); 46 | 47 | grunt.loadNpmTasks('grunt-contrib-concat'); 48 | grunt.loadNpmTasks('grunt-ngmin'); 49 | grunt.loadNpmTasks('grunt-contrib-uglify'); 50 | grunt.loadNpmTasks('grunt-contrib-copy'); 51 | grunt.loadNpmTasks('grunt-contrib-clean'); 52 | grunt.loadNpmTasks('grunt-contrib-watch'); 53 | grunt.loadNpmTasks('grunt-karma'); 54 | 55 | grunt.registerTask('default', ['ngmin', 'concat', 'uglify', 'clean']); 56 | 57 | }; 58 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Chinmay Kulkarni 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-cog 2 | =========== 3 | declarative ajax requests for angularjs 4 | 5 | ### Cog? 6 | A subordinate member of an organization who performs necessary but usually minor or routine functions. 7 | 8 | ### Groundwork first 9 | angular-cog exposes few directives that act as helpers for ajax requests. For every request 4 attributes are most important 10 | 11 | 1. Url 12 | 2. Data to be sent 13 | 3. Headers 14 | 3. Whether reponse is success or error 15 | 16 | angular-cog allows you to configure these from html directive and remove the clutter from javascript. 17 | 18 | ### Installation 19 | Installation is very straight forward. Grab the latest zip from github. Copy angular-cog.min.js in your root, and refer it in your page. 20 | ```html 21 | 22 | ``` 23 | Then,add it as dependency in your module 24 | ```javascript 25 | angular.module('yourApp', ['angularCog']); 26 | ``` 27 | ### Directive 28 | ```html 29 | 30 |
38 |
39 | ``` 40 | 41 | Attribute | Meaning | Example 42 | --- | --- | --- 43 | ```cog-*verb*``` | url to call, [get,post,put,delete] decides the http verb | ```cog-get="/users"```, ```cog-post="/users"``` 44 | ```cog-model``` | data that will be passed with post and put request | ```cog-model="user"``` 45 | ```cog-success``` | expression to evaluate if response is 200 | ```cog-success="users = $data"```, ```cog-success="setUsers($data)``` 46 | ```cog-error``` | expression to evaluate if response is not 200 | ```cog-error="error($headers)"``` 47 | ```cog-trigger``` | by default, request will be sent as soon as directive is compiled, in case you would like to delay the request you could use cog-trigger. angular-cog will watch the expression and fire the request as soon as it becomes true. **applicable to cog-get only** | ```cog-trigger="user != null"``` 48 | ```cog-no-spinner``` | if you don't want to display spinner for this particular request, refer [spinner](#spinner) for more information | ```cog-no-spinner="true"``` 49 | ```cog-absolute-url``` | by default CogConfig.rootUrl will be prepended to every url, if you don't want to then use this attribute | ```cog-absolute-url="true"``` 50 | ```cog-config``` | header object to pass to angular $http | 51 | 52 | **Note:** ```cog-post``` and ```cog-put``` are only allowed on ```
``` elements. Refer [sample](#sample-usages) usages for more information. 53 | 54 | **Special attributes available from directive** 55 | ```javascript 56 | $data //angularjs data object 57 | $status //angularjs status object 58 | $headers //angularjs headers object 59 | $config //angularjs config object 60 | ``` 61 | for more information please visit [$http](http://docs.angularjs.org/api/ng/service/$http) documentation. 62 | 63 | ### Configuration 64 | angular-cog allows you to configure following, comment in front of attribute states default value 65 | ```javascript 66 | angular.module('example').config(function(CogConfigProvider) { 67 | //prepended to every request url 68 | CogConfigProvider.rootUrl = {url}; //"" 69 | //log function to call after every request, could be used for tracing 70 | CogConfigProvider.log = {function}; //angular.noop; 71 | //this will be called if request returns with error 72 | CogConfigProvider.error = {function}; //angular.noop; 73 | //this will be called if request returns with success 74 | CogConfigProvider.success = {function}; //angular.noop; 75 | //enables, disables spinner. refer spinner section for more 76 | CogConfigProvider.enableSpinner = {true|false}; //true 77 | }); 78 | ``` 79 | 80 | ### Sample usages 81 | **cog-get** 82 | ```html 83 | 85 | 86 | 88 |
89 | 90 | 92 |
93 | ``` 94 | **cog-post** 95 | ```html 96 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
106 | ``` 107 | **cog-put** 108 | ```html 109 | 111 | 112 |
113 | 114 | 115 | 116 | 117 |
118 | ``` 119 | **Note:** for ```cog-put``` and ```cog-post```, ```cog-model``` is necessary. else the request will be sent with blank payload. Also, ```cog-put``` and ```cog-post``` can be only used with ```
``` elements, this restriction is imposed so we can leverage validations provided by angularjs. 120 | 121 | **cog-delete** 122 | ```html 123 | 125 |
126 | ``` 127 | 128 | ### Spinner 129 | It could be a lot of pain to track all ajax requests from view and display loaders accordingly. angular-cog provides an easy fix for this problem. Each request is pushed to a stack and poped once returned, and till the stack has atleast one element we display the spinner. 130 | 131 | To get spinner working in your project follow these steps: 132 | 133 | 1. Set ```CogConfigProvider.enableSpinner``` to ```true``` 134 | 2. There is no step two. 135 | 136 | 137 | By default all requests made by angular-cog will be tracked, to get spinner for your own ajax requests follow this: 138 | 139 | 1. Add ```SpinnerService``` as an argument to your controller 140 | 2. Call ```SpinnerService.spin()``` before making the request 141 | 3. Call ```SpinnerService.stop()``` once the response is received (either success or error!) 142 | 4. Drink tea, tea is good. 143 | 144 | ### Read too much ? Enjoy this meme 145 | ![Why don't we](https://raw.githubusercontent.com/chinmaymk/angular-cog/master/why.jpg) 146 | 147 | ### License 148 | The MIT License (MIT) 149 | 150 | Copyright (c) 2014 Chinmay Kulkarni 151 | 152 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 153 | 154 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 155 | 156 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 157 | 158 | ### Got suggestions ? 159 | Feel free to submit a pull request, file an issue, or get in touch on twitter [@_chinmaymk](https://twitter.com/_chinmaymk) 160 | -------------------------------------------------------------------------------- /api: -------------------------------------------------------------------------------- 1 | 2 | directive 3 | 4 |
12 |
13 | 14 | 15 | config 16 | 17 | rootUrl = {url}; //"" 18 | log = {function}; //angular.noop; 19 | imagePath = {path to spinner}; //"spinner.gif"; 20 | error = {function}; //angular.noop; 21 | success = {function}; //angular.noop; 22 | enableSpinner = {true|false}; //true 23 | 24 | arguments available 25 | $data 26 | $header 27 | $config 28 | $status 29 | 30 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-cog", 3 | "version": "1.0.0", 4 | "authors": [ 5 | "chinmaymk" 6 | ], 7 | "description": "declarative $http for angularjs", 8 | "keywords": [ 9 | "http", 10 | "ajax", 11 | "angular", 12 | "angularjs" 13 | ], 14 | "license": "MIT", 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "test", 20 | "tests" 21 | ], 22 | "main": ["./dist/angular-cog.js"], 23 | "dependencies": { 24 | "angular": "latest" 25 | } 26 | } -------------------------------------------------------------------------------- /dist/angular-cog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main module 3 | */ 4 | angular.module('angularCog', []); 5 | /** 6 | * GET directive, this sends request immedietly 7 | * @param {[type]} MakeRequestService [description] 8 | * @return {[type]} [description] 9 | */ 10 | angular.module('angularCog').directive('cogGet', [ 11 | 'MakeRequestService', 12 | function (MakeRequestService) { 13 | return { 14 | restrict: 'A', 15 | scope: true, 16 | link: function (scope, element, attrs) { 17 | //check if there's a trigger 18 | if (attrs.cogTrigger) { 19 | //watch for the expression 20 | scope.$watch(attrs.cogTrigger, function (newVal, oldVal) { 21 | //if the changed value becomes true, fire away request! 22 | if (newVal) { 23 | MakeRequestService('get', null, scope, attrs); 24 | } 25 | }); 26 | } else { 27 | //make immediate request if there isn't 28 | MakeRequestService('get', null, scope, attrs); 29 | } 30 | } 31 | }; 32 | } 33 | ]); 34 | /** 35 | * POST directive, listens to form submit 36 | * @param {[type]} MakeRequestService [description] 37 | * @return {[type]} [description] 38 | */ 39 | angular.module('angularCog').directive('cogPost', [ 40 | 'MakeRequestService', 41 | function (MakeRequestService) { 42 | return { 43 | restrict: 'A', 44 | require: '^form', 45 | scope: true, 46 | link: function (scope, element, attrs) { 47 | element.bind('submit', function () { 48 | var data = scope[attrs.cogModel]; 49 | MakeRequestService('post', data, scope, attrs); 50 | }); 51 | } 52 | }; 53 | } 54 | ]); 55 | /** 56 | * PUT directive, listens to form submit 57 | * @param {[type]} MakeRequestService [description] 58 | * @return {[type]} [description] 59 | */ 60 | angular.module('angularCog').directive('cogPut', [ 61 | 'MakeRequestService', 62 | function (MakeRequestService) { 63 | return { 64 | restrict: 'A', 65 | require: '^form', 66 | scope: true, 67 | link: function (scope, element, attrs) { 68 | element.bind('submit', function () { 69 | var data = scope[attrs.cogModel]; 70 | MakeRequestService('put', data, scope, attrs); 71 | }); 72 | } 73 | }; 74 | } 75 | ]); 76 | /** 77 | * DELETE directive, listens to click event 78 | * @param {[type]} MakeRequestService [description] 79 | * @return {[type]} [description] 80 | */ 81 | angular.module('angularCog').directive('cogDelete', [ 82 | 'MakeRequestService', 83 | function (MakeRequestService) { 84 | return { 85 | restrict: 'A', 86 | scope: true, 87 | link: function (scope, element, attrs) { 88 | element.bind('click', function () { 89 | console.log('called'); 90 | MakeRequestService('delete', null, scope, attrs); 91 | }); 92 | } 93 | }; 94 | } 95 | ]); 96 | /** 97 | * Helper service for making http calls, just to DRY up directives 98 | * @param {[type]} $http [description] 99 | * @param {[type]} CogConfig [description] 100 | * @param {[type]} SpinnerService [description] 101 | * @return {[type]} [description] 102 | */ 103 | angular.module('angularCog').factory('MakeRequestService', [ 104 | '$http', 105 | 'CogConfig', 106 | 'SpinnerService', 107 | function ($http, CogConfig, SpinnerService) { 108 | /** 109 | * Does actual request 110 | * @param {string} verb http verb 111 | * @param {object|string} data data to be sent to server 112 | * @param {object} scope angularjs scope 113 | * @param {object} attrs attributes of element 114 | */ 115 | function MakeRequest(verb, data, scope, attrs) { 116 | //send the http request 117 | $http({ 118 | method: verb, 119 | url: getUrl(verb, attrs), 120 | data: data, 121 | config: scope[attrs.CogConfig] 122 | }).success(function (data, status, headers, config) { 123 | //common function to set scope variables 124 | responseReceived(data, status, headers, config); 125 | //evaluates cog-success expression of directive 126 | scope.$eval(attrs.cogSuccess); 127 | //global success handler 128 | CogConfig.success(data, status, headers, config); 129 | }).error(function (data, status, headers, config) { 130 | //common function to set scope variables 131 | responseReceived(data, status, headers, config); 132 | //evaluates cog-error expression of directive 133 | scope.$eval(attrs.cogError); 134 | //global error handler 135 | CogConfig.error(data, status, headers, config); 136 | }); 137 | //sometimes $apply is not called immedietly, so this fix 138 | if (!scope.$$phase) { 139 | scope.$apply(); 140 | } 141 | //start the spinner if enabled 142 | if (CogConfig.enableSpinner && !attrs.cogNoSpinner) { 143 | SpinnerService.spin(); 144 | } 145 | /** 146 | * Common function that gets called for setting scope variables 147 | * @param {object|string} data http response 148 | * @param {int} status response code 149 | * @param {object} headers [description] 150 | * @param {object} config [description] 151 | * @return {[type]} [description] 152 | */ 153 | function responseReceived(data, status, headers, config) { 154 | scope.$data = data; 155 | scope.$status = status; 156 | scope.$headers = headers; 157 | scope.$config = config; 158 | //stop the spinner once response is recieved 159 | if (CogConfig.enableSpinner && !attrs.cogNoSpinner) { 160 | SpinnerService.stop(); 161 | } 162 | //pass the data to global log 163 | CogConfig.log(data, status, headers, config); 164 | } 165 | } 166 | ; 167 | /** 168 | * return appropriate url based on verb 169 | * @param {string} verb http verb 170 | * @param {object} attrs [description] 171 | * @return {[type]} [description] 172 | */ 173 | function getUrl(verb, attrs) { 174 | var url = attrs.cogAbsoluteUrl ? '' : CogConfig.rootUrl; 175 | switch (verb) { 176 | case 'get': 177 | url += attrs.cogGet; 178 | break; 179 | case 'post': 180 | url += attrs.cogPost; 181 | break; 182 | case 'put': 183 | url += attrs.cogPut; 184 | break; 185 | case 'delete': 186 | url += attrs.cogDelete; 187 | break; 188 | } 189 | return url; 190 | } 191 | //return MakeRequest as factory handler 192 | return MakeRequest; 193 | } 194 | ]); 195 | /** 196 | * Hacked a service to display spinner on screen 197 | * I'm not proud of it, but it had to be done. 198 | * @param {[type]} CogConfig [description] 199 | * @return {[type]} [description] 200 | */ 201 | angular.module('angularCog').service('SpinnerService', [ 202 | 'CogConfig', 203 | function (CogConfig) { 204 | //html elements and styles 205 | var spinnerStyle = [ 206 | 'position:absolute;', 207 | 'top:50%;', 208 | 'left:50%;', 209 | 'z-index:2000' 210 | ].join(''); 211 | var spinner = angular.element('
'); 212 | spinner.attr('style', spinnerStyle); 213 | //spinner.find("img").attr("src", CogConfig.imagePath); 214 | var backdropStyle = [ 215 | 'position:fixed;', 216 | 'top:0;', 217 | 'left:0;', 218 | 'width:100%;', 219 | 'height:100%;', 220 | 'background:white;', 221 | 'text-align:center;', 222 | 'opacity:0.7;' 223 | ].join(''); 224 | var backdrop = angular.element('
'); 225 | backdrop.attr('style', backdropStyle); 226 | //keep track of all requests 227 | //makes life easier when multiple ajax requests are fired from same view 228 | var requests = []; 229 | var isSpinning = false; 230 | return { 231 | spin: function () { 232 | requests.push(1); 233 | if (!isSpinning) { 234 | angular.element(document.body).append(backdrop); 235 | angular.element(document.body).append(spinner); 236 | isSpinning = true; 237 | } 238 | }, 239 | stop: function () { 240 | requests.pop(); 241 | if (requests.length == 0) { 242 | backdrop.remove(); 243 | spinner.remove(); 244 | isSpinning = false; 245 | } 246 | } 247 | }; 248 | } 249 | ]); 250 | /** 251 | * Configuration for angular-cog 252 | * @return {[type]} [description] 253 | */ 254 | angular.module('angularCog').provider('CogConfig', function () { 255 | this.rootUrl = ''; 256 | this.log = angular.noop; 257 | this.imagePath = 'spinner.gif'; 258 | this.error = angular.noop; 259 | this.success = angular.noop; 260 | this.enableSpinner = true; 261 | var self = this; 262 | this.$get = function () { 263 | return self; 264 | }; 265 | }); -------------------------------------------------------------------------------- /dist/angular-cog.min.js: -------------------------------------------------------------------------------- 1 | /*! angular-cog - v1.0.0 */angular.module("angularCog",[]),angular.module("angularCog").directive("cogGet",["MakeRequestService",function(a){return{restrict:"A",scope:!0,link:function(b,c,d){d.cogTrigger?b.$watch(d.cogTrigger,function(c){c&&a("get",null,b,d)}):a("get",null,b,d)}}}]),angular.module("angularCog").directive("cogPost",["MakeRequestService",function(a){return{restrict:"A",require:"^form",scope:!0,link:function(b,c,d){c.bind("submit",function(){var c=b[d.cogModel];a("post",c,b,d)})}}}]),angular.module("angularCog").directive("cogPut",["MakeRequestService",function(a){return{restrict:"A",require:"^form",scope:!0,link:function(b,c,d){c.bind("submit",function(){var c=b[d.cogModel];a("put",c,b,d)})}}}]),angular.module("angularCog").directive("cogDelete",["MakeRequestService",function(a){return{restrict:"A",scope:!0,link:function(b,c,d){c.bind("click",function(){console.log("called"),a("delete",null,b,d)})}}}]),angular.module("angularCog").factory("MakeRequestService",["$http","CogConfig","SpinnerService",function(a,b,c){function d(d,f,g,h){function i(a,d,e,f){g.$data=a,g.$status=d,g.$headers=e,g.$config=f,b.enableSpinner&&!h.cogNoSpinner&&c.stop(),b.log(a,d,e,f)}a({method:d,url:e(d,h),data:f,config:g[h.CogConfig]}).success(function(a,c,d,e){i(a,c,d,e),g.$eval(h.cogSuccess),b.success(a,c,d,e)}).error(function(a,c,d,e){i(a,c,d,e),g.$eval(h.cogError),b.error(a,c,d,e)}),g.$$phase||g.$apply(),b.enableSpinner&&!h.cogNoSpinner&&c.spin()}function e(a,c){var d=c.cogAbsoluteUrl?"":b.rootUrl;switch(a){case"get":d+=c.cogGet;break;case"post":d+=c.cogPost;break;case"put":d+=c.cogPut;break;case"delete":d+=c.cogDelete}return d}return d}]),angular.module("angularCog").service("SpinnerService",["CogConfig",function(){var a=["position:absolute;","top:50%;","left:50%;","z-index:2000"].join(""),b=angular.element("
");b.attr("style",a);var c=["position:fixed;","top:0;","left:0;","width:100%;","height:100%;","background:white;","text-align:center;","opacity:0.7;"].join(""),d=angular.element("
");d.attr("style",c);var e=[],f=!1;return{spin:function(){e.push(1),f||(angular.element(document.body).append(d),angular.element(document.body).append(b),f=!0)},stop:function(){e.pop(),0==e.length&&(d.remove(),b.remove(),f=!1)}}}]),angular.module("angularCog").provider("CogConfig",function(){this.rootUrl="",this.log=angular.noop,this.imagePath="spinner.gif",this.error=angular.noop,this.success=angular.noop,this.enableSpinner=!0;var a=this;this.$get=function(){return a}}); -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | angular-cog demo! 6 | 7 | 8 | 9 | 10 |
11 | {{users}} 12 |
13 | 14 |
15 |
16 | 17 | 18 | 24 | 25 | 26 | 27 |
28 | 29 |
35 | 36 | 37 | 38 | 39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/main.controller.js: -------------------------------------------------------------------------------- 1 | angular.module('example', ['angularCog']); 2 | 3 | function MainController($scope) { 4 | $scope.users = []; 5 | } -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var grunt = require('grunt'); 2 | module.exports = function(karma) { 3 | karma.set({ 4 | /** 5 | * From where to look for files, starting with the location of this file. 6 | */ 7 | basePath: '.', 8 | 9 | /** 10 | * This is the list of file patterns to load into the browser during testing. 11 | */ 12 | files: [ 13 | "bower_components/angular/angular.min.js", 14 | "bower_components/angular/angular-mocks.js", 15 | "dist/*.js", 16 | "tests/*.js" 17 | ], 18 | 19 | preprocessors: { 20 | 'dist/*.js': ['coverage'] 21 | }, 22 | 23 | frameworks: ['jasmine'], 24 | plugins: ['karma-jasmine', 'karma-chrome-launcher', 'karma-coverage'], 25 | 26 | logLevel: 'WARN', 27 | /** 28 | * How to report, by default. 29 | */ 30 | reporters: ['dots', 'coverage'], 31 | 32 | coverageReporter: { 33 | type: 'html', 34 | dir: 'coverage/' 35 | }, 36 | /** 37 | * On which port should the browser connect, on which port is the test runner 38 | * operating, and what is the URL path for the browser to use. 39 | */ 40 | port: 7019, 41 | urlRoot: '/', 42 | 43 | /** 44 | * Disable file watching by default. 45 | */ 46 | autoWatch: true, 47 | 48 | /** 49 | * The list of browsers to launch to test ondest * default, but other browser names include: 50 | * Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS 51 | * 52 | * Note that you can also use the executable name of the browser, like "chromium" 53 | * or "firefox", but that these vary based on your operating system. 54 | * 55 | * You may also leave this blank and manually navigate your browser to 56 | * http://localhost:9018/ when you're running tests. The window/tab can be left 57 | * open and the tests will automatically occur there during the build. This has 58 | * the aesthetic advantage of not launching a browser every time you save. 59 | */ 60 | browsers: [ 61 | 'Chrome' 62 | ] 63 | }); 64 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-cog", 3 | "version": "1.0.0", 4 | "description": "declarative $http for angularjs", 5 | "main": "Gruntfile.js", 6 | "preinstall" : "npm install -g grunt && npm install grunt-cli -g", 7 | "devDependencies": { 8 | "grunt": "~0.4.1", 9 | "grunt-contrib-concat": "*", 10 | "grunt-contrib-uglify": "*", 11 | "grunt-contrib-copy": "*", 12 | "grunt-contrib-clean": "*", 13 | "grunt-ngmin": "0.0.3", 14 | "grunt-contrib-watch": "~0.5.3", 15 | "karma": "^0.12.6", 16 | "grunt-karma": "^0.8.2", 17 | "karma-jasmine": "^0.1.5", 18 | "karma-chrome-launcher": "^0.1.3", 19 | "karma-coverage": "^0.2.1" 20 | }, 21 | "scripts": { 22 | "test": "./node_modules/karma/bin/karma start" 23 | }, 24 | "repository": "", 25 | "author": "chinmaymk", 26 | "license": "MIT" 27 | } 28 | -------------------------------------------------------------------------------- /src/angular-cog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main module 3 | */ 4 | angular.module('angularCog', []); 5 | 6 | 7 | /** 8 | * GET directive, this sends request immedietly 9 | * @param {[type]} MakeRequestService [description] 10 | * @return {[type]} [description] 11 | */ 12 | angular.module('angularCog').directive('cogGet', function(MakeRequestService) { 13 | return { 14 | restrict: 'A', 15 | scope: true, 16 | link: function(scope, element, attrs) { 17 | //check if there's a trigger 18 | if (attrs.cogTrigger) { 19 | //watch for the expression 20 | scope.$watch(attrs.cogTrigger, function(newVal, oldVal) { 21 | //if the changed value becomes true, fire away request! 22 | if (newVal) { 23 | MakeRequestService('get', null, scope, attrs); 24 | } 25 | }) 26 | } else { 27 | //make immediate request if there isn't 28 | MakeRequestService('get', null, scope, attrs); 29 | } 30 | } 31 | } 32 | }); 33 | 34 | /** 35 | * POST directive, listens to form submit 36 | * @param {[type]} MakeRequestService [description] 37 | * @return {[type]} [description] 38 | */ 39 | angular.module('angularCog').directive('cogPost', function(MakeRequestService) { 40 | return { 41 | restrict: 'A', 42 | require: '^form', 43 | scope: true, 44 | link: function(scope, element, attrs) { 45 | element.bind("submit", function() { 46 | var data = scope[attrs.cogModel]; 47 | MakeRequestService('post', data, scope, attrs); 48 | }); 49 | } 50 | } 51 | }) 52 | 53 | /** 54 | * PUT directive, listens to form submit 55 | * @param {[type]} MakeRequestService [description] 56 | * @return {[type]} [description] 57 | */ 58 | angular.module('angularCog').directive('cogPut', function(MakeRequestService) { 59 | return { 60 | restrict: 'A', 61 | require: '^form', 62 | scope: true, 63 | link: function(scope, element, attrs) { 64 | element.bind("submit", function() { 65 | var data = scope[attrs.cogModel]; 66 | MakeRequestService('put', data, scope, attrs); 67 | }); 68 | } 69 | } 70 | }) 71 | 72 | /** 73 | * DELETE directive, listens to click event 74 | * @param {[type]} MakeRequestService [description] 75 | * @return {[type]} [description] 76 | */ 77 | angular.module('angularCog').directive('cogDelete', function(MakeRequestService) { 78 | return { 79 | restrict: 'A', 80 | scope: true, 81 | link: function(scope, element, attrs) { 82 | element.bind("click", function() { 83 | console.log('called'); 84 | MakeRequestService('delete', null, scope, attrs); 85 | }); 86 | } 87 | } 88 | }); 89 | 90 | /** 91 | * Helper service for making http calls, just to DRY up directives 92 | * @param {[type]} $http [description] 93 | * @param {[type]} CogConfig [description] 94 | * @param {[type]} SpinnerService [description] 95 | * @return {[type]} [description] 96 | */ 97 | angular.module('angularCog').factory('MakeRequestService', function($http, CogConfig, SpinnerService) { 98 | /** 99 | * Does actual request 100 | * @param {string} verb http verb 101 | * @param {object|string} data data to be sent to server 102 | * @param {object} scope angularjs scope 103 | * @param {object} attrs attributes of element 104 | */ 105 | function MakeRequest(verb, data, scope, attrs) { 106 | //send the http request 107 | $http({ 108 | method: verb, 109 | //gets url based on verb and attribute 110 | url: getUrl(verb, attrs), 111 | data: data, 112 | config: scope[attrs.CogConfig] 113 | }).success(function(data, status, headers, config) { 114 | //common function to set scope variables 115 | responseReceived(data, status, headers, config); 116 | //evaluates cog-success expression of directive 117 | scope.$eval(attrs.cogSuccess); 118 | //global success handler 119 | CogConfig.success(data, status, headers, config); 120 | }).error(function(data, status, headers, config) { 121 | //common function to set scope variables 122 | responseReceived(data, status, headers, config); 123 | //evaluates cog-error expression of directive 124 | scope.$eval(attrs.cogError); 125 | //global error handler 126 | CogConfig.error(data, status, headers, config); 127 | }); 128 | 129 | //sometimes $apply is not called immedietly, so this fix 130 | if (!scope.$$phase) { 131 | scope.$apply(); 132 | } 133 | 134 | //start the spinner if enabled 135 | if (CogConfig.enableSpinner && !attrs.cogNoSpinner) { 136 | SpinnerService.spin(); 137 | } 138 | 139 | /** 140 | * Common function that gets called for setting scope variables 141 | * @param {object|string} data http response 142 | * @param {int} status response code 143 | * @param {object} headers [description] 144 | * @param {object} config [description] 145 | * @return {[type]} [description] 146 | */ 147 | function responseReceived(data, status, headers, config) { 148 | scope.$data = data; 149 | scope.$status = status; 150 | scope.$headers = headers; 151 | scope.$config = config; 152 | //stop the spinner once response is recieved 153 | if (CogConfig.enableSpinner && !attrs.cogNoSpinner) { 154 | SpinnerService.stop(); 155 | } 156 | //pass the data to global log 157 | CogConfig.log(data, status, headers, config); 158 | } 159 | }; 160 | 161 | /** 162 | * return appropriate url based on verb 163 | * @param {string} verb http verb 164 | * @param {object} attrs [description] 165 | * @return {[type]} [description] 166 | */ 167 | function getUrl(verb, attrs) { 168 | var url = attrs.cogAbsoluteUrl ? '' : CogConfig.rootUrl; 169 | switch (verb) { 170 | case 'get': 171 | url += attrs.cogGet; 172 | break; 173 | case 'post': 174 | url += attrs.cogPost; 175 | break; 176 | case 'put': 177 | url += attrs.cogPut; 178 | break; 179 | case 'delete': 180 | url += attrs.cogDelete; 181 | break; 182 | } 183 | return url; 184 | } 185 | //return MakeRequest as factory handler 186 | return MakeRequest; 187 | }) 188 | 189 | /** 190 | * Hacked a service to display spinner on screen 191 | * I'm not proud of it, but it had to be done. 192 | * @param {[type]} CogConfig [description] 193 | * @return {[type]} [description] 194 | */ 195 | angular.module('angularCog').service("SpinnerService", function(CogConfig) { 196 | 197 | //html elements and styles 198 | var spinnerStyle = [ 199 | "position:absolute;", 200 | "top:50%;", 201 | "left:50%;", 202 | "z-index:2000" 203 | ].join(''); 204 | var spinner = angular.element("
"); 205 | spinner.attr("style", spinnerStyle) 206 | //spinner.find("img").attr("src", CogConfig.imagePath); 207 | 208 | var backdropStyle = ["position:fixed;", 209 | "top:0;", 210 | "left:0;", 211 | "width:100%;", 212 | "height:100%;", 213 | "background:white;", 214 | "text-align:center;", 215 | "opacity:0.7;" 216 | ].join(''); 217 | var backdrop = angular.element("
"); 218 | backdrop.attr("style", backdropStyle); 219 | 220 | //keep track of all requests 221 | //makes life easier when multiple ajax requests are fired from same view 222 | var requests = []; 223 | var isSpinning = false; 224 | return { 225 | //enqueue the request 226 | //append backdrop 227 | //append the spinner 228 | //if you dont spinner for a particular request set this to true 229 | spin: function() { 230 | requests.push(1); 231 | if (!isSpinning) { 232 | angular.element(document.body).append(backdrop); 233 | angular.element(document.body).append(spinner); 234 | isSpinning = true; 235 | } 236 | }, 237 | //dequeue the request 238 | //remove the backdrop 239 | //remove spinner 240 | stop: function() { 241 | requests.pop(); 242 | if (requests.length == 0) { 243 | backdrop.remove(); 244 | spinner.remove(); 245 | isSpinning = false; 246 | } 247 | } 248 | } 249 | }); 250 | 251 | /** 252 | * Configuration for angular-cog 253 | * @return {[type]} [description] 254 | */ 255 | angular.module('angularCog').provider('CogConfig', function() { 256 | this.rootUrl = ''; 257 | this.log = angular.noop; 258 | this.imagePath = "spinner.gif"; 259 | this.error = angular.noop; 260 | this.success = angular.noop; 261 | this.enableSpinner = true; 262 | var self = this; 263 | this.$get = function() { 264 | return self; 265 | } 266 | }); 267 | -------------------------------------------------------------------------------- /tests/angular-cog.tests.js: -------------------------------------------------------------------------------- 1 | describe('angular-cog directive', function() { 2 | 3 | // we declare some global vars to be used in the tests 4 | var elm, // our directive jqLite element 5 | scope, // the scope where our directive is inserted 6 | httpBackend; 7 | 8 | // load the modules we want to test 9 | beforeEach(module('angularCog')); 10 | 11 | // before each test, creates a new fresh scope 12 | // the inject function interest is to make use of the angularJS 13 | // dependency injection to get some other services in our test 14 | // here we need $rootScope to create a new scope 15 | beforeEach(inject(function($rootScope, $compile, $httpBackend) { 16 | scope = $rootScope.$new(); 17 | scope.success = function() { 18 | scope.done = true; 19 | } 20 | scope.error = function() { 21 | scope.notdone = true; 22 | } 23 | httpBackend = $httpBackend; 24 | httpBackend.when("GET", "/users").respond([{}, {}, {}]); 25 | httpBackend.when("GET", "/notusers").respond(404); 26 | })); 27 | 28 | afterEach(function() { 29 | httpBackend.verifyNoOutstandingExpectation(); 30 | httpBackend.verifyNoOutstandingRequest(); 31 | }); 32 | 33 | function compileDirective(tpl) { 34 | // function to compile a fresh directive with the given template, or a default one 35 | // compile the tpl with the $rootScope created above 36 | // wrap our directive inside a form to be able to test 37 | // that our form integration works well (via ngModelController) 38 | // our directive instance is then put in the global 'elm' variable for further tests 39 | var template = tpl || '
'; 40 | 41 | // inject allows you to use AngularJS dependency injection 42 | // to retrieve and use other services 43 | inject(function($compile) { 44 | $compile(template)(scope); 45 | }); 46 | // $digest is necessary to finalize the directive generation 47 | scope.$digest(); 48 | } 49 | 50 | // make successful request 51 | it('should make a get request to /users', function() { 52 | compileDirective('
'); 53 | httpBackend.flush(); 54 | expect(scope.done).toBe(true); 55 | }); 56 | 57 | // make successful request 58 | it('should make a failed get request to /users', function() { 59 | compileDirective('
'); 60 | httpBackend.flush(); 61 | expect(scope.notdone).toBe(true); 62 | }); 63 | }); -------------------------------------------------------------------------------- /why.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaymk/angular-cog/5020d2602712321a19432ae7cd3e5fd6381c9121/why.jpg --------------------------------------------------------------------------------