PageSourceSearch

https://burgerking.com.sg/Content/js/plugins/featherlight.js

js burgerking.com.sg collected 2026-10-02 11:35:21 UTC 24,835 bytes, 550 lines download raw bytes

1/**
2 * Featherlight - ultra slim jQuery lightbox
3 * Version 1.3.4 - http://noelboss.github.io/featherlight/
4 *
5 * Copyright 2015, Noël Raoul Bossart (http://www.noelboss.com)
6 * MIT Licensed.
7**/
8(function ($) {
9    "use strict";
10
11    if ('undefined' === typeof $) {
12        if ('console' in window) { window.console.info('Too much lightness, Featherlight needs jQuery.'); }
13        return;
14    }
15
16    /* Featherlight is exported as $.featherlight.
17	   It is a function used to open a featherlight lightbox.
18
19	   [tech]
20	   Featherlight uses prototype inheritance.
21	   Each opened lightbox will have a corresponding object.
22	   That object may have some attributes that override the
23	   prototype's.
24	   Extensions created with Featherlight.extend will have their
25	   own prototype that inherits from Featherlight's prototype,
26	   thus attributes can be overriden either at the object level,
27	   or at the extension level.
28	   To create callbacks that chain themselves instead of overriding,
29	   use chainCallbacks.
30	   For those familiar with CoffeeScript, this correspond to
31	   Featherlight being a class and the Gallery being a class
32	   extending Featherlight.
33	   The chainCallbacks is used since we don't have access to
34	   CoffeeScript's `super`.
35	*/
36
37    function Featherlight($content, config) {
38        if (this instanceof Featherlight) {  /* called with new */
39            this.id = Featherlight.id++;
40            this.setup($content, config);
41            this.chainCallbacks(Featherlight._callbackChain);
42        } else {
43            var fl = new Featherlight($content, config);
44            fl.open();
45            return fl;
46        }
47    }
48
49    var opened = [],
50		pruneOpened = function (remove) {
51		    opened = $.grep(opened, function (fl) {
52		        return fl !== remove && fl.$instance.closest('body').length > 0;
53		    });
54		    return opened;
55		};
56
57    // structure({iframeMinHeight: 44, foo: 0}, 'iframe')
58    //   #=> {min-height: 44}
59    var structure = function (obj, prefix) {
60        var result = {},
61			regex = new RegExp('^' + prefix + '([A-Z])(.*)');
62        for (var key in obj) {
63            var match = key.match(regex);
64            if (match) {
65                var dasherized = (match[1] + match[2].replace(/([A-Z])/g, '-$1')).toLowerCase();
66                result[dasherized] = obj[key];
67            }
68        }
69        return result;
70    };
71
72    /* document wide key handler */
73    var eventMap = { keyup: 'onKeyUp', resize: 'onResize' };
74
75    var globalEventHandler = function (event) {
76        $.each(Featherlight.opened().reverse(), function () {
77            if (!event.isDefaultPrevented()) {
78                if (false === this[eventMap[event.type]](event)) {
79                    event.preventDefault(); event.stopPropagation(); return false;
80                }
81            }
82        });
83    };
84
85    var toggleGlobalEvents = function (set) {
86        if (set !== Featherlight._globalHandlerInstalled) {
87            Featherlight._globalHandlerInstalled = set;
88            var events = $.map(eventMap, function (_, name) { return name + '.' + Featherlight.prototype.namespace; }).join(' ');
89            $(window)[set ? 'on' : 'off'](events, globalEventHandler);
90        }
91    };
92
93    Featherlight.prototype = {
94        constructor: Featherlight,
95        /*** defaults ***/
96        /* extend featherlight with defaults and methods */
97        namespace: 'featherlight',         /* Name of the events and css class prefix */
98        targetAttr: 'data-featherlight',    /* Attribute of the triggered element that contains the selector to the lightbox content */
99        variant: null,                   /* Class that will be added to change look of the lightbox */
100        resetCss: false,                  /* Reset all css */
101        background: null,                   /* Custom DOM for the background, wrapper and the closebutton */
102        openTrigger: 'click',                /* Event that triggers the lightbox */
103        closeTrigger: 'click',                /* Event that triggers the closing of the lightbox */
104        filter: null,                   /* Selector to filter events. Think $(...).on('click', filter, eventHandler) */
105        root: 'body',                 /* Where to append featherlights */
106        openSpeed: 250,                    /* Duration of opening animation */
107        closeSpeed: 250,                    /* Duration of closing animation */
108        closeOnClick: 'background',           /* Close lightbox on click ('background', 'anywhere' or false) */
109        closeOnEsc: true,                   /* Close lightbox when pressing esc */
110        closeIcon: '✕',             /* Close icon */
111        loading: '',                     /* Content to show while initial content is loading */
112        persist: false,									/* If set, the content persist and will be shown again when opened again. 'shared' is a special value when binding multiple elements for them to share the same content */
113        otherClose: null,                   /* Selector for alternate close buttons (e.g. "a.close") */
114        beforeOpen: $.noop,                 /* Called before open. can return false to prevent opening of lightbox. Gets event as parameter, this contains all data */
115        beforeContent: $.noop,                /* Called when content is loaded. Gets event as parameter, this contains all data */
116        beforeClose: $.noop,                 /* Called before close. can return false to prevent opening of lightbox. Gets event as parameter, this contains all data */
117        afterOpen: $.noop,                 /* Called after open. Gets event as parameter, this contains all data */
118        afterContent: $.noop,                 /* Called after content is ready and has been set. Gets event as parameter, this contains all data */
119        afterClose: $.noop,                 /* Called after close. Gets event as parameter, this contains all data */
120        onKeyUp: $.noop,                 /* Called on key down for the frontmost featherlight */
121        onResize: $.noop,                 /* Called after new content and when a window is resized */
122        type: null,                   /* Specify type of lightbox. If unset, it will check for the targetAttrs value. */
123        contentFilters: ['jquery', 'image', 'html', 'ajax', 'iframe', 'text'], /* List of content filters to use to determine the content */
124
125        /*** methods ***/
126        /* setup iterates over a single instance of featherlight and prepares the background and binds the events */
127        setup: function (target, config) {
128            /* all arguments are optional */
129            if (typeof target === 'object' && target instanceof $ === false && !config) {
130                config = target;
131                target = undefined;
132            }
133
134            var self = $.extend(this, config, { target: target }),
135				css = !self.resetCss ? self.namespace : self.namespace + '-reset', /* by adding -reset to the classname, we reset all the default css */
136				$background = $(self.background || [
137					'<div class="' + css + '-loading ' + css + '">',
138						'<div class="' + css + '-content">',
139							'<span class="' + css + '-close-icon ' + self.namespace + '-close">',
140								self.closeIcon,
141							'</span>',
142							'<div class="' + self.namespace + '-inner">' + self.loading + '</div>',
143						'</div>',
144					'</div>'].join('')),
145				closeButtonSelector = '.' + self.namespace + '-close' + (self.otherClose ? ',' + self.otherClose : '');
146
147            self.$instance = $background.clone().addClass(self.variant); /* clone DOM for the background, wrapper and the close button */
148
149            /* close when click on background/anywhere/null or closebox */
150            self.$instance.on(self.closeTrigger + '.' + self.namespace, function (event) {
151                var $target = $(event.target);
152                if (('background' === self.closeOnClick && $target.is('.' + self.namespace))
153					|| 'anywhere' === self.closeOnClick
154					|| $target.closest(closeButtonSelector).length) {
155                    event.preventDefault();
156                    self.close();
157                }
158            });
159
160            return this;
161        },
162
163        /* this method prepares the content and converts it into a 
163jQuery object or a promise */
164        getContent: function () {
165            if (this.persist !== false && this.$content) {
166                return this.$content;
167            }
168            var self = this,
169				filters = this.constructor.contentFilters,
170				readTargetAttr = function (name) { return self.$currentTarget && self.$currentTarget.attr(name); },
171				targetValue = readTargetAttr(self.targetAttr),
172				data = self.target || targetValue || '';
173
174            /* Find which filter applies */
175            var filter = filters[self.type]; /* check explicit type like {type: 'image'} */
176
177            /* check explicit type like data-featherlight="image" */
178            if (!filter && data in filters) {
179                filter = filters[data];
180                data = self.target && targetValue;
181            }
182            data = data || readTargetAttr('href') || '';
183
184            /* check explicity type & content like {image: 'photo.jpg'} */
185            if (!filter) {
186                for (var filterName in filters) {
187                    if (self[filterName]) {
188                        filter = filters[filterName];
189                        data = self[filterName];
190                    }
191                }
192            }
193
194            /* otherwise it's implicit, run checks */
195            if (!filter) {
196                var target = data;
197                data = null;
198                $.each(self.contentFilters, function () {
199                    filter = filters[this];
200                    if (filter.test) {
201                        data = filter.test(target);
202                    }
203                    if (!data && filter.regex && target.match && target.match(filter.regex)) {
204                        data = target;
205                    }
206                    return !data;
207                });
208                if (!data) {
209                    if ('console' in window) { window.console.error('Featherlight: no content filter found ' + (target ? ' for "' + target + '"' : ' (no target specified)')); }
210                    return false;
211                }
212            }
213            /* Process it */
214            return filter.process.call(self, data);
215        },
216
217        /* sets the content of $instance to $content */
218        setContent: function ($content) {
219            var self = this;
220            /* we need a special class for the iframe */
221            if ($content.is('iframe') || $('iframe', $content).length > 0) {
222                self.$instance.addClass(self.namespace + '-iframe');
223            }
224
225            self.$instance.removeClass(self.namespace + '-loading');
226
227            /* replace content by appending to existing one before it is removed
228			   this insures that featherlight-inner remain at the same relative
229				 position to any other items added to featherlight-content */
230            self.$instance.find('.' + self.namespace + '-inner')
231				.not($content)                /* excluded new content, important if persisted */
232				.slice(1).remove().end()			/* In the unexpected event where there are many inner elements, remove all but the first one */
233				.replaceWith($.contains(self.$instance[0], $content[0]) ? '' : $content);
234
235            self.$content = $content.addClass(self.namespace + '-inner');
236
237            return self;
238        },
239
240        /* opens the lightbox. "this" contains $instance with the lightbox, and with the config.
241			Returns a promise that is resolved after is successfully opened. */
242        open: function (event) {
243            var self = this;
244            self.$instance.hide().appendTo(self.root);
245            if ((!event || !event.isDefaultPrevented())
246				&& self.beforeOpen(event) !== false) {
247
248                if (event) {
249                    event.preventDefault();
250                }
251                var $content = self.getContent();
252
253                if ($content) {
254                    opened.push(self);
255
256                    toggleGlobalEvents(true);
257
258                    self.$instance.fadeIn(self.openSpeed);
259                    self.beforeContent(event);
260
261                    /* Set content and show */
262                    return $.when($content)
263						.always(function ($content) {
264						    self.setContent($content);
265						    self.afterContent(event);
266						})
267						.then(self.$instance.promise())
268						/* Call afterOpen after fadeIn is done */
269						.done(function () { self.afterOpen(event); });
270                }
271            }
272            self.$instance.detach();
273            return $.Deferred().reject().promise();
274        },
275
276        /* closes the lightbox. "this" contains $instance with the lightbox, and with the config
277			returns a promise, resolved after the lightbox is successfully closed. */
278        close: function (event) {
279            var self = this,
280				deferred = $.Deferred();
281
282            if (self.beforeClose(event) === false) {
283                deferred.reject();
284            } else {
285
286                if (0 === pruneOpened(self).length) {
287                    toggleGlobalEvents(false);
288                }
289
290                self.$instance.fadeOut(self.closeSpeed, function () {
291                    self.$instance.detach();
292                    self.afterClose(event);
293                    deferred.resolve();
294                });
295            }
296            return deferred.promise();
297        },
298
299        /* Utility function to chain callbacks
300		   [Warning: guru-level]
301		   Used be extensions that want to let users specify callbacks but
302		   also need themselves to use the callbacks.
303		   The argument 'chain' has callback names as keys and function(super, event)
304		   as values. That function is meant to call `super` at some point.
305		*/
306        chainCallbacks: function (chain) {
307            for (var name in chain) {
308                this[name] = $.proxy(chain[name], this, $.proxy(this[name], this));
309            }
310        }
311    };
312
313    $.extend(Featherlight, {
314        id: 0,                                    /* Used to id single featherlight instances */
315        autoBind: '[data-featherlight]',    /* Will automatically bind elements matching this selector. Clear or set before onReady */
316        defaults: Featherlight.prototype,   /* You can access and override all defaults using $.featherlight.defaults, which is just a synonym for $.featherlight.prototype */
317        /* Contains the logic to determine content */
318        contentFilters: {
319            jquery: {
320                regex: /^[#.]\w/,         /* Anything that starts with a class name or identifiers */
321                test: function (elem) { return elem instanceof $ && elem; },
322                process: function (elem) { return this.persist !== false ? $(elem) : $(elem).clone(true); }
323            },
324            image: {
325                regex: /\.(png|jpg|jpeg|gif|tiff|bmp|svg)(\?\S*)?$/i,
326                process: function (url) {
327                    var self = this,
328						deferred = $.Deferred(),
329						img = new Image(),
330						$img = $('<img src="' + url + '" alt="" class="' + self.namespace + '-image" />');
331                    img.onload = function () {
332                        /* Store naturalWidth & height for IE8 */
333                        $img.naturalWidth = img.width; $img.naturalHeight = img.height;
334                        deferred.resolve($img);
335                    };
336                    img.onerror = function () { deferred.reject($img); };
337                    img.src = url;
338                    return deferred.promise();
339                }
340            },
341            html: {
342                regex: /^\s*<[\w!][^<]*>/, /* Anything that starts with some kind of valid tag */
343                process: function (html) { return $(html); }
344            },
345            ajax: {
346                regex: /./,            /* At this point, any content is assumed to be an URL */
347                process: function (url) {
348                    var self = this,
349						deferred = $.Deferred();
350                    /* we are using load so one can specify a target with: url.html #targetelement */
351                    var $container = $('<div></div>').load(url, function (response, status) {
352                        if (status !== "error") {
353                            deferred.resolve($container.contents());
354                        }
355                        deferred.fail();
356                    });
357                    return deferred.promise();
358                }
359            },
360            iframe: {
361                process: function (url) {
362                    var deferred = new $.Deferred();
363                    var $content = $('<iframe/>')
364						.hide()
365						.attr('src', url)
366						.css(structure(this, 'iframe'))
367						.on('load', function () { deferred.resolve($content.show()); })
368						// We can't move an <iframe> and avoid reloading it,
369						// so let's put it in place ourselves right now:
370						.appendTo(this.$instance.find('.' + this.namespace + '-content'));
371                    return deferred.promise();
372                }
373            },
374            text: {
375                process: function (text) { return $('<div>', { text: text }); }
376            }
377        },
378
379        functionAttributes: ['beforeOpen', 'afterOpen', 'beforeContent', 'afterContent', 'beforeClose', 'afterClose'],
380
381        /*** class methods ***/
382        /* read element's attributes starting with data-featherlight- */
383        readElementConfig: function (element, namespace) {
384            var Klass = this,
385				regexp = new RegExp('^data-' + namespace + '-(.*)'),
386				config = {};
387            if (element && element.attributes) {
388                $.each(element.attributes, function () {
389                    var match = this.name.match(regexp);
390                    if (match) {
391                        var val = this.value,
392							name = $.camelCase(match[1]);
393                        if ($.inArray(name, Klass.functionAttributes) >= 0) {  /* jshint -W054 */
394                            val = new Function(val);                           /* jshint +W054 */
395                        } else {
396                            try { val = $.parseJSON(val); }
397                            catch (e) { }
398                        }
399                        config[name] = val;
400                    }
401                });
402            }
403            return config;
404        },
405
406        /* Used to create a Featherlight extension
407		   [Warning: guru-level]
408		   Creates the extension's prototype that in turn
409		   inherits Featherlight's prototype.
410		   Could be used to extend an extension too...
411		   This is pretty high level wizardy, it comes pretty much straight
412		   from CoffeeScript and won't teach you anything about Featherlight
413		   as it's not really specific to this library.
414		   My suggestion: move along and keep your sanity.
415		*/
416        extend: function (child, defaults) {
417            /* Setup class hierarchy, adapted from CoffeeScript */
418            var Ctor = function () { this.constructor = child; };
419            Ctor.prototype = this.prototype;
420            child.prototype = new Ctor();
421            child.__super__ = this.prototype;
422            /* Copy class methods & attributes */
423            $.extend(child, this, defaults);
424            child.defaults = child.prototype;
425            return child;
426        },
427
428        attach: function ($source, $content, config) {
429            var Klass = this;
430            if (typeof $content === 'object' && $content instanceof $ === false && !config) {
431                config = $content;
432                $content = undefined;
433            }
434            /* make a copy */
435            config = $.extend({}, config);
436
437            /* Only for openTrigger and namespace... */
438            var namespace = config.namespace || Klass.defaults.namespace,
439				tempConfig = $.extend({}, Klass.defaults, Klass.readElementConfig($source[0], namespace), c
439onfig),
440				sharedPersist;
441
442            $source.on(tempConfig.openTrigger + '.' + tempConfig.namespace, tempConfig.filter, function (event) {
443                /* ... since we might as well compute the config on the actual target */
444                var elemConfig = $.extend(
445					{ $source: $source, $currentTarget: $(this) },
446					Klass.readElementConfig($source[0], tempConfig.namespace),
447					Klass.readElementConfig(this, tempConfig.namespace),
448					config);
449                var fl = sharedPersist || $(this).data('featherlight-persisted') || new Klass($content, elemConfig);
450                if (fl.persist === 'shared') {
451                    sharedPersist = fl;
452                } else if (fl.persist !== false) {
453                    $(this).data('featherlight-persisted', fl);
454                }
455                elemConfig.$currentTarget.blur(); // Otherwise 'enter' key might trigger the dialog again
456                fl.open(event);
457            });
458            return $source;
459        },
460
461        current: function () {
462            var all = this.opened();
463            return all[all.length - 1] || null;
464        },
465
466        opened: function () {
467            var klass = this;
468            pruneOpened();
469            return $.grep(opened, function (fl) { return fl instanceof klass; });
470        },
471
472        close: function () {
473            var cur = this.current();
474            if (cur) { return cur.close(); }
475        },
476
477        /* Does the auto binding on startup.
478		   Meant only to be used by Featherlight and its extensions
479		*/
480        _onReady: function () {
481            var Klass = this;
482            if (Klass.autoBind) {
483                /* Bind existing elements */
484                $(Klass.autoBind).each(function () {
485                    Klass.attach($(this));
486                });
487                /* If a click propagates to the document level, then we have an item that was added later on */
488                $(document).on('click', Klass.autoBind, function (evt) {
489                    if (evt.isDefaultPrevented()) {
490                        return;
491                    }
492                    evt.preventDefault();
493                    /* Bind featherlight */
494                    Klass.attach($(evt.currentTarget));
495                    /* Click again; this time our binding will catch it */
496                    $(evt.target).click();
497                });
498            }
499        },
500
501        /* Featherlight uses the onKeyUp callback to intercept the escape key.
502		   Private to Featherlight.
503		*/
504        _callbackChain: {
505            onKeyUp: function (_super, event) {
506                if (27 === event.keyCode) {
507                    if (this.closeOnEsc) {
508                        this.$instance.find('.' + this.namespace + '-close:first').click();
509                    }
510                    return false;
511                } else {
512                    return _super(event);
513                }
514            },
515
516            onResize: function (_super, event) {
517                if (this.$content.naturalWidth) {
518                    var w = this.$content.naturalWidth, h = this.$content.naturalHeight;
519                    /* Reset apparent image size first so container grows */
520                    this.$content.css('width', '').css('height', '');
521                    /* Calculate the worst ratio so that dimensions fit */
522                    var ratio = Math.max(
523						w / parseInt(this.$content.parent().css('width'), 10),
524						h / parseInt(this.$content.parent().css('height'), 10));
525                    /* Resize content */
526                    if (ratio > 1) {
527                        this.$content.css('width', '' + w / ratio + 'px').css('height', '' + h / ratio + 'px');
528                    }
529                }
530                return _super(event);
531            },
532
533            afterContent: function (_super, event) {
534                var r = _super(event);
535                this.onResize(event);
536                return r;
537            }
538        }
539    });
540
541    $.featherlight = Featherlight;
542
543    /* bind jQuery elements to trigger featherlight */
544    $.fn.featherlight = function ($content, config) {
545        return Featherlight.attach(this, $content, config);
546    };
547
548    /* bind featherlight on ready if config autoBind is set */
549    $(document).ready(function () { Featherlight._onReady(); });
550}(jQuery));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.