if (!CMS.Views['Metadata']) CMS.Views.Metadata = {}; CMS.Views.Metadata.Editor = Backbone.View.extend({ // Model is CMS.Models.MetadataCollection, initialize : function() { var tpl = $("#metadata-editor-tpl").text(); if(!tpl) { console.error("Couldn't load metadata editor template"); } this.template = _.template(tpl); this.$el.html(this.template({numEntries: this.collection.length})); var counter = 0; var self = this; this.collection.each( function (model) { var data = { el: self.$el.find('.metadata_entry')[counter++], model: model }; if (model.getType() === CMS.Models.Metadata.SELECT_TYPE) { new CMS.Views.Metadata.Option(data); } else if (model.getType() === CMS.Models.Metadata.INTEGER_TYPE || model.getType() === CMS.Models.Metadata.FLOAT_TYPE) { new CMS.Views.Metadata.Number(data); } else if(model.getType() === CMS.Models.Metadata.LIST_TYPE) { new CMS.Views.Metadata.List(data); } else { // Everything else is treated as GENERIC_TYPE, which uses String editor. new CMS.Views.Metadata.String(data); } }); }, /** * Returns the just the modified metadata values, in the format used to persist to the server. */ getModifiedMetadataValues: function () { var modified_values = {}; this.collection.each( function (model) { if (model.isModified()) { modified_values[model.getFieldName()] = model.getValue(); } } ); return modified_values; }, /** * Returns a display name for the component related to this metadata. This method looks to see * if there is a metadata entry called 'display_name', and if so, it returns its value. If there * is no such entry, or if display_name does not have a value set, it returns an empty string. */ getDisplayName: function () { var displayName = ''; this.collection.each( function (model) { if (model.get('field_name') === 'display_name') { var displayNameValue = model.get('value'); // It is possible that there is no display name value set. In that case, return empty string. displayName = displayNameValue ? displayNameValue : ''; } } ); return displayName; } }); CMS.Views.Metadata.AbstractEditor = Backbone.View.extend({ // Model is CMS.Models.Metadata. initialize : function() { var self = this; var templateName = _.result(this, 'templateName'); // Backbone model cid is only unique within the collection. this.uniqueId = _.uniqueId(templateName + "_"); var tpl = document.getElementById(templateName).text; if(!tpl) { console.error("Couldn't load template: " + templateName); } this.template = _.template(tpl); this.$el.html(this.template({model: this.model, uniqueId: this.uniqueId})); this.listenTo(this.model, 'change', this.render); this.render(); }, /** * The ID/name of the template. Subclasses must override this. */ templateName: '', /** * Returns the value currently displayed in the editor/view. Subclasses should implement this method. */ getValueFromEditor : function () {}, /** * Sets the value currently displayed in the editor/view. Subclasses should implement this method. */ setValueInEditor : function (value) {}, /** * Sets the value in the model, using the value currently displayed in the view. */ updateModel: function () { this.model.setValue(this.getValueFromEditor()); }, /** * Clears the value currently set in the model (reverting to the default). */ clear: function () { this.model.clear(); }, /** * Shows the clear button, if it is not already showing. */ showClearButton: function() { if (!this.$el.hasClass('is-set')) { this.$el.addClass('is-set'); this.getClearButton().removeClass('inactive'); this.getClearButton().addClass('active'); } }, /** * Returns the clear button. */ getClearButton: function () { return this.$el.find('.setting-clear'); }, /** * Renders the editor, updating the value displayed in the view, as well as the state of * the clear button. */ render: function () { if (!this.template) return; this.setValueInEditor(this.model.getDisplayValue()); if (this.model.isExplicitlySet()) { this.showClearButton(); } else { this.$el.removeClass('is-set'); this.getClearButton().addClass('inactive'); this.getClearButton().removeClass('active'); } return this; } }); CMS.Views.Metadata.String = CMS.Views.Metadata.AbstractEditor.extend({ events : { "change input" : "updateModel", "keypress .setting-input" : "showClearButton" , "click .setting-clear" : "clear" }, templateName: "metadata-string-entry", getValueFromEditor : function () { return this.$el.find('#' + this.uniqueId).val(); }, setValueInEditor : function (value) { this.$el.find('input').val(value); } }); CMS.Views.Metadata.Number = CMS.Views.Metadata.AbstractEditor.extend({ events : { "change input" : "updateModel", "keypress .setting-input" : "keyPressed", "change .setting-input" : "changed", "click .setting-clear" : "clear" }, render: function () { CMS.Views.Metadata.AbstractEditor.prototype.render.apply(this); if (!this.initialized) { var numToString = function (val) { return val.toFixed(4); }; var min = "min"; var max = "max"; var step = "step"; var options = this.model.getOptions(); if (options.hasOwnProperty(min)) { this.min = Number(options[min]); this.$el.find('input').attr(min, numToString(this.min)); } if (options.hasOwnProperty(max)) { this.max = Number(options[max]); this.$el.find('input').attr(max, numToString(this.max)); } var stepValue = undefined; if (options.hasOwnProperty(step)) { // Parse step and convert to String. Polyfill doesn't like float values like ".1" (expects "0.1"). stepValue = numToString(Number(options[step])); } else if (this.isIntegerField()) { stepValue = "1"; } if (stepValue !== undefined) { this.$el.find('input').attr(step, stepValue); } // Manually runs polyfill for input number types to correct for Firefox non-support. // inputNumber will be undefined when unit test is running. if ($.fn.inputNumber) { this.$el.find('.setting-input-number').inputNumber(); } this.initialized = true; } return this; }, templateName: "metadata-number-entry", getValueFromEditor : function () { return this.$el.find('#' + this.uniqueId).val(); }, setValueInEditor : function (value) { this.$el.find('input').val(value); }, /** * Returns true if this view is restricted to integers, as opposed to floating points values. */ isIntegerField : function () { return this.model.getType() === 'Integer'; }, keyPressed: function (e) { this.showClearButton(); // This first filtering if statement is take from polyfill to prevent // non-numeric input (for browsers that don't use polyfill because they DO have a number input type). var _ref, _ref1; if (((_ref = e.keyCode) !== 8 && _ref !== 9 && _ref !== 35 && _ref !== 36 && _ref !== 37 && _ref !== 39) && ((_ref1 = e.which) !== 45 && _ref1 !== 46 && _ref1 !== 48 && _ref1 !== 49 && _ref1 !== 50 && _ref1 !== 51 && _ref1 !== 52 && _ref1 !== 53 && _ref1 !== 54 && _ref1 !== 55 && _ref1 !== 56 && _ref1 !== 57)) { e.preventDefault(); } // For integers, prevent decimal points. if (this.isIntegerField() && e.keyCode === 46) { e.preventDefault(); } }, changed: function () { // Limit value to the range specified by min and max (necessary for browsers that aren't using polyfill). var value = this.getValueFromEditor(); if ((this.max !== undefined) && value > this.max) { value = this.max; } else if ((this.min != undefined) && value < this.min) { value = this.min; } this.setValueInEditor(value); this.updateModel(); } }); CMS.Views.Metadata.Option = CMS.Views.Metadata.AbstractEditor.extend({ events : { "change select" : "updateModel", "click .setting-clear" : "clear" }, templateName: "metadata-option-entry", getValueFromEditor : function () { var selectedText = this.$el.find('#' + this.uniqueId).find(":selected").text(); var selectedValue; _.each(this.model.getOptions(), function (modelValue) { if (modelValue === selectedText) { selectedValue = modelValue; } else if (modelValue['display_name'] === selectedText) { selectedValue = modelValue['value']; } }); return selectedValue; }, setValueInEditor : function (value) { // Value here is the json value as used by the field. The choice may instead be showing display names. // Find the display name matching the value passed in. _.each(this.model.getOptions(), function (modelValue) { if (modelValue['value'] === value) { value = modelValue['display_name']; } }); this.$el.find('#' + this.uniqueId + " option").filter(function() { return $(this).text() === value; }).prop('selected', true); } }); CMS.Views.Metadata.List = CMS.Views.Metadata.AbstractEditor.extend({ events : { "click .setting-clear" : "clear", "keypress .setting-input" : "showClearButton", "change input" : "updateModel", "input input" : "enableAdd", "click .create-setting" : "addEntry", "click .remove-setting" : "removeEntry" }, templateName: "metadata-list-entry", getValueFromEditor: function () { return _.map( this.$el.find('li input'), function (ele) { return ele.value.trim(); } ).filter(_.identity); }, setValueInEditor: function (value) { var list = this.$el.find('ol'); list.empty(); _.each(value, function(ele, index) { var template = _.template( '<li class="list-settings-item">' + '<input type="text" class="input" value="<%= ele %>">' + '<a href="#" class="remove-action remove-setting" data-index="<%= index %>"><i class="icon-remove-sign"></i><span class="sr">Remove</span></a>' + '</li>' ); list.append($(template({'ele': ele, 'index': index}))); }); }, addEntry: function(event) { event.preventDefault(); // We don't call updateModel here since it's bound to the // change event var list = this.model.get('value') || []; this.setValueInEditor(list.concat([''])) this.$el.find('.create-setting').addClass('is-disabled'); }, removeEntry: function(event) { event.preventDefault(); var entry = $(event.currentTarget).siblings().val(); this.setValueInEditor(_.without(this.model.get('value'), entry)); this.updateModel(); this.$el.find('.create-setting').removeClass('is-disabled'); }, enableAdd: function() { this.$el.find('.create-setting').removeClass('is-disabled'); } });