http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/brooklyn-ui/src/main/webapp/assets/js/view/application-add-wizard.js ---------------------------------------------------------------------- diff --git a/brooklyn-ui/src/main/webapp/assets/js/view/application-add-wizard.js b/brooklyn-ui/src/main/webapp/assets/js/view/application-add-wizard.js deleted file mode 100644 index 2c4f012..0000000 --- a/brooklyn-ui/src/main/webapp/assets/js/view/application-add-wizard.js +++ /dev/null @@ -1,838 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. -*/ -/** - * Builds a Twitter Bootstrap modal as the framework for a Wizard. - * Also creates an empty Application model. - */ -define([ - "underscore", "jquery", "backbone", "brooklyn-utils", "js-yaml", - "model/entity", "model/application", "model/location", "model/catalog-application", - "text!tpl/app-add-wizard/modal-wizard.html", - "text!tpl/app-add-wizard/create.html", - "text!tpl/app-add-wizard/create-step-template-entry.html", - "text!tpl/app-add-wizard/create-entity-entry.html", - "text!tpl/app-add-wizard/required-config-entry.html", - "text!tpl/app-add-wizard/edit-config-entry.html", - "text!tpl/app-add-wizard/deploy.html", - "text!tpl/app-add-wizard/deploy-version-option.html", - "text!tpl/app-add-wizard/deploy-location-row.html", - "text!tpl/app-add-wizard/deploy-location-option.html", - "bootstrap" - -], function (_, $, Backbone, Util, JsYaml, Entity, Application, Location, CatalogApplication, - ModalHtml, CreateHtml, CreateStepTemplateEntryHtml, CreateEntityEntryHtml, - RequiredConfigEntryHtml, EditConfigEntryHtml, DeployHtml, - DeployVersionOptionHtml, DeployLocationRowHtml, DeployLocationOptionHtml -) { - - /** Special ID to indicate that no locations will be provided when starting the server. */ - var NO_LOCATION_INDICATOR = "__NONE__"; - - function setVisibility(obj, isVisible) { - if (isVisible) obj.show(); - else obj.hide(); - } - - function setEnablement(obj, isEnabled) { - obj.attr("disabled", !isEnabled) - } - - /** converts old-style spec with "entities" to camp-style spec with services */ - function oldSpecToCamp(spec) { - var services; - if (spec.type) { - services = [entityToCamp({type: spec.type, version: spec.version, config: spec.config})]; - } else if (spec.entities) { - services = []; - var entities = spec.entities; - for (var i = 0; i < entities.length; i++) { - services.push(entityToCamp(entities[i])); - } - } - var result = {}; - if (spec.name) result.name = spec.name; - if (spec.locations) { - if (spec.locations.length>1) - result.locations = spec.locations; - else - result.location = spec.locations[0]; - } - if (services) result.services = services; - // NB: currently nothing else is supported in this spec - return result; - } - function entityToCamp(entity) { - var result = {}; - if (entity.name && (!options || !options.exclude_name)) result.name = entity.name; - if (entity.type) result.type = entity.type; - if (entity.type && entity.version) result.type += ":" + entity.version; - if (entity.config && _.size(entity.config)) result["brooklyn.config"] = entity.config; - return result; - } - function getConvertedConfigValue(value) { - try { - return $.parseJSON(value); - } catch (e) { - return value; - } - } - - var ModalWizard = Backbone.View.extend({ - tagName:'div', - className:'modal hide fade', - events:{ - 'click #prev_step':'prevStep', - 'click #next_step':'nextStep', - 'click #preview_step':'previewStep', - 'click #finish_step':'finishStep' - }, - template:_.template(ModalHtml), - initialize:function () { - this.catalog = {} - this.catalog.applications = {} - this.model = {} - this.model.spec = new Application.Spec; - this.model.yaml = ""; - this.model.mode = "template"; // or "yaml" or "other" - this.currentStep = 0; - this.steps = [ - { - step_id:'what-app', - title:'Create Application', - instructions:'Choose or build the application to deploy', - view:new ModalWizard.StepCreate({ model:this.model, wizard: this, catalog: this.catalog }) - }, - { - // TODO rather than make this another step -- since we now on preview revert to the yaml tab - // this should probably be shown in the catalog tab, replacing the other contents. - step_id:'name-and-locations', - title:'<%= appName %>', - instructions:'Specify the locations to deploy to and any additional configuration', - view:new ModalWizard.StepDeploy({ model:this.model, catalog: this.catalog }) - } - ] - }, - beforeClose:function () { - // ensure we close the sub-views - _.each(this.steps, function (step) { - step.view.close() - }, this) - }, - render:function () { - this.$el.html(this.template({})) - this.renderCurrentStep() - return this - }, - - renderCurrentStep:function (callback) { - var name = this.model.name || ""; - this.title = this.$("h3#step_title") - this.instructions = this.$("p#step_instructions") - - var currentStepObj = this.steps[this.currentStep] - this.title.html(_.template(currentStepObj.title)({appName: name})); - this.instructions.html(currentStepObj.instructions) - this.currentView = currentStepObj.view - - // delegate to sub-views !! - this.currentView.render() - this.currentView.updateForState() - this.$(".modal-body").replaceWith(this.currentView.el) - if (callback) callback(this.currentView); - - this.updateButtonVisibility(); - }, - updateButtonVisibility:function () { - var currentStepObj = this.steps[this.currentStep] - - setVisibility(this.$("#prev_step"), (this.currentStep > 0)) - - // next shown for first step, but not for yaml - var nextVisible = (this.currentStep < 1) && (this.model.mode != "yaml") - setVisibility(this.$("#next_step"), nextVisible) - - // previous shown for step 2 (but again, not yaml) - var previewVisible = (this.currentStep == 1) && (this.model.mode != "yaml") - setVisibility(this.$("#preview_step"), previewVisible) - - // now set next/preview enablement - if (nextVisible || previewVisible) { - var nextEnabled = true; - if (this.currentStep==0 && this.model.mode=="template" && currentStepObj && currentStepObj.view) { - // disable if this is template selction (lozenge) view, and nothing is selected - if (! currentStepObj.view.selectedTemplate) - nextEnabled = false; - } - - if (nextVisible) - setEnablement(this.$("#next_step"), nextEnabled) - if (previewVisible) - setEnablement(this.$("#preview_step"), nextEnabled) - } - - // finish from config step, preview step, and from first step if yaml tab selected (and valid) - var finishVisible = (this.currentStep >= 1) - var finishEnabled = finishVisible - if (!finishEnabled && this.currentStep==0) { - if (this.model.mode == "yaml") { - // should do better validation than non-empty - finishVisible = true; - var yaml_code = this.$("#yaml_code").val() - if (yaml_code) { - finishEnabled = true; - } - } - } - setVisibility(this.$("#finish_step"), finishVisible) - setEnablement(this.$("#finish_step"), finishEnabled) - }, - - submitApplication:function (event) { - var that = this - var $modal = $('.add-app #modal-container .modal') - $modal.fadeTo(500,0.5); - - var yaml; - if (this.model.mode == "yaml") { - yaml = this.model.yaml; - } else { - // Drop any "None" locations. - this.model.spec.pruneLocations(); - yaml = JsYaml.safeDump(oldSpecToCamp(this.model.spec.toJSON())); - } - - $.ajax({ - url:'/v1/applications', - type:'post', - contentType:'application/yaml', - processData:false, - data:yaml, - success:function (data) { - that.onSubmissionComplete(true, data, $modal) - }, - error:function (data) { - that.onSubmissionComplete(false, data, $modal) - } - }); - - return false - }, - onSubmissionComplete: function(succeeded, data, $modal) { - var that = this; - if (succeeded) { - $modal.modal('hide') - $modal.fadeTo(500,1); - if (that.options.callback) that.options.callback(); - } else { - log("ERROR submitting application: "+data.responseText); - var response, summary="Server responded with an error"; - try { - if (data.responseText) { - response = JSON.parse(data.responseText) - if (response) { - summary = response.message; - } - } - } catch (e) { - summary = data.responseText; - } - that.$el.fadeTo(100,1).delay(200).fadeTo(200,0.2).delay(200).fadeTo(200,1); - that.steps[that.currentStep].view.showFailure(summary) - } - }, - - prevStep:function () { - this.currentStep -= 1; - this.renderCurrentStep(); - }, - nextStep:function () { - if (this.currentStep == 0) { - if (this.currentView.validate()) { - var yaml = (this.currentView && this.currentView.selectedTemplate && this.currentView.selectedTemplate.yaml); - if (yaml) { - try { - yaml = JsYaml.safeLoad(yaml); - hasLocation = yaml.location || yaml.locations; - if (!hasLocation) { - // look for locations defined in locations - svcs = yaml.services; - if (svcs) { - for (svcI in svcs) { - if (svcs[svcI].location || svcs[svcI].locations) { - hasLocation = true; - break; - } - } - } - } - yaml = (hasLocation ? true : false); - } catch (e) { - log("Warning: could not parse yaml template") - log(yaml); - yaml = false; - } - } - if (yaml) { - // it's a yaml catalog template which includes a location, show the yaml tab - $("ul#app-add-wizard-create-tab").find("a[href='#yamlTab']").tab('show'); - $("#yaml_code").setCaretToStart(); - } else { - // it's a java catalog template or yaml template without a location, go to wizard - this.currentStep += 1; - this.renderCurrentStep(); - } - } else { - // the call to validate will have done the showFailure - } - } else { - throw "Unexpected step: "+this.currentStep; - } - }, - previewStep:function () { - if (this.currentView.validate()) { - this.currentStep = 0; - var that = this; - this.renderCurrentStep(function callback(view) { - // Drop any "None" locations. - that.model.spec.pruneLocations(); - $("textarea#yaml_code").val(JsYaml.safeDump(oldSpecToCamp(that.model.spec.toJSON()))); - $("ul#app-add-wizard-create-tab").find("a[href='#yamlTab']").tab('show'); - $("#yaml_code").setCaretToStart(); - }); - } else { - // call to validate should showFailure - } - }, - finishStep:function () { - if (this.currentView.validate()) { - this.submitApplication() - } else { - // call to validate should showFailure - } - } - }) - - // Note: this does not restore values on a back click; setting type and entity type+name is easy, - // but relevant config lines is a little bit more tedious - ModalWizard.StepCreate = Backbone.View.extend({ - className:'modal-body', - events:{ - 'click #add-app-entity':'addEntityBox', - 'click .editable-entity-heading':'expandEntity', - 'click .remove-entity-button':'removeEntityClick', - 'click .editable-entity-button':'saveEntityClick', - 'click #remove-config':'removeConfigRow', - 'click #add-config':'addConfigRow', - 'click .template-lozenge':'templateClick', - 'keyup .text-filter input':'applyFilter', - 'change .text-filter input':'applyFilter', - 'paste .text-filter input':'applyFilter', - 'keyup #yaml_code':'onYamlCodeChange', - 'change #yaml_code':'onYamlCodeChange', - 'paste #yaml_code':'onYamlCodeChange', - 'shown a[data-toggle="tab"]':'onTabChange', - 'click #templateTab #catalog-add':'switchToCatalogAdd', - 'click #templateTab #catalog-yaml':'showYamlTab' - }, - template:_.template(CreateHtml), - wizard: null, - initialize:function () { - var self = this - self.catalogEntityIds = [] - - this.$el.html(this.template({})) - - // for building from entities - this.addEntityBox() - - // TODO: Make into models, allow options to override, then pass in in test - // with overrridden url. Can then think about fixing tests in application-add-wizard-spec.js. - $.get('/v1/catalog/entities', {}, function (result) { - self.catalogEntityItems = result - self.catalogEntityIds = _.map(result, function(item) { return item.id }) - self.$(".entity-type-input").typeahead().data('typeahead').source = self.catalogEntityIds - }) - this.options.catalog.applications = new CatalogApplication.Collection(); - this.options.catalog.applications.fetch({ - data: $.param({ - allVersions: true - }), - success: function (collection, response, options) { - self.$("#appClassTab .application-type-input").typeahead().data('typeahead').source = collection.getTypes(); - $('#catalog-applications-throbber').hide(); - $('#catalog-applications-empty').hide(); - if (collection.size() > 0) { - self.addTemplateLozenges() - } else { - $('#catalog-applications-empty').show(); - self.showYamlTab(); - } - } - }); - }, - renderConfiguredEntities:function () { - var $configuredEntities = this.$('#entitiesAccordionish').empty() - var that = this - if (this.model.spec.get("entities") && this.model.spec.get("entities").length > 0) { - _.each(this.model.spec.get("entities"), function (entity) { - that.addEntityHtml($configuredEntities, entity) - }) - } - }, - updateForState: function () {}, - render:function () { - this.renderConfiguredEntities() - this.delegateEvents() - return this - }, - onTabChange: function(e) { - var tabText = $(e.target).text(); - if (tabText=="Catalog") { - $("li.text-filter").show() - } else { - $("li.text-filter").hide() - } - - if (tabText=="YAML") { - this.model.mode = "yaml"; - } else if (tabText=="Template") { - this.model.mode = "template"; - } else { - this.model.mode = "other"; - } - - if (this.options.wizard) - this.options.wizard.updateButtonVisibility(); - }, - onYamlCodeChange: function() { - if (this.options.wizard) - this.options.wizard.updateButtonVisibility(); - }, - switchToCatalogAdd: function() { - var $modal = $('.add-app #modal-container .modal') - $modal.modal('hide'); - window.location.href="#v1/catalog/new"; - }, - showYamlTab: function() { - $("ul#app-add-wizard-create-tab").find("a[href='#yamlTab']").tab('show') - $("#yaml_code").focus(); - }, - applyFilter: function(e) { - var filter = $(e.currentTarget).val().toLowerCase() - if (!filter) { - $(".template-lozenge").show() - } else { - _.each($(".template-lozenge"), function(it) { - var viz = $(it).text().toLowerCase().indexOf(filter)>=0 - if (viz) - $(it).show() - else - $(it).hide() - }) - } - }, - addTemplateLozenges: function(event) { - var that = this - _.each(this.options.catalog.applications.getDistinctApplications(), function(item) { - that.addTemplateLozenge(that, item[0]) - }) - }, - addTemplateLozenge: function(that, item) { - var $tempel = _.template(CreateStepTemplateEntryHtml, { - id: item.get('id'), - type: item.get('type'), - name: item.get('name') || item.get('id'), - description: item.get('description'), - planYaml: item.get('planYaml'), - iconUrl: item.get('iconUrl') - }) - $("#create-step-template-entries", that.$el).append($tempel) - }, - templateClick: function(event) { - var $tl = $(event.target).closest(".template-lozenge"); - var wasSelected = $tl.hasClass("selected") - $(".template-lozenge").removeClass("selected") - if (!wasSelected) { - $tl.addClass("selected") - this.selectedTemplate = { - id: $tl.attr('id'), - type: $tl.data('type'), - name: $tl.data("name"), - yaml: $tl.data("yaml"), - }; - if (this.selectedTemplate.yaml) { - $("textarea#yaml_code").val(this.selectedTemplate.yaml); - } else { - $("textarea#yaml_code").val("services:\n- type: "+this.selectedTemplate.type); - } - } else { - this.selectedTemplate = null; - } - - if (this.options.wizard) - this.options.wizard.updateButtonVisibility(); - }, - expandEntity:function (event) { - $(event.currentTarget).next().show('fast').delay(1000).prev().hide('slow') - }, - saveEntityClick:function (event) { - this.saveEntity($(event.currentTarget).closest(".editable-entity-group")); - }, - saveEntity:function ($entityGroup) { - var that = this - var name = $('#entity-name',$entityGroup).val() - var type = $('#entity-type',$entityGroup).val() - if (type=="" || !_.contains(that.catalogEntityIds, type)) { - that.showFailure("Missing or invalid type"); - return false - } - var saveTarget = this.model.spec.get("entities")[$entityGroup.index()]; - this.model.spec.set("type", null) - saveTarget.name = name - saveTarget.type = type - saveTarget.config = this.getConfigMap($entityGroup) - - if (name=="") name=type; - if (name=="") name="<i>(new entity)</i>"; - $('#entity-name-header',$entityGroup).html( name ) - $('.editable-entity-body',$entityGroup).prev().show('fast').next().hide('fast') - return true; - }, - getConfigMap:function (root) { - var map = {} - $('.app-add-wizard-config-entry',root).each( function (index,elt) { - var value = getConvertedConfigValue($('#value',elt).val()); - if (value !== null) { - map[$('#key',elt).val()] = value; - } - }) - return map; - }, - saveTemplate:function () { - if (!this.selectedTemplate) return false - var type = this.selectedTemplate.type; - if (!this.options.catalog.applications.hasType(type)) { - $('.entity-info-message').show('slow').delay(2000).hide('slow') - return false - } - - this.model.spec.set("type", type); - this.model.name = this.selectedTemplate.name; - this.model.catalogEntityData = "LOAD" - return true; - }, - saveAppClass:function () { - var that = this - var tab = $.find('#appClassTab') - var type = $(tab).find('#app-java-type').val() - if (!this.options.catalog.applications.hasType(type)) { - $('.entity-info-message').show('slow').delay(2000).hide('slow') - return false - } - this.model.spec.set("type", type); - return true; - }, - addEntityBox:function () { - var entity = new Entity.Model - this.model.spec.addEntity( entity ) - this.addEntityHtml($('#entitiesAccordionish', this.$el), entity) - }, - addEntityHtml:function (parent, entity) { - var $entity = _.template(CreateEntityEntryHtml, {}) - var that = this - parent.append($entity) - parent.children().last().find('.entity-type-input').typeahead({ source: that.catalogEntityIds }) - }, - removeEntityClick:function (event) { - var $entityGroup = $(event.currentTarget).parent().parent().parent(); - this.model.spec.removeEntityIndex($entityGroup.index()) - $entityGroup.remove() - }, - - addConfigRow:function (event) { - var $row = _.template(EditConfigEntryHtml, {}) - $(event.currentTarget).parent().prev().append($row) - }, - removeConfigRow:function (event) { - $(event.currentTarget).parent().remove() - }, - - validate:function () { - var that = this - var tabName = $('#app-add-wizard-create-tab li[class="active"] a').attr('href') - if (tabName=='#entitiesTab') { - delete this.model.spec.attributes["id"] - var allokay = true - $($.find('.editable-entity-group')).each( - function (i,$entityGroup) { - allokay = that.saveEntity($($entityGroup)) & allokay - }) - if (!allokay) return false; - if (this.model.spec.get("entities") && this.model.spec.get("entities").length > 0) { - this.model.spec.set("type", null); - return true; - } - } else if (tabName=='#templateTab') { - delete this.model.spec.attributes["id"] - if (this.saveTemplate()) { - this.model.spec.set("entities", []); - return true - } - } else if (tabName=='#appClassTab') { - delete this.model.spec.attributes["id"] - if (this.saveAppClass()) { - this.model.spec.set("entities", []); - return true - } - } else if (tabName=='#yamlTab') { - this.model.yaml = this.$("#yaml_code").val(); - if (this.model.yaml) { - return true; - } - } else { - console.info("NOT IMPLEMENTED YET") - // TODO - other tabs not implemented yet - // do nothing, show error return false below - } - this.showFailure("Invalid application type/spec"); - return false - }, - - showFailure: function(text) { - if (!text) text = "Failure performing the specified action"; - this.$('div.error-message .error-message-text').html(_.escape(text)); - this.$('div.error-message').slideDown(250).delay(10000).slideUp(500); - } - - }) - - ModalWizard.StepDeploy = Backbone.View.extend({ - className:'modal-body', - - events:{ - 'click #add-selector-container':'addLocation', - 'click #remove-app-location':'removeLocation', - 'change .select-version': 'selectionVersion', - 'change .select-location': 'selectionLocation', - 'blur #application-name':'updateName', - 'click #remove-config':'removeConfigRow', - 'click #add-config':'addConfigRow' - }, - - template:_.template(DeployHtml), - versionOptionTemplate:_.template(DeployVersionOptionHtml), - locationRowTemplate:_.template(DeployLocationRowHtml), - locationOptionTemplate:_.template(DeployLocationOptionHtml), - - initialize:function () { - this.model.spec.on("change", this.render, this) - this.$el.html(this.template()) - this.locations = new Location.Collection() - }, - beforeClose:function () { - this.model.spec.off("change", this.render) - }, - renderName:function () { - this.$('#application-name').val(this.model.spec.get("name")) - }, - renderVersions: function() { - var optionTemplate = this.versionOptionTemplate - select = this.$('.select-version') - container = this.$('#app-versions') - defaultVersion = '0.0.0.SNAPSHOT'; - - select.empty(); - - var versions = this.options.catalog.applications.getVersions(this.model.spec.get('type')); - for (var vi = 0; vi < versions.length; vi++) { - var version = versions[vi]; - select.append(optionTemplate({ - version: version - })); - } - - if (versions.length === 1 && versions[0] === defaultVersion) { - this.model.spec.set('version', ''); - container.hide(); - } else { - this.model.spec.set('version', versions[0]); - container.show(); - } - }, - renderAddedLocations:function () { - // renders the locations added to the model - var rowTemplate = this.locationRowTemplate, - optionTemplate = this.locationOptionTemplate, - container = this.$("#selector-container-location"); - container.empty(); - for (var li = 0; li < this.model.spec.get("locations").length; li++) { - var chosenLocation = this.model.spec.get("locations")[li]; - container.append(rowTemplate({ - initialValue: chosenLocation, - rowId: li - })); - } - var $locationOptions = container.find('.select-location'); - var templated = this.locations.map(function(aLocation) { - return optionTemplate({ - id: aLocation.id || "", - name: aLocation.getPrettyName() - }); - }); - - // insert "none" location - $locationOptions.append(templated.join("")); - $locationOptions.each(function(i) { - var option = $($locationOptions[i]); - option.val(option.parent().attr('initialValue')); - // Only append dashes if there are any locations - if (option.find("option").length > 0) { - option.append("<option disabled>------</option>"); - } - option.append(optionTemplate({ - id: NO_LOCATION_INDICATOR, - name: "None" - })); - }); - }, - render:function () { - this.delegateEvents() - return this - }, - updateForState: function () { - var that = this - // clear any error message (we are being displayed fresh; if there are errors in the update, we'll show them in code below) - this.$('div.error-message').hide(); - this.renderName() - this.renderVersions() - this.locations.fetch({ - success:function () { - if (that.model.spec.get("locations").length==0) - that.addLocation() - else - that.renderAddedLocations() - }}) - - if (this.model.catalogEntityData==null) { - this.renderStaticConfig(null) - } else if (this.model.catalogEntityData=="LOAD") { - this.renderStaticConfig("LOADING") - $.get('/v1/catalog/entities/'+this.model.spec.get("type"), {}, function (result) { - that.model.catalogEntityData = result - that.renderStaticConfig(that.model.catalogEntityData) - }) - } else { - this.renderStaticConfig(this.model.catalogEntityData) - } - }, - addLocation:function () { - if (this.locations.models.length>0) { - this.model.spec.addLocation(this.locations.models[0].get("id")) - } else { - // i.e. No location - this.model.spec.addLocation(undefined); - } - this.renderAddedLocations() - }, - removeLocation:function (event) { - var toBeRemoved = $(event.currentTarget).parent().attr('rowId') - this.model.spec.removeLocationIndex(toBeRemoved) - this.renderAddedLocations() - }, - addConfigRow:function (event) { - var $row = _.template(EditConfigEntryHtml, {}) - $(event.currentTarget).parent().prev().append($row) - }, - removeConfigRow:function (event) { - $(event.currentTarget).parent().parent().remove() - }, - renderStaticConfig:function (catalogEntryItem) { - this.$('.config-table').html('') - if (catalogEntryItem=="LOADING") { - this.$('.required-config-loading').show() - } else { - var configs = [] - this.$('.required-config-loading').hide() - if (catalogEntryItem!=null && catalogEntryItem.config!=null) { - var that = this - _.each(catalogEntryItem.config, function (cfg) { - if (cfg.priority !== undefined) { - var html = _.template(RequiredConfigEntryHtml, {data:cfg}); - that.$('.config-table').append(html) - } - }) - } - } - }, - getConfigMap:function() { - var map = {}; - $('.app-add-wizard-config-entry').each( function (index,elt) { - var value = $('#checkboxValue',elt).length ? $('#checkboxValue',elt).is(':checked') : - getConvertedConfigValue($('#value',elt).val()); - if (value !== null) { - map[$('#key',elt).val()] = value; - } - }) - return map; - }, - selectionVersion:function (event) { - this.model.spec.set("version", $(event.currentTarget).val()) - }, - selectionLocation:function (event) { - var loc_id = $(event.currentTarget).val(), - isNoneLocation = loc_id === NO_LOCATION_INDICATOR; - var locationValid = isNoneLocation || this.locations.find(function (candidate) { - return candidate.get("id")==loc_id; - }); - if (!locationValid) { - log("invalid location "+loc_id); - this.showFailure("Invalid location "+loc_id); - this.model.spec.set("locations",[]); - } else { - var index = $(event.currentTarget).parent().attr('rowId'); - this.model.spec.setLocationAtIndex(index, isNoneLocation ? undefined : loc_id); - } - }, - updateName:function () { - var name = this.$('#application-name').val(); - if (name) - this.model.spec.set("name", name); - else - this.model.spec.set("name", ""); - }, - validate:function () { - this.model.spec.set("config", this.getConfigMap()) - if (this.model.spec.get("locations").length !== 0) { - return true - } else { - this.showFailure("A location is required"); - return false; - } - }, - showFailure: function(text) { - if (!text) text = "Failure performing the specified action"; - log("showing error: "+text); - this.$('div.error-message .error-message-text').html(_.escape(text)); - // flash the error, but make sure it goes away (we do not currently have any other logic for hiding this error message) - this.$('div.error-message').slideDown(250).delay(10000).slideUp(500); - } - }) - - return ModalWizard -})
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/brooklyn-ui/src/main/webapp/assets/js/view/application-explorer.js ---------------------------------------------------------------------- diff --git a/brooklyn-ui/src/main/webapp/assets/js/view/application-explorer.js b/brooklyn-ui/src/main/webapp/assets/js/view/application-explorer.js deleted file mode 100644 index e9c23bc..0000000 --- a/brooklyn-ui/src/main/webapp/assets/js/view/application-explorer.js +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. -*/ -/** - * This should render the main content in the Application Explorer page. - * Components on this page should be rendered as sub-views. - * @type {*} - */ -define([ - "underscore", "jquery", "backbone", "view/viewutils", - "./application-add-wizard", "model/application", "model/entity-summary", "model/app-tree", "./application-tree", "./entity-details", - "text!tpl/apps/details.html", "text!tpl/apps/entity-not-found.html", "text!tpl/apps/page.html" -], function (_, $, Backbone, ViewUtils, - AppAddWizard, Application, EntitySummary, AppTree, ApplicationTreeView, EntityDetailsView, - EntityDetailsEmptyHtml, EntityNotFoundHtml, PageHtml) { - - var ApplicationExplorerView = Backbone.View.extend({ - tagName:"div", - className:"container container-fluid", - id:'application-explorer', - template:_.template(PageHtml), - notFoundTemplate: _.template(EntityNotFoundHtml), - events:{ - 'click .application-tree-refresh': 'refreshApplicationsInPlace', - 'click #add-new-application':'createApplication', - 'click .delete':'deleteApplication' - }, - initialize: function () { - this.$el.html(this.template({})) - $(".nav1").removeClass("active"); - $(".nav1_apps").addClass("active"); - - this.treeView = new ApplicationTreeView({ - collection:this.collection, - appRouter:this.options.appRouter - }) - this.treeView.on('entitySelected', function(e) { - this.displayEntityId(e.id, e.get('applicationId'), false); - }, this); - this.$('div#app-tree').html(this.treeView.renderFull().el) - this.$('div#details').html(EntityDetailsEmptyHtml); - - ViewUtils.fetchRepeatedlyWithDelay(this, this.collection) - }, - refreshApplicationsInPlace: function() { - // fetch without reset sets of change events, which now get handled correctly - // (not a full visual recompute, which reset does - both in application-tree.js) - this.collection.fetch(); - }, - beforeClose: function () { - this.collection.off("reset", this.render); - this.treeView.close(); - if (this.detailsView) - this.detailsView.close(); - }, - show: function(entityId) { - var tab = ""; - var tabDetails = ""; - if (entityId) { - if (entityId[0]=='/') entityId = entityId.substring(1); - var slash = entityId.indexOf('/'); - if (slash>0) { - tab = entityId.substring(slash+1) - entityId = entityId.substring(0, slash); - } - } - if (tab) { - var slash = tab.indexOf('/'); - if (slash>0) { - tabDetails = tab.substring(slash+1) - tab = tab.substring(0, slash); - } - this.preselectTab(tab, tabDetails); - } - this.treeView.selectEntity(entityId) - }, - createApplication:function () { - var that = this; - if (this._modal) { - this._modal.close() - } - var wizard = new AppAddWizard({ - appRouter:that.options.appRouter, - callback:function() { that.refreshApplicationsInPlace() } - }) - this._modal = wizard - this.$(".add-app #modal-container").html(wizard.render().el) - this.$(".add-app #modal-container .modal") - .on("hidden",function () { - wizard.close() - }).modal('show') - }, - deleteApplication:function (event) { - // call Backbone destroy() which does HTTP DELETE on the model - this.collection.get(event.currentTarget['id']).destroy({wait:true}) - }, - /** - * Causes the tab with the given name to be selected automatically when - * the view is next rendered. - */ - preselectTab: function(tab, tabDetails) { - this.currentTab = tab; - this.currentTabDetails = tabDetails; - }, - showDetails: function(app, entitySummary) { - var that = this; - ViewUtils.cancelFadeOnceLoaded($("div#details")); - - var whichTab = this.currentTab; - if (!whichTab) { - whichTab = "summary"; - if (this.detailsView) { - whichTab = this.detailsView.$el.find(".tab-pane.active").attr("id"); - this.detailsView.close(); - } - } - if (this.detailsView) { - this.detailsView.close(); - } - this.detailsView = new EntityDetailsView({ - model: entitySummary, - application: app, - appRouter: this.options.appRouter, - preselectTab: whichTab, - preselectTabDetails: this.currentTabDetails, - }); - - this.detailsView.on("entity.expunged", function() { - that.preselectTab("summary"); - var id = that.selectedEntityId; - var model = that.collection.get(id); - if (model && model.get("parentId")) { - that.displayEntityId(model.get("parentId")); - } else if (that.collection) { - that.displayEntityId(that.collection.first().id); - } else if (id) { - that.displayEntityNotFound(id); - } else { - that.displayEntityNotFound("?"); - } - that.collection.fetch(); - }); - this.detailsView.render( $("div#details") ); - }, - displayEntityId: function (id, appName, afterLoad) { - var that = this; - var entityLoadFailed = function() { - return that.displayEntityNotFound(id); - }; - if (appName === undefined) { - if (!afterLoad) { - // try a reload if given an ID we don't recognise - this.collection.includeEntities([id]); - this.collection.fetch({ - success: function() { _.defer(function() { that.displayEntityId(id, appName, true); }); }, - error: function() { _.defer(function() { that.displayEntityId(id, appName, true); }); } - }); - ViewUtils.fadeToIndicateInitialLoad($("div#details")) - return; - } else { - // no such app - entityLoadFailed(); - return; - } - } - - var app = new Application.Model(); - var entitySummary = new EntitySummary.Model; - - app.url = "/v1/applications/" + appName; - entitySummary.url = "/v1/applications/" + appName + "/entities/" + id; - - // in case the server response time is low, fade out while it refreshes - // (since we can't show updated details until we've retrieved app + entity details) - ViewUtils.fadeToIndicateInitialLoad($("div#details")); - - $.when(app.fetch(), entitySummary.fetch()) - .done(function() { - that.showDetails(app, entitySummary); - }) - .fail(entityLoadFailed); - }, - displayEntityNotFound: function(id) { - $("div#details").html(this.notFoundTemplate({"id": id})); - ViewUtils.cancelFadeOnceLoaded($("div#details")) - }, - }) - - return ApplicationExplorerView -}) http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/brooklyn-ui/src/main/webapp/assets/js/view/application-tree.js ---------------------------------------------------------------------- diff --git a/brooklyn-ui/src/main/webapp/assets/js/view/application-tree.js b/brooklyn-ui/src/main/webapp/assets/js/view/application-tree.js deleted file mode 100644 index 2f7a3d0..0000000 --- a/brooklyn-ui/src/main/webapp/assets/js/view/application-tree.js +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. -*/ -/** - * Sub-View to render the Application tree. - * @type {*} - */ -define([ - "underscore", "jquery", "backbone", "view/viewutils", - "model/app-tree", "text!tpl/apps/tree-item.html", "text!tpl/apps/tree-empty.html" -], function (_, $, Backbone, ViewUtils, - AppTree, TreeItemHtml, EmptyTreeHtml) { - - var emptyTreeTemplate = _.template(EmptyTreeHtml); - var treeItemTemplate = _.template(TreeItemHtml); - - var findAllTreeboxes = function(id, $scope) { - return $('.tree-box[data-entity-id="' + id + '"]', $scope); - }; - - var findRootTreebox = function(id) { - return $('.lozenge-app-tree-wrapper').children('.tree-box[data-entity-id="' + id + '"]', this.$el); - }; - - var findChildTreebox = function(id, $parentTreebox) { - return $parentTreebox.children('.node-children').children('.tree-box[data-entity-id="' + id + '"]'); - }; - - var findMasterTreebox = function(id, $scope) { - return $('.tree-box[data-entity-id="' + id + '"]:not(.indirect)', $scope); - }; - - var createEntityTreebox = function(id, name, $domParent, depth, indirect) { - // Tildes in sort key force entities with no name to bottom of list (z < ~). - var sortKey = (name ? name.toLowerCase() : "~~~") + " " + id.toLowerCase(); - - // Create the wrapper. - var $treebox = $( - '<div data-entity-id="'+id+'" data-sort-key="'+sortKey+'" data-depth="'+depth+'" ' + - 'class="tree-box toggler-group' + - (indirect ? " indirect" : "") + - (depth == 0 ? " outer" : " inner " + (depth % 2 ? " depth-odd" : " depth-even")+ - (depth == 1 ? " depth-first" : "")) + '">'+ - '<div class="entity_tree_node_wrapper"></div>'+ - '<div class="node-children toggler-target hide"></div>'+ - '</div>'); - - // Insert into the passed DOM parent, maintaining sort order relative to siblings: name then id. - var placed = false; - var contender = $(".toggler-group", $domParent).first(); - while (contender.length && !placed) { - var contenderKey = contender.data("sort-key"); - if (sortKey < contenderKey) { - contender.before($treebox); - placed = true; - } else { - contender = contender.next(".toggler-group", $domParent); - } - } - if (!placed) { - $domParent.append($treebox); - } - return $treebox; - }; - - var getOrCreateApplicationTreebox = function(id, name, treeView) { - var $treebox = findRootTreebox(id); - if (!$treebox.length) { - var $insertionPoint = $('.lozenge-app-tree-wrapper', treeView.$el); - if (!$insertionPoint.length) { - // entire view must be created - treeView.$el.html( - '<div class="navbar_main_wrapper treeloz">'+ - '<div id="tree-list" class="navbar_main treeloz">'+ - '<div class="lozenge-app-tree-wrapper">'+ - '</div></div></div>'); - $insertionPoint = $('.lozenge-app-tree-wrapper', treeView.$el); - } - $treebox = createEntityTreebox(id, name, $insertionPoint, 0, false); - } - return $treebox; - }; - - var getOrCreateChildTreebox = function(id, name, isIndirect, $parentTreebox) { - var $treebox = findChildTreebox(id, $parentTreebox); - if (!$treebox.length) { - $treebox = createEntityTreebox(id, name, $parentTreebox.children('.node-children'), $parentTreebox.data("depth") + 1, isIndirect); - } - return $treebox; - }; - - var updateTreeboxContent = function(entity, $treebox, treeView) { - var $newContent = $(treeView.template({ - id: entity.get('id'), - parentId: entity.get('parentId'), - model: entity, - statusIconUrl: ViewUtils.computeStatusIconInfo(entity.get("serviceUp"), entity.get("serviceState")).url, - indirect: $treebox.hasClass('indirect'), - })); - - var $wrapper = $treebox.children('.entity_tree_node_wrapper'); - - // Preserve old display status (just chevron direction at present). - if ($wrapper.find('.tree-node-state').hasClass('icon-chevron-down')) { - $newContent.find('.tree-node-state').removeClass('icon-chevron-right').addClass('icon-chevron-down'); - } - - $wrapper.html($newContent); - addEventsToNode($treebox, treeView); - }; - - var addEventsToNode = function($node, treeView) { - // show the "light-popup" (expand / expand all / etc) menu - // if user hovers for 500ms. surprising there is no option for this (hover delay). - // also, annoyingly, clicks around the time the animation starts don't seem to get handled - // if the click is in an overlapping reason; this is why we position relative top: 12px in css - $('.light-popup', $node).parent().parent().hover( - function(parent) { - treeView.cancelHoverTimer(); - treeView.hoverTimer = setTimeout(function() { - var menu = $(parent.currentTarget).find('.light-popup'); - menu.show(); - }, 500); - }, - function(parent) { - treeView.cancelHoverTimer(); - $('.light-popup').hide(); - } - ); - }; - - var selectTreebox = function(id, $treebox, treeView) { - $('.entity_tree_node_wrapper').removeClass('active'); - $treebox.children('.entity_tree_node_wrapper').addClass('active'); - - var entity = treeView.collection.get(id); - if (entity) { - treeView.selectedEntityId = id; - treeView.trigger('entitySelected', entity); - } - }; - - - return Backbone.View.extend({ - template: treeItemTemplate, - hoverTimer: null, - - events: { - 'click span.entity_tree_node .tree-change': 'treeChange', - 'click span.entity_tree_node': 'nodeClicked' - }, - - initialize: function() { - this.collection.on('add', this.entityAdded, this); - this.collection.on('change', this.entityChanged, this); - this.collection.on('remove', this.entityRemoved, this); - this.collection.on('reset', this.renderFull, this); - _.bindAll(this); - }, - - beforeClose: function() { - this.collection.off("reset", this.renderFull); - }, - - entityAdded: function(entity) { - // Called when the full entity model is fetched into our collection, at which time we can replace - // the empty contents of any placeholder tree nodes (.tree-box) that were created earlier. - // The entity may have multiple 'treebox' views (in the case of group members). - - // If the new entity is an application, we must create its placeholder in the DOM. - if (!entity.get('parentId')) { - var $treebox = getOrCreateApplicationTreebox(entity.id, entity.get('name'), this); - - // Select the new app if there's no current selection. - if (!this.selectedEntityId) - selectTreebox(entity.id, $treebox, this); - } - - this.entityChanged(entity); - }, - - entityChanged: function(entity) { - // The entity may have multiple 'treebox' views (in the case of group members). - var that = this; - findAllTreeboxes(entity.id).each(function() { - var $treebox = $(this); - updateTreeboxContent(entity, $treebox, that); - }); - }, - - entityRemoved: function(entity) { - // The entity may have multiple 'treebox' views (in the case of group members). - findAllTreeboxes(entity.id, this.$el).remove(); - // Collection seems sometimes to retain children of the removed node; - // not sure why, but that's okay for now. - if (this.collection.getApplications().length == 0) - this.renderFull(); - }, - - nodeClicked: function(event) { - var $treebox = $(event.currentTarget).closest('.tree-box'); - var id = $treebox.data('entityId'); - selectTreebox(id, $treebox, this); - return false; - }, - - selectEntity: function(id) { - var $treebox = findMasterTreebox(id, this.$el); - selectTreebox(id, $treebox, this); - }, - - renderFull: function() { - var that = this; - this.$el.empty(); - - // Display tree and highlight the selected entity. - if (this.collection.getApplications().length == 0) { - this.$el.append(emptyTreeTemplate()); - - } else { - _.each(this.collection.getApplications(), function(appId) { - var entity = that.collection.get(appId); - var $treebox = getOrCreateApplicationTreebox(entity.id, entity.name, that); - updateTreeboxContent(entity, $treebox, that); - }); - } - - // Select the first app if there's no current selection. - if (!this.selectedEntityId) { - var firstApp = _.first(this.collection.getApplications()); - if (firstApp) - this.selectEntity(firstApp); - } - return this; - }, - - cancelHoverTimer: function() { - if (this.hoverTimer != null) { - clearTimeout(this.hoverTimer); - this.hoverTimer = null; - } - }, - - treeChange: function(event) { - var $target = $(event.currentTarget); - var $treeBox = $target.closest('.tree-box'); - if ($target.hasClass('tr-expand')) { - this.showChildrenOf($treeBox, false); - } else if ($target.hasClass('tr-expand-all')) { - this.showChildrenOf($treeBox, true); - } else if ($target.hasClass('tr-collapse')) { - this.hideChildrenOf($treeBox, false); - } else if ($target.hasClass('tr-collapse-all')) { - this.hideChildrenOf($treeBox, true); - } else { - // default - toggle - if ($treeBox.children('.node-children').is(':visible')) { - this.hideChildrenOf($treeBox, false); - } else { - this.showChildrenOf($treeBox, false); - } - } - // hide the popup menu - this.cancelHoverTimer(); - $('.light-popup').hide(); - // don't let other events interfere - return false; - }, - - showChildrenOf: function($treeBox, recurse, excludedEntityIds) { - excludedEntityIds = excludedEntityIds || []; - var idToExpand = $treeBox.data('entityId'); - var $wrapper = $treeBox.children('.entity_tree_node_wrapper'); - var $childContainer = $treeBox.children('.node-children'); - var model = this.collection.get(idToExpand); - if (model == null) { - // not yet loaded; parallel thread should load - return; - } - - var that = this; - var children = model.get('children'); // entity summaries: {id: ..., name: ...} - var renderChildrenAsIndirect = $treeBox.hasClass("indirect"); - _.each(children, function(child) { - var $treebox = getOrCreateChildTreebox(child.id, child.name, renderChildrenAsIndirect, $treeBox); - var model = that.collection.get(child.id); - if (model) { - updateTreeboxContent(model, $treebox, that); - } - }); - var members = model.get('members'); // entity summaries: {id: ..., name: ...} - _.each(members, function(member) { - var $treebox = getOrCreateChildTreebox(member.id, member.name, true, $treeBox); - var model = that.collection.get(member.id); - if (model) { - updateTreeboxContent(model, $treebox, that); - } - }); - - // Avoid infinite recursive expansion using a "taboo list" of indirect entities already expanded in this - // operation. Example: a group that contains itself or one of its own ancestors. Such cycles can only - // originate via "indirect" subordinates. - var expandIfNotExcluded = function($treebox, excludedEntityIds, defer) { - if ($treebox.hasClass('indirect')) { - var id = $treebox.data('entityId'); - if (_.contains(excludedEntityIds, id)) - return; - excludedEntityIds.push(id); - } - var doExpand = function() { that.showChildrenOf($treebox, recurse, excludedEntityIds); }; - if (defer) _.defer(doExpand); - else doExpand(); - }; - - if (this.collection.includeEntities(_.union(children, members))) { - // we have to load entities before we can proceed - this.collection.fetch({ - success: function() { - if (recurse) { - $childContainer.children('.tree-box').each(function () { - expandIfNotExcluded($(this), excludedEntityIds, true); - }); - } - } - }); - } - - $childContainer.slideDown(300); - $wrapper.find('.tree-node-state').removeClass('icon-chevron-right').addClass('icon-chevron-down'); - if (recurse) { - $childContainer.children('.tree-box').each(function () { - expandIfNotExcluded($(this), excludedEntityIds, false); - }); - } - }, - - hideChildrenOf: function($treeBox, recurse) { - var $wrapper = $treeBox.children('.entity_tree_node_wrapper'); - var $childContainer = $treeBox.children('.node-children'); - if (recurse) { - var that = this; - $childContainer.children('.tree-box').each(function () { - that.hideChildrenOf($(this), recurse); - }); - } - $childContainer.slideUp(300); - $wrapper.find('.tree-node-state').removeClass('icon-chevron-down').addClass('icon-chevron-right'); - }, - - }); - -}); http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/brooklyn-ui/src/main/webapp/assets/js/view/catalog.js ---------------------------------------------------------------------- diff --git a/brooklyn-ui/src/main/webapp/assets/js/view/catalog.js b/brooklyn-ui/src/main/webapp/assets/js/view/catalog.js deleted file mode 100644 index 7d4ab2a..0000000 --- a/brooklyn-ui/src/main/webapp/assets/js/view/catalog.js +++ /dev/null @@ -1,613 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. -*/ -define([ - "underscore", "jquery", "backbone", "brooklyn", - "model/location", "model/entity", - "text!tpl/catalog/page.html", - "text!tpl/catalog/details-entity.html", - "text!tpl/catalog/details-generic.html", - "text!tpl/catalog/details-location.html", - "text!tpl/catalog/add-catalog-entry.html", - "text!tpl/catalog/add-yaml.html", - "text!tpl/catalog/add-location.html", - "text!tpl/catalog/nav-entry.html", - - "bootstrap", "jquery-form" -], function(_, $, Backbone, Brooklyn, - Location, Entity, - CatalogPageHtml, DetailsEntityHtml, DetailsGenericHtml, LocationDetailsHtml, - AddCatalogEntryHtml, AddYamlHtml, AddLocationHtml, EntryHtml) { - - // Holds the currently active details type, e.g. applications, policies. Bit of a workaround - // to share the active view with all instances of AccordionItemView, so clicking the 'reload - // catalog' button (handled by the parent of the AIVs) does not apply the 'active' class to - // more than one element. - var activeDetailsView; - - var CatalogItemDetailsView = Backbone.View.extend({ - - events: { - "click .delete": "deleteItem" - }, - - initialize: function() { - _.bindAll(this); - this.options.template = _.template(this.options.template || DetailsGenericHtml); - }, - - render: function() { - if (!this.options.model) { - return this.renderEmpty(); - } else { - return this.renderDetails(); - } - }, - - renderEmpty: function(extraMessage) { - this.$el.html("<div class='catalog-details'>" + - "<h3>Select an entry on the left</h3>" + - (extraMessage ? extraMessage : "") + - "</div>"); - return this; - }, - - renderDetails: function() { - var that = this, - model = this.options.model, - template = this.options.template; - var show = function() { - // Keep the previously open section open between items. Duplication between - // here and setDetailsView, below. This case handles view refreshes from this - // view directly (e.g. when indicating an error), below handles keeping the - // right thing open when navigating from view to view. - var open = this.$(".in").attr("id"); - var newHtml = $(template({model: model, viewName: that.options.name})); - $(newHtml).find("#"+open).addClass("in"); - that.$el.html(newHtml); - // rewire events. previous callbacks are removed automatically. - that.delegateEvents() - }; - - this.activeModel = model; - // Load the view with currently available data and refresh once the load is complete. - // Only refreshes the view if the model changes and the user hasn't selected another - // item while the load was executing. - show(); - model.on("change", function() { - if (that.activeModel.cid === model.cid) { - show(); - } - }); - model.fetch() - .fail(function(xhr, textStatus, errorThrown) { - console.log("error loading", model.id, ":", errorThrown); - if (that.activeModel.cid === model.cid) { - model.error = true; - show(); - } - }) - // Runs after the change event fires, or after the xhr completes - .always(function () { - model.off("change"); - }); - return this; - }, - - deleteItem: function(event) { - // Could use wait flag to block removal of model from collection - // until server confirms deletion and success handler to perform - // removal. Useful if delete fails for e.g. lack of entitlement. - var that = this; - var displayName = $(event.currentTarget).data("name") || "item"; - this.activeModel.destroy({ - success: function() { - that.renderEmpty("Deleted " + displayName); - }, - error: function(info) { - that.renderEmpty("Unable to permanently delete " + displayName+". Deletion is temporary, client-side only."); - } - }); - } - }); - - var AddCatalogEntryView = Backbone.View.extend({ - template: _.template(AddCatalogEntryHtml), - events: { - "click .show-context": "showContext" - }, - initialize: function() { - _.bindAll(this); - }, - render: function (initialView) { - this.$el.html(this.template()); - if (initialView) { - if (initialView == "entity") initialView = "yaml"; - - this.$("[data-context='"+initialView+"']").addClass("active"); - this.showFormForType(initialView) - } - return this; - }, - clearWithHtml: function(template) { - if (this.contextView) this.contextView.close(); - this.context = undefined; - this.$(".btn").removeClass("active"); - this.$("#catalog-add-form").html(template); - }, - beforeClose: function () { - if (this.contextView) this.contextView.close(); - }, - showContext: function(event) { - var $event = $(event.currentTarget); - var context = $event.data("context"); - if (this.context !== context) { - if (this.contextView) { - this.contextView.close(); - } - this.showFormForType(context) - } - }, - showFormForType: function (type) { - this.context = type; - if (type == "yaml" || type == "entity") { - this.contextView = newYamlForm(this, this.options.parent); - } else if (type == "location") { - this.contextView = newLocationForm(this, this.options.parent); - } else if (type !== undefined) { - console.log("unknown catalog type " + type); - this.showFormForType("yaml"); - return; - } - Backbone.history.navigate("/v1/catalog/new/" + type); - this.$("#catalog-add-form").html(this.contextView.$el); - } - }); - - function newYamlForm(addView, addViewParent) { - return new Brooklyn.view.Form({ - template: _.template(AddYamlHtml), - onSubmit: function (model) { - var submitButton = this.$(".catalog-submit-button"); - // "loading" is an indicator to Bootstrap, not a string to display - submitButton.button("loading"); - var self = this; - var options = { - url: "/v1/catalog/", - data: model.get("yaml"), - processData: false, - type: "post" - }; - $.ajax(options) - .done(function (data, status, xhr) { - // Can extract location of new item with: - //model.url = Brooklyn.util.pathOf(xhr.getResponseHeader("Location")); - if (_.size(data)==0) { - addView.clearWithHtml( "No items supplied." ); - } else { - addView.clearWithHtml( "Added: "+_.escape(_.keys(data).join(", ")) - + (_.size(data)==1 ? ". Loading..." : "") ); - addViewParent.loadAnyAccordionItem(_.size(data)==1 ? _.keys(data)[0] : undefined); - } - }) - .fail(function (xhr, status, error) { - submitButton.button("reset"); - self.$(".catalog-save-error") - .removeClass("hide") - .find(".catalog-error-message") - .html(_.escape(Brooklyn.util.extractError(xhr, "Could not add catalog item:\n'n" + error))); - }); - } - }); - } - - // Could adapt to edit existing locations too. - function newLocationForm(addView, addViewParent) { - // Renders with config key list - var body = new (Backbone.View.extend({ - beforeClose: function() { - if (this.configKeyList) { - this.configKeyList.close(); - } - }, - render: function() { - this.configKeyList = new Brooklyn.view.ConfigKeyInputPairList().render(); - var template = _.template(AddLocationHtml); - this.$el.html(template); - this.$("#new-location-config").html(this.configKeyList.$el); - }, - showError: function (message) { - self.$(".catalog-save-error") - .removeClass("hide") - .find(".catalog-error-message") - .html(message); - } - })); - var form = new Brooklyn.view.Form({ - body: body, - model: Location.Model, - onSubmit: function (location) { - var configKeys = body.configKeyList.getConfigKeys(); - if (!configKeys.displayName) { - configKeys.displayName = location.get("name"); - } - var submitButton = this.$(".catalog-submit-button"); - // "loading" is an indicator to Bootstrap, not a string to display - submitButton.button("loading"); - location.set("config", configKeys); - location.save() - .done(function (data) { - addView.clearWithHtml( "Added: "+data.id+". Loading..." ); - addViewParent.loadAccordionItem("locations", data.id); - }) - .fail(function (response) { - submitButton.button("reset"); - body.showError(Brooklyn.util.extractError(response)); - }); - } - }); - - return form; - } - - var Catalog = Backbone.Collection.extend({ - modelX: Backbone.Model.extend({ - url: function() { - return "/v1/catalog/" + this.name + "/" + this.id + "?allVersions=true"; - } - }), - initialize: function(models, options) { - this.name = options["name"]; - if (!this.name) { - throw new Error("Catalog collection must know its name"); - } - //this.model is a constructor so it shouldn't be _.bind'ed to this - //It actually works when a browser provided .bind is used, but the - //fallback implementation doesn't support it. - var that = this; - var model = this.model.extend({ - url: function() { - return "/v1/catalog/" + that.name + "/" + this.id.split(":").join("/"); - } - }); - _.bindAll(this); - this.model = model; - }, - url: function() { - return "/v1/catalog/" + this.name+"?allVersions=true"; - } - }); - - /** Use to fill single accordion view list. */ - var AccordionItemView = Backbone.View.extend({ - tag: "div", - className: "accordion-item", - events: { - 'click .accordion-head': 'toggle', - 'click .accordion-nav-row': 'showDetails' - }, - bodyTemplate: _.template( - "<div class='accordion-head capitalized'><%= name %></div>" + - "<div class='accordion-body' style='display: <%= display %>'></div>"), - - initialize: function() { - _.bindAll(this); - this.name = this.options.name; - if (!this.name) { - throw new Error("Name should have been given for accordion entry"); - } else if (!this.options.onItemSelected) { - throw new Error("onItemSelected(model, element) callback should have been given for accordion entry"); - } - - // Generic templates - this.template = _.template(this.options.template || EntryHtml); - - // Returns template applied to function arguments. Alter if collection altered. - // Will be run in the context of the AccordionItemView. - this.entryTemplateArgs = this.options.entryTemplateArgs || function(model, index) { - return {type: model.getVersionedAttr("type"), id: model.get("id")}; - }; - - // undefined argument is used for existing model items - var collectionModel = this.options.model || Backbone.Model; - this.collection = this.options.collection || new Catalog(undefined, { - name: this.name, - model: collectionModel - }); - // Refreshes entries list when the collection is synced with the server or - // any of its members are destroyed. - this.collection - .on("sync", this.renderEntries) - .on("destroy", this.renderEntries); - this.refresh(); - }, - - beforeClose: function() { - this.collection.off(); - }, - - render: function() { - this.$el.html(this.bodyTemplate({ - name: this.name, - display: this.options.autoOpen ? "block" : "none" - })); - this.renderEntries(); - return this; - }, - - singleItemTemplater: function(isChild, model, index) { - var args = _.extend({ - cid: model.cid, - isChild: isChild, - extraClasses: (activeDetailsView == this.name && model.cid == this.activeCid) ? "active" : "" - }, this.entryTemplateArgs(model)); - return this.template(args); - }, - - renderEntries: function() { - var elements = this.collection.map(_.partial(this.singleItemTemplater, false), this); - this.updateContent(elements.join('')); - }, - - updateContent: function(markup) { - this.$(".accordion-body") - .empty() - .append(markup); - }, - - refresh: function() { - this.collection.fetch(); - }, - - showDetails: function(event) { - var $event = $(event.currentTarget); - var cid = $event.data("cid"); - if (activeDetailsView !== this.name || this.activeCid !== cid) { - activeDetailsView = this.name; - this.activeCid = cid; - var model = this.collection.get(cid); - Backbone.history.navigate("v1/catalog/" + this.name + "/" + model.id); - this.options.onItemSelected(activeDetailsView, model, $event); - } - }, - - toggle: function() { - var body = this.$(".accordion-body"); - var hidden = this.hidden = body.css("display") == "none"; - if (hidden) { - body.removeClass("hide").slideDown('fast'); - } else { - body.slideUp('fast') - } - }, - - show: function() { - var body = this.$(".accordion-body"); - var hidden = this.hidden = body.css("display") == "none"; - if (hidden) { - body.removeClass("hide").slideDown('fast'); - } - } - }); - - var AccordionEntityView = AccordionItemView.extend({ - renderEntries: function() { - var symbolicNameFn = function(model) {return model.get("symbolicName")}; - var groups = this.collection.groupBy(symbolicNameFn); - var orderedIds = _.uniq(this.collection.map(symbolicNameFn)); - - function getLatestStableVersion(items) { - //the server sorts items by descending version, snapshots at the back - return items[0]; - } - - var catalogTree = _.map(orderedIds, function(symbolicName) { - var group = groups[symbolicName]; - var root = getLatestStableVersion(group); - var children = _.reject(group, function(model) {return root.id == model.id;}); - return {root: root, children: children}; - }); - - var templater = function(memo, item, index) { - memo.push(this.singleItemTemplater(false, item.root)); - return memo.concat(_.map(item.children, _.partial(this.singleItemTemplater, true), this)); - }; - - var elements = _.reduce(catalogTree, templater, [], this); - this.updateContent(elements.join('')); - } - }); - - // Controls whole page. Parent of accordion items and details view. - var CatalogResourceView = Backbone.View.extend({ - tagName:"div", - className:"container container-fluid", - entryTemplate:_.template(EntryHtml), - - events: { - 'click .refresh':'refresh', - 'click #add-new-thing': 'createNewThing' - }, - - initialize: function() { - $(".nav1").removeClass("active"); - $(".nav1_catalog").addClass("active"); - // Important that bind happens before accordion object is created. If it happens after - // `this' will not be set correctly for the onItemSelected callbacks. - _.bindAll(this); - this.accordion = this.options.accordion || { - "applications": new AccordionEntityView({ - name: "applications", - singular: "application", - onItemSelected: _.partial(this.showCatalogItem, DetailsEntityHtml), - model: Entity.Model, - autoOpen: !this.options.kind || this.options.kind == "applications" - }), - "entities": new AccordionEntityView({ - name: "entities", - singular: "entity", - onItemSelected: _.partial(this.showCatalogItem, DetailsEntityHtml), - model: Entity.Model, - autoOpen: this.options.kind == "entities" - }), - "policies": new AccordionEntityView({ - // TODO needs parsing, and probably its own model - // but cribbing "entity" works for now - // (and not setting a model can cause errors intermittently) - onItemSelected: _.partial(this.showCatalogItem, DetailsEntityHtml), - name: "policies", - singular: "policy", - model: Entity.Model, - autoOpen: this.options.kind == "policies" - }), - "locations": new AccordionItemView({ - name: "locations", - singular: "location", - onItemSelected: _.partial(this.showCatalogItem, LocationDetailsHtml), - collection: this.options.locations, - autoOpen: this.options.kind == "locations", - entryTemplateArgs: function (location, index) { - return { - type: location.getIdentifierName(), - id: location.getLinkByName("self") - }; - } - }) - }; - }, - - beforeClose: function() { - _.invoke(this.accordion, 'close'); - }, - - render: function() { - this.$el.html(_.template(CatalogPageHtml, {})); - var parent = this.$(".catalog-accordion-parent"); - _.each(this.accordion, function(child) { - parent.append(child.render().$el); - }); - if (this.options.kind === "new") { - this.createNewThing(this.options.id); - } else if (this.options.kind && this.options.id) { - this.loadAccordionItem(this.options.kind, this.options.id) - } else { - // Show empty details view to start - this.setDetailsView(new CatalogItemDetailsView().render()); - } - return this - }, - - /** Refreshes the contents of each accordion pane */ - refresh: function() { - _.invoke(this.accordion, 'refresh'); - }, - - createNewThing: function (type) { - // Discard if it's the jquery event object. - if (!_.isString(type)) { - type = undefined; - } - var viewName = "createNewThing"; - if (!type) { - Backbone.history.navigate("/v1/catalog/new"); - } - activeDetailsView = viewName; - this.$(".accordion-nav-row").removeClass("active"); - var newView = new AddCatalogEntryView({ - parent: this - }).render(type); - this.setDetailsView(newView); - }, - - loadAnyAccordionItem: function (id) { - this.loadAccordionItem("entities", id); - this.loadAccordionItem("applications", id); - this.loadAccordionItem("policies", id); - this.loadAccordionItem("locations", id); - }, - - loadAccordionItem: function (kind, id) { - if (!this.accordion[kind]) { - console.error("No accordion for: " + kind); - } else { - var accordion = this.accordion[kind]; - var self = this; - // reset is needed because we rely on server's ordering; - // without it, server additions are placed at end of list - accordion.collection.fetch({reset: true}) - .then(function() { - var model = accordion.collection.get(id); - if (!model) { - // if a version is supplied, try it without a version - needed for locations, navigating after deletion - if (id && id.split(":").length>1) { - model = accordion.collection.get( id.split(":")[0] ); - } - } - if (!model) { - // if an ID is supplied without a version, look for first matching version (should be newest) - if (id && id.split(":").length==1 && accordion.collection.models) { - model = _.find(accordion.collection.models, function(m) { - return m && m.id && m.id.startsWith(id+":"); - }); - } - } - // TODO could look in collection for any starting with ID - if (model) { - Backbone.history.navigate("/v1/catalog/"+kind+"/"+id); - activeDetailsView = kind; - accordion.activeCid = model.cid; - accordion.options.onItemSelected(kind, model); - accordion.show(); - } else { - // catalog item not found, or not found yet (it might be reloaded and another callback will try again) - } - }); - } - }, - - showCatalogItem: function(template, viewName, model, $target) { - this.$(".accordion-nav-row").removeClass("active"); - if ($target) { - $target.addClass("active"); - } else { - this.$("[data-cid=" + model.cid + "]").addClass("active"); - } - var newView = new CatalogItemDetailsView({ - model: model, - template: template, - name: viewName - }).render(); - this.setDetailsView(newView) - }, - - setDetailsView: function(view) { - this.$("#details").html(view.el); - if (this.detailsView) { - // Try to re-open sections that were previously visible. - var openedItem = this.detailsView.$(".in").attr("id"); - if (openedItem) { - view.$("#" + openedItem).addClass("in"); - } - this.detailsView.close(); - } - this.detailsView = view; - } - }); - - return CatalogResourceView -});
