PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/rel/overlay/share/www/script/jquery.couch.js

http://github.com/cloudant/bigcouch
JavaScript | 705 lines | 656 code | 28 blank | 21 comment | 141 complexity | e5f604afbc38c0231d0f01e5f8aa06a6 MD5 | raw file
Possible License(s): Apache-2.0
  1. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  2. // use this file except in compliance with the License. You may obtain a copy of
  3. // the License at
  4. //
  5. // http://www.apache.org/licenses/LICENSE-2.0
  6. //
  7. // Unless required by applicable law or agreed to in writing, software
  8. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. // License for the specific language governing permissions and limitations under
  11. // the License.
  12. (function($) {
  13. $.couch = $.couch || {};
  14. function encodeDocId(docID) {
  15. var parts = docID.split("/");
  16. if (parts[0] == "_design") {
  17. parts.shift();
  18. return "_design/" + encodeURIComponent(parts.join('/'));
  19. }
  20. return encodeURIComponent(docID);
  21. };
  22. var uuidCache = [];
  23. $.extend($.couch, {
  24. urlPrefix: '',
  25. activeTasks: function(options) {
  26. ajax(
  27. {url: this.urlPrefix + "/_active_tasks"},
  28. options,
  29. "Active task status could not be retrieved"
  30. );
  31. },
  32. allDbs: function(options) {
  33. ajax(
  34. {url: this.urlPrefix + "/_all_dbs"},
  35. options,
  36. "An error occurred retrieving the list of all databases"
  37. );
  38. },
  39. config: function(options, section, option, value) {
  40. var req = {url: this.urlPrefix + "/_config/"};
  41. if (section) {
  42. req.url += encodeURIComponent(section) + "/";
  43. if (option) {
  44. req.url += encodeURIComponent(option);
  45. }
  46. }
  47. if (value === null) {
  48. req.type = "DELETE";
  49. } else if (value !== undefined) {
  50. req.type = "PUT";
  51. req.data = toJSON(value);
  52. req.contentType = "application/json";
  53. req.processData = false;
  54. }
  55. ajax(req, options,
  56. "An error occurred retrieving/updating the server configuration"
  57. );
  58. },
  59. session: function(options) {
  60. options = options || {};
  61. $.ajax({
  62. type: "GET", url: this.urlPrefix + "/_session",
  63. beforeSend: function(xhr) {
  64. xhr.setRequestHeader('Accept', 'application/json');
  65. },
  66. complete: function(req) {
  67. var resp = httpData(req, "json");
  68. if (req.status == 200) {
  69. if (options.success) options.success(resp);
  70. } else if (options.error) {
  71. options.error(req.status, resp.error, resp.reason);
  72. } else {
  73. alert("An error occurred getting session info: " + resp.reason);
  74. }
  75. }
  76. });
  77. },
  78. userDb : function(callback) {
  79. $.couch.session({
  80. success : function(resp) {
  81. var userDb = $.couch.db(resp.info.authentication_db);
  82. callback(userDb);
  83. }
  84. });
  85. },
  86. signup: function(user_doc, password, options) {
  87. options = options || {};
  88. // prepare user doc based on name and password
  89. user_doc = this.prepareUserDoc(user_doc, password);
  90. $.couch.userDb(function(db) {
  91. db.saveDoc(user_doc, options);
  92. });
  93. },
  94. prepareUserDoc: function(user_doc, new_password) {
  95. if (typeof hex_sha1 == "undefined") {
  96. alert("creating a user doc requires sha1.js to be loaded in the page");
  97. return;
  98. }
  99. var user_prefix = "org.couchdb.user:";
  100. user_doc._id = user_doc._id || user_prefix + user_doc.name;
  101. if (new_password) {
  102. // handle the password crypto
  103. user_doc.salt = $.couch.newUUID();
  104. user_doc.password_sha = hex_sha1(new_password + user_doc.salt);
  105. }
  106. user_doc.type = "user";
  107. if (!user_doc.roles) {
  108. user_doc.roles = [];
  109. }
  110. return user_doc;
  111. },
  112. login: function(options) {
  113. options = options || {};
  114. $.ajax({
  115. type: "POST", url: this.urlPrefix + "/_session", dataType: "json",
  116. data: {name: options.name, password: options.password},
  117. beforeSend: function(xhr) {
  118. xhr.setRequestHeader('Accept', 'application/json');
  119. },
  120. complete: function(req) {
  121. var resp = httpData(req, "json");
  122. if (req.status == 200) {
  123. if (options.success) options.success(resp);
  124. } else if (options.error) {
  125. options.error(req.status, resp.error, resp.reason);
  126. } else {
  127. alert("An error occurred logging in: " + resp.reason);
  128. }
  129. }
  130. });
  131. },
  132. logout: function(options) {
  133. options = options || {};
  134. $.ajax({
  135. type: "DELETE", url: this.urlPrefix + "/_session", dataType: "json",
  136. username : "_", password : "_",
  137. beforeSend: function(xhr) {
  138. xhr.setRequestHeader('Accept', 'application/json');
  139. },
  140. complete: function(req) {
  141. var resp = httpData(req, "json");
  142. if (req.status == 200) {
  143. if (options.success) options.success(resp);
  144. } else if (options.error) {
  145. options.error(req.status, resp.error, resp.reason);
  146. } else {
  147. alert("An error occurred logging out: " + resp.reason);
  148. }
  149. }
  150. });
  151. },
  152. db: function(name, db_opts) {
  153. db_opts = db_opts || {};
  154. var rawDocs = {};
  155. function maybeApplyVersion(doc) {
  156. if (doc._id && doc._rev && rawDocs[doc._id] && rawDocs[doc._id].rev == doc._rev) {
  157. // todo: can we use commonjs require here?
  158. if (typeof Base64 == "undefined") {
  159. alert("please include /_utils/script/base64.js in the page for base64 support");
  160. return false;
  161. } else {
  162. doc._attachments = doc._attachments || {};
  163. doc._attachments["rev-"+doc._rev.split("-")[0]] = {
  164. content_type :"application/json",
  165. data : Base64.encode(rawDocs[doc._id].raw)
  166. };
  167. return true;
  168. }
  169. }
  170. };
  171. return {
  172. name: name,
  173. uri: this.urlPrefix + "/" + encodeURIComponent(name) + "/",
  174. compact: function(options) {
  175. $.extend(options, {successStatus: 202});
  176. ajax({
  177. type: "POST", url: this.uri + "_compact",
  178. data: "", processData: false
  179. },
  180. options,
  181. "The database could not be compacted"
  182. );
  183. },
  184. viewCleanup: function(options) {
  185. $.extend(options, {successStatus: 202});
  186. ajax({
  187. type: "POST", url: this.uri + "_view_cleanup",
  188. data: "", processData: false
  189. },
  190. options,
  191. "The views could not be cleaned up"
  192. );
  193. },
  194. compactView: function(groupname, options) {
  195. $.extend(options, {successStatus: 202});
  196. ajax({
  197. type: "POST", url: this.uri + "_compact/" + groupname,
  198. data: "", processData: false
  199. },
  200. options,
  201. "The view could not be compacted"
  202. );
  203. },
  204. create: function(options) {
  205. $.extend(options, {successStatus: 201});
  206. ajax({
  207. type: "PUT", url: this.uri, contentType: "application/json",
  208. data: "", processData: false
  209. },
  210. options,
  211. "The database could not be created"
  212. );
  213. },
  214. drop: function(options) {
  215. ajax(
  216. {type: "DELETE", url: this.uri},
  217. options,
  218. "The database could not be deleted"
  219. );
  220. },
  221. info: function(options) {
  222. ajax(
  223. {url: this.uri},
  224. options,
  225. "Database information could not be retrieved"
  226. );
  227. },
  228. changes: function(since, options) {
  229. options = options || {};
  230. // set up the promise object within a closure for this handler
  231. var timeout = 100, db = this, active = true,
  232. listeners = [],
  233. promise = {
  234. onChange : function(fun) {
  235. listeners.push(fun);
  236. },
  237. stop : function() {
  238. active = false;
  239. }
  240. };
  241. // call each listener when there is a change
  242. function triggerListeners(resp) {
  243. $.each(listeners, function() {
  244. this(resp);
  245. });
  246. };
  247. // when there is a change, call any listeners, then check for another change
  248. options.success = function(resp) {
  249. timeout = 100;
  250. if (active) {
  251. since = resp.last_seq;
  252. triggerListeners(resp);
  253. getChangesSince();
  254. };
  255. };
  256. options.error = function() {
  257. if (active) {
  258. setTimeout(getChangesSince, timeout);
  259. timeout = timeout * 2;
  260. }
  261. };
  262. // actually make the changes request
  263. function getChangesSince() {
  264. var opts = $.extend({heartbeat : 10 * 1000}, options, {
  265. feed : "longpoll",
  266. since : since
  267. });
  268. ajax(
  269. {url: db.uri + "_changes"+encodeOptions(opts)},
  270. options,
  271. "Error connecting to "+db.uri+"/_changes."
  272. );
  273. }
  274. // start the first request
  275. if (since) {
  276. getChangesSince();
  277. } else {
  278. db.info({
  279. success : function(info) {
  280. since = info.update_seq;
  281. getChangesSince();
  282. }
  283. });
  284. }
  285. return promise;
  286. },
  287. allDocs: function(options) {
  288. var type = "GET";
  289. var data = null;
  290. if (options["keys"]) {
  291. type = "POST";
  292. var keys = options["keys"];
  293. delete options["keys"];
  294. data = toJSON({ "keys": keys });
  295. }
  296. ajax({
  297. type: type,
  298. data: data,
  299. url: this.uri + "_all_docs" + encodeOptions(options)
  300. },
  301. options,
  302. "An error occurred retrieving a list of all documents"
  303. );
  304. },
  305. allDesignDocs: function(options) {
  306. this.allDocs($.extend({startkey:"_design", endkey:"_design0"}, options));
  307. },
  308. allApps: function(options) {
  309. options = options || {};
  310. var self = this;
  311. if (options.eachApp) {
  312. this.allDesignDocs({
  313. success: function(resp) {
  314. $.each(resp.rows, function() {
  315. self.openDoc(this.id, {
  316. success: function(ddoc) {
  317. var index, appPath, appName = ddoc._id.split('/');
  318. appName.shift();
  319. appName = appName.join('/');
  320. index = ddoc.couchapp && ddoc.couchapp.index;
  321. if (index) {
  322. appPath = ['', name, ddoc._id, index].join('/');
  323. } else if (ddoc._attachments && ddoc._attachments["index.html"]) {
  324. appPath = ['', name, ddoc._id, "index.html"].join('/');
  325. }
  326. if (appPath) options.eachApp(appName, appPath, ddoc);
  327. }
  328. });
  329. });
  330. }
  331. });
  332. } else {
  333. alert("Please provide an eachApp function for allApps()");
  334. }
  335. },
  336. openDoc: function(docId, options, ajaxOptions) {
  337. options = options || {};
  338. if (db_opts.attachPrevRev || options.attachPrevRev) {
  339. $.extend(options, {
  340. beforeSuccess : function(req, doc) {
  341. rawDocs[doc._id] = {
  342. rev : doc._rev,
  343. raw : req.responseText
  344. };
  345. }
  346. });
  347. } else {
  348. $.extend(options, {
  349. beforeSuccess : function(req, doc) {
  350. if (doc["jquery.couch.attachPrevRev"]) {
  351. rawDocs[doc._id] = {
  352. rev : doc._rev,
  353. raw : req.responseText
  354. };
  355. }
  356. }
  357. });
  358. }
  359. ajax({url: this.uri + encodeDocId(docId) + encodeOptions(options)},
  360. options,
  361. "The document could not be retrieved",
  362. ajaxOptions
  363. );
  364. },
  365. saveDoc: function(doc, options) {
  366. options = options || {};
  367. var db = this;
  368. var beforeSend = fullCommit(options);
  369. if (doc._id === undefined) {
  370. var method = "POST";
  371. var uri = this.uri;
  372. } else {
  373. var method = "PUT";
  374. var uri = this.uri + encodeDocId(doc._id);
  375. }
  376. var versioned = maybeApplyVersion(doc);
  377. $.ajax({
  378. type: method, url: uri + encodeOptions(options),
  379. contentType: "application/json",
  380. dataType: "json", data: toJSON(doc),
  381. beforeSend : beforeSend,
  382. complete: function(req) {
  383. var resp = httpData(req, "json");
  384. if (req.status == 200 || req.status == 201 || req.status == 202) {
  385. doc._id = resp.id;
  386. doc._rev = resp.rev;
  387. if (versioned) {
  388. db.openDoc(doc._id, {
  389. attachPrevRev : true,
  390. success : function(d) {
  391. doc._attachments = d._attachments;
  392. if (options.success) options.success(resp);
  393. }
  394. });
  395. } else {
  396. if (options.success) options.success(resp);
  397. }
  398. } else if (options.error) {
  399. options.error(req.status, resp.error, resp.reason);
  400. } else {
  401. alert("The document could not be saved: " + resp.reason);
  402. }
  403. }
  404. });
  405. },
  406. bulkSave: function(docs, options) {
  407. var beforeSend = fullCommit(options);
  408. $.extend(options, {successStatus: 201, beforeSend : beforeSend});
  409. ajax({
  410. type: "POST",
  411. url: this.uri + "_bulk_docs" + encodeOptions(options),
  412. contentType: "application/json", data: toJSON(docs)
  413. },
  414. options,
  415. "The documents could not be saved"
  416. );
  417. },
  418. removeDoc: function(doc, options) {
  419. ajax({
  420. type: "DELETE",
  421. url: this.uri +
  422. encodeDocId(doc._id) +
  423. encodeOptions({rev: doc._rev})
  424. },
  425. options,
  426. "The document could not be deleted"
  427. );
  428. },
  429. bulkRemove: function(docs, options){
  430. docs.docs = $.each(
  431. docs.docs, function(i, doc){
  432. doc._deleted = true;
  433. }
  434. );
  435. $.extend(options, {successStatus: 201});
  436. ajax({
  437. type: "POST",
  438. url: this.uri + "_bulk_docs" + encodeOptions(options),
  439. data: toJSON(docs)
  440. },
  441. options,
  442. "The documents could not be deleted"
  443. );
  444. },
  445. copyDoc: function(docId, options, ajaxOptions) {
  446. ajaxOptions = $.extend(ajaxOptions, {
  447. complete: function(req) {
  448. var resp = httpData(req, "json");
  449. if (req.status == 201) {
  450. if (options.success) options.success(resp);
  451. } else if (options.error) {
  452. options.error(req.status, resp.error, resp.reason);
  453. } else {
  454. alert("The document could not be copied: " + resp.reason);
  455. }
  456. }
  457. });
  458. ajax({
  459. type: "COPY",
  460. url: this.uri + encodeDocId(docId)
  461. },
  462. options,
  463. "The document could not be copied",
  464. ajaxOptions
  465. );
  466. },
  467. query: function(mapFun, reduceFun, language, options) {
  468. language = language || "javascript";
  469. if (typeof(mapFun) !== "string") {
  470. mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")";
  471. }
  472. var body = {language: language, map: mapFun};
  473. if (reduceFun != null) {
  474. if (typeof(reduceFun) !== "string")
  475. reduceFun = reduceFun.toSource ? reduceFun.toSource() : "(" + reduceFun.toString() + ")";
  476. body.reduce = reduceFun;
  477. }
  478. ajax({
  479. type: "POST",
  480. url: this.uri + "_temp_view" + encodeOptions(options),
  481. contentType: "application/json", data: toJSON(body)
  482. },
  483. options,
  484. "An error occurred querying the database"
  485. );
  486. },
  487. list: function(list, view, options) {
  488. var list = list.split('/');
  489. var options = options || {};
  490. var type = 'GET';
  491. var data = null;
  492. if (options['keys']) {
  493. type = 'POST';
  494. var keys = options['keys'];
  495. delete options['keys'];
  496. data = toJSON({'keys': keys });
  497. }
  498. ajax({
  499. type: type,
  500. data: data,
  501. url: this.uri + '_design/' + list[0] +
  502. '/_list/' + list[1] + '/' + view + encodeOptions(options)
  503. },
  504. options, 'An error occured accessing the list'
  505. );
  506. },
  507. view: function(name, options) {
  508. var name = name.split('/');
  509. var options = options || {};
  510. var type = "GET";
  511. var data= null;
  512. if (options["keys"]) {
  513. type = "POST";
  514. var keys = options["keys"];
  515. delete options["keys"];
  516. data = toJSON({ "keys": keys });
  517. }
  518. ajax({
  519. type: type,
  520. data: data,
  521. url: this.uri + "_design/" + name[0] +
  522. "/_view/" + name[1] + encodeOptions(options)
  523. },
  524. options, "An error occurred accessing the view"
  525. );
  526. },
  527. getDbProperty: function(propName, options, ajaxOptions) {
  528. ajax({url: this.uri + propName + encodeOptions(options)},
  529. options,
  530. "The property could not be retrieved",
  531. ajaxOptions
  532. );
  533. },
  534. setDbProperty: function(propName, propValue, options, ajaxOptions) {
  535. ajax({
  536. type: "PUT",
  537. url: this.uri + propName + encodeOptions(options),
  538. data : JSON.stringify(propValue)
  539. },
  540. options,
  541. "The property could not be updated",
  542. ajaxOptions
  543. );
  544. }
  545. };
  546. },
  547. encodeDocId: encodeDocId,
  548. info: function(options) {
  549. ajax(
  550. {url: this.urlPrefix + "/"},
  551. options,
  552. "Server information could not be retrieved"
  553. );
  554. },
  555. replicate: function(source, target, ajaxOptions, repOpts) {
  556. repOpts = $.extend({source: source, target: target}, repOpts);
  557. if (repOpts.continuous && !repOpts.cancel) {
  558. ajaxOptions.successStatus = 202;
  559. }
  560. ajax({
  561. type: "POST", url: this.urlPrefix + "/_replicate",
  562. data: JSON.stringify(repOpts),
  563. contentType: "application/json"
  564. },
  565. ajaxOptions,
  566. "Replication failed"
  567. );
  568. },
  569. newUUID: function(cacheNum) {
  570. if (cacheNum === undefined) {
  571. cacheNum = 1;
  572. }
  573. if (!uuidCache.length) {
  574. ajax({url: this.urlPrefix + "/_uuids", data: {count: cacheNum}, async: false}, {
  575. success: function(resp) {
  576. uuidCache = resp.uuids;
  577. }
  578. },
  579. "Failed to retrieve UUID batch."
  580. );
  581. }
  582. return uuidCache.shift();
  583. }
  584. });
  585. var httpData = $.httpData || function( xhr, type, s ) { // lifted from jq1.4.4
  586. var ct = xhr.getResponseHeader("content-type") || "",
  587. xml = type === "xml" || !type && ct.indexOf("xml") >= 0,
  588. data = xml ? xhr.responseXML : xhr.responseText;
  589. if ( xml && data.documentElement.nodeName === "parsererror" ) {
  590. $.error( "parsererror" );
  591. }
  592. if ( s && s.dataFilter ) {
  593. data = s.dataFilter( data, type );
  594. }
  595. if ( typeof data === "string" ) {
  596. if ( type === "json" || !type && ct.indexOf("json") >= 0 ) {
  597. data = $.parseJSON( data );
  598. } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) {
  599. $.globalEval( data );
  600. }
  601. }
  602. return data;
  603. };
  604. function ajax(obj, options, errorMessage, ajaxOptions) {
  605. var defaultAjaxOpts = {
  606. contentType: "application/json",
  607. headers:{"Accept": "application/json"}
  608. };
  609. options = $.extend({successStatus: 200}, options);
  610. ajaxOptions = $.extend(defaultAjaxOpts, ajaxOptions);
  611. errorMessage = errorMessage || "Unknown error";
  612. $.ajax($.extend($.extend({
  613. type: "GET", dataType: "json", cache : !$.browser.msie,
  614. beforeSend: function(xhr){
  615. if(ajaxOptions && ajaxOptions.headers){
  616. for (var header in ajaxOptions.headers){
  617. xhr.setRequestHeader(header, ajaxOptions.headers[header]);
  618. }
  619. }
  620. },
  621. complete: function(req) {
  622. try {
  623. var resp = httpData(req, "json");
  624. } catch(e) {
  625. if (options.error) {
  626. options.error(req.status, req, e);
  627. } else {
  628. alert(errorMessage + ": " + e);
  629. }
  630. return;
  631. }
  632. if (options.ajaxStart) {
  633. options.ajaxStart(resp);
  634. }
  635. if (req.status == options.successStatus) {
  636. if (options.beforeSuccess) options.beforeSuccess(req, resp);
  637. if (options.success) options.success(resp);
  638. } else if (options.error) {
  639. options.error(req.status, resp && resp.error || errorMessage, resp && resp.reason || "no response");
  640. } else {
  641. alert(errorMessage + ": " + resp.reason);
  642. }
  643. }
  644. }, obj), ajaxOptions));
  645. }
  646. function fullCommit(options) {
  647. var options = options || {};
  648. if (typeof options.ensure_full_commit !== "undefined") {
  649. var commit = options.ensure_full_commit;
  650. delete options.ensure_full_commit;
  651. return function(xhr) {
  652. xhr.setRequestHeader('Accept', 'application/json');
  653. xhr.setRequestHeader("X-Couch-Full-Commit", commit.toString());
  654. };
  655. }
  656. };
  657. // Convert a options object to an url query string.
  658. // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
  659. function encodeOptions(options) {
  660. var buf = [];
  661. if (typeof(options) === "object" && options !== null) {
  662. for (var name in options) {
  663. if ($.inArray(name, ["error", "success", "beforeSuccess", "ajaxStart"]) >= 0)
  664. continue;
  665. var value = options[name];
  666. if ($.inArray(name, ["key", "startkey", "endkey"]) >= 0) {
  667. value = toJSON(value);
  668. }
  669. buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
  670. }
  671. }
  672. return buf.length ? "?" + buf.join("&") : "";
  673. }
  674. function toJSON(obj) {
  675. return obj !== null ? JSON.stringify(obj) : null;
  676. }
  677. })(jQuery);