From 8cc4e8318f062587071b666b949ca70cb383e7ad Mon Sep 17 00:00:00 2001 From: Jordan Mele Date: Fri, 15 Dec 2017 14:03:15 +1100 Subject: [PATCH 1/3] Resolves #740 --- CHANGELOG.md | 1 + .../core/assets/userfrosting/js/uf-modal.js | 271 +++++++----------- 2 files changed, 112 insertions(+), 160 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06c1ba361..5e42e6da2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v4.1.16-alpha - Use locale requested by browser when possible for guests (#718) - Add locale drop down to registration page, with the currently applied locale selected (#718) +- Migrated ufModal to new jQuery boilerplate (#740) - Integrated improvements from v4.0.25-Alpha - Support npm for frontend vendor assets, and deprecation of bower (#737) - Duplicate frontend vendor assets are no longer downloaded (#727) diff --git a/app/sprinkles/core/assets/userfrosting/js/uf-modal.js b/app/sprinkles/core/assets/userfrosting/js/uf-modal.js index b84a59a21..2b02c2491 100644 --- a/app/sprinkles/core/assets/userfrosting/js/uf-modal.js +++ b/app/sprinkles/core/assets/userfrosting/js/uf-modal.js @@ -3,190 +3,141 @@ * * * UserFrosting https://www.userfrosting.com - * @author Alexander Weissman https://alexanderweissman.com + * @author Alexander Weissman */ -(function( $ ) -{ - /** - * The plugin namespace, ie for $('.selector').ufModal(options) - * - * Also the id for storing the object state via $('.selector').data() - */ - var PLUGIN_NS = 'ufModal'; - - var Plugin = function ( target, options ) - { - - this.$T = $(target); - - /** #### OPTIONS #### */ - this.options= $.extend( - true, // deep extend - { - sourceUrl : "", - ajaxParams: {}, - msgTarget : null, - DEBUG: false - }, - options - ); - - this.modal = null; - - this._init( target ); - - return this; +;(function($, window, document, undefined) { + 'use strict'; + + // Define plugin name and defaults. + var pluginName = 'ufModal', + defaults = { + sourceUrl : '', + ajaxParams: {}, + msgTarget : null, + DEBUG : false }; - /** #### INITIALISER #### */ - Plugin.prototype._init = function ( target ) - { - var base = this; - var $el = $(target); + // Constructor + function Plugin (element, options) { + this.element = element[0]; + this.$element = $(this.element); + this.settings = $.extend(true, {}, defaults, options); + this._defaults = defaults; + this._name = pluginName; + + // True plugin initalisation commences + this.modal = null; // Delete any existing modals attached to the element (should have been deleted already anyway) - if ($el.find(".modal").length) { - $el.find(".modal").remove(); - } + if (this.$element.find('.modal').length) this.$element.find('.modal').remove(); - // Fetch and render the form + // Fetch and render form $.ajax({ - type: "GET", - url: base.options.sourceUrl, - data: base.options.ajaxParams, - cache: false - }) - .then( - // Fetch successful + context: this, + type: 'GET', + url: this.settings.sourceUrl, + data: this.settings.ajaxParms, + cache: false + }).then( + // Success function (data) { - // Append the form as a modal dialog to the body - base.modal = $(data); - $el.append(base.modal); + // Append the data as a modal dialog to the target element + this.modal = $(data); + this.$element.append(this.modal); - base.modal.modal('show'); + // Trigger modal dialog + this.modal.modal('show'); - // Bind modal to be deleted when closed - base.modal.on("hidden.bs.modal", function () { - base.destroy(); - }); + // Bind destroy function to close event + this.modal.on('hidden.bs.modal', function () { this.destroy(); }.bind(this)); - base.$T.trigger('renderSuccess.ufModal'); - return data; + // Trigger success event + this.$element.trigger('renderSuccess.ufModal'); }, - // Fetch failed + // Failure function (data) { - // Error messages - if ((typeof site !== "undefined") && site.debug.ajax && data.responseText) { - base.$T.trigger('renderError.ufModal'); + // Handle error messages + if (site !== undefined && site.debug.ajax && data.responseText) { + // Trigger failure event + this.$element.trigger('renderError.ufModal'); + + // Replace document content with response, and handle browser quirks document.write(data.responseText); document.close(); } else { - if (base.options.DEBUG) { - console.log("Error (" + data.status + "): " + data.responseText ); - } - // Display errors on failure - // TODO: ufAlerts widget should have a 'destroy' method - if (!base.options.msgTarget.data('ufAlerts')) { - base.options.msgTarget.ufAlerts(); + // Debug logging + if (this.settings.DEBUG) console.log('Error (' + data.status + '): ' + data.responseText); + + // Refresh ufAlerts for errors if target defined + if (this.settings.msgTarget) { + // Check if ufAlerts is instanced and empty + if (!this.settings.msgTarget.data('ufAlerts')) this.settings.msgTarget.ufAlerts(); + else this.settings.msgTarget.ufAlerts('clear'); + + // Trigger failure event on render.ufAlerts event + this.settings.msgTarget.on('render.ufAlerts', function () { + this.$element.trigger('renderError.ufModal'); + }.bind(this)); + + // Pull alerts + this.settings.msgTarget.ufAlerts('fetch').ufAlerts('render'); } else { - base.options.msgTarget.ufAlerts('clear'); + // renderError.ufModal event should always be able to trigger + this.$element.trigger('renderError.ufModal'); } - - base.options.msgTarget.ufAlerts('fetch').ufAlerts('render'); - base.options.msgTarget.on("render.ufAlerts", function () { - base.$T.trigger('renderError.ufModal'); - }); } - - base.destroy(); - - return data; } - ); - }; - - Plugin.prototype.destroy = function () { - var base = this; - var $el = base.$T; - - // Delete the plugin object - base.delete; + ) + } - // Remove the modal from the selector - if (base.modal) { - base.modal.remove(); + $.extend(Plugin.prototype, { + /** + * Destroys instance + */ + destroy: function () { + // Remove modal from selector + if (this.modal) this.modal.remove(); + + // Unbind plugin events + this.$element.off('.' + this._name); + + // Remove plugin data from internal jQuery store (jQuery doesn't store with data-*, but can access it) + this.$element.removeData(this._name); + + return this.$element; + }, + /** + * Returns underlying modal + */ + getModal: function () { + return this.modal; } - - // Unbind any modal events bound to the selector - $el.off('.ufModal'); - - // Remove plugin name from selector's data-* attribute - $el.removeData(PLUGIN_NS); - }; - - Plugin.prototype.getModal = function () { - return this.modal; - }; + }); - /** - * EZ Logging/Warning (technically private but saving an '_' is worth it imo) - */ - Plugin.prototype.DLOG = function () - { - if (!this.DEBUG) return; - for (var i in arguments) { - console.log( PLUGIN_NS + ': ', arguments[i] ); + // Handles instantiation and access to non-private methods. + $.fn[pluginName] = function(methodOrOptions) { + // Grab plugin instance + var instance = $(this).data(pluginName); + // If undefined or object, initalise plugin. + if (methodOrOptions === undefined || typeof methodOrOptions === 'object') { + // Only initalise if not previously done. + if (!instance) { + $(this).data(pluginName, new Plugin(this, methodOrOptions)); + } + return this; } - } - Plugin.prototype.DWARN = function () - { - this.DEBUG && console.warn( arguments ); - } - - -/*################################################################################### - * JQUERY HOOK - ###################################################################################*/ - - /** - * Generic jQuery plugin instantiation method call logic - * - * Method options are stored via jQuery's data() method in the relevant element(s) - * Notice, myActionMethod mustn't start with an underscore (_) as this is used to - * indicate private methods on the PLUGIN class. - */ - $.fn[ PLUGIN_NS ] = function( methodOrOptions ) - { - if (!$(this).length) { - return $(this); + // Otherwise ensure first parameter is a valid string, and is the name of an actual function. + else if (typeof methodOrOptions === 'string' && typeof instance[methodOrOptions] === 'function') { + // Ensure not a private function + if (methodOrOptions.indexOf('_') !== 0) { + return instance[methodOrOptions]( Array.prototype.slice.call(arguments, 1)); + } + else { + $.error('Method ' + methodOrOptions + ' is private!'); + } } - var instance = $(this).data(PLUGIN_NS); - - // CASE: action method (public method on PLUGIN class) - if ( instance - && methodOrOptions.indexOf('_') != 0 - && instance[ methodOrOptions ] - && typeof( instance[ methodOrOptions ] ) == 'function' ) { - - return instance[ methodOrOptions ]( Array.prototype.slice.call( arguments, 1 ) ); - - - // CASE: argument is options object or empty = initialise - } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) { - - instance = new Plugin( $(this), methodOrOptions ); // ok to overwrite if this is a re-init - $(this).data( PLUGIN_NS, instance ); - return $(this); - - // CASE: method called before init - } else if ( !instance ) { - console.warn( 'Plugin must be initialised before using method: ' + methodOrOptions ); - - // CASE: invalid method - } else if ( methodOrOptions.indexOf('_') == 0 ) { - console.warn( 'Method ' + methodOrOptions + ' is private!' ); - } else { - console.warn( 'Method ' + methodOrOptions + ' does not exist.' ); + else { + $.error('Method ' + methodOrOptions + ' does not exist.'); } }; -})(jQuery); \ No newline at end of file +})(jQuery, window, document); \ No newline at end of file From 3e72d6f21040265809074ce37b9f21ccc979d978 Mon Sep 17 00:00:00 2001 From: Jordan Mele Date: Fri, 15 Dec 2017 16:01:32 +1100 Subject: [PATCH 2/3] Conformance with other plugins --- app/sprinkles/core/assets/userfrosting/js/uf-modal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/sprinkles/core/assets/userfrosting/js/uf-modal.js b/app/sprinkles/core/assets/userfrosting/js/uf-modal.js index 2b02c2491..687d60146 100644 --- a/app/sprinkles/core/assets/userfrosting/js/uf-modal.js +++ b/app/sprinkles/core/assets/userfrosting/js/uf-modal.js @@ -133,11 +133,11 @@ return instance[methodOrOptions]( Array.prototype.slice.call(arguments, 1)); } else { - $.error('Method ' + methodOrOptions + ' is private!'); + console.error('Method ' + methodOrOptions + ' is private!'); } } else { - $.error('Method ' + methodOrOptions + ' does not exist.'); + console.error('Method ' + methodOrOptions + ' does not exist.'); } }; })(jQuery, window, document); \ No newline at end of file From 3a9ca5eee5d9b9fa52f74db634a30c790a7defb5 Mon Sep 17 00:00:00 2001 From: Jordan Mele Date: Sat, 24 Apr 2021 21:06:43 +1000 Subject: [PATCH 3/3] Refactored ufModal to reduce dependence on `this` and provide _some_ type documentation --- .../core/assets/userfrosting/js/uf-modal.js | 150 ++++++++++-------- 1 file changed, 81 insertions(+), 69 deletions(-) diff --git a/app/sprinkles/core/assets/userfrosting/js/uf-modal.js b/app/sprinkles/core/assets/userfrosting/js/uf-modal.js index 687d60146..e2e34ffd5 100644 --- a/app/sprinkles/core/assets/userfrosting/js/uf-modal.js +++ b/app/sprinkles/core/assets/userfrosting/js/uf-modal.js @@ -1,143 +1,155 @@ +/// /** * ufModal plugin. Handles modal windows that dynamically their fetch content from a specified URL. * - * * UserFrosting https://www.userfrosting.com * @author Alexander Weissman */ -;(function($, window, document, undefined) { +;(function($, document, undefined) { 'use strict'; // Define plugin name and defaults. - var pluginName = 'ufModal', - defaults = { + var pluginName = 'ufModal'; + /** + * @typedef {{ + * sourceUrl: string, + * ajaxParams: JQuery.PlainObject | string, + * msgTarget?: JQuery|null + * DEBUG?: boolean, + * }} Options + * @type {Options} + */ + var defaults = { sourceUrl : '', ajaxParams: {}, msgTarget : null, DEBUG : false }; - // Constructor - function Plugin (element, options) { - this.element = element[0]; - this.$element = $(this.element); - this.settings = $.extend(true, {}, defaults, options); - this._defaults = defaults; - this._name = pluginName; + /** + * @param {ArrayLike} inElement + * @param {Options|undefined} options + */ + function createPlugin(inElement, options) { + var element = inElement[0]; + var $element = $(element); + var settings = $.extend(true, defaults, options); - // True plugin initalisation commences - this.modal = null; + // True plugin initialization commences + /** @type {JQuery|null} */ + var modal = null; // Delete any existing modals attached to the element (should have been deleted already anyway) - if (this.$element.find('.modal').length) this.$element.find('.modal').remove(); + if ($element.find('.modal').length) $element.find('.modal').remove(); + + function destroy() { + // Remove modal from selector + if (modal) modal.remove(); + + // Unbind plugin events + $element.off('.' + pluginName); + + // Remove plugin data from internal jQuery store (jQuery doesn't store with data-*, but can access it) + $element.removeData(pluginName); + + return $element; + } - // Fetch and render form + // Fetch and render $.ajax({ - context: this, type: 'GET', - url: this.settings.sourceUrl, - data: this.settings.ajaxParms, - cache: false + url: settings.sourceUrl, + data: settings.ajaxParams, + cache: false, }).then( // Success function (data) { // Append the data as a modal dialog to the target element - this.modal = $(data); - this.$element.append(this.modal); + modal = $(data); + $element.append(modal); // Trigger modal dialog - this.modal.modal('show'); + modal.modal('show'); // Bind destroy function to close event - this.modal.on('hidden.bs.modal', function () { this.destroy(); }.bind(this)); + modal.on('hidden.bs.modal', function () { destroy(); }); // Trigger success event - this.$element.trigger('renderSuccess.ufModal'); + $element.trigger('renderSuccess.ufModal'); }, // Failure function (data) { // Handle error messages if (site !== undefined && site.debug.ajax && data.responseText) { // Trigger failure event - this.$element.trigger('renderError.ufModal'); + $element.trigger('renderError.ufModal'); // Replace document content with response, and handle browser quirks document.write(data.responseText); document.close(); } else { // Debug logging - if (this.settings.DEBUG) console.log('Error (' + data.status + '): ' + data.responseText); + if (settings.DEBUG) console.log('Error (' + data.status + '): ' + data.responseText); // Refresh ufAlerts for errors if target defined - if (this.settings.msgTarget) { + if (settings.msgTarget) { // Check if ufAlerts is instanced and empty - if (!this.settings.msgTarget.data('ufAlerts')) this.settings.msgTarget.ufAlerts(); - else this.settings.msgTarget.ufAlerts('clear'); + if (!settings.msgTarget.data('ufAlerts')) settings.msgTarget.ufAlerts(); + else settings.msgTarget.ufAlerts('clear'); // Trigger failure event on render.ufAlerts event - this.settings.msgTarget.on('render.ufAlerts', function () { - this.$element.trigger('renderError.ufModal'); - }.bind(this)); + settings.msgTarget.on('render.ufAlerts', function () { + $element.trigger('renderError.ufModal'); + }); // Pull alerts - this.settings.msgTarget.ufAlerts('fetch').ufAlerts('render'); + settings.msgTarget.ufAlerts('fetch').ufAlerts('render'); } else { // renderError.ufModal event should always be able to trigger - this.$element.trigger('renderError.ufModal'); + $element.trigger('renderError.ufModal'); } } } ) - } - - $.extend(Plugin.prototype, { - /** - * Destroys instance - */ - destroy: function () { - // Remove modal from selector - if (this.modal) this.modal.remove(); - - // Unbind plugin events - this.$element.off('.' + this._name); - - // Remove plugin data from internal jQuery store (jQuery doesn't store with data-*, but can access it) - this.$element.removeData(this._name); - return this.$element; - }, /** * Returns underlying modal */ - getModal: function () { - return this.modal; + function getModal() { + return modal; } - }); - - // Handles instantiation and access to non-private methods. - $.fn[pluginName] = function(methodOrOptions) { + + return { + destroy, + getModal, + }; + } + + /** + * Handles instantiation and access to non-private methods. + * @param {Options|keyof ReturnType|undefined} methodOrOptions + */ + function interop(methodOrOptions) { // Grab plugin instance + /** @type {ReturnType|undefined} */ var instance = $(this).data(pluginName); - // If undefined or object, initalise plugin. - if (methodOrOptions === undefined || typeof methodOrOptions === 'object') { - // Only initalise if not previously done. + + // If undefined or object, initialize plugin. + if (typeof methodOrOptions === 'undefined' || typeof methodOrOptions === 'object') { + // Only initialize if not previously done. if (!instance) { - $(this).data(pluginName, new Plugin(this, methodOrOptions)); + $(this).data(pluginName, createPlugin(this, methodOrOptions)); } return this; } // Otherwise ensure first parameter is a valid string, and is the name of an actual function. else if (typeof methodOrOptions === 'string' && typeof instance[methodOrOptions] === 'function') { - // Ensure not a private function - if (methodOrOptions.indexOf('_') !== 0) { - return instance[methodOrOptions]( Array.prototype.slice.call(arguments, 1)); - } - else { - console.error('Method ' + methodOrOptions + ' is private!'); - } + return instance[methodOrOptions](); } else { console.error('Method ' + methodOrOptions + ' does not exist.'); } }; -})(jQuery, window, document); \ No newline at end of file + + $.fn[pluginName] = interop; +})(jQuery, document); \ No newline at end of file