app/assets/javascripts/pageflow/dist/ui.js in pageflow-15.6.1 vs app/assets/javascripts/pageflow/dist/ui.js in pageflow-15.7.0

- old
+ new

@@ -350,10 +350,28 @@ }); // The self-propagating extend function that Backbone classes use. BaseObject.extend = Backbone.Model.extend; + var serverSideValidation = { + initialize: function initialize() { + var _this = this; + + this.validationErrors = {}; + this.listenTo(this, 'error', function (model, request) { + if (request.status === 422) { + _this.validationErrors = JSON.parse(request.responseText).errors; + + _this.trigger('invalid'); + } + }); + this.listenTo(this, 'sync', function () { + _this.validationErrors = {}; + }); + } + }; + var CollectionView = Marionette.View.extend({ initialize: function initialize() { this.rendered = false; this.itemViews = new ChildViewContainer(); this.collection.map(this.addItem, this); @@ -750,14 +768,16 @@ defaultTab: this.options.tab }); this.configure(); }, configure: function configure() {}, - tab: function tab(name, callback) { + tab: function tab(name, callbackOrOptions, callback) { + callback = callback || callbackOrOptions; + var options = callback ? callbackOrOptions : {}; this.tabsView.tab(name, _.bind(function () { var tabView = new ConfigurationEditorTabView({ - model: this.model, + model: options.model || this.model, placeholderModel: this.options.placeholderModel, tab: name, attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes }); callback.call(tabView); @@ -894,11 +914,11 @@ }); var TableHeaderCellView = TableCellView.extend({ tagName: 'th', render: function render() { - this.$el.text(this.attributeTranslation('column_header')); + this.$el.text(this.options.column.headerText || this.attributeTranslation('column_header')); this.$el.data('columnName', this.options.column.name); return this; } }); @@ -1271,10 +1291,11 @@ }, isDisabled: function isDisabled() { return this.getAttributeBoundOption('disabled'); }, updateDisabled: function updateDisabled() { + this.$el.toggleClass('input-disabled', !!this.isDisabled()); this.updateInlineHelp(); if (this.ui.input) { this.updateDisabledAttribute(this.ui.input); } @@ -1498,10 +1519,35 @@ placeholderModelValue: function placeholderModelValue() { return this.options.placeholderModel && this.options.placeholderModel.get(this.options.propertyName); } }; + var viewWithValidationErrorMessages = { + onRender: function onRender() { + this.listenTo(this.model, 'invalid sync', this.updateValidationErrorMessages); + this.updateValidationErrorMessages(); + }, + updateValidationErrorMessages: function updateValidationErrorMessages() { + var _this = this; + + var errors = this.model.validationErrors && this.model.validationErrors[this.options.propertyName] || []; + + if (errors.length) { + this.validationErrorList = this.validationErrorList || $('<ul class="validation_error_messages" />').appendTo(this.el); + this.validationErrorList.html(''); + errors.forEach(function (error) { + return _this.validationErrorList.append("<li>".concat(error, "</li>")); + }); + this.$el.addClass('invalid'); + } else if (this.validationErrorList) { + this.validationErrorList.remove(); + this.validationErrorList = null; + this.$el.removeClass('invalid'); + } + } + }; + function template$6(data) { var __p = ''; __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<input type="text" dir="auto" />\n'; return __p } @@ -1525,11 +1571,11 @@ * * @class */ var TextInputView = Marionette.ItemView.extend({ - mixins: [inputView, inputWithPlaceholderText], + mixins: [inputView, inputWithPlaceholderText, viewWithValidationErrorMessages], template: template$6, ui: { input: 'input' }, events: { @@ -2025,13 +2071,13 @@ ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.insert_unordered_list') )) == null ? '' : __t) + '"></a>\n\n <div data-wysihtml5-dialog="createLink" class="dialog link_dialog" style="display: none;">\n <div class="link_type_select">\n <label>\n <input type="radio" name="link_type" class="url_link_radio_button">\n ' + ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.link_type.url') )) == null ? '' : __t) + '\n </label>\n <label>\n <input type="radio" name="link_type" class="fragment_link_radio_button">\n ' + ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.link_type.page_link') )) == null ? '' : __t) + - '\n </label>\n </div>\n <div class="url_link_panel">\n <label>\n ' + + '\n </label>\n </div>\n <div class="url_link_panel">\n <label>\n <span>\n ' + ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.target') )) == null ? '' : __t) + - '\n </label>\n <input type="text" class="display_url">\n <div class="open_in_new_tab_section">\n <label>\n <input type="checkbox" class="open_in_new_tab">\n ' + + '\n </span>\n </label>\n <input type="text" class="display_url">\n <div class="open_in_new_tab_section">\n <label>\n <input type="checkbox" class="open_in_new_tab">\n ' + ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.open_in_new_tab') )) == null ? '' : __t) + '\n </label>\n <span class="inline_help">\n ' + ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.open_in_new_tab_help') )) == null ? '' : __t) + '\n </span>\n </div>\n </div>\n <div class="fragment_link_panel">\n <!-- LinkInputView is inserted here -->\n </div>\n\n <!-- wysihtml5 does not handle hidden fields correctly -->\n <div class="internal">\n <input type="text" data-wysihtml5-dialog-field="href" class="current_url" value="">\n <input type="text" data-wysihtml5-dialog-field="target" class="current_target" value="_blank">\n </div>\n\n <a class="button" data-wysihtml5-dialog-action="save">\n ' + ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.save') )) == null ? '' : __t) + @@ -2361,10 +2407,19 @@ * Override to change the list of supported host names. */ supportedHosts: function supportedHosts() { return this.options.supportedHosts; }, + // Host names used to be expected to include protocols. Remove + // protocols for backwards compatilbity. Since supportedHosts + // is supposed to be overridden in subclasses, we do it in a + // separate method. + supportedHostsWithoutLegacyProtocols: function supportedHostsWithoutLegacyProtocols() { + return _.map(this.supportedHosts(), function (host) { + return host.replace(/^https?:\/\//, ''); + }); + }, validate: function validate(success) { var view = this; var options = this.options; var value = this.ui.input.val(); @@ -2397,27 +2452,30 @@ function isValidUrl(url) { return options.permitHttps ? url.match(/^https?:\/\//i) : url.match(/^http:\/\//i); } function hasSupportedHost(url) { - return _.any(view.supportedHosts(), function (host) { - return url.match(new RegExp('^' + host)); + return _.any(view.supportedHostsWithoutLegacyProtocols(), function (host) { + return url.match(new RegExp('^https?://' + host)); }); } function displayValidationError(message) { view.$el.addClass('invalid'); + view.ui.input.attr('aria-invalid', 'true'); view.ui.validation.removeClass('pending').addClass('failed').html(message).show(); } function displayValidationPending(message) { view.$el.removeClass('invalid'); + view.ui.input.removeAttr('aria-invalid'); view.ui.validation.removeClass('failed').addClass('pending').html(message).show(); } function resetValidationError(message) { view.$el.removeClass('invalid'); + view.ui.input.attr('aria-invalid', 'false'); view.ui.validation.hide(); } } }); @@ -2648,10 +2706,19 @@ * * @param {boolean} [options.displayUncheckedIfDisabled=false] * Ignore the attribute value if the input is disabled and display * an unchecked check box. * + * @param {boolean} [options.displayCheckedIfDisabled=false] + * Ignore the attribute value if the input is disabled and display + * an checked check box. + * + * @param {string} [options.storeInverted] + * Display checked by default and store true in given attribute when + * unchecked. The property name passed to `input` is only used for + * translations. + * * @class */ var CheckBoxInputView = Marionette.ItemView.extend({ mixins: [inputView], @@ -2673,28 +2740,71 @@ updateDisabled: function updateDisabled() { this.load(); }, save: function save() { if (!this.isDisabled()) { - this.model.set(this.options.propertyName, this.ui.input.is(':checked')); + var value = this.ui.input.is(':checked'); + + if (this.options.storeInverted) { + this.model.set(this.options.storeInverted, !value); + } else { + this.model.set(this.options.propertyName, value); + } } }, load: function load() { if (!this.isClosed) { this.ui.input.prop('checked', this.displayValue()); } }, displayValue: function displayValue() { if (this.isDisabled() && this.options.displayUncheckedIfDisabled) { return false; + } else if (this.isDisabled() && this.options.displayCheckedIfDisabled) { + return true; + } else if (this.options.storeInverted) { + return !this.model.get(this.options.storeInverted); } else { return this.model.get(this.options.propertyName); } } }); /** + * Render a separator in a {@link ConfigurationEditorView} tab. + * + * @example + * + * this.view(SeparatorView); + * + * @class + */ + + var SeparatorView = Marionette.View.extend({ + className: 'separator' + }); + + /** + * Render an input that is only a label. Can be used to render + * additional inline help. + * + * See {@link inputView} for further options + * + * @class + */ + + var LabelOnlyView = Marionette.ItemView.extend({ + mixins: [inputView], + template: function template() { + return "\n <label>\n <span class=\"name\"></span>\n <span class=\"inline_help\"></span>\n </label>\n "; + }, + ui: { + label: 'label' + } + }); + + /** * A table cell mapping column attribute values to a list of * translations. * * ## Attribute Translations * @@ -2909,12 +3019,15 @@ if (this.subviews) { this.subviews.call('close'); } } }; - Cocktail.mixin(Marionette.View, subviewContainer); + if (!Marionette.View.prototype.appendSubview) { + Cocktail.mixin(Marionette.View, subviewContainer); + } + var tooltipContainer = { events: { 'mouseover [data-tooltip]': function mouseoverDataTooltip(event) { if (!this.tooltip.visible) { var target = $(event.currentTarget); @@ -2966,14 +3079,16 @@ exports.DeleteRowTableCellView = DeleteRowTableCellView; exports.EnumTableCellView = EnumTableCellView; exports.ExtendedSelectInputView = ExtendedSelectInputView; exports.IconTableCellView = IconTableCellView; exports.JsonInputView = JsonInputView; + exports.LabelOnlyView = LabelOnlyView; exports.Object = BaseObject; exports.PresenceTableCellView = PresenceTableCellView; exports.ProxyUrlInputView = ProxyUrlInputView; exports.SelectInputView = SelectInputView; + exports.SeparatorView = SeparatorView; exports.SliderInputView = SliderInputView; exports.SortableCollectionView = SortableCollectionView; exports.TableCellView = TableCellView; exports.TableHeaderCellView = TableHeaderCellView; exports.TableRowView = TableRowView; @@ -2987,11 +3102,13 @@ exports.UrlInputView = UrlInputView; exports.cssModulesUtils = cssModulesUtils; exports.i18nUtils = i18nUtils; exports.inputView = inputView; exports.inputWithPlaceholderText = inputWithPlaceholderText; + exports.serverSideValidation = serverSideValidation; exports.subviewContainer = subviewContainer; exports.tooltipContainer = tooltipContainer; + exports.viewWithValidationErrorMessages = viewWithValidationErrorMessages; return exports; }({}, Backbone.Marionette, _, jQuery, I18n, Backbone, Backbone.ChildViewContainer, IScroll, jQuery, wysihtml5, Cocktail));