PageRenderTime 55ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

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

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