<commit_before>/**
 * UX Rocket Modal
 * jQuery based modal
 * @author Bilal Cinarli
 */

(function(factory) {
    'use strict';
    if(typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
    } else if(typeof exports === 'object' && typeof require === 'function') {
        // Browserify
        factory(jQuery);
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function($) {
    var ux,
        rocketName = 'uxrModal',
        instance = 1,

        overlay = '

',
        content = '
' +
                  '

' +
                  '

' +
                  '    ' +
                  '
',
        openedInstances = {},

        defaults = {
            className    : '',
            href         : '',
            width        : '',
            height       : '',
            maxWidth     : '',
            maxHeight    : '',
            appendTo     : 'body',
            allowMultiple: false,
            blockUI      : true,
            overlayClose : true,
            close        : true,
            iframe       : false,
            html         : false,
            fixed        : false,

            onReady : false,
            onOpen  : false,
            onStart : false,
            onLoad  : false,
            onClose : false,
            onRemove: false
        },

        events = {
            click     : 'click.' + rocketName,
            touchstart: 'touchstart.' + rocketName,
            toucemove : 'touchmove.' + rocketName,
            touchend  : 'touchend.' + rocketName,
            mousedown : 'mousedown.' + rocketName,
            mousemove : 'mousemove.' + rocketName,
            mouseup   : 'mouseup.' + rocketName,
            open      : 'uxropen.' + rocketName,
            start     : 'uxrstart.' + rocketName,
            load      : 'uxrload.' + rocketName,
            close     : 'uxrclose.' + rocketName,
            ready     : 'uxrready.' + rocketName,
            resize    : 'uxrresize.' + rocketName,
            remove    : 'uxrremove.' + rocketName
        },

        ns = {
            prefix : 'uxr-',
            rocket : 'uxRocket',
            data   : rocketName,
            name   : 'modal',
            classes: {
                ready    : 'ready',
                overlay  : 'overlay',
                container: 'container',
                drag     : 'drag',
                content  : 'content',
                loading  : 'loading',
                close    : 'close',
                hide     : 'hide'
            }
        };

    var Modal = function(el, options, selector) {
        this._name = rocketName;
        this._defaults = defaults;
        this._instance = instance;

        this.el = el;
        this.$el = $(el);
        this.$content = null;
        this.options = $.extend(true, {}, defaults, options, this.$el.data());
        this.href = this.options.href || this.$el.attr('href') || false;
        this.selector = selector;

        instance++;

        this.init();
    };

    Modal.prototype.init = function() {
        var uxrocket = this.$el.data(ns.rocket) || {};

        // register plugin data to rocket
        uxrocket[ns.data] = {hasWrapper: false, ready: utils.getClassname('ready'), selector: this.selector, options: this.options};
        this.$el.data(ns.rocket, uxrocket);

        this.bindUIActions();

        this.$el.addClass(utils.getClassname('ready'));

        this.emitEvent('ready');
    };

    Modal.prototype.bindUIActions = function() {
        var _this = this;

        this.$el.on(events.click, function(e) {
            e.preventDefault();

            // do not open same modal more then once
            if(typeof openedInstances[_this._instance] !== 'undefined') {
                return;
            }

            _this.emitEvent('start');
            _this.prepare();
        })
            .on(events.ready, function() {
                _this.onReady();
            })
            .on(events.open, function() {
                _this.onOpen();
            })
            .on(events.start, function() {
                _this.onStart();
            })
            .on(events.load, function() {
                _this.onLoad();
            })
            .on(events.close, function() {
                _this.onClose();
            })
            .on(events.remove, function() {
                _this.onRemove();
            });

        $('body')
            .on(events.click + ' ' + events.touchstart, '#uxr-modal-overlay', function() {
                modal.closeFromOverlay();
            })
            .on(events.click + ' ' + events.touchstart, '#uxr-modal-instance-' + _this._instance + ' .' + utils.getClassname('close'), function(e) {
                e.preventDefault();
                _this.close();
            })
            .on(events.mousedown + ' ' + events.touchstart, '#uxr-modal-instance-' + _this._instance + ' .' + utils.getClassname('drag'), function(e) {
                e.preventDefault();
                modal.move.start(_this, e);
            });

        $(document)
            .on(events.mousemove + ' ' + events.touchmove, function(e) {
                modal.move.drag(e);
            }).on(events.mouseup + ' ' + events.touchend, function() {
                modal.move.stop();
            });
    };

    Modal.prototype.prepare = function() {
        var css = {},
            $overlay = $('#uxr-modal-overlay'),
            _content = content.replace('{{id}}', 'uxr-modal-instance-' + this._instance);

        if(!this.options.allowMultiple && modal.isOnlyInstance()) {
            modal.closeFromOverlay();
        }

        if(this.options.blockUI) {
            if($overlay.length === 0) {
                $(utils.escapeSelector(this.options.appendTo)).append(overlay);
            }
            else {
                $overlay.removeClass(utils.getClassname('hide'));
            }
        }

        if(this.$content === null) {
            this.$content = $(_content);

            if(this.options.fixed) {
                css.position = 'fixed';
            }

            if(this.options.maxWidth !== '') {
                css.maxWidth = this.options.maxWidth;
            }

            if(this.options.maxHeight !== '') {
                css.maxHeight = this.options.maxHeight;
            }

            if(Object.keys(css).length > 0) {
                this.$content.css(css);
            }

            $(utils.escapeSelector(this.options.appendTo)).append(this.$content);
        }

        else {
            this.$content.removeClass(utils.getClassname('hide'));
        }

        this.$content.addClass(utils.getClassname('loading'));

        modal.registerInstance(this);

        this.open();
    };

    Modal.prototype.open = function() {
        this.emitEvent('open');
        this.get();
    };

    Modal.prototype.get = function() {
        var _this = this,
            iframe;

        if(this.options.iframe) {
            this.options.width = this.options.width || 800;
            this.options.height = this.options.height || 600;

            iframe = '';
            this.html = iframe;
            _this.emitEvent('load');
        }

        else if(!this.isInpage()) {
            $.get(this.href).done(function(data) {
                _this.html = data;
                setTimeout(function() {
                    _this.emitEvent('load');
                });
            });
        }
        else if(this.href !== false) {
            this.html = $(utils.escapeSelector(this.href));
            _this.emitEvent('load');
        }
        else {
            this.html = this.options.html;
            _this.emitEvent('load');
        }
    };

    Modal.prototype.load = function() {
        var loadedContent = this.$content.find('.' + utils.getClassname('content'));

        this.$content.removeClass(utils.getClassname('loading'));

        if(!this.inpage) {
            loadedContent.html(this.html);
        }

        else {
            loadedContent.append(this.html);
        }

        if(this.options.close) {
            this.$content
                .find('.' + utils.getClassname('close'))
                .removeClass(utils.getClassname('hide'));
        }

        this.resize();
    };

    Modal.prototype.close = function() {
        if(modal.isOnlyInstance()) {
            $('#uxr-modal-overlay').addClass(utils.getClassname('hide'));
        }

        this.$content
            .addClass(utils.getClassname('hide'))
            .find('.' + utils.getClassname('close'))
            .addClass(utils.getClassname('hide'));

        modal.close(this);

        this.emitEvent('close');
    };

    Modal.prototype.resize = function() {
        var css = {},
            loadedContent = this.$content.find('.' + utils.getClassname('content'));

        if(this.options.width !== '') {
            css.width = this.options.width;
        }

        if(this.options.height !== '') {
            css.height = this.options.height;
        }

        loadedContent.css(css);

        if(this.$content.height() < loadedContent.height()) {
            this.$content.css('overflowY', 'scroll');
        }
    };

    Modal.prototype.isInpage = function() {
        if(typeof this.inpage === 'undefined') {
            this.inpage = false;

            if(this.href === false) {
                this.inpage = true;
            }

            else if(this.href.charAt(0) === '#') {
                this.inpage = true;
            }

            else {
                var loc = window.location.href.replace(location.hash, '').replace(location.protocol, ''),
                    test = this.href.replace(location.hash, '').replace(location.protocol, '');

                if(loc === test) {
                    this.inpage = true;
                }
            }
        }

        return this.inpage;
    };

    Modal.prototype.onReady = function() {
        utils.callback(this.options.onReady);
    };

    Modal.prototype.onOpen = function() {
        utils.callback(this.options.onOpen);
    };

    Modal.prototype.onStart = function() {
        utils.callback(this.options.onStart);
    };

    Modal.prototype.onLoad = function() {
        this.load();

        utils.callback(this.options.onLoad);
    };

    Modal.prototype.onClose = function() {
        utils.callback(this.options.onClose);
    };

    Modal.prototype.onRemove = function() {
        utils.callback(this.options.onRemove);
    };

    Modal.prototype.emitEvent = function(which) {
        this.$el.trigger(events[which]);
    };

    Modal.prototype.getOpenedInstances = function() {
        return openedInstances;
    };

    var modal = {
        registerInstance: function(instance) {
            openedInstances[instance._instance] = instance;
            instance.previousInstance = openedInstances.lastInstance || instance._instance;
            openedInstances.lastInstance = instance._instance;
        },
        close           : function(instance) {
            openedInstances.lastInstance = instance.previousInstance;
            delete openedInstances[instance._instance];
        },
        closeFromOverlay: function() {
            var activeInstance = openedInstances[openedInstances.lastInstance];

            if(Object.keys(openedInstances).length === 1) {
                return;
            }

            if(!activeInstance.options.overlayClose) {
                return;
            }

            activeInstance.close();
        },
        isOnlyInstance  : function() {
            return (Object.keys(openedInstances).length - 1) === 1;
        },
        move            : {
            pos     : {},
            selected: false,
            previous: false,
            start   : function(instance, e) {
                var pointer = this.pointer(e);

                this.selected = instance;
                this.selected.$content.css('zIndex', 10001);
                this.pos.top = pointer.y - this.selected.$content[0].offsetTop;
                this.pos.left = pointer.x - this.selected.$content[0].offsetLeft;

                if(!this.previous) {
                    this.previous = instance;
                }
                else {
                    this.previous.$content.css('zIndex', 10000);
                }
            },
            drag    : function(e) {
                var pointer = this.pointer(e);

                if(this.selected) {
                    this.selected.$content[0].style.top = pointer.y + 'px';
                    this.selected.$content[0].style.left = pointer.x + 'px';
                }
            },
            stop    : function() {
                if(this.selected) {
                    this.previous = this.selected;
                    this.selected = false;
                }
            },
            pointer : function(e) {
                var out = {x: 0, y: 0};
                if(e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend' || e.type === 'touchcancel') {
                    var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
                    out.x = touch.pageX;
                    out.y = touch.pageY;
                } else if(e.type === 'mousedown' || e.type === 'mouseup' || e.type === 'mousemove' || e.type === 'mouseover' || e.type === 'mouseout' || e.type === 'mouseenter' || e.type === 'mouseleave') {
                    out.x = e.pageX;
                    out.y = e.pageY;
                }
                return out;
            }
        }
    };

    var utils = {
        callback: function(fn) {
            // if callback string is function call it directly
            if(typeof fn === 'function') {
                fn.apply(this);
            }

            // if callback defined via data-attribute, call it via new Function
            else {
                if(fn !== false) {
                    var func = new Function('return ' + fn);
                    func();
                }
            }
        },

        escapeSelector: function(selector) {
            var is_ID = selector.charAt(0) === '#',
                re = /([ !"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g;

            return is_ID ? '#' + selector.substring(1).replace(re, '\\$1') : selector;
        },

        getStringVariable: function(str) {
            var val;
            // check if it is chained
            if(str.indexOf('.') > -1) {
                var chain = str.split('.'),
                    chainVal = window[chain[0]];

                for(var i = 1; i < chain.length; i++) {
                    chainVal = chainVal[chain[i]];
                }

                val = chainVal;
            }

            else {
                val = window[str];
            }

            return val;
        },

        getClassname: function(which) {
            return ns.prefix + ns.name + '-' + ns.classes[which];
        }
    };

    ux = $.fn.modal = $.fn.uxrmodal = $.uxrmodal = function(options) {
        var selector = this.selector;

        // direct call to dialog
        if(typeof this === 'function') {
            Modal(null, options, null);
            return;
        }

        return this.each(function() {
            if($.data(this, ns.data)) {
                return;
            }

            // Bind the plugin and attach the instance to data
            $.data(this, ns.data, new Modal(this, options, selector));
        });
    };

    ux.resize = function() {
        var activeInstance = openedInstances[openedInstances.lastInstance];

        if(Object.keys(openedInstances).length === 1) {
            return;
        }

        activeInstance.resize();
    };

    ux.close = function() {
        var activeInstance = openedInstances[openedInstances.lastInstance];

        if(Object.keys(openedInstances).length === 1) {
            return;
        }

        activeInstance.close();
    };

    ux.version = '1.1.0';

    ux.settings = defaults;
}));<commit_msg>Drag disabled for single modal<commit_after>/**
 * UX Rocket Modal
 * jQuery based modal
 * @author Bilal Cinarli
 */

(function(factory) {
    'use strict';
    if(typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
    } else if(typeof exports === 'object' && typeof require === 'function') {
        // Browserify
        factory(jQuery);
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function($) {
    var ux,
        rocketName = 'uxrModal',
        instance = 1,

        overlay = '

',
        content = '
' +
                  '

' +
                  '

' +
                  '    ' +
                  '
',
        openedInstances = {},

        defaults = {
            className    : '',
            href         : '',
            width        : '',
            height       : '',
            maxWidth     : '',
            maxHeight    : '',
            appendTo     : 'body',
            allowMultiple: false,
            blockUI      : true,
            overlayClose : true,
            close        : true,
            iframe       : false,
            html         : false,
            fixed        : false,

            onReady : false,
            onOpen  : false,
            onStart : false,
            onLoad  : false,
            onClose : false,
            onRemove: false
        },

        events = {
            click     : 'click.' + rocketName,
            touchstart: 'touchstart.' + rocketName,
            toucemove : 'touchmove.' + rocketName,
            touchend  : 'touchend.' + rocketName,
            mousedown : 'mousedown.' + rocketName,
            mousemove : 'mousemove.' + rocketName,
            mouseup   : 'mouseup.' + rocketName,
            open      : 'uxropen.' + rocketName,
            start     : 'uxrstart.' + rocketName,
            load      : 'uxrload.' + rocketName,
            close     : 'uxrclose.' + rocketName,
            ready     : 'uxrready.' + rocketName,
            resize    : 'uxrresize.' + rocketName,
            remove    : 'uxrremove.' + rocketName
        },

        ns = {
            prefix : 'uxr-',
            rocket : 'uxRocket',
            data   : rocketName,
            name   : 'modal',
            classes: {
                ready    : 'ready',
                overlay  : 'overlay',
                container: 'container',
                drag     : 'drag',
                content  : 'content',
                loading  : 'loading',
                close    : 'close',
                hide     : 'hide'
            }
        };

    var Modal = function(el, options, selector) {
        this._name = rocketName;
        this._defaults = defaults;
        this._instance = instance;

        this.el = el;
        this.$el = $(el);
        this.$content = null;
        this.options = $.extend(true, {}, defaults, options, this.$el.data());
        this.href = this.options.href || this.$el.attr('href') || false;
        this.selector = selector;

        instance++;

        this.init();
    };

    Modal.prototype.init = function() {
        var uxrocket = this.$el.data(ns.rocket) || {};

        // register plugin data to rocket
        uxrocket[ns.data] = {hasWrapper: false, ready: utils.getClassname('ready'), selector: this.selector, options: this.options};
        this.$el.data(ns.rocket, uxrocket);

        this.bindUIActions();

        this.$el.addClass(utils.getClassname('ready'));

        this.emitEvent('ready');
    };

    Modal.prototype.bindUIActions = function() {
        var _this = this;

        this.$el.on(events.click, function(e) {
            e.preventDefault();

            // do not open same modal more then once
            if(typeof openedInstances[_this._instance] !== 'undefined') {
                return;
            }

            _this.emitEvent('start');
            _this.prepare();
        })
            .on(events.ready, function() {
                _this.onReady();
            })
            .on(events.open, function() {
                _this.onOpen();
            })
            .on(events.start, function() {
                _this.onStart();
            })
            .on(events.load, function() {
                _this.onLoad();
            })
            .on(events.close, function() {
                _this.onClose();
            })
            .on(events.remove, function() {
                _this.onRemove();
            });

        $('body')
            .on(events.click + ' ' + events.touchstart, '#uxr-modal-overlay', function() {
                modal.closeFromOverlay();
            })
            .on(events.click + ' ' + events.touchstart, '#uxr-modal-instance-' + _this._instance + ' .' + utils.getClassname('close'), function(e) {
                e.preventDefault();
                _this.close();
            })
            .on(events.mousedown + ' ' + events.touchstart, '#uxr-modal-instance-' + _this._instance + ' .' + utils.getClassname('drag'), function(e) {
                e.preventDefault();
                modal.move.start(_this, e);
            });

        $(document)
            .on(events.mousemove + ' ' + events.touchmove, function(e) {
                modal.move.drag(e);
            }).on(events.mouseup + ' ' + events.touchend, function() {
                modal.move.stop();
            });
    };

    Modal.prototype.prepare = function() {
        var css = {},
            $overlay = $('#uxr-modal-overlay'),
            _content = content.replace('{{id}}', 'uxr-modal-instance-' + this._instance);

        if(!this.options.allowMultiple && modal.isOnlyInstance()) {
            modal.closeFromOverlay();
        }

        if(this.options.blockUI) {
            if($overlay.length === 0) {
                $(utils.escapeSelector(this.options.appendTo)).append(overlay);
            }
            else {
                $overlay.removeClass(utils.getClassname('hide'));
            }
        }

        if(this.$content === null) {
            this.$content = $(_content);

            if(this.options.fixed) {
                css.position = 'fixed';
            }

            if(this.options.maxWidth !== '') {
                css.maxWidth = this.options.maxWidth;
            }

            if(this.options.maxHeight !== '') {
                css.maxHeight = this.options.maxHeight;
            }

            if(Object.keys(css).length > 0) {
                this.$content.css(css);
            }

            $(utils.escapeSelector(this.options.appendTo)).append(this.$content);
        }

        else {
            this.$content.removeClass(utils.getClassname('hide'));
        }

        this.$content.addClass(utils.getClassname('loading'));

        modal.registerInstance(this);

        this.open();
    };

    Modal.prototype.open = function() {
        this.emitEvent('open');
        this.get();
    };

    Modal.prototype.get = function() {
        var _this = this,
            iframe;

        if(this.options.iframe) {
            this.options.width = this.options.width || 800;
            this.options.height = this.options.height || 600;

            iframe = '';
            this.html = iframe;
            _this.emitEvent('load');
        }

        else if(!this.isInpage()) {
            $.get(this.href).done(function(data) {
                _this.html = data;
                setTimeout(function() {
                    _this.emitEvent('load');
                });
            });
        }
        else if(this.href !== false) {
            this.html = $(utils.escapeSelector(this.href));
            _this.emitEvent('load');
        }
        else {
            this.html = this.options.html;
            _this.emitEvent('load');
        }
    };

    Modal.prototype.load = function() {
        var loadedContent = this.$content.find('.' + utils.getClassname('content'));

        this.$content.removeClass(utils.getClassname('loading'));

        if(this.options.close) {
            this.$content
                .find('.' + utils.getClassname('close'))
                .removeClass(utils.getClassname('hide'));
        }

        if(this.options.allowMultiple){
            this.$content
                .find('.' + utils.getClassname('drag'))
                .removeClass(utils.getClassname('hide'));
        }

        if(!this.inpage) {
            loadedContent.html(this.html);
        }

        else {
            loadedContent.append(this.html);
        }

        this.resize();
    };

    Modal.prototype.close = function() {
        if(modal.isOnlyInstance()) {
            $('#uxr-modal-overlay').addClass(utils.getClassname('hide'));
        }

        this.$content
            .addClass(utils.getClassname('hide'))
            .find('.' + utils.getClassname('close'))
            .addClass(utils.getClassname('hide'));

        modal.close(this);

        this.emitEvent('close');
    };

    Modal.prototype.resize = function() {
        var css = {},
            loadedContent = this.$content.find('.' + utils.getClassname('content'));

        if(this.options.width !== '') {
            css.width = this.options.width;
        }

        if(this.options.height !== '') {
            css.height = this.options.height;
        }

        loadedContent.css(css);

        if(this.$content.height() < loadedContent.height()) {
            this.$content.css('overflowY', 'scroll');
        }
    };

    Modal.prototype.isInpage = function() {
        if(typeof this.inpage === 'undefined') {
            this.inpage = false;

            if(this.href === false) {
                this.inpage = true;
            }

            else if(this.href.charAt(0) === '#') {
                this.inpage = true;
            }

            else {
                var loc = window.location.href.replace(location.hash, '').replace(location.protocol, ''),
                    test = this.href.replace(location.hash, '').replace(location.protocol, '');

                if(loc === test) {
                    this.inpage = true;
                }
            }
        }

        return this.inpage;
    };

    Modal.prototype.onReady = function() {
        utils.callback(this.options.onReady);
    };

    Modal.prototype.onOpen = function() {
        utils.callback(this.options.onOpen);
    };

    Modal.prototype.onStart = function() {
        utils.callback(this.options.onStart);
    };

    Modal.prototype.onLoad = function() {
        this.load();

        utils.callback(this.options.onLoad);
    };

    Modal.prototype.onClose = function() {
        utils.callback(this.options.onClose);
    };

    Modal.prototype.onRemove = function() {
        utils.callback(this.options.onRemove);
    };

    Modal.prototype.emitEvent = function(which) {
        this.$el.trigger(events[which]);
    };

    Modal.prototype.getOpenedInstances = function() {
        return openedInstances;
    };

    var modal = {
        registerInstance: function(instance) {
            openedInstances[instance._instance] = instance;
            instance.previousInstance = openedInstances.lastInstance || instance._instance;
            openedInstances.lastInstance = instance._instance;
        },
        close           : function(instance) {
            openedInstances.lastInstance = instance.previousInstance;
            delete openedInstances[instance._instance];
        },
        closeFromOverlay: function() {
            var activeInstance = openedInstances[openedInstances.lastInstance];

            if(Object.keys(openedInstances).length === 1) {
                return;
            }

            if(!activeInstance.options.overlayClose) {
                return;
            }

            activeInstance.close();
        },
        isOnlyInstance  : function() {
            return (Object.keys(openedInstances).length - 1) === 1;
        },
        move            : {
            pos     : {},
            selected: false,
            previous: false,
            start   : function(instance, e) {
                var pointer = this.pointer(e);

                this.selected = instance;
                this.selected.$content.css('zIndex', 10001);
                this.pos.top = pointer.y - this.selected.$content[0].offsetTop;
                this.pos.left = pointer.x - this.selected.$content[0].offsetLeft;

                if(!this.previous) {
                    this.previous = instance;
                }
                else {
                    this.previous.$content.css('zIndex', 10000);
                }
            },
            drag    : function(e) {
                var pointer = this.pointer(e);

                if(this.selected) {
                    this.selected.$content[0].style.top = pointer.y + 'px';
                    this.selected.$content[0].style.left = pointer.x + 'px';
                }
            },
            stop    : function() {
                if(this.selected) {
                    this.previous = this.selected;
                    this.selected = false;
                }
            },
            pointer : function(e) {
                var out = {x: 0, y: 0};
                if(e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend' || e.type === 'touchcancel') {
                    var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
                    out.x = touch.pageX;
                    out.y = touch.pageY;
                } else if(e.type === 'mousedown' || e.type === 'mouseup' || e.type === 'mousemove' || e.type === 'mouseover' || e.type === 'mouseout' || e.type === 'mouseenter' || e.type === 'mouseleave') {
                    out.x = e.pageX;
                    out.y = e.pageY;
                }
                return out;
            }
        }
    };

    var utils = {
        callback: function(fn) {
            // if callback string is function call it directly
            if(typeof fn === 'function') {
                fn.apply(this);
            }

            // if callback defined via data-attribute, call it via new Function
            else {
                if(fn !== false) {
                    var func = new Function('return ' + fn);
                    func();
                }
            }
        },

        escapeSelector: function(selector) {
            var is_ID = selector.charAt(0) === '#',
                re = /([ !"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g;

            return is_ID ? '#' + selector.substring(1).replace(re, '\\$1') : selector;
        },

        getStringVariable: function(str) {
            var val;
            // check if it is chained
            if(str.indexOf('.') > -1) {
                var chain = str.split('.'),
                    chainVal = window[chain[0]];

                for(var i = 1; i < chain.length; i++) {
                    chainVal = chainVal[chain[i]];
                }

                val = chainVal;
            }

            else {
                val = window[str];
            }

            return val;
        },

        getClassname: function(which) {
            return ns.prefix + ns.name + '-' + ns.classes[which];
        }
    };

    ux = $.fn.modal = $.fn.uxrmodal = $.uxrmodal = function(options) {
        var selector = this.selector;

        // direct call to dialog
        if(typeof this === 'function') {
            Modal(null, options, null);
            return;
        }

        return this.each(function() {
            if($.data(this, ns.data)) {
                return;
            }

            // Bind the plugin and attach the instance to data
            $.data(this, ns.data, new Modal(this, options, selector));
        });
    };

    ux.resize = function() {
        var activeInstance = openedInstances[openedInstances.lastInstance];

        if(Object.keys(openedInstances).length === 1) {
            return;
        }

        activeInstance.resize();
    };

    ux.close = function() {
        var activeInstance = openedInstances[openedInstances.lastInstance];

        if(Object.keys(openedInstances).length === 1) {
            return;
        }

        activeInstance.close();
    };

    ux.version = '1.1.0';

    ux.settings = defaults;
}));<|endoftext|>


<commit_before>import ChunkedBlob from './ChunkedBlob'

export default class DownloadFile {

  constructor(name, size, type) {
    this.name = name
    this.size = size
    this.type = type
    this.chunks = new ChunkedBlob()
  }

  addChunk(b) {
    this.chunks.add(b)
  }

  clearChunks() {
    this.chunks = new ChunkedBlob()
  }

  isComplete() {
    return this.getProgress() === 1
  }

  getProgress() {
    return this.chunks.size / this.size
  }

  download() {
    let blob = this.chunks.toBlob()
    let url = URL.createObjectURL(blob)
    let a = document.createElement('a')
    a.download = this.name
    a.href = url
    a.click()
    URL.revokeObjectURL(url)
  }

  toJSON() {
    return {
      name: this.name,
      size: this.size,
      type: this.type
    }
  }

}
<commit_msg>Make downloads work in FF.<commit_after>import ChunkedBlob from './ChunkedBlob'

export default class DownloadFile {

  constructor(name, size, type) {
    this.name = name
    this.size = size
    this.type = type
    this.chunks = new ChunkedBlob()
  }

  addChunk(b) {
    this.chunks.add(b)
  }

  clearChunks() {
    this.chunks = new ChunkedBlob()
  }

  isComplete() {
    return this.getProgress() === 1
  }

  getProgress() {
    return this.chunks.size / this.size
  }

  download() {

    let blob = this.chunks.toBlob()
    let url = URL.createObjectURL(blob)

    let a = document.createElement('a')
    document.body.appendChild(a)
    a.download = this.name
    a.href = url
    a.click()

    setTimeout(() => {
      URL.revokeObjectURL(url)
      document.body.removeChild(a)
    }, 0)

  }

  toJSON() {
    return {
      name: this.name,
      size: this.size,
      type: this.type
    }
  }

}
<|endoftext|>


<commit_before>/** @babel */
/** @jsx etch.dom */

import { CompositeDisposable } from 'atom';
import etch from 'etch';
import changeCase from 'change-case';
import path from 'path';
import { EDIT_URI } from './view-uri';
import manager from '../Manager';
import Project from '../models/Project';

const disposables = new CompositeDisposable();

etch.setScheduler(atom.views);

export default class EditView {
  constructor(props, children) {
    this.props = props;
    this.children = children;
    etch.initialize(this);

    this.storeFocusedElement();

    this.setFocus();

    this.element.addEventListener('click', (event) => {
      if (event.target === this.refs.save) {
        this.saveProject();
      }
    });

    disposables.add(atom.commands.add(this.element, {
      'core:save': () => this.saveProject(),
      'core:confirm': () => this.saveProject(),
    }));

    disposables.add(atom.commands.add('atom-workspace', {
      'core:cancel': () => this.close(),
    }));
  }

  getFocusElement() {
    return this.refs.title;
  }

  setFocus() {
    const focusElement = this.getFocusElement();

    if (focusElement) {
      setTimeout(() => {
        focusElement.focus();
      }, 0);
    }
  }

  storeFocusedElement() {
    this.previouslyFocusedElement = document.activeElement;
  }

  restoreFocus() {
    if (this.previouslyFocusedElement) {
      this.previouslyFocusedElement.focus();
    }
  }

  close() {
    this.destroy();
  }

  async destroy() {
    const pane = atom.workspace.paneForURI(EDIT_URI);
    if (pane) {
      const item = pane.itemForURI(EDIT_URI);
      pane.destroyItem(item);
    }

    disposables.dispose();
    await etch.destroy(this);
  }

  saveProject() {
    const projectProps = {
      title: this.refs.title.value,
      paths: atom.project.getPaths(),
      group: this.refs.group.value,
      icon: this.refs.icon.value,
      color: this.refs.color.value,
      devMode: this.refs.devMode.checked,
    };
    let message = `${projectProps.title} has been saved.`;

    if (this.props.project) {
      // Paths should already be up-to-date, so use
      // the current paths as to not break possible relative paths.
      projectProps.paths = this.props.project.getProps().paths;
    }

    // many stuff will break if there is no root path,
    // so we don't continue without a root path
    if (!projectProps.paths.length) {
      atom.notifications.addError('You must have at least one folder in your project before you can save !');
    } else {
      manager.saveProject(projectProps);

      if (this.props.project) {
        message = `${this.props.project.title} has been updated.`;
      }
      atom.notifications.addSuccess(message);

      this.close();
    }
  }

  update(props, children) {
    this.props = props;
    this.children = children;
  }

  getTitle() {
    if (this.props.project) {
      return `Edit ${this.props.project.title}`;
    }

    return 'Save Project';
  }

  getIconName() { // eslint-disable-line class-methods-use-this
    return 'gear';
  }

  getURI() { // eslint-disable-line class-methods-use-this
    return EDIT_URI;
  }

  render() {
    const defaultProps = Project.defaultProps;
    const rootPath = atom.project.getPaths()[0];
    let props = { ...defaultProps, title: path.basename(rootPath) };

    if (atom.config.get('project-manager.prettifyTitle')) {
      props.title = changeCase.titleCase(path.basename(rootPath));
    }

    if (this.props.project && this.props.project.source === 'file') {
      const projectProps = this.props.project.getProps();
      props = Object.assign({}, props, projectProps);
    }

    const wrapperStyle = {
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
    };

    const style = {
      width: '500px',
    };

    const colorDisplay = {
      color: props.color,
    };

    return (

{this.getTitle()}

            Title
            <input ref="title" type="text" className="input-text" value={props.title} tabIndex="0" />

            Group
            <input ref="group" type="text" className="input-text" value={props.group} tabIndex="1" />

            Icon
            <input ref="icon" type="text" className="input-text" value={props.icon} tabIndex="2" />

            Color
            <input ref="color" type="text" className="input-text" value={props.color} tabIndex="3" style={colorDisplay} />

            Development mode
              <input
                ref="devMode"
                id="devMode"
                name="devMode"
                type="checkbox"
                className="input-toggle"
                checked={props.devMode}
                tabIndex="4"
              />

            <button ref="save" className="btn btn-primary" tabIndex="5">Save</button>

    );
  }
}
<commit_msg>Autofocus title field when saving/editing a project. Fixes #385<commit_after>/** @babel */
/** @jsx etch.dom */

import { CompositeDisposable } from 'atom';
import etch from 'etch';
import changeCase from 'change-case';
import path from 'path';
import { EDIT_URI } from './view-uri';
import manager from '../Manager';
import Project from '../models/Project';

const disposables = new CompositeDisposable();

etch.setScheduler(atom.views);

export default class EditView {
  constructor(props, children) {
    this.props = props;
    this.children = children;
    etch.initialize(this);

    this.storeFocusedElement();

    this.element.addEventListener('click', (event) => {
      if (event.target === this.refs.save) {
        this.saveProject();
      }
    });

    disposables.add(atom.commands.add(this.element, {
      'core:save': () => this.saveProject(),
      'core:confirm': () => this.saveProject(),
    }));

    disposables.add(atom.commands.add('atom-workspace', {
      'core:cancel': () => this.close(),
    }));
  }

  storeFocusedElement() {
    this.previouslyFocusedElement = document.activeElement;
  }

  restoreFocus() {
    if (this.previouslyFocusedElement) {
      this.previouslyFocusedElement.focus();
    }
  }

  close() {
    this.destroy();
  }

  async destroy() {
    const pane = atom.workspace.paneForURI(EDIT_URI);
    if (pane) {
      const item = pane.itemForURI(EDIT_URI);
      pane.destroyItem(item);
    }

    disposables.dispose();
    await etch.destroy(this);
  }

  saveProject() {
    const projectProps = {
      title: this.refs.title.value,
      paths: atom.project.getPaths(),
      group: this.refs.group.value,
      icon: this.refs.icon.value,
      color: this.refs.color.value,
      devMode: this.refs.devMode.checked,
    };
    let message = `${projectProps.title} has been saved.`;

    if (this.props.project) {
      // Paths should already be up-to-date, so use
      // the current paths as to not break possible relative paths.
      projectProps.paths = this.props.project.getProps().paths;
    }

    // many stuff will break if there is no root path,
    // so we don't continue without a root path
    if (!projectProps.paths.length) {
      atom.notifications.addError('You must have at least one folder in your project before you can save !');
    } else {
      manager.saveProject(projectProps);

      if (this.props.project) {
        message = `${this.props.project.title} has been updated.`;
      }
      atom.notifications.addSuccess(message);

      this.close();
    }
  }

  update(props, children) {
    this.props = props;
    this.children = children;
  }

  getTitle() {
    if (this.props.project) {
      return `Edit ${this.props.project.title}`;
    }

    return 'Save Project';
  }

  getIconName() { // eslint-disable-line class-methods-use-this
    return 'gear';
  }

  getURI() { // eslint-disable-line class-methods-use-this
    return EDIT_URI;
  }

  render() {
    const defaultProps = Project.defaultProps;
    const rootPath = atom.project.getPaths()[0];
    let props = { ...defaultProps, title: path.basename(rootPath) };

    if (atom.config.get('project-manager.prettifyTitle')) {
      props.title = changeCase.titleCase(path.basename(rootPath));
    }

    if (this.props.project && this.props.project.source === 'file') {
      const projectProps = this.props.project.getProps();
      props = Object.assign({}, props, projectProps);
    }

    const wrapperStyle = {
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
    };

    const style = {
      width: '500px',
    };

    const colorDisplay = {
      color: props.color,
    };

    return (

{this.getTitle()}

            Title
            <input autofocus="true" ref="title" type="text" className="input-text" value={props.title} tabIndex="0" />

            Group
            <input ref="group" type="text" className="input-text" value={props.group} tabIndex="1" />

            Icon
            <input ref="icon" type="text" className="input-text" value={props.icon} tabIndex="2" />

            Color
            <input ref="color" type="text" className="input-text" value={props.color} tabIndex="3" style={colorDisplay} />

            Development mode
              <input
                ref="devMode"
                id="devMode"
                name="devMode"
                type="checkbox"
                className="input-toggle"
                checked={props.devMode}
                tabIndex="4"
              />

            <button ref="save" className="btn btn-primary" tabIndex="5">Save</button>

    );
  }
}
<|endoftext|>


<commit_before>/**
 * @class FileAppender
 *
 * @author: darryl.west@raincitysoftware.com
 * @created: 7/7/14 5:15 PM
 */
const Logger = require('./Logger' ),
    AbstractAppender = require('./AbstractAppender' ),
    dash = require( 'lodash' ),
    fs = require( 'fs' ),
    path = require( 'path' );

const FileAppender = function(options) {
    'use strict';

    const appender = this;

    var typeName = options.typeName,
        autoOpen = dash.isBoolean( options.autoOpen ) ? options.autoOpen : true,
        logFilePath = options.logFilePath,
        level = options.level || Logger.DEFAULT_LEVEL,
        levels = options.levels || Logger.STANDARD_LEVELS,
        currentLevel = levels.indexOf( level ),
        writer = options.writer;

    if (!typeName) {
        typeName = options.typeName = 'FileAppender';
    }

    AbstractAppender.extend( this, options );

    /**
     * default formatter for this appender;
     * @param entry
     */
    this.formatter = function(entry) {
        var fields = appender.formatEntry( entry );

        // add new line
        fields.push( '\n' );

        return fields.join( appender.separator );
    };

    /**
     * call formatter then write the entry to the console output
     * @param entry - the log entry
     */
    this.write = function(entry) {
        if (levels.indexOf( entry.level ) >= currentLevel) {
            writer.write( appender.formatter( entry ) );
        }
    };

    this.setLevel = function(level) {
        var idx = levels.indexOf( level );
        if (idx >= 0) {
            currentLevel = idx;
        }
    };

    // writer is opened on construction
    var openWriter = function() {
        if (!writer) {
            var file = path.normalize( logFilePath ),
                opts = {
                    flags:'a',
                    encoding:'utf8'
                };

            writer = fs.createWriteStream( file, opts );
            // writer.write('\n');
        }
    };

    this.closeWriter = function() {
        if (writer) {
            writer.end('\n');
        }
    };

    // constructor tests
    if (!logFilePath) throw new Error('appender must be constructed with a log file path');

    if (autoOpen) {
        openWriter();
    }
};

module.exports = FileAppender;<commit_msg>added CR for file writer when running in windows<commit_after>/**
 * @class FileAppender
 *
 * @author: darryl.west@raincitysoftware.com
 * @created: 7/7/14 5:15 PM
 */
const Logger = require('./Logger' ),
    AbstractAppender = require('./AbstractAppender' ),
    dash = require( 'lodash' ),
    fs = require( 'fs' ),
    path = require( 'path' );

const FileAppender = function(options) {
    'use strict';

    const appender = this;

    var typeName = options.typeName,
        autoOpen = dash.isBoolean( options.autoOpen ) ? options.autoOpen : true,
        logFilePath = options.logFilePath,
        level = options.level || Logger.DEFAULT_LEVEL,
        levels = options.levels || Logger.STANDARD_LEVELS,
        currentLevel = levels.indexOf( level ),
        writer = options.writer;

    if (!typeName) {
        typeName = options.typeName = 'FileAppender';
    }

    AbstractAppender.extend( this, options );

    /**
     * default formatter for this appender;
     * @param entry
     */
    this.formatter = function(entry) {
        var fields = appender.formatEntry( entry );

        // add new line and CR for windows
        if (/^win/.test(process.platform)) {
            fields.push( '\r\n' );
        } else {
            fields.push( '\n' );
        }

        return fields.join( appender.separator );
    };

    /**
     * call formatter then write the entry to the console output
     * @param entry - the log entry
     */
    this.write = function(entry) {
        if (levels.indexOf( entry.level ) >= currentLevel) {
            writer.write( appender.formatter( entry ) );
        }
    };

    this.setLevel = function(level) {
        var idx = levels.indexOf( level );
        if (idx >= 0) {
            currentLevel = idx;
        }
    };

    // writer is opened on construction
    var openWriter = function() {
        if (!writer) {
            var file = path.normalize( logFilePath ),
                opts = {
                    flags:'a',
                    encoding:'utf8'
                };

            writer = fs.createWriteStream( file, opts );
            // writer.write('\n');
        }
    };

    this.closeWriter = function() {
        if (writer) {
            writer.end('\n');
        }
    };

    // constructor tests
    if (!logFilePath) throw new Error('appender must be constructed with a log file path');

    if (autoOpen) {
        openWriter();
    }
};

module.exports = FileAppender;
<|endoftext|>


<commit_before>module.exports = watchDirectory;

var util = require('./fs-util'),
	watch = require('node-watch'),
	path = require('path'),
	fs = require('fs');

var liveReload = require('./live-reload.js'),
	thumbnails = require('./thumbnails'),
	woods = require('./woods');

function watchDirectory(site) {
	var options = { recursive: true, followSymLinks: true },
		changes = [],
		timeoutId;

	var changed = function(uri, removed) {
		// ignore hidden files
		if ((/^\./).test(path.basename(uri)))
			return;
		woods.emit('changed', site, uri, removed);
		site.emit('changed', uri, removed);

		if ((/\/(assets)\//).test(uri))
			return liveReload.changedFile(uri);

		if ((/\/(content)\//).test(uri))
			site.dirty = true;

		if (timeoutId)
			clearTimeout(timeoutId);

		changes.push(uri);

		// Make the timeout longer by the amount of filesystem changes:
		var timeoutTime = 10 * changes.length + 30;

		timeoutId = setTimeout(function() {
			var then = new Date();
			// TODO: go through changes array instead and apply individual changes
			// instead of rebuilding the whole site?
			if (site.dirty) {
				site.build(function(err) {
					if (err)
						console.log(err);
					console.log('Rebuilt', site.pageCount, 'pages in', new Date() - then);
					liveReload.changed();
					site.dirty = false;
				});
			}
			changes.length = 0;
		}, timeoutTime);
	};
	watch(site.getUri(), options, function(uri) {
		fs.exists(uri, function(exists) {
			changed(uri, !exists);
		});
	});
}<commit_msg>Rebuild site when template changed.<commit_after>module.exports = watchDirectory;

var util = require('./fs-util'),
	watch = require('node-watch'),
	path = require('path'),
	fs = require('fs');

var liveReload = require('./live-reload.js'),
	thumbnails = require('./thumbnails'),
	woods = require('./woods');

function watchDirectory(site) {
	var options = { recursive: true, followSymLinks: true },
		changes = [],
		timeoutId;

	var changed = function(uri, removed) {
		// ignore hidden files
		if ((/^\./).test(path.basename(uri)))
			return;
		woods.emit('changed', site, uri, removed);
		site.emit('changed', uri, removed);

		if ((/\/(assets)\//).test(uri))
			return liveReload.changedFile(uri);

		if ((/\/(content)\/|jade$/).test(uri))
			site.dirty = true;

		if (timeoutId)
			clearTimeout(timeoutId);

		changes.push(uri);

		// Make the timeout longer by the amount of filesystem changes:
		var timeoutTime = 10 * changes.length + 30;

		timeoutId = setTimeout(function() {
			var then = new Date();
			// TODO: go through changes array instead and apply individual changes
			// instead of rebuilding the whole site?
			if (site.dirty) {
				site.build(function(err) {
					if (err)
						console.log(err);
					console.log('Rebuilt', site.pageCount, 'pages in', new Date() - then);
					liveReload.changed();
					site.dirty = false;
				});
			}
			changes.length = 0;
		}, timeoutTime);
	};
	watch(site.getUri(), options, function(uri) {
		fs.exists(uri, function(exists) {
			changed(uri, !exists);
		});
	});
}<|endoftext|>


<commit_before>/**
# web-components.js

Helper methods for creating Web Components.

## Methods

### makeWebComponent

    makeWebComponent(tagName, {
      superClass=HTMLElement, // the class you're extending
      style=false,            // expect object
      methods={},             // map names to class methods
      eventHandlers={},       // map eventTypes to event handlers
      attributes={},          // map attributes to default values
      content=slot(),         // HTMLElement or DocumentFragment or falsy
      ariaRole=false,         // expect string
    })                        // returns the class

Defines a new [Web Component](https://www.webcomponents.org/)).

Returns the component class (in case you want to subclass it).

- `style` can be CSS source or a map of selector rules to maps of css rules.
  If no style is passed, no shadowRoot will be created
- `methods` will become class methods, notably `render` and `childListChange`
  - `render` is where the widget gets expressed in the DOM, based on its state
  - `childListChange` indicates that children of the (original) DOM node have changed
- `eventHandlers` will be bound to the DOM element (i.e. `this` will refer to the
  expected instance)
- `attributes` will be converted into object setters and getters.
  - the default value is assumed to be the correct type (if string or number);
    for any other type (e.g. null or an object) the value is preserved as an element
    property and not reflected in the DOM, and if an attribute is found, it's treated
    as JSON.
  - `value` is treated like any other attribute
    - boolean values are encoded by the value attribute being present or absent
    - when `value` is changed, a change event is triggered
  - a DOM mutation observer will automatically update the element (i.e. the `render`
    method will fire if the attributes are changed).
  - **Note**: if you specify any attributes, a `MutationObserver` will be created
    to trigger `render()` when attributes are changed.
  - a boolean attribute will be reflected as a boolean attribute in the DOM (e.g.
    the way `disabled` works).
- `content` will default to being a `<slot>` (pass explicit `false` or content that
  explicitly does not include a `<slot>` element and you'll create a sealed element,
  i.e. an element that ignores its content).
  - you can be lazy and pass a string to content (it will become a `TextNode`) or
    an array of `HTMLElement` and (you can wrap them in a `DocumentFragment` but
    you don't have to).
- `ariaRole` will do the expected thing
- by default, setting the `hidden` attribute will hide a styled component (this is
  implemented via styles), not attributes, so there's no MutationObserver oberhead.

The class has a single static method, `defaultAttributes()` which returns (shockingly)
the a clone of the `atrributes` array used to construct the component. This is useful
for introspection (e.g. if you're building a UI builder that needs to know what attributes
a given custom element has).

#### Component Lifecycle

makeComponent copies all methods provided into the component's prototype, so the
standard lifecycle methods `connectedCallback`, `disconnectedCallback`,
`adoptedCallback`, and `attributeChangedCallback` work as normal.

#### Performance Notes

The "lightest" web-components have no `style` (and hence no shadowDOM) or `attributes`.
- supporting attributes involves creating a MutationObserver, which has an overhead.
- adding a `style` object has a larger perf overhead.

Even so, in general web-components should perform better than components implemented using
Javascript frameworks (including `b8r` components).

More information is provided in [this blog entry](http://loewald.com/blog/2019/01/more-on-web-components-and-perf/).

### makeElement

    const makeElement = (tagType, {
      content=false,  // text, or something that can be appended to an HTMLElement
      attributes={},  // attribute map
      styles={},      // style object
      classes=[],     // list of classes
    })                // returns the element

A handy method for creating a DOM element with specified properties. Content can
be an `HTMLElement` or `DocumentFragment` or a string (which is converted to a text
node).

If you want to create a reusable element 'factory' you can simply write
something like:

    const div = (settings={}) => makeElement('div', settings);

This is how the convenience methods below were created, and affords lightweight
rendering of DOM structures in vanilla javascript without requiring transpilation, etc.

E.g. instead of writing this:

    import {styled} from "styletron-react";
    const StyledElement = styled('div', {
      ... css stuff ...
    });
    ...

…and relying on transpilation, you can write:

    import {makeElement, div} from "web-components";
    const styledElement = makeElement('div', {
      styles: {
        ... css stuff ...
      }
    });
    ...
    styledElement({ content: [div(), div(), div()] });

…and it will work transparently in any modern browser.

### dispatch

The recommended way for custom-elements to notify the rest of the world that
they've changed in some way is by triggering events.

    dipatch(target, type) // triggers an event of type on the target element

### div, span, input, slot

These are convenience methods wrapped around makeElement, so instead of writing:

    makeElement('span', {...});

You can write:

    span({...}); // and the object is optional, so span() works too.

### fragment

    fragment(...elements)

Creates a document fragment containing the elements passed to it.

### Example

This is from an older version of the `<b8r-select>` control:

    fragment(
      makeElement('div', {classes: ['selection']}),
      makeElement('div', {content: '▾', classes: ['indicator']}),
      makeElement('div', {classes: ['menu'], content: makeElement('slot', {})}),
    )

Convenience methods allow this to be simplified to:

    fragment(
      div({classes: ['selection']}),
      div({content: '▾', classes: ['indicator']}),
      div({classes: ['menu'], content: slot()}),
    )

## TODO

- Resize events
- Component styling scope (see below)

## Recommended Reading

### Best Practices

I do not follow these slavishly (some I flat out disagree with) but include them for
reference purposes.

- [Custom Elements Best Practices](https://developers.google.com/web/fundamentals/web-components/best-practices)
- [Web Components Best Practices](https://www.webcomponents.org/community/articles/web-components-best-practices)

### Styling

- [Styling a Web Component](https://css-tricks.com/styling-a-web-component/)

The above is more of a "how to" than best practices. I'm still trying to figure
out best practices myself. Right now, I'd say that **the best way to style
web-components is as little as possible** -- unless they're actual views
(and my jury is out on whether building views as web-components is at all a
good idea).

Creating components with minimal styling and no shadowDOM is another possibility.
Instead of creating an internal style node, they could simply insert a singleton
stylesheet in the `<header>` the way `b8r` components do.

*/
/* global Event, MutationObserver, HTMLElement, requestAnimationFrame */

const appendContentToElement = (elt, content) => {
  if (content) {
    if (typeof content === 'string') {
      elt.textContent = content
    } else if (Array.isArray(content)) {
      content.forEach(node => elt.appendChild(node.cloneNode(true)))
    } else if (content.cloneNode) {
      elt.appendChild(content.cloneNode(true))
    } else {
      throw new Error('expect text content or document node')
    }
  }
}

const makeElement = (tagType, {
  content = false,
  attributes = {},
  styles = {},
  classes = []
}) => {
  const elt = document.createElement(tagType)
  appendContentToElement(elt, content)
  Object.keys(attributes).forEach((attributeName) => elt.setAttribute(attributeName, attributes[attributeName]))
  Object.keys(styles).forEach((styleName) => {
    elt.style[styleName] = styles[styleName]
  })
  classes.forEach((className) => elt.classList.add(className))
  return elt
}

const div = (settings = {}) => makeElement('div', settings)
const span = (settings = {}) => makeElement('span', settings)
const input = (settings = {}) => makeElement('input', settings)
const button = (settings = {}) => makeElement('button', settings)
const slot = (settings = {}) => makeElement('slot', settings)

const fragment = (...elements) => {
  const container = document.createDocumentFragment()
  elements.forEach(element => container.appendChild(element.cloneNode(true)))
  return container
}

const _hyphenated = s => s.replace(/[A-Z]/g, c => '-' + c.toLowerCase())

const _css = (obj) => {
  if (typeof obj === 'object') {
    const selectors = Object.keys(obj).map((selector) => {
      const body = obj[selector]
      const rule = Object.keys(body)
        .map((prop) => `  ${_hyphenated(prop)}: ${body[prop]};`)
        .join('\n')
      return `${selector} {\n${rule}\n}`
    })
    return selectors.join('\n\n')
  } else {
    return obj
  }
}

const makeWebComponent = (tagName, {
  superClass = HTMLElement, // the class you're exetending
  value = false, // expect boolean
  style = false, // expect object
  methods = {}, // map names to functions
  eventHandlers = {}, // map eventTypes to event handlers
  props = {}, // map of instance properties to defaults
  attributes = {}, // map attributes to default values
  content = slot(), // HTMLElement or DocumentFragment
  ariaRole = false // expect string
}) => {
  let styleNode = null
  if (style) {
    style = Object.assign({ ':host([hidden])': { display: 'none !important' } }, style)
    styleNode = makeElement('style', { content: _css(style) })
  } else if (style) {
    console.error(`style for a web-component ${tagName} with now shadowRoot is not supported`)
  }
  if (methods.render) {
    methods = Object.assign({
      queueRender (change = false) {
        if (!this._changeQueued) this._changeQueued = change
        if (!this._renderQueued) {
          this._renderQueued = true
          requestAnimationFrame(() => {
            if (this._changeQueued) dispatch(this, 'change')
            this._changeQueued = false
            this._renderQueued = false
            this.render()
          })
        }
      }
    }, methods)
  }

  const componentClass = class extends superClass {
    constructor () {
      super()
      Object.assign(this, props)
      if (styleNode) {
        const shadow = this.attachShadow({ mode: 'open' })
        shadow.appendChild(styleNode.cloneNode(true))
        appendContentToElement(shadow, content)
      } else {
        appendContentToElement(this, content)
      }
      Object.keys(eventHandlers).forEach(eventType => {
        this.addEventListener(eventType, eventHandlers[eventType].bind(this))
      })
      if (ariaRole) this.setAttribute('aria-role', ariaRole)
      const attributeNames = Object.keys(attributes)
      if (eventHandlers.childListChange) {
        const observer = new MutationObserver(eventHandlers.childListChange.bind(this))
        observer.observe(this, { childList: true })
      }
      if (attributeNames.length) {
        const attributeValues = {}
        const observer = new MutationObserver((mutationsList) => {
          let triggerChange = false
          let triggerRender = false
          mutationsList.forEach((mutation) => {
            triggerChange = mutation.attributeChange === 'value'
            triggerRender = triggerRender || triggerChange || attributeNames.includes(mutation.attributeName)
          })
          if (triggerRender && this.queueRender) this.queueRender(triggerChange)
        })
        observer.observe(this, { attributes: true })
        attributeNames.forEach(attributeName => {
          Object.defineProperty(this, attributeName, {
            writeable: true,
            enumerable: false,
            get () {
              if (typeof attributes[attributeName] === 'boolean') {
                return this.hasAttribute(attributeName)
              } else {
                if (this.hasAttribute(attributeName)) {
                  return typeof attributes[attributeName] === 'number'
                    ? parseFloat(this.getAttribute(attributeName))
                    : this.getAttribute(attributeName)
                } else if (attributeValues[attributeName] !== undefined) {
                  return attributeValues[attributeName]
                } else {
                  return attributes[attributeName]
                }
              }
            },
            set (value) {
              if (typeof attributes[attributeName] === 'boolean') {
                if (value !== this[attributeName]) {
                  if (value) {
                    this.setAttribute(attributeName, '')
                  } else {
                    this.removeAttribute(attributeName)
                  }
                  if (this.queueRender) this.queueRender(attributeName === 'value')
                }
              } else if (typeof attributes[attributeName] === 'number') {
                if (value !== parseFloat(this[attributeName])) {
                  this.setAttribute(attributeName, value)
                  if (this.queueRender) this.queueRender(attributeName === 'value')
                }
              } else {
                if (typeof value === 'object' || `${value}` !== `${this[attributeName]}`) {
                  if (value === null || value === undefined || typeof value === 'object') {
                    this.removeAttribute(attributeName)
                  } else {
                    this.setAttribute(attributeName, value)
                  }
                  attributeValues[attributeName] = value
                  if (this.queueRender) this.queueRender(attributeName === 'value')
                }
              }
            }
          })
        })
      }
      if (this.queueRender) this.queueRender()
    }

    static defaultAttributes () {
      return { ...attributes }
    }
  }

  Object.keys(methods).forEach(methodName => {
    componentClass.prototype[methodName] = methods[methodName]
  })

  window.customElements.define(tagName, componentClass)

  return componentClass
}

const dispatch = (target, type) => {
  const event = new Event(type)
  target.dispatchEvent(event)
}

export {
  fragment,
  makeElement,
  makeWebComponent,
  div,
  slot,
  input,
  button,
  span,
  dispatch
}
<commit_msg>tweaks to web-component declarations<commit_after>/**
# web-components.js

Helper methods for creating Web Components.

## Methods

### makeWebComponent

    makeWebComponent(tagName, {
      superClass=HTMLElement, // the class you're extending
      style=false,            // expect object
      methods={},             // map names to class methods
      eventHandlers={},       // map eventTypes to event handlers
      attributes={},          // map attributes to default values
      content=slot(),         // HTMLElement or DocumentFragment or falsy
      ariaRole=false,         // expect string
    })                        // returns the class

Defines a new [Web Component](https://www.webcomponents.org/)).

Returns the component class (in case you want to subclass it).

- `style` can be CSS source or a map of selector rules to maps of css rules.
  If no style is passed, no shadowRoot will be created
- `methods` will become class methods, notably `render` and `childListChange`
  - `render` is where the widget gets expressed in the DOM, based on its state
  - `childListChange` indicates that children of the (original) DOM node have changed
- `eventHandlers` will be bound to the DOM element (i.e. `this` will refer to the
  expected instance)
- `attributes` will be converted into object setters and getters.
  - the default value is assumed to be the correct type (if string or number);
    for any other type (e.g. null or an object) the value is preserved as an element
    property and not reflected in the DOM, and if an attribute is found, it's treated
    as JSON.
  - `value` is treated like any other attribute
    - boolean values are encoded by the value attribute being present or absent
    - when `value` is changed, a change event is triggered
  - a DOM mutation observer will automatically update the element (i.e. the `render`
    method will fire if the attributes are changed).
  - **Note**: if you specify any attributes, a `MutationObserver` will be created
    to trigger `render()` when attributes are changed.
  - a boolean attribute will be reflected as a boolean attribute in the DOM (e.g.
    the way `disabled` works).
- `content` will default to being a `<slot>` (pass explicit `false` or content that
  explicitly does not include a `<slot>` element and you'll create a sealed element,
  i.e. an element that ignores its content).
  - you can be lazy and pass a string to content (it will become a `TextNode`) or
    an array of `HTMLElement` and (you can wrap them in a `DocumentFragment` but
    you don't have to).
- `ariaRole` will do the expected thing
- by default, setting the `hidden` attribute will hide a styled component (this is
  implemented via styles), not attributes, so there's no MutationObserver oberhead.

The class has a single static method, `defaultAttributes()` which returns (shockingly)
the a clone of the `atrributes` array used to construct the component. This is useful
for introspection (e.g. if you're building a UI builder that needs to know what attributes
a given custom element has).

#### Component Lifecycle

makeComponent copies all methods provided into the component's prototype, so the
standard lifecycle methods `connectedCallback`, `disconnectedCallback`,
`adoptedCallback`, and `attributeChangedCallback` work as normal.

#### Performance Notes

The "lightest" web-components have no `style` (and hence no shadowDOM) or `attributes`.
- supporting attributes involves creating a MutationObserver, which has an overhead.
- adding a `style` object has a larger perf overhead.

Even so, in general web-components should perform better than components implemented using
Javascript frameworks (including `b8r` components).

More information is provided in [this blog entry](http://loewald.com/blog/2019/01/more-on-web-components-and-perf/).

### makeElement

    const makeElement = (tagType, {
      content=false,  // text, or something that can be appended to an HTMLElement
      attributes={},  // attribute map
      styles={},      // style object
      classes=[],     // list of classes
    })                // returns the element

A handy method for creating a DOM element with specified properties. Content can
be an `HTMLElement` or `DocumentFragment` or a string (which is converted to a text
node).

If you want to create a reusable element 'factory' you can simply write
something like:

    const div = (settings={}) => makeElement('div', settings);

This is how the convenience methods below were created, and affords lightweight
rendering of DOM structures in vanilla javascript without requiring transpilation, etc.

E.g. instead of writing this:

    import {styled} from "styletron-react";
    const StyledElement = styled('div', {
      ... css stuff ...
    });
    ...

…and relying on transpilation, you can write:

    import {makeElement, div} from "web-components";
    const styledElement = makeElement('div', {
      styles: {
        ... css stuff ...
      }
    });
    ...
    styledElement({ content: [div(), div(), div()] });

…and it will work transparently in any modern browser.

### dispatch

The recommended way for custom-elements to notify the rest of the world that
they've changed in some way is by triggering events.

    dipatch(target, type) // triggers an event of type on the target element

### div, span, input, slot

These are convenience methods wrapped around makeElement, so instead of writing:

    makeElement('span', {...});

You can write:

    span({...}); // and the object is optional, so span() works too.

### fragment

    fragment(...elements)

Creates a document fragment containing the elements passed to it.

### Example

This is from an older version of the `<b8r-select>` control:

    fragment(
      makeElement('div', {classes: ['selection']}),
      makeElement('div', {content: '▾', classes: ['indicator']}),
      makeElement('div', {classes: ['menu'], content: makeElement('slot', {})}),
    )

Convenience methods allow this to be simplified to:

    fragment(
      div({classes: ['selection']}),
      div({content: '▾', classes: ['indicator']}),
      div({classes: ['menu'], content: slot()}),
    )

## TODO

- Resize events
- Component styling scope (see below)

## Recommended Reading

### Best Practices

I do not follow these slavishly (some I flat out disagree with) but include them for
reference purposes.

- [Custom Elements Best Practices](https://developers.google.com/web/fundamentals/web-components/best-practices)
- [Web Components Best Practices](https://www.webcomponents.org/community/articles/web-components-best-practices)

### Styling

- [Styling a Web Component](https://css-tricks.com/styling-a-web-component/)

The above is more of a "how to" than best practices. I'm still trying to figure
out best practices myself. Right now, I'd say that **the best way to style
web-components is as little as possible** -- unless they're actual views
(and my jury is out on whether building views as web-components is at all a
good idea).

Creating components with minimal styling and no shadowDOM is another possibility.
Instead of creating an internal style node, they could simply insert a singleton
stylesheet in the `<header>` the way `b8r` components do.

*/
/* global Event, MutationObserver, HTMLElement, requestAnimationFrame */

const makeElement = (tagType, {
  content = false,
  attributes = {},
  styles = {},
  classes = []
}) => {
  const elt = document.createElement(tagType)
  appendContentToElement(elt, content)
  Object.keys(attributes).forEach((attributeName) => elt.setAttribute(attributeName, attributes[attributeName]))
  Object.keys(styles).forEach((styleName) => {
    elt.style[styleName] = styles[styleName]
  })
  classes.forEach((className) => elt.classList.add(className))
  return elt
}

const button = (settings = {}) => makeElement('button', settings)
const div = (settings = {}) => makeElement('div', settings)
const input = (settings = {}) => makeElement('input', settings)
const label = (settings = {}) => makeElement('label', settings)
const slot = (settings = {}) => makeElement('slot', settings)
const span = (settings = {}) => makeElement('span', settings)
const text = s => document.createTextNode(s)

const appendContentToElement = (elt, content) => {
  if (content) {
    if (typeof content === 'string') {
      elt.textContent = content
    } else if (Array.isArray(content)) {
      content.forEach(node => {
        elt.appendChild(node.cloneNode ? node.cloneNode(true) : text(node))
      })
    } else if (content.cloneNode) {
      elt.appendChild(content.cloneNode(true))
    } else {
      throw new Error('expect text content or document node')
    }
  }
}

const fragment = (...elements) => {
  const container = document.createDocumentFragment()
  elements.forEach(element => container.appendChild(element.cloneNode(true)))
  return container
}

const _hyphenated = s => s.replace(/[A-Z]/g, c => '-' + c.toLowerCase())

const _css = (obj) => {
  if (typeof obj === 'object') {
    const selectors = Object.keys(obj).map((selector) => {
      const body = obj[selector]
      const rule = Object.keys(body)
        .map((prop) => `  ${_hyphenated(prop)}: ${body[prop]};`)
        .join('\n')
      return `${selector} {\n${rule}\n}`
    })
    return selectors.join('\n\n')
  } else {
    return obj
  }
}

const makeWebComponent = (tagName, {
  superClass = HTMLElement, // the class you're exetending
  value = false, // expect boolean
  style = false, // expect object
  methods = {}, // map names to functions
  eventHandlers = {}, // map eventTypes to event handlers
  props = {}, // map of instance properties to defaults
  attributes = {}, // map attributes to default values
  content = slot(), // HTMLElement or DocumentFragment
  ariaRole = false // expect string
}) => {
  let styleNode = null
  if (style) {
    style = Object.assign({ ':host([hidden])': { display: 'none !important' } }, style)
    styleNode = makeElement('style', { content: _css(style) })
  } else if (style) {
    console.error(`style for a web-component ${tagName} with now shadowRoot is not supported`)
  }
  if (methods.render) {
    methods = Object.assign({
      queueRender (change = false) {
        if (!this._changeQueued) this._changeQueued = change
        if (!this._renderQueued) {
          this._renderQueued = true
          requestAnimationFrame(() => {
            if (this._changeQueued) dispatch(this, 'change')
            this._changeQueued = false
            this._renderQueued = false
            this.render()
          })
        }
      }
    }, methods)
  }

  const componentClass = class extends superClass {
    constructor () {
      super()
      Object.assign(this, props)
      if (styleNode) {
        const shadow = this.attachShadow({ mode: 'open' })
        shadow.appendChild(styleNode.cloneNode(true))
        appendContentToElement(shadow, content)
      } else {
        appendContentToElement(this, content)
      }
      Object.keys(eventHandlers).forEach(eventType => {
        this.addEventListener(eventType, eventHandlers[eventType].bind(this))
      })
      if (ariaRole) this.setAttribute('aria-role', ariaRole)
      const attributeNames = Object.keys(attributes)
      if (eventHandlers.childListChange) {
        const observer = new MutationObserver(eventHandlers.childListChange.bind(this))
        observer.observe(this, { childList: true })
      }
      if (attributeNames.length) {
        const attributeValues = {}
        const observer = new MutationObserver((mutationsList) => {
          let triggerChange = false
          let triggerRender = false
          mutationsList.forEach((mutation) => {
            triggerChange = mutation.attributeChange === 'value'
            triggerRender = triggerRender || triggerChange || attributeNames.includes(mutation.attributeName)
          })
          if (triggerRender && this.queueRender) this.queueRender(triggerChange)
        })
        observer.observe(this, { attributes: true })
        attributeNames.forEach(attributeName => {
          Object.defineProperty(this, attributeName, {
            writeable: true,
            enumerable: false,
            get () {
              if (typeof attributes[attributeName] === 'boolean') {
                return this.hasAttribute(attributeName)
              } else {
                if (this.hasAttribute(attributeName)) {
                  return typeof attributes[attributeName] === 'number'
                    ? parseFloat(this.getAttribute(attributeName))
                    : this.getAttribute(attributeName)
                } else if (attributeValues[attributeName] !== undefined) {
                  return attributeValues[attributeName]
                } else {
                  return attributes[attributeName]
                }
              }
            },
            set (value) {
              if (typeof attributes[attributeName] === 'boolean') {
                if (value !== this[attributeName]) {
                  if (value) {
                    this.setAttribute(attributeName, '')
                  } else {
                    this.removeAttribute(attributeName)
                  }
                  if (this.queueRender) this.queueRender(attributeName === 'value')
                }
              } else if (typeof attributes[attributeName] === 'number') {
                if (value !== parseFloat(this[attributeName])) {
                  this.setAttribute(attributeName, value)
                  if (this.queueRender) this.queueRender(attributeName === 'value')
                }
              } else {
                if (typeof value === 'object' || `${value}` !== `${this[attributeName]}`) {
                  if (value === null || value === undefined || typeof value === 'object') {
                    this.removeAttribute(attributeName)
                  } else {
                    this.setAttribute(attributeName, value)
                  }
                  attributeValues[attributeName] = value
                  if (this.queueRender) this.queueRender(attributeName === 'value')
                }
              }
            }
          })
        })
      }
      if (this.queueRender) this.queueRender()
    }

    static defaultAttributes () {
      return { ...attributes }
    }
  }

  Object.keys(methods).forEach(methodName => {
    componentClass.prototype[methodName] = methods[methodName]
  })

  window.customElements.define(tagName, componentClass)

  return componentClass
}

const dispatch = (target, type) => {
  const event = new Event(type)
  target.dispatchEvent(event)
}

export {
  fragment,
  makeElement,
  makeWebComponent,
  div,
  slot,
  input,
  button,
  label,
  span,
  text,
  dispatch
}
<|endoftext|>


<commit_before>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

var service=require('cordova/plugin/webos/service'),
    cordova = require('cordova');

module.exports = {
    id: "webos",
    initialize: function() {
        var modulemapper = require('cordova/modulemapper');

        modulemapper.loadMatchingModules(/cordova.*\/symbols$/);
        modulemapper.mapModules(window);

        if (window.PalmSystem) {
            window.PalmSystem.stageReady();
        }

        // create global Mojo object if it does not exist
        Mojo = window.Mojo || {};

        // wait for deviceready before listening and firing document events
        document.addEventListener("deviceready", function () {

            // LunaSysMgr calls this when the windows is maximized or opened.
            window.Mojo.stageActivated = function() {
                console.log("stageActivated");
                cordova.fireDocumentEvent("resume");
            };
            // LunaSysMgr calls this when the windows is minimized or closed.
            window.Mojo.stageDeactivated = function() {
                console.log("stageDeactivated");
                cordova.fireDocumentEvent("pause");
            };
            // LunaSysMgr calls this when a KeepAlive app's window is hidden
            window.Mojo.hide = function() {
                console.log("hide");
            };
            // LunaSysMgr calls this when a KeepAlive app's window is shown
            window.Mojo.show = function() {
                console.log("show");
            };

            // LunaSysMgr calls this whenever an app is "launched;"
            window.Mojo.relaunch = function() {
                // need to return true to tell sysmgr the relaunch succeeded.
                // otherwise, it'll try to focus the app, which will focus the first
                // opened window of an app with multiple windows.

                var lp=JSON.parse(PalmSystem.launchParams) || {};

                if (lp['palm-command'] && lp['palm-command'] == 'open-app-menu') {
                    console.log("event:ToggleAppMenu");
                    cordova.fireDocumentEvent("menubutton");
                }

                console.log("relaunch");
                return true;
            };

            // start to listen for network connection changes
            service.Request('palm://com.palm.connectionmanager', {
                method: 'getstatus',
                parameters: { subscribe: true },
                onSuccess: function (result) {
                    console.log("subscribe:result:"+JSON.stringify(result));

                    if (!result.isInternetConnectionAvailable) {
                        if (navigator.onLine) {
                            console.log("Firing event:offline");
                            cordova.fireDocumentEvent("offline");
                        }
                    } else {
                        console.log("Firing event:online");
                        cordova.fireDocumentEvent("online");
                    }
                },
                onFailure: function(e) {
                    console.error("subscribe:error");
                }
            });

        });
    },
    merges: {
        navigator: {
            children: {
                service: {
                    path: "cordova/plugin/webos/service"
                },
                application: {
                    path: "cordova/plugin/webos/application"
                },
                window: {
                    path: "cordova/plugin/webos/window"
                },
                orientation: {
                    path: "cordova/plugin/webos/orientation"
                },
                keyboard: {
                    path: "cordova/plugin/webos/keyboard"
                }
            }
        }
    }
};
<commit_msg>[webos] modulemapper refactor for platform plugins.<commit_after>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

var service=require('cordova/plugin/webos/service'),
    cordova = require('cordova');

module.exports = {
    id: "webos",
    initialize: function() {
        var modulemapper = require('cordova/modulemapper');

        modulemapper.loadMatchingModules(/cordova.*\/symbols$/);

        modulemapper.merges('cordova/plugin/webos/service', 'navigator.service');
        modulemapper.merges('cordova/plugin/webos/application', 'navigator.application');
        modulemapper.merges('cordova/plugin/webos/window', 'navigator.window');
        modulemapper.merges('cordova/plugin/webos/orientation', 'navigator.orientation');
        modulemapper.merges('cordova/plugin/webos/keyboard', 'navigator.keyboard');

        modulemapper.mapModules(window);

        if (window.PalmSystem) {
            window.PalmSystem.stageReady();
        }

        // create global Mojo object if it does not exist
        Mojo = window.Mojo || {};

        // wait for deviceready before listening and firing document events
        document.addEventListener("deviceready", function () {

            // LunaSysMgr calls this when the windows is maximized or opened.
            window.Mojo.stageActivated = function() {
                console.log("stageActivated");
                cordova.fireDocumentEvent("resume");
            };
            // LunaSysMgr calls this when the windows is minimized or closed.
            window.Mojo.stageDeactivated = function() {
                console.log("stageDeactivated");
                cordova.fireDocumentEvent("pause");
            };
            // LunaSysMgr calls this when a KeepAlive app's window is hidden
            window.Mojo.hide = function() {
                console.log("hide");
            };
            // LunaSysMgr calls this when a KeepAlive app's window is shown
            window.Mojo.show = function() {
                console.log("show");
            };

            // LunaSysMgr calls this whenever an app is "launched;"
            window.Mojo.relaunch = function() {
                // need to return true to tell sysmgr the relaunch succeeded.
                // otherwise, it'll try to focus the app, which will focus the first
                // opened window of an app with multiple windows.

                var lp=JSON.parse(PalmSystem.launchParams) || {};

                if (lp['palm-command'] && lp['palm-command'] == 'open-app-menu') {
                    console.log("event:ToggleAppMenu");
                    cordova.fireDocumentEvent("menubutton");
                }

                console.log("relaunch");
                return true;
            };

            // start to listen for network connection changes
            service.Request('palm://com.palm.connectionmanager', {
                method: 'getstatus',
                parameters: { subscribe: true },
                onSuccess: function (result) {
                    console.log("subscribe:result:"+JSON.stringify(result));

                    if (!result.isInternetConnectionAvailable) {
                        if (navigator.onLine) {
                            console.log("Firing event:offline");
                            cordova.fireDocumentEvent("offline");
                        }
                    } else {
                        console.log("Firing event:online");
                        cordova.fireDocumentEvent("online");
                    }
                },
                onFailure: function(e) {
                    console.error("subscribe:error");
                }
            });

        });
    }
};
<|endoftext|>