angular-rails-engine 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ # See http://help.github.com/ignore-files/ for more about ignoring files.
2
+ #
3
+ # If you find yourself ignoring temporary files generated by your text editor
4
+ # or operating system, you probably want to add a global ignore instead:
5
+ # git config --global core.excludesfile ~/.gitignore_global
6
+
7
+ # Ignore bundler config
8
+ /.bundle
9
+
10
+ # Ignore the default SQLite database.
11
+ /db/*.sqlite3
12
+
13
+ # Ignore all logfiles and tempfiles.
14
+ /log/*.log
15
+ /tmp
16
+ /public/assets/
17
+ /config/secret_settings.yml
18
+
19
+ *.log
20
+ .DS_Store
21
+ .sass-cache/
22
+ *.bak
23
+ *~
24
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in bootstrap-rails-engine.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2012 Yen-Ju Chen
2
+ Copyright (c) 2012 Kenn Ejima (jquery-rails-cdn)
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # Angular.js for Rails 3
2
+ Make [Angular.js](http://angularjs.org) into Rails Engine.
3
+
4
+ ## Version
5
+ Angular.js 1.0.2
6
+
7
+ ## Rails 3.1 or later
8
+ Include Gemfile,
9
+
10
+ gem 'angular-rails-engine'
11
+
12
+ Add javascripts into application.js
13
+
14
+ //= require angular/angular
15
+ //= require angular/angular-resource
16
+
17
+ ## CDN
18
+
19
+ This gem supports cdn the same as [jquery-rails-cdn](https://github.com/yjchen/jquery-rails-cdn). In the application layout, add
20
+
21
+ = angular_include_tag :default
22
+
23
+ and remove corresponding lines in application.js.
24
+
25
+ ## License
26
+
27
+ Copyright (c) 2012 Yen-Ju Chen
28
+
29
+ MIT License
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining
32
+ a copy of this software and associated documentation files (the
33
+ "Software"), to deal in the Software without restriction, including
34
+ without limitation the rights to use, copy, modify, merge, publish,
35
+ distribute, sublicense, and/or sell copies of the Software, and to
36
+ permit persons to whom the Software is furnished to do so, subject to
37
+ the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be
40
+ included in all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
43
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
45
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
46
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
47
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
48
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'bundler/gem_tasks'
4
+ Bundler::GemHelper.install_tasks
5
+
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/angular-rails-engine/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "angular-rails-engine"
6
+ gem.version = AngularRailsEngine::Rails::VERSION
7
+ gem.platform = Gem::Platform::RUBY
8
+ gem.authors = ["Yen-Ju Chen"]
9
+ gem.email = ["yjchenx@gmail.com"]
10
+ gem.homepage = "https://github.com/yjchen/angular-rails-engine"
11
+ gem.description = %q{Angular.js for Rails 3}
12
+ gem.summary = gem.description
13
+
14
+ gem.files = `git ls-files`.split("\n")
15
+ gem.require_paths = ["lib"]
16
+
17
+ gem.add_dependency "railties", ">= 3.0"
18
+ gem.add_development_dependency "bundler", ">= 1.0"
19
+ gem.add_development_dependency "rake"
20
+ end
21
+
@@ -0,0 +1,34 @@
1
+ module AngularRailsEngine
2
+ module ActionViewExtensions
3
+ OFFLINE = (::Rails.env.development? or ::Rails.env.test?)
4
+
5
+ CDNS = {
6
+ :angular_js => {
7
+ :default => "//ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"
8
+ }
9
+ }
10
+
11
+ def angular_js_url(name, options = {})
12
+ return CDNS[:angular_js][name]
13
+ end
14
+
15
+ def angular_js_include_tag(name, options = {})
16
+ if OFFLINE and !options[:force]
17
+ return javascript_include_tag('angular/angular')
18
+ else
19
+ [ javascript_include_tag(angular_js_url(name, options)),
20
+ javascript_tag("window.angular || document.write(unescape('#{javascript_include_tag('angular/angular').gsub('<','%3C')}'))")
21
+ ].join("\n").html_safe
22
+ end
23
+ end
24
+ end
25
+
26
+
27
+ class Engine < ::Rails::Engine
28
+ initializer 'angular-rails-engine.action_view' do |app|
29
+ ActiveSupport.on_load(:action_view) do
30
+ include AngularRailsEngine::ActionViewExtensions
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module AngularRailsEngine
2
+ module Rails
3
+ VERSION = "0.9.0"
4
+ end
5
+ end
@@ -0,0 +1,171 @@
1
+ /**
2
+ * @license AngularJS v1.0.2
3
+ * (c) 2010-2012 Google, Inc. http://angularjs.org
4
+ * License: MIT
5
+ */
6
+ (function(window, angular, undefined) {
7
+ 'use strict';
8
+
9
+ /**
10
+ * @ngdoc overview
11
+ * @name ngCookies
12
+ */
13
+
14
+
15
+ angular.module('ngCookies', ['ng']).
16
+ /**
17
+ * @ngdoc object
18
+ * @name ngCookies.$cookies
19
+ * @requires $browser
20
+ *
21
+ * @description
22
+ * Provides read/write access to browser's cookies.
23
+ *
24
+ * Only a simple Object is exposed and by adding or removing properties to/from
25
+ * this object, new cookies are created/deleted at the end of current $eval.
26
+ *
27
+ * @example
28
+ */
29
+ factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
30
+ var cookies = {},
31
+ lastCookies = {},
32
+ lastBrowserCookies,
33
+ runEval = false,
34
+ copy = angular.copy,
35
+ isUndefined = angular.isUndefined;
36
+
37
+ //creates a poller fn that copies all cookies from the $browser to service & inits the service
38
+ $browser.addPollFn(function() {
39
+ var currentCookies = $browser.cookies();
40
+ if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
41
+ lastBrowserCookies = currentCookies;
42
+ copy(currentCookies, lastCookies);
43
+ copy(currentCookies, cookies);
44
+ if (runEval) $rootScope.$apply();
45
+ }
46
+ })();
47
+
48
+ runEval = true;
49
+
50
+ //at the end of each eval, push cookies
51
+ //TODO: this should happen before the "delayed" watches fire, because if some cookies are not
52
+ // strings or browser refuses to store some cookies, we update the model in the push fn.
53
+ $rootScope.$watch(push);
54
+
55
+ return cookies;
56
+
57
+
58
+ /**
59
+ * Pushes all the cookies from the service to the browser and verifies if all cookies were stored.
60
+ */
61
+ function push() {
62
+ var name,
63
+ value,
64
+ browserCookies,
65
+ updated;
66
+
67
+ //delete any cookies deleted in $cookies
68
+ for (name in lastCookies) {
69
+ if (isUndefined(cookies[name])) {
70
+ $browser.cookies(name, undefined);
71
+ }
72
+ }
73
+
74
+ //update all cookies updated in $cookies
75
+ for(name in cookies) {
76
+ value = cookies[name];
77
+ if (!angular.isString(value)) {
78
+ if (angular.isDefined(lastCookies[name])) {
79
+ cookies[name] = lastCookies[name];
80
+ } else {
81
+ delete cookies[name];
82
+ }
83
+ } else if (value !== lastCookies[name]) {
84
+ $browser.cookies(name, value);
85
+ updated = true;
86
+ }
87
+ }
88
+
89
+ //verify what was actually stored
90
+ if (updated){
91
+ updated = false;
92
+ browserCookies = $browser.cookies();
93
+
94
+ for (name in cookies) {
95
+ if (cookies[name] !== browserCookies[name]) {
96
+ //delete or reset all cookies that the browser dropped from $cookies
97
+ if (isUndefined(browserCookies[name])) {
98
+ delete cookies[name];
99
+ } else {
100
+ cookies[name] = browserCookies[name];
101
+ }
102
+ updated = true;
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }]).
108
+
109
+
110
+ /**
111
+ * @ngdoc object
112
+ * @name ngCookies.$cookieStore
113
+ * @requires $cookies
114
+ *
115
+ * @description
116
+ * Provides a key-value (string-object) storage, that is backed by session cookies.
117
+ * Objects put or retrieved from this storage are automatically serialized or
118
+ * deserialized by angular's toJson/fromJson.
119
+ * @example
120
+ */
121
+ factory('$cookieStore', ['$cookies', function($cookies) {
122
+
123
+ return {
124
+ /**
125
+ * @ngdoc method
126
+ * @name ngCookies.$cookieStore#get
127
+ * @methodOf ngCookies.$cookieStore
128
+ *
129
+ * @description
130
+ * Returns the value of given cookie key
131
+ *
132
+ * @param {string} key Id to use for lookup.
133
+ * @returns {Object} Deserialized cookie value.
134
+ */
135
+ get: function(key) {
136
+ return angular.fromJson($cookies[key]);
137
+ },
138
+
139
+ /**
140
+ * @ngdoc method
141
+ * @name ngCookies.$cookieStore#put
142
+ * @methodOf ngCookies.$cookieStore
143
+ *
144
+ * @description
145
+ * Sets a value for given cookie key
146
+ *
147
+ * @param {string} key Id for the `value`.
148
+ * @param {Object} value Value to be stored.
149
+ */
150
+ put: function(key, value) {
151
+ $cookies[key] = angular.toJson(value);
152
+ },
153
+
154
+ /**
155
+ * @ngdoc method
156
+ * @name ngCookies.$cookieStore#remove
157
+ * @methodOf ngCookies.$cookieStore
158
+ *
159
+ * @description
160
+ * Remove given cookie
161
+ *
162
+ * @param {string} key Id of the key-value pair to delete.
163
+ */
164
+ remove: function(key) {
165
+ delete $cookies[key];
166
+ }
167
+ };
168
+
169
+ }]);
170
+
171
+ })(window, window.angular);
@@ -0,0 +1,7 @@
1
+ /*
2
+ AngularJS v1.0.2
3
+ (c) 2010-2012 Google, Inc. http://angularjs.org
4
+ License: MIT
5
+ */
6
+ (function(m,f,l){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,c){var b={},g={},h,i=!1,j=f.copy,k=f.isUndefined;c.addPollFn(function(){var a=c.cookies();h!=a&&(h=a,j(a,g),j(a,b),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(b[a])&&c.cookies(a,l);for(a in b)e=b[a],f.isString(e)?e!==g[a]&&(c.cookies(a,e),d=!0):f.isDefined(g[a])?b[a]=g[a]:delete b[a];if(d)for(a in e=c.cookies(),b)b[a]!==e[a]&&(k(e[a])?delete b[a]:b[a]=e[a])});return b}]).factory("$cookieStore",
7
+ ["$cookies",function(d){return{get:function(c){return f.fromJson(d[c])},put:function(c,b){d[c]=f.toJson(b)},remove:function(c){delete d[c]}}}])})(window,window.angular);
@@ -0,0 +1,428 @@
1
+ /**
2
+ * @license AngularJS v1.0.2
3
+ * (c) 2010-2012 Google, Inc. http://angularjs.org
4
+ * License: MIT
5
+ */
6
+ (function(window, angular, undefined) {
7
+ 'use strict';
8
+
9
+ /**
10
+ * @ngdoc overview
11
+ * @name ngResource
12
+ * @description
13
+ */
14
+
15
+ /**
16
+ * @ngdoc object
17
+ * @name ngResource.$resource
18
+ * @requires $http
19
+ *
20
+ * @description
21
+ * A factory which creates a resource object that lets you interact with
22
+ * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
23
+ *
24
+ * The returned resource object has action methods which provide high-level behaviors without
25
+ * the need to interact with the low level {@link ng.$http $http} service.
26
+ *
27
+ * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
28
+ * `/user/:username`.
29
+ *
30
+ * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
31
+ * `actions` methods.
32
+ *
33
+ * Each key value in the parameter object is first bound to url template if present and then any
34
+ * excess keys are appended to the url search query after the `?`.
35
+ *
36
+ * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
37
+ * URL `/path/greet?salutation=Hello`.
38
+ *
39
+ * If the parameter value is prefixed with `@` then the value of that parameter is extracted from
40
+ * the data object (useful for non-GET operations).
41
+ *
42
+ * @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
43
+ * default set of resource actions. The declaration should be created in the following format:
44
+ *
45
+ * {action1: {method:?, params:?, isArray:?},
46
+ * action2: {method:?, params:?, isArray:?},
47
+ * ...}
48
+ *
49
+ * Where:
50
+ *
51
+ * - `action` – {string} – The name of action. This name becomes the name of the method on your
52
+ * resource object.
53
+ * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
54
+ * and `JSONP`
55
+ * - `params` – {object=} – Optional set of pre-bound parameters for this action.
56
+ * - isArray – {boolean=} – If true then the returned object for this action is an array, see
57
+ * `returns` section.
58
+ *
59
+ * @returns {Object} A resource "class" object with methods for the default set of resource actions
60
+ * optionally extended with custom `actions`. The default set contains these actions:
61
+ *
62
+ * { 'get': {method:'GET'},
63
+ * 'save': {method:'POST'},
64
+ * 'query': {method:'GET', isArray:true},
65
+ * 'remove': {method:'DELETE'},
66
+ * 'delete': {method:'DELETE'} };
67
+ *
68
+ * Calling these methods invoke an {@link ng.$http} with the specified http method,
69
+ * destination and parameters. When the data is returned from the server then the object is an
70
+ * instance of the resource class `save`, `remove` and `delete` actions are available on it as
71
+ * methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read,
72
+ * update, delete) on server-side data like this:
73
+ * <pre>
74
+ var User = $resource('/user/:userId', {userId:'@id'});
75
+ var user = User.get({userId:123}, function() {
76
+ user.abc = true;
77
+ user.$save();
78
+ });
79
+ </pre>
80
+ *
81
+ * It is important to realize that invoking a $resource object method immediately returns an
82
+ * empty reference (object or array depending on `isArray`). Once the data is returned from the
83
+ * server the existing reference is populated with the actual data. This is a useful trick since
84
+ * usually the resource is assigned to a model which is then rendered by the view. Having an empty
85
+ * object results in no rendering, once the data arrives from the server then the object is
86
+ * populated with the data and the view automatically re-renders itself showing the new data. This
87
+ * means that in most case one never has to write a callback function for the action methods.
88
+ *
89
+ * The action methods on the class object or instance object can be invoked with the following
90
+ * parameters:
91
+ *
92
+ * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
93
+ * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
94
+ * - non-GET instance actions: `instance.$action([parameters], [success], [error])`
95
+ *
96
+ *
97
+ * @example
98
+ *
99
+ * # Credit card resource
100
+ *
101
+ * <pre>
102
+ // Define CreditCard class
103
+ var CreditCard = $resource('/user/:userId/card/:cardId',
104
+ {userId:123, cardId:'@id'}, {
105
+ charge: {method:'POST', params:{charge:true}}
106
+ });
107
+
108
+ // We can retrieve a collection from the server
109
+ var cards = CreditCard.query(function() {
110
+ // GET: /user/123/card
111
+ // server returns: [ {id:456, number:'1234', name:'Smith'} ];
112
+
113
+ var card = cards[0];
114
+ // each item is an instance of CreditCard
115
+ expect(card instanceof CreditCard).toEqual(true);
116
+ card.name = "J. Smith";
117
+ // non GET methods are mapped onto the instances
118
+ card.$save();
119
+ // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
120
+ // server returns: {id:456, number:'1234', name: 'J. Smith'};
121
+
122
+ // our custom method is mapped as well.
123
+ card.$charge({amount:9.99});
124
+ // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
125
+ });
126
+
127
+ // we can create an instance as well
128
+ var newCard = new CreditCard({number:'0123'});
129
+ newCard.name = "Mike Smith";
130
+ newCard.$save();
131
+ // POST: /user/123/card {number:'0123', name:'Mike Smith'}
132
+ // server returns: {id:789, number:'01234', name: 'Mike Smith'};
133
+ expect(newCard.id).toEqual(789);
134
+ * </pre>
135
+ *
136
+ * The object returned from this function execution is a resource "class" which has "static" method
137
+ * for each action in the definition.
138
+ *
139
+ * Calling these methods invoke `$http` on the `url` template with the given `method` and `params`.
140
+ * When the data is returned from the server then the object is an instance of the resource type and
141
+ * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
142
+ * operations (create, read, update, delete) on server-side data.
143
+
144
+ <pre>
145
+ var User = $resource('/user/:userId', {userId:'@id'});
146
+ var user = User.get({userId:123}, function() {
147
+ user.abc = true;
148
+ user.$save();
149
+ });
150
+ </pre>
151
+ *
152
+ * It's worth noting that the success callback for `get`, `query` and other method gets passed
153
+ * in the response that came from the server as well as $http header getter function, so one
154
+ * could rewrite the above example and get access to http headers as:
155
+ *
156
+ <pre>
157
+ var User = $resource('/user/:userId', {userId:'@id'});
158
+ User.get({userId:123}, function(u, getResponseHeaders){
159
+ u.abc = true;
160
+ u.$save(function(u, putResponseHeaders) {
161
+ //u => saved user object
162
+ //putResponseHeaders => $http header getter
163
+ });
164
+ });
165
+ </pre>
166
+
167
+ * # Buzz client
168
+
169
+ Let's look at what a buzz client created with the `$resource` service looks like:
170
+ <doc:example>
171
+ <doc:source jsfiddle="false">
172
+ <script>
173
+ function BuzzController($resource) {
174
+ this.userId = 'googlebuzz';
175
+ this.Activity = $resource(
176
+ 'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
177
+ {alt:'json', callback:'JSON_CALLBACK'},
178
+ {get:{method:'JSONP', params:{visibility:'@self'}}, replies: {method:'JSONP', params:{visibility:'@self', comments:'@comments'}}}
179
+ );
180
+ }
181
+
182
+ BuzzController.prototype = {
183
+ fetch: function() {
184
+ this.activities = this.Activity.get({userId:this.userId});
185
+ },
186
+ expandReplies: function(activity) {
187
+ activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
188
+ }
189
+ };
190
+ BuzzController.$inject = ['$resource'];
191
+ </script>
192
+
193
+ <div ng-controller="BuzzController">
194
+ <input ng-model="userId"/>
195
+ <button ng-click="fetch()">fetch</button>
196
+ <hr/>
197
+ <div ng-repeat="item in activities.data.items">
198
+ <h1 style="font-size: 15px;">
199
+ <img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
200
+ <a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
201
+ <a href ng-click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a>
202
+ </h1>
203
+ {{item.object.content | html}}
204
+ <div ng-repeat="reply in item.replies.data.items" style="margin-left: 20px;">
205
+ <img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
206
+ <a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
207
+ </div>
208
+ </div>
209
+ </div>
210
+ </doc:source>
211
+ <doc:scenario>
212
+ </doc:scenario>
213
+ </doc:example>
214
+ */
215
+ angular.module('ngResource', ['ng']).
216
+ factory('$resource', ['$http', '$parse', function($http, $parse) {
217
+ var DEFAULT_ACTIONS = {
218
+ 'get': {method:'GET'},
219
+ 'save': {method:'POST'},
220
+ 'query': {method:'GET', isArray:true},
221
+ 'remove': {method:'DELETE'},
222
+ 'delete': {method:'DELETE'}
223
+ };
224
+ var noop = angular.noop,
225
+ forEach = angular.forEach,
226
+ extend = angular.extend,
227
+ copy = angular.copy,
228
+ isFunction = angular.isFunction,
229
+ getter = function(obj, path) {
230
+ return $parse(path)(obj);
231
+ };
232
+
233
+ /**
234
+ * We need our custom mehtod because encodeURIComponent is too agressive and doesn't follow
235
+ * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
236
+ * segments:
237
+ * segment = *pchar
238
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
239
+ * pct-encoded = "%" HEXDIG HEXDIG
240
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
241
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
242
+ * / "*" / "+" / "," / ";" / "="
243
+ */
244
+ function encodeUriSegment(val) {
245
+ return encodeUriQuery(val, true).
246
+ replace(/%26/gi, '&').
247
+ replace(/%3D/gi, '=').
248
+ replace(/%2B/gi, '+');
249
+ }
250
+
251
+
252
+ /**
253
+ * This method is intended for encoding *key* or *value* parts of query component. We need a custom
254
+ * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
255
+ * encoded per http://tools.ietf.org/html/rfc3986:
256
+ * query = *( pchar / "/" / "?" )
257
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
258
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
259
+ * pct-encoded = "%" HEXDIG HEXDIG
260
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
261
+ * / "*" / "+" / "," / ";" / "="
262
+ */
263
+ function encodeUriQuery(val, pctEncodeSpaces) {
264
+ return encodeURIComponent(val).
265
+ replace(/%40/gi, '@').
266
+ replace(/%3A/gi, ':').
267
+ replace(/%24/g, '$').
268
+ replace(/%2C/gi, ',').
269
+ replace((pctEncodeSpaces ? null : /%20/g), '+');
270
+ }
271
+
272
+ function Route(template, defaults) {
273
+ this.template = template = template + '#';
274
+ this.defaults = defaults || {};
275
+ var urlParams = this.urlParams = {};
276
+ forEach(template.split(/\W/), function(param){
277
+ if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) {
278
+ urlParams[param] = true;
279
+ }
280
+ });
281
+ this.template = template.replace(/\\:/g, ':');
282
+ }
283
+
284
+ Route.prototype = {
285
+ url: function(params) {
286
+ var self = this,
287
+ url = this.template,
288
+ encodedVal;
289
+
290
+ params = params || {};
291
+ forEach(this.urlParams, function(_, urlParam){
292
+ encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || "");
293
+ url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1");
294
+ });
295
+ url = url.replace(/\/?#$/, '');
296
+ var query = [];
297
+ forEach(params, function(value, key){
298
+ if (!self.urlParams[key]) {
299
+ query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value));
300
+ }
301
+ });
302
+ query.sort();
303
+ url = url.replace(/\/*$/, '');
304
+ return url + (query.length ? '?' + query.join('&') : '');
305
+ }
306
+ };
307
+
308
+
309
+ function ResourceFactory(url, paramDefaults, actions) {
310
+ var route = new Route(url);
311
+
312
+ actions = extend({}, DEFAULT_ACTIONS, actions);
313
+
314
+ function extractParams(data){
315
+ var ids = {};
316
+ forEach(paramDefaults || {}, function(value, key){
317
+ ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
318
+ });
319
+ return ids;
320
+ }
321
+
322
+ function Resource(value){
323
+ copy(value || {}, this);
324
+ }
325
+
326
+ forEach(actions, function(action, name) {
327
+ var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH';
328
+ Resource[name] = function(a1, a2, a3, a4) {
329
+ var params = {};
330
+ var data;
331
+ var success = noop;
332
+ var error = null;
333
+ switch(arguments.length) {
334
+ case 4:
335
+ error = a4;
336
+ success = a3;
337
+ //fallthrough
338
+ case 3:
339
+ case 2:
340
+ if (isFunction(a2)) {
341
+ if (isFunction(a1)) {
342
+ success = a1;
343
+ error = a2;
344
+ break;
345
+ }
346
+
347
+ success = a2;
348
+ error = a3;
349
+ //fallthrough
350
+ } else {
351
+ params = a1;
352
+ data = a2;
353
+ success = a3;
354
+ break;
355
+ }
356
+ case 1:
357
+ if (isFunction(a1)) success = a1;
358
+ else if (hasBody) data = a1;
359
+ else params = a1;
360
+ break;
361
+ case 0: break;
362
+ default:
363
+ throw "Expected between 0-4 arguments [params, data, success, error], got " +
364
+ arguments.length + " arguments.";
365
+ }
366
+
367
+ var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
368
+ $http({
369
+ method: action.method,
370
+ url: route.url(extend({}, extractParams(data), action.params || {}, params)),
371
+ data: data
372
+ }).then(function(response) {
373
+ var data = response.data;
374
+
375
+ if (data) {
376
+ if (action.isArray) {
377
+ value.length = 0;
378
+ forEach(data, function(item) {
379
+ value.push(new Resource(item));
380
+ });
381
+ } else {
382
+ copy(data, value);
383
+ }
384
+ }
385
+ (success||noop)(value, response.headers);
386
+ }, error);
387
+
388
+ return value;
389
+ };
390
+
391
+
392
+ Resource.bind = function(additionalParamDefaults){
393
+ return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
394
+ };
395
+
396
+
397
+ Resource.prototype['$' + name] = function(a1, a2, a3) {
398
+ var params = extractParams(this),
399
+ success = noop,
400
+ error;
401
+
402
+ switch(arguments.length) {
403
+ case 3: params = a1; success = a2; error = a3; break;
404
+ case 2:
405
+ case 1:
406
+ if (isFunction(a1)) {
407
+ success = a1;
408
+ error = a2;
409
+ } else {
410
+ params = a1;
411
+ success = a2 || noop;
412
+ }
413
+ case 0: break;
414
+ default:
415
+ throw "Expected between 1-3 arguments [params, success, error], got " +
416
+ arguments.length + " arguments.";
417
+ }
418
+ var data = hasBody ? this : undefined;
419
+ Resource[name].call(this, params, data, success, error);
420
+ };
421
+ });
422
+ return Resource;
423
+ }
424
+
425
+ return ResourceFactory;
426
+ }]);
427
+
428
+ })(window, window.angular);