PageRenderTime 69ms CodeModel.GetById 39ms RepoModel.GetById 0ms app.codeStats 1ms

/share/www/script/browse.js

http://github.com/Erlang-Communitivity/c3couchdb
JavaScript | 819 lines | 754 code | 44 blank | 21 comment | 169 complexity | 9b0b3219a310f69ad642d70c09fbb64b 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
  3. // of 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. /*
  13. * Page class for browse/index.html
  14. */
  15. function CouchIndexPage() {
  16. page = this;
  17. this.addDatabase = function() {
  18. $.showDialog("_create_database.html", {
  19. submit: function(data, callback) {
  20. if (!data.name || data.name.length == 0) {
  21. callback({name: "Please enter a name."});
  22. return;
  23. }
  24. $.couch.db(data.name).create({
  25. error: function(status, id, reason) { callback({name: reason}) },
  26. success: function(resp) {
  27. if (window !== parent) parent.setTimeout("updateDatabaseList()", 500);
  28. location.href = "database.html?" + encodeURIComponent(data.name);
  29. callback();
  30. }
  31. });
  32. }
  33. });
  34. return false;
  35. }
  36. this.updateDatabaseListing = function() {
  37. $(document.body).addClass("loading");
  38. $.couch.allDbs({
  39. success: function(dbs) {
  40. if (dbs.length == 0) {
  41. $(document.body).removeClass("loading");
  42. }
  43. $.each(dbs, function(idx, dbName) {
  44. $("#databases tbody.content").append("<tr>" +
  45. "<th><a href='database.html?" + encodeURIComponent(dbName) + "'>" +
  46. dbName + "</a></th>" +
  47. "<td class='size'></td><td class='count'></td>" +
  48. "<td class='seq'></td></tr>");
  49. $.couch.db(dbName).info({
  50. success: function(info) {
  51. $("#databases tbody.content tr:eq(" + idx + ")")
  52. .find("td.size").text(prettyPrintSize(info.disk_size)).end()
  53. .find("td.count").text(info.doc_count).end()
  54. .find("td.seq").text(info.update_seq);
  55. if (idx == dbs.length - 1) {
  56. $(document.body).removeClass("loading");
  57. }
  58. }
  59. });
  60. });
  61. $("#databases tbody tr:odd").addClass("odd");
  62. $("#databases tbody.footer tr td").text(dbs.length + " database(s)");
  63. }
  64. });
  65. }
  66. }
  67. /*
  68. * Page class for browse/database.html
  69. */
  70. function CouchDatabasePage() {
  71. var urlParts = location.search.substr(1).split("/");
  72. var dbName = decodeURIComponent(urlParts.shift());
  73. var viewName = (urlParts.length > 0) ? urlParts.join("/") : null;
  74. if (viewName) {
  75. viewName = decodeURIComponent(viewName);
  76. $.cookies.set(dbName + ".view", viewName);
  77. } else {
  78. viewName = $.cookies.get(dbName + ".view") || "";
  79. }
  80. var db = $.couch.db(dbName);
  81. this.dbName = dbName;
  82. this.viewName = viewName;
  83. this.db = db;
  84. this.isDirty = false;
  85. page = this;
  86. this.addDocument = function() {
  87. $.showDialog("_create_document.html", {
  88. submit: function(data, callback) {
  89. db.saveDoc(data.docid ? {_id: data.docid} : {}, {
  90. error: function(status, error, reason) {
  91. callback({docid: reason});
  92. },
  93. success: function(resp) {
  94. location.href = "document.html?" + encodeURIComponent(dbName) +
  95. "/" + encodeURIComponent(resp.id);
  96. }
  97. });
  98. }
  99. });
  100. }
  101. this.compactDatabase = function() {
  102. $.showDialog("_compact_database.html", {
  103. submit: function(data, callback) {
  104. db.compact({
  105. success: function(resp) {
  106. callback();
  107. }
  108. });
  109. }
  110. });
  111. }
  112. this.deleteDatabase = function() {
  113. $.showDialog("_delete_database.html", {
  114. submit: function(data, callback) {
  115. db.drop({
  116. success: function(resp) {
  117. callback();
  118. location.href = "index.html";
  119. if (window !== null) {
  120. parent.$("#dbs li").filter(function(index) {
  121. return $("a", this).text() == dbName;
  122. }).remove();
  123. }
  124. }
  125. });
  126. }
  127. });
  128. }
  129. this.populateViewEditor = function() {
  130. if (viewName.match(/^_design\//)) {
  131. page.revertViewChanges(function() {
  132. var dirtyTimeout = null;
  133. function updateDirtyState() {
  134. clearTimeout(dirtyTimeout);
  135. dirtyTimeout = setTimeout(function() {
  136. var buttons = $("#viewcode button.save, #viewcode button.revert");
  137. page.isDirty = ($("#viewcode_map").val() != page.storedViewCode.map)
  138. || ($("#viewcode_reduce").val() != page.storedViewCode.reduce);
  139. if (page.isDirty) {
  140. buttons.removeAttr("disabled");
  141. } else {
  142. buttons.attr("disabled", "disabled");
  143. }
  144. }, 100);
  145. }
  146. $("#viewcode textarea").bind("input", updateDirtyState);
  147. if ($.browser.msie) { // sorry, browser detection
  148. $("#viewcode textarea").get(0).onpropertychange = updateDirtyState
  149. } else if ($.browser.safari) {
  150. $("#viewcode textarea").bind("paste", updateDirtyState)
  151. .bind("change", updateDirtyState)
  152. .bind("keydown", updateDirtyState)
  153. .bind("keypress", updateDirtyState)
  154. .bind("keyup", updateDirtyState)
  155. .bind("textInput", updateDirtyState);
  156. }
  157. });
  158. }
  159. }
  160. this.populateViewsMenu = function() {
  161. var select = $("#switch select");
  162. db.allDocs({startkey: "_design/", endkey: "_design/ZZZ",
  163. success: function(resp) {
  164. select[0].options.length = 3;
  165. for (var i = 0; i < resp.rows.length; i++) {
  166. db.openDoc(resp.rows[i].id, {
  167. success: function(doc) {
  168. var optGroup = $("<optgroup></optgroup>").attr("label", doc._id.substr(8));
  169. var optGroup = $(document.createElement("optgroup"))
  170. .attr("label", doc._id.substr(8));
  171. for (var name in doc.views) {
  172. if (!doc.views.hasOwnProperty(name)) continue;
  173. var option = $(document.createElement("option"))
  174. .attr("value", doc._id + "/" + name).text(name)
  175. .appendTo(optGroup);
  176. if (doc._id + "/" + name == viewName) {
  177. option[0].selected = true;
  178. }
  179. }
  180. optGroup.appendTo(select);
  181. }
  182. });
  183. }
  184. }
  185. });
  186. if (!viewName.match(/^_design\//)) {
  187. $.each(["_all_docs", "_design_docs", "_temp_view"], function(idx, name) {
  188. if (viewName == name) {
  189. select[0].options[idx].selected = true;
  190. }
  191. });
  192. }
  193. }
  194. this.revertViewChanges = function(callback) {
  195. if (!page.storedViewCode) {
  196. var viewNameParts = viewName.split("/");
  197. var designDocId = viewNameParts[1];
  198. var localViewName = viewNameParts[2];
  199. db.openDoc(["_design", designDocId].join("/"), {
  200. error: function(status, error, reason) {
  201. if (status == 404) {
  202. $.cookies.remove(dbName + ".view");
  203. location.reload();
  204. }
  205. },
  206. success: function(resp) {
  207. var viewCode = resp.views[localViewName];
  208. $("#viewcode_map").val(viewCode.map);
  209. $("#viewcode_reduce").val(viewCode.reduce || "");
  210. var lines = Math.max(viewCode.map.split("\n").length,
  211. (viewCode.reduce ? viewCode.reduce.split("\n").length : 1));
  212. $("#viewcode textarea").attr("rows", Math.min(15, Math.max(3, lines)));
  213. $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled");
  214. page.storedViewCode = viewCode;
  215. if (callback) callback();
  216. }
  217. });
  218. } else {
  219. $("#viewcode_map").val(page.storedViewCode.map);
  220. $("#viewcode_reduce").val(page.storedViewCode.reduce || "");
  221. page.isDirty = false;
  222. $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled");
  223. if (callback) callback();
  224. }
  225. }
  226. this.saveViewAs = function() {
  227. if (viewName && /^_design/.test(viewName)) {
  228. var viewNameParts = viewName.split("/");
  229. var designDocId = viewNameParts[1];
  230. var localViewName = viewNameParts[2];
  231. } else {
  232. var designDocId = "", localViewName = ""
  233. }
  234. $.showDialog("_save_view_as.html", {
  235. load: function(elem) {
  236. $("#input_docid", elem).val(designDocId).suggest(function(text, callback) {
  237. db.allDocs({
  238. count: 10, startkey: "_design/" + text,
  239. endkey: "_design/" + text + "ZZZZ",
  240. success: function(docs) {
  241. var matches = [];
  242. for (var i = 0; i < docs.rows.length; i++) {
  243. matches[i] = docs.rows[i].id.substr(8);
  244. }
  245. callback(matches);
  246. }
  247. });
  248. });
  249. $("#input_name", elem).val(localViewName).suggest(function(text, callback) {
  250. db.openDoc("_design/" + $("#input_docid").val(), {
  251. error: function() {}, // ignore
  252. success: function(doc) {
  253. var matches = [];
  254. if (!doc.views) return;
  255. for (var viewName in doc.views) {
  256. if (!doc.views.hasOwnProperty(viewName) || !viewName.match("^" + text)) {
  257. continue;
  258. }
  259. matches.push(viewName);
  260. }
  261. callback(matches);
  262. }
  263. });
  264. });
  265. },
  266. submit: function(data, callback) {
  267. if (!data.docid || !data.name) {
  268. var errors = {};
  269. if (!data.docid) errors.docid = "Please enter a document ID";
  270. if (!data.name) errors.name = "Please enter a view name";
  271. callback(errors);
  272. } else {
  273. var viewCode = {
  274. map: $("#viewcode_map").val(),
  275. reduce: $("#viewcode_reduce").val() || undefined
  276. };
  277. var docId = ["_design", data.docid].join("/");
  278. function save(doc) {
  279. if (!doc) doc = {_id: docId, language: "javascript"};
  280. if (doc.views === undefined) doc.views = {};
  281. doc.views[data.name] = viewCode;
  282. db.saveDoc(doc, {
  283. success: function(resp) {
  284. callback();
  285. page.isDirty = false;
  286. location.href = "database.html?" + encodeURIComponent(dbName) +
  287. "/" + encodeURIComponent(doc._id) +
  288. "/" + encodeURIComponent(data.name);
  289. }
  290. });
  291. }
  292. db.openDoc(docId, {
  293. error: function(status, error, reason) {
  294. if (status == 404) save(null);
  295. else alert(reason);
  296. },
  297. success: function(doc) {
  298. save(doc);
  299. }
  300. });
  301. }
  302. }
  303. });
  304. }
  305. this.saveViewChanges = function() {
  306. var viewNameParts = viewName.split("/");
  307. var designDocId = viewNameParts[1];
  308. var localViewName = viewNameParts[2];
  309. $(document.body).addClass("loading");
  310. db.openDoc(["_design", designDocId].join("/"), {
  311. success: function(doc) {
  312. var viewDef = doc.views[localViewName];
  313. viewDef.map = $("#viewcode_map").val();
  314. viewDef.reduce = $("#viewcode_reduce").val() || undefined;
  315. db.saveDoc(doc, {
  316. success: function(resp) {
  317. page.isDirty = false;
  318. $("#viewcode button.revert, #viewcode button.save")
  319. .attr("disabled", "disabled");
  320. $(document.body).removeClass("loading");
  321. }
  322. });
  323. }
  324. });
  325. }
  326. this.updateDesignDocLink = function() {
  327. if (viewName && /^_design/.test(viewName)) {
  328. var docId = "_design/" + viewName.split("/")[1];
  329. $("#designdoc-link").attr("href", "document.html?" +
  330. encodeURIComponent(dbName) + "/" + encodeURIComponent(docId)).text(docId);
  331. } else {
  332. $("#designdoc-link").removeAttr("href").text("");
  333. }
  334. }
  335. this.updateDocumentListing = function(options) {
  336. $(document.body).addClass("loading");
  337. if (options === undefined) options = {};
  338. if (options.count === undefined) {
  339. options.count = parseInt($("#perpage").val(), 10);
  340. }
  341. if (options.group === undefined) {
  342. options.group = true;
  343. }
  344. if ($("#documents thead th.key").is(".desc")) {
  345. options.descending = true;
  346. $.cookies.set(dbName + ".desc", "1");
  347. } else {
  348. if (options.descending !== undefined) delete options.descending;
  349. $.cookies.remove(dbName + ".desc");
  350. }
  351. $("#paging a").unbind();
  352. $("#documents tbody.content").empty();
  353. this.updateDesignDocLink();
  354. options.success = function(resp) {
  355. if (resp.offset === undefined) {
  356. resp.offset = 0;
  357. }
  358. if (resp.rows !== null && resp.offset > 0) {
  359. $("#paging a.prev").attr("href", "#" + (resp.offset - options.count)).click(function() {
  360. var firstDoc = resp.rows[0];
  361. page.updateDocumentListing({
  362. startkey: firstDoc.key !== undefined ? firstDoc.key : null,
  363. startkey_docid: firstDoc.id,
  364. skip: 1,
  365. count: -options.count
  366. });
  367. return false;
  368. });
  369. } else {
  370. $("#paging a.prev").removeAttr("href");
  371. }
  372. if (resp.rows !== null && resp.total_rows - resp.offset > options.count) {
  373. $("#paging a.next").attr("href", "#" + (resp.offset + options.count)).click(function() {
  374. var lastDoc = resp.rows[resp.rows.length - 1];
  375. page.updateDocumentListing({
  376. startkey: lastDoc.key !== undefined ? lastDoc.key : null,
  377. startkey_docid: lastDoc.id,
  378. skip: 1,
  379. count: options.count
  380. });
  381. return false;
  382. });
  383. } else {
  384. $("#paging a.next").removeAttr("href");
  385. }
  386. for (var i = 0; i < resp.rows.length; i++) {
  387. var row = resp.rows[i];
  388. var tr = $("<tr></tr>");
  389. var key = row.key;
  390. if (row.id) {
  391. $("<td class='key'><a href='document.html?" + encodeURIComponent(db.name) +
  392. "/" + encodeURIComponent(row.id) + "'><strong></strong><br>" +
  393. "<span class='docid'>ID:&nbsp;" + row.id + "</span></a></td>")
  394. .find("strong").text(key !== null ? prettyPrintJSON(key, 0, "") : "null").end()
  395. .appendTo(tr);
  396. } else {
  397. $("<td class='key'><strong></strong></td>")
  398. .find("strong").text(key !== null ? prettyPrintJSON(key, 0, "") : "null").end()
  399. .appendTo(tr);
  400. }
  401. var value = row.value;
  402. $("<td class='value'></td>").text(
  403. value !== null ? prettyPrintJSON(value, 0, "") : "null"
  404. ).appendTo(tr).dblclick(function() {
  405. location.href = this.previousSibling.firstChild.href;
  406. });
  407. tr.appendTo("#documents tbody.content");
  408. }
  409. var firstNum = 1;
  410. var lastNum = totalNum = resp.rows.length;
  411. if (resp.total_rows != null) {
  412. firstNum = Math.min(resp.total_rows, resp.offset + 1);
  413. lastNum = firstNum + resp.rows.length - 1;
  414. totalNum = resp.total_rows;
  415. $("#paging").show();
  416. } else {
  417. $("#paging").hide();
  418. }
  419. $("#documents tbody.footer td span").text(
  420. "Showing " + firstNum + "-" + lastNum + " of " + totalNum +
  421. " row" + (firstNum != lastNum ? "s" : ""));
  422. $("#documents tbody tr:odd").addClass("odd");
  423. $(document.body).removeClass("loading");
  424. }
  425. options.error = function(status, error, reason) {
  426. alert("Error: " + error + "\n\n" + reason);
  427. $(document.body).removeClass("loading");
  428. }
  429. if (!viewName) {
  430. $("#switch select").get(0).selectedIndex = 0;
  431. db.allDocs(options);
  432. } else {
  433. if (viewName == "_temp_view") {
  434. $("#viewcode").show().removeClass("collapsed");
  435. var mapFun = $("#viewcode_map").val();
  436. $.cookies.set(db.name + ".map", mapFun);
  437. var reduceFun = $("#viewcode_reduce").val() || null;
  438. if (reduceFun != null) {
  439. $.cookies.set(db.name + ".reduce", reduceFun);
  440. } else {
  441. $.cookies.remove(db.name + ".reduce");
  442. }
  443. db.query(mapFun, reduceFun, null, options);
  444. } else if (viewName == "_design_docs") {
  445. options.startkey = options.descending ? "_design/ZZZZ" : "_design/";
  446. options.endkey = options.descending ? "_design/" : "_design/ZZZZ";
  447. db.allDocs(options);
  448. } else {
  449. $("#viewcode").show();
  450. var currentMapCode = $("#viewcode_map").val();
  451. var currentReduceCode = $("#viewcode_reduce").val() || null;
  452. if (page.isDirty) {
  453. db.query(currentMapCode, currentReduceCode, null, options);
  454. } else {
  455. db.view(viewName.substr(8), options);
  456. }
  457. }
  458. }
  459. }
  460. window.onbeforeunload = function() {
  461. $("#switch select").val(viewName);
  462. if (page.isDirty) {
  463. return "You've made changes to the view code that have not been " +
  464. "saved yet.";
  465. }
  466. }
  467. }
  468. /*
  469. * Page class for browse/database.html
  470. */
  471. function CouchDocumentPage() {
  472. var urlParts = location.search.substr(1).split("/");
  473. var dbName = decodeURIComponent(urlParts.shift());
  474. var idParts = urlParts.join("/").split("@", 2);
  475. var docId = decodeURIComponent(idParts[0]);
  476. var docRev = (idParts.length > 1) ? idParts[1] : null;
  477. var db = $.couch.db(dbName);
  478. this.dbName = dbName;
  479. this.db = db;
  480. this.docId = docId;
  481. this.doc = null;
  482. this.isDirty = false;
  483. page = this;
  484. this.addField = function() {
  485. var fieldName = "unnamed";
  486. var fieldIdx = 1;
  487. while (page.doc.hasOwnProperty(fieldName)) {
  488. fieldName = "unnamed " + fieldIdx++;
  489. }
  490. page.doc[fieldName] = null;
  491. var row = _addRowForField(page.doc, fieldName);
  492. page.isDirty = true;
  493. _editKey(page.doc, row.find("th"), fieldName);
  494. }
  495. var _sortFields = function(a, b) {
  496. var a0 = a.charAt(0), b0 = b.charAt(0);
  497. if (a0 == "_" && b0 != "_") {
  498. return -1;
  499. } else if (a0 != "_" && b0 == "_") {
  500. return 1;
  501. } else if (a == "_attachments" || b == "_attachments") {
  502. return a0 == "_attachments" ? 1 : -1;
  503. } else {
  504. return a < b ? -1 : a != b ? 1 : 0;
  505. }
  506. }
  507. this.updateFieldListing = function() {
  508. $(document.body).addClass("loading");
  509. $("#fields tbody.content").empty();
  510. function handleResult(doc, revs) {
  511. page.doc = doc;
  512. var propNames = [];
  513. for (var prop in doc) {
  514. if (!doc.hasOwnProperty(prop)) continue;
  515. propNames.push(prop);
  516. }
  517. // Order properties alphabetically, but put internal fields first
  518. propNames.sort(_sortFields);
  519. for (var pi = 0; pi < propNames.length; pi++) {
  520. _addRowForField(doc, propNames[pi]);
  521. }
  522. if (revs.length > 1) {
  523. var currentIndex = 0;
  524. for (var i = 0; i < revs.length; i++) {
  525. if (revs[i].rev == doc._rev) {
  526. currentIndex = i;
  527. break;
  528. }
  529. }
  530. if (currentIndex < revs.length - 1) {
  531. var prevRev = revs[currentIndex + 1].rev;
  532. $("#paging a.prev").attr("href", "?" + encodeURIComponent(dbName) +
  533. "/" + encodeURIComponent(docId) + "@" + prevRev);
  534. }
  535. if (currentIndex > 0) {
  536. var nextRev = revs[currentIndex - 1].rev;
  537. $("#paging a.next").attr("href", "?" + encodeURIComponent(dbName) +
  538. "/" + encodeURIComponent(docId) + "@" + nextRev);
  539. }
  540. $("#fields tbody.footer td span").text("Showing revision " +
  541. (revs.length - currentIndex) + " of " + revs.length);
  542. }
  543. $(document.body).removeClass("loading");
  544. }
  545. db.openDoc(docId, {revs_info: true,
  546. success: function(doc) {
  547. var revs = doc._revs_info;
  548. delete doc._revs_info;
  549. if (docRev != null) {
  550. db.openDoc(docId, {rev: docRev,
  551. error: function(status, error, reason) {
  552. alert("The requested revision was not found. " +
  553. "You will be redirected back to the latest revision.");
  554. location.href = "?" + encodeURIComponent(dbName) +
  555. "/" + encodeURIComponent(docId);
  556. },
  557. success: function(doc) {
  558. handleResult(doc, revs);
  559. }
  560. });
  561. } else {
  562. handleResult(doc, revs);
  563. }
  564. }
  565. });
  566. }
  567. this.deleteDocument = function() {
  568. $.showDialog("_delete_document.html", {
  569. submit: function(data, callback) {
  570. db.removeDoc(page.doc, {
  571. success: function(resp) {
  572. callback();
  573. location.href = "database.html?" + encodeURIComponent(dbName);
  574. }
  575. });
  576. }
  577. });
  578. }
  579. this.saveDocument = function() {
  580. $(document.body).addClass("loading");
  581. db.saveDoc(page.doc, {
  582. success: function(resp) {
  583. page.isDirty = false;
  584. location.href = "?" + encodeURIComponent(dbName) +
  585. "/" + encodeURIComponent(docId);
  586. }
  587. });
  588. }
  589. window.onbeforeunload = function() {
  590. if (page.isDirty) {
  591. return "You've made changes to this document that have not been " +
  592. "saved yet.";
  593. }
  594. }
  595. function _addRowForField(doc, fieldName) {
  596. var row = $("<tr><th></th><td></td></tr>").find("th").append($("<b></b>")
  597. .text(fieldName)).end().appendTo("#fields tbody.content");
  598. if (fieldName == "_attachments") {
  599. row
  600. .find("td").append(_renderAttachmentList(doc[fieldName]));
  601. } else {
  602. var value = _renderValue(doc[fieldName]);
  603. row
  604. .find("th b").dblclick(function() {
  605. _editKey(doc, this, $(this).text());
  606. }).end()
  607. .find("td").append(value).dblclick(function() {
  608. _editValue(doc, this, $(this).prev("th").text());
  609. }).end();
  610. if (fieldName != "_id" && fieldName != "_rev") {
  611. row.find("th, td").attr("title", "Double click to edit");
  612. _initKey(doc, row, fieldName);
  613. _initValue(value);
  614. }
  615. }
  616. $("#fields tbody tr").removeClass("odd").filter(":odd").addClass("odd");
  617. return row;
  618. }
  619. function _editKey(doc, cell, fieldName) {
  620. if (fieldName == "_id" || fieldName == "_rev") return;
  621. var th = $(cell);
  622. th.empty();
  623. var input = $("<input type='text' spellcheck='false'>");
  624. input.dblclick(function() { return false; }).keydown(function(evt) {
  625. switch (evt.keyCode) {
  626. case 13: applyChange(); break;
  627. case 27: cancelChange(); break;
  628. }
  629. });
  630. var tools = $("<div class='tools'></div>");
  631. function applyChange() {
  632. input.nextAll().remove();
  633. var newName = input.val();
  634. if (!newName.length || newName == fieldName) {
  635. cancelChange();
  636. return;
  637. }
  638. doc[newName] = doc[fieldName];
  639. delete doc[fieldName];
  640. th.children().remove();
  641. th.append($("<b></b>").text(newName));
  642. _initKey(doc, th.parent("tr"), fieldName);
  643. page.isDirty = true;
  644. }
  645. function cancelChange() {
  646. th.children().remove();
  647. th.append($("<b></b>").text(fieldName));
  648. _initKey(doc, th.parent("tr"), fieldName);
  649. }
  650. $("<button type='button' class='apply'></button>").click(function() {
  651. applyChange();
  652. }).appendTo(tools);
  653. $("<button type='button' class='cancel'></button>").click(function() {
  654. cancelChange();
  655. }).appendTo(tools);
  656. tools.appendTo(th);
  657. input.val(fieldName).appendTo(th);
  658. input.each(function() { this.focus(); this.select(); });
  659. }
  660. function _editValue(doc, cell, fieldName) {
  661. if (!fieldName || fieldName == "_id" || fieldName == "_rev") return;
  662. var td = $(cell);
  663. var value = doc[fieldName];
  664. var needsTextarea = $("dl", td).length > 0 || $("code", td).text().length > 60;
  665. td.empty();
  666. if (needsTextarea) {
  667. var input = $("<textarea rows='8' cols='40' spellcheck='false'></textarea>");
  668. } else {
  669. var input = $("<input type='text' spellcheck='false'>");
  670. }
  671. input.dblclick(function() { return false; }).keydown(function(evt) {
  672. switch (evt.keyCode) {
  673. case 13: if (!needsTextarea) applyChange(); break;
  674. case 27: cancelChange(); break;
  675. }
  676. });
  677. var tools = $("<div class='tools'></div>");
  678. function applyChange() {
  679. input.nextAll().remove();
  680. try {
  681. var newValue = input.val() || "null";
  682. if (newValue == doc[fieldName]) {
  683. cancelChange();
  684. return;
  685. }
  686. doc[fieldName] = JSON.parse(newValue);
  687. td.children().remove();
  688. page.isDirty = true;
  689. var value = _renderValue(doc[fieldName]);
  690. td.append(value);
  691. _initValue(value);
  692. } catch (err) {
  693. input.addClass("invalid");
  694. var msg = err.message;
  695. if (msg == "parseJSON") {
  696. msg = "Please enter a valid JSON value (for example, \"string\").";
  697. }
  698. $("<div class='error'></div>").text(msg).insertAfter(input);
  699. }
  700. }
  701. function cancelChange() {
  702. td.children().remove();
  703. var value = _renderValue(doc[fieldName]);
  704. td.append(value);
  705. _initValue(value);
  706. }
  707. $("<button type='button' class='apply' title='Apply change'></button>").click(function() {
  708. applyChange();
  709. }).appendTo(tools);
  710. $("<button type='button' class='cancel' title='Revert change'></button>").click(function() {
  711. cancelChange();
  712. }).appendTo(tools);
  713. tools.appendTo(td);
  714. input.val(prettyPrintJSON(value)).appendTo(td);
  715. input.each(function() { this.focus(); this.select(); });
  716. if (needsTextarea) input.makeResizable({vertical: true});
  717. }
  718. function _initKey(doc, row, fieldName) {
  719. if (fieldName != "_id" && fieldName != "_rev") {
  720. $("<button type='button' class='delete' title='Delete field'></button>").click(function() {
  721. delete doc[fieldName];
  722. row.remove();
  723. page.isDirty = true;
  724. $("#fields tbody tr").removeClass("odd").filter(":odd").addClass("odd");
  725. }).prependTo(row.find("th"));
  726. }
  727. }
  728. function _initValue(value) {
  729. value.find("dd:has(dl)").hide().prev("dt").addClass("collapsed");
  730. value.find("dd:not(:has(dl))").addClass("inline").prev().addClass("inline");
  731. value.find("dt.collapsed").click(function() {
  732. $(this).toggleClass("collapsed").next().toggle();
  733. });
  734. }
  735. function _renderValue(value) {
  736. var type = typeof(value);
  737. if (type == "object" && value !== null) {
  738. var list = $("<dl></dl>");
  739. for (var i in value) {
  740. if (!value.hasOwnProperty(i)) continue;
  741. $("<dt></dt>").text(i).appendTo(list);
  742. $("<dd></dd>").append(_renderValue(value[i])).appendTo(list);
  743. }
  744. return list;
  745. } else {
  746. return $("<code></code>").addClass(type).text(
  747. value !== null ? JSON.stringify(value) : "null"
  748. );
  749. }
  750. }
  751. function _renderAttachmentList(attachments) {
  752. var ul = $("<ul></ul>").addClass("attachments");
  753. $.each(attachments, function(idx, attachment) {
  754. _renderAttachmentItem(idx, attachment).appendTo(ul);
  755. });
  756. return ul;
  757. }
  758. function _renderAttachmentItem(name, attachment) {
  759. var li = $("<li></li>");
  760. $("<a href='' title='Download file' target='_top'></a>").text(name)
  761. .attr("href", db.uri + encodeURIComponent(docId) + "/" + encodeURIComponent(name))
  762. .wrapInner("<tt></tt>").appendTo(li);
  763. $("<span>()</span>").text("" + prettyPrintSize(attachment.length) +
  764. ", " + attachment.content_type).addClass("info").appendTo(li);
  765. _initAttachmentItem(name, attachment, li);
  766. return li;
  767. }
  768. function _initAttachmentItem(name, attachment, li) {
  769. $("<button type='button' class='delete' title='Delete attachment'></button>").click(function() {
  770. delete page.doc._attachments[name];
  771. li.remove();
  772. page.isDirty = true;
  773. }).prependTo($("a", li));
  774. }
  775. }