PageSourceSearch

https://mavrxlab.org/site_libs/pymjs-1.3.2/pym.v1.js

js mavrxlab.org collected 2026-10-06 13:17:41 UTC 38,896 bytes, 1,116 lines download raw bytes

1/*! pym.js - v1.3.1 - 2017-08-06 */
2/*
3* Pym.js is library that resizes an iframe based on the width of the parent and the resulting height of the child.
4* Check out the docs at http://blog.apps.npr.org/pym.js/ or the readme at README.md for usage.
5*/
6
7/** @module pym */
8(function(factory) {
9    if (typeof define === 'function' && define.amd) {
10        define(factory);
11    }
12    else if (typeof module !== 'undefined' && module.exports) {
13        module.exports = factory();
14    } else {
15        window.pym = factory.call(this);
16    }
17})(function() {
18    var MESSAGE_DELIMITER = 'xPYMx';
19
20    var lib = {};
21
22    /**
23    * Create and dispatch a custom pym event
24    *
25    * @method _raiseCustomEvent
26    * @inner
27    *
28    * @param {String} eventName
29    */
30   var _raiseCustomEvent = function(eventName) {
31     var event = document.createEvent('Event');
32     event.initEvent('pym:' + eventName, true, true);
33     document.dispatchEvent(event);
34   };
35
36    /**
37    * Generic function for parsing URL params.
38    * Via http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
39    *
40    * @method _getParameterByName
41    * @inner
42    *
43    * @param {String} name The name of the paramter to get from the URL.
44    */
45    var _getParameterByName = function(name) {
46        var regex = new RegExp("[\\?&]" + name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]') + '=([^&#]*)');
47        var results = regex.exec(location.search);
48
49        if (results === null) {
50            return '';
51        }
52
53        return decodeURIComponent(results[1].replace(/\+/g, " "));
54    };
55
56    /**
57     * Check the message to make sure it comes from an acceptable xdomain.
58     * Defaults to '*' but can be overriden in config.
59     *
60     * @method _isSafeMessage
61     * @inner
62     *
63     * @param {Event} e The message event.
64     * @param {Object} settings Configuration.
65     */
66    var _isSafeMessage = function(e, settings) {
67        if (settings.xdomain !== '*') {
68            // If origin doesn't match our xdomain, return.
69            if (!e.origin.match(new RegExp(settings.xdomain + '$'))) { return; }
70        }
71
72        // Ignore events that do not carry string data #151
73        if (typeof e.data !== 'string') { return; }
74
75        return true;
76    };
77
78    /**
79     * Construct a message to send between frames.
80     *
81     * NB: We use string-building here because JSON message passing is
82     * not supported in all browsers.
83     *
84     * @method _makeMessage
85     * @inner
86     *
87     * @param {String} id The unique id of the message recipient.
88     * @param {String} messageType The type of message to send.
89     * @param {String} message The message to send.
90     */
91    var _makeMessage = function(id, messageType, message) {
92        var bits = ['pym', id, messageType, message];
93
94        return bits.join(MESSAGE_DELIMITER);
95    };
96
97    /**
98     * Construct a regex to validate and parse messages.
99     *
100     * @method _makeMessageRegex
101     * @inner
102     *
103     * @param {String} id The unique id of the message recipient.
104     */
105    var _makeMessageRegex = function(id) {
106        var bits = ['pym', id, '(\\S+)', '(.*)'];
107
108        return new RegExp('^' + bits.join(MESSAGE_DELIMITER) + '$');
109    };
110
111    /**
112    * Underscore implementation of getNow
113    *
114    * @method _getNow
115    * @inner
116    *
117    */
118    var _getNow = Date.now || function() {
119        return new Date().getTime();
120    };
121
122    /**
123    * Underscore implementation of throttle
124    *
125    * @method _throttle
126    * @inner
127    *
128    * @param {function} func Throttled function
129    * @param {number} wait Throttle wait time
130    * @param {object} options Throttle settings
131    */
132
133    var _throttle = function(func, wait, options) {
134        var context, args, result;
135        var timeout = null;
136        var previous = 0;
137        if (!options) {options = {};}
138        var later = function() {
139            previous = options.leading === false ? 0 : _getNow();
140            timeout = null;
141            result = func.apply(context, args);
142            if (!timeout) {context = args = null;}
143        };
144        return function() {
145            var now = _getNow();
146            if (!previous && options.leading === false) {previous = now;}
147            var remaining = wait - (now - previous);
148            context = this;
149            args = arguments;
150            if (remaining <= 0 || remaining > wait) {
151                if (timeout) {
152                    clearTimeout(timeout);
153                    timeout = null;
154                }
155                previous = now;
156                result = func.apply(context, args);
157                if (!timeout) {context = args = null;}
158            } else if (!timeout && options.trailing !== false) {
159                timeout = setTimeout(later, remaining);
160            }
161            return result;
162        };
163    };
164
165    /**
166     * Clean autoInit Instances: those that point to contentless iframes
167     * @method _cleanAutoInitInstances
168     * @inner
169     */
170    var _cleanAutoInitInstances = function() {
171        var length = lib.autoInitInstances.length;
172
173        // Loop backwards to avoid index issues
174        for (var idx = length - 1; idx >= 0; idx--) {
175            var instance = lib.autoInitInstances[idx];
176            // If instance has been removed or is contentless then remove it
177            if (instance.el.getElementsByTagName('iframe').length &&
178                instance.el.getElementsByTagName('iframe')[0].contentWindow) {
179                continue;
180            }
181            else {
182                // Remove the reference to the removed or orphan instance
183                lib.autoInitInstances.splice(idx,1);
184            }
185        }
186    };
187
188    /**
189     * Store auto initialized Pym instances for further reference
190     * @name module:pym#autoInitInstances
191     * @type Array
192     * @default []
193     */
194    lib.autoInitInstances = [];
195
196    /**
197     * Initialize Pym for elements on page that have data-pym attributes.
198     * Expose autoinit in case we need to call it from the outside
199     * @instance
200     * @method autoInit
201     * @param {Boolean} doNotRaiseEvents flag to avoid sending custom events
202     */
203    lib.autoInit = function(doNotRaiseEvents) {
204        var elements = document.querySelectorAll('[data-pym-src]:not([data-pym-auto-initialized])');
205        var length = elements.length;
206
207        // Clean stored instances in case needed
208        _cleanAutoInitInstances();
209        for (var idx = 0; idx < length; ++idx) {
210            var element = elements[idx];
211            /*
212            * Mark automatically-initialized elements so they are not
213            * re-initialized if the user includes pym.js more than once in the
214            * same document.
215            */
216            element.setAttribute('data-pym-auto-initialized', '');
217
218            // Ensure elements have an id
219            if (element.id === '') {
220                element.id = 'pym-' + idx + "-" + Math.random().toString(36).substr(2,5);
221            }
222
223            var src = element.getAttribute('data-pym-src');
224
225            // List of data attributes to configure the component
226            // structure: {'attribute name': 'type'}
227            var settings = {'xdomain': 'string', 'title': 'string', 'name': 'string', 'id': 'string',
228                            'sandbox': 'string', 'allowfullscreen': 'boolean',
229                            'parenturlparam': 'string', 'parenturlvalue': 'string',
230                            'optionalparams': 'boolean', 'trackscroll': 'boolean',
231                            'scrollwait': 'number', 'lazyload': 'boolean'};
232
233            var config = {};
234
235            for (var attribute in settings) {
236                // via https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute#Notes
237               if (element.getAttribute('data-pym-'+attribute) !== null) {
238                  switch (settings[attribute]) {
239                    case 'boolean':
240                       config[attribute] = !(element.getAttribute('data-pym-'+attribute) === 'false'); // jshint ignore:line
241                       break;
242                    case 'string':
243                       config[attribute] = element.getAttribute('data-pym-'+attribute);
244                       break;
245                    case 'number':
246                        var n = Number(element.getAttribute('data-pym-'+attribute));
247                        if (!isNaN(n)) {
248                            config[attribute] = n;
249                        }
250                        break;
251                    default:
252                       console.err('unrecognized attribute type');
253                  }
254               }
255            }
256
257            // Store references to autoinitialized pym instances
258            var parent = new lib.Parent(element.id, src, config);
259            lib.autoInitInstances.push(parent);
260        }
261
262        // Fire customEvent
263        if (!doNotRaiseEvents) {
264            _raiseCustomEvent("pym-initialized");
265        }
266        // Return stored autoinitalized pym instances
267        return lib.autoInitInstances;
268    };
269
270    /**
271     * The Parent half of a response iframe.
272     *
273     * @memberof module:pym
274     * @class Parent
275     * @param {String} id The id of the div into which the iframe will be rendered. sets {@link module:pym.Parent~id}
276     * @param {String} url The url of the iframe source. sets {@link module:pym.Parent~url}
277     * @param {Object} [config] Configuration for the parent instance. sets {@link module:pym.Parent~settings}
278     * @param {string} [config.xdomain='*'] - xdomain to validate messages received
279     * @param {string} [config.title] - if passed it will be assigned to the iframe title attribute
280     * @param {string} [config.name] - if passed it will be assigned to the iframe name attribute
281     * @param {string} [config.id] - if passed it will be assigned to the iframe id attribute
282     * @param {boolean} [config.allowfullscreen] - if passed and different than false it will be assigned to the iframe allowfullscreen attribute
283     * @param {string} [config.sandbox] - if passed it will be assigned to the iframe sandbox attribute (we do not validate the syntax so be careful!!)
284     * @param {string} [config.parenturlparam] - if passed it will be override the default parentUrl query string parameter name passed to the iframe src
285     * @param {string} [config.parenturlvalue] - if passed it will be override the default parentUrl query string parameter value passed to the iframe src
286     * @param {string} [config.optionalparams] - if passed and different than false it will strip the querystring params parentUrl and parentTitle passed to the iframe src
287     * @param {boolean} [config.trackscroll] - if passed it will activate scroll tracking on the parent
288     * @param {number} [config.scrollwait] - if passed it will set the throttle wait in order to fire scroll messaging. Defaults to 100 ms.
289     * @param {boolean} [config.lazyload] - if passed and different than false construct a iframe that can be lazy loaded along with {@link http://dinbror.dk/blog/blazy/?ref=github#iframe blazy-iframe}
290     * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe iFrame}
291     */
292    lib.Parent = function(id, url, config) {
293        /**
294         * The id of the container element
295         *
296         * @memberof module:pym.Parent
297         * @member {string} id
298         * @inner
299         */
300        this.id = id;
301        /**
302         * The url that will be set as the iframe's src
303         *
304         * @memberof module:pym.Parent
305         * @member {String} url
306         * @inner
307         */
308        this.url = url;
309
310        /**
311         * The container DOM object
312         *
313         * @memberof module:pym.Parent
314         * @member {HTMLElement} el
315         * @inner
316         */
317        this.el = document.getElementById(id);
318        /**
319         * The contained child iframe
320         *
321         * @memberof module:pym.Parent
322         * @member {HTMLElement} iframe
323         * @inner
324         * @default null
325         */
326        this.iframe = null;
327        /**
328         * The parent instance settings, updated by the values passed in the config object
329         *
330         * @memberof module:pym.Parent
331         * @member {Object} settings
332         * @inner
333         */
334        this.settings = {
335            xdomain: '*',
336            optionalparams: true,
337            parenturlparam: 'parentUrl',
338            parenturlvalue: window.location.href,
339            trackscroll: false,
340            scrollwait: 100,
341        };
342        /**
343         * RegularExpression to validate the received messages
344         *
345         * @memberof module:pym.Parent
346         * @member {String} messageRegex
347         * @inner
348         */
349        this.messageRegex = _makeMessageRegex(this.id);
350        /**
351         * Stores the registered messageHandlers for each messageType
352         *
353         * @memberof module:pym.Parent
354         * @member {Object} messageHandlers
355         * @inner
356         */
357        this.messageHandlers = {};
358
359        // ensure a config object
360        config = (config || {});
361
362        /**
363         * Construct the iframe.
364         *
365         * @memberof module:pym.Parent
366         * @method _constructIframe
367         * @inner
368         */
369        this._constructIframe = function() {
370            // Calculate the width of this element.
371            var width = this.el.offsetWidth.toString();
372
373            // Create an iframe element attached to the document.
374            this.iframe = document.createElement('iframe');
375
376            // Save fragment id
377            var hash = '';
378            var hashIndex = this.url.indexOf('#');
379
380            if (hashIndex > -1) {
381                hash = this.url.substring(hashIndex, this.url.length);
382                this.url = this.url.substring(0, hashIndex);
383            }
384
385            // If the URL contains querystring bits, use them.
386            // Otherwise, just create a set of valid params.
387            if (this.url.indexOf('?') < 0) {
388                this.url += '?';
389            } else {
390                this.url += '&';
391            }
392
393            // Append the initial width as a querystring parameter
394            // and optional params if configured to do so
395            this.iframe.src = this.url + 'initialWidth=' + width +
396                                         '&childId=' + this.id;
397
398            if (this.settings.optionalparams) {
399                this.iframe.src += '&parentTitle=' + encodeURIComponent(document.title);
400                this.iframe.src += '&'+ this.settings.parenturlparam + '=' + encodeURIComponent(this.settings.parenturlvalue);
401            }
402            this.iframe.src +=hash;
403
404            if(this.settings.lazyload) {
405              this.iframe.setAttribute('data-src', this.iframe.src);
406              this.iframe.setAttribute('class', 'b-lazy');
407              this.iframe.src = 'about:blank';
408            }
409
410            // Set some attributes to this proto-iframe.
411            this.iframe.setAttribute('width', '100%');
412            this.iframe.setAttribute('scrolling', 'no');
413            this.iframe.setAttribute('marginheight', '0');
414            this.iframe.setAttribute('frameborder', '0');
415
416            if (this.settings.title) {
417                this.iframe.setAttribute('title', this.settings.title);
418            }
419
420            if (this.settings.allowfullscreen !== undefined && this.settings.allowfullscreen !== false) {
421                this.iframe.setAttribute('allowfullscreen','');
422            }
423
424            if (this.settings.sandbox !== undefined && typeof this.settings.sandbox === 'string') {
425                this.iframe.setAttribute('sandbox', this.settings.sandbox);
426            }
427
428            if (this.settings.id) {
429                if (!document.getElementById(this.settings.id)) {
430                    this.iframe.setAttribute('id', this.settings.id);
431                }
432            }
433
434            if (this.settings.name) {
435                this.iframe.setAttribute('name', this.settings.name);
436            }
437
438            // Replace the child content if needed
439            // (some CMSs might strip out empty elements)
440            while(this.el.firstChild) { this.el.removeChild(this.el.firstChild); }
441            // Append the iframe to our element.
442            this.el.appendChild(this.iframe);
443
444            // Add an event listener that will handle redrawing the child on resize.
445            window.addEventListener('resize', this._onResize);
446
447            // Add an event listener that will send the child the viewport.
448            if (this.settings.trackscroll) {
449                window.addEventListener('scroll', this._throttleOnScroll);
450            }
451        };
452
453        /**
454         * Send width on resize.
455         *
456         * @memberof module:pym.Parent
457         * @method _onResize
458         * @inner
459         */
460        this._onResize = function() {
461            this.sendWidth();
462            if (this.settings.trackscroll) {
463                this.sendViewportAndIFramePosition();
464            }
465        }.bind(this);
466
467        /**
468         * Send viewport and iframe info on scroll.
469         *
470         * @memberof module:pym.Parent
471         * @method _onScroll
472         * @inner
473         */
474        this._onScroll = function() {
475            this.sendViewportAndIFramePosition();
476        }.bind(this);
477
478        /**
479         * Fire all event handlers for a given message type.
480         *
481         * @memberof module:pym.Parent
482         * @method _fire
483         * @inner
484         *
485         * @param {String} messageType The type of message.
486         * @param {String} message The message data.
487         */
488        this._fire = function(messageType, message) {
489            if (messageType in this.messageHandlers) {
490                for (var i = 0; i < this.messageHandlers[messageType].length; i++) {
491                   this.messageHandlers[messageType][i].call(this, message);
492                }
493            }
494        };
495
496        /**
497         * Remove this parent from the page and unbind it's event handlers.
498         *
499         * @memberof module:pym.Parent
500         * @method remove
501         * @instance
502         */
503        this.remove = function() {
504            window.removeEventListener('message', this._processMessage);
505            window.removeEventListener('resize', this._onResize);
506
507            this.el.removeChild(this.iframe);
508            // _cleanAutoInitInstances in case this parent was autoInitialized
509            _cleanAutoInitInstances();
510        };
511
512        /**
513         * Process a new message from the child.
514         *
515         * @memberof module:pym.Parent
516         * @method _processMessage
517         * @inner
518         *
519         * @param {Event} e A message event.
520         */
521        this._processMessage = function(e) {
522            // First, punt if this isn't from an acceptable xdomain.
523            if (!_isSafeMessage(e, this.settings)) {
524                return;
525            }
526
527            // Discard object messages, we only care about strings
528            if (typeof e.data !== 'string') {
529                return;
530            }
531
532            // Grab the message from the child and parse it.
533            var match = e.data.match(this.messageRegex);
534
535            // If there's no match or too many matches in the message, punt.
536            if (!match || match.length !== 3) {
537                return false;
538            }
539
540            var messageType = match[1];
541            var message = match[2];
542
543            this._fire(messageType, message);
544        }.bind(this);
545
546        /**
547         * Resize iframe in response to new height message from child.
548         *
549         * @memberof module:pym.Parent
550         * @method _onHeightMessage
551         * @inner
552         *
553         * @param {String} message The new height.
554         */
555        this._onHeightMessage = function(message) {
556            /*
557             * Handle parent height message from child.
558             */
559            var height = parseInt(message);
560
561            this.iframe.setAttribute('height', height + 'px');
562        };
563
564        /**
565         * Navigate parent to a new url.
566         *
567         * @memberof module:pym.Parent
568         * @method _onNavigateToMessage
569         * @inner
570         *
571         * @param {String} message The url to navigate to.
572         */
573        this._onNavigateToMessage = function(message) {
574            /*
575             * Handle parent scroll message from child.
576             */
577            document.location.href = message;
578        };
579
580        /**
581         * Scroll parent to a given child position.
582         *
583         * @memberof module:pym.Parent
584         * @method _onScrollToChildPosMessage
585         * @inner
586         *
587         * @param {String} message The offset inside the child page.
588         */
589        this._onScrollToChildPosMessage = function(message) {
590            // Get the child container position using getBoundingClientRect + pageYOffset
591            // via https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
592            var iframePos = document.getElementById(this.id).getBoundingClientRect().top + window.pageYOffset;
593
594            var totalOffset = iframePos + parseInt(message);
595            window.scrollTo(0, totalOffset);
596        };
597
598        /**
599         * Bind a callback to a given messageType from the child.
600         *
601         * Reserved message names are: "height", "scrollTo" and "navigateTo".
602         *
603         * @memberof module:pym.Parent
604         * @method onMessage
605         * @instance
606         *
607         * @param {String} messageType The type of message being listened for.
608         * @param {module:pym.Parent~onMessageCallback} callback The callback to invoke when a message of the given type is received.
609         */
610        this.onMessage = function(messageType, callback) {
611            if (!(messageType in this.messageHandlers)) {
612                this.messageHandlers[messageType] = [];
613            }
614
615            this.messageHandlers[messageType].push(callback);
616        };
617
618        /**
619         * @callback module:pym.Parent~onMessageCallback
620         * @param {String} message The message data.
621         */
622
623        /**
624         * Send a message to the the child.
625         *
626         * @memberof module:pym.Parent
627         * @method sendMessage
628         * @instance
629         *
630         * @param {String} messageType The type of message to send.
631         * @param {String} message The message data to send.
632         */
633        this.sendMessage = function(messageType, message) {
634            // When used alongside with pjax some references are lost
635            if (this.el.getElementsByTagName('iframe').length) {
636                if (this.el.getElementsByTagName('iframe')[0].contentWindow) {
637                    this.el.getElementsByTagName('iframe')[0].contentWindow
638                        .postMessage(_makeMessage(this.id, messageType, message), '*');
639                }
640                else {
641                    // Contentless child detected remove listeners and iframe
642                    this.remove();
643                }
644            }
645        };
646
647        /**
648         * Transmit the current iframe width to the child.
649         *
650         * You shouldn't need to call this directly.
651         *
652         * @memberof module:pym.Parent
653         * @method sendWidth
654         * @instance
655         */
656        this.sendWidth = function() {
657            var width = this.el.offsetWidth.toString();
658            this.sendMessage('width', width);
659        };
660
661        /**
662         * Transmit the current viewport and iframe position to the child.
663         * Sends viewport width, viewport height
664         * and iframe bounding rect top-left-bottom-right
665         * all separated by spaces
666         *
667         * You shouldn't need to call this directly.
668         *
669         * @memberof module:pym.Parent
670         * @method sendViewportAndIFramePosition
671         * @instance
672         */
673        this.sendViewportAndIFramePosition = function() {
674            var iframeRect = this.iframe.getBoundingClientRect();
675            var vWidth   = window.innerWidth || document.documentElement.clientWidth;
676            var vHeight  = window.innerHeight || document.documentElement.clientHeight;
677            var payload = vWidth + ' ' + vHeight;
678            payload += ' ' + iframeRect.top + ' ' + iframeRect.left;
679            payload += ' ' + iframeRect.bottom + ' ' + iframeRect.right;
680            this.sendMessage('viewport-iframe-position', payload);
681        };
682
683        // Add any overrides to settings coming from config.
684        for (var key in config) {
685            this.settings[key] = config[key];
686        }
687
688        /**
689         * Throttled scroll function.
690         *
691         * @memberof module:pym.Parent
692         * @method _throttleOnScroll
693         * @inner
694         */
695        this._throttleOnScroll = _throttle(this._onScroll.bind(this), this.settings.scrollwait);
696
697        // Bind required message handlers
698        this.onMessage('height', this._onHeightMessage);
699        this.onMessage('navigateTo', this._onNavigateToMessage);
700        this.onMessage('scrollToChildPos', this._onScrollToChildPosMessage);
701        this.onMessage('parentPositionInfo', this.sendViewportAndIFramePosition);
702
703        // Add a listener for processing messages from the child.
704        window.addEventListener('message', this._processMessage, false);
705
706        // Construct the iframe in the container element.
707        this._constructIframe();
708
709        return this;
710    };
711
712    /**
713     * The Child half of a responsive iframe.
714     *
715     * @memberof module:pym
716     * @class Child
717     * @param {Object} [config] Configuration for the child instance. sets {@link module:pym.Child~settings}
718     * @param {function} [config.renderCallback=null] Callback invoked after receiving a resize event from the parent, sets {@link module:pym.Child#settings.renderCallback}
719     * @param {string} [config.xdomain='*'] - xdomain to validate messages received
720     * @param {number} [config.polling=0] - polling frequency in milliseconds to send height to parent
721     * @param {number} [config.id] - parent container id used when navigating the child iframe to a new page but we want to keep it responsive.
722     * @param {string} [config.parenturlparam] - if passed it will be override the default parentUrl query string parameter name expected on the iframe src
723     */
724    lib.Child = function(config) {
725        /**
726         * The initial width of the parent page
727         *
728         * @memberof module:pym.Child
729         * @member {string} parentWidth
730         * @inner
731         */
732        this.parentWidth = null;
733        /**
734         * The id of the parent container
735         *
736         * @memberof module:pym.Child
737         * @member {String} id
738         * @inner
739         */
740        this.id = null;
741        /**
742         * The title of the parent page from document.title.
743         *
744         * @memberof module:pym.Child
745         * @member {String} parentTitle
746         * @inner
747         */
748        this.parentTitle = null;
749        /**
750         * The URL of the parent page from window.location.href.
751         *
752         * @memberof module:pym.Child
753         * @member {String} parentUrl
754         * @inner
755         */
756        this.parentUrl = null;
757        /**
758         * The settings for the child instance. Can be overriden by passing a config object to the child constructor
759         * i.e.: var pymChild = new pym.Child({renderCallback: render, xdomain: "\\*\.npr\.org"})
760         *
761         * @memberof module:pym.Child.settings
762         * @member {Object} settings - default settings for the child instance
763         * @inner
764         */
765        this.settings = {
766            renderCallback: null,
767            xdomain: '*',
768            polling: 0,
769            parenturlparam: 'parentUrl'
770        };
771
772        /**
773         * The timerId in order to be able to stop when polling is enabled
774         *
775         * @memberof module:pym.Child
776         * @member {String} timerId
777         * @inner
778         */
779        this.timerId = null;
780        /**
781         * RegularExpression to validate the received messages
782         *
783         * @memberof module:pym.Child
784         * @member {String} messageRegex
785         * @inner
786         */
787        this.messageRegex = null;
788        /**
789         * Stores the registered messageHandlers for each messageType
790         *
791         * @memberof module:pym.Child
792         * @member {Object} messageHandlers
793         * @inner
794         */
795        this.messageHandlers = {};
796
797        // Ensure a config object
798        config = (config || {});
799
800        /**
801         * Bind a callback to a given messageType from the child.
802         *
803         * Reserved message names are: "width".
804         *
805         * @memberof module:pym.Child
806         * @method onMessage
807         * @instance
808         *
809         * @param {String} messageType The type of message being listened for.
810         * @param {module:pym.Child~onMessageCallback} callback The callback to invoke when a message of the given type is received.
811         */
812        this.onMessage = function(messageType, callback) {
813
814            if (!(messageType in this.messageHandlers)) {
815                this.messageHandlers[messageType] = [];
816            }
817
818            this.messageHandlers[messageType].push(callback);
819        };
820
821        /**
822         * @callback module:pym.Child~onMessageCallback
823         * @param {String} message The message data.
824         */
825
826
827        /**
828         * Fire all event handlers for a given message type.
829         *
830         * @memberof module:pym.Child
831         * @method _fire
832         * @inner
833         *
834         * @param {String} messageType The type of message.
835         * @param {String} message The message data.
836         */
837        this._fire = function(messageType, message) {
838            /*
839             * Fire all event handlers for a given message type.
840             */
841            if (messageType in this.messageHandlers) {
842                for (var i = 0; i < this.messageHandlers[messageType].length; i++) {
843                   this.messageHandlers[messageType][i].call(this, message);
844                }
845            }
846        };
847
848        /**
849         * Process a new message from the parent.
850         *
851         * @memberof module:pym.Child
852         * @method _processMessage
853         * @inner
854         *
855         * @param {Event} e A message event.
856         */
857        this._processMessage = function(e) {
858            /*
859            * Process a new message from parent frame.
860            */
861            // First, punt if this isn't from an acceptable xdomain.
862            if (!_isSafeMessage(e, this.settings)) {
863                return;
864            }
865
866            // Discard object messages, we only care about strings
867            if (typeof e.data !== 'string') {
868                return;
869            }
870
871            // Get the message from the parent.
872            var match = e.data.match(this.messageRegex);
873
874            // If there's no match or it's a bad format, punt.
875            if (!match || match.length !== 3) { return; }
876
877            var messageType = match[1];
878            var message = match[2];
879
880            this._fire(messageType, message);
881        }.bind(this);
882
883        /**
884         * Resize iframe in response to new width message from parent.
885         *
886         * @memberof module:pym.Child
887         * @method _onWidthMessage
888         * @inner
889         *
890         * @param {String} message The new width.
891         */
892        this._onWidthMessage = function(message) {
893            /*
894             * Handle width message from the child.
895             */
896            var width = parseInt(message);
897
898            // Change the width if it's different.
899            if (width !== this.parentWidth) {
900                this.parentWidth = width;
901
902                // Call the callback function if it exists.
903                if (this.settings.renderCallback) {
904                    this.settings.renderCallback(width);
905                }
906
907                // Send the height back to the parent.
908                this.sendHeight();
909            }
910        };
911
912        /**
913         * Send a message to the the Parent.
914         *
915         * @memberof module:pym.Child
916         * @method sendMessage
917         * @instance
918         *
919         * @param {String} messageType The type of message to send.
920         * @param {String} message The message data to send.
921         */
922        this.sendMessage = function(messageType, message) {
923            /*
924             * Send a message to the parent.
925             */
926            window.parent.postMessage(_makeMessage(this.id, messageType, message), '*');
927        };
928
929        /**
930         * Transmit the current iframe height to the parent.
931         *
932         * Call this directly in cases where you manually alter the height of the iframe contents.
933         *
934         * @memberof module:pym.Child
935         * @method sendHeight
936         * @instance
937         */
938        this.sendHeight = function() {
939            // Get the child's height.
940            var height = document.getElementsByTagName('body')[0].offsetHeight.toString();
941
942            // Send the height to the parent.
943            this.sendMessage('height', height);
944
945            return height;
946        }.bind(this);
947
948        /**
949         * Ask parent to send the current viewport and iframe position information
950         *
951         * @memberof module:pym.Child
952         * @method sendHeight
953         * @instance
954         */
955        this.getParentPositionInfo = function() {
956            // Send the height to the parent.
957            this.sendMessage('parentPositionInfo');
958        };
959
960        /**
961         * Scroll parent to a given element id.
962         *
963         * @memberof module:pym.Child
964         * @method scrollParentTo
965         * @instance
966         *
967         * @param {String} hash The id of the element to scroll to.
968         */
969        this.scrollParentTo = function(hash) {
970            this.sendMessage('navigateTo', '#' + hash);
971        };
972
973        /**
974         * Navigate parent to a given url.
975         *
976         * @memberof module:pym.Child
977         * @method navigateParentTo
978         * @instance
979         *
980         * @param {String} url The url to navigate to.
981         */
982        this.navigateParentTo = function(url) {
983            this.sendMessage('navigateTo', url);
984        };
985
986        /**
987         * Scroll parent to a given child element id.
988         *
989         * @memberof module:pym.Child
990         * @method scrollParentToChildEl
991         * @instance
992         *
993         * @param {String} id The id of the child element to scroll to.
994         */
995        this.scrollParentToChildEl = function(id) {
996            // Get the child element position using getBoundingClientRect + pageYOffset
997            // via https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
998            var topPos = document.getElementById(id).getBoundingClientRect().top + window.pageYOffset;
999            this.scrollParentToChildPos(topPos);
1000        };
1001
1002        /**
1003         * Scroll parent to a particular child offset.
1004         *
1005         * @memberof module:pym.Child
1006         * @method scrollParentToChildPos
1007         * @instance
1008         *
1009         * @param {Number} pos The offset of the child element to scroll to.
1010         */
1011        this.scrollParentToChildPos = function(pos) {
1012            this.sendMessage('scrollToChildPos', pos.toString());
1013        };
1014
1015        /**
1016         * Mark Whether the child is embedded or not
1017         * executes a callback in case it was passed to the config
1018         *
1019         * @memberof module:pym.Child
1020         * @method _markWhetherEmbedded
1021         * @inner
1022         *
1023         * @param {module:pym.Child~onMarkedEmbeddedStatus} The callback to execute after determining whether embedded or not.
1024         */
1025        var _markWhetherEmbedded = function(onMarkedEmbeddedStatus) {
1026          var htmlElement = document.getElementsByTagName('html')[0],
1027              newClassForHtml,
1028              originalHtmlClasses = htmlElement.className;
1029          try {
1030            if(window.self !== window.top) {
1031              newClassForHtml = "embedded";
1032            }else{
1033              newClassForHtml = "not-embedded";
1034            }
1035          }catch(e) {
1036            newClassForHtml = "embedded";
1037          }
1038          if(originalHtmlClasses.indexOf(newClassForHtml) < 0) {
1039            htmlElement.className = originalHtmlClasses ? originalHtmlClasses + ' ' + newClassForHtml : newClassForHtml;
1040            if(onMarkedEmbeddedStatus){
1041              onMarkedEmbeddedStatus(newClassForHtml);
1042            }
1043            _raiseCustomEvent("marked-embedded");
1044          }
1045        };
1046
1047        /**
1048         * @callback module:pym.Child~onMarkedEmbeddedStatus
1049         * @param {String} classname "embedded" or "not-embedded".
1050         */
1051
1052        /**
1053         * Unbind child event handlers and timers.
1054         *
1055         * @memberof module:pym.Child
1056         * @method remove
1057         * @instance
1058         */
1059        this.remove = function() {
1060            window.removeEventListener('message', this._processMessage);
1061            if (this.timerId) {
1062                clearInterval(this.timerId);
1063            }
1064        };
1065
1066        // Initialize settings with overrides.
1067        for (var key in config) {
1068            this.settings[key] = config[key];
1069        }
1070
1071        // Identify what ID the parent knows this child as.
1072        this.id = _getParameterByName('childId') || config.id;
1073        this.messageRegex = new RegExp('^pym' + MESSAGE_DELIMITER + this.id + MESSAGE_DELIMITER + '(\\S+)' + MESSAGE_DELIMITER + '(.*)$');
1074
1075        // Get the initial width from a URL parameter.
1076        var width = parseInt(_getParameterByName('initialWidth'));
1077
1078        // Get the url of the parent frame
1079        this.parentUrl = _getParameterByName(this.settings.parenturlparam);
1080
1081        // Get the title of the parent frame
1082        this.parentTitle = _getParameterByName('parentTitle');
1083
1084        // Bind the required message handlers
1085        this.onMessage('width', this._onWidthMessage);
1086
1087        // Set up a listener to handle any incoming messages.
1088        window.addEventListener('message', this._processMessage, false);
1089
1090        // If there's a callback function, call it.
1091        if (this.settings.renderCallback) {
1092            this.settings.renderCallback(width);
1093        }
1094
1095        // Send the initial height to the parent.
1096        this.sendHeight();
1097
1098        // If we're configured to poll, create a setInterval to handle that.
1099        if (this.settings.polling) {
1100            this.timerId = window.setInterval(this.sendHeight, this.settings.polling);
1101        }
1102
1103        _markWhetherEmbedded(config.onMarkedEmbeddedStatus);
1104
1105        return this;
1106    };
1107
1108    // Initialize elements with pym data attributes
1109    // if we are not in server configuration
1110    if(typeof document !== "undefined") {
1111        lib.autoInit(true);
1112    }
1113
1114    return lib;
1115});
1116

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.