PageRenderTime 58ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/solutiongrove/academygrove
JavaScript | 236 lines | 169 code | 32 blank | 35 comment | 15 complexity | e9355c2efacd32f6a36a7dc6e9df8b21 MD5 | raw file
Possible License(s): BSD-3-Clause, CC-BY-3.0, GPL-3.0, MIT
  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. "keypress .nickname": "onNicknameKeypress_",
  20. "keypress .username": "onUsernameKeypress_",
  21. "click :input": "onInputClick_",
  22. "click #save-profile-info": "onSaveClick_",
  23. "click #cancel-profile-info": "onCancelClicked_"
  24. },
  25. delegateEvents: function(events) {
  26. $(this.el)
  27. .on(Keys.textChangeEvents,
  28. ".nickname",
  29. Keys.wrapTextChangeHandler(this.onNicknameInput_, this))
  30. .on(Keys.textChangeEvents,
  31. ".username",
  32. Keys.wrapTextChangeHandler(this.onUsernameInput_, this));
  33. UsernamePickerView.__super__.delegateEvents.call(this, events);
  34. },
  35. onInputClick_: function(e) {
  36. // Force focus on the input after a click. This is to work around
  37. // the fact that in IE, the input can sometimes not get focus,
  38. // even though the user is properly typing in it. Since only
  39. // events with focus fire keyup events, we would otherwise miss them.
  40. $(e.target).focus();
  41. },
  42. initialize: function() {
  43. this.template = Templates.get("profile.username-picker");
  44. this.shouldShowUsernameWarning_ = false;
  45. this.model.bind("validate:nickname", this.onValidateNickname_, this);
  46. this.model.bind("validate:username", this.onValidateUsername_, this);
  47. this.model.bind("savesuccess", this.onSaveSuccess_, this);
  48. this.model.bind("error", this.onSaveRejected_, this);
  49. },
  50. render: function() {
  51. // TODO: Make idempotent
  52. // maybe making the resetFields_ function obsolete
  53. var context = {
  54. username: this.model.get("username"),
  55. nickname: this.model.get("nickname")
  56. },
  57. html = this.template(context);
  58. $(this.el).html(html)
  59. .addClass("modal fade hide")
  60. .modal({
  61. keyboard: true,
  62. backdrop: true
  63. })
  64. .bind("hidden", _.bind(this.resetFields_, this))
  65. .bind("shown", _.bind(this.onPickerShown_, this));
  66. return this;
  67. },
  68. onCancelClicked_: function() {
  69. this.toggle();
  70. },
  71. toggle: function(setPublic) {
  72. $(this.el).modal("toggle");
  73. this.setPublicAfterSave_ = setPublic;
  74. if (setPublic) {
  75. $(".notification.info").show();
  76. $("#save-profile-info").val("Save and make profile public");
  77. }
  78. },
  79. resetFields_: function() {
  80. var nickname = this.model.get("nickname"),
  81. username = this.model.get("username");
  82. this.nicknameFieldAcceptable_ = true;
  83. this.usernameFieldAcceptable_ = true;
  84. this.$(".notification").hide();
  85. this.$(".nickname").val(nickname);
  86. this.$(".username").val(username);
  87. this.$(".example-username").text(username);
  88. this.$(".sidenote").text("").removeClass("success").removeClass("error");
  89. this.$("#save-profile-info").prop("disabled", false).val("Save");
  90. },
  91. onPickerShown_: function() {
  92. // If the user already has a username, be sure that we warn them about
  93. // the holding period that happens if they change it.
  94. Promos.hasUserSeen("Username change warning", function(hasSeen) {
  95. this.shouldShowUsernameWarning_ = !hasSeen;
  96. }, this);
  97. },
  98. onNicknameInput_: function(e) {
  99. this.model.validateNickname(this.getFormValue_(".nickname"));
  100. },
  101. onNicknameKeypress_: function(e) {
  102. if (e.keyCode === $.ui.keyCode.ENTER) {
  103. // Treat enter as "tab" to the next field.
  104. this.$(".username").focus();
  105. }
  106. },
  107. onUsernameKeypress_: function(e) {
  108. if (e.keyCode === $.ui.keyCode.ENTER) {
  109. if (!this.$("#save-profile-info").prop("disabled")) {
  110. this.$("#save-profile-info").click();
  111. }
  112. this.model.validateUsername(this.getFormValue_(".username"));
  113. }
  114. },
  115. onUsernameInput_: function(e) {
  116. this.$("#save-profile-info").prop("disabled", true);
  117. if (this.shouldShowUsernameWarning_ && this.model.get("username")) {
  118. $(".notification.error").show();
  119. Promos.markAsSeen("Username change warning");
  120. this.shouldShowUsernameWarning_ = false;
  121. }
  122. this.$(".example-username").text(this.getFormValue_(".username"));
  123. this.showSidenote_(".username-row", "Checking...");
  124. this.debouncedValidateUsername_();
  125. },
  126. debouncedValidateUsername_: _.debounce(function() {
  127. this.model.validateUsername(this.getFormValue_(".username"));
  128. }, 1000),
  129. syncSaveButtonState_: function() {
  130. this.$("#save-profile-info").prop(
  131. "disabled",
  132. !this.usernameFieldAcceptable_ || !this.nicknameFieldAcceptable_);
  133. },
  134. onValidateNickname_: function(isValid) {
  135. if (isValid) {
  136. this.showSidenote_(".nickname-row", "");
  137. } else {
  138. this.showSidenote_(".nickname-row", "Can't leave empty.", false);
  139. }
  140. this.usernameFieldAcceptable_ = isValid;
  141. this.syncSaveButtonState_();
  142. },
  143. onValidateUsername_: function(message, isValid) {
  144. this.showSidenote_(".username-row", message, isValid);
  145. // Accept the username if it's unchanged or if it's changed to
  146. // a valid value. Note that users may start with no username, which
  147. // is itself an "invalid" value (empty), but it's acceptable here
  148. // since we don't require they select a new username.
  149. this.usernameFieldAcceptable_ =
  150. this.getFormValue_(".username") === this.model.get("username") ||
  151. isValid;
  152. this.syncSaveButtonState_();
  153. },
  154. getFormValue_: function(selector) {
  155. return $.trim(this.$(selector).val());
  156. },
  157. /**
  158. * Show the message in the specified row's sidenote.
  159. * If isValid === true, show a green checkmark (success),
  160. * if isValid === false, show a red x (error),
  161. * otherwise, don't show any such indicator.
  162. */
  163. showSidenote_: function(rowSelector, message, isValid) {
  164. var jelSidenote = this.$(rowSelector).find(".sidenote"),
  165. message = message || "";
  166. // Note that isValid may be undefined, in which case neither class
  167. // is enabled.
  168. jelSidenote.toggleClass("success", isValid === true);
  169. jelSidenote.toggleClass("error", isValid === false);
  170. jelSidenote.text(message);
  171. },
  172. onSaveClick_: function() {
  173. var nickname = this.getFormValue_(".nickname"),
  174. username = this.getFormValue_(".username"),
  175. attrs = {
  176. nickname: nickname,
  177. username: username
  178. };
  179. if (this.setPublicAfterSave_) {
  180. attrs.isPublic = true;
  181. }
  182. var usernameChange = username != this.model.get("username");
  183. this.model.save(attrs);
  184. $("#save-profile-info").prop("disabled", true);
  185. if (usernameChange) {
  186. // Keep the modal open and wait for a save success, since this
  187. // is an important, ocean-boiling operation.
  188. $("#save-profile-info").val("Saving...");
  189. this.savePending_ = true;
  190. } else {
  191. this.toggle();
  192. }
  193. },
  194. onSaveSuccess_: function() {
  195. if (this.savePending_) {
  196. $(this.el).modal("hide");
  197. this.savePending_ = false;
  198. }
  199. },
  200. onSaveRejected_: function() {
  201. // No difference in behavior right now.
  202. this.onSaveSuccess_();
  203. }
  204. });