jquery.rest.custom.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. // jQuery REST Client - v1.0.2 - https://github.com/jpillora/jquery.rest
  2. // Jaime Pillora <dev@jpillora.com> - MIT Copyright 2014
  3. (function(window,$,undefined) {'use strict';
  4. var Cache, Resource, Verb, defaultOpts, deleteWarning, encode64, error, inheritExtend, s, stringify, validateOpts, validateStr;
  5. error = function(msg) {
  6. throw new Error("ERROR: jquery.rest: " + msg);
  7. };
  8. s = function(n) {
  9. var t;
  10. t = "";
  11. while (n-- > 0) {
  12. t += " ";
  13. }
  14. return t;
  15. };
  16. encode64 = function(s) {
  17. if (!window.btoa) {
  18. error("You need a polyfill for 'btoa' to use basic auth.");
  19. }
  20. return window.btoa(s);
  21. };
  22. stringify = function(obj) {
  23. if (!window.JSON) {
  24. error("You need a polyfill for 'JSON' to use stringify.");
  25. }
  26. return window.JSON.stringify(obj);
  27. };
  28. inheritExtend = function(a, b) {
  29. var F;
  30. F = function() {};
  31. F.prototype = a;
  32. return $.extend(true, new F(), b);
  33. };
  34. validateOpts = function(options) {
  35. if (!(options && $.isPlainObject(options))) {
  36. return false;
  37. }
  38. $.each(options, function(name) {
  39. if (defaultOpts[name] === undefined) {
  40. return error("Unknown option: '" + name + "'");
  41. }
  42. });
  43. return null;
  44. };
  45. validateStr = function(name, str) {
  46. if ('string' !== $.type(str)) {
  47. return error("'" + name + "' must be a string");
  48. }
  49. };
  50. deleteWarning = function() {
  51. return alert('"delete()" has been deprecated. Please use "destroy()" or "del()" instead.');
  52. };
  53. defaultOpts = {
  54. url: '',
  55. cache: 0,
  56. request: function(resource, options) {
  57. return $.ajax(options);
  58. },
  59. isSingle: false,
  60. autoClearCache: true,
  61. cachableMethods: ['GET'],
  62. methodOverride: false,
  63. stringifyData: false,
  64. stripTrailingSlash: true,
  65. password: null,
  66. username: null,
  67. token: null,
  68. verbs: {
  69. 'create': 'POST',
  70. 'read': 'GET',
  71. 'update': 'PUT',
  72. 'destroy': 'DELETE'
  73. },
  74. ajax: {
  75. dataType: 'json'
  76. }
  77. };
  78. Cache = (function() {
  79. function Cache(parent) {
  80. this.parent = parent;
  81. this.c = {};
  82. }
  83. Cache.prototype.valid = function(date) {
  84. var diff;
  85. diff = new Date().getTime() - date.getTime();
  86. return diff <= this.parent.opts.cache * 1000;
  87. };
  88. Cache.prototype.key = function(obj) {
  89. var key,
  90. _this = this;
  91. key = "";
  92. $.each(obj, function(k, v) {
  93. return key += k + "=" + ($.isPlainObject(v) ? "{" + _this.key(v) + "}" : v) + "|";
  94. });
  95. return key;
  96. };
  97. Cache.prototype.get = function(key) {
  98. var result;
  99. result = this.c[key];
  100. if (!result) {
  101. return;
  102. }
  103. if (this.valid(result.created)) {
  104. return result.data;
  105. }
  106. };
  107. Cache.prototype.put = function(key, data) {
  108. return this.c[key] = {
  109. created: new Date(),
  110. data: data
  111. };
  112. };
  113. Cache.prototype.clear = function(regexp) {
  114. var _this = this;
  115. if (regexp) {
  116. return $.each(this.c, function(k) {
  117. if (k.match(regexp)) {
  118. return delete _this.c[k];
  119. }
  120. });
  121. } else {
  122. return this.c = {};
  123. }
  124. };
  125. return Cache;
  126. })();
  127. Verb = (function() {
  128. function Verb(name, method, options, parent) {
  129. this.name = name;
  130. this.method = method;
  131. if (options == null) {
  132. options = {};
  133. }
  134. this.parent = parent;
  135. validateStr('name', this.name);
  136. validateStr('method', this.method);
  137. validateOpts(options);
  138. if (this.parent[this.name]) {
  139. error("Cannot add Verb: '" + name + "' already exists");
  140. }
  141. this.method = method.toUpperCase();
  142. if (!options.url) {
  143. options.url = '';
  144. }
  145. this.opts = inheritExtend(this.parent.opts, options);
  146. this.root = this.parent.root;
  147. this.custom = !defaultOpts.verbs[this.name];
  148. this.call = $.proxy(this.call, this);
  149. this.call.instance = this;
  150. }
  151. Verb.prototype.call = function() {
  152. var data, url, _ref;
  153. _ref = this.parent.extractUrlData(this.method, arguments), url = _ref.url, data = _ref.data;
  154. if (this.custom) {
  155. url += this.opts.url || this.name;
  156. }
  157. return this.parent.ajax.call(this, this.method, url, data);
  158. };
  159. Verb.prototype.show = function(d) {
  160. return console.log(s(d) + this.name + ": " + this.method);
  161. };
  162. return Verb;
  163. })();
  164. Resource = (function() {
  165. function Resource(nameOrUrl, options, parent) {
  166. if (options == null) {
  167. options = {};
  168. }
  169. validateOpts(options);
  170. if (parent && parent instanceof Resource) {
  171. this.name = nameOrUrl;
  172. validateStr('name', this.name);
  173. this.constructChild(parent, options);
  174. } else {
  175. this.url = nameOrUrl || '';
  176. validateStr('url', this.url);
  177. this.constructRoot(options);
  178. }
  179. }
  180. Resource.prototype.constructRoot = function(options) {
  181. this.opts = inheritExtend(defaultOpts, options);
  182. this.root = this;
  183. this.expectedIds = 0;
  184. this.urlNoId = this.url;
  185. this.cache = new Cache(this);
  186. this.parent = null;
  187. return this.name = this.opts.name || 'ROOT';
  188. };
  189. Resource.prototype.constructChild = function(parent, options) {
  190. this.parent = parent;
  191. validateStr('name', this.name);
  192. if (!(this.parent instanceof Resource)) {
  193. this.error("Invalid parent");
  194. }
  195. if (this.parent[this.name]) {
  196. this.error("'" + name + "' already exists");
  197. }
  198. if (!options.url) {
  199. options.url = '';
  200. }
  201. this.opts = inheritExtend(this.parent.opts, options);
  202. this.opts.isSingle = 'isSingle' in options && options.isSingle;
  203. this.root = this.parent.root;
  204. this.urlNoId = this.parent.url + ("" + (this.opts.url || this.name) + "/");
  205. this.url = this.urlNoId;
  206. this.expectedIds = this.parent.expectedIds;
  207. if (!this.opts.isSingle) {
  208. this.expectedIds += 1;
  209. this.url += ":ID_" + this.expectedIds + "/";
  210. }
  211. $.each(this.opts.verbs, $.proxy(this.addVerb, this));
  212. if (this.destroy) {
  213. this.del = this.destroy;
  214. return this["delete"] = deleteWarning;
  215. }
  216. };
  217. Resource.prototype.error = function(msg) {
  218. return error("Cannot add Resource: " + msg);
  219. };
  220. Resource.prototype.add = function(name, options) {
  221. return this[name] = new Resource(name, options, this);
  222. };
  223. Resource.prototype.addVerb = function(name, method, options) {
  224. return this[name] = new Verb(name, method, options, this).call;
  225. };
  226. Resource.prototype.show = function(d) {
  227. if (d == null) {
  228. d = 0;
  229. }
  230. if (d > 25) {
  231. error("Plugin Bug! Recursion Fail");
  232. }
  233. if (this.name) {
  234. console.log(s(d) + this.name + ": " + this.url);
  235. }
  236. $.each(this, function(name, fn) {
  237. if ($.type(fn) === 'function' && fn.instance instanceof Verb && name !== 'del') {
  238. return fn.instance.show(d + 1);
  239. }
  240. });
  241. $.each(this, function(name, res) {
  242. if (name !== "parent" && name !== "root" && res instanceof Resource) {
  243. return res.show(d + 1);
  244. }
  245. });
  246. return null;
  247. };
  248. Resource.prototype.toString = function() {
  249. return this.name;
  250. };
  251. Resource.prototype.extractUrlData = function(name, args) {
  252. var arg, canUrl, canUrlNoId, data, i, id, ids, msg, params, providedIds, t, url, _i, _j, _len, _len1;
  253. ids = [];
  254. data = null;
  255. params = null;
  256. for (_i = 0, _len = args.length; _i < _len; _i++) {
  257. arg = args[_i];
  258. t = $.type(arg);
  259. if (t === 'string' || t === 'number') {
  260. ids.push(arg);
  261. } else if (t === 'object' && data === null) {
  262. data = arg;
  263. } else if (t === 'object' && params === null) {
  264. params = arg;
  265. } else {
  266. error(("Invalid argument: " + arg + " (" + t + ").") + " Must be strings or ints (IDs) followed by one optional object and one optional query params object.");
  267. }
  268. }
  269. providedIds = ids.length;
  270. canUrl = name !== 'create';
  271. canUrlNoId = name !== 'update' && name !== 'delete';
  272. url = null;
  273. if (canUrl && providedIds === this.expectedIds) {
  274. url = this.url;
  275. }
  276. if (canUrlNoId && providedIds === this.expectedIds - 1) {
  277. url = this.urlNoId;
  278. }
  279. if (url === null) {
  280. if (canUrlNoId) {
  281. msg = this.expectedIds - 1;
  282. }
  283. if (canUrl) {
  284. msg = (msg ? msg + ' or ' : '') + this.expectedIds;
  285. }
  286. error("Invalid number of ID arguments, required " + msg + ", provided " + providedIds);
  287. }
  288. for (i = _j = 0, _len1 = ids.length; _j < _len1; i = ++_j) {
  289. id = ids[i];
  290. url = url.replace(new RegExp("\/:ID_" + (i + 1) + "\/"), "/" + id + "/");
  291. }
  292. if (params) {
  293. url += "?" + ($.param(params));
  294. }
  295. return {
  296. url: url,
  297. data: data
  298. };
  299. };
  300. Resource.prototype.ajax = function(method, url, data) {
  301. var ajaxOpts, encoded, escapedUrl, headers, key, req, useCache,
  302. _this = this;
  303. if (!method) {
  304. error("method missing");
  305. }
  306. if (!url) {
  307. error("url missing");
  308. }
  309. headers = {};
  310. if (this.opts.username && this.opts.password) {
  311. encoded = encode64(this.opts.username + ":" + this.opts.password);
  312. headers['Authorization'] = "Basic " + encoded;
  313. }
  314. if (this.opts.token) {
  315. headers['Authorization'] = "Bearer " + this.opts.token;
  316. }
  317. if (data && this.opts.stringifyData && (method !== 'GET' && method !== 'HEAD')) {
  318. data = stringify(data);
  319. headers['Content-Type'] = "application/json";
  320. }
  321. if (this.opts.methodOverride && (method !== 'GET' && method !== 'HEAD' && method !== 'POST')) {
  322. headers['X-HTTP-Method-Override'] = method;
  323. method = 'POST';
  324. }
  325. if (this.opts.stripTrailingSlash) {
  326. url = url.replace(/\/$/, "");
  327. }
  328. ajaxOpts = {
  329. url: url,
  330. type: method,
  331. headers: headers
  332. };
  333. if (data) {
  334. ajaxOpts.data = data;
  335. }
  336. ajaxOpts = $.extend(true, {}, this.opts.ajax, ajaxOpts);
  337. useCache = this.opts.cache && $.inArray(method, this.opts.cachableMethods) >= 0;
  338. if (useCache) {
  339. key = this.root.cache.key(ajaxOpts);
  340. req = this.root.cache.get(key);
  341. if (req) {
  342. return req;
  343. }
  344. }
  345. if (this.opts.cache && this.opts.autoClearCache && $.inArray(method, this.opts.cachableMethods) === -1) {
  346. escapedUrl = url.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
  347. this.root.cache.clear(new RegExp(escapedUrl));
  348. }
  349. req = this.opts.request(this.parent, ajaxOpts);
  350. if (useCache) {
  351. req.done(function() {
  352. return _this.root.cache.put(key, req);
  353. });
  354. }
  355. return req;
  356. };
  357. return Resource;
  358. })();
  359. Resource.defaults = defaultOpts;
  360. $.RestClient = Resource;
  361. }.call(this,window,jQuery));