1"use strict"; 2 3/* 4Used for the newcustomimage_modal actions 5 6The .data('recipe') value on the outer element determines which 7recipe ID is used as the basis for the new custom image recipe created via 8this modal. 9 10Use newCustomImageModalSetRecipes() to set the recipes available as a base 11for the new custom image. This will manage the addition of radio buttons 12to select the base image (or remove the radio buttons, if there is only a 13single base image available). 14*/ 15 16function newCustomImageModalInit(){ 17 18 var newCustomImgBtn = $("#create-new-custom-image-btn"); 19 var imgCustomModal = $("#new-custom-image-modal"); 20 var invalidNameHelp = $("#invalid-name-help"); 21 var invalidRecipeHelp = $("#invalid-recipe-help"); 22 var nameInput = imgCustomModal.find('input'); 23 24 var invalidNameMsg = "Image names cannot contain spaces or capital letters. The only allowed special character is dash (-)."; 25 var duplicateNameMsg = "An image with this name already exists. Image names must be unique."; 26 var duplicateImageInProjectMsg = "An image with this name already exists in this project." 27 var invalidBaseRecipeIdMsg = "Please select an image to customise."; 28 var missingParentRecipe = "The parent recipe file was not found. Cancel this action, build any target (like 'quilt-native') to force all new layers to clone, and try again"; 29 var unknownError = "Unexpected error: "; 30 31 // set button to "submit" state and enable text entry so user can 32 // enter the custom recipe name 33 showSubmitState(); 34 35 /* capture clicks on radio buttons inside the modal; when one is selected, 36 * set the recipe on the modal 37 */ 38 imgCustomModal.on("click", "[name='select-image']", function(e) { 39 clearRecipeError(); 40 $(".radio").each(function(){ 41 $(this).removeClass("has-error"); 42 }); 43 44 var recipeId = $(e.target).attr('data-recipe'); 45 imgCustomModal.data('recipe', recipeId); 46 }); 47 48 newCustomImgBtn.click(function(e){ 49 // disable the button and text entry 50 showLoadingState(); 51 52 e.preventDefault(); 53 54 var baseRecipeId = imgCustomModal.data('recipe'); 55 56 if (!baseRecipeId) { 57 showRecipeError(invalidBaseRecipeIdMsg); 58 $(".radio").each(function(){ 59 $(this).addClass("has-error"); 60 }); 61 return; 62 } 63 64 if (nameInput.val().length > 0) { 65 libtoaster.createCustomRecipe(nameInput.val(), baseRecipeId, 66 function(ret) { 67 showSubmitState(); 68 if (ret.error !== "ok") { 69 console.warn(ret.error); 70 if (ret.error === "invalid-name") { 71 showNameError(invalidNameMsg); 72 return; 73 } else if (ret.error === "recipe-already-exists") { 74 showNameError(duplicateNameMsg); 75 return; 76 } else if (ret.error === "image-already-exists") { 77 showNameError(duplicateImageInProjectMsg); 78 return; 79 } else if (ret.error === "recipe-parent-not-exist") { 80 showNameError(missingParentRecipe); 81 } else { 82 showNameError(unknownError + ret.error); 83 } 84 } else { 85 imgCustomModal.modal('hide'); 86 imgCustomModal.one('hidden.bs.modal', showSubmitState); 87 window.location.replace(ret.url + '?notify=new'); 88 } 89 }); 90 } 91 }); 92 93 // enable text entry, show "Create image" button text 94 function showSubmitState() { 95 libtoaster.enableAjaxLoadingTimer(); 96 newCustomImgBtn.find('[data-role="loading-state"]').hide(); 97 newCustomImgBtn.find('[data-role="submit-state"]').show(); 98 newCustomImgBtn.removeAttr('disabled'); 99 nameInput.removeAttr('disabled'); 100 } 101 102 // disable text entry, show "Creating image..." button text; 103 // we also disabled the page-level ajax loading spinner while this spinner 104 // is active 105 function showLoadingState() { 106 libtoaster.disableAjaxLoadingTimer(); 107 newCustomImgBtn.find('[data-role="submit-state"]').hide(); 108 newCustomImgBtn.find('[data-role="loading-state"]').show(); 109 newCustomImgBtn.attr('disabled', 'disabled'); 110 nameInput.attr('disabled', 'disabled'); 111 } 112 113 function showNameError(text){ 114 invalidNameHelp.text(text); 115 invalidNameHelp.show(); 116 nameInput.parent().addClass('has-error'); 117 } 118 119 function showRecipeError(text){ 120 invalidRecipeHelp.text(text); 121 invalidRecipeHelp.show(); 122 } 123 124 function clearRecipeError(){ 125 invalidRecipeHelp.hide(); 126 } 127 128 nameInput.on('keyup', function(){ 129 if (nameInput.val().length === 0){ 130 newCustomImgBtn.prop("disabled", true); 131 return 132 } 133 134 if (nameInput.val().search(/[^a-z|0-9|-]/) != -1){ 135 showNameError(invalidNameMsg); 136 newCustomImgBtn.prop("disabled", true); 137 nameInput.parent().addClass('has-error'); 138 } else { 139 invalidNameHelp.hide(); 140 newCustomImgBtn.prop("disabled", false); 141 nameInput.parent().removeClass('has-error'); 142 } 143 }); 144} 145 146/* Set the image recipes which can used as the basis for the custom 147 * image recipe the user is creating 148 * baseRecipes: a list of one or more recipes which can be 149 * used as the base for the new custom image recipe in the format: 150 * [{'id': <recipe ID>, 'name': <recipe name>'}, ...] 151 * 152 * if recipes is a single recipe, just show the text box to set the 153 * name for the new custom image; if recipes contains multiple recipe objects, 154 * show a set of radio buttons so the user can decide which to use as the 155 * basis for the new custom image 156 */ 157function newCustomImageModalSetRecipes(baseRecipes) { 158 var imgCustomModal = $("#new-custom-image-modal"); 159 var imageSelector = $('#new-custom-image-modal [data-role="image-selector"]'); 160 var imageSelectRadiosContainer = $('#new-custom-image-modal [data-role="image-selector-radios"]'); 161 162 // remove any existing radio buttons + labels 163 imageSelector.remove('[data-role="image-radio"]'); 164 165 if (baseRecipes.length === 1) { 166 // hide the radio button container 167 imageSelector.hide(); 168 169 /* set the single recipe ID on the modal as it's the only one 170 * we can build from. 171 */ 172 imgCustomModal.data('recipe', baseRecipes[0].id); 173 } 174 else { 175 // add radio buttons; note that the handlers for the radio buttons 176 // are set in newCustomImageModalInit via event delegation 177 for (var i = 0; i < baseRecipes.length; i++) { 178 var recipe = baseRecipes[i]; 179 imageSelectRadiosContainer.append( 180 '<div class="radio"><label data-role="image-radio">' + 181 '<input type="radio" name="select-image" ' + 182 'data-recipe="' + recipe.id + '">' + 183 recipe.name + 184 '</label></div>' 185 ); 186 } 187 188 /* select the first radio button as default selection. Radio button 189 * groups should always display with an option checked 190 */ 191 imageSelectRadiosContainer.find("input:radio:first").attr("checked", "checked"); 192 193 /* check which radio button is selected by default inside the modal, 194 * and set the recipe on the modal accordingly 195 */ 196 imageSelectRadiosContainer.find("input:radio").each(function(){ 197 if ( $(this).is(":checked") ) { 198 var recipeId = $(this).attr("data-recipe"); 199 imgCustomModal.data("recipe", recipeId); 200 } 201 }); 202 203 // show the radio button container 204 imageSelector.show(); 205 } 206} 207