<commit_before>'use strict';
var express = require('express');
var path = require('path');
var fs = require('fs');
var clc = require('cli-color');
var log4js = require('log4js');
var redis = require('redis');
var lxHelpers = require('lx-helpers');
var RedisSessionStore = require('connect-redis')(express);

module.exports = function (rootPath) {
    var notice = clc.cyan;
    var middleware = {};
    var config = require('./config')(rootPath, notice);
    var logging = require('./logging')(config);
    var syslog = logging.syslog;
    var mail = require('./mail')(config);
    var rights = require('./rights')(config, logging);
    var app = express();
    var baboonErrors = require('./errors');

    ///////////////////////////////////////////
    // baboon
    ///////////////////////////////////////////

    var baboon = {
        middleware: middleware,
        config: config,
        rights: rights,
        logging: logging,
        mail: mail,
        server: {
            app: app
        }
    };

    // register Errors in baboon
    lxHelpers.forEach(baboonErrors, function (key, value) {
        baboon[key] = value;
    });

    var api = require(config.path.api)(baboon);

    ///////////////////////////////////////////
    // redis server
    ///////////////////////////////////////////
    var redisClient = redis.createClient(config.redis.server1.port, config.redis.server1.host);
    var sessionStore = new RedisSessionStore({
        client: redisClient,
        ttl: config.sessionMaxLife
    });
    var RedisSocketStore = require('socket.io/lib/stores/redis');
    var pub = redis.createClient(config.redis.server1.port, config.redis.server1.host);
    var sub = redis.createClient(config.redis.server1.port, config.redis.server1.host);

    redisClient.on('error', function (err) {
        var msg = 'error event - ' + redisClient.host + ':' + redisClient.port + ' - ' + err;

        syslog.error(msg);
        throw new Error(msg);
    });

    ///////////////////////////////////////////
    // config server
    ///////////////////////////////////////////

    // set log level for syslog logger
    syslog.setLevel(config.logging.syslog);

    // set log level for syslog logger
    logging.audit.setLevel(config.logging.audit);

    var server;

    // setup server to https or http from config protocol
    if (config.protocol === 'https') {
        var https = require('https'),
            serverOptions = {
                key: fs.readFileSync(path.join(rootPath, 'server', 'ssl', 'ssl.key')),
                cert: fs.readFileSync(path.join(rootPath, 'server', 'ssl', 'ssl.crt'))
            };

        server = https.createServer(serverOptions, app);
    } else if (config.protocol === 'http') {
        var http = require('http');

        server = http.createServer(app);
    } else {
        var msg = 'error server config - missing or wrong protocol' + config.protocol;

        syslog.error(msg);
        throw new Error(msg);
    }

    var io = require('socket.io').listen(server);
    baboon.server.sio = io;

    ///////////////////////////////////////////
    // Middleware
    ///////////////////////////////////////////

    middleware.errorHandler = require('./middleware/errorHandler')(syslog);
    middleware.session = require('./middleware/session')(baboon);
    middleware.nav = require('./middleware/nav')(baboon);
    middleware.auth = require('./middleware/auth')(baboon);

    ///////////////////////////////////////////
    // start server
    ///////////////////////////////////////////

    baboon.server.start = function () {
        var startOn = 'application server has been started on: ' + config.protocol + '://' + config.host + ':' + config.port,
            startMode = 'application server has been started in ' + config.node_env + ' mode';

        server.listen(app.get('port'), function () {
            syslog.info(startOn);

            if (config.node_env !== 'production') {
                syslog.warn(startMode);
            } else {
                syslog.info(startMode);
                console.log(
                    notice('   info  - ') + startOn + '\n' +
                        notice('   info  - ') + startMode + '\n' +
                        notice('   info  - ') + 'write all entries to logfile...');
            }
        });
    };

    ///////////////////////////////////////////
    // View Engine EJS
    ///////////////////////////////////////////

    // view engine for application
    app.engine('.html', require('ejs').__express);

    ///////////////////////////////////////////
    // Init all app controllers
    ///////////////////////////////////////////

    var controllers = api.initAppControllers();

    ///////////////////////////////////////////
    // Express
    ///////////////////////////////////////////

    // express default config
    var expressConfig = function () {
        app.set('port', config.port);
        app.set('views', config.path.dist + '/app');
        app.set('view engine', 'html');

        // set log level for syslog logger
        logging.express.setLevel(config.logging.express);

        // logging
        if (config.node_env === 'production') {
            app.use(log4js.connectLogger(logging.express, {level: 'auto'}));
        } else {
            app.use(log4js.connectLogger(logging.express, {level: 'auto', format: ':status :method :url'}));
        }

        app.use(express.favicon(config.path.public + '/favicon.ico'));
        app.use(express.compress());
        app.use(express.static(config.path.public));
        app.use(express.json());
        app.use(express.urlencoded());
        app.use(express.methodOverride());
        app.use(express.cookieParser());

        // Session has session cookie with expires:false.
        // Session is only destroyed when the user closes the browser.
        // Redis ttl is maximal lifetime of session, after this time session is destroyed
        // Middleware checks the inactive period of the session by user.
        // When this period is over, the session destroyed.
        app.use(express.session({
            key: config.sessionKey,
            store: sessionStore,
            secret: config.sessionSecret,
            cookie: {expires: false}
        }));

        // routing
        app.use(app.router);

        // restricted api
        app.use('/api/v1', middleware.auth.restrictedApi);

        // Catch all other rest api requests and forward to controller
        app.use('/api/v1', function (req, res) {
            // remove trailing slash
            var route = req.url.substring(1);
            var parsedRoute = api.parseRoute(route);

            // check if user has access to route
            if (rights.userHasAccessTo(req.session.user, route)) {
                var data = req.body;

                // create request object for controller
                var request = {
                    /**
                     * Returns the current session.
                     *
                     * @param {!function(error, result)} callback The callback.
                     */
                    getSession: function (callback) {
                        callback(null, req.session);
                    },
                    /**
                     * Sets the session.
                     *
                     * @param {!Object} session The session object.
                     * @param {function(error, result)=} callback The callback.
                     */
                    setSession: function (session, callback) {
                        if (!lxHelpers.isObject(session)) {
                            var error = lxHelpers.getTypeError('session', session, {});
                            syslog.error(error);

                            if (callback) {
                                callback(error);
                            }

                            return;
                        }

                        if (callback) {
                            callback(null, true);
                        }
                    }
                };

                if (controllers[parsedRoute.controllerFullPath]) {
                    controllers[parsedRoute.controllerFullPath][parsedRoute.action](data, request, function (error, result) {
                        res.send({error: api.parseError(error, data), data: result});
                    });
                } else {
                    res.json(403, 'Wrong url');
                }
            } else {
                res.json(403, 'Access denied.');
            }
        });

        // restricted route
        app.use(middleware.auth.restrictedRoute);

        // catch all unresolved routes
        app.use(function (req, res) {
            middleware.session.checkSession(req, res, function () {
                middleware.nav.setNavData(req);
                res.render('index');
            });
        });
    };

    // express production config
    var expressProductionConfig = function () {
        // error handling middleware
        app.use(middleware.errorHandler.productionHandler);
    };

    // express development config
    var expressDevelopmentConfig = function () {
        // error handling middleware
        app.use(middleware.errorHandler.developmentHandler);
    };

    // express configure
    app.configure(expressConfig);
    app.configure('development', expressDevelopmentConfig);
    app.configure('production', expressProductionConfig);

    ///////////////////////////////////////////
    // default routes
    ///////////////////////////////////////////

    if (config.useRightsSystem === true) {

        // login middleware rest api
        app.post('/api/v1/auth/login', middleware.auth.login);

        // login extra login page app
        if (config.useRightsSystem && config.extraLoginPage) {
            app.get('/login', function (req, res) {
                middleware.session.checkSession(req, res, function () {
                    res.render('auth/index');
                });
            });
        }

        // logout middleware route
        app.get('/auth/logout', middleware.auth.logout);
    }

    // rest public api outside rights system.
    app.post('/api/v1/session/setActivity', middleware.session.setActivity);
    app.post('/api/v1/session/getLastActivity', middleware.session.getLastActivity);
    app.post('/api/v1/session/getData', middleware.session.getData);
    app.post('/api/v1/session/setData', middleware.session.setData);
    app.post('/api/v1/session/deleteData', middleware.session.deleteData);
    app.post('/api/v1/auth/getAuthData', middleware.auth.getAuthData);
//    app.post('/api/v1/auth/activateUser', middleware.auth.activateUser);
    app.post('/api/v1/nav/getNavData', middleware.nav.getNavData);

    app.get('/*/*.md', function (req, res) {

        var mdPath = path.join(rootPath, 'client', 'app', req.url);
        fs.readFile(mdPath, 'utf8', function (err, data) {
            if (err) {
                res.json(200, { markdown: err});
            }
            res.json(200, { markdown: data});
        });
    });

    ///////////////////////////////////////////
    // Socket.IO
    ///////////////////////////////////////////

    // set log level for socket logger
    logging.socket.setLevel(config.logging.socket);

    // default config
    io.configure(function () {
        // set socket.io redis store
        io.set('store', new RedisSocketStore({
            redisPub: pub,
            redisSub: sub,
            redisClient: redisClient
        }));

        // Transport
        io.set('transports',
            [
                'websocket'
                //'htmlfile',
                //'xhr-polling',
                //'jsonp-polling'
            ]
        );

        io.set('authorization', function (data, callback) {
            // check if cookie exists
            if (!data.headers.cookie) {
                callback(null, false);
            } else {
                // parse cookie in sessionId
                var signedCookies = require('express/node_modules/cookie').parse(data.headers.cookie),
                    sessionId = require('express/node_modules/connect/lib/utils')
                        .parseSignedCookies(signedCookies, config.sessionSecret)['baboon.sid'];

                // get session from redis session store
                sessionStore.get(sessionId, function (err, session) {
                    if (err || !session) {
                        callback(null, false);
                    } else {

                        var isAuth = true;

                        // restricted socket connection when extraLoginPage is enabled
                        if (config.useRightsSystem && config.extraLoginPage) {
                            isAuth = session.user && session.user.id !== -1;
                        }

                        if (isAuth) {
                            data.session = session;
                            data.session.sessionID = sessionId;
                            callback(null, true);
                        }
                        else {
                            callback(null, false);
                        }
                    }
                });
            }
        });
    });

    // production config
    io.configure('production', function () {
        io.enable('browser client minification');  // send minified client
        io.enable('browser client etag');          // apply etag caching logic based on version number
        io.enable('browser client gzip');          // gzip the file
        io.set('logger', logging.socket);
    });

    // development config
    io.configure('development', function () {
        io.set('logger', logging.socket);
    });

    // socket connection event
    io.sockets.on('connection', function (socket) {
        var session = socket.handshake.session,
            sessionId = session.sessionID;

        // save socketId in session
        session.socketID = socket.id;

        syslog.info('socket: client connected');
        syslog.info('socket: {socketId: %s, username: %s, sessionID: %s }', socket.id, ((session.user || {}).name || 'no user'), session.sessionID);

        socket.on('disconnect', function () {
            syslog.info('socket: %s disconnected', socket.id);
        });

        // register socket events based on user acl
        rights.getAclObj((session.user || {}).acl, function (error, result) {
            if (error) {
                syslog.error('rights: Error loading acl for user %s (%s)', (session.user || {}).name, (session.user || {})._id);
                return;
            }

            // create request object for controller
            var request = {
                /**
                 * Returns the current session.
                 *
                 * @param {!function(error, result)} callback The callback.
                 */
                getSession: function (callback) {
                    // get session from redis session store
                    sessionStore.get(session.sessionID, callback);
                },
                /**
                 * Sets the session.
                 *
                 * @param {!Object} session The session object.
                 * @param {function(error, result)=} callback The callback.
                 */
                setSession: function (session, callback) {
                    if (!lxHelpers.isObject(session)) {
                        var error = lxHelpers.getTypeError('session', session, {});
                        syslog.error(error);

                        if (callback) {
                            callback(error);
                        }

                        return;
                    }

                    sessionStore.set(sessionId, session, callback);
                },
                /**
                 * Returns the current socket object.
                 *
                 * @returns {*}
                 */
                getSocket: function () {
                    return socket;
                }
            };

            api.registerSocketEvents(socket, result, controllers, request);
        });
    });

    return baboon;
};<commit_msg>fix(session): use session key from config<commit_after>'use strict';
var express = require('express');
var path = require('path');
var fs = require('fs');
var clc = require('cli-color');
var log4js = require('log4js');
var redis = require('redis');
var lxHelpers = require('lx-helpers');
var RedisSessionStore = require('connect-redis')(express);

module.exports = function (rootPath) {
    var notice = clc.cyan;
    var middleware = {};
    var config = require('./config')(rootPath, notice);
    var logging = require('./logging')(config);
    var syslog = logging.syslog;
    var mail = require('./mail')(config);
    var rights = require('./rights')(config, logging);
    var app = express();
    var baboonErrors = require('./errors');

    ///////////////////////////////////////////
    // baboon
    ///////////////////////////////////////////

    var baboon = {
        middleware: middleware,
        config: config,
        rights: rights,
        logging: logging,
        mail: mail,
        server: {
            app: app
        }
    };

    // register Errors in baboon
    lxHelpers.forEach(baboonErrors, function (key, value) {
        baboon[key] = value;
    });

    var api = require(config.path.api)(baboon);

    ///////////////////////////////////////////
    // redis server
    ///////////////////////////////////////////
    var redisClient = redis.createClient(config.redis.server1.port, config.redis.server1.host);
    var sessionStore = new RedisSessionStore({
        client: redisClient,
        ttl: config.sessionMaxLife
    });
    var RedisSocketStore = require('socket.io/lib/stores/redis');
    var pub = redis.createClient(config.redis.server1.port, config.redis.server1.host);
    var sub = redis.createClient(config.redis.server1.port, config.redis.server1.host);

    redisClient.on('error', function (err) {
        var msg = 'error event - ' + redisClient.host + ':' + redisClient.port + ' - ' + err;

        syslog.error(msg);
        throw new Error(msg);
    });

    ///////////////////////////////////////////
    // config server
    ///////////////////////////////////////////

    // set log level for syslog logger
    syslog.setLevel(config.logging.syslog);

    // set log level for syslog logger
    logging.audit.setLevel(config.logging.audit);

    var server;

    // setup server to https or http from config protocol
    if (config.protocol === 'https') {
        var https = require('https'),
            serverOptions = {
                key: fs.readFileSync(path.join(rootPath, 'server', 'ssl', 'ssl.key')),
                cert: fs.readFileSync(path.join(rootPath, 'server', 'ssl', 'ssl.crt'))
            };

        server = https.createServer(serverOptions, app);
    } else if (config.protocol === 'http') {
        var http = require('http');

        server = http.createServer(app);
    } else {
        var msg = 'error server config - missing or wrong protocol' + config.protocol;

        syslog.error(msg);
        throw new Error(msg);
    }

    var io = require('socket.io').listen(server);
    baboon.server.sio = io;

    ///////////////////////////////////////////
    // Middleware
    ///////////////////////////////////////////

    middleware.errorHandler = require('./middleware/errorHandler')(syslog);
    middleware.session = require('./middleware/session')(baboon);
    middleware.nav = require('./middleware/nav')(baboon);
    middleware.auth = require('./middleware/auth')(baboon);

    ///////////////////////////////////////////
    // start server
    ///////////////////////////////////////////

    baboon.server.start = function () {
        var startOn = 'application server has been started on: ' + config.protocol + '://' + config.host + ':' + config.port,
            startMode = 'application server has been started in ' + config.node_env + ' mode';

        server.listen(app.get('port'), function () {
            syslog.info(startOn);

            if (config.node_env !== 'production') {
                syslog.warn(startMode);
            } else {
                syslog.info(startMode);
                console.log(
                    notice('   info  - ') + startOn + '\n' +
                        notice('   info  - ') + startMode + '\n' +
                        notice('   info  - ') + 'write all entries to logfile...');
            }
        });
    };

    ///////////////////////////////////////////
    // View Engine EJS
    ///////////////////////////////////////////

    // view engine for application
    app.engine('.html', require('ejs').__express);

    ///////////////////////////////////////////
    // Init all app controllers
    ///////////////////////////////////////////

    var controllers = api.initAppControllers();

    ///////////////////////////////////////////
    // Express
    ///////////////////////////////////////////

    // express default config
    var expressConfig = function () {
        app.set('port', config.port);
        app.set('views', config.path.dist + '/app');
        app.set('view engine', 'html');

        // set log level for syslog logger
        logging.express.setLevel(config.logging.express);

        // logging
        if (config.node_env === 'production') {
            app.use(log4js.connectLogger(logging.express, {level: 'auto'}));
        } else {
            app.use(log4js.connectLogger(logging.express, {level: 'auto', format: ':status :method :url'}));
        }

        app.use(express.favicon(config.path.public + '/favicon.ico'));
        app.use(express.compress());
        app.use(express.static(config.path.public));
        app.use(express.json());
        app.use(express.urlencoded());
        app.use(express.methodOverride());
        app.use(express.cookieParser());

        // Session has session cookie with expires:false.
        // Session is only destroyed when the user closes the browser.
        // Redis ttl is maximal lifetime of session, after this time session is destroyed
        // Middleware checks the inactive period of the session by user.
        // When this period is over, the session destroyed.
        app.use(express.session({
            key: config.sessionKey,
            store: sessionStore,
            secret: config.sessionSecret,
            cookie: {expires: false}
        }));

        // routing
        app.use(app.router);

        // restricted api
        app.use('/api/v1', middleware.auth.restrictedApi);

        // Catch all other rest api requests and forward to controller
        app.use('/api/v1', function (req, res) {
            // remove trailing slash
            var route = req.url.substring(1);
            var parsedRoute = api.parseRoute(route);

            // check if user has access to route
            if (rights.userHasAccessTo(req.session.user, route)) {
                var data = req.body;

                // create request object for controller
                var request = {
                    /**
                     * Returns the current session.
                     *
                     * @param {!function(error, result)} callback The callback.
                     */
                    getSession: function (callback) {
                        callback(null, req.session);
                    },
                    /**
                     * Sets the session.
                     *
                     * @param {!Object} session The session object.
                     * @param {function(error, result)=} callback The callback.
                     */
                    setSession: function (session, callback) {
                        if (!lxHelpers.isObject(session)) {
                            var error = lxHelpers.getTypeError('session', session, {});
                            syslog.error(error);

                            if (callback) {
                                callback(error);
                            }

                            return;
                        }

                        if (callback) {
                            callback(null, true);
                        }
                    }
                };

                if (controllers[parsedRoute.controllerFullPath]) {
                    controllers[parsedRoute.controllerFullPath][parsedRoute.action](data, request, function (error, result) {
                        res.send({error: api.parseError(error, data), data: result});
                    });
                } else {
                    res.json(403, 'Wrong url');
                }
            } else {
                res.json(403, 'Access denied.');
            }
        });

        // restricted route
        app.use(middleware.auth.restrictedRoute);

        // catch all unresolved routes
        app.use(function (req, res) {
            middleware.session.checkSession(req, res, function () {
                middleware.nav.setNavData(req);
                res.render('index');
            });
        });
    };

    // express production config
    var expressProductionConfig = function () {
        // error handling middleware
        app.use(middleware.errorHandler.productionHandler);
    };

    // express development config
    var expressDevelopmentConfig = function () {
        // error handling middleware
        app.use(middleware.errorHandler.developmentHandler);
    };

    // express configure
    app.configure(expressConfig);
    app.configure('development', expressDevelopmentConfig);
    app.configure('production', expressProductionConfig);

    ///////////////////////////////////////////
    // default routes
    ///////////////////////////////////////////

    if (config.useRightsSystem === true) {

        // login middleware rest api
        app.post('/api/v1/auth/login', middleware.auth.login);

        // login extra login page app
        if (config.useRightsSystem && config.extraLoginPage) {
            app.get('/login', function (req, res) {
                middleware.session.checkSession(req, res, function () {
                    res.render('auth/index');
                });
            });
        }

        // logout middleware route
        app.get('/auth/logout', middleware.auth.logout);
    }

    // rest public api outside rights system.
    app.post('/api/v1/session/setActivity', middleware.session.setActivity);
    app.post('/api/v1/session/getLastActivity', middleware.session.getLastActivity);
    app.post('/api/v1/session/getData', middleware.session.getData);
    app.post('/api/v1/session/setData', middleware.session.setData);
    app.post('/api/v1/session/deleteData', middleware.session.deleteData);
    app.post('/api/v1/auth/getAuthData', middleware.auth.getAuthData);
//    app.post('/api/v1/auth/activateUser', middleware.auth.activateUser);
    app.post('/api/v1/nav/getNavData', middleware.nav.getNavData);

    app.get('/*/*.md', function (req, res) {

        var mdPath = path.join(rootPath, 'client', 'app', req.url);
        fs.readFile(mdPath, 'utf8', function (err, data) {
            if (err) {
                res.json(200, { markdown: err});
            }
            res.json(200, { markdown: data});
        });
    });

    ///////////////////////////////////////////
    // Socket.IO
    ///////////////////////////////////////////

    // set log level for socket logger
    logging.socket.setLevel(config.logging.socket);

    // default config
    io.configure(function () {
        // set socket.io redis store
        io.set('store', new RedisSocketStore({
            redisPub: pub,
            redisSub: sub,
            redisClient: redisClient
        }));

        // Transport
        io.set('transports',
            [
                'websocket'
                //'htmlfile',
                //'xhr-polling',
                //'jsonp-polling'
            ]
        );

        io.set('authorization', function (data, callback) {
            // check if cookie exists
            if (!data.headers.cookie) {
                callback(null, false);
            } else {
                // parse cookie in sessionId
                var signedCookies = require('express/node_modules/cookie').parse(data.headers.cookie),
                    sessionId = require('express/node_modules/connect/lib/utils')
                        .parseSignedCookies(signedCookies, config.sessionSecret)[config.sessionKey];

                // get session from redis session store
                sessionStore.get(sessionId, function (err, session) {
                    if (err || !session) {
                        callback(null, false);
                    } else {

                        var isAuth = true;

                        // restricted socket connection when extraLoginPage is enabled
                        if (config.useRightsSystem && config.extraLoginPage) {
                            isAuth = session.user && session.user.id !== -1;
                        }

                        if (isAuth) {
                            data.session = session;
                            data.session.sessionID = sessionId;
                            callback(null, true);
                        }
                        else {
                            callback(null, false);
                        }
                    }
                });
            }
        });
    });

    // production config
    io.configure('production', function () {
        io.enable('browser client minification');  // send minified client
        io.enable('browser client etag');          // apply etag caching logic based on version number
        io.enable('browser client gzip');          // gzip the file
        io.set('logger', logging.socket);
    });

    // development config
    io.configure('development', function () {
        io.set('logger', logging.socket);
    });

    // socket connection event
    io.sockets.on('connection', function (socket) {
        var session = socket.handshake.session,
            sessionId = session.sessionID;

        // save socketId in session
        session.socketID = socket.id;

        syslog.info('socket: client connected');
        syslog.info('socket: {socketId: %s, username: %s, sessionID: %s }', socket.id, ((session.user || {}).name || 'no user'), session.sessionID);

        socket.on('disconnect', function () {
            syslog.info('socket: %s disconnected', socket.id);
        });

        // register socket events based on user acl
        rights.getAclObj((session.user || {}).acl, function (error, result) {
            if (error) {
                syslog.error('rights: Error loading acl for user %s (%s)', (session.user || {}).name, (session.user || {})._id);
                return;
            }

            // create request object for controller
            var request = {
                /**
                 * Returns the current session.
                 *
                 * @param {!function(error, result)} callback The callback.
                 */
                getSession: function (callback) {
                    // get session from redis session store
                    sessionStore.get(session.sessionID, callback);
                },
                /**
                 * Sets the session.
                 *
                 * @param {!Object} session The session object.
                 * @param {function(error, result)=} callback The callback.
                 */
                setSession: function (session, callback) {
                    if (!lxHelpers.isObject(session)) {
                        var error = lxHelpers.getTypeError('session', session, {});
                        syslog.error(error);

                        if (callback) {
                            callback(error);
                        }

                        return;
                    }

                    sessionStore.set(sessionId, session, callback);
                },
                /**
                 * Returns the current socket object.
                 *
                 * @returns {*}
                 */
                getSocket: function () {
                    return socket;
                }
            };

            api.registerSocketEvents(socket, result, controllers, request);
        });
    });

    return baboon;
};<|endoftext|>


<commit_before>'use strict';

(function () {

  function setupRequestAnimationFrame () {
    var lastTime = 0;
    var vendors = ['webkit', 'moz', 'ms', 'o'];

    for (var i = 0; !window.requestAnimationFrame && i < vendors.length; i += 1) {
      var vendor = vendors[i];
      window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame'];
      window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] ||
        window[vendor + 'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame) {
      window.requestAnimationFrame = function (callback) {
        var now = new Date().getTime();
        var nextTime = Math.max(0, 16 - (now - lastTime));

        var id = window.setTimeout(function () {
          callback(now + nextTime);
        }, nextTime);

        lastTime = now + nextTime;
        return id;
      };
    }

    if (!window.cancelAnimationFrame) {
      window.cancelAnimationFrame = function (id) {
        clearTimeout(id);
      };
    }
  }

  setupRequestAnimationFrame();

  function setupPerformanceNow () {
    if (!('performance' in window)) {
      window.performance = {};
    }

    if (!('now' in window.performance)) {
      var dateNow = Date.now || function () {
        return new Date().getTime();
      };

      var nowOffset = dateNow();

      if (performance.timing && performance.timing.navigationStart) {
        nowOffset = performance.timing.navigationStart;
      }

      window.performance.now = function now () {
        return dateNow() - nowOffset;
      };
    }
  }

  setupPerformanceNow();

  function getSlik (Immutable) {

    function multiply (fromValue, toValue, multiplier) {
      return fromValue * multiplier + toValue * (1 - multiplier);
    }

    function Linear (fromValue, toValue, progress) {
      var multiplier = progress;
      return multiply(fromValue, toValue, multiplier);
    }

    function Ease (fromValue, toValue, progress) {
      var multiplier = (1 - Math.cos(progress * Math.PI)) / 2;
      return multiply(fromValue, toValue, multiplier);
    }

    function EaseIn (fromValue, toValue, progress) {
      var multiplier = (1 - Math.cos(progress * Math.PI / 2));
      return multiply(fromValue, toValue, multiplier);
    }

    function EaseOut (fromValue, toValue, progress) {
      var multiplier = Math.cos((1 - progress) * Math.PI / 2);
      return multiply(fromValue, toValue, multiplier);
    }

    function getDefault (value, defaultValue) {
      return typeof value === 'undefined' ? defaultValue : value;
    }

    function Animation (initial) {
      var self = this;

      var raf, startTime, lastTime, pausedAfter;

      var fromValues = Immutable.fromJS(getDefault(initial.from, {}));
      var toValues = Immutable.Map(getDefault(initial.to, {}));
      var durationMillis = getDefault(initial.duration, 500);
      var frameRate = 1000 / getDefault(initial.fps, 60);
      var easing = getDefault(initial.easing, Linear);
      var shouldLoop = getDefault(initial.loop, false);

      var currentValues = fromValues;

      var events = Immutable.Map({
        all: Immutable.List(),
        start: Immutable.List(),
        stop: Immutable.List(),
        pause: Immutable.List(),
        end: Immutable.List(),
        update: Immutable.List(),
        loop: Immutable.List()
      });

      function triggerEvent (type) {

      }

      function easeValues () {
        cancelAnimationFrame(raf);

        var now = performance.now();
        var progress = Math.min(Math.max((now - startTime) / durationMillis, 0), 1);

        if (lastTime - now >= frameRate) {
          function mapValues (fromValue, key) {
            if (Immutable.Iterable.isIterable(fromValue)) {
              // Ease nested immutable objects
              return fromValue.map(mapValues);
            }

            var toValue = toValues.get(key);

            if (typeof toValue !== 'undefined') {
              return easing(fromValue, toValue, progress);
            }

            return fromValue;
          }

          if (Immutable.Iterable.isIterable(fromValues)) {
            // Ease immutable objects
            currentValues = currentValues.map(mapValues);
          } else {
            // Ease individual value
            currentValues = easing(fromValues, toValues, progress);
          }

          triggerEvent('update');
        }

        lastTime = now;

        if (progress >= 1) {
          if (shouldLoop) {
            startTime = now;
            triggerEvent('loop');
            raf = requestAnimationFrame(easeValues);
          } else {
            triggerEvent('end');
            cancelAnimationFrame(raf);
          }
        } else {
          raf = requestAnimationFrame(easeValues);
        }
      }

      // Set the frameRate
      function fps (input) {
        frameRate = 1000 / input;
        return self;
      }

      // Set from values
      function from (input) {
        fromValues = Immutable.fromJS(input);
        return self;
      }

      // Set to values
      function to (input) {
        toValues = Immutable.fromJS(input);
        return self;
      }

      // Set animation duration
      function duration (input) {
        durationMillis = input;
        return self;
      }

      // Set easing
      function ease (input) {
        if (typeof input !== 'function') {
          throw new Error('Ease must be a function, instead got: ' + (typeof input));
        }

        easing = input;
        return self;
      }

      // Set whether the animation should loop
      function loop (input) {
        shouldLoop = input;
        return self;
      }

      // Invert animation values
      function invert () {
        return self;
      }

      // Play animation in reverse
      function reverse () {
        var temp = fromValues;
        fromValues = toValues;
        toValues = temp;
        return self;
      }

      // Start or resume animation
      function start () {
        cancelAnimationFrame(raf);

        if (typeof pausedAfter !== 'undefined') {
          startTime = performance.now() - pausedAfter;
        } else {
          startTime = performance.now();
        }

        lastTime = startTime;
        pausedAfter = undefined;

        triggerEvent('start');

        raf = requestAnimationFrame(easeValues);

        return self;
      }

      // Stop animation and resume from beginning
      function stop () {
        triggerEvent('stop');
        cancelAnimationFrame(raf);
        pausedAfter = undefined;
        startTime = undefined;
        return self;
      }

      // Pause animation and resume from this point
      function pause () {
        triggerEvent('pause');
        cancelAnimationFrame(raf);
        pausedAfter = startTime - performance.now();
        startTime = undefined;
        return self;
      }

      // Add event listener
      function bind (type, callback) {
        if (!events.has(type)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var eventsOfType = events.get(type, Immutable.List());

        if (!eventsOfType.includes(callback)) {
          events = events.set(type, eventsOfType.push(callback));
        }

        return self;
      }

      // Remove event listener
      function unbind (type, callback) {
        if (!events.has(type)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var eventsOfType = events.get(type, Immutable.List());

        if (eventsOfType.includes(callback)) {
          events = events.set(type, eventsOfType.delete(eventsOfType.indexOf(callback)));
        }

        return self;
      }

      // Run on complete and automatically unbind
      function then (callback) {
        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        function thenCallback () {
          callback();
          unbind('end', thenCallback);
        }

        bind('end', thenCallback);

        return self;
      }

      self.fps = self.frameRate = fps;
      self.from = from;
      self.to = to;
      self.duration = duration;
      self.ease = ease;
      self.loop = loop;
      self.invert = invert;
      self.reverse = reverse;
      self.start = start;
      self.stop = self.reset = stop;
      self.pause = pause;
      self.bind = self.on = bind;
      self.unbind = self.off = unbind;
      self.then = then;
    }

    return {
      Animation: Animation,
      Easing: {
        Linear: Linear,
        Ease: Ease,
        EaseIn: EaseIn,
        EaseOut: EaseOut
      }
    };

  }

  // Export for commonjs / browserify
  if (typeof exports === 'object' && typeof module !== 'undefined') {
    module.exports = getSlik(require('immutable')); // eslint-disable-line no-undef
  // Export for amd / require
  } else if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef
    define(['immutable'], function (Immutable) { // eslint-disable-line no-undef
      return getSlik(Immutable);
    });
  // Export globally
  } else {
    var root;

    if (typeof window !== 'undefined') {
      root = window;
    } else if (typeof global !== 'undefined') {
      root = global; // eslint-disable-line no-undef
    } else if (typeof self !== 'undefined') {
      root = self; // eslint-disable-line no-undef
    } else {
      root = this;
    }

    root.Slik = getSlik(root.Immutable);
  }

})();
<commit_msg>Trigger events<commit_after>'use strict';

(function () {

  function setupRequestAnimationFrame () {
    var lastTime = 0;
    var vendors = ['webkit', 'moz', 'ms', 'o'];

    for (var i = 0; !window.requestAnimationFrame && i < vendors.length; i += 1) {
      var vendor = vendors[i];
      window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame'];
      window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] ||
        window[vendor + 'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame) {
      window.requestAnimationFrame = function (callback) {
        var now = new Date().getTime();
        var nextTime = Math.max(0, 16 - (now - lastTime));

        var id = window.setTimeout(function () {
          callback(now + nextTime);
        }, nextTime);

        lastTime = now + nextTime;
        return id;
      };
    }

    if (!window.cancelAnimationFrame) {
      window.cancelAnimationFrame = function (id) {
        clearTimeout(id);
      };
    }
  }

  setupRequestAnimationFrame();

  function setupPerformanceNow () {
    if (!('performance' in window)) {
      window.performance = {};
    }

    if (!('now' in window.performance)) {
      var dateNow = Date.now || function () {
        return new Date().getTime();
      };

      var nowOffset = dateNow();

      if (performance.timing && performance.timing.navigationStart) {
        nowOffset = performance.timing.navigationStart;
      }

      window.performance.now = function now () {
        return dateNow() - nowOffset;
      };
    }
  }

  setupPerformanceNow();

  function getSlik (Immutable) {

    function multiply (fromValue, toValue, multiplier) {
      return fromValue * multiplier + toValue * (1 - multiplier);
    }

    function Linear (fromValue, toValue, progress) {
      var multiplier = progress;
      return multiply(fromValue, toValue, multiplier);
    }

    function Ease (fromValue, toValue, progress) {
      var multiplier = (1 - Math.cos(progress * Math.PI)) / 2;
      return multiply(fromValue, toValue, multiplier);
    }

    function EaseIn (fromValue, toValue, progress) {
      var multiplier = (1 - Math.cos(progress * Math.PI / 2));
      return multiply(fromValue, toValue, multiplier);
    }

    function EaseOut (fromValue, toValue, progress) {
      var multiplier = Math.cos((1 - progress) * Math.PI / 2);
      return multiply(fromValue, toValue, multiplier);
    }

    function getDefault (value, defaultValue) {
      return typeof value === 'undefined' ? defaultValue : value;
    }

    function Animation (initial) {
      var self = this;

      var raf, startTime, lastTime, pausedAfter;

      var fromValues = Immutable.fromJS(getDefault(initial.from, {}));
      var toValues = Immutable.Map(getDefault(initial.to, {}));
      var durationMillis = getDefault(initial.duration, 500);
      var frameRate = 1000 / getDefault(initial.fps, 60);
      var easing = getDefault(initial.easing, Linear);
      var shouldLoop = getDefault(initial.loop, false);

      var currentValues = fromValues;

      var events = Immutable.Map({
        all: Immutable.List(),
        start: Immutable.List(),
        stop: Immutable.List(),
        pause: Immutable.List(),
        end: Immutable.List(),
        update: Immutable.List(),
        loop: Immutable.List()
      });

      function triggerEvent (type) {
        events.get(type, Immutable.List()).forEach(function (callback) {
          callback(currentValues);
        });

        events.get('all', Immutable.List()).forEach(function (callback) {
          callback(currentValues);
        });
      }

      function easeValues () {
        cancelAnimationFrame(raf);

        var now = performance.now();
        var progress = Math.min(Math.max((now - startTime) / durationMillis, 0), 1);

        if (lastTime - now >= frameRate) {
          function mapValues (fromValue, key) {
            if (Immutable.Iterable.isIterable(fromValue)) {
              // Ease nested immutable objects
              return fromValue.map(mapValues);
            }

            var toValue = toValues.get(key);

            if (typeof toValue !== 'undefined') {
              return easing(fromValue, toValue, progress);
            }

            return fromValue;
          }

          if (Immutable.Iterable.isIterable(fromValues)) {
            // Ease immutable objects
            currentValues = currentValues.map(mapValues);
          } else {
            // Ease individual value
            currentValues = easing(fromValues, toValues, progress);
          }

          triggerEvent('update');
        }

        lastTime = now;

        if (progress >= 1) {
          if (shouldLoop) {
            startTime = now;
            triggerEvent('loop');
            raf = requestAnimationFrame(easeValues);
          } else {
            triggerEvent('end');
            cancelAnimationFrame(raf);
          }
        } else {
          raf = requestAnimationFrame(easeValues);
        }
      }

      // Set the frameRate
      function fps (input) {
        frameRate = 1000 / input;
        return self;
      }

      // Set from values
      function from (input) {
        fromValues = Immutable.fromJS(input);
        return self;
      }

      // Set to values
      function to (input) {
        toValues = Immutable.fromJS(input);
        return self;
      }

      // Set animation duration
      function duration (input) {
        durationMillis = input;
        return self;
      }

      // Set easing
      function ease (input) {
        if (typeof input !== 'function') {
          throw new Error('Ease must be a function, instead got: ' + (typeof input));
        }

        easing = input;
        return self;
      }

      // Set whether the animation should loop
      function loop (input) {
        shouldLoop = input;
        return self;
      }

      // Invert animation values
      function invert () {
        return self;
      }

      // Play animation in reverse
      function reverse () {
        var temp = fromValues;
        fromValues = toValues;
        toValues = temp;
        return self;
      }

      // Start or resume animation
      function start () {
        cancelAnimationFrame(raf);

        if (typeof pausedAfter !== 'undefined') {
          startTime = performance.now() - pausedAfter;
        } else {
          startTime = performance.now();
        }

        lastTime = startTime;
        pausedAfter = undefined;

        triggerEvent('start');

        raf = requestAnimationFrame(easeValues);

        return self;
      }

      // Stop animation and resume from beginning
      function stop () {
        triggerEvent('stop');
        cancelAnimationFrame(raf);
        pausedAfter = undefined;
        startTime = undefined;
        return self;
      }

      // Pause animation and resume from this point
      function pause () {
        triggerEvent('pause');
        cancelAnimationFrame(raf);
        pausedAfter = startTime - performance.now();
        startTime = undefined;
        return self;
      }

      // Add event listener
      function bind (type, callback) {
        if (!events.has(type)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var eventsOfType = events.get(type, Immutable.List());

        if (!eventsOfType.includes(callback)) {
          events = events.set(type, eventsOfType.push(callback));
        }

        return self;
      }

      // Remove event listener
      function unbind (type, callback) {
        if (!events.has(type)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var eventsOfType = events.get(type, Immutable.List());

        if (eventsOfType.includes(callback)) {
          events = events.set(type, eventsOfType.delete(eventsOfType.indexOf(callback)));
        }

        return self;
      }

      // Run on complete and automatically unbind
      function then (callback) {
        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        function thenCallback () {
          callback();
          unbind('end', thenCallback);
        }

        bind('end', thenCallback);

        return self;
      }

      self.fps = self.frameRate = fps;
      self.from = from;
      self.to = to;
      self.duration = duration;
      self.ease = ease;
      self.loop = loop;
      self.invert = invert;
      self.reverse = reverse;
      self.start = start;
      self.stop = self.reset = stop;
      self.pause = pause;
      self.bind = self.on = bind;
      self.unbind = self.off = unbind;
      self.then = then;
    }

    return {
      Animation: Animation,
      Easing: {
        Linear: Linear,
        Ease: Ease,
        EaseIn: EaseIn,
        EaseOut: EaseOut
      }
    };

  }

  // Export for commonjs / browserify
  if (typeof exports === 'object' && typeof module !== 'undefined') {
    module.exports = getSlik(require('immutable')); // eslint-disable-line no-undef
  // Export for amd / require
  } else if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef
    define(['immutable'], function (Immutable) { // eslint-disable-line no-undef
      return getSlik(Immutable);
    });
  // Export globally
  } else {
    var root;

    if (typeof window !== 'undefined') {
      root = window;
    } else if (typeof global !== 'undefined') {
      root = global; // eslint-disable-line no-undef
    } else if (typeof self !== 'undefined') {
      root = self; // eslint-disable-line no-undef
    } else {
      root = this;
    }

    root.Slik = getSlik(root.Immutable);
  }

})();
<|endoftext|>


<commit_before>/*
MagJS v0.29.3
http://github.com/magnumjs/mag.js
(c) Michael Glazer
License: MIT
*/
(function(mag) {

  'use strict';

  var modules = [],
    MAGNUM = mag.MAGNUM,
    controllers = []

  var mod = {
    runningViewInstance: -1,
    innerMods: [],
    cache: []
  }

  mod.getState = function(index) {
    return modules[index][1]
  }
  mod.setState = function(index, state) {
    modules[index][1] = state
  }
  mod.getView = function(index) {
    return modules[index][0]
  }
  mod.getProps = function(index) {
    return modules[index] && modules[index][2]
  }
  mod.setProps = function(index, props) {
    return modules[index][2] = props
  }
  mod.remove = function(key) {
    //remove mod completely
    if (modules[key]) modules[key] = 0
  }
  mod.getId = function(index) {
    return modules[index] && modules[index][3]
  }
  mod.exists = function(index) {
    return typeof modules[index] != 'undefined'
  }
  mod.setFrameId = function(index, fid) {
    modules[index][4] = fid;
  }
  mod.getFrameId = function(index) {
    return modules[index][4];
  }
  mod.getMod = function(index) {
    return modules[index][5]
  }

  var bindMethods = (obj, context) => {
    context = context || obj;

    for (var key in obj) {
      var val = obj[key];

      if (typeof val == 'function' && !~['controller', 'view'].indexOf(key)) {
        obj[key] = val.bind(context);
      }
    }

    return obj;
  }

  mod.submodule = function(id, index, module, props) {
    if (modules[index]) {
      // new call to existing
      // update props, merge with existing if same key
      if (props.key && props.key == mod.getProps(index).key) {
        mod.setProps(index, mag.utils.copy(mag.utils.merge(mod.getProps(index), props)));
      } else {
        mod.setProps(index, props);
      }
      // reinitialize the controller ?
      return modules[index]
    }

    module = mag.utils.copy(module)
    bindMethods(module, module)

    modules[index] = [0, 0, 0, 0, 0, 0]
    mod.setProps(index, props);
    var controller = function(context) {
        module.props = mod.getProps(index)
        module.state = context;
        module.element = mag.getNode(id);
        return (module.controller || function() {}).call(context, module.props) || context
      },
      view = function(index, state, ele) {
        module.element = ele;
        module.state = state;
        module.props = mod.getProps(index)
        module.view && module.view.call(module, state, module.props, ele)
      }.bind({}, index),
      output = {
        controller: controller,
        view: view
      }
    modules[index][0] = output.view;
    modules[index][3] = id;
    modules[index][1] = getController(output.controller, index, id);
    modules[index][5] = module;
    // register the module
    return modules[index]
  }

  var timers = [];

  function findMissing(change, element) {
    var prop = change.name;
    if (typeof change.object[change.name] == 'undefined' && prop[0] != '_') {
      // prop might be hierarchical?
      // getparent Object property chain?

      // get value of property from DOM
      var a = mag.fill.find(element, prop),
        greedy = prop[0] === '$',
        // can be an array, object or string
        // for each
        tmp = [];

      if (!a.length) return MAGNUM;

      a.forEach(function(item) {
        var i;
        if (item && item.type && !~['submit', 'button'].indexOf(item.type)) {
          if (item.value && item.value.length > 0) {

            if (item.type == 'checkbox' || item.type == 'radio') {

              if (item.checked) {
                i = {};
                i._checked = true;
              }
              if (!greedy) {
                //if grouped name item
                var items = element.querySelectorAll('[name=' + item.name + ']');
                if (items.length) {
                  for (var sitem of items) {
                    if (sitem.checked) {
                      if (i) {
                        i._value = sitem.value;
                      } else {
                        i = {
                          _value: sitem.value
                        };
                      }
                    }
                  }
                }
              } else {
                if (i) {
                  i._value = item.value;
                } else {
                  i = {
                    _value: item.value
                  };
                }
              }
            } else {
              var vals = item.multiple && Array.prototype.map.call(item.selectedOptions, x => x.value)

              i = {
                _value: vals || item.value
              };
            }
          }
        } else if ((typeof change.oldValue == 'undefined' && typeof change.object[change.name] == 'undefined') && item && !~['submit', 'button'].indexOf(item.type) && item.childNodes.length == 1 && item.childNodes[0].textContent.trim()) {
          i = {
            _text: item.childNodes[0].textContent.trim()
          };
        }
        if (i) tmp.push(i);
        else if (item && !item.hasChildNodes()) {
          tmp.push(MAGNUM)
        }

      });

      if (tmp.length === 0) return;
      else if (greedy) return tmp;
      else {
        return tmp[0] && typeof tmp[0]._value != 'undefined' ? tmp[0]._value : tmp[0] && tmp[0]._text ? tmp[0]._text : tmp[0]
      }
    } else if (prop[0] == '_') {
      var attr = prop.substr(1);

      if (element.length) {
        var tmp = [];
        for (var k in element) {
          var ele = element[k]
          var has = ele[attr];
          if (typeof has == 'undefined') has = ele.getAttribute(attr)
          tmp.push(has)
        }
        return tmp;
      } else {
        var has = element[attr];
        if (typeof has == 'undefined') has = element.getAttribute(attr)
        return has;
      }
    }
  }

  var findParentNodeWithPath = function(node, path) {
    var snode = node;
    var parts = path.split('/');

    for (var prop of parts) {
      if (prop) {
        var a = mag.fill.find(snode, prop);
        if (a.length) {
          snode = a;
        }
      }
    }
    return snode
  };

  var handler = function(type, index, change) {

    if (change.type == 'get' && type != 'props' && !~mag.fill.ignorekeys.indexOf(change.name.toString()) && typeof change.oldValue == 'undefined') {

      var rootNode = mag.getNode(mod.getId(index));

      //Get parent correct parent not just root!?
      if (change.path && change.path[0] == '/') var fnode = findParentNodeWithPath(rootNode, change.path)

      // if greedy pass all elements
      if (fnode && change.path.split('/').pop()[0] != '$') {
        fnode = fnode[0]
      }

      var res = findMissing(change, fnode ? fnode : rootNode);

      if (res !== null && typeof res == 'object' && change.object) {
        mag.utils.merge(res, change.object[change.name]);
      }
      if (res) {
        mod.cached[index] = 0;
        return res;
      }
    } else if (change.type == 'set' && type != 'props' && change.object[change.name] && change.object[change.name].draw && typeof change.object[change.name].draw == 'function') {

      // setting a state prop to a module

      // call unloader for module
      mod.innerMods[mod.getId(index)] = [change.name, change.object[change.name]];
    }

    // call setup handler
    var fun = mod.getFrameId(index);
    if (typeof fun == 'function' && change.type == 'set') {
      //debounce
      cancelAnimationFrame(timers[index]);
      timers[index] = requestAnimationFrame(fun);
    }

  };

  function getController(ctrl, index, id) {

    //mod.setProps(index, mag.proxy(mod.getProps(index), handler.bind({}, 'props', index), 'prop'));

    return new ctrl(mag.proxy({}, handler.bind({}, 'state', index)));

  }

  mod.iscached = function(key) {
    //TODO: shallow equals
    var data = mag.utils.toJson([mod.getProps(key), mod.getState(key)])
    if (key in mod.cache && mod.cache[key] == data) {
      return true
    }
    mod.cache[key] = data;
  }

  mod.clear = function(key) {
    if (~key && mod.cache[key]) {
      mod.cache.splice(key, 1);
    }
  }

  mod.cached = [];
  mod.callView = function(node, index) {
    //TODO: ASYNC, promises .. ?
    mod.runningViewInstance = index;
    if (!mod.cached[index]) {
      mag.props.attachToArgs(index, mod.getState(index), node)
      mod.cached[index] = 1
    }
    try {
      mod.getView(index)(mod.getState(index), node)
    } finally {
      mod.runningViewInstance = -1;
    }

  }

  mag.mod = mod;

}(mag));
<commit_msg>Update module.js<commit_after>/*
MagJS v0.29.4
http://github.com/magnumjs/mag.js
(c) Michael Glazer
License: MIT
*/
(function(mag) {

  'use strict';

  var modules = [],
    MAGNUM = mag.MAGNUM,
    controllers = []

  var mod = {
    runningViewInstance: -1,
    innerMods: [],
    cache: []
  }

  mod.getState = function(index) {
    return modules[index][1]
  }
  mod.setState = function(index, state) {
    modules[index][1] = state
  }
  mod.getView = function(index) {
    return modules[index][0]
  }
  mod.getProps = function(index) {
    return modules[index] && modules[index][2]
  }
  mod.setProps = function(index, props) {
    return modules[index][2] = props
  }
  mod.remove = function(key) {
    //remove mod completely
    if (modules[key]) modules[key] = 0
  }
  mod.getId = function(index) {
    return modules[index] && modules[index][3]
  }
  mod.exists = function(index) {
    return typeof modules[index] != 'undefined'
  }
  mod.setFrameId = function(index, fid) {
    modules[index][4] = fid;
  }
  mod.getFrameId = function(index) {
    return modules[index][4];
  }
  mod.getMod = function(index) {
    return modules[index][5]
  }

  var bindMethods = (obj, context) => {
    context = context || obj;

    for (var key in obj) {
      var val = obj[key];

      if (typeof val == 'function' && !~['controller', 'view'].indexOf(key)) {
        obj[key] = val.bind(context);
      }
    }

    return obj;
  }

function freeze(obj){
  return Object.freeze(mag.utils.copy(obj))
}

  mod.submodule = function(id, index, module, props) {
    if (modules[index]) {
      // new call to existing
      // update props, merge with existing if same key
      if (props.key && props.key == mod.getProps(index).key) {
        mod.setProps(index, mag.utils.copy(mag.utils.merge(mod.getProps(index), props)));
      } else {
        mod.setProps(index, props);
      }
      // reinitialize the controller ?
      return modules[index]
    }

    module = mag.utils.copy(module)
    bindMethods(module, module)

    modules[index] = [0, 0, 0, 0, 0, 0]
    mod.setProps(index, props);
    var controller = function(context) {
        module.props = freeze(mod.getProps(index))
        module.state = context;
        module.element = mag.getNode(id);
        return (module.controller || function() {}).call(context, module.props) || context
      },
      view = function(index, state, ele) {
        module.element = ele;
        module.state = state;
        module.props = freeze(mod.getProps(index))
        module.view && module.view.call(module, state, module.props, ele)
      }.bind({}, index),
      output = {
        controller: controller,
        view: view
      }
    modules[index][0] = output.view;
    modules[index][3] = id;
    modules[index][1] = getController(output.controller, index, id);
    modules[index][5] = module;
    // register the module
    return modules[index]
  }

  var timers = [];

  function findMissing(change, element) {
    var prop = change.name;
    if (typeof change.object[change.name] == 'undefined' && prop[0] != '_') {
      // prop might be hierarchical?
      // getparent Object property chain?

      // get value of property from DOM
      var a = mag.fill.find(element, prop),
        greedy = prop[0] === '$',
        // can be an array, object or string
        // for each
        tmp = [];

      if (!a.length) return MAGNUM;

      a.forEach(function(item) {
        var i;
        if (item && item.type && !~['submit', 'button'].indexOf(item.type)) {
          if (item.value && item.value.length > 0) {

            if (item.type == 'checkbox' || item.type == 'radio') {

              if (item.checked) {
                i = {};
                i._checked = true;
              }
              if (!greedy) {
                //if grouped name item
                var items = element.querySelectorAll('[name=' + item.name + ']');
                if (items.length) {
                  for (var sitem of items) {
                    if (sitem.checked) {
                      if (i) {
                        i._value = sitem.value;
                      } else {
                        i = {
                          _value: sitem.value
                        };
                      }
                    }
                  }
                }
              } else {
                if (i) {
                  i._value = item.value;
                } else {
                  i = {
                    _value: item.value
                  };
                }
              }
            } else {
              var vals = item.multiple && Array.prototype.map.call(item.selectedOptions, x => x.value)

              i = {
                _value: vals || item.value
              };
            }
          }
        } else if ((typeof change.oldValue == 'undefined' && typeof change.object[change.name] == 'undefined') && item && !~['submit', 'button'].indexOf(item.type) && item.childNodes.length == 1 && item.childNodes[0].textContent.trim()) {
          i = {
            _text: item.childNodes[0].textContent.trim()
          };
        }
        if (i) tmp.push(i);
        else if (item && !item.hasChildNodes()) {
          tmp.push(MAGNUM)
        }

      });

      if (tmp.length === 0) return;
      else if (greedy) return tmp;
      else {
        return tmp[0] && typeof tmp[0]._value != 'undefined' ? tmp[0]._value : tmp[0] && tmp[0]._text ? tmp[0]._text : tmp[0]
      }
    } else if (prop[0] == '_') {
      var attr = prop.substr(1);

      if (element.length) {
        var tmp = [];
        for (var k in element) {
          var ele = element[k]
          var has = ele[attr];
          if (typeof has == 'undefined') has = ele.getAttribute(attr)
          tmp.push(has)
        }
        return tmp;
      } else {
        var has = element[attr];
        if (typeof has == 'undefined') has = element.getAttribute(attr)
        return has;
      }
    }
  }

  var findParentNodeWithPath = function(node, path) {
    var snode = node;
    var parts = path.split('/');

    for (var prop of parts) {
      if (prop) {
        var a = mag.fill.find(snode, prop);
        if (a.length) {
          snode = a;
        }
      }
    }
    return snode
  };

  var handler = function(type, index, change) {

    if (change.type == 'get' && type != 'props' && !~mag.fill.ignorekeys.indexOf(change.name.toString()) && typeof change.oldValue == 'undefined') {

      var rootNode = mag.getNode(mod.getId(index));

      //Get parent correct parent not just root!?
      if (change.path && change.path[0] == '/') var fnode = findParentNodeWithPath(rootNode, change.path)

      // if greedy pass all elements
      if (fnode && change.path.split('/').pop()[0] != '$') {
        fnode = fnode[0]
      }

      var res = findMissing(change, fnode ? fnode : rootNode);

      if (res !== null && typeof res == 'object' && change.object) {
        mag.utils.merge(res, change.object[change.name]);
      }
      if (res) {
        mod.cached[index] = 0;
        return res;
      }
    } else if (change.type == 'set' && type != 'props' && change.object[change.name] && change.object[change.name].draw && typeof change.object[change.name].draw == 'function') {

      // setting a state prop to a module

      // call unloader for module
      mod.innerMods[mod.getId(index)] = [change.name, change.object[change.name]];
    }

    // call setup handler
    var fun = mod.getFrameId(index);
    if (typeof fun == 'function' && change.type == 'set') {
      //debounce
      cancelAnimationFrame(timers[index]);
      timers[index] = requestAnimationFrame(fun);
    }

  };

  function getController(ctrl, index, id) {

    //mod.setProps(index, mag.proxy(mod.getProps(index), handler.bind({}, 'props', index), 'prop'));

    return new ctrl(mag.proxy({}, handler.bind({}, 'state', index)));

  }

  mod.iscached = function(key) {
    //TODO: shallow equals
    var data = mag.utils.toJson([mod.getProps(key), mod.getState(key)])
    if (key in mod.cache && mod.cache[key] == data) {
      return true
    }
    mod.cache[key] = data;
  }

  mod.clear = function(key) {
    if (~key && mod.cache[key]) {
      mod.cache.splice(key, 1);
    }
  }

  mod.cached = [];
  mod.callView = function(node, index) {
    //TODO: ASYNC, promises .. ?
    mod.runningViewInstance = index;
    if (!mod.cached[index]) {
      mag.props.attachToArgs(index, mod.getState(index), node)
      mod.cached[index] = 1
    }
    try {
      mod.getView(index)(mod.getState(index), node)
    } finally {
      mod.runningViewInstance = -1;
    }

  }

  mag.mod = mod;

}(mag));
<|endoftext|>


<commit_before>import Emitter from 'tiny-emitter'
import { getSupport } from './support'
import { keyCodes } from './keycodes'

const EVT_ID = 'virtualscroll'
var support

export default class VirtualScroll {
    #options
    #el
    #emitter
    #event
    #touchStart
    #bodyTouchAction

    constructor(options) {
        // Make sure these events listeners have the proper context (for both .addEventListener and .removeEventListener)
        bindThis([
            '_onWheel',
            '_onMouseWheel',
            '_onTouchStart',
            '_onTouchMove',
            '_onKeyDown'
        ], this)

        this.#el = window
        if (options && options.el) {
            this.#el = options.el
            delete options.el
        }

        if (!support) support = getSupport()

        this.#options = Object.assign(
            {
                mouseMultiplier: 1,
                touchMultiplier: 2,
                firefoxMultiplier: 15,
                keyStep: 120,
                preventTouch: false,
                unpreventTouchClass: 'vs-touchmove-allowed',
                useKeyboard: true,
                useTouch: true
            },
            options
        )

        this.#emitter = new Emitter()
        this.#event = {
            y: 0,
            x: 0,
            deltaX: 0,
            deltaY: 0
        }
        this.#touchStart = {
            x: null,
            y: null
        }
        this.#bodyTouchAction = null

        if (this.#options.passive !== undefined) {
            this.listenerOptions = { passive: this.#options.passive }
        }
    }

    _notify(e) {
        var evt = this.#event
        evt.x += evt.deltaX
        evt.y += evt.deltaY

        this.#emitter.emit(EVT_ID, {
            x: evt.x,
            y: evt.y,
            deltaX: evt.deltaX,
            deltaY: evt.deltaY,
            originalEvent: e
        })
    }

    _onWheel(e) {
        var options = this.#options
        var evt = this.#event

        // In Chrome and in Firefox (at least the new one)
        evt.deltaX = e.wheelDeltaX || e.deltaX * -1
        evt.deltaY = e.wheelDeltaY || e.deltaY * -1

        // for our purpose deltamode = 1 means user is on a wheel mouse, not touch pad
        // real meaning: https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent#Delta_modes
        if (support.isFirefox && e.deltaMode === 1) {
            evt.deltaX *= options.firefoxMultiplier
            evt.deltaY *= options.firefoxMultiplier
        }

        evt.deltaX *= options.mouseMultiplier
        evt.deltaY *= options.mouseMultiplier

        this._notify(e)
    }

    _onMouseWheel(e) {
        var evt = this.#event

        // In Safari, IE and in Chrome if 'wheel' isn't defined
        evt.deltaX = e.wheelDeltaX ? e.wheelDeltaX : 0
        evt.deltaY = e.wheelDeltaY ? e.wheelDeltaY : e.wheelDelta

        this._notify(e)
    }

    _onTouchStart(e) {
        var t = e.targetTouches ? e.targetTouches[0] : e
        this.#touchStart.x = t.pageX
        this.#touchStart.y = t.pageY
    }

    _onTouchMove(e) {
        var options = this.#options
        if (
            options.preventTouch &&
            !e.target.classList.contains(options.unpreventTouchClass)
        ) {
            e.preventDefault()
        }

        var evt = this.#event

        var t = e.targetTouches ? e.targetTouches[0] : e

        evt.deltaX = (t.pageX - this.#touchStart.x) * options.touchMultiplier
        evt.deltaY = (t.pageY - this.#touchStart.y) * options.touchMultiplier

        this.#touchStart.x = t.pageX
        this.#touchStart.y = t.pageY

        this._notify(e)
    }

    _onKeyDown(e) {
        var evt = this.#event
        evt.deltaX = evt.deltaY = 0
        var windowHeight = window.innerHeight - 40

        switch (e.keyCode) {
            case keyCodes.LEFT:
            case keyCodes.UP:
                evt.deltaY = this.#options.keyStep
                break

            case keyCodes.RIGHT:
            case keyCodes.DOWN:
                evt.deltaY = -this.#options.keyStep
                break
            case keyCodes.SPACE:
                evt.deltaY = windowHeight * (e.shiftKey ? 1 : -1)
                break
            default:
                return
        }

        this._notify(e)
    }

    _bind() {
        if (support.hasWheelEvent) {
            this.#el.addEventListener(
                'wheel',
                this._onWheel,
                this.listenerOptions
            )
        }

        if (support.hasMouseWheelEvent) {
            this.#el.addEventListener(
                'mousewheel',
                this._onMouseWheel,
                this.listenerOptions
            )
        }

        if (support.hasTouch && this.#options.useTouch) {
            this.#el.addEventListener(
                'touchstart',
                this._onTouchStart,
                this.listenerOptions
            )
            this.#el.addEventListener(
                'touchmove',
                this._onTouchMove,
                this.listenerOptions
            )
        }

        if (support.hasPointer && support.hasTouchWin) {
            this.#bodyTouchAction = document.body.style.msTouchAction
            document.body.style.msTouchAction = 'none'
            this.#el.addEventListener('MSPointerDown', this._onTouchStart, true)
            this.#el.addEventListener('MSPointerMove', this._onTouchMove, true)
        }

        if (support.hasKeyDown && this.#options.useKeyboard) {
            document.addEventListener('keydown', this._onKeyDown)
        }
    }

    _unbind() {
        if (support.hasWheelEvent) {
            this.#el.removeEventListener('wheel', this._onWheel)
        }

        if (support.hasMouseWheelEvent) {
            this.#el.removeEventListener('mousewheel', this._onMouseWheel)
        }

        if (support.hasTouch) {
            this.#el.removeEventListener('touchstart', this._onTouchStart)
            this.#el.removeEventListener('touchmove', this._onTouchMove)
        }

        if (support.hasPointer && support.hasTouchWin) {
            document.body.style.msTouchAction = this.#bodyTouchAction
            this.#el.removeEventListener(
                'MSPointerDown',
                this._onTouchStart,
                true
            )
            this.#el.removeEventListener(
                'MSPointerMove',
                this._onTouchMove,
                true
            )
        }

        if (support.hasKeyDown && this.#options.useKeyboard) {
            document.removeEventListener('keydown', this._onKeyDown)
        }
    }

    on(cb, ctx) {
        this.#emitter.on(EVT_ID, cb, ctx)

        var events = this.#emitter.e
        if (events && events[EVT_ID] && events[EVT_ID].length === 1)
            this._bind()
    }

    off(cb, ctx) {
        this.#emitter.off(EVT_ID, cb, ctx)

        var events = this.#emitter.e
        if (!events[EVT_ID] || events[EVT_ID].length <= 0) this._unbind()
    }

    destroy() {
        this.#emitter.off()
        this._unbind()
    }
}

function bindThis(fns, ctx) {
    fns.forEach(fn => {
        ctx[fn] = ctx[fn].bind(ctx)
    })
}
<commit_msg>replace bindThis by scoped arrow methods<commit_after>import Emitter from 'tiny-emitter'
import { getSupport } from './support'
import { keyCodes } from './keycodes'

const EVT_ID = 'virtualscroll'
var support

export default class VirtualScroll {
    #options
    #el
    #emitter
    #event
    #touchStart
    #bodyTouchAction

    constructor(options) {
        this.#el = window
        if (options && options.el) {
            this.#el = options.el
            delete options.el
        }

        if (!support) support = getSupport()

        this.#options = Object.assign(
            {
                mouseMultiplier: 1,
                touchMultiplier: 2,
                firefoxMultiplier: 15,
                keyStep: 120,
                preventTouch: false,
                unpreventTouchClass: 'vs-touchmove-allowed',
                useKeyboard: true,
                useTouch: true
            },
            options
        )

        this.#emitter = new Emitter()
        this.#event = {
            y: 0,
            x: 0,
            deltaX: 0,
            deltaY: 0
        }
        this.#touchStart = {
            x: null,
            y: null
        }
        this.#bodyTouchAction = null

        if (this.#options.passive !== undefined) {
            this.listenerOptions = { passive: this.#options.passive }
        }
    }

    _notify(e) {
        var evt = this.#event
        evt.x += evt.deltaX
        evt.y += evt.deltaY

        this.#emitter.emit(EVT_ID, {
            x: evt.x,
            y: evt.y,
            deltaX: evt.deltaX,
            deltaY: evt.deltaY,
            originalEvent: e
        })
    }

    _onWheel = (e) => {
        var options = this.#options
        var evt = this.#event

        // In Chrome and in Firefox (at least the new one)
        evt.deltaX = e.wheelDeltaX || e.deltaX * -1
        evt.deltaY = e.wheelDeltaY || e.deltaY * -1

        // for our purpose deltamode = 1 means user is on a wheel mouse, not touch pad
        // real meaning: https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent#Delta_modes
        if (support.isFirefox && e.deltaMode === 1) {
            evt.deltaX *= options.firefoxMultiplier
            evt.deltaY *= options.firefoxMultiplier
        }

        evt.deltaX *= options.mouseMultiplier
        evt.deltaY *= options.mouseMultiplier

        this._notify(e)
    }

    _onMouseWheel = (e) => {
        var evt = this.#event

        // In Safari, IE and in Chrome if 'wheel' isn't defined
        evt.deltaX = e.wheelDeltaX ? e.wheelDeltaX : 0
        evt.deltaY = e.wheelDeltaY ? e.wheelDeltaY : e.wheelDelta

        this._notify(e)
    }

    _onTouchStart = (e) => {
        var t = e.targetTouches ? e.targetTouches[0] : e
        this.#touchStart.x = t.pageX
        this.#touchStart.y = t.pageY
    }

    _onTouchMove = (e) => {
        var options = this.#options
        if (
            options.preventTouch &&
            !e.target.classList.contains(options.unpreventTouchClass)
        ) {
            e.preventDefault()
        }

        var evt = this.#event

        var t = e.targetTouches ? e.targetTouches[0] : e

        evt.deltaX = (t.pageX - this.#touchStart.x) * options.touchMultiplier
        evt.deltaY = (t.pageY - this.#touchStart.y) * options.touchMultiplier

        this.#touchStart.x = t.pageX
        this.#touchStart.y = t.pageY

        this._notify(e)
    }

    _onKeyDown = (e) => {
        var evt = this.#event
        evt.deltaX = evt.deltaY = 0
        var windowHeight = window.innerHeight - 40

        switch (e.keyCode) {
            case keyCodes.LEFT:
            case keyCodes.UP:
                evt.deltaY = this.#options.keyStep
                break

            case keyCodes.RIGHT:
            case keyCodes.DOWN:
                evt.deltaY = -this.#options.keyStep
                break
            case keyCodes.SPACE:
                evt.deltaY = windowHeight * (e.shiftKey ? 1 : -1)
                break
            default:
                return
        }

        this._notify(e)
    }

    _bind() {
        if (support.hasWheelEvent) {
            this.#el.addEventListener(
                'wheel',
                this._onWheel,
                this.listenerOptions
            )
        }

        if (support.hasMouseWheelEvent) {
            this.#el.addEventListener(
                'mousewheel',
                this._onMouseWheel,
                this.listenerOptions
            )
        }

        if (support.hasTouch && this.#options.useTouch) {
            this.#el.addEventListener(
                'touchstart',
                this._onTouchStart,
                this.listenerOptions
            )
            this.#el.addEventListener(
                'touchmove',
                this._onTouchMove,
                this.listenerOptions
            )
        }

        if (support.hasPointer && support.hasTouchWin) {
            this.#bodyTouchAction = document.body.style.msTouchAction
            document.body.style.msTouchAction = 'none'
            this.#el.addEventListener('MSPointerDown', this._onTouchStart, true)
            this.#el.addEventListener('MSPointerMove', this._onTouchMove, true)
        }

        if (support.hasKeyDown && this.#options.useKeyboard) {
            document.addEventListener('keydown', this._onKeyDown)
        }
    }

    _unbind() {
        if (support.hasWheelEvent) {
            this.#el.removeEventListener('wheel', this._onWheel)
        }

        if (support.hasMouseWheelEvent) {
            this.#el.removeEventListener('mousewheel', this._onMouseWheel)
        }

        if (support.hasTouch) {
            this.#el.removeEventListener('touchstart', this._onTouchStart)
            this.#el.removeEventListener('touchmove', this._onTouchMove)
        }

        if (support.hasPointer && support.hasTouchWin) {
            document.body.style.msTouchAction = this.#bodyTouchAction
            this.#el.removeEventListener(
                'MSPointerDown',
                this._onTouchStart,
                true
            )
            this.#el.removeEventListener(
                'MSPointerMove',
                this._onTouchMove,
                true
            )
        }

        if (support.hasKeyDown && this.#options.useKeyboard) {
            document.removeEventListener('keydown', this._onKeyDown)
        }
    }

    on(cb, ctx) {
        this.#emitter.on(EVT_ID, cb, ctx)

        var events = this.#emitter.e
        if (events && events[EVT_ID] && events[EVT_ID].length === 1)
            this._bind()
    }

    off(cb, ctx) {
        this.#emitter.off(EVT_ID, cb, ctx)

        var events = this.#emitter.e
        if (!events[EVT_ID] || events[EVT_ID].length <= 0) this._unbind()
    }

    destroy() {
        this.#emitter.off()
        this._unbind()
    }
}<|endoftext|>