define('jira/field/create-project-field', ['jira/util/formatter', 'jira/lib/class', 'jira/ajs/dark-features', 'jira/project/project-key-generator', 'jira/project/project-sample', 'aui/inline-dialog', 'wrm/context-path', 'jquery'], function (formatter, Class, DarkFeatures, ProjectKeyGenerator, ProjectSample, InlineDialog, wrmContextPath, jQuery) { 'use strict'; var contextPath = wrmContextPath(); /** * Hooks up the input controls for the create project fields. * * @class CreateProjectField * @extends Class */ return Class.extend({ TIMEOUT_MS: 100, projectNames: [], // TODO get this from the form so that it is only defined in one place maxNameLength: 80, init: function init(options) { this.$element = options.element; this.$nameElement = this.$element.find("input.text[name='name']"); this.$keyElement = this.$element.find("input.text[name='key']"); this.$keyEditedElement = this.$element.find("input[name='keyEdited']"); this.$avatarElement = this.$element.find(".jira-inline-avatar-picker-trigger"); this.eventBus = { src: jQuery(this), trigger: function trigger(name, args) { this.src.trigger(name, args); }, bind: function bind(name, func) { this.src.bind(name, func); } }; var maxKeyLength = +this.$keyElement.attr("maxlength"); if (!maxKeyLength) { // The maxlength attribute should be set, if not though, use a sensible default maxKeyLength = 10; } this.keygen = new ProjectKeyGenerator({ desiredKeyLength: 4, maxKeyLength: maxKeyLength }); this.lastKeyValidated = ""; if (DarkFeatures.isEnabled("addproject.project.sample")) { this.sample = new ProjectSample({ element: this.$element.find("#sample-project-container"), events: this.eventBus }); } // Input restrictions this.$keyElement.attr("style", "text-transform: uppercase"); // Show any existing errors as inline errors this.initialName = this.$nameElement.val(); this.initialKey = this.$keyElement.val(); this.$element.find(".error").addClass("description initial-error"); this.showInitialError(this.$nameElement); this.showInitialError(this.$keyElement); // Poll the name field for updates this.$nameElement.focus(jQuery.proxy(this._bindNameHook, this)); this.$nameElement.blur(jQuery.proxy(this._unbindHook, this)); // Poll the key field for updates this.$keyElement.focus(jQuery.proxy(this._bindKeyHook, this)); this.$keyElement.blur(jQuery.proxy(this._unbindHook, this)); this.$keyElement.blur(jQuery.proxy(this.autofillKeyIfNeeded, this)); // Hook up help icon var $keyHelpElement = this.$keyElement.parent().find("#add-project-key-icon").removeAttr("target data-helplink"); if ($keyHelpElement.length) { new InlineDialog($keyHelpElement, "project-key-help-popup", function (contents, trigger, show) { contents.html(JIRA.Templates.CreateProject.keyHelp()); show(); }, { width: 330, offsetX: -30 }); } if (this.$avatarElement.size()) { // Keep the sample avatar in sync with the selection this.eventBus.trigger("updated.Avatar", this.$avatarElement.attr("src")); this.$avatarElement.bind("AvatarSelected", jQuery.proxy(function () { this.eventBus.trigger("updated.Avatar", this.$avatarElement.attr("src")); }, this)); } this._loadExistingProjects(); }, _loadExistingProjects: function _loadExistingProjects() { var instance = this; // Get the list of existing project keys and names jQuery.ajax({ url: contextPath + "/rest/api/latest/project", success: function success(projects) { projects = projects || []; for (var i = 0, ii = projects.length; i < ii; i++) { instance.projectNames.push(projects[i].name.toUpperCase()); } } }); }, _bindNameHook: function _bindNameHook(e) { this._bindHook(e, this.onNameTimeout); }, _bindKeyHook: function _bindKeyHook(e) { var el = jQuery(e.target); el.data("lastValue", el.val()); this._bindHook(e, this.onKeyTimeout); }, _bindHook: function _bindHook(e, func) { var instance = this; var el = jQuery(e.target); var _hook; _hook = function hook() { instance._unbindHook(e); func.apply(instance); if (el.is(":visible")) { el.data("checkHook", setTimeout(_hook, instance.TIMEOUT_MS)); } }; if (!el.data("checkHook")) { el.data("checkHook", setTimeout(_hook, 0)); } }, _unbindHook: function _unbindHook(e) { var el = jQuery(e.target); clearTimeout(el.data("checkHook")); el.removeData("checkHook"); }, shouldUpdateKey: function shouldUpdateKey() { return this.$keyEditedElement.val() !== "true"; }, setKeyEdited: function setKeyEdited(key) { // If the key is manually edited, do not suggest automatically generated keys anymore // If the key field is cleared, resume suggesting automatically generated keys if (this.$keyElement.data("lastValue") !== key) { this.$keyEditedElement.val(key ? "true" : "false"); } this.$keyElement.data("lastValue", key); }, updateKey: function updateKey(key) { this.$keyElement.val(key); this.validateKey(key); this.eventBus.trigger("updated.Key", key); }, autofillKeyIfNeeded: function autofillKeyIfNeeded() { if (this.shouldUpdateKey()) { var key = this.keygen.generateKey(this.$nameElement.val()); // JRADEV-10797 - Rather than validate the key, // we'll pretend that a key is always invalid if it's less than 1 character long. if (key.length > 1) { this.updateKey(key); } else { // Blank the key without validation. this.$keyElement.val(""); } } }, onNameTimeout: function onNameTimeout() { var name = this.$nameElement.val(); this.validateName(name); this.eventBus.trigger("updated.Name", name); this.autofillKeyIfNeeded(); }, onKeyTimeout: function onKeyTimeout() { var key = this.$keyElement.val(); this.setKeyEdited(key); this.validateKey(key); this.eventBus.trigger("updated.Key", key); }, validateName: function validateName(name) { if (name === this.initialName && this.$nameElement.parent().find(".error").size()) { return; // leave the error on this field until its value is changed. } else { if (name.length > this.maxNameLength) { this.showInlineError(this.$nameElement, this.initialName, formatter.I18n.getText("admin.errors.project.name.too.long", this.maxNameLength)); return; } for (var i = 0, ii = this.projectNames.length; i < ii; i++) { if (name.toUpperCase() === this.projectNames[i]) { this.showInlineError(this.$nameElement, this.initialName, formatter.I18n.getText("admin.errors.project.with.that.name.already.exists")); return; } } } this.hideInlineError(this.$nameElement); }, validateKey: function validateKey(key) { var instance = this; // Only validate the key if it has changed since the last time we validated it var changed = instance.lastKeyValidated !== key; this.lastKeyValidated = key; if (!changed) { return; } if (key) { jQuery.ajax({ url: contextPath + "/rest/api/latest/projectvalidate/key?key=" + key.toUpperCase(), success: function success(errors) { if (errors.errors && errors.errors["projectKey"]) { instance.showInlineError(instance.$keyElement, instance.initialKey, errors.errors["projectKey"]); } else { instance.hideInlineError(instance.$keyElement); } } }); } else { instance.hideInlineError(instance.$keyElement); } }, /** * Show an error for an input element, in the place of its description. * * @param $element * @param initialVal * @param msg */ showInlineError: function showInlineError($element, initialVal, msg) { var $initialErrorElement = $element.parent().find(".initial-error"); // Don't show an inline error if the field holds the initial value and there is an initial error present if ($element.val() === initialVal && $initialErrorElement.length) { this.showInitialError($element); } else { var $errorElement = $element.parent().find(".error.description"); if (!$errorElement.length) { $errorElement = jQuery("
"); $element.parent().append($errorElement); } $errorElement.text(msg); $element.parent().find(".description").hide(); $initialErrorElement.hide(); $errorElement.show(); } }, /** * Hide any errors for an input field shown by showInlineError, and its description. * * @param $element */ hideInlineError: function hideInlineError($element) { $element.parent().find(".description").show(); $element.parent().find(".error.description").hide(); }, showInitialError: function showInitialError($element) { var $initialErrorElement = $element.parent().find(".initial-error"); if ($initialErrorElement.length) { $element.parent().find(".description").hide(); $element.parent().find(".error.description").hide(); $initialErrorElement.show(); } } }); }); AJS.namespace('JIRA.CreateProjectField', null, require('jira/field/create-project-field'));