PageRenderTime 103ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 1ms

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

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