PageRenderTime 62ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

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

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