Completed
Push — development ( f93eb8...ffa1a0 )
by Thomas
20s
created

htdocs/theme/frontend/js/jquery.plugin-base.js   C

Complexity

Total Complexity 57
Complexity/F 1.54

Size

Lines of Code 442
Function Count 37

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
c 1
b 0
f 0
nc 3264
dl 0
loc 442
rs 5.7222
wmc 57
mnd 6
bc 50
fnc 37
bpm 1.3513
cpm 1.5405
noi 3

21 Functions

Rating   Name   Duplication   Size   Complexity  
A Object.create 0 5 1
A $.subscribe 0 3 1
A PluginBase.getEventName 0 12 2
A PluginBase.getOptions 0 3 1
B jquery.plugin-base.js ➔ PluginBase 0 40 1
B $.plugin 0 29 1
A PluginBase._on 0 14 1
A PluginBase.getOption 0 3 1
A PluginBase.update 0 3 1
A PluginBase.getName 0 3 1
A PluginBase.setOption 0 7 1
A PluginBase.getElement 0 3 1
A PluginBase.init 0 3 1
B PluginBase._off 0 26 1
A $.unsubscribe 0 3 1
A PluginBase.destroy 0 7 3
A $.publish 0 3 1
A PluginBase._destroy 0 21 1
A PluginBase.applyDataAttributes 0 19 1
B jquery.plugin-base.js ➔ deserializeValue 0 13 8
B $.overridePlugin 0 25 3

How to fix   Complexity   

Complexity

Complex classes like htdocs/theme/frontend/js/jquery.plugin-base.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
;(function ($) {
2
    /*! Tiny Pub/Sub - v0.7.0 - 2013-01-29
3
     * https://github.com/cowboy/jquery-tiny-pubsub
4
     * Copyright (c) 2014 "Cowboy" Ben Alman; Licensed MIT */
5
    var o = $({});
6
    $.subscribe = function () {
7
        o.on.apply(o, arguments);
8
    };
9
10
    $.unsubscribe = function () {
11
        o.off.apply(o, arguments);
12
    };
13
14
    $.publish = function () {
15
        o.trigger.apply(o, arguments);
16
    };
17
}(jQuery));
18
19
;(function ($, window) {
20
    'use strict';
21
22
    var numberRegex = /^-?\d*\.?\d*$/,
23
        objectRegex = /^[[{]/;
24
25
    /**
26
     * Tries to deserialize the given string value and returns the right
27
     * value if its successful.
28
     *
29
     * @private
30
     * @method deserializeValue
31
     * @param {String} value
32
     * @returns {String|Boolean|Number|Object|Array|null}
33
     */
34
    function deserializeValue(value) {
35
        try {
36
            return !value ? value : value === 'true' || (
37
                value === 'false' ? false
38
                    : value === 'null' ? null
39
                    : numberRegex.test(value) ? +value
40
                    : objectRegex.test(value) ? $.parseJSON(value)
41
                    : value
42
            );
43
        } catch (e) {
44
            return value;
45
        }
46
    }
47
48
    /**
49
     * Constructor method of the PluginBase class. This method will try to
50
     * call the ```init```-method, where you can place your custom initialization of the plugin.
51
     *
52
     * @class PluginBase
53
     * @constructor
54
     * @param {String} name - Plugin name that is used for the events suffixes.
55
     * @param {HTMLElement} element - Element which should be used for the plugin.
56
     * @param {Object} options - The user settings, which overrides the default settings
57
     */
58
    function PluginBase(name, element, options) {
59
        var me = this;
60
61
        /**
62
         * @property {String} _name - Name of the Plugin
63
         * @private
64
         */
65
        me._name = name;
66
67
        /**
68
         * @property {jQuery} $el - Plugin element wrapped by jQuery
69
         */
70
        me.$el = $(element);
71
72
        /**
73
         * @property {Object} opts - Merged plugin options
74
         */
75
        me.opts = $.extend({}, me.defaults || {}, options);
76
77
        /**
78
         * @property {string} eventSuffix - Suffix which will be appended to the eventType to get namespaced events
79
         */
80
        me.eventSuffix = '.' + name;
81
82
        /**
83
         * @property {Array} _events Registered events listeners. See {@link PluginBase._on} for registration
84
         * @private
85
         */
86
        me._events = [];
87
88
        // Create new selector for the plugin
89
        $.expr[':']['plugin-' + name.toLowerCase()] = function (elem) {
90
            return !!$.data(elem, 'plugin_' + name);
91
        };
92
93
        // Call the init method of the plugin
94
        me.init(element, options);
95
96
        $.publish('plugin/' + name + '/onInit', [ me ]);
97
    }
98
99
    PluginBase.prototype = {
100
101
        /**
102
         * Template function for the plugin initialisation.
103
         * Must be overridden for custom initialisation logic or an error will be thrown.
104
         *
105
         * @public
106
         * @method init
107
         */
108
        init: function () {
109
            throw new Error('Plugin ' + this.getName() + ' has to have an init function!');
110
        },
111
112
        /**
113
         * Template function for the plugin destruction.
114
         * Should be overridden for custom destruction code.
115
         *
116
         * @public
117
         * @method destroy
118
         */
119
        destroy: function () {
120
            if (typeof console !== 'undefined' && typeof console.warn === 'function') {
121
                console.warn('Plugin ' + this.getName() + ' should have a custom destroy method!');
122
            }
123
124
            this._destroy();
125
        },
126
127
        /**
128
         * Template function to update the plugin.
129
         * This function will be called when the breakpoint has changed but the configurations are the same.
130
         *
131
         * @public
132
         * @method update
133
         */
134
        update: function () {
135
136
        },
137
138
        /**
139
         * Destroys the plugin on the {@link HTMLElement}. It removes the instance of the plugin
140
         * which is bounded to the {@link jQuery} element.
141
         *
142
         * If the plugin author has used the {@link PluginBase._on} method, the added event listeners
143
         * will automatically be cleared.
144
         *
145
         * @private
146
         * @method _destroy
147
         * @returns {PluginBase}
148
         */
149
        _destroy: function () {
150
            var me = this,
151
                name = me.getName();
152
153
            $.each(me._events, function (i, obj) {
154
                if (typeof obj === 'object') {
155
                    obj.el.off(obj.event);
156
                }
157
            });
158
159
            // remove all references of external plugins
160
            $.each(me.opts, function (o) {
161
                delete me.opts[o];
162
            });
163
164
            me.$el.removeData('plugin_' + name);
165
166
            $.publish('plugin/' + name + '/onDestroy', [ me ]);
167
168
            return me;
169
        },
170
171
        /**
172
         * Wrapper method for {@link jQuery.on}, which registers in the event in the {@link PluginBase._events} array,
173
         * so the listeners can automatically be removed using the {@link PluginBase._destroy} method.
174
         *
175
         * @params {jQuery} Element, which should be used to add the listener
176
         * @params {String} Event type, you want to register.
177
         * @returns {PluginBase}
178
         */
179
        _on: function () {
180
            var me = this,
181
                $el = $(arguments[0]),
182
                event = me.getEventName(arguments[1]),
183
                args = Array.prototype.slice.call(arguments, 2);
184
185
            me._events.push({ 'el': $el, 'event': event });
186
            args.unshift(event);
187
            $el.on.apply($el, args);
188
189
            $.publish('plugin/' + me._name + '/onRegisterEvent', [ $el, event ]);
190
191
            return me;
192
        },
193
194
        /**
195
         * Wrapper method for {@link jQuery.off}, which removes the event listener from the {@link PluginBase._events}
196
         * array.
197
         *
198
         * @param {jQuery} element - Element, which contains the listener
199
         * @param {String} event - Name of the event to remove.
200
         * @returns {PluginBase}
201
         * @private
202
         */
203
        _off: function (element, event) {
204
            var me = this,
205
                events = me._events,
206
                pluginEvent = me.getEventName(event),
207
                eventIds = [],
208
                $element = $(element),
209
                filteredEvents = $.grep(events, function (obj, index) {
210
                    eventIds.push(index);
211
                    return typeof obj !== 'undefined' && pluginEvent === obj.event && $element[0] === obj.el[0];
212
                });
213
214
            $.each(filteredEvents, function (index, event) {
215
                $element.off(event.event);
216
            });
217
218
            $.each(eventIds, function (id) {
219
                if (!events[id]) {
220
                    return;
221
                }
222
                delete events[id];
223
            });
224
225
            $.publish('plugin/' + me._name + '/onRemoveEvent', [ $element, event ]);
226
227
            return me;
228
        },
229
230
        /**
231
         * Returns the name of the plugin.
232
         * @returns {PluginBase._name|String}
233
         */
234
        getName: function () {
235
            return this._name;
236
        },
237
238
        /**
239
         * Returns the event name with the event suffix appended.
240
         * @param {String} event - Event name
241
         * @returns {String}
242
         */
243
        getEventName: function (event) {
244
            var suffix = this.eventSuffix,
245
                parts = event.split(' '),
246
                len = parts.length,
247
                i = 0;
248
249
            for (; i < len; i++) {
250
                parts[i] += suffix;
251
            }
252
253
            return parts.join(' ');
254
        },
255
256
        /**
257
         * Returns the element which registered the plugin.
258
         * @returns {PluginBase.$el}
259
         */
260
        getElement: function () {
261
            return this.$el;
262
        },
263
264
        /**
265
         * Returns the options of the plugin. The method returns a copy of the options object and not a reference.
266
         * @returns {Object}
267
         */
268
        getOptions: function () {
269
            return $.extend({}, this.opts);
270
        },
271
272
        /**
273
         * Returns the value of a single option.
274
         * @param {String} key - Option key.
275
         * @returns {mixed}
276
         */
277
        getOption: function (key) {
278
            return this.opts[key];
279
        },
280
281
        /**
282
         * Sets a plugin option. Deep linking of the options are now supported.
283
         * @param {String} key - Option key
284
         * @param {mixed} value - Option value
285
         * @returns {PluginBase}
286
         */
287
        setOption: function (key, value) {
288
            var me = this;
289
290
            me.opts[key] = value;
291
292
            return me;
293
        },
294
295
        /**
296
         * Fetches the configured options based on the {@link PluginBase.$el}.
297
         *
298
         * @param {Boolean} shouldDeserialize
299
         * @returns {mixed} configuration
300
         */
301
        applyDataAttributes: function (shouldDeserialize) {
302
            var me = this, attr;
303
304
            $.each(me.opts, function (key) {
305
                attr = me.$el.attr('data-' + key);
306
307
                if (typeof attr === 'undefined') {
308
                    return true;
309
                }
310
311
                me.opts[key] = shouldDeserialize !== false ? deserializeValue(attr) : attr;
312
313
                return true;
314
            });
315
316
            $.publish('plugin/' + me._name + '/onDataAttributes', [ me.$el, me.opts ]);
317
318
            return me.opts;
319
        }
320
    };
321
322
    // Expose the private PluginBase constructor to global jQuery object
323
    $.PluginBase = PluginBase;
324
325
    // Object.create support test, and fallback for browsers without it
326
    if (typeof Object.create !== 'function') {
327
        Object.create = function (o) {
0 ignored issues
show
Compatibility Best Practice introduced by
You are extending the built-in type Object. This may have unintended consequences on other objects using this built-in type. Consider subclassing instead.
Loading history...
328
            function F() { }
329
            F.prototype = o;
330
            return new F();
331
        };
332
    }
333
334
    /**
335
     * Creates a new jQuery plugin based on the {@link PluginBase} object prototype. The plugin will
336
     * automatically created in {@link jQuery.fn} namespace and will initialized on the fly.
337
     *
338
     * The {@link PluginBase} object supports an automatically destruction of the registered events. To
339
     * do so, please use the {@link PluginBase._on} method to create event listeners.
340
     *
341
     * @param {String} name - Name of the plugin
342
     * @param {Object|Function} plugin - Plugin implementation
343
     * @returns {void}
344
     *
345
     * @example
346
     * // Register your plugin
347
     * $.plugin('yourName', {
348
     *    defaults: { key: 'value' },
349
     *
350
     *    init: function() {
351
     *        // ...initialization code
352
     *    },
353
     *
354
     *    destroy: function() {
355
     *      // ...your destruction code
356
     *
357
     *      // Use the force! Use the internal destroy method.
358
     *      me._destroy();
359
     *    }
360
     * });
361
     *
362
     * // Call the plugin
363
     * $('.test').yourName();
364
     */
365
    $.plugin = function (name, plugin) {
366
        var pluginFn = function (options) {
367
            return this.each(function () {
368
                var element = this,
369
                    pluginData = $.data(element, 'plugin_' + name);
370
371
                if (!pluginData) {
372
                    if (typeof plugin === 'function') {
373
                        /* eslint new-cap: "off" */
374
                        pluginData = new plugin();
0 ignored issues
show
Coding Style Best Practice introduced by
By convention, constructors like plugin should be capitalized.
Loading history...
375
                    } else {
376
                        var Plugin = function () {
377
                            PluginBase.call(this, name, element, options);
378
                        };
379
380
                        Plugin.prototype = $.extend(Object.create(PluginBase.prototype), { constructor: Plugin }, plugin);
381
                        pluginData = new Plugin();
382
                    }
383
384
                    $.data(element, 'plugin_' + name, pluginData);
385
                }
386
            });
387
        };
388
389
        window.PluginsCollection = window.PluginsCollection || {};
390
        window.PluginsCollection[name] = plugin;
391
392
        $.fn[name] = pluginFn;
393
    };
394
395
    /**
396
     * Provides the ability to overwrite jQuery plugins which are built on top of the {@link PluginBase} class. All of
397
     * our jQuery plugins (or to be more technical about it, the prototypes of our plugins) are registered in the object
398
     * {@link window.PluginsCollection} which can be accessed from anywhere in your storefront.
399
     *
400
     * Please keep in mind that the method overwrites the plugin in jQuery's plugin namespace {@link jQuery.fn} as well,
401
     * but you still have the ability to access the overwritten method(s) using the ```superclass``` object property.
402
     *
403
     * @example How to overwrite the ```showResult```-method in the "search" plugin.
404
     * $.overridePlugin('search', {
405
     *    showResult: function(response) {
406
     *        //.. do something with the response
407
     *    }
408
     * });
409
     *
410
     * @example Call the original method without modifications
411
     * $.overridePlugin('search', {
412
     *    showResult: function(response) {
413
     *        this.superclass.showResult.apply(this, arguments);
414
     *    }
415
     * });
416
     */
417
    $.overridePlugin = function (pluginName, override) {
418
        var overridePlugin = window.PluginsCollection[pluginName];
419
420
        if (typeof overridePlugin !== 'object' || typeof override !== 'object') {
421
            return false;
422
        }
423
424
        $.fn[pluginName] = function (options) {
425
            return this.each(function () {
426
                var element = this,
427
                    pluginData = $.data(element, 'plugin_' + pluginName);
428
429
                if (!pluginData) {
430
                    var Plugin = function () {
431
                        PluginBase.call(this, pluginName, element, options);
432
                    };
433
434
                    Plugin.prototype = $.extend(Object.create(PluginBase.prototype), { constructor: Plugin, superclass: overridePlugin }, overridePlugin, override);
435
                    pluginData = new Plugin();
436
437
                    $.data(element, 'plugin_' + pluginName, pluginData);
438
                }
439
            });
440
        };
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
441
    };
442
})(jQuery, window);
443