PageRenderTime 56ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

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

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