PageRenderTime 72ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 0ms

/share/www/script/futon.browse.js

https://bitbucket.org/benoitc/couchdb
JavaScript | 1099 lines | 1016 code | 64 blank | 19 comment | 254 complexity | f47cdfaed5fd05e1753e3756d12b105e 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. $.futon = $.futon || {};
  14. $.extend($.futon, {
  15. // Page class for browse/index.html
  16. CouchIndexPage: function() {
  17. page = this;
  18. this.addDatabase = function() {
  19. $.showDialog("dialog/_create_database.html", {
  20. submit: function(data, callback) {
  21. if (!data.name || data.name.length == 0) {
  22. callback({name: "Please enter a name."});
  23. return;
  24. }
  25. $.couch.db(data.name).create({
  26. error: function(status, id, reason) { callback({name: reason}) },
  27. success: function(resp) {
  28. location.href = "database.html?" + encodeURIComponent(data.name);
  29. callback();
  30. }
  31. });
  32. }
  33. });
  34. return false;
  35. }
  36. this.updateDatabaseListing = function(offset) {
  37. offset |= 0;
  38. var maxPerPage = parseInt($("#perpage").val(), 10);
  39. $.couch.allDbs({
  40. success: function(dbs) {
  41. $("#paging a").unbind();
  42. $("#databases tbody.content").empty();
  43. var dbsOnPage = dbs.slice(offset, offset + maxPerPage);
  44. $.each(dbsOnPage, function(idx, dbName) {
  45. $("#databases tbody.content").append("<tr>" +
  46. "<th><a href='database.html?" + encodeURIComponent(dbName) + "'>" +
  47. dbName + "</a></th>" +
  48. "<td class='size'></td><td class='count'></td>" +
  49. "<td class='seq'></td></tr>");
  50. $.couch.db(dbName).info({
  51. success: function(info) {
  52. $("#databases tbody.content tr:eq(" + idx + ")")
  53. .find("td.size").text($.futon.formatSize(info.disk_size)).end()
  54. .find("td.count").text(info.doc_count).end()
  55. .find("td.seq").text(info.update_seq);
  56. }
  57. });
  58. });
  59. $("#databases tbody tr:odd").addClass("odd");
  60. if (offset > 0) {
  61. $("#paging a.prev").attr("href", "#" + (offset - maxPerPage)).click(function() {
  62. page.updateDatabaseListing(offset - maxPerPage);
  63. });
  64. } else {
  65. $("#paging a.prev").removeAttr("href");
  66. }
  67. if (offset + maxPerPage < dbs.length) {
  68. $("#paging a.next").attr("href", "#" + (offset + maxPerPage)).click(function() {
  69. page.updateDatabaseListing(offset + maxPerPage);
  70. });
  71. } else {
  72. $("#paging a.next").removeAttr("href");
  73. }
  74. var firstNum = offset + 1;
  75. var lastNum = firstNum + dbsOnPage.length - 1;
  76. $("#databases tbody.footer tr td span").text(
  77. "Showing " + firstNum + "-" + lastNum + " of " + dbs.length +
  78. " databases");
  79. }
  80. });
  81. }
  82. },
  83. // Page class for browse/database.html
  84. CouchDatabasePage: function() {
  85. var urlParts = location.search.substr(1).split("/");
  86. var dbName = decodeURIComponent(urlParts.shift());
  87. var viewName = (urlParts.length > 0) ? urlParts.join("/") : null;
  88. if (viewName) {
  89. $.cookies.set(dbName + ".view", viewName);
  90. } else {
  91. viewName = $.cookies.get(dbName + ".view", "");
  92. if (viewName) {
  93. this.redirecting = true;
  94. location.href = "database.html?" + encodeURIComponent(dbName) +
  95. "/" + viewName;
  96. }
  97. }
  98. var db = $.couch.db(dbName);
  99. this.dbName = dbName;
  100. this.viewName = viewName;
  101. this.viewLanguage = "javascript";
  102. this.db = db;
  103. this.isDirty = false;
  104. this.isTempView = viewName == "_temp_view";
  105. page = this;
  106. var templates = {
  107. javascript: "function(doc) {\n emit(null, doc);\n}",
  108. python: "def fun(doc):\n yield None, doc",
  109. ruby: "{|doc|\n emit(nil, doc);\n}"
  110. }
  111. this.newDocument = function() {
  112. location.href = "document.html?" + encodeURIComponent(db.name);
  113. }
  114. this.compactDatabase = function() {
  115. $.showDialog("dialog/_compact_database.html", {
  116. submit: function(data, callback) {
  117. db.compact({
  118. success: function(resp) {
  119. callback();
  120. }
  121. });
  122. }
  123. });
  124. }
  125. this.deleteDatabase = function() {
  126. $.showDialog("dialog/_delete_database.html", {
  127. submit: function(data, callback) {
  128. db.drop({
  129. success: function(resp) {
  130. callback();
  131. location.href = "index.html";
  132. if (window !== null) {
  133. $("#dbs li").filter(function(index) {
  134. return $("a", this).text() == dbName;
  135. }).remove();
  136. $.futon.navigation.removeDatabase(dbName);
  137. }
  138. }
  139. });
  140. }
  141. });
  142. }
  143. this.populateViewEditor = function() {
  144. if (viewName.match(/^_design\//)) {
  145. page.revertViewChanges(function() {
  146. var dirtyTimeout = null;
  147. function updateDirtyState() {
  148. clearTimeout(dirtyTimeout);
  149. dirtyTimeout = setTimeout(function() {
  150. var buttons = $("#viewcode button.save, #viewcode button.revert");
  151. page.isDirty = ($("#viewcode_map").val() != page.storedViewCode.map)
  152. || ($("#viewcode_reduce").val() != (page.storedViewCode.reduce || ""))
  153. || page.viewLanguage != page.storedViewLanguage;
  154. if (page.isDirty) {
  155. buttons.removeAttr("disabled");
  156. } else {
  157. buttons.attr("disabled", "disabled");
  158. }
  159. }, 100);
  160. }
  161. $("#viewcode textarea").bind("input", updateDirtyState);
  162. if ($.browser.msie || $.browser.safari) {
  163. $("#viewcode textarea").bind("paste", updateDirtyState)
  164. .bind("change", updateDirtyState)
  165. .bind("keydown", updateDirtyState)
  166. .bind("keypress", updateDirtyState)
  167. .bind("keyup", updateDirtyState)
  168. .bind("textInput", updateDirtyState);
  169. }
  170. $("#language").change(updateDirtyState);
  171. page.updateDocumentListing();
  172. });
  173. $("#grouptruenotice").show();
  174. } else if (viewName == "_temp_view") {
  175. page.viewLanguage = $.cookies.get(db.name + ".language", page.viewLanguage);
  176. page.updateViewEditor(
  177. $.cookies.get(db.name + ".map", templates[page.viewLanguage]),
  178. $.cookies.get(db.name + ".reduce", "")
  179. );
  180. $("#grouptruenotice").show();
  181. } else {
  182. page.updateDocumentListing();
  183. }
  184. page.populateLanguagesMenu();
  185. if (this.isTempView) {
  186. $("#tempwarn").show();
  187. }
  188. }
  189. // Populate the languages dropdown, and listen to selection changes
  190. this.populateLanguagesMenu = function() {
  191. var all_langs = {};
  192. fill_language = function() {
  193. var select = $("#language");
  194. for (var language in all_langs) {
  195. var option = $(document.createElement("option"))
  196. .attr("value", language).text(language)
  197. .appendTo(select);
  198. }
  199. if (select[0].options.length == 1) {
  200. select[0].disabled = true;
  201. } else {
  202. select[0].disabled = false;
  203. select.val(page.viewLanguage);
  204. select.change(function() {
  205. var language = $("#language").val();
  206. if (language != page.viewLanguage) {
  207. var mapFun = $("#viewcode_map").val();
  208. if (mapFun == "" || mapFun == templates[page.viewLanguage]) {
  209. // no edits made, so change to the new default
  210. $("#viewcode_map").val(templates[language]);
  211. }
  212. page.viewLanguage = language;
  213. $("#viewcode_map")[0].focus();
  214. }
  215. return false;
  216. });
  217. }
  218. }
  219. $.couch.config({
  220. success: function(resp) {
  221. for (var language in resp) {
  222. all_langs[language] = resp[language];
  223. }
  224. $.couch.config({
  225. success: function(resp) {
  226. for (var language in resp) {
  227. all_langs[language] = resp[language];
  228. }
  229. fill_language();
  230. }
  231. }, "native_query_servers");
  232. }
  233. }, "query_servers");
  234. }
  235. this.populateViewsMenu = function() {
  236. var select = $("#switch select");
  237. db.allDocs({startkey: "_design/", endkey: "_design0",
  238. include_docs: true,
  239. success: function(resp) {
  240. select[0].options.length = 3;
  241. for (var i = 0; i < resp.rows.length; i++) {
  242. var doc = resp.rows[i].doc;
  243. var optGroup = $(document.createElement("optgroup"))
  244. .attr("label", doc._id.substr(8)).appendTo(select);
  245. var viewNames = [];
  246. for (var name in doc.views) {
  247. viewNames.push(name);
  248. }
  249. viewNames.sort();
  250. for (var j = 0; j < viewNames.length; j++) {
  251. var path = $.couch.encodeDocId(doc._id) + "/_view/" +
  252. encodeURIComponent(viewNames[j]);
  253. var option = $(document.createElement("option"))
  254. .attr("value", path).text(viewNames[j]).appendTo(optGroup);
  255. if (path == viewName) {
  256. option[0].selected = true;
  257. }
  258. }
  259. }
  260. }
  261. });
  262. if (!viewName.match(/^_design\//)) {
  263. $.each(["_all_docs", "_design_docs", "_temp_view"], function(idx, name) {
  264. if (viewName == name) {
  265. select[0].options[idx].selected = true;
  266. }
  267. });
  268. }
  269. }
  270. this.revertViewChanges = function(callback) {
  271. if (!page.storedViewCode) {
  272. var viewNameParts = viewName.split("/");
  273. var designDocId = decodeURIComponent(viewNameParts[1]);
  274. var localViewName = decodeURIComponent(viewNameParts[3]);
  275. db.openDoc("_design/" + designDocId, {
  276. error: function(status, error, reason) {
  277. if (status == 404) {
  278. $.cookies.remove(dbName + ".view");
  279. location.href = "database.html?" + encodeURIComponent(db.name);
  280. }
  281. },
  282. success: function(resp) {
  283. if(!resp.views || !resp.views[localViewName]) {
  284. $.cookies.remove(dbName + ".view");
  285. location.href = "database.html?" + encodeURIComponent(db.name);
  286. }
  287. var viewCode = resp.views[localViewName];
  288. page.viewLanguage = resp.language || "javascript";
  289. $("#language").val(page.viewLanguage);
  290. page.updateViewEditor(viewCode.map, viewCode.reduce || "");
  291. $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled");
  292. page.storedViewCode = viewCode;
  293. page.storedViewLanguage = page.viewLanguage;
  294. if (callback) callback();
  295. }
  296. },{async:false});
  297. } else {
  298. page.updateViewEditor(page.storedViewCode.map,
  299. page.storedViewCode.reduce || "");
  300. page.viewLanguage = page.storedViewLanguage;
  301. $("#language").val(page.viewLanguage);
  302. $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled");
  303. page.isDirty = false;
  304. if (callback) callback();
  305. }
  306. }
  307. this.updateViewEditor = function(mapFun, reduceFun) {
  308. if (!mapFun) return;
  309. $("#viewcode_map").val(mapFun);
  310. $("#viewcode_reduce").val(reduceFun);
  311. var lines = Math.max(
  312. mapFun.split("\n").length,
  313. reduceFun.split("\n").length
  314. );
  315. $("#viewcode textarea").attr("rows", Math.min(15, Math.max(3, lines)));
  316. }
  317. this.saveViewAs = function() {
  318. if (viewName && /^_design/.test(viewName)) {
  319. var viewNameParts = viewName.split("/");
  320. var designDocId = decodeURIComponent(viewNameParts[1]);
  321. var localViewName = decodeURIComponent(viewNameParts[3]);
  322. } else {
  323. var designDocId = "", localViewName = "";
  324. }
  325. $.showDialog("dialog/_save_view_as.html", {
  326. load: function(elem) {
  327. $("#input_docid", elem).val(designDocId).suggest(function(text, callback) {
  328. db.allDocs({
  329. limit: 10, startkey: "_design/" + text, endkey: "_design0",
  330. success: function(docs) {
  331. var matches = [];
  332. for (var i = 0; i < docs.rows.length; i++) {
  333. var docName = docs.rows[i].id.substr(8);
  334. if (docName.indexOf(text) == 0) {
  335. matches[i] = docName;
  336. }
  337. }
  338. callback(matches);
  339. }
  340. });
  341. });
  342. $("#input_name", elem).val(localViewName).suggest(function(text, callback) {
  343. db.openDoc("_design/" + $("#input_docid").val(), {
  344. error: function() {}, // ignore
  345. success: function(doc) {
  346. var matches = [];
  347. if (!doc.views) return;
  348. for (var viewName in doc.views) {
  349. if (viewName.indexOf(text) == 0) {
  350. matches.push(viewName);
  351. }
  352. }
  353. callback(matches);
  354. }
  355. });
  356. });
  357. },
  358. submit: function(data, callback) {
  359. if (!data.docid || !data.name) {
  360. var errors = {};
  361. if (!data.docid) errors.docid = "Please enter a document ID";
  362. if (!data.name) errors.name = "Please enter a view name";
  363. callback(errors);
  364. } else {
  365. var viewCode = {
  366. map: $("#viewcode_map").val(),
  367. reduce: $("#viewcode_reduce").val() || undefined
  368. };
  369. var docId = ["_design", data.docid].join("/");
  370. function save(doc) {
  371. if (!doc) {
  372. doc = {_id: docId, language: page.viewLanguage};
  373. } else {
  374. var numViews = 0;
  375. for (var viewName in (doc.views || {})) {
  376. if (viewName != data.name) numViews++;
  377. }
  378. if (numViews > 0 && page.viewLanguage != doc.language) {
  379. callback({
  380. docid: "Cannot save to " + data.docid +
  381. " because its language is \"" + doc.language +
  382. "\", not \"" + page.viewLanguage + "\"."
  383. });
  384. return;
  385. }
  386. doc.language = page.viewLanguage;
  387. }
  388. if (doc.views === undefined) doc.views = {};
  389. doc.views[data.name] = viewCode;
  390. db.saveDoc(doc, {
  391. success: function(resp) {
  392. callback();
  393. page.isDirty = false;
  394. location.href = "database.html?" + encodeURIComponent(dbName) +
  395. "/" + $.couch.encodeDocId(doc._id) +
  396. "/_view/" + encodeURIComponent(data.name);
  397. }
  398. });
  399. }
  400. db.openDoc(docId, {
  401. error: function(status, error, reason) {
  402. if (status == 404) save(null);
  403. else alert(reason);
  404. },
  405. success: function(doc) {
  406. save(doc);
  407. }
  408. });
  409. }
  410. }
  411. });
  412. }
  413. this.saveViewChanges = function() {
  414. var viewNameParts = viewName.split("/");
  415. var designDocId = decodeURIComponent(viewNameParts[1]);
  416. var localViewName = decodeURIComponent(viewNameParts[3]);
  417. db.openDoc("_design/" + designDocId, {
  418. success: function(doc) {
  419. var numViews = 0;
  420. for (var viewName in (doc.views || {})) {
  421. if (viewName != localViewName) numViews++;
  422. }
  423. if (numViews > 0 && page.viewLanguage != doc.language) {
  424. alert("Cannot save view because the design document language " +
  425. "is \"" + doc.language + "\", not \"" +
  426. page.viewLanguage + "\".");
  427. return;
  428. }
  429. doc.language = page.viewLanguage;
  430. var viewDef = doc.views[localViewName];
  431. viewDef.map = $("#viewcode_map").val();
  432. viewDef.reduce = $("#viewcode_reduce").val() || undefined;
  433. db.saveDoc(doc, {
  434. success: function(resp) {
  435. page.isDirty = false;
  436. $("#viewcode button.revert, #viewcode button.save")
  437. .attr("disabled", "disabled");
  438. }
  439. });
  440. }
  441. });
  442. }
  443. this.updateDesignDocLink = function() {
  444. if (viewName && /^_design/.test(viewName)) {
  445. var docId = "_design/" + decodeURIComponent(viewName.split("/")[1]);
  446. $("#designdoc-link").attr("href", "document.html?" +
  447. encodeURIComponent(dbName) + "/" + $.couch.encodeDocId(docId)).text(docId);
  448. } else {
  449. $("#designdoc-link").removeAttr("href").text("");
  450. }
  451. }
  452. this.jumpToDocument = function(docId) {
  453. if (docId != "") {
  454. location.href = 'document.html?' + encodeURIComponent(db.name)
  455. + "/" + $.couch.encodeDocId(docId);
  456. }
  457. }
  458. this.updateDocumentListing = function(options) {
  459. if (options === undefined) options = {};
  460. if (options.limit === undefined) {
  461. per_page = parseInt($("#perpage").val(), 10)
  462. // Fetch an extra row so we know when we're on the last page for
  463. // reduce views
  464. options.limit = per_page + 1;
  465. } else {
  466. per_page = options.limit - 1;
  467. }
  468. if ($("#documents thead th.key").is(".desc")) {
  469. if (typeof options.descending == 'undefined') options.descending = true;
  470. var descend = true;
  471. $.cookies.set(dbName + ".desc", "1");
  472. } else {
  473. var descend = false;
  474. $.cookies.remove(dbName + ".desc");
  475. }
  476. $("#paging a").unbind();
  477. $("#documents").find("tbody.content").empty().end().show();
  478. this.updateDesignDocLink();
  479. options.success = function(resp) {
  480. if (resp.offset === undefined) {
  481. resp.offset = 0;
  482. }
  483. var descending_reverse = ((options.descending && !descend) || (descend && (options.descending === false)));
  484. var has_reduce_prev = resp.total_rows === undefined && (descending_reverse ? resp.rows.length > per_page : options.startkey !== undefined);
  485. if (descending_reverse && resp.rows) {
  486. resp.rows = resp.rows.reverse();
  487. if (resp.rows.length > per_page) {
  488. resp.rows.push(resp.rows.shift());
  489. }
  490. }
  491. if (resp.rows !== null && (has_reduce_prev || (descending_reverse ?
  492. (resp.total_rows - resp.offset > per_page) :
  493. (resp.offset > 0)))) {
  494. $("#paging a.prev").attr("href", "#" + (resp.offset - per_page)).click(function() {
  495. var opt = {
  496. descending: !descend,
  497. limit: options.limit
  498. };
  499. if (resp.rows.length > 0) {
  500. var firstDoc = resp.rows[0];
  501. opt.startkey = firstDoc.key !== undefined ? firstDoc.key : null;
  502. if (firstDoc.id !== undefined) {
  503. opt.startkey_docid = firstDoc.id;
  504. }
  505. opt.skip = 1;
  506. }
  507. page.updateDocumentListing(opt);
  508. return false;
  509. });
  510. } else {
  511. $("#paging a.prev").removeAttr("href");
  512. }
  513. var has_reduce_next = resp.total_rows === undefined && (descending_reverse ? options.startkey !== undefined : resp.rows.length > per_page);
  514. if (resp.rows !== null && (has_reduce_next || (descending_reverse ?
  515. (resp.offset - resp.total_rows < per_page) :
  516. (resp.total_rows - resp.offset > per_page)))) {
  517. $("#paging a.next").attr("href", "#" + (resp.offset + per_page)).click(function() {
  518. var opt = {
  519. descending: descend,
  520. limit: options.limit
  521. };
  522. if (resp.rows.length > 0) {
  523. var lastDoc = resp.rows[Math.min(per_page, resp.rows.length) - 1];
  524. opt.startkey = lastDoc.key !== undefined ? lastDoc.key : null;
  525. if (lastDoc.id !== undefined) {
  526. opt.startkey_docid = lastDoc.id;
  527. }
  528. opt.skip = 1;
  529. }
  530. page.updateDocumentListing(opt);
  531. return false;
  532. });
  533. } else {
  534. $("#paging a.next").removeAttr("href");
  535. }
  536. for (var i = 0; i < Math.min(per_page, resp.rows.length); i++) {
  537. var row = resp.rows[i];
  538. var tr = $("<tr></tr>");
  539. var key = "null";
  540. if (row.key !== null) {
  541. key = $.futon.formatJSON(row.key, {indent: 0, linesep: ""});
  542. }
  543. if (row.id) {
  544. $("<td class='key'><a href='document.html?" + encodeURIComponent(db.name) +
  545. "/" + $.couch.encodeDocId(row.id) + "'><strong></strong><br>" +
  546. "<span class='docid'>ID:&nbsp;" + row.id + "</span></a></td>")
  547. .find("strong").text(key).end()
  548. .appendTo(tr);
  549. } else {
  550. $("<td class='key'><strong></strong></td>")
  551. .find("strong").text(key).end()
  552. .appendTo(tr);
  553. }
  554. var value = "null";
  555. if (row.value !== null) {
  556. value = $.futon.formatJSON(row.value, {
  557. html: true, indent: 0, linesep: "", quoteKeys: false
  558. });
  559. }
  560. $("<td class='value'><div></div></td>").find("div").html(value).end().appendTo(tr).dblclick(function() {
  561. location.href = this.previousSibling.firstChild.href;
  562. });
  563. tr.appendTo("#documents tbody.content");
  564. }
  565. var firstNum = 1;
  566. var lastNum = totalNum = Math.min(per_page, resp.rows.length);
  567. if (resp.total_rows != null) {
  568. if (descending_reverse) {
  569. lastNum = Math.min(resp.total_rows, resp.total_rows - resp.offset);
  570. firstNum = lastNum - totalNum + 1;
  571. } else {
  572. firstNum = Math.min(resp.total_rows, resp.offset + 1);
  573. lastNum = firstNum + totalNum - 1;
  574. }
  575. totalNum = resp.total_rows;
  576. } else {
  577. totalNum = "unknown";
  578. }
  579. $("#paging").show();
  580. $("#documents tbody.footer td span").text(
  581. "Showing " + firstNum + "-" + lastNum + " of " + totalNum +
  582. " row" + (firstNum != lastNum || totalNum == "unknown" ? "s" : ""));
  583. $("#documents tbody tr:odd").addClass("odd");
  584. }
  585. options.error = function(status, error, reason) {
  586. alert("Error: " + error + "\n\n" + reason);
  587. }
  588. if (!viewName || viewName == "_all_docs") {
  589. $("#switch select")[0].selectedIndex = 0;
  590. db.allDocs(options);
  591. } else {
  592. if (viewName == "_temp_view") {
  593. $("#viewcode").show().removeClass("collapsed");
  594. var mapFun = $("#viewcode_map").val();
  595. $.cookies.set(db.name + ".map", mapFun);
  596. var reduceFun = $.trim($("#viewcode_reduce").val()) || null;
  597. if (reduceFun) {
  598. $.cookies.set(db.name + ".reduce", reduceFun);
  599. options.group = true;
  600. } else {
  601. $.cookies.remove(db.name + ".reduce");
  602. }
  603. $.cookies.set(db.name + ".language", page.viewLanguage);
  604. db.query(mapFun, reduceFun, page.viewLanguage, options);
  605. } else if (viewName == "_design_docs") {
  606. options.startkey = options.descending ? "_design0" : "_design";
  607. options.endkey = options.descending ? "_design" : "_design0";
  608. db.allDocs(options);
  609. } else {
  610. $("#viewcode").show();
  611. var currentMapCode = $("#viewcode_map").val();
  612. var currentReduceCode = $.trim($("#viewcode_reduce").val()) || null;
  613. if (currentReduceCode) {
  614. options.group = true;
  615. }
  616. if (page.isDirty) {
  617. db.query(currentMapCode, currentReduceCode, page.viewLanguage, options);
  618. } else {
  619. var viewParts = viewName.split('/');
  620. db.view(viewParts[1] + "/" + viewParts[3], options);
  621. }
  622. }
  623. }
  624. }
  625. window.onbeforeunload = function() {
  626. $("#switch select").val(viewName);
  627. if (page.isDirty) {
  628. return "You've made changes to the view code that have not been " +
  629. "saved yet.";
  630. }
  631. }
  632. },
  633. // Page class for browse/database.html
  634. CouchDocumentPage: function() {
  635. var urlParts = location.search.substr(1).split("/");
  636. var dbName = decodeURIComponent(urlParts.shift());
  637. if (urlParts.length) {
  638. var idParts = urlParts.join("/").split("@", 2);
  639. var docId = decodeURIComponent(idParts[0]);
  640. var docRev = (idParts.length > 1) ? idParts[1] : null;
  641. this.isNew = false;
  642. } else {
  643. var docId = $.couch.newUUID();
  644. var docRev = null;
  645. this.isNew = true;
  646. }
  647. var db = $.couch.db(dbName);
  648. this.dbName = dbName;
  649. this.db = db;
  650. this.docId = docId;
  651. this.doc = null;
  652. this.isDirty = this.isNew;
  653. page = this;
  654. this.activateTabularView = function() {
  655. if ($("#fields tbody.source textarea").length > 0)
  656. return;
  657. $("#tabs li").removeClass("active").filter(".tabular").addClass("active");
  658. $("#fields thead th:first").text("Field").attr("colspan", 1).next().show();
  659. $("#fields tbody.content").show();
  660. $("#fields tbody.source").hide();
  661. }
  662. this.activateSourceView = function() {
  663. $("#tabs li").removeClass("active").filter(".source").addClass("active");
  664. $("#fields thead th:first").text("Source").attr("colspan", 2).next().hide();
  665. $("#fields tbody.content").hide();
  666. $("#fields tbody.source").find("td").each(function() {
  667. $(this).html($("<pre></pre>").html($.futon.formatJSON(page.doc, {html: true})))
  668. .makeEditable({allowEmpty: false,
  669. createInput: function(value) {
  670. return $("<textarea rows='8' cols='80' spellcheck='false'></textarea>");
  671. },
  672. prepareInput: function(input) {
  673. $(input).makeResizable({vertical: true});
  674. },
  675. end: function() {
  676. $(this).html($("<pre></pre>").html($.futon.formatJSON(page.doc, {html: true})));
  677. },
  678. accept: function(newValue) {
  679. page.doc = JSON.parse(newValue);
  680. page.isDirty = true;
  681. page.updateFieldListing(true);
  682. },
  683. populate: function(value) {
  684. return $.futon.formatJSON(page.doc);
  685. },
  686. validate: function(value) {
  687. try {
  688. var doc = JSON.parse(value);
  689. if (typeof doc != "object")
  690. throw new SyntaxError("Please enter a valid JSON document (for example, {}).");
  691. return true;
  692. } catch (err) {
  693. var msg = err.message;
  694. if (msg == "parseJSON" || msg == "JSON.parse") {
  695. msg = "There is a syntax error in the document.";
  696. }
  697. $("<div class='error'></div>").text(msg).appendTo(this);
  698. return false;
  699. }
  700. }
  701. });
  702. }).end().show();
  703. }
  704. this.addField = function() {
  705. if (!$("#fields tbody.content:visible").length) {
  706. location.hash = "#tabular";
  707. page.activateTabularView();
  708. }
  709. var fieldName = "unnamed";
  710. var fieldIdx = 1;
  711. while (page.doc.hasOwnProperty(fieldName)) {
  712. fieldName = "unnamed " + fieldIdx++;
  713. }
  714. page.doc[fieldName] = null;
  715. var row = _addRowForField(page.doc, fieldName);
  716. page.isDirty = true;
  717. row.find("th b").dblclick();
  718. }
  719. var _sortFields = function(a, b) {
  720. var a0 = a.charAt(0), b0 = b.charAt(0);
  721. if (a0 == "_" && b0 != "_") {
  722. return -1;
  723. } else if (a0 != "_" && b0 == "_") {
  724. return 1;
  725. } else if (a == "_attachments" || b == "_attachments") {
  726. return a0 == "_attachments" ? 1 : -1;
  727. } else {
  728. return a < b ? -1 : a != b ? 1 : 0;
  729. }
  730. }
  731. this.updateFieldListing = function(noReload) {
  732. $("#fields tbody.content").empty();
  733. function handleResult(doc, revs) {
  734. page.doc = doc;
  735. var propNames = [];
  736. for (var prop in doc) {
  737. propNames.push(prop);
  738. }
  739. // Order properties alphabetically, but put internal fields first
  740. propNames.sort(_sortFields);
  741. for (var pi = 0; pi < propNames.length; pi++) {
  742. _addRowForField(doc, propNames[pi]);
  743. }
  744. if (revs.length > 1) {
  745. var currentIndex = 0;
  746. for (var i = 0; i < revs.length; i++) {
  747. if (revs[i].rev == doc._rev) {
  748. currentIndex = i;
  749. break;
  750. }
  751. }
  752. if (currentIndex < revs.length - 1) {
  753. var prevRev = revs[currentIndex + 1].rev;
  754. $("#paging a.prev").attr("href", "?" + encodeURIComponent(dbName) +
  755. "/" + $.couch.encodeDocId(docId) + "@" + prevRev);
  756. }
  757. if (currentIndex > 0) {
  758. var nextRev = revs[currentIndex - 1].rev;
  759. $("#paging a.next").attr("href", "?" + encodeURIComponent(dbName) +
  760. "/" + $.couch.encodeDocId(docId) + "@" + nextRev);
  761. }
  762. $("#fields tbody.footer td span").text("Showing revision " +
  763. (revs.length - currentIndex) + " of " + revs.length);
  764. }
  765. if (location.hash == "#source" && !noReload) {
  766. page.activateSourceView();
  767. }
  768. }
  769. if (noReload) {
  770. handleResult(page.doc, []);
  771. return;
  772. }
  773. if (!page.isNew) {
  774. db.openDoc(docId, {revs_info: true,
  775. success: function(doc) {
  776. var revs = doc._revs_info || [];
  777. delete doc._revs_info;
  778. if (docRev != null) {
  779. db.openDoc(docId, {rev: docRev,
  780. error: function(status, error, reason) {
  781. alert("The requested revision was not found. You will " +
  782. "be redirected back to the latest revision.");
  783. location.href = "?" + encodeURIComponent(dbName) +
  784. "/" + $.couch.encodeDocId(docId);
  785. },
  786. success: function(doc) {
  787. handleResult(doc, revs);
  788. }
  789. });
  790. } else {
  791. handleResult(doc, revs);
  792. }
  793. }
  794. });
  795. } else {
  796. handleResult({_id: docId}, []);
  797. $("#fields tbody td").dblclick();
  798. }
  799. }
  800. this.deleteDocument = function() {
  801. $.showDialog("dialog/_delete_document.html", {
  802. submit: function(data, callback) {
  803. db.removeDoc(page.doc, {
  804. success: function(resp) {
  805. callback();
  806. location.href = "database.html?" + encodeURIComponent(dbName);
  807. }
  808. });
  809. }
  810. });
  811. }
  812. this.saveDocument = function() {
  813. db.saveDoc(page.doc, {
  814. error: function(status, error, reason) {
  815. alert("Error: " + error + "\n\n" + reason);
  816. },
  817. success: function(resp) {
  818. page.isDirty = false;
  819. location.href = "?" + encodeURIComponent(dbName) +
  820. "/" + $.couch.encodeDocId(page.docId);
  821. }
  822. });
  823. }
  824. this.uploadAttachment = function() {
  825. if (page.isDirty) {
  826. alert("You need to save or revert any changes you have made to the " +
  827. "document before you can attach a new file.");
  828. return false;
  829. }
  830. $.showDialog("dialog/_upload_attachment.html", {
  831. load: function(elem) {
  832. $("input[name='_rev']", elem).val(page.doc._rev);
  833. },
  834. submit: function(data, callback) {
  835. if (!data._attachments || data._attachments.length == 0) {
  836. callback({_attachments: "Please select a file to upload."});
  837. return;
  838. }
  839. var form = $("#upload-form");
  840. form.find("#progress").css("visibility", "visible");
  841. form.ajaxSubmit({
  842. url: db.uri + $.couch.encodeDocId(page.docId),
  843. success: function(resp) {
  844. form.find("#progress").css("visibility", "hidden");
  845. page.isDirty = false;
  846. location.href = "?" + encodeURIComponent(dbName) +
  847. "/" + $.couch.encodeDocId(page.docId);
  848. }
  849. });
  850. }
  851. });
  852. }
  853. window.onbeforeunload = function() {
  854. if (page.isDirty) {
  855. return "You've made changes to this document that have not been " +
  856. "saved yet.";
  857. }
  858. }
  859. function _addRowForField(doc, fieldName) {
  860. var row = $("<tr><th></th><td></td></tr>")
  861. .find("th").append($("<b></b>").text(fieldName)).end()
  862. .appendTo("#fields tbody.content");
  863. if (fieldName == "_attachments") {
  864. row.find("td").append(_renderAttachmentList(doc[fieldName]));
  865. } else {
  866. row.find("td").append(_renderValue(doc[fieldName]));
  867. _initKey(doc, row, fieldName);
  868. _initValue(doc, row, fieldName);
  869. }
  870. $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd");
  871. row.data("name", fieldName);
  872. return row;
  873. }
  874. function _initKey(doc, row, fieldName) {
  875. if (fieldName == "_id" || fieldName == "_rev") {
  876. return;
  877. }
  878. var cell = row.find("th");
  879. $("<button type='button' class='delete' title='Delete field'></button>").click(function() {
  880. delete doc[fieldName];
  881. row.remove();
  882. page.isDirty = true;
  883. $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd");
  884. }).prependTo(cell);
  885. cell.find("b").makeEditable({allowEmpty: false,
  886. accept: function(newName, oldName) {
  887. doc[newName] = doc[oldName];
  888. delete doc[oldName];
  889. row.data("name", newName);
  890. $(this).text(newName);
  891. page.isDirty = true;
  892. },
  893. begin: function() {
  894. row.find("th button.delete").hide();
  895. return true;
  896. },
  897. end: function(keyCode) {
  898. row.find("th button.delete").show();
  899. if (keyCode == 9) { // tab, move to editing the value
  900. row.find("td").dblclick();
  901. }
  902. },
  903. validate: function(newName, oldName) {
  904. $("div.error", this).remove();
  905. if (newName != oldName && doc[newName] !== undefined) {
  906. $("<div class='error'>Already have field with that name.</div>")
  907. .appendTo(this);
  908. return false;
  909. }
  910. return true;
  911. }
  912. });
  913. }
  914. function _initValue(doc, row, fieldName) {
  915. if ((fieldName == "_id" && !page.isNew) || fieldName == "_rev") {
  916. return;
  917. }
  918. row.find("td").makeEditable({allowEmpty: true,
  919. createInput: function(value) {
  920. if ($("dl", this).length > 0 || $("code", this).text().length > 60) {
  921. return $("<textarea rows='8' cols='40' spellcheck='false'></textarea>");
  922. }
  923. return $("<input type='text' spellcheck='false'>");
  924. },
  925. end: function() {
  926. $(this).children().remove();
  927. $(this).append(_renderValue(doc[row.data("name")]));
  928. },
  929. prepareInput: function(input) {
  930. if ($(input).is("textarea")) {
  931. $(input).makeResizable({vertical: true});
  932. }
  933. },
  934. accept: function(newValue) {
  935. var fieldName = row.data("name");
  936. doc[fieldName] = JSON.parse(newValue);
  937. page.isDirty = true;
  938. if (fieldName == "_id") {
  939. page.docId = page.doc._id = doc[fieldName];
  940. $("h1 strong").text(page.docId);
  941. }
  942. },
  943. populate: function(value) {
  944. return $.futon.formatJSON(doc[row.data("name")]);
  945. },
  946. validate: function(value) {
  947. $("div.error", this).remove();
  948. try {
  949. var parsed = JSON.parse(value);
  950. if (row.data("name") == "_id" && typeof(parsed) != "string") {
  951. $("<div class='error'>The document ID must be a string.</div>")
  952. .appendTo(this);
  953. return false;
  954. }
  955. return true;
  956. } catch (err) {
  957. var msg = err.message;
  958. if (msg == "parseJSON" || msg == "JSON.parse") {
  959. msg = "Please enter a valid JSON value (for example, \"string\").";
  960. }
  961. $("<div class='error'></div>").text(msg).appendTo(this);
  962. return false;
  963. }
  964. }
  965. });
  966. }
  967. function _renderValue(value) {
  968. function render(val) {
  969. var type = typeof(val);
  970. if (type == "object" && val !== null) {
  971. var list = $("<dl></dl>");
  972. for (var i in val) {
  973. $("<dt></dt>").text(i).appendTo(list);
  974. $("<dd></dd>").append(render(val[i])).appendTo(list);
  975. }
  976. return list;
  977. } else {
  978. return $($.futon.formatJSON(val, {html: true}));
  979. }
  980. }
  981. var elem = render(value);
  982. elem.find("dd:has(dl)").hide().prev("dt").addClass("collapsed");
  983. elem.find("dd:not(:has(dl))").addClass("inline").prev().addClass("inline");
  984. elem.find("dt.collapsed").click(function() {
  985. $(this).toggleClass("collapsed").next().toggle();
  986. });
  987. return elem;
  988. }
  989. function _renderAttachmentList(attachments) {
  990. var ul = $("<ul></ul>").addClass("attachments");
  991. $.each(attachments, function(idx, attachment) {
  992. _renderAttachmentItem(idx, attachment).appendTo(ul);
  993. });
  994. return ul;
  995. }
  996. function _renderAttachmentItem(name, attachment) {
  997. var attachmentHref = db.uri + $.couch.encodeDocId(page.docId)
  998. + "/" + encodeAttachment(name);
  999. var li = $("<li></li>");
  1000. $("<a href='' title='Download file' target='_top'></a>").text(name)
  1001. .attr("href", attachmentHref)
  1002. .wrapInner("<tt></tt>").appendTo(li);
  1003. $("<span>()</span>").text("" + $.futon.formatSize(attachment.length) +
  1004. ", " + attachment.content_type).addClass("info").appendTo(li);
  1005. if (name == "tests.js") {
  1006. li.find('span.info').append(', <a href="/_utils/couch_tests.html?'
  1007. + attachmentHref + '">open in test runner</a>');
  1008. }
  1009. _initAttachmentItem(name, attachment, li);
  1010. return li;
  1011. }
  1012. function _initAttachmentItem(name, attachment, li) {
  1013. $("<button type='button' class='delete' title='Delete attachment'></button>").click(function() {
  1014. if (!li.siblings("li").length) {
  1015. delete page.doc._attachments;
  1016. li.parents("tr").remove();
  1017. $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd");
  1018. } else {
  1019. delete page.doc._attachments[name];
  1020. li.remove();
  1021. }
  1022. page.isDirty = true;
  1023. return false;
  1024. }).prependTo($("a", li));
  1025. }
  1026. }
  1027. });
  1028. function encodeAttachment(name) {
  1029. var encoded = [], parts = name.split('/');
  1030. for (var i=0; i < parts.length; i++) {
  1031. encoded.push(encodeURIComponent(parts[i]));
  1032. };
  1033. return encoded.join('/');
  1034. }
  1035. })(jQuery);