<commit_before>var engine = typeof E === 'undefined' ? global.engine : E;

// link cache for the engine global
engine._links = {};

// create the websocket
var websocket = engine.socket = new WebSocket('ws://' + window.location.host + '/');

/**
 * Load the entrypoint module instance on socket open.
 *
 * @private
 */
websocket.onopen = function () {
    engine.module();
};

/**
 * Message handler.
 *
 * @private
 * @param {string} The message string.
 */
websocket.onmessage = function (message) {
    engine.link.message(message.data, websocket);
};

/**
 * The module class.
 *
 * @class Module
 */
var Module = {
    emit: engine.emit,
    on: engine.on,
    off: engine.off,
    _events: {}
};

// module instance cache
engine.modules = {};

/**
 * Create a new module instance.
 *
 * @public
 * @param {object} The object, which is extended with the observable methods.
 */
engine.module = function (name) {

    // check modules cache
    if (engine.modules[name]) {
        return;
    }

    // create a new empty Module
    var module = engine.clone(Module);

    // save module instance in cache to prevent overlapping requests
    if (name) {
        engine.modules[name] = module;
    }

    // module instances link cache
    module._links = {};

    // create a link
    var link = this.link('load', function (err, config) {

        // handle error
        if (err) {

            // remove module from cache
            delete engine.modules[name];

            // emit error event
            return engine.emit('moduleLoadError', err);
        }

        // load css
        config.css && engine.css(config.css);

        // load scripts
        config.scripts && engine.scripts(config.module, config.scripts, loadHandler);

        // load sub modules
        if (config.load) {
            for (var i = 0; i < config.load.length; ++i) {
                engine.module(config.load[i]);
            }
        }

        // create event object
        module._events = {};

        // save module name
        module._name = config.name || name;

        // save module name on module instance
        module._module = config.module;

        // save module instance with configured name in cache
        if (config.name && !engine.modules[config.name]) {
            engine.modules[config.name] = module;
        }
    });

    // send module instance name
    link.send(null, name);
};

/**
 * Handles the initialization order for sub modules.
 *
 * @public
 * @param {object|string} The even name or object, which is emitted.
 * @param {mixed} The following arguments are passed to the event handlers.
 */
function loadHandler (module) {
    console.log('loadHandler');
}

/**
 * Load css files.
 *
 * @public
 * @param {array} The array containing css file urls.
 */
engine.css = function (urls) {

    for (var i in urls) {
        if (!engine.csss[urls[i]]) {
            engine.csss[urls[i]] = 1;

            var link = body.createElement('link');
            link.setAttribute('rel', 'stylesheet');
            link.setAttribute('href', urls[i]);
            body.head.appendChild(link);
        }
    }
};

/**
 * Replace data fields in a string.
 *
 * @private
 * @param {string} The string.
 * @param {object} The data context.
 */
function parsePath (path, event) {
    var self = this;
    var match = path.match(find_tmpl);

    // replace route with data
    if (match) {
        for (var i = 0, value; i < match.length; ++i) {

            // get value from object
            value = self._path(match[i].replace(find_braces, ''), event);

            // replace value in route
            if (typeof value !== 'undefined') {
                path = path.replace(match[i], value);
            }
        }
    }

    return path;
}

/**
 * Parse a state search string to JSON.
 * Credentials: http://snipplr.com/view/70905/search-string-to-json/
 *
 * @private
 */
function searchToJSON(){
    var rep = {'?':'{"','=':'":"','&':'","'};
    var s = state.search.replace(/[\?\=\&]/g, function(r) {
        return rep[r];
    });
    return JSON.parse(s.length? s+'"}' : "{}");
}

/*
    // setup default as server events
    coreInstance.on('I>', send('I>'));
    coreInstance.on('V>', send('V>'));
    coreInstance.on('M>', send('M>'));

    // setup and open a websocket
    coreInstance.socket = setupWebSocket;
    coreInstance.socket();

    // load start instance when websocket is connected
    coreInstance._ws.onopen = function () {
        Z._load();
    };

    // init constructors from top to bottom
    function initConstructors (err, constructors) {

        var length = constructors.length;
        var count = 0;
        var emitReady = [];
        var initConstructorsHandler = function (init) {

            if (!init) {

                // handle ready state
                for (var i = 0; i < emitReady.length; ++i) {

                    // mark element as ready
                    emitReady[i]._ready = true;
                    emitReady[i].emit('ready');
                }

                // emit empty route
                Z.route();

                return;
            }

            // set up ovserve configs from views and modules
            if (!init[0]) {
                flow.call(init[1], init[2], true);
                return initConstructorsHandler(constructors[++count]);
            }

            // call constructor
            init[0].call(init[1], init[2] || {}, function instanceConstructorCallback (err) {

                // get instances to emit ready event after all resources are loaded
                emitReady.push(init[1]);

                // continue initialization
                initConstructorsHandler(constructors[++count]);
            });
        };

        initConstructorsHandler(constructors[count]);
    }

    // load resources in parallel
    // TODO handle loading errors
    function loader (factory, clone, config, callback, sub, viewIndex) {
        var self = this;
        var elements = 1;
        var count = 0;
        var inits = [];
        var loaderHandler = function (err, constructor, _inits) {

            // catch constructor
            if (constructor) {
                inits.unshift([constructor, clone, config.config]);
            }

            inits = inits.concat(_inits || []);

            // init or callback when all elements are ready
            if (elements === ++count) {

                // add observe events config to initialization
                if (config.flow) {
                    inits.push([null, self, config.flow]);
                }

                // pass constructors to parent
                if (sub) {
                    callback(err, null, inits);

                // init constructors
                } else if (inits.length) {
                    initConstructors(err, inits);

                // finish without constructors
                } else {
                    callback(err, clone);
                }
            }
        };

        // factory clone
        factory && factory.call(self, clone, config, viewIndex - 1);

        // skip resources
        if (!config.load && !config.scripts) {
            return loaderHandler();
        }

        // load elements
        if (config.load) {

            // update the view index
            self._vi = self._vi || 0;

            // add the number of sub elements to count
            elements += config.load.length;

            // load sub elements
            for (var i = 0, type; i < config.load.length; ++i) {
                type = config.load[i][0];

                // increment view render order index
                if (type == 'V') {
                    ++self._vi;
                }

                // call loader
                self._load(type, config.load[i][1], loaderHandler, config.module ? true : false, self._vi);
            }
        }

        // load scripts
        if (config.scripts) {
            loadJS(config.module, config.main || 0, config.scripts, loaderHandler);
        }
    }

    // set up event flow
    function flow (config, onlyObs, onlyDom, domScope, data) {
        var self = this;
        var i, e, s;
        var elms;
        var flow;
        var scope = [domScope];

        // set children as scope if there is more then one data item
        if (domScope && data.length > 1 && domScope.children) {
            scope = domScope.children;
        }

        for (i = 0; i < config.length; ++i) {
            flow = config[i];

            // handle dom event
            if (flow.selector && !onlyObs) {

                // overwrite scope with the document
                if (flow.scope == 'global') {
                    scope = [doc];
                }

                // overwrite scope with parent
                if (flow.scope == 'parent') {
                    scope = [domScope];
                }

                for (s = 0; s < scope.length; ++s) {
                    elms = flow.selector === '.' ? [scope[s]] : scope[s].querySelectorAll(flow.selector);
                    if (elms) {
                        for (e = 0; e < elms.length; ++e) {

                            elms[e].addEventListener(
                                flow['in'],
                                createHandler.call(
                                    self,
                                    flow.out,
                                    scope[s],
                                    data[s],
                                    elms,
                                    flow.dontPrevent
                                )
                            );
                        }
                    }
                }

            // handle osberver event
            } else if (!onlyDom) {

                self.on(
                    flow['in'],
                    createHandler.call(self, flow.out),
                    flow['1'],
                    flow.noRoute
                );
            }
        }
    }

    // createHandler
    function createHandler (outConfig, domScope, dataItem, elms, dontPrevent) {
        var self = this;
        var i;

        return function handler (event, _data) {
            var config;
            var key;
            var to;
            var _path;
            var _domElm;
            var i;
            var loadElms = [];
            var path;
            var data;

            // create an event object
            event = event || {};
            event.ori = event.ori || self._name;

            // extend dom event object
            if (domScope) {

                // add dom scope to event
                event._scope = event._scope || domScope;

                // dont prevent default browser actions
                if (!dontPrevent) {
                    event.preventDefault();
                }

                // add found elements to event
                event._elms = elms;

                // add index of found elements
                event._item = event._item || dataItem;
            }

            // add pathname to data object
            event._path = win_location.pathname.substr(1).split('/');

            // parse and append url search to data
            if (win_location.search && !event._search) {
                event._search = searchToJSON();
            }

            // append url hash to data
            if (win_location.hash && !event._hash) {
                event._hash = win_location.hash.substr(1);
            }

            for (i = 0; i < outConfig.length; ++i) {

                config = outConfig[i];
                to = null;

                // check if target instance exists and set new scope
                if (config.to && !(to = cache.I[config.to])) {
                    continue;
                }

                // update scope
                var eSelf = to || self;

                // copy the static data or create a new data object
                data = config.data ? JSON.parse(JSON.stringify(config.data)) : {};

                // merge argument data to data object
                if (_data) {
                    for (key in _data) {
                        data[key] = _data[key];
                    }
                }

                // add dynamic data to the data object
                if (config.set) {

                    // add data to the data object
                    for (key in config.set) {

                        // parse path
                        path = parsePath.call(self, config.set[key], event);

                        // get an attribute value from a dom element or the element itself
                        if (path[0] === '$') {
                            _domElm = path.substr(1).split(':');

                            // TODO global scope should also be an option
                            _domElm[0] = doc.querySelector(_domElm[0]);

                            // get an attribute
                            if (_domElm[1]) {
                                _domElm[1] = _domElm[0][_domElm[1]];
                            }

                            // add dom attribute value or the element itself
                            data[key] = _domElm[1] === undefined ? _domElm[0] : _domElm[1];

                        // get search value with a path: 1. events, 2. data, 3. instance, 4. window
                        } else {
                            data[key] = self._path(path, event, true) || self._path(path, data, true) || self._path(path);
                        }
                    }

                    // create deep object out of flat keys
                    data = self._deep(data);
                }

                // collect elements to load
                if (config.load) {
                    loadElms.push(config.load);
                }

                // adapt to method
                if (config.route) {
                    eSelf.route(parsePath.call(self, config.route, data), data);
                }

                // emit an event
                if (config.emit) {
                    eSelf.emit.call(eSelf, config.emit, event, data);
                }

                // call a method
                if (config.call) {

                    // find call method when the event handler is called
                    if (typeof config.call === 'string' && typeof (config.call = eSelf._path(config.call)) !== fn) {
                        return console.error(self._name + ':', 'Cannot call', eSelf._name + ':' + config.call);
                    }

                    // call method
                    config.call.call(eSelf, event, data);
                }
            }

            // load elements in parallel
            loadElms[0] && loader.call(self, null, self, {load: loadElms}, function () {});
        };
    }
*/<commit_msg>attach "link" method to the Module class<commit_after>var engine = typeof E === 'undefined' ? global.engine : E;

// link cache for the engine global
engine._links = {};

// create the websocket
var websocket = engine.socket = new WebSocket('ws://' + window.location.host + '/');

/**
 * Load the entrypoint module instance on socket open.
 *
 * @private
 */
websocket.onopen = function () {
    engine.module();
};

/**
 * Message handler.
 *
 * @private
 * @param {string} The message string.
 */
websocket.onmessage = function (message) {
    engine.link.message(message.data, websocket);
};

/**
 * The module class.
 *
 * @class Module
 */
var Module = {
    emit: engine.emit,
    on: engine.on,
    off: engine.off,
    _events: {},
    link: engine.link
};

// module instance cache
engine.modules = {};

/**
 * Create a new module instance.
 *
 * @public
 * @param {object} The object, which is extended with the observable methods.
 */
engine.module = function (name) {

    // check modules cache
    if (engine.modules[name]) {
        return;
    }

    // create a new empty Module
    var module = engine.clone(Module);

    // save module instance in cache to prevent overlapping requests
    if (name) {
        engine.modules[name] = module;
    }

    // module instances link cache
    module._links = {};

    // create a link
    var link = engine.link('load', function (err, config) {

        // handle error
        if (err) {

            // remove module from cache
            delete engine.modules[name];

            // emit error event
            return this.emit('moduleLoadError', err);
        }

        // load css
        config.css && engine.css(config.css);

        // load scripts
        config.scripts && engine.scripts(config.module, config.scripts, loadHandler);

        // load sub modules
        if (config.load) {
            for (var i = 0; i < config.load.length; ++i) {
                engine.module(config.load[i]);
            }
        }

        // create event object
        module._events = {};

        // save module name
        module._name = config.name || name;

        // save module name on module instance
        module._module = config.module;

        // save module instance with configured name in cache
        if (config.name && !engine.modules[config.name]) {
            engine.modules[config.name] = module;
        }
    });

    // send module instance name
    link.send(null, name);
};

/**
 * Handles the initialization order for sub modules.
 *
 * @public
 * @param {object|string} The even name or object, which is emitted.
 * @param {mixed} The following arguments are passed to the event handlers.
 */
function loadHandler (module) {
    console.log('loadHandler');
}

/**
 * Load css files.
 *
 * @public
 * @param {array} The array containing css file urls.
 */
engine.css = function (urls) {

    for (var i in urls) {
        if (!engine.csss[urls[i]]) {
            engine.csss[urls[i]] = 1;

            var link = body.createElement('link');
            link.setAttribute('rel', 'stylesheet');
            link.setAttribute('href', urls[i]);
            body.head.appendChild(link);
        }
    }
};

/**
 * Replace data fields in a string.
 *
 * @private
 * @param {string} The string.
 * @param {object} The data context.
 */
function parsePath (path, event) {
    var self = this;
    var match = path.match(find_tmpl);

    // replace route with data
    if (match) {
        for (var i = 0, value; i < match.length; ++i) {

            // get value from object
            value = self._path(match[i].replace(find_braces, ''), event);

            // replace value in route
            if (typeof value !== 'undefined') {
                path = path.replace(match[i], value);
            }
        }
    }

    return path;
}

/**
 * Parse a state search string to JSON.
 * Credentials: http://snipplr.com/view/70905/search-string-to-json/
 *
 * @private
 */
function searchToJSON(){
    var rep = {'?':'{"','=':'":"','&':'","'};
    var s = state.search.replace(/[\?\=\&]/g, function(r) {
        return rep[r];
    });
    return JSON.parse(s.length? s+'"}' : "{}");
}

/*
    // setup default as server events
    coreInstance.on('I>', send('I>'));
    coreInstance.on('V>', send('V>'));
    coreInstance.on('M>', send('M>'));

    // setup and open a websocket
    coreInstance.socket = setupWebSocket;
    coreInstance.socket();

    // load start instance when websocket is connected
    coreInstance._ws.onopen = function () {
        Z._load();
    };

    // init constructors from top to bottom
    function initConstructors (err, constructors) {

        var length = constructors.length;
        var count = 0;
        var emitReady = [];
        var initConstructorsHandler = function (init) {

            if (!init) {

                // handle ready state
                for (var i = 0; i < emitReady.length; ++i) {

                    // mark element as ready
                    emitReady[i]._ready = true;
                    emitReady[i].emit('ready');
                }

                // emit empty route
                Z.route();

                return;
            }

            // set up ovserve configs from views and modules
            if (!init[0]) {
                flow.call(init[1], init[2], true);
                return initConstructorsHandler(constructors[++count]);
            }

            // call constructor
            init[0].call(init[1], init[2] || {}, function instanceConstructorCallback (err) {

                // get instances to emit ready event after all resources are loaded
                emitReady.push(init[1]);

                // continue initialization
                initConstructorsHandler(constructors[++count]);
            });
        };

        initConstructorsHandler(constructors[count]);
    }

    // load resources in parallel
    // TODO handle loading errors
    function loader (factory, clone, config, callback, sub, viewIndex) {
        var self = this;
        var elements = 1;
        var count = 0;
        var inits = [];
        var loaderHandler = function (err, constructor, _inits) {

            // catch constructor
            if (constructor) {
                inits.unshift([constructor, clone, config.config]);
            }

            inits = inits.concat(_inits || []);

            // init or callback when all elements are ready
            if (elements === ++count) {

                // add observe events config to initialization
                if (config.flow) {
                    inits.push([null, self, config.flow]);
                }

                // pass constructors to parent
                if (sub) {
                    callback(err, null, inits);

                // init constructors
                } else if (inits.length) {
                    initConstructors(err, inits);

                // finish without constructors
                } else {
                    callback(err, clone);
                }
            }
        };

        // factory clone
        factory && factory.call(self, clone, config, viewIndex - 1);

        // skip resources
        if (!config.load && !config.scripts) {
            return loaderHandler();
        }

        // load elements
        if (config.load) {

            // update the view index
            self._vi = self._vi || 0;

            // add the number of sub elements to count
            elements += config.load.length;

            // load sub elements
            for (var i = 0, type; i < config.load.length; ++i) {
                type = config.load[i][0];

                // increment view render order index
                if (type == 'V') {
                    ++self._vi;
                }

                // call loader
                self._load(type, config.load[i][1], loaderHandler, config.module ? true : false, self._vi);
            }
        }

        // load scripts
        if (config.scripts) {
            loadJS(config.module, config.main || 0, config.scripts, loaderHandler);
        }
    }

    // set up event flow
    function flow (config, onlyObs, onlyDom, domScope, data) {
        var self = this;
        var i, e, s;
        var elms;
        var flow;
        var scope = [domScope];

        // set children as scope if there is more then one data item
        if (domScope && data.length > 1 && domScope.children) {
            scope = domScope.children;
        }

        for (i = 0; i < config.length; ++i) {
            flow = config[i];

            // handle dom event
            if (flow.selector && !onlyObs) {

                // overwrite scope with the document
                if (flow.scope == 'global') {
                    scope = [doc];
                }

                // overwrite scope with parent
                if (flow.scope == 'parent') {
                    scope = [domScope];
                }

                for (s = 0; s < scope.length; ++s) {
                    elms = flow.selector === '.' ? [scope[s]] : scope[s].querySelectorAll(flow.selector);
                    if (elms) {
                        for (e = 0; e < elms.length; ++e) {

                            elms[e].addEventListener(
                                flow['in'],
                                createHandler.call(
                                    self,
                                    flow.out,
                                    scope[s],
                                    data[s],
                                    elms,
                                    flow.dontPrevent
                                )
                            );
                        }
                    }
                }

            // handle osberver event
            } else if (!onlyDom) {

                self.on(
                    flow['in'],
                    createHandler.call(self, flow.out),
                    flow['1'],
                    flow.noRoute
                );
            }
        }
    }

    // createHandler
    function createHandler (outConfig, domScope, dataItem, elms, dontPrevent) {
        var self = this;
        var i;

        return function handler (event, _data) {
            var config;
            var key;
            var to;
            var _path;
            var _domElm;
            var i;
            var loadElms = [];
            var path;
            var data;

            // create an event object
            event = event || {};
            event.ori = event.ori || self._name;

            // extend dom event object
            if (domScope) {

                // add dom scope to event
                event._scope = event._scope || domScope;

                // dont prevent default browser actions
                if (!dontPrevent) {
                    event.preventDefault();
                }

                // add found elements to event
                event._elms = elms;

                // add index of found elements
                event._item = event._item || dataItem;
            }

            // add pathname to data object
            event._path = win_location.pathname.substr(1).split('/');

            // parse and append url search to data
            if (win_location.search && !event._search) {
                event._search = searchToJSON();
            }

            // append url hash to data
            if (win_location.hash && !event._hash) {
                event._hash = win_location.hash.substr(1);
            }

            for (i = 0; i < outConfig.length; ++i) {

                config = outConfig[i];
                to = null;

                // check if target instance exists and set new scope
                if (config.to && !(to = cache.I[config.to])) {
                    continue;
                }

                // update scope
                var eSelf = to || self;

                // copy the static data or create a new data object
                data = config.data ? JSON.parse(JSON.stringify(config.data)) : {};

                // merge argument data to data object
                if (_data) {
                    for (key in _data) {
                        data[key] = _data[key];
                    }
                }

                // add dynamic data to the data object
                if (config.set) {

                    // add data to the data object
                    for (key in config.set) {

                        // parse path
                        path = parsePath.call(self, config.set[key], event);

                        // get an attribute value from a dom element or the element itself
                        if (path[0] === '$') {
                            _domElm = path.substr(1).split(':');

                            // TODO global scope should also be an option
                            _domElm[0] = doc.querySelector(_domElm[0]);

                            // get an attribute
                            if (_domElm[1]) {
                                _domElm[1] = _domElm[0][_domElm[1]];
                            }

                            // add dom attribute value or the element itself
                            data[key] = _domElm[1] === undefined ? _domElm[0] : _domElm[1];

                        // get search value with a path: 1. events, 2. data, 3. instance, 4. window
                        } else {
                            data[key] = self._path(path, event, true) || self._path(path, data, true) || self._path(path);
                        }
                    }

                    // create deep object out of flat keys
                    data = self._deep(data);
                }

                // collect elements to load
                if (config.load) {
                    loadElms.push(config.load);
                }

                // adapt to method
                if (config.route) {
                    eSelf.route(parsePath.call(self, config.route, data), data);
                }

                // emit an event
                if (config.emit) {
                    eSelf.emit.call(eSelf, config.emit, event, data);
                }

                // call a method
                if (config.call) {

                    // find call method when the event handler is called
                    if (typeof config.call === 'string' && typeof (config.call = eSelf._path(config.call)) !== fn) {
                        return console.error(self._name + ':', 'Cannot call', eSelf._name + ':' + config.call);
                    }

                    // call method
                    config.call.call(eSelf, event, data);
                }
            }

            // load elements in parallel
            loadElms[0] && loader.call(self, null, self, {load: loadElms}, function () {});
        };
    }
*/<|endoftext|>


<commit_before>import Ember from 'ember';
import module from '../helpers/module-for-db';
import { test } from '../helpers/qunit';

const {
  run,
  RSVP: { all }
} = Ember;

const ddoc = {
  views: {
    'all': {
      map(doc) {
        /* global emit */
        emit(doc._id);
      }
    }
  }
};

let _opts = {
  ddoc: 'main',
  view: 'all',
  limit: 3,
  start: null,
  end: {}
};

module('paginated-loader', {
  async beforeEach() {
    this.owner = Ember.Object.create({ id: null });
    this.opts = {
      owner: [ 'id' ],
      query(owner, state) {
        let opts = _opts;

        let ddoc = opts.ddoc;
        let view = opts.view;
        let limit = opts.limit + 1;
        let skip;
        let startkey;
        let startkey_docid;
        let endkey = opts.end;

        if(state) {
          skip = 1;
          startkey_docid = state.id;
          startkey = state.value;
          skip = 1;
        } else {
          startkey = opts.start;
        }

        return {
          ddoc,
          view,
          skip,
          limit,
          startkey,
          startkey_docid,
          endkey
        };
      },
      didLoad(array) {
        let opts = _opts;

        let length = array.get('length');
        let last = array.get('lastObject');

        let isMore = false;
        let state = null;

        if(last) {
          let id = last.getId();
          state = {
            id: id,
            value: id
          };
          isMore = length > opts.limit;
        }

        return {
          isMore,
          state
        };
      }
    };
    this.insert = (count=10) => {
      let promises = [];
      for(let i = 0; i < count; i++) {
        promises.push(this.docs.save({ _id: `duck:${i}` }));
      }
      return all(promises);
    }
    this.loader = () => this.db._createInternalPaginatedLoader(this.owner, this.opts).model(true);
    this.settle = loader => loader._internal.settle();
    this.design = () => this.docs.get('design').save('main', ddoc);
  }
});

test('it exists', function(assert) {
  this.opts.autoload = false;

  let loader = this.loader();
  assert.ok(loader);

  run(() => loader.destroy());
});

test.skip('pagination logic', async function(assert) {
  await this.recreate();
  await all([ this.design(), this.insert() ]);

  let opts = {
    start: null,
    end: {},
    limit: 3
  };

  let state = null;

  let load = async (expectedIds, expectedState) => {
    let startkey;
    let startkey_docid;
    let skip;

    if(state) {
      startkey_docid = state.startkey_docid;
      startkey = state.value;
      skip = 1;
    } else {
      startkey = opts.start;
    }

    let limit = opts.limit + 1;
    let endkey = opts.end;

    let json = await this.docs.view('main', 'all', { skip, limit, endkey, startkey_docid, startkey, include_docs: true });
    let docs = json.rows.map(row => row.doc);

    if(docs.length > 0) {
      let last = docs[docs.length - 1];
      state = {
        startkey_docid: last._id,
        value: last._id,
        isMoreAvailable: docs.length > opts.limit
      };
    } else {
      state = {
        isMoreAvailable: false
      };
    }

    assert.deepEqual(docs.map(doc => doc._id), expectedIds);
    assert.deepEqual(state, expectedState);
  };

  let tap = this.tap();

  await load([
    "duck:0",
    "duck:1",
    "duck:2",
    "duck:3"
  ], {
    "isMoreAvailable": true,
    "startkey_docid": "duck:3",
    "value": "duck:3"
  });

  await load([
    "duck:4",
    "duck:5",
    "duck:6",
    "duck:7"
  ], {
    "isMoreAvailable": true,
    "startkey_docid": "duck:7",
    "value": "duck:7"
  });

  await load([
    "duck:8",
    "duck:9"
  ], {
    "isMoreAvailable": false,
    "startkey_docid": "duck:9",
    "value": "duck:9"
  });

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?startkey=\"duck:3\"&endkey={}&include_docs=true&startkey_docid=duck:3&limit=4&skip=1",
    "GET _design/main/_view/all?startkey=\"duck:7\"&endkey={}&include_docs=true&startkey_docid=duck:7&limit=4&skip=1"
  ]);

});

test('load first page', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": false,
    "isLoading": false,
    "isMore": false
  });

  let promise = loader.load();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": false,
    "isLoading": true,
    "isMore": false
  });

  await promise;

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": true,
    "isLoading": false,
    "isMore": true
  });

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('load all pages', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  await loader.loadMore();
  await loader.loadMore();
  await loader.loadMore();
  await loader.loadMore(); // extra

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?startkey=\"duck:3\"&endkey={}&include_docs=true&startkey_docid=duck:3&limit=4&skip=1",
    "GET _design/main/_view/all?startkey=\"duck:7\"&endkey={}&include_docs=true&startkey_docid=duck:7&limit=4&skip=1"
  ]);

  run(() => loader.destroy());
});

test('reload', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  await loader.reload();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('reload with few loaded pages', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  await loader.loadMore();
  await loader.loadMore();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?startkey=\"duck:3\"&endkey={}&include_docs=true&startkey_docid=duck:3&limit=4&skip=1"
  ]);

  tap.clear();

  await loader.reload();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  tap.clear();

  await loader.load();

  assert.deepEqual(tap.urls, []);

  run(() => loader.destroy());
});

test('parallel load and loadMore', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  loader.load();
  loader.loadMore();

  await loader._internal.settle();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('parallel loadMore and reload', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  await loader.load();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": true,
    "isLoading": false,
    "isMore": true
  });

  loader.loadMore();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": true,
    "isLoading": true,
    "isMore": true
  });

  loader.reload();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": false,
    "isLoading": true,
    "isMore": false
  });

  await loader._internal.settle();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": true,
    "isLoading": false,
    "isMore": true
  });

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?startkey=\"duck:3\"&endkey={}&include_docs=true&startkey_docid=duck:3&limit=4&skip=1",
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('autoload for state query', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  let loader = this.loader();

  assert.equal(loader._internal.state.isLoading, false);
  assert.equal(loader.get('isLoading'), true);
  assert.equal(loader._internal.operations.length, 1);

  await loader._internal.settle();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('autoload on owner property change', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  let loader = this.loader();

  this.owner.set('id', 'one');
  this.owner.set('id', 'two');

  await loader._internal.settle();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('autoload resets load state', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  let loader = this.loader();

  await loader.load();
  await loader.loadMore();

  this.owner.set('id', 'one');
  this.owner.set('id', 'two');

  await loader._internal.settle();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?startkey=\"duck:3\"&endkey={}&include_docs=true&startkey_docid=duck:3&limit=4&skip=1",
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});
<commit_msg>remove skipped pagination logic test<commit_after>import Ember from 'ember';
import module from '../helpers/module-for-db';
import { test } from '../helpers/qunit';

const {
  run,
  RSVP: { all }
} = Ember;

const ddoc = {
  views: {
    'all': {
      map(doc) {
        /* global emit */
        emit(doc._id);
      }
    }
  }
};

let _opts = {
  ddoc: 'main',
  view: 'all',
  limit: 3,
  start: null,
  end: {}
};

module('paginated-loader', {
  async beforeEach() {
    this.owner = Ember.Object.create({ id: null });
    this.opts = {
      owner: [ 'id' ],
      query(owner, state) {
        let opts = _opts;

        let ddoc = opts.ddoc;
        let view = opts.view;
        let limit = opts.limit + 1;
        let skip;
        let startkey;
        let startkey_docid;
        let endkey = opts.end;

        if(state) {
          skip = 1;
          startkey_docid = state.id;
          startkey = state.value;
          skip = 1;
        } else {
          startkey = opts.start;
        }

        return {
          ddoc,
          view,
          skip,
          limit,
          startkey,
          startkey_docid,
          endkey
        };
      },
      didLoad(array) {
        let opts = _opts;

        let length = array.get('length');
        let last = array.get('lastObject');

        let isMore = false;
        let state = null;

        if(last) {
          let id = last.getId();
          state = {
            id: id,
            value: id
          };
          isMore = length > opts.limit;
        }

        return {
          isMore,
          state
        };
      }
    };
    this.insert = (count=10) => {
      let promises = [];
      for(let i = 0; i < count; i++) {
        promises.push(this.docs.save({ _id: `duck:${i}` }));
      }
      return all(promises);
    }
    this.loader = () => this.db._createInternalPaginatedLoader(this.owner, this.opts).model(true);
    this.settle = loader => loader._internal.settle();
    this.design = () => this.docs.get('design').save('main', ddoc);
  }
});

test('it exists', function(assert) {
  this.opts.autoload = false;

  let loader = this.loader();
  assert.ok(loader);

  run(() => loader.destroy());
});

test('load first page', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": false,
    "isLoading": false,
    "isMore": false
  });

  let promise = loader.load();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": false,
    "isLoading": true,
    "isMore": false
  });

  await promise;

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": true,
    "isLoading": false,
    "isMore": true
  });

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('load all pages', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  await loader.loadMore();
  await loader.loadMore();
  await loader.loadMore();
  await loader.loadMore(); // extra

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?startkey=\"duck:3\"&endkey={}&include_docs=true&startkey_docid=duck:3&limit=4&skip=1",
    "GET _design/main/_view/all?startkey=\"duck:7\"&endkey={}&include_docs=true&startkey_docid=duck:7&limit=4&skip=1"
  ]);

  run(() => loader.destroy());
});

test('reload', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  await loader.reload();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('reload with few loaded pages', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  await loader.loadMore();
  await loader.loadMore();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?startkey=\"duck:3\"&endkey={}&include_docs=true&startkey_docid=duck:3&limit=4&skip=1"
  ]);

  tap.clear();

  await loader.reload();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  tap.clear();

  await loader.load();

  assert.deepEqual(tap.urls, []);

  run(() => loader.destroy());
});

test('parallel load and loadMore', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  loader.load();
  loader.loadMore();

  await loader._internal.settle();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('parallel loadMore and reload', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  this.opts.autoload = false;
  let loader = this.loader();

  await loader.load();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": true,
    "isLoading": false,
    "isMore": true
  });

  loader.loadMore();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": true,
    "isLoading": true,
    "isMore": true
  });

  loader.reload();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": false,
    "isLoading": true,
    "isMore": false
  });

  await loader._internal.settle();

  assert.deepEqual(loader.get('state'), {
    "error": null,
    "isError": false,
    "isLoaded": true,
    "isLoading": false,
    "isMore": true
  });

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?startkey=\"duck:3\"&endkey={}&include_docs=true&startkey_docid=duck:3&limit=4&skip=1",
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('autoload for state query', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  let loader = this.loader();

  assert.equal(loader._internal.state.isLoading, false);
  assert.equal(loader.get('isLoading'), true);
  assert.equal(loader._internal.operations.length, 1);

  await loader._internal.settle();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('autoload on owner property change', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  let loader = this.loader();

  this.owner.set('id', 'one');
  this.owner.set('id', 'two');

  await loader._internal.settle();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});

test('autoload resets load state', async function(assert) {
  await this.recreate();
  await all([ this.insert(), this.design() ]);
  let tap = this.tap();
  let loader = this.loader();

  await loader.load();
  await loader.loadMore();

  this.owner.set('id', 'one');
  this.owner.set('id', 'two');

  await loader._internal.settle();

  assert.deepEqual(tap.urls, [
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?startkey=\"duck:3\"&endkey={}&include_docs=true&startkey_docid=duck:3&limit=4&skip=1",
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4",
    "GET _design/main/_view/all?endkey={}&include_docs=true&limit=4"
  ]);

  run(() => loader.destroy());
});
<|endoftext|>


<commit_before>/**
 * Diagnostic Factory
 *
 * @description description
 */
(function() {
  'use strict';

  angular
    .module('starter.services')
    .factory('DiagnosticService', DiagnosticService);

  DiagnosticService.$inject = ['devUtils', 'logger'];

  function DiagnosticService(devUtils, logger) {
  	return {
  		getCachedFlag : getCachedFlag,

  		getRecentLogs: getRecentLogs,

  		testVfRemote: testVfRemote
	  };

	  /**
	   * Returns whether or not we have a "cached" event in the cacheSoup
	   * @return {boolean}
	   */
	  function getCachedFlag() {
	    return new Promise(function(resolve, reject) {
	    	devUtils.readRecords("cacheSoup").then(function(result){
	    		if ( _.findWhere(result.records, {"Description": "cached"})) {
		    		resolve("True");
		    	} else {
		    		resolve("False");
		    	}
	    	}).catch(function(e){
	    		logger.error(e);
	    		reject(e);
	    	});
	    });
	  }

	  /**
	   * Returns latest entries in Mobile_Log__mc table. If entry is a JSON string
	   * 	then we format it too.
	   * @param  {integer} count Number of entries to return
	   * @return {array}       Array of Mobile Log entries
	   */
	  function getRecentLogs(count) {
	    return new Promise(function(resolve, reject) {
	    	devUtils.readRecords("Mobile_Log__mc").then(function(result){
	    		var latestLogs = _.chain(result.records)
  					.sortBy("_soupLastModifiedDate")
  					.reverse()
  					.first(count)
  					.value();
  				latestLogs.forEach(function(logObj){
  					logObj.errorObj = [];
  					// Check if we have a JSON string
  					try {
  						var textJson = JSON.parse(logObj.mobilecaddy1__Error_Text__c);
  						for (var prop in textJson) {
							 logObj.errorObj.push(JSON.stringify(textJson[prop]));
							}
							logObj.mobilecaddy1__Error_Text__c = '';
  					} catch  (e) {
  						// OK, carry on
  					}
  				});
	    		resolve(latestLogs);
		    }).catch(function(e){
	    		logger.error(e);
	    		reject(e);
	    	});
		  });
	  }

	  /**
	   * @function testVfRemote
	   * @description Does a low-level call to the MobileCaddy heartbeat
	   * @return {object}
	   */
	  function testVfRemote() {
	    return new Promise(function(resolve, reject) {
	    	try {
	    	Visualforce.remoting.Manager.invokeAction('mobilecaddy1.MobileCaddyStartPageController001_mc.heartBeat',
					function(result, event) {
						resolve({result: result, event: event});
					},
					{escape:false,timeout:30000});
		    } catch (e) {
		    	reject(e);
		    }
	    });
	  }

  }

})();<commit_msg>Removing Analytics log from diagnostics view<commit_after>/**
 * Diagnostic Factory
 *
 * @description description
 */
(function() {
  'use strict';

  angular
    .module('starter.services')
    .factory('DiagnosticService', DiagnosticService);

  DiagnosticService.$inject = ['devUtils', 'logger'];

  function DiagnosticService(devUtils, logger) {
  	return {
  		getCachedFlag : getCachedFlag,

  		getRecentLogs: getRecentLogs,

  		testVfRemote: testVfRemote
	  };

	  /**
	   * Returns whether or not we have a "cached" event in the cacheSoup
	   * @return {boolean}
	   */
	  function getCachedFlag() {
	    return new Promise(function(resolve, reject) {
	    	devUtils.readRecords("cacheSoup").then(function(result){
	    		if ( _.findWhere(result.records, {"Description": "cached"})) {
		    		resolve("True");
		    	} else {
		    		resolve("False");
		    	}
	    	}).catch(function(e){
	    		logger.error(e);
	    		reject(e);
	    	});
	    });
	  }

	  /**
	   * Returns latest entries in Mobile_Log__mc table. If entry is a JSON string
	   * 	then we format it too.
	   * @param  {integer} count Number of entries to return
	   * @return {array}       Array of Mobile Log entries
	   */
	  function getRecentLogs(count) {
	    return new Promise(function(resolve, reject) {
	    	devUtils.readRecords("Mobile_Log__mc").then(function(result){
	    		var latestLogs = _.chain(result.records)
  					.sortBy("_soupLastModifiedDate")
  					.reverse()
  					.first(count)
  					.value();
  				var ll2 = latestLogs.filter(function(logObj){
						return ( logObj.mobilecaddy1__Log_Type__c != "analytic" );
  				});
  				ll2.map(function(logObj){
  					logObj.errorObj = [];
  					// Check if we have a JSON string
  					try {
  						var textJson = JSON.parse(logObj.mobilecaddy1__Error_Text__c);
  						for (var prop in textJson) {
							 logObj.errorObj.push(JSON.stringify(textJson[prop]));
							}
							logObj.mobilecaddy1__Error_Text__c = '';
  					} catch  (e) {
  						// OK, carry on
  					}
  					return logObj;
  				});
	    		resolve(ll2);
		    }).catch(function(e){
	    		logger.error(e);
	    		reject(e);
	    	});
		  });
	  }

	  /**
	   * @function testVfRemote
	   * @description Does a low-level call to the MobileCaddy heartbeat
	   * @return {object}
	   */
	  function testVfRemote() {
	    return new Promise(function(resolve, reject) {
	    	try {
	    	Visualforce.remoting.Manager.invokeAction('mobilecaddy1.MobileCaddyStartPageController001_mc.heartBeat',
					function(result, event) {
						resolve({result: result, event: event});
					},
					{escape:false,timeout:30000});
		    } catch (e) {
		    	reject(e);
		    }
	    });
	  }

  }

})();<|endoftext|>


<commit_before>// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
//
// Licensed 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.

/** @fileoverview A XHR client. */

goog.provide('webdriver.http.XhrClient');

goog.require('goog.json');
goog.require('goog.net.XmlHttp');
goog.require('webdriver.http.Response');

/**
 * A HTTP client that sends requests using XMLHttpRequests.
 * @param {string} url URL for the WebDriver server to send commands to.
 * @constructor
 * @implements {webdriver.http.Client}
 */
webdriver.http.XhrClient = function(url) {

  /**
   * URL for the WebDriver server to send commands to.
   * @type {string}
   * @private
   */
  this.url_ = url;
};

/** @override */
webdriver.http.XhrClient.prototype.send = function(request, callback) {
  try {
    var xhr = new goog.net.XmlHttp;
    var url = this.url_ + request.path;
    xhr.open(request.method, url, true);

    xhr.onload = function() {
      callback(null, webdriver.http.Response.fromXmlHttpRequest(xhr));
    };

    xhr.onerror = function() {
      callback(Error([
        'Unable to send request: ', request.method, ' ', url,
        '\nOriginal request:\n', request
      ].join('')));
    };

    for (var header in request.headers) {
      xhr.setRequestHeader(header, request.headers[header]);
    }

    xhr.send(goog.json.serialize(request.data));
  } catch (ex) {
    callback(ex);
  }
};
<commit_msg>JasonLeyba: Fixing type check error flagged by the closure compiler.<commit_after>// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
//
// Licensed 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.

/** @fileoverview A XHR client. */

goog.provide('webdriver.http.XhrClient');

goog.require('goog.json');
goog.require('goog.net.XmlHttp');
goog.require('webdriver.http.Response');

/**
 * A HTTP client that sends requests using XMLHttpRequests.
 * @param {string} url URL for the WebDriver server to send commands to.
 * @constructor
 * @implements {webdriver.http.Client}
 */
webdriver.http.XhrClient = function(url) {

  /**
   * URL for the WebDriver server to send commands to.
   * @type {string}
   * @private
   */
  this.url_ = url;
};

/** @override */
webdriver.http.XhrClient.prototype.send = function(request, callback) {
  try {
    var xhr = goog.net.XmlHttp();
    var url = this.url_ + request.path;
    xhr.open(request.method, url, true);

    xhr.onload = function() {
      callback(null, webdriver.http.Response.fromXmlHttpRequest(xhr));
    };

    xhr.onerror = function() {
      callback(Error([
        'Unable to send request: ', request.method, ' ', url,
        '\nOriginal request:\n', request
      ].join('')));
    };

    for (var header in request.headers) {
      xhr.setRequestHeader(header, request.headers[header]);
    }

    xhr.send(goog.json.serialize(request.data));
  } catch (ex) {
    callback(ex);
  }
};
<|endoftext|>


<commit_before>var CloudCommander, CloudFunc;
/* object contains viewer FuncyBox
 * https://github.com/fancyapps/fancyBox
 */
CloudCommander.Viewer = {};
CloudCommander.Viewer.FuncyBox = {
    load: function(){

        CloudCommander.cssLoad({
                src : 'http://fancyapps.com/fancybox/source/jquery.fancybox.css'
        });

        CloudCommander.jsload(
            'http://fancyapps.com/fancybox/source/jquery.fancybox.pack.js',
            function(){
                $(".fancybox").fancybox({
                    openEffect	: 'none',
                    closeEffect	: 'none'
                });
            });

    },
    show: function(){
        try{
            /* get current panel (left or right) */
            var lPanel = this.getByClass('current-file');

            lPanel.length &&
                (lPanel = lPanel[0].parentElement);

            /* get all file links */
            var lA = lPanel.getElementsByTagName('a');

            var lName;
            for (var i; i < lA.length; i++) {
                lName = lA[i].title || lA[i].textContent;

                CloudFunc.checkExtension(lName,'png') &&
                    (lA[i].className = 'fancybox');
            }
        }catch(pError){
            console.log(pError);
        }
    },
    getById     : function(pId){return document.getElementById(pId);},
    getByClass  : function(pClass){
        return document.getElementsByClassName(pClass);
    }
};<commit_msg>minor changes<commit_after>var CloudCommander, CloudFunc;
/* object contains viewer FuncyBox
 * https://github.com/fancyapps/fancyBox
 */
CloudCommander.Viewer = {};
CloudCommander.Viewer.FuncyBox = {
    load: function(){

        CloudCommander.cssLoad({
                src : 'http://fancyapps.com/fancybox/source/jquery.fancybox.css'
        });

        CloudCommander.jsload(
            'http://fancyapps.com/fancybox/source/jquery.fancybox.pack.js',
            function(){
                $(".fancybox").fancybox({
                    openEffect	: 'none',
                    closeEffect	: 'none'
                });
            });

    },
    show: function(){
        try{
            /* get current panel (left or right) */
            var lPanel = this.getByClass('current-file');

            lPanel.length &&
                (lPanel = lPanel[0].parentElement);

            /* get all file links */
            var lA = lPanel.getElementsByTagName('a');

            var lName;
            for (var i=0; i < lA.length; i++) {
                lName = lA[i].title || lA[i].textContent;

                CloudFunc.checkExtension(lName,'png') &&
                    (lA[i].className = 'fancybox');
            }
        }catch(pError){
            console.log(pError);
        }
    },
    getById     : function(pId){return document.getElementById(pId);},
    getByClass  : function(pClass){
        return document.getElementsByClassName(pClass);
    }
};<|endoftext|>


<commit_before>import Ember from 'ember';
import MultitonIdsMixin from 'ember-theater/mixins/ember-theater/multiton-ids';

const {
  computed,
  get
} = Ember;

export default Ember.Object.extend(MultitonIdsMixin, {
  subscribers: computed(() => Ember.A()),

  register(subscriber) {
    const subscribers = get(this, 'subscribers');

    subscribers.removeObject(subscriber);
    subscribers.pushObject(subscriber);
  },

  unregister(subscriber) {
    get(this, 'subscribers').removeObject(subscriber);
  },

  publish(name, ...messages) {
    get(this, 'subscribers').forEach((subscriber) => {
      subscriber.trigger(name, ...messages);
    })
  }
});
<commit_msg>remove message bug multiton<commit_after><|endoftext|>


<commit_before>/**
    * Created by kevin on 24/10/2015 for PodcastServer
    */

export default class SearchItemCache {
    constructor(DefaultItemSearchParameters, $sessionStorage) {
        "ngInject";
        this.$sessionStorage = $sessionStorage;
        this.$sessionStorage.searchParameters = DefaultItemSearchParameters;
    }

    getParameters() {
        return this.$sessionStorage.searchParameters;
    }

    page(pageNumber) {
        if (angular.isNumber(pageNumber)) {
            this.$sessionStorage.searchParameters.page = pageNumber;
        }

        return this.$sessionStorage.searchParameters.page;
    }

    size(sizeNumber) {
        if (angular.isNumber(sizeNumber)) {
            this.$sessionStorage.searchParameters.size = sizeNumber;
        }

        return this.$sessionStorage.searchParameters.size;
    }

    updateSearchParam(searchParam) {
        this.$sessionStorage.searchParameters.term = searchParam.term;
        this.$sessionStorage.searchParameters.tags = searchParam.tags;
        this.$sessionStorage.searchParameters.direction =  searchParam.direction;
        this.$sessionStorage.searchParameters.properties =  searchParam.properties;
        this.$sessionStorage.searchParameters.downloaded = searchParam.downloaded;
    }
}<commit_msg>refactor(search): eslint improvement<commit_after>/**
    * Created by kevin on 24/10/2015 for PodcastServer
    */
import angular from 'angular';

export default class SearchItemCache {
    constructor(DefaultItemSearchParameters, $sessionStorage) {
        "ngInject";
        this.$sessionStorage = $sessionStorage;
        this.$sessionStorage.searchParameters = DefaultItemSearchParameters;
    }

    getParameters() {
        return this.$sessionStorage.searchParameters;
    }

    page(pageNumber) {
        if (angular.isNumber(pageNumber)) {
            this.$sessionStorage.searchParameters.page = pageNumber;
        }

        return this.$sessionStorage.searchParameters.page;
    }

    size(sizeNumber) {
        if (angular.isNumber(sizeNumber)) {
            this.$sessionStorage.searchParameters.size = sizeNumber;
        }

        return this.$sessionStorage.searchParameters.size;
    }

    updateSearchParam(searchParam) {
        this.$sessionStorage.searchParameters.term = searchParam.term;
        this.$sessionStorage.searchParameters.tags = searchParam.tags;
        this.$sessionStorage.searchParameters.direction =  searchParam.direction;
        this.$sessionStorage.searchParameters.properties =  searchParam.properties;
        this.$sessionStorage.searchParameters.downloaded = searchParam.downloaded;
    }
}<|endoftext|>


<commit_before>var CloudCommander, CloudFunc, $;
/* object contains viewer FancyBox
 * https://github.com/fancyapps/fancyBox
 */
(function(){
    var cloudcmd                        = CloudCommander;
    var Util                            = cloudcmd.Util;
    var KeyBinding                      = cloudcmd.KeyBinding;

    CloudCommander.Viewer               = {
        get: (function(){
            return this.FancyBox;
        })
    };

    var FancyBox                        = {};

    cloudcmd.Viewer.dir                 = './lib/client/viewer/';
    FancyBox.dir                        = cloudcmd.Viewer.dir + 'fancybox/';

    /* function return configureation
     * for FancyBox open and
     * onclick (it shoud be
     * different objects)
     */
    FancyBox.getConfig                  = (function(){
        return{
            /* function do her work
             * before FauncyBox shows
             */
            beforeShow  : function(){
                KeyBinding.unSet();
            },

            afterShow   : function(){
                var lEditor = Util.getById('CloudViewer');
                if(lEditor)
                    lEditor.focus();
            },

            beforeClose : function(){
                KeyBinding.set();
            },

            openEffect      : 'none',
            closeEffect     : 'none',
            autoSize        : false,
            height          : window.innerHeight,
            helpers : {
                overlay : {
                    opacity: 0.1,
                    css : {
                        'background-color'  : '#fff'
                    }
                }
            },
            padding : 0
        };
    });

    /* function loads css and js of FancyBox
     * @pParent     - this
     * @pCallBack   -  executes, when everything loaded
     */
    FancyBox.load                       = (function(pThis, pCallBack){
        Util.jqueryLoad();

        var ljsLoad_f = function(){
            var lSrc = pThis.dir + 'jquery.fancybox.js';
            Util.jsload(lSrc,{
                    onload: pCallBack
            });
        };

        Util.cssSet({id:'viewer',
                inner : '#CloudViewer{'                         +
                            'font-size: 16px;'                  +
                            'white-space :pre'                  +
                        '}'                                     +
                        '#CloudViewer::selection{'              +
                                'background: #fe57a1;'          +
                                'color: #fff;'                  +
                                'text-shadow: none;'            +
                        '}'
        });

        var lSrc = pThis.dir +'jquery.fancybox.pack.css';
        Util.cssLoad({
            src  : lSrc,
            func : {
                onload: ljsLoad_f
            }
        });
    });

    FancyBox.loadData                   = (function(pA){
        Util.Images.showLoad();

        var lThis = this;
        var lConfig = this.getConfig();

        var lLink = pA.href;

        /* убираем адрес хоста*/
        lLink = '/' + lLink.replace(document.location.href,'');

        if (lLink.indexOf(CloudFunc.NOJS) === CloudFunc.FS.length)
            lLink = lLink.replace(CloudFunc.NOJS, '');

        Util.ajax({
            url     : lLink,
             error  : (function(jqXHR, textStatus, errorThrown){
                 lThis.loading             = false;
                 return Util.Images.showError(jqXHR, textStatus, errorThrown);
             }),

             success:function(data, textStatus, jqXHR){
                /* if we got json - show it */
                if(typeof data === 'object')
                    data = JSON.stringify(data, null, 4);

                $.fancybox('
' + data + '
', lConfig);

                Util.Images.hideLoad();
            }
        });
    });

    FancyBox.set                        = (function(){
        if(Util.getByClass('fancybox').length)
            return;
        try{
            /* get current panel (left or right) */
            var lPanel = Util.getPanel();

            /* get all file links */
            var lA = Util.getByTag('a', lPanel);

            var lThis = this;

            var lDblClick_f = function(pA){
                return function(){
                    var lConfig  = lThis.getConfig();
                    lConfig.href = pA.href;
                    if(pA.rel)
                        $.fancybox(lConfig);
                    else
                        lThis.loadData(pA);
                };
            };

            /* first two is not files nor folders*/
            for (var i=2; i < lA.length; i++) {
                var lName = lA[i].title || lA[i].textContent;

                lA[i].className    = 'fancybox';
                if(CloudFunc.checkExtension(lName, ['png','jpg', 'gif','ico'])){
                    lA[i].rel          = 'gallery';
                }

                lA[i].ondblclick = lDblClick_f(lA[i]);
            }

        }catch(pError){
            console.log(pError);
        }
    });

    FancyBox.show                       = (function(pCurrentFile){
        FancyBox.set();

        var lConfig = this.getConfig();

        if(typeof pCurrentFile !== 'function'){
            var lA = Util.getByClass('fancybox', pCurrentFile)[0];
            if(lA){
                if(lA.rel)
                    $.fancybox.open({ href : lA.href },
                        lConfig);
                else this.loadData(lA);
            }
        }
        else {
            var lFunc_f = pCurrentFile;
            lFunc_f();
        }

        Util.Images.hideLoad();
    });

    cloudcmd.Viewer.Keys                = (function(pCurrentFile){
        "use strict";

        var lCallBack_f = (function(){
            var key_event = (function(pEvent){
                /* если клавиши можно обрабатывать */
                if( KeyBinding.get() )
                    /* if f3 pressed */
                    if(pEvent.keyCode === cloudcmd.KEY.F3 &&
                    pEvent.shiftKey){
                        var lCurrentFile = Util.getCurrentFile();
                        FancyBox.show(lCurrentFile);

                        pEvent.preventDefault();
                    }
            });

            /* добавляем обработчик клавишь */
            if (document.addEventListener)
                document.addEventListener('keydown', key_event, false);

            else{
                var lFunc;
                if(typeof document.onkeydown === 'function')
                    lFunc = document.onkeydown;

                document.onkeydown = function(){
                    if(lFunc)
                        lFunc();

                    key_event();
                };
            }

            /* showing images  preview*/
            FancyBox.show(pCurrentFile);
            Util.Images.hideLoad();
        });

        FancyBox.load(this.FancyBox, lCallBack_f);
    });

    cloudcmd.Viewer.FancyBox            = FancyBox;
})();<commit_msg>minor changes<commit_after>var CloudCommander, CloudFunc, $;
/* object contains viewer FancyBox
 * https://github.com/fancyapps/fancyBox
 */
(function(){
    var cloudcmd                        = CloudCommander;
    var Util                            = CloudCommander.Util;
    var KeyBinding                      = CloudCommander.KeyBinding;

    cloudcmd.Viewer               = {
        get: (function(){
            return this.FancyBox;
        })
    };

    var FancyBox                        = {};

    cloudcmd.Viewer.dir                 = './lib/client/viewer/';
    FancyBox.dir                        = cloudcmd.Viewer.dir + 'fancybox/';

    /* function return configureation
     * for FancyBox open and
     * onclick (it shoud be
     * different objects)
     */
    FancyBox.getConfig                  = (function(){
        return{
            /* function do her work
             * before FauncyBox shows
             */
            beforeShow  : function(){
                KeyBinding.unSet();
            },

            afterShow   : function(){
                var lEditor = Util.getById('CloudViewer');
                if(lEditor)
                    lEditor.focus();
            },

            beforeClose : function(){
                KeyBinding.set();
            },

            openEffect      : 'none',
            closeEffect     : 'none',
            autoSize        : false,
            height          : window.innerHeight,
            helpers : {
                overlay : {
                    opacity: 0.1,
                    css : {
                        'background-color'  : '#fff'
                    }
                }
            },
            padding : 0
        };
    });

    /* function loads css and js of FancyBox
     * @pParent     - this
     * @pCallBack   -  executes, when everything loaded
     */
    FancyBox.load                       = (function(pThis, pCallBack){
        Util.jqueryLoad();

        var ljsLoad_f = function(){
            var lSrc = pThis.dir + 'jquery.fancybox.js';
            Util.jsload(lSrc,{
                    onload: pCallBack
            });
        };

        Util.cssSet({id:'viewer',
                inner : '#CloudViewer{'                         +
                            'font-size: 16px;'                  +
                            'white-space :pre'                  +
                        '}'                                     +
                        '#CloudViewer::selection{'              +
                                'background: #fe57a1;'          +
                                'color: #fff;'                  +
                                'text-shadow: none;'            +
                        '}'
        });

        var lSrc = pThis.dir +'jquery.fancybox.pack.css';
        Util.cssLoad({
            src  : lSrc,
            func : {
                onload: ljsLoad_f
            }
        });
    });

    FancyBox.loadData                   = (function(pA){
        Util.Images.showLoad();

        var lThis = this;
        var lConfig = this.getConfig();

        var lLink = pA.href;

        /* убираем адрес хоста*/
        lLink = '/' + lLink.replace(document.location.href,'');

        if (lLink.indexOf(CloudFunc.NOJS) === CloudFunc.FS.length)
            lLink = lLink.replace(CloudFunc.NOJS, '');

        Util.ajax({
            url     : lLink,
             error  : (function(jqXHR, textStatus, errorThrown){
                 lThis.loading             = false;
                 return Util.Images.showError(jqXHR, textStatus, errorThrown);
             }),

             success:function(data, textStatus, jqXHR){
                /* if we got json - show it */
                if(typeof data === 'object')
                    data = JSON.stringify(data, null, 4);

                $.fancybox('
' + data + '
', lConfig);

                Util.Images.hideLoad();
            }
        });
    });

    FancyBox.set                        = (function(){
        if(Util.getByClass('fancybox').length)
            return;
        try{
            /* get current panel (left or right) */
            var lPanel = Util.getPanel();

            /* get all file links */
            var lA = Util.getByTag('a', lPanel);

            var lThis = this;

            var lDblClick_f = function(pA){
                return function(){
                    var lConfig  = lThis.getConfig();
                    lConfig.href = pA.href;
                    if(pA.rel)
                        $.fancybox(lConfig);
                    else
                        lThis.loadData(pA);
                };
            };

            /* first two is not files nor folders*/
            for (var i=2; i < lA.length; i++) {
                var lName = lA[i].title || lA[i].textContent;

                lA[i].className    = 'fancybox';
                if(CloudFunc.checkExtension(lName, ['png','jpg', 'gif','ico'])){
                    lA[i].rel          = 'gallery';
                }

                lA[i].ondblclick = lDblClick_f(lA[i]);
            }

        }catch(pError){
            console.log(pError);
        }
    });

    FancyBox.show                       = (function(pCurrentFile){
        FancyBox.set();

        var lConfig = this.getConfig();

        if(typeof pCurrentFile !== 'function'){
            var lA = Util.getByClass('fancybox', pCurrentFile)[0];
            if(lA){
                if(lA.rel)
                    $.fancybox.open({ href : lA.href },
                        lConfig);
                else this.loadData(lA);
            }
        }
        else {
            var lFunc_f = pCurrentFile;
            lFunc_f();
        }

        Util.Images.hideLoad();
    });

    cloudcmd.Viewer.Keys                = (function(pCurrentFile){
        "use strict";

        var lCallBack_f = (function(){
            var key_event = (function(pEvent){
                /* если клавиши можно обрабатывать */
                if( KeyBinding.get() )
                    /* if f3 pressed */
                    if(pEvent.keyCode === cloudcmd.KEY.F3 &&
                    pEvent.shiftKey){
                        var lCurrentFile = Util.getCurrentFile();
                        FancyBox.show(lCurrentFile);

                        pEvent.preventDefault();
                    }
            });

            /* добавляем обработчик клавишь */
            if (document.addEventListener)
                document.addEventListener('keydown', key_event, false);

            else{
                var lFunc;
                if(typeof document.onkeydown === 'function')
                    lFunc = document.onkeydown;

                document.onkeydown = function(){
                    if(lFunc)
                        lFunc();

                    key_event();
                };
            }

            /* showing images  preview*/
            FancyBox.show(pCurrentFile);
            Util.Images.hideLoad();
        });

        FancyBox.load(this.FancyBox, lCallBack_f);
    });

    cloudcmd.Viewer.FancyBox            = FancyBox;
})();<|endoftext|>


<commit_before>var logger = require('./logger');
var requestPromise = require('request-promise');

/**
 * @param {String} baseUrl
 * @param {Number} clientKey
 */
CMApiClient = function(baseUrl, clientKey) {
  this.baseUrl = baseUrl;
  this.clientKey = clientKey;
};

/**
 * @param {String} action
 * @param {Object} data
 * @returns {Promise}
 * @private
 */
CMApiClient.prototype._request = function(action, data) {
  var options = {
    method: 'POST',
    uri: this.baseUrl + '/rpc/CM_Janus_RpcEndpoints.' + action,
    body: data
  };
  return requestPromise(options)
    .catch(function(error) {
      logger.error(error);
      throw error;
    });
};

/**
 * @param {String} streamName
 * @param {Number} start
 * @param {JSON} data
 * @returns {Promise}
 */
CMApiClient.prototype.publish = function(streamName, start, data) {
  return this._request('publish', {
    streamName: streamName,
    clientKey: this.clientKey,
    start: +start,
    data: data
  });
};

/**
 * @param {String} streamName
 * @returns {Promise}
 */
CMApiClient.prototype.unpublish = function(streamName) {
  return this._request('unpublish', {streamName: streamName});
};

/**
 * @param {String} streamName
 * @param {Number} start
 * @param {JSON} data
 * @returns {Promise}
 */
CMApiClient.prototype.subscribe = function(streamName, start, data) {
  return this._request('subscribe', {
    streamName: streamName,
    clientKey: this.clientKey,
    start: +start,
    data: data
  });
};

/**
 * @param {String} streamName
 * @returns {Promise}
 */
CMApiClient.prototype.unsubscribe = function(streamName) {
  return this._request('unsubscribe', {streamName: streamName});
};

/**
 * @param {String} sessionId
 * @returns {Promise}
 */
CMApiClient.prototype.authPublish = function(sessionId) {
  return this._request('authPublish', {sessionId: sessionId});
};

/**
 * @param {String} sessionId
 * @returns {Promise}
 */
CMApiClient.prototype.authSubscribe = function(sessionId) {
  return this._request('authSubscribe', {sessionId: sessionId});
};

module.exports = CMApiClient;
<commit_msg>Fix CMApiClient to accept clientKey instead of storing it<commit_after>var logger = require('./logger');
var requestPromise = require('request-promise');

/**
 * @param {String} baseUrl
 */
CMApiClient = function(baseUrl) {
  this.baseUrl = baseUrl;
};

/**
 * @param {String} action
 * @param {Object} data
 * @returns {Promise}
 * @private
 */
CMApiClient.prototype._request = function(action, data) {
  var options = {
    method: 'POST',
    uri: this.baseUrl + '/rpc/CM_Janus_RpcEndpoints.' + action,
    body: data
  };
  return requestPromise(options)
    .catch(function(error) {
      logger.error(error);
      throw error;
    });
};

/**
 * @param {String} streamName
 * @param {String} clientKey
 * @param {Number} start
 * @param {JSON} [data]
 * @returns {Promise}
 */
CMApiClient.prototype.publish = function(streamName, clientKey, start, data) {
  return this._request('publish', {
    streamName: streamName,
    clientKey: clientKey,
    start: +start,
    data: data || {}
  });
};

/**
 * @param {String} streamName
 * @param {String} clientKey
 * @returns {Promise}
 */
CMApiClient.prototype.unpublish = function(streamName, clientKey) {
  return this._request('unpublish', {
    streamName: streamName,
    clientKey: clientKey
  });
};

/**
 * @param {String} streamName
 * @param {String} clientKey
 * @param {Number} start
 * @param {JSON} [data]
 * @returns {Promise}
 */
CMApiClient.prototype.subscribe = function(streamName, clientKey, start, data) {
  return this._request('subscribe', {
    streamName: streamName,
    clientKey: clientKey,
    start: +start,
    data: data || {}
  });
};

/**
 * @param {String} streamName
 * @param {String} clientKey
 * @returns {Promise}
 */
CMApiClient.prototype.unsubscribe = function(streamName, clientKey) {
  return this._request('unsubscribe', {
    streamName: streamName,
    clientKey: clientKey
  });
};

/**
 * @param {String} sessionId
 * @returns {Promise}
 */
CMApiClient.prototype.authPublish = function(sessionId) {
  return this._request('authPublish', {sessionId: sessionId});
};

/**
 * @param {String} sessionId
 * @returns {Promise}
 */
CMApiClient.prototype.authSubscribe = function(sessionId) {
  return this._request('authSubscribe', {sessionId: sessionId});
};

module.exports = CMApiClient;
<|endoftext|>


<commit_before>/* Javascript for the Skytap XBlock. */

function SkytapXBlock(runtime, element) {
    "use strict";

    // Set up gettext in case it isn't available in the client runtime:
    if (typeof gettext == 'undefined') {
        window.gettext = function gettext_stub(string) { return string; };
        window.ngettext = function ngettext_stub(strA, strB, n) { return n == 1 ? strA : strB; };
    }

    var launchForm = $('.skytap-launch-form', element),
        launchButton = launchForm.find('.skytap-launch'),
        launchSpinner = launchForm.find('.skytap-spinner'),
        launchXHR;

    // Prepare UI
    launchSpinner.hide();

    // Set up click handler for button that allows learners to launch exercise environment
    launchButton.on('click', function(e) {
        e.preventDefault();

        var handlerUrl = runtime.handlerUrl(element, 'launch'),
            keyboardLayout = launchForm.find('select').val();

        if (launchXHR) {
            launchXHR.abort();
        }

        launchSpinner.show();
        launchButton.prop('disabled', true);

        launchXHR = $.post(handlerUrl, JSON.stringify(keyboardLayout))
            .success(function(response) {
                var url = response.sharing_portal_url;

                /*
                 The standard behaviour is to open the exercise environment in a new tab using a popup,
                 to allow the user to keep the course tab open too. However on iOS devices for example,
                 popups are blocked by default and the user is not informed that the popup failed to open.
                 Therefore for iOS devices a redirect is used instead. For consistency this is done for all
                 mobile devices.
                 */
                var isiOS = navigator.userAgent.match(/(iPod|iPhone|iPad)/i);
                var isAndroid = navigator.userAgent.match(/(android)/i);
                var isWindows = navigator.userAgent.match(/(Windows Phone|iemobile)/i);
                if (isiOS || isAndroid || isWindows) {
                    // simply redirect for mobile devices
                    window.location = url;
                } else {
                    // desktop browsers offer an easy way to allow the popup so being blocked is ok
                    var sharingPortal = window.open(url, '_blank');
                    if (sharingPortal == null) {
                        // use '==' instead of '===' to test for both null (from desktop browsers blocking the popup)
                        // and also undefined (from mobile Safari refusing to show the popup)
                        alert(
                            gettext("The browser's popup blocker prevented the exercise environment from being launched.")
                        );
                    } else {
                        sharingPortal.focus();
                    }
                }
            })
            .error(function(jqXHR, textStatus, errorThrown) {
                var error;
                if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('error')) {
                    error = jqXHR.responseJSON.error;
                } else {
                    error = gettext('An unknown error occurred while launching.');
                }
                $('#skytap-error-message').text('Error: ' + error);
            })
            .complete(function() {
                launchSpinner.hide();
                launchButton.prop('disabled', false);
            });

    });

}
<commit_msg>Adding behaviour to redirect mobile devices on launching, including feedback from review<commit_after>/* Javascript for the Skytap XBlock. */

function SkytapXBlock(runtime, element) {
    "use strict";

    // Set up gettext in case it isn't available in the client runtime:
    if (typeof gettext == 'undefined') {
        window.gettext = function gettext_stub(string) { return string; };
        window.ngettext = function ngettext_stub(strA, strB, n) { return n == 1 ? strA : strB; };
    }

    var launchForm = $('.skytap-launch-form', element),
        launchButton = launchForm.find('.skytap-launch'),
        launchSpinner = launchForm.find('.skytap-spinner'),
        launchXHR;

    // Prepare UI
    launchSpinner.hide();

    // Set up click handler for button that allows learners to launch exercise environment
    launchButton.on('click', function(e) {
        e.preventDefault();

        var handlerUrl = runtime.handlerUrl(element, 'launch'),
            keyboardLayout = launchForm.find('select').val();

        if (launchXHR) {
            launchXHR.abort();
        }

        launchSpinner.show();
        launchButton.prop('disabled', true);

        launchXHR = $.post(handlerUrl, JSON.stringify(keyboardLayout))
            .success(function(response) {
                var url = response.sharing_portal_url;

                /*
                 The standard behaviour is to open the exercise environment in a new tab using a popup,
                 to allow the user to keep the course tab open too. However on iOS devices for example,
                 popups are blocked by default and the user is not informed that the popup failed to open.
                 Therefore for iOS devices a redirect is used instead. For consistency this is done for all
                 mobile devices.
                 */
                var isiOS = navigator.userAgent.match(/(iPod|iPhone|iPad)/i);
                var isAndroid = navigator.userAgent.match(/(android)/i);
                var isWindows = navigator.userAgent.match(/(Windows Phone|iemobile)/i);
                if (isiOS || isAndroid || isWindows) {
                    // Simply redirect for mobile devices.
                    window.location = url;
                } else {
                    // Desktop browsers offer an easy way to allow the popup so being blocked is ok.
                    var sharingPortal = window.open(url, '_blank');
                    if (sharingPortal === undefined || sharingPortal === null) {
                        // Use '==' instead of '===' to test for both null (from desktop browsers blocking the popup)
                        // and also undefined (from mobile Safari refusing to show the popup).
                        alert(gettext("The browser's popup blocker prevented the exercise environment from being launched."));
                    } else {
                        sharingPortal.focus();
                    }
                }
            })
            .error(function(jqXHR, textStatus, errorThrown) {
                var error;
                if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('error')) {
                    error = jqXHR.responseJSON.error;
                } else {
                    error = gettext('An unknown error occurred while launching.');
                }
                $('#skytap-error-message').text('Error: ' + error);
            })
            .complete(function() {
                launchSpinner.hide();
                launchButton.prop('disabled', false);
            });

    });

}
<|endoftext|>