PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

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

http://github.com/cloudant/bigcouch
JavaScript | 535 lines | 472 code | 47 blank | 16 comment | 86 complexity | 92636ffcc8be0126a725aa04e8d7679b 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. // $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/
  13. function $$(node) {
  14. var data = $(node).data("$$");
  15. if (data) {
  16. return data;
  17. } else {
  18. data = {};
  19. $(node).data("$$", data);
  20. return data;
  21. }
  22. };
  23. (function($) {
  24. function Session() {
  25. function doLogin(name, password, callback) {
  26. $.couch.login({
  27. name : name,
  28. password : password,
  29. success : function() {
  30. $.futon.session.sidebar();
  31. callback();
  32. },
  33. error : function(code, error, reason) {
  34. $.futon.session.sidebar();
  35. callback({name : "Error logging in: "+reason});
  36. }
  37. });
  38. };
  39. function doSignup(name, password, callback, runLogin) {
  40. $.couch.signup({
  41. name : name
  42. }, password, {
  43. success : function() {
  44. if (runLogin) {
  45. doLogin(name, password, callback);
  46. } else {
  47. callback();
  48. }
  49. },
  50. error : function(status, error, reason) {
  51. $.futon.session.sidebar();
  52. if (error == "conflict") {
  53. callback({name : "Name '"+name+"' is taken"});
  54. } else {
  55. callback({name : "Signup error: "+reason});
  56. }
  57. }
  58. });
  59. };
  60. function validateUsernameAndPassword(data, callback) {
  61. if (!data.name || data.name.length == 0) {
  62. callback({name: "Please enter a name."});
  63. return false;
  64. };
  65. return validatePassword(data, callback);
  66. };
  67. function validatePassword(data, callback) {
  68. if (!data.password || data.password.length == 0) {
  69. callback({password: "Please enter a password."});
  70. return false;
  71. };
  72. return true;
  73. };
  74. function createAdmin() {
  75. $.showDialog("dialog/_create_admin.html", {
  76. submit: function(data, callback) {
  77. if (!validateUsernameAndPassword(data, callback)) return;
  78. $.couch.config({
  79. success : function() {
  80. doLogin(data.name, data.password, function(errors) {
  81. if(!$.isEmptyObject(errors)) {
  82. callback(errors);
  83. return;
  84. }
  85. doSignup(data.name, null, function(errors) {
  86. if (errors && errors.name && errors.name.indexOf && errors.name.indexOf("taken") == -1) {
  87. callback(errors);
  88. } else {
  89. callback();
  90. }
  91. }, false);
  92. });
  93. }
  94. }, "admins", data.name, data.password);
  95. }
  96. });
  97. return false;
  98. };
  99. function login() {
  100. $.showDialog("dialog/_login.html", {
  101. submit: function(data, callback) {
  102. if (!validateUsernameAndPassword(data, callback)) return;
  103. doLogin(data.name, data.password, callback);
  104. }
  105. });
  106. return false;
  107. };
  108. function logout() {
  109. $.couch.logout({
  110. success : function(resp) {
  111. $.futon.session.sidebar();
  112. }
  113. })
  114. };
  115. function signup() {
  116. $.showDialog("dialog/_signup.html", {
  117. submit: function(data, callback) {
  118. if (!validateUsernameAndPassword(data, callback)) return;
  119. doSignup(data.name, data.password, callback, true);
  120. }
  121. });
  122. return false;
  123. };
  124. function changePassword () {
  125. $.showDialog("dialog/_change_password.html", {
  126. submit: function(data, callback) {
  127. if (validatePassword(data, callback)) {
  128. if (data.password != data.verify_password) {
  129. callback({verify_password: "Passwords don't match."});
  130. return false;
  131. }
  132. } else {
  133. return false;
  134. }
  135. $.couch.session({success: function (resp) {
  136. if (resp.userCtx.roles.indexOf("_admin") > -1) {
  137. $.couch.config({
  138. success : function () {
  139. doLogin(resp.userCtx.name, data.password, function(errors) {
  140. if(!$.isEmptyObject(errors)) {
  141. callback(errors);
  142. return;
  143. } else {
  144. location.reload();
  145. }
  146. });
  147. }
  148. }, "admins", resp.userCtx.name, data.password);
  149. } else {
  150. $.couch.db(resp.info.authentication_db).openDoc("org.couchdb.user:"+resp.userCtx.name, {
  151. success: function (user) {
  152. $.couch.db(resp.info.authentication_db).saveDoc($.couch.prepareUserDoc(user, data.password), {
  153. success: function() {
  154. doLogin(user.name, data.password, function(errors) {
  155. if(!$.isEmptyObject(errors)) {
  156. callback(errors);
  157. return;
  158. } else {
  159. location.reload();
  160. }
  161. });
  162. }
  163. });
  164. }
  165. });
  166. }
  167. }});
  168. }
  169. });
  170. return false;
  171. };
  172. this.setupSidebar = function() {
  173. $("#userCtx .login").click(login);
  174. $("#userCtx .logout").click(logout);
  175. $("#userCtx .signup").click(signup);
  176. $("#userCtx .createadmin").click(createAdmin);
  177. $("#userCtx .changepass").click(changePassword);
  178. };
  179. this.sidebar = function() {
  180. // get users db info?
  181. $("#userCtx span").hide();
  182. $.couch.session({
  183. success : function(r) {
  184. var userCtx = r.userCtx;
  185. $$("#userCtx").userCtx = userCtx;
  186. if (userCtx.name) {
  187. $("#userCtx .name").text(userCtx.name).attr({href : $.couch.urlPrefix + "/_utils/document.html?"+encodeURIComponent(r.info.authentication_db)+"/org.couchdb.user%3A"+encodeURIComponent(userCtx.name)});
  188. if (userCtx.roles.indexOf("_admin") != -1) {
  189. $("#userCtx .loggedin").show();
  190. $("#userCtx .loggedinadmin").show();
  191. } else {
  192. $("#userCtx .loggedin").show();
  193. }
  194. } else if (userCtx.roles.indexOf("_admin") != -1) {
  195. $("#userCtx .adminparty").show();
  196. } else {
  197. $("#userCtx .loggedout").show();
  198. };
  199. }
  200. })
  201. };
  202. };
  203. function Navigation() {
  204. var nav = this;
  205. this.loaded = false;
  206. this.eventHandlers = {
  207. load: []
  208. };
  209. this.ready = function(callback) {
  210. if (callback) {
  211. if (this.loaded) {
  212. callback.apply(this);
  213. }
  214. this.eventHandlers["load"].push(callback);
  215. } else {
  216. this.loaded = true;
  217. callbacks = this.eventHandlers["load"];
  218. for (var i = 0; i < callbacks.length; i++) {
  219. callbacks[i].apply(this);
  220. }
  221. }
  222. }
  223. this.addDatabase = function(name) {
  224. var current = $.futon.storage.get("recent", "");
  225. var recentDbs = current ? current.split(",") : [];
  226. if ($.inArray(name, recentDbs) == -1) {
  227. recentDbs.unshift(name);
  228. if (recentDbs.length > 10) recentDbs.length = 10;
  229. $.futon.storage.set("recent", recentDbs.join(","));
  230. this.updateDatabases();
  231. }
  232. }
  233. this.removeDatabase = function(name) {
  234. // remove database from recent databases list
  235. var current = $.futon.storage.get("recent", "");
  236. var recentDbs = current ? current.split(",") : [];
  237. var recentIdx = $.inArray(name, recentDbs);
  238. if (recentIdx >= 0) {
  239. recentDbs.splice(recentIdx, 1);
  240. $.futon.storage.set("recent", recentDbs.join(","));
  241. this.updateDatabases();
  242. }
  243. }
  244. this.updateDatabases = function() {
  245. var selection = null;
  246. $("#dbs .selected a").each(function() {
  247. selection = [this.pathname, this.search];
  248. });
  249. $("#dbs").empty();
  250. var recentDbs = $.futon.storage.get("recent").split(",");
  251. recentDbs.sort();
  252. $.each(recentDbs, function(idx, name) {
  253. if (name) {
  254. name = encodeURIComponent(name);
  255. $("#dbs").append("<li>" +
  256. "<button class='remove' title='Remove from list' value='" + name + "'></button>" +
  257. "<a href='database.html?" + name + "' title='" + name + "'>" + name +
  258. "</a></li>");
  259. }
  260. });
  261. if (selection) {
  262. this.updateSelection(selection[0], selection[1]);
  263. }
  264. $("#dbs button.remove").click(function() {
  265. nav.removeDatabase(this.value);
  266. return false;
  267. });
  268. }
  269. this.updateSelection = function(path, queryString) {
  270. function fixupPath(path) { // hack for IE/Win
  271. return (path.charAt(0) != "/") ? ("/" + path) : path;
  272. }
  273. if (!path) {
  274. path = location.pathname;
  275. if (!queryString) {
  276. queryString = location.search;
  277. }
  278. } else if (!queryString) {
  279. queryString = "";
  280. }
  281. var href = fixupPath(path + queryString);
  282. $("#nav li").removeClass("selected");
  283. $("#nav li a").each(function() {
  284. if (fixupPath(this.pathname) + this.search != href) return;
  285. $(this).parent("li").addClass("selected").parents("li").addClass("selected");
  286. });
  287. }
  288. this.toggle = function(speed) {
  289. if (speed === undefined) {
  290. speed = 500;
  291. }
  292. var sidebar = $("#sidebar").stop(true, true);
  293. var hidden = !$(sidebar).is(".hidden");
  294. $("#wrap").animate({
  295. marginRight: hidden ? 0 : 210
  296. }, speed, function() {
  297. $(document.body).toggleClass("fullwidth", hidden);
  298. });
  299. sidebar.toggleClass("hidden").animate({
  300. width: hidden ? 26 : 210,
  301. height: hidden ? $("h1").outerHeight() - 1 : "100%",
  302. right: hidden ? 0 : -210
  303. }, speed).children(":not(#sidebar-toggle)").animate({
  304. opacity: "toggle"
  305. }, speed);
  306. $("h1").animate({marginRight: hidden ? 26 : 0}, speed);
  307. $("#sidebar-toggle")
  308. .attr("title", hidden ? "Show Sidebar" : "Hide Sidebar");
  309. $.futon.storage.set("sidebar", hidden ? "hidden" : "show");
  310. };
  311. }
  312. function Storage() {
  313. var storage = this;
  314. this.decls = {};
  315. this.declare = function(name, options) {
  316. this.decls[name] = $.extend({}, {
  317. scope: "window",
  318. defaultValue: null,
  319. prefix: ""
  320. }, options || {});
  321. }
  322. this.declareWithPrefix = function(prefix, decls) {
  323. for (var name in decls) {
  324. var options = decls[name];
  325. options.prefix = prefix;
  326. storage.declare(name, options);
  327. }
  328. }
  329. this.del = function(name) {
  330. lookup(name, function(decl) {
  331. handlers[decl.scope].del(decl.prefix + name);
  332. });
  333. }
  334. this.get = function(name, defaultValue) {
  335. return lookup(name, function(decl) {
  336. var value = handlers[decl.scope].get(decl.prefix + name);
  337. if (value !== undefined) {
  338. return value;
  339. }
  340. if (defaultValue !== undefined) {
  341. return defaultValue;
  342. }
  343. return decl.defaultValue;
  344. });
  345. }
  346. this.set = function(name, value) {
  347. lookup(name, function(decl) {
  348. if (value == decl.defaultValue) {
  349. handlers[decl.scope].del(decl.prefix + name);
  350. } else {
  351. handlers[decl.scope].set(decl.prefix + name, value);
  352. }
  353. });
  354. }
  355. function lookup(name, callback) {
  356. var decl = storage.decls[name];
  357. if (decl === undefined) {
  358. return decl;
  359. }
  360. return callback(decl);
  361. }
  362. function windowName() {
  363. try {
  364. return JSON.parse(window.name || "{}");
  365. } catch (e) {
  366. return {};
  367. }
  368. }
  369. // add suffix to cookie names to be able to separate between ports
  370. var cookiePrefix = location.port + "_";
  371. var handlers = {
  372. "cookie": {
  373. get: function(name) {
  374. var nameEq = cookiePrefix + name + "=";
  375. var parts = document.cookie.split(';');
  376. for (var i = 0; i < parts.length; i++) {
  377. var part = parts[i].replace(/^\s+/, "");
  378. if (part.indexOf(nameEq) == 0) {
  379. return unescape(part.substring(nameEq.length, part.length));
  380. }
  381. }
  382. },
  383. set: function(name, value) {
  384. var date = new Date();
  385. date.setTime(date.getTime() + 14*24*60*60*1000); // two weeks
  386. document.cookie = cookiePrefix + name + "=" + escape(value) +
  387. "; expires=" + date.toGMTString();
  388. },
  389. del: function(name) {
  390. var date = new Date();
  391. date.setTime(date.getTime() - 24*60*60*1000); // yesterday
  392. document.cookie = cookiePrefix + name + "=" +
  393. "; expires=" + date.toGMTString();
  394. }
  395. },
  396. "window": {
  397. get: function(name) {
  398. return windowName()[name];
  399. },
  400. set: function(name, value) {
  401. var obj = windowName();
  402. obj[name] = value || null;
  403. window.name = JSON.stringify(obj);
  404. },
  405. del: function(name) {
  406. var obj = windowName();
  407. delete obj[name];
  408. window.name = JSON.stringify(obj);
  409. }
  410. }
  411. };
  412. }
  413. $.couch.urlPrefix = "..";
  414. $.futon = $.futon || {};
  415. $.extend($.futon, {
  416. navigation: new Navigation(),
  417. session : new Session(),
  418. storage: new Storage()
  419. });
  420. $.fn.addPlaceholder = function() {
  421. if (this[0] && "placeholder" in document.createElement("input")) {
  422. return; // found native placeholder support
  423. }
  424. return this.live('focusin', function() {
  425. var input = $(this);
  426. if (input.val() === input.attr("placeholder")) {
  427. input.removeClass("placeholder").val("");
  428. }
  429. }).live("focusout", function() {
  430. var input = $(this);
  431. if (input.val() === "") {
  432. input.val(input.attr("placeholder")).addClass("placeholder");
  433. }
  434. }).trigger("focusout");
  435. }
  436. $.fn.enableTabInsertion = function(chars) {
  437. chars = chars || "\t";
  438. var width = chars.length;
  439. return this.keydown(function(evt) {
  440. if (evt.keyCode == 9) {
  441. var v = this.value;
  442. var start = this.selectionStart;
  443. var scrollTop = this.scrollTop;
  444. if (start !== undefined) {
  445. this.value = v.slice(0, start) + chars + v.slice(start);
  446. this.selectionStart = this.selectionEnd = start + width;
  447. } else {
  448. document.selection.createRange().text = chars;
  449. this.caretPos += width;
  450. }
  451. return false;
  452. }
  453. });
  454. }
  455. $(document)
  456. .ajaxStart(function() { $(this.body).addClass("loading"); })
  457. .ajaxStop(function() { $(this.body).removeClass("loading"); });
  458. $.futon.storage.declare("sidebar", {scope: "cookie", defaultValue: "show"});
  459. $.futon.storage.declare("recent", {scope: "cookie", defaultValue: ""});
  460. $(function() {
  461. document.title = "Apache CouchDB - Futon: " + document.title;
  462. if ($.futon.storage.get("sidebar") == "hidden") {
  463. // doing this as early as possible prevents flickering
  464. $(document.body).addClass("fullwidth");
  465. }
  466. $("input[placeholder]").addPlaceholder();
  467. $.get("_sidebar.html", function(resp) {
  468. $("#wrap").append(resp)
  469. .find("#sidebar-toggle").click(function(e) {
  470. $.futon.navigation.toggle(e.shiftKey ? 2500 : 500);
  471. return false;
  472. });
  473. if ($.futon.storage.get("sidebar") == "hidden") {
  474. $.futon.navigation.toggle(0);
  475. }
  476. $.futon.navigation.updateDatabases();
  477. $.futon.navigation.updateSelection();
  478. $.futon.navigation.ready();
  479. $.futon.session.setupSidebar();
  480. $.futon.session.sidebar();
  481. $.couch.info({
  482. success: function(info, status) {
  483. $("#version").text(info.version);
  484. }
  485. });
  486. });
  487. });
  488. })(jQuery);