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

/javascript/profile-package/username-picker.js

https://bitbucket.org/pandapanda/debastabiilne
JavaScript | 272 lines | 197 code | 33 blank | 42 comment | 23 complexity | 29d73f74ff87ef57fd27c1d4de67807d MD5 | raw file
Possible License(s): MIT, BSD-3-Clause, CC-BY-3.0, GPL-3.0
  1. /**
  2. * Code to handle the profile info changer.
  3. */
  4. UsernamePickerView = Backbone.View.extend({
  5. id: "username-picker-container",
  6. setPublicAfterSave_: false,
  7. savePending_: false,
  8. /**
  9. * Whether or not the nickname is acceptable/valid to try and save.
  10. */
  11. nicknameFieldAcceptable_: true,
  12. /**
  13. * Whether or not the username field is acceptable. Note that an empty
  14. * username, while invalid, is acceptable in the field if the user
  15. * had not selected one prior to opening this view.
  16. */
  17. usernameFieldAcceptable_: true,
  18. events: {
  19. "keyup .nickname": "onNicknameKeyup_",
  20. "keyup .username": "onUsernameKeyup_",
  21. "click :input": "onInputClick_",
  22. "click #save-profile-info": "onSaveClick_",
  23. "click #cancel-profile-info": "onCancelClicked_"
  24. },
  25. onInputClick_: function(e) {
  26. // Force focus on the input after a click. This is to work around
  27. // the fact that in IE, the input can sometimes not get focus,
  28. // even though the user is properly typing in it. Since only
  29. // events with focus fire keyup events, we would otherwise miss them.
  30. $(e.target).focus();
  31. },
  32. initialize: function() {
  33. this.template = Templates.get("profile.username-picker");
  34. this.shouldShowUsernameWarning_ = false;
  35. this.keyupTimeout = null;
  36. this.model.bind("validate:nickname", this.onValidateNickname_, this);
  37. this.model.bind("validate:username", this.onValidateUsername_, this);
  38. this.model.bind("savesuccess", this.onSaveSuccess_, this);
  39. this.model.bind("error", this.onSaveRejected_, this);
  40. },
  41. render: function() {
  42. // TODO: Make idempotent
  43. // maybe making the resetFields_ function obsolete
  44. var context = {
  45. username: this.model.get("username"),
  46. nickname: this.model.get("nickname")
  47. },
  48. html = this.template(context);
  49. $(this.el).html(html)
  50. .addClass("modal fade hide")
  51. .modal({
  52. keyboard: true,
  53. backdrop: true
  54. })
  55. .bind("hidden", _.bind(this.resetFields_, this))
  56. .bind("shown", _.bind(this.onPickerShown_, this));
  57. return this;
  58. },
  59. onCancelClicked_: function() {
  60. this.toggle();
  61. },
  62. toggle: function(setPublic) {
  63. $(this.el).modal("toggle");
  64. this.setPublicAfterSave_ = setPublic;
  65. if (setPublic) {
  66. $(".notification.info").show();
  67. $("#save-profile-info").val("Save and make profile public");
  68. }
  69. },
  70. resetFields_: function() {
  71. var nickname = this.model.get("nickname"),
  72. username = this.model.get("username");
  73. this.nicknameFieldAcceptable_ = true;
  74. this.usernameFieldAcceptable_ = true;
  75. this.$(".notification").hide();
  76. this.$(".nickname").val(nickname);
  77. this.$(".username").val(username);
  78. this.$(".example-username").text(username);
  79. this.$(".sidenote").text("").removeClass("success").removeClass("error");
  80. this.$("#save-profile-info").prop("disabled", false).val("Save");
  81. },
  82. onPickerShown_: function() {
  83. // If the user already has a username, be sure that we warn them about
  84. // the holding period that happens if they change it.
  85. Promos.hasUserSeen("Username change warning", function(hasSeen) {
  86. this.shouldShowUsernameWarning_ = !hasSeen;
  87. }, this);
  88. },
  89. // TODO: move elsewhere
  90. /**
  91. * A conservative check to see if a key event is text-modifying.
  92. * This leans towards the side of truthiness - that is if a key is
  93. * unknown, it is deemed to be text modifying.
  94. */
  95. isTextModifyingKeyEvent_: function(e) {
  96. if ((e.altKey && !e.ctrlKey) || e.metaKey ||
  97. // Function keys don't generate text
  98. e.keyCode >= 112 && e.keyCode <= 123) {
  99. return false;
  100. }
  101. switch (e.keyCode) {
  102. case $.ui.keyCode.ALT:
  103. case $.ui.keyCode.CAPS_LOCK:
  104. case $.ui.keyCode.COMMAND:
  105. case $.ui.keyCode.COMMAND_LEFT:
  106. case $.ui.keyCode.COMMAND_RIGHT:
  107. case $.ui.keyCode.CONTROL:
  108. case $.ui.keyCode.DOWN:
  109. case $.ui.keyCode.END:
  110. case $.ui.keyCode.ESCAPE:
  111. case $.ui.keyCode.HOME:
  112. case $.ui.keyCode.INSERT:
  113. case $.ui.keyCode.LEFT:
  114. case $.ui.keyCode.MENU:
  115. case $.ui.keyCode.PAGE_DOWN:
  116. case $.ui.keyCode.PAGE_UP:
  117. case $.ui.keyCode.RIGHT:
  118. case $.ui.keyCode.SHIFT:
  119. case $.ui.keyCode.UP:
  120. case $.ui.keyCode.WINDOWS:
  121. return false;
  122. default:
  123. return true;
  124. }
  125. },
  126. onNicknameKeyup_: function(e) {
  127. if (!this.isTextModifyingKeyEvent_(e)) {
  128. return;
  129. }
  130. this.model.validateNickname(this.getFormValue_(".nickname"));
  131. if (e.keyCode === $.ui.keyCode.ENTER) {
  132. // Treat enter as "tab" to the next field.
  133. this.$(".username").focus();
  134. }
  135. },
  136. onUsernameKeyup_: function(e) {
  137. if (!this.isTextModifyingKeyEvent_(e)) {
  138. return;
  139. }
  140. if (e.keyCode === $.ui.keyCode.ENTER) {
  141. if (!this.$("#save-profile-info").prop("disabled")) {
  142. this.$("#save-profile-info").click();
  143. return;
  144. }
  145. this.onTimeout_();
  146. }
  147. this.$("#save-profile-info").prop("disabled", true);
  148. if (this.shouldShowUsernameWarning_ && this.model.get("username")) {
  149. $(".notification.error").show();
  150. Promos.markAsSeen("Username change warning");
  151. this.shouldShowUsernameWarning_ = false;
  152. }
  153. this.$(".example-username").text(this.getFormValue_(".username"));
  154. if (this.keyupTimeout) {
  155. window.clearTimeout(this.keyupTimeout);
  156. }
  157. this.keyupTimeout = window.setTimeout(_.bind(this.onTimeout_, this), 1000);
  158. },
  159. onTimeout_: function() {
  160. this.showSidenote_(".username-row", "Checking...");
  161. this.model.validateUsername(this.getFormValue_(".username"));
  162. this.keyupTimeout = null;
  163. },
  164. syncSaveButtonState_: function() {
  165. this.$("#save-profile-info").prop(
  166. "disabled",
  167. !this.usernameFieldAcceptable_ || !this.nicknameFieldAcceptable_);
  168. },
  169. onValidateNickname_: function(isValid) {
  170. if (isValid) {
  171. this.showSidenote_(".nickname-row", "");
  172. } else {
  173. this.showSidenote_(".nickname-row", "Can't leave empty.", false);
  174. }
  175. this.usernameFieldAcceptable_ = isValid;
  176. this.syncSaveButtonState_();
  177. },
  178. onValidateUsername_: function(message, isValid) {
  179. this.showSidenote_(".username-row", message, isValid);
  180. // Accept the username if it's unchanged or if it's changed to
  181. // a valid value. Note that users may start with no username, which
  182. // is itself an "invalid" value (empty), but it's acceptable here
  183. // since we don't require they select a new username.
  184. this.usernameFieldAcceptable_ =
  185. this.getFormValue_(".username") === this.model.get("username") ||
  186. isValid;
  187. this.syncSaveButtonState_();
  188. },
  189. getFormValue_: function(selector) {
  190. return $.trim(this.$(selector).val());
  191. },
  192. /**
  193. * Show the message in the specified row's sidenote.
  194. * If isValid === true, show a green checkmark (success),
  195. * if isValid === false, show a red x (error),
  196. * otherwise, don't show any such indicator.
  197. */
  198. showSidenote_: function(rowSelector, message, isValid) {
  199. var jelSidenote = this.$(rowSelector).find(".sidenote"),
  200. message = message || "";
  201. // Note that isValid may be undefined, in which case neither class
  202. // is enabled.
  203. jelSidenote.toggleClass("success", isValid === true);
  204. jelSidenote.toggleClass("error", isValid === false);
  205. jelSidenote.text(message);
  206. },
  207. onSaveClick_: function() {
  208. var nickname = this.getFormValue_(".nickname"),
  209. username = this.getFormValue_(".username"),
  210. attrs = {
  211. nickname: nickname,
  212. username: username
  213. };
  214. if (this.setPublicAfterSave_) {
  215. attrs.isPublic = true;
  216. }
  217. var usernameChange = username != this.model.get("username");
  218. this.model.save(attrs);
  219. $("#save-profile-info").prop("disabled", true);
  220. if (usernameChange) {
  221. // Keep the modal open and wait for a save success, since this
  222. // is an important, ocean-boiling operation.
  223. $("#save-profile-info").val("Saving...");
  224. this.savePending_ = true;
  225. } else {
  226. this.toggle();
  227. }
  228. },
  229. onSaveSuccess_: function() {
  230. if (this.savePending_) {
  231. $(this.el).modal("hide");
  232. this.savePending_ = false;
  233. }
  234. },
  235. onSaveRejected_: function() {
  236. // No difference in behavior right now.
  237. this.onSaveSuccess_();
  238. }
  239. });