PageRenderTime 53ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

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

http://github.com/apache/couchdb
JavaScript | 1344 lines | 1278 code | 47 blank | 19 comment | 154 complexity | d0dd66f64f1bc5e18eb749892d3ebc16 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file