<commit_before>//
// Vows.js - asynchronous event-based BDD for node.js
//
//   usage:
//
//       var vows = require('vows');
//
//       vows.describe('Deep Thought').addVows(function () {
//           question('what is the answer to the universe?').addVow(function (answer) {
//               assert.equals(answer, 42);
//           }, 'it should know the answer to the ultimate question of life');
//       });
//
var path = require('path');

require.paths.unshift(__dirname);

var sys = require('sys'),
    assert = require('assert'),
    events = require('events'),
    eyes = require('eyes').inspector({ stream: null }),
    vows = exports;

// Options
vows.options = {
    Emitter: events.EventEmitter,
    brief: false,
    json: false,
    matcher: /.*/
};

vows.__defineGetter__('reporter', function () {
    if (vows.options.json) {
        return require('vows/reporters/json');
    } else {
        return require('vows/reporters/console');
    }
});

var stylize = require('vows/reporters/console').stylize;

// Keeps track of the outcome of vows.
var total  = 0, honored = 0,
    broken = 0, errored = 0,

    start,  // Start time
    end,    // End time
    suites; // Number of test suites added by `addVows()`

// Context stack, used in addVow() to keep track
var lastContext;

// Output buffer
var buffer;

var argv = [];
//
// Parse command-line parameters
//
for (var i = 0, arg; i < process.argv.length; i++) {
    arg = process.argv[i];

    if (arg === __filename) { continue }

    if (arg[0] !== '-') {
        argv.push(arg);
    } else {
        arg = arg.match(/^--?(.*)/)[1];

        if (arg[0] === 'R') {
            vows.options.matcher = new(RegExp)(arg.slice(1));
        } else if (arg in vows.options) {
            vows.options[arg] = true;
        }
    }
}

// Get rid of process runner
// ('node' in most cases)
argv = argv.slice(1);

//
// Assertion Macros
//
vows.macros = require('vows/macros');

assert.AssertionError.prototype.toString = function () {
    var that = this,
        source = this.stack.match(/([a-zA-Z0-9_-]+\.js)(:\d+):\d+/);

    function parse(str) {
        return str.replace(/{actual}/g,   inspect(that.actual)).
                   replace(/{expected}/g, inspect(that.expected)).
                   replace(/{operator}/g, stylize(that.operator, 'bold'));
    }

    if (this.message) {
        return stylize(parse(this.message), 'yellow') +
               stylize(' // ' + source[1] + source[2], 'grey');
    } else {
        return stylize([
            this.expected,
            this.operator,
            this.actual
        ].join(' '), 'yellow');
    }
}

//
// This function gets added to events.EventEmitter.prototype, by default.
// It's essentially a wrapper around `addCallback`, which adds all the specification
// goodness.
//
function addVow(/* description & callback */) {
    var desc, callback, context = '', vow = {}, args = arguments;

    total++;

    if (args.length > 1) {
        // Sometimes it might be nicer to pass the proof first,
        // and the description second, so we let the user choose
        // the order.
        if (typeof(args[0]) === "function") {
            vow.callback = args[0], vow.description = args[1];
        } else {
            vow.callback = args[1], vow.description = args[0];
        }
    } else if (args[0].callback && args[0].context) {
        vow = args[0];
    } else {
        throw new(Error)("wrong argument type for addVow().");
    }

    return this.addListener("success", function () {
        var args = Array.prototype.slice.call(arguments);
        // If the callback is expecting two or more arguments,
        // pass the error as the first (null) and the result after.
        if (vow.callback.length >= 2) {
            args.unshift(null);
        }
        runTest(args);
        tryFinish(vows.remaining, vow.promise);

    }).addListener("error", function (err) {
        var exception;

        if (vow.callback.length >= 2) {
            runTest([err]);
        } else {
            exception = { type: 'promise', error: err };
            errored++;
            output('errored', exception);
        }
        tryFinish(vows.remaining, vow.promise);
    });

    function runTest(args) {
        var exception, topic, status;

        // Run the test, and try to catch `AssertionError`s and other exceptions;
        // increment counters accordingly.
        try {
            vow.callback.apply(vow.binding || null, args);
            output('honored', exception);
            honored++;
        } catch (e) {
            if (e.name && e.name.match(/AssertionError/)) {
                exception = e.toString();
                output('broken', exception);
                broken++;
            } else {
                exception = e.stack || e.message || e.toString() || e;
                errored++;
                output('errored', exception);
            }
        }
    }

    function output(status, exception) {
        if (exception || !vows.options.brief) {
            if (vow.context && lastContext !== vow.context) {
                lastContext = vow.context;
                vows.reporter.report(['context', vow.context]);
            }
            vows.reporter.report(['vow', {
                title: vow.description,
                status: status,
                exception: exception || null
            }]);
        }
    }
};

function Context(vow, ctx, env) {
    var that = this;

    this.tests = vow.callback;
    this.topics = (ctx.topics || []).slice(0);
    this.emitter = null;
    this.env = env || {};
    this.env.context = this;
    this.env.__defineGetter__('callback', function () {
        that._callback = true;
        return function (e, res) {
            var args = Array.prototype.slice.call(arguments, 1);
            if (e) { that.emitter.emit('error', e) }
            else   { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) }
        };
    });
    this.name = (ctx.name ? ctx.name + ' ' : '') +
                (vow.description || '');
}

function addVows(tests) {
    var promise = new(events.EventEmitter);
    var remaining = 0;

    suites++;

    vows.promises.push(promise);

    if (typeof(tests) === 'object') {
        if ('topic' in tests) {
            throw new(Error)("missing top-level context.");
        }
        // Count the number of vows/promises expected to fire,
        // so we know when the tests are over.
        // We match the keys against `matcher`, to decide
        // whether or not they should be included in the test.
        (function count(tests) {
            var match = false;
            remaining++;
            Object.keys(tests).filter(function (k) {
                return k !== 'topic';
            }).forEach(function (key) {
                if (typeof(tests[key]) === "object") {
                    if (! (match = count(tests[key]) ||
                           match || vows.options.matcher.test(key))) {
                        delete tests[key];
                        remaining--;
                    }
                }
            });
            return match;
        })(tests);
    }

    this.addListener("end", function (honored, broken, errored) {
        var topic, vow, env;

        vows.remaining += remaining;

        if (typeof(tests) === 'function') {
            return tests.call(null);
        } else {
            // The test runner, it calls itself recursively, passing the
            // previous context to the inner contexts. This is so the `topic`
            // functions have access to all the previous context topics in their
            // arguments list.
            // It is defined and invoked at the same time.
            // If it encounters a `topic` function, it waits for the returned
            // promise to emit (the topic), at which point it runs the functions under it,
            // passing the topic as an argument.
            (function run(ctx, lastTopic) {
                var old = false;
                topic = ctx.tests.topic;

                if (typeof(topic) === 'function') {
                    // Run the topic, passing the previous context topics
                    topic = topic.apply(ctx.env, ctx.topics);
                }

                // If this context has a topic, store it in `lastTopic`,
                // if not, use the last topic, passed down by a parent
                // context.
                if (topic) {
                    lastTopic = topic;
                } else {
                    old   = true;
                    topic = lastTopic;
                }

                // If the topic doesn't return an event emitter (such as a promise),
                // we create it ourselves, and emit the value on the next tick.
                if (! (topic instanceof vows.options.Emitter)) {
                    ctx.emitter = new(vows.options.Emitter);

                    if (! ctx._callback) {
                        process.nextTick(function (val) {
                            return function () { ctx.emitter.emit("success", val) };
                        }(topic));
                    } else if (typeof(topic) !== "undefined" && !old) {
                        throw new(Error)("topic must not return anything when using `this.callback`.");
                    }
                    topic = ctx.emitter;
                }

                topic.addListener('success', function (val) {
                    // Once the topic fires, add the return value
                    // to the beginning of the topics list, so it
                    // becomes the first argument for the next topic.
                    // If we're using the parent topic, no need to
                    // prepend it to the topics list, or we'll get
                    // duplicates.
                    if (! old) ctx.topics.unshift(val);
                });

                // Now run the tests, or sub-contexts
                Object.keys(ctx.tests).filter(function (k) {
                    return ctx.tests[k] && k !== 'topic';
                }).forEach(function (item) {
                    // Holds the current test or context
                    vow = Object.create({
                        callback: ctx.tests[item],
                        context: ctx.name,
                        description: item,
                        binding: ctx.env,
                        promise: promise
                    });
                    env = Object.create(ctx.env);

                    // If we encounter a function, add it to the callbacks
                    // of the `topic` function, so it'll get called once the
                    // topic fires.
                    // If we encounter an object literal, we recurse, sending it
                    // our current context.
                    if (typeof(vow.callback) === 'function') {
                        topic.addVow(vow);
                    } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) {
                        // If there's a setup stage, we have to wait for it to fire,
                        // before calling the inner context. Else, just run the inner context
                        // synchronously.
                        if (topic) {
                            topic.addListener("success", function (vow, ctx, env) {
                                return function (val) {
                                    return run(new(Context)(vow, ctx, env), lastTopic);
                                };
                            }(vow, ctx, env));
                        } else {
                            run(new(Context)(vow, ctx, env), lastTopic);
                        }
                    }
                });
                // Check if we're done running the tests
                tryFinish(--vows.remaining, promise);
            // This is our initial, empty context
            })(new(Context)({ callback: tests, context: null, description: null }, {}));
        }
    });
    return promise;
}

//
// Checks if all the required tests have been run,
// and either triggers the next test suite, if any,
// or exits the process.
//
function tryFinish(remaining, promise) {
    var result, style, time;

    // Output results once all the vows have been checked
    if (honored + broken + errored === total && remaining === 0) {
        // If this isn't the last test suite in the chain,
        // emit 'end', to trigger the next test suite.
        if (promise && promise.listeners('end').length > 0) {
            vows.reporter.report(['end']);
            promise.emit('end', honored, broken, errored);
        } else {
            time = (new(Date) - start) / 1000;
            // The 'finish' event is triggered once all the tests have been run.
            // It's used by bin/vows
            vows.promises[suites - 1].emit("finish", honored, broken, errored);

            vows.reporter.report([ 'finish', {
                honored: honored,
                broken: broken,
                errored: errored,
                total: total,
                time: time
            }]);

            if ((broken || errored) && buffer.length) { sys.puts(buffer.join('\n') + '\n') }
            // Don't exit until stdout is empty
            process.stdout.addListener('drain', function () {
                process.exit(broken || errored ? 1 : 0);
            });
        }
    }
}

//
// On exit, check that all promises have been fired.
// If not, report an error message.
//
process.addListener('exit', function () {
    if (honored + broken + errored < total) {
        vows.reporter.report(['error', { error: "An EventEmitter has failed to fire.", type: 'promise' }]);
    }
});

//
// Wrap a Node.js style async function into an EventEmmitter
//
vows.prepare = function (obj, targets) {
    targets.forEach(function (target) {
        if (target in obj) {
            obj[target] = (function (fun) {
                return function () {
                    var args = Array.prototype.slice.call(arguments);
                    var ee = new(events.EventEmitter);

                    args.push(function (err /* [, data] */) {
                        var args = Array.prototype.slice.call(arguments, 1);

                        if (err) { ee.emit('error', err) }
                        else     { ee.emit.apply(ee, ['success'].concat(args)) }
                    });
                    fun.apply(obj, args);

                    return ee;
                };
            })(obj[target]);
        }
    });
    return obj;
};

// Run all vows/tests.
// It can take either a function as `tests`,
// or an object literal.
vows.describe = function (subject) {
    this.options.Emitter.prototype.addVow = addVow;
    this.options.Emitter.prototype.addVows = addVows;
    this.remaining = 0;
    this.promises = [];

    // Reset values
    total  = 0, honored = 0,
    broken = 0, errored = 0;
    buffer = [], suites = 0;

    if (!vows.options.brief) {
        this.reporter.report(['subject', subject]);
    }

    return new(events.EventEmitter)().addListener('newListener', function (e, listener) {
        var that = this;
        if (e === 'end') {
            this.removeListener(e, listener);
            process.nextTick(function () {
                start = new(Date);
                listener.call(that);
            });
        }
    });
};

// Return the `vows` object after setting some options
vows.config = function (opts) {
    for (var k in opts) { this.options[k] = opts[k] }
    return this;
};

//
// Utility functions
//

function inspect(val) {
    return '\033[1m' + eyes(val) + '\033[22m';
}

<commit_msg>Cleaned up the inner loop a little<commit_after>//
// Vows.js - asynchronous event-based BDD for node.js
//
//   usage:
//
//       var vows = require('vows');
//
//       vows.describe('Deep Thought').addVows(function () {
//           question('what is the answer to the universe?').addVow(function (answer) {
//               assert.equals(answer, 42);
//           }, 'it should know the answer to the ultimate question of life');
//       });
//
var path = require('path');

require.paths.unshift(__dirname);

var sys = require('sys'),
    assert = require('assert'),
    events = require('events'),
    eyes = require('eyes').inspector({ stream: null }),
    vows = exports;

// Options
vows.options = {
    Emitter: events.EventEmitter,
    brief: false,
    json: false,
    matcher: /.*/
};

vows.__defineGetter__('reporter', function () {
    if (vows.options.json) {
        return require('vows/reporters/json');
    } else {
        return require('vows/reporters/console');
    }
});

var stylize = require('vows/reporters/console').stylize;

// Keeps track of the outcome of vows.
var total  = 0, honored = 0,
    broken = 0, errored = 0,

    start,  // Start time
    end,    // End time
    suites; // Number of test suites added by `addVows()`

// Context stack, used in addVow() to keep track
var lastContext;

// Output buffer
var buffer;

var argv = [];
//
// Parse command-line parameters
//
for (var i = 0, arg; i < process.argv.length; i++) {
    arg = process.argv[i];

    if (arg === __filename) { continue }

    if (arg[0] !== '-') {
        argv.push(arg);
    } else {
        arg = arg.match(/^--?(.*)/)[1];

        if (arg[0] === 'R') {
            vows.options.matcher = new(RegExp)(arg.slice(1));
        } else if (arg in vows.options) {
            vows.options[arg] = true;
        }
    }
}

// Get rid of process runner
// ('node' in most cases)
argv = argv.slice(1);

//
// Assertion Macros
//
vows.macros = require('vows/macros');

assert.AssertionError.prototype.toString = function () {
    var that = this,
        source = this.stack.match(/([a-zA-Z0-9_-]+\.js)(:\d+):\d+/);

    function parse(str) {
        return str.replace(/{actual}/g,   inspect(that.actual)).
                   replace(/{expected}/g, inspect(that.expected)).
                   replace(/{operator}/g, stylize(that.operator, 'bold'));
    }

    if (this.message) {
        return stylize(parse(this.message), 'yellow') +
               stylize(' // ' + source[1] + source[2], 'grey');
    } else {
        return stylize([
            this.expected,
            this.operator,
            this.actual
        ].join(' '), 'yellow');
    }
}

//
// This function gets added to events.EventEmitter.prototype, by default.
// It's essentially a wrapper around `addCallback`, which adds all the specification
// goodness.
//
function addVow(/* description & callback */) {
    var desc, callback, context = '', vow = {}, args = arguments;

    total++;

    if (args.length > 1) {
        // Sometimes it might be nicer to pass the proof first,
        // and the description second, so we let the user choose
        // the order.
        if (typeof(args[0]) === "function") {
            vow.callback = args[0], vow.description = args[1];
        } else {
            vow.callback = args[1], vow.description = args[0];
        }
    } else if (args[0].callback && args[0].context) {
        vow = args[0];
    } else {
        throw new(Error)("wrong argument type for addVow().");
    }

    return this.addListener("success", function () {
        var args = Array.prototype.slice.call(arguments);
        // If the callback is expecting two or more arguments,
        // pass the error as the first (null) and the result after.
        if (vow.callback.length >= 2) {
            args.unshift(null);
        }
        runTest(args);
        tryFinish(vows.remaining, vow.promise);

    }).addListener("error", function (err) {
        var exception;

        if (vow.callback.length >= 2) {
            runTest([err]);
        } else {
            exception = { type: 'promise', error: err };
            errored++;
            output('errored', exception);
        }
        tryFinish(vows.remaining, vow.promise);
    });

    function runTest(args) {
        var exception, topic, status;

        // Run the test, and try to catch `AssertionError`s and other exceptions;
        // increment counters accordingly.
        try {
            vow.callback.apply(vow.binding || null, args);
            output('honored', exception);
            honored++;
        } catch (e) {
            if (e.name && e.name.match(/AssertionError/)) {
                exception = e.toString();
                output('broken', exception);
                broken++;
            } else {
                exception = e.stack || e.message || e.toString() || e;
                errored++;
                output('errored', exception);
            }
        }
    }

    function output(status, exception) {
        if (exception || !vows.options.brief) {
            if (vow.context && lastContext !== vow.context) {
                lastContext = vow.context;
                vows.reporter.report(['context', vow.context]);
            }
            vows.reporter.report(['vow', {
                title: vow.description,
                status: status,
                exception: exception || null
            }]);
        }
    }
};

function Context(vow, ctx, env) {
    var that = this;

    this.tests = vow.callback;
    this.topics = (ctx.topics || []).slice(0);
    this.emitter = null;
    this.env = env || {};
    this.env.context = this;
    this.env.__defineGetter__('callback', function () {
        that._callback = true;
        return function (e, res) {
            var args = Array.prototype.slice.call(arguments, 1);
            if (e) { that.emitter.emit('error', e) }
            else   { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) }
        };
    });
    this.name = (ctx.name ? ctx.name + ' ' : '') +
                (vow.description || '');
}

function addVows(tests) {
    var promise = new(events.EventEmitter);
    var remaining = 0;

    suites++;

    vows.promises.push(promise);

    if (typeof(tests) === 'object') {
        if ('topic' in tests) {
            throw new(Error)("missing top-level context.");
        }
        // Count the number of vows/promises expected to fire,
        // so we know when the tests are over.
        // We match the keys against `matcher`, to decide
        // whether or not they should be included in the test.
        (function count(tests) {
            var match = false;
            remaining++;
            Object.keys(tests).filter(function (k) {
                return k !== 'topic';
            }).forEach(function (key) {
                if (typeof(tests[key]) === "object") {
                    if (! (match = count(tests[key]) ||
                           match || vows.options.matcher.test(key))) {
                        delete tests[key];
                        remaining--;
                    }
                }
            });
            return match;
        })(tests);
    }

    this.addListener("end", function (honored, broken, errored) {
        var topic;

        vows.remaining += remaining;

        if (typeof(tests) === 'function') {
            return tests.call(null);
        } else {
            // The test runner, it calls itself recursively, passing the
            // previous context to the inner contexts. This is so the `topic`
            // functions have access to all the previous context topics in their
            // arguments list.
            // It is defined and invoked at the same time.
            // If it encounters a `topic` function, it waits for the returned
            // promise to emit (the topic), at which point it runs the functions under it,
            // passing the topic as an argument.
            (function run(ctx, lastTopic) {
                var old = false;
                topic = ctx.tests.topic;

                if (typeof(topic) === 'function') {
                    // Run the topic, passing the previous context topics
                    topic = topic.apply(ctx.env, ctx.topics);
                }

                // If this context has a topic, store it in `lastTopic`,
                // if not, use the last topic, passed down by a parent
                // context.
                if (topic) {
                    lastTopic = topic;
                } else {
                    old   = true;
                    topic = lastTopic;
                }

                // If the topic doesn't return an event emitter (such as a promise),
                // we create it ourselves, and emit the value on the next tick.
                if (! (topic instanceof vows.options.Emitter)) {
                    ctx.emitter = new(vows.options.Emitter);

                    if (! ctx._callback) {
                        process.nextTick(function (val) {
                            return function () { ctx.emitter.emit("success", val) };
                        }(topic));
                    } else if (typeof(topic) !== "undefined" && !old) {
                        throw new(Error)("topic must not return anything when using `this.callback`.");
                    }
                    topic = ctx.emitter;
                }

                topic.addListener('success', function (val) {
                    // Once the topic fires, add the return value
                    // to the beginning of the topics list, so it
                    // becomes the first argument for the next topic.
                    // If we're using the parent topic, no need to
                    // prepend it to the topics list, or we'll get
                    // duplicates.
                    if (! old) ctx.topics.unshift(val);
                });

                // Now run the tests, or sub-contexts
                Object.keys(ctx.tests).filter(function (k) {
                    return ctx.tests[k] && k !== 'topic';
                }).forEach(function (item) {
                    // Create a new evaluation context,
                    // inheriting from the parent one.
                    var env = Object.create(ctx.env);

                    // Holds the current test or context
                    var vow = Object.create({
                        callback: ctx.tests[item],
                        context: ctx.name,
                        description: item,
                        binding: ctx.env,
                        promise: promise
                    });

                    // If we encounter a function, add it to the callbacks
                    // of the `topic` function, so it'll get called once the
                    // topic fires.
                    // If we encounter an object literal, we recurse, sending it
                    // our current context.
                    if (typeof(vow.callback) === 'function') {
                        topic.addVow(vow);
                    } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) {
                        // If there's a setup stage, we have to wait for it to fire,
                        // before calling the inner context. Else, just run the inner context
                        // synchronously.
                        if (topic) {
                            topic.addListener("success", function (ctx) {
                                return function (val) {
                                    return run(new(Context)(vow, ctx, env), lastTopic);
                                };
                            }(ctx));
                        } else {
                            run(new(Context)(vow, ctx, env), lastTopic);
                        }
                    }
                });
                // Check if we're done running the tests
                tryFinish(--vows.remaining, promise);
            // This is our initial, empty context
            })(new(Context)({ callback: tests, context: null, description: null }, {}));
        }
    });
    return promise;
}

//
// Checks if all the required tests have been run,
// and either triggers the next test suite, if any,
// or exits the process.
//
function tryFinish(remaining, promise) {
    var result, style, time;

    // Output results once all the vows have been checked
    if (honored + broken + errored === total && remaining === 0) {
        // If this isn't the last test suite in the chain,
        // emit 'end', to trigger the next test suite.
        if (promise && promise.listeners('end').length > 0) {
            vows.reporter.report(['end']);
            promise.emit('end', honored, broken, errored);
        } else {
            time = (new(Date) - start) / 1000;
            // The 'finish' event is triggered once all the tests have been run.
            // It's used by bin/vows
            vows.promises[suites - 1].emit("finish", honored, broken, errored);

            vows.reporter.report([ 'finish', {
                honored: honored,
                broken: broken,
                errored: errored,
                total: total,
                time: time
            }]);

            if ((broken || errored) && buffer.length) { sys.puts(buffer.join('\n') + '\n') }
            // Don't exit until stdout is empty
            process.stdout.addListener('drain', function () {
                process.exit(broken || errored ? 1 : 0);
            });
        }
    }
}

//
// On exit, check that all promises have been fired.
// If not, report an error message.
//
process.addListener('exit', function () {
    if (honored + broken + errored < total) {
        vows.reporter.report(['error', { error: "An EventEmitter has failed to fire.", type: 'promise' }]);
    }
});

//
// Wrap a Node.js style async function into an EventEmmitter
//
vows.prepare = function (obj, targets) {
    targets.forEach(function (target) {
        if (target in obj) {
            obj[target] = (function (fun) {
                return function () {
                    var args = Array.prototype.slice.call(arguments);
                    var ee = new(events.EventEmitter);

                    args.push(function (err /* [, data] */) {
                        var args = Array.prototype.slice.call(arguments, 1);

                        if (err) { ee.emit('error', err) }
                        else     { ee.emit.apply(ee, ['success'].concat(args)) }
                    });
                    fun.apply(obj, args);

                    return ee;
                };
            })(obj[target]);
        }
    });
    return obj;
};

// Run all vows/tests.
// It can take either a function as `tests`,
// or an object literal.
vows.describe = function (subject) {
    this.options.Emitter.prototype.addVow = addVow;
    this.options.Emitter.prototype.addVows = addVows;
    this.remaining = 0;
    this.promises = [];

    // Reset values
    total  = 0, honored = 0,
    broken = 0, errored = 0;
    buffer = [], suites = 0;

    if (!vows.options.brief) {
        this.reporter.report(['subject', subject]);
    }

    return new(events.EventEmitter)().addListener('newListener', function (e, listener) {
        var that = this;
        if (e === 'end') {
            this.removeListener(e, listener);
            process.nextTick(function () {
                start = new(Date);
                listener.call(that);
            });
        }
    });
};

// Return the `vows` object after setting some options
vows.config = function (opts) {
    for (var k in opts) { this.options[k] = opts[k] }
    return this;
};

//
// Utility functions
//

function inspect(val) {
    return '\033[1m' + eyes(val) + '\033[22m';
}

<|endoftext|>


<commit_before>var u = 'undefined', o = 'object';

(function (klass){
  o === typeof exports ? module.exports = klass :
  u !== typeof Meteor ? Ways = klass :
  this.Ways = klass;
})(
  _module.apply(null, [
    u !== typeof Happens? Happens : require('happens'),
    u !== typeof WaysWay ? WaysWay : require('./way'),
    u !== typeof WaysFlow ? WaysFlow : require('./flow'),
    u !== typeof Meteor && Meteor.isClient ? WaysAddressBar :
    u !== typeof Meteor ? null : require('ways-addressbar')
  ])
);

function _module (Happens, Way, Flow, AddresssBar) {
  'use strict';

  // Config
  var flow = null;
  var mode = null;
  var plugin = null;
  var routes = [];

  Happens(Ways);

  /**
   * Sets up a new route
   * @param {String} pattern      Pattern string
   * @param {Function} runner     Route's action runner
   * @param {Function} destroyer  Optional, Route's action destroyer (flow mode)
   * @param {String} dependency   Optional, specifies a dependency by pattern
   */
  function Ways(pattern, runner, destroyer, dependency){

    if(flow && arguments.length < 3)
      throw new Error('In `flow` mode you must to pass at least 3 args.');

    var route = new Way(pattern, runner, destroyer, dependency);
    routes.push(route);

    return route;
  }

  Ways.flow = function (m){
    routes = [];
    if((mode = m) != null)
      flow = new Flow(routes, mode);
  };

  Ways.use = function(plug){
    plugin = new plug;
    plugin.on('url:change', plugin_url_change);
  };

  Ways.pathname = function(){
    if(plugin)
      return plugin.pathname();
  };

  Ways.go = function(url, title, state){
    if(plugin)
      plugin.push(url, title, state);
    else
      dispatch(url);
  };

  Ways.go.silent = function(url, title, state){
    if(plugin)
      plugin.replace(url, title, state);
  };

  Ways.reset = function(){
    if(plugin)
      plugin.off('url:change', plugin_url_change)

    flow = null;
    mode = null;
    plugin = null;
    routes = [];
  };

  Ways.addressbar = AddresssBar || {
    error: 'addressbar plugin can be used only in the client'
  };

  function plugin_url_change(url){
    dispatch(plugin.pathname());
  }

  function dispatch(url) {
    var i, route, url = '/' + url.replace(/^[\/]+|[\/]+$/mg, '');

    for(i in routes)
      if((route = routes[i]).matcher.test(url)){
        Ways.emit("url:change", url);
        return (flow ? flow.run(url, route) : route.run(url));
      }

    throw new Error("Route not found for url '"+ url +"'");
  };

  return Ways;
};<commit_msg>saving pathname every time a route changes, this way it'll work also when no plugin (addressbar) is in use <commit_after>var u = 'undefined', o = 'object';

(function (klass){
  o === typeof exports ? module.exports = klass :
  u !== typeof Meteor ? Ways = klass :
  this.Ways = klass;
})(
  _module.apply(null, [
    u !== typeof Happens? Happens : require('happens'),
    u !== typeof WaysWay ? WaysWay : require('./way'),
    u !== typeof WaysFlow ? WaysFlow : require('./flow'),
    u !== typeof Meteor && Meteor.isClient ? WaysAddressBar :
    u !== typeof Meteor ? null : require('ways-addressbar')
  ])
);

function _module (Happens, Way, Flow, AddresssBar) {
  'use strict';

  // Config
  var flow = null;
  var mode = null;
  var plugin = null;
  var routes = [];

  var current_pathname = null;

  Happens(Ways);

  /**
   * Sets up a new route
   * @param {String} pattern      Pattern string
   * @param {Function} runner     Route's action runner
   * @param {Function} destroyer  Optional, Route's action destroyer (flow mode)
   * @param {String} dependency   Optional, specifies a dependency by pattern
   */
  function Ways(pattern, runner, destroyer, dependency){

    if(flow && arguments.length < 3)
      throw new Error('In `flow` mode you must to pass at least 3 args.');

    var route = new Way(pattern, runner, destroyer, dependency);
    routes.push(route);

    return route;
  }

  Ways.flow = function (m){
    routes = [];
    if((mode = m) != null)
      flow = new Flow(routes, mode);
  };

  Ways.use = function(plug){
    plugin = new plug;
    plugin.on('url:change', plugin_url_change);
  };

  Ways.pathname = function(){
    if(plugin)
      return plugin.pathname();
    else
      return current_pathname;
  };

  Ways.go = function(url, title, state){
    if(plugin)
      plugin.push(url, title, state);
    else
      dispatch(url);
  };

  Ways.go.silent = function(url, title, state){
    if(plugin)
      plugin.replace(url, title, state);
  };

  Ways.reset = function(){
    if(plugin)
      plugin.off('url:change', plugin_url_change)

    flow = null;
    mode = null;
    plugin = null;
    routes = [];

    current_pathname = null;
  };

  Ways.addressbar = AddresssBar || {
    error: 'addressbar plugin can be used only in the client'
  };

  function plugin_url_change(url){
    dispatch(plugin.pathname());
  }

  function dispatch(url) {
    var i, route, url = '/' + url.replace(/^[\/]+|[\/]+$/mg, '');

    for(i in routes)
      if((route = routes[i]).matcher.test(url)){
        Ways.emit("url:change", current_pathname = url);
        return (flow ? flow.run(url, route) : route.run(url));
      }

    throw new Error("Route not found for url '"+ url +"'");
  };

  return Ways;
};<|endoftext|>


<commit_before>(function (global, factory) {
  module && module.exports ? module.exports = factory() :
  define && define.amd ? define(factory) : global.Ways = factory();
}(this, function () {

  'use strict';

  // avoid requiring files if CJS or AMD is not supported
  if((module && module.exports) || (define && defined.amd)){
    var Way = require('./way');
    var Flow = require('./flow');
    var Location = require('./location');
  };

  // Config
  var flow = null;
  var mode = null;
  // Ways.location = new Location;
  var location = null;
  var routes = [];

  /**
   * Sets up a new route
   * @param {String} pattern      Pattern string
   * @param {Function} runner     Route's action runner
   * @param {Function} destroyer  Optional, Route's action destroyer (flow mode)
   * @param {String} dependency   Optional, specifies a dependency by pattern
   */
  function Ways(pattern, runner, destroyer, dependency){
    if(flow && arguments.length < 3)
      throw new Error('In `flow` mode you must to pass at least 3 args.');

    var route = new Way(pattern, runner, destroyer, dependency);
    routes.push(route);
    return route;
  }

  Ways.init = function() {
    dispatch(Ways.pathname());
  };

  Ways.mode = function (m){
    routes = [];
    if((mode = m) != null)
      flow = new Flow(routes, mode);
  };

  Ways.use = function(location_middleware){
    location = new location_middleware;
    location.on('url:change', function() {
      dispatch(location.pathname());
    });
  };

  Ways.pathname = function(){
    if(location)
      return location.pathname();
  };

  Ways.go = function(url, title, state){
    if(location)
      location.push(url, title, state);
    else
      dispatch(url);
  };

  Ways.go.silent = function(url, title, state){
    if(location)
      location.replace(url, title, state);
  };

  Ways.reset = function(){
    flow = null;
    mode = null;
    routes = [];
  };

  function dispatch(url) {
    var i, url = '/' + url.replace(/^[\/]+|[\/]+$/mg, '');

    for(i in routes)
      if(routes[i].matcher.test(url))
        return run(url, routes[i]);

    throw new Error("Route not found for url '"+ url +"'");
  };

  function run(url, route) {
    flow ? flow.run(url, route) : route.run(url);
    return route;
  };

  return Ways;
}));<commit_msg>removing init method (gonna be handled by plugins)<commit_after>(function (global, factory) {
  module && module.exports ? module.exports = factory() :
  define && define.amd ? define(factory) : global.Ways = factory();
}(this, function () {

  'use strict';

  // avoid requiring files if CJS or AMD is not supported
  if((module && module.exports) || (define && defined.amd)){
    var Way = require('./way');
    var Flow = require('./flow');
    var Location = require('./location');
  };

  // Config
  var flow = null;
  var mode = null;
  // Ways.location = new Location;
  var location = null;
  var routes = [];

  /**
   * Sets up a new route
   * @param {String} pattern      Pattern string
   * @param {Function} runner     Route's action runner
   * @param {Function} destroyer  Optional, Route's action destroyer (flow mode)
   * @param {String} dependency   Optional, specifies a dependency by pattern
   */
  function Ways(pattern, runner, destroyer, dependency){
    if(flow && arguments.length < 3)
      throw new Error('In `flow` mode you must to pass at least 3 args.');

    var route = new Way(pattern, runner, destroyer, dependency);
    routes.push(route);
    return route;
  }

  Ways.mode = function (m){
    routes = [];
    if((mode = m) != null)
      flow = new Flow(routes, mode);
  };

  Ways.use = function(location_middleware){
    location = new location_middleware;
    location.on('url:change', function() {
      dispatch(location.pathname());
    });
  };

  Ways.pathname = function(){
    if(location)
      return location.pathname();
  };

  Ways.go = function(url, title, state){
    if(location)
      location.push(url, title, state);
    else
      dispatch(url);
  };

  Ways.go.silent = function(url, title, state){
    if(location)
      location.replace(url, title, state);
  };

  Ways.reset = function(){
    flow = null;
    mode = null;
    routes = [];
  };

  function dispatch(url) {
    var i, url = '/' + url.replace(/^[\/]+|[\/]+$/mg, '');

    for(i in routes)
      if(routes[i].matcher.test(url))
        return run(url, routes[i]);

    throw new Error("Route not found for url '"+ url +"'");
  };

  function run(url, route) {
    flow ? flow.run(url, route) : route.run(url);
    return route;
  };

  return Ways;
}));<|endoftext|>


<commit_before>import Container from 'react-container';
import dialogs from 'cordova-dialogs';
import React from 'react';
import Tappable from 'react-tappable';
import { UI } from 'touchstonejs';

const scrollable = Container.initScrollable();

// html5 input types for testing
// omitted: button, checkbox, radio, image, hidden, reset, submit
const HTML5_INPUT_TYPES = ['color', 'date', 'datetime', 'datetime-local', 'email', 'file', 'month', 'number', 'password', 'range', 'search', 'tel', 'text', 'time', 'url', 'week'];
const FLAVOURS = [
	{ label: 'Vanilla',    value: 'vanilla' },
	{ label: 'Chocolate',  value: 'chocolate' },
	{ label: 'Caramel',    value: 'caramel' },
	{ label: 'Strawberry', value: 'strawberry' }
];

module.exports = React.createClass({
	statics: {
		navigationBar: 'main',
		getNavigation () {
			return {
				title: 'Forms'
			}
		}
	},

	getInitialState () {
		return {
			flavourLabelSelect: 'chocolate',
			flavourRadioList: 'chocolate',
			switchValue: true
		}
	},

	handleRadioListChange (key, newValue) {
		console.log('handleFlavourChange:', key, newValue);
		let newState = {};
		newState[key] = newValue;

		this.setState(newState);
	},

	handleLabelSelectChange (key, event) {
		console.log('handleFlavourChange:', key, event.target.value);
		let newState = {};
		newState[key] = event.target.value;

		this.setState(newState);
	},

	handleSwitch (key, event) {
		let newState = {};
		newState[key] = !this.state[key];

		this.setState(newState);
	},

	alert (message) {
		dialogs.alert(message, function() {}, null)
	},

	// used for testing
	renderInputTypes () {
		return HTML5_INPUT_TYPES.map(type => {
			return <UI.LabelInput key={type} type={type} label={type} placeholder={type} />;
		});
	},

	showDatePicker () {
		this.setState({datePicker: true});
	},

	handleDatePickerChange (d) {
		this.setState({datePicker: false, date: d});
	},

	formatDate (date) {
		if (date) {
			return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
		}
	},

	render () {

		return (
			<Container fill>
				<Container scrollable={scrollable}>
					{/*<UI.Group>
						<UI.GroupHeader>Input Type Experiment</UI.GroupHeader>
						<UI.GroupBody>
							{this.renderInputTypes()}
						</UI.GroupBody>
					</UI.Group>*/}
					<UI.Group>
						<UI.GroupHeader>Checkbox</UI.GroupHeader>
						<UI.GroupBody>
							<UI.Item>
								<UI.ItemInner>
									<UI.FieldLabel>Switch</UI.FieldLabel>
									<UI.Switch onTap={this.handleSwitch.bind(this, 'switchValue')} on={this.state.switchValue} />
								</UI.ItemInner>
							</UI.Item>
							<UI.Item>
								<UI.ItemInner>
									<UI.FieldLabel>Disabled</UI.FieldLabel>
									<UI.Switch disabled />
								</UI.ItemInner>
							</UI.Item>
						</UI.GroupBody>
					</UI.Group>
					<UI.Group>
						<UI.GroupHeader>Radio</UI.GroupHeader>
						<UI.GroupBody>
							<UI.RadioList value={this.state.flavourRadioList} onChange={this.handleRadioListChange.bind(this, 'flavourRadioList')} options={FLAVOURS} />
						</UI.GroupBody>
					</UI.Group>
					<UI.Group>
						<UI.GroupHeader>Inputs</UI.GroupHeader>
						<UI.GroupBody>
							<UI.Input placeholder="Default" />
							<UI.Input defaultValue="With Value" placeholder="Placeholder" />
							<UI.Textarea defaultValue="Longtext is good for bios etc." placeholder="Longtext" />
						</UI.GroupBody>
					</UI.Group>
					<UI.Group>
						<UI.GroupHeader>Labelled Inputs</UI.GroupHeader>
						<UI.GroupBody>
							<UI.LabelInput type="email" label="Email"   placeholder="your.name@example.com" />
							<Tappable component="label" onTap={this.showDatePicker}>
								<UI.LabelValue label="Date" value={this.formatDate(this.state.date)} placeholder="Select a date" />
							</Tappable>
							<UI.LabelInput type="url"   label="URL"     placeholder="http://www.yourwebsite.com" />
							<UI.LabelInput noedit       label="No Edit" defaultValue="Un-editable, scrollable, selectable content" />
							<UI.LabelSelect label="Flavour" value={this.state.flavourLabelSelect} onChange={this.handleLabelSelectChange.bind(this, 'flavourLabelSelect')} options={FLAVOURS} />
						</UI.GroupBody>
					</UI.Group>
					<UI.Button type="primary" onTap={this.alert.bind(this, 'You clicked the Primary Button')}>
						Primary Button
					</UI.Button>
					<UI.Button onTap={this.alert.bind(this, 'You clicked the Default Button')}>
						Default Button
					</UI.Button>
					<UI.Button type="danger" onTap={this.alert.bind(this, 'You clicked the Danger Button')}>
						Danger Button
					</UI.Button>
					<UI.Button type="danger" onTap={this.alert.bind(this, 'You clicked the Danger Button')} disabled>
						Disabled Button
					</UI.Button>
				</Container>
				<UI.DatePickerPopup visible={this.state.datePicker} date={this.state.date} onChange={this.handleDatePickerChange}/>
			</Container>
		);
	}
});
<commit_msg>Disable unpublished components<commit_after>import Container from 'react-container';
import dialogs from 'cordova-dialogs';
import React from 'react';
import Tappable from 'react-tappable';
import { UI } from 'touchstonejs';

const scrollable = Container.initScrollable();

// html5 input types for testing
// omitted: button, checkbox, radio, image, hidden, reset, submit
const HTML5_INPUT_TYPES = ['color', 'date', 'datetime', 'datetime-local', 'email', 'file', 'month', 'number', 'password', 'range', 'search', 'tel', 'text', 'time', 'url', 'week'];
const FLAVOURS = [
	{ label: 'Vanilla',    value: 'vanilla' },
	{ label: 'Chocolate',  value: 'chocolate' },
	{ label: 'Caramel',    value: 'caramel' },
	{ label: 'Strawberry', value: 'strawberry' }
];

module.exports = React.createClass({
	statics: {
		navigationBar: 'main',
		getNavigation () {
			return {
				title: 'Forms'
			}
		}
	},

	getInitialState () {
		return {
			flavourLabelSelect: 'chocolate',
			flavourRadioList: 'chocolate',
			switchValue: true
		}
	},

	handleRadioListChange (key, newValue) {
		console.log('handleFlavourChange:', key, newValue);
		let newState = {};
		newState[key] = newValue;

		this.setState(newState);
	},

	handleLabelSelectChange (key, event) {
		console.log('handleFlavourChange:', key, event.target.value);
		let newState = {};
		newState[key] = event.target.value;

		this.setState(newState);
	},

	handleSwitch (key, event) {
		let newState = {};
		newState[key] = !this.state[key];

		this.setState(newState);
	},

	alert (message) {
		dialogs.alert(message, function() {}, null)
	},

	// used for testing
	renderInputTypes () {
		return HTML5_INPUT_TYPES.map(type => {
			return <UI.LabelInput key={type} type={type} label={type} placeholder={type} />;
		});
	},

	showDatePicker () {
		this.setState({datePicker: true});
	},

	handleDatePickerChange (d) {
		this.setState({datePicker: false, date: d});
	},

	formatDate (date) {
		if (date) {
			return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
		}
	},

	render () {

		return (
			<Container fill>
				<Container scrollable={scrollable}>
					{/*<UI.Group>
						<UI.GroupHeader>Input Type Experiment</UI.GroupHeader>
						<UI.GroupBody>
							{this.renderInputTypes()}
						</UI.GroupBody>
					</UI.Group>*/}
					<UI.Group>
						<UI.GroupHeader>Checkbox</UI.GroupHeader>
						<UI.GroupBody>
							<UI.Item>
								<UI.ItemInner>
									<UI.FieldLabel>Switch</UI.FieldLabel>
									<UI.Switch onTap={this.handleSwitch.bind(this, 'switchValue')} on={this.state.switchValue} />
								</UI.ItemInner>
							</UI.Item>
							<UI.Item>
								<UI.ItemInner>
									<UI.FieldLabel>Disabled</UI.FieldLabel>
									<UI.Switch disabled />
								</UI.ItemInner>
							</UI.Item>
						</UI.GroupBody>
					</UI.Group>
					<UI.Group>
						<UI.GroupHeader>Radio</UI.GroupHeader>
						<UI.GroupBody>
							<UI.RadioList value={this.state.flavourRadioList} onChange={this.handleRadioListChange.bind(this, 'flavourRadioList')} options={FLAVOURS} />
						</UI.GroupBody>
					</UI.Group>
					<UI.Group>
						<UI.GroupHeader>Inputs</UI.GroupHeader>
						<UI.GroupBody>
							<UI.Input placeholder="Default" />
							<UI.Input defaultValue="With Value" placeholder="Placeholder" />
							<UI.Textarea defaultValue="Longtext is good for bios etc." placeholder="Longtext" />
						</UI.GroupBody>
					</UI.Group>
					<UI.Group>
						<UI.GroupHeader>Labelled Inputs</UI.GroupHeader>
						<UI.GroupBody>
							<UI.LabelInput type="email" label="Email"   placeholder="your.name@example.com" />
							{/*<Tappable component="label" onTap={this.showDatePicker}>
								<UI.LabelValue label="Date" value={this.formatDate(this.state.date)} placeholder="Select a date" />
							</Tappable>*/}
							<UI.LabelInput type="url"   label="URL"     placeholder="http://www.yourwebsite.com" />
							<UI.LabelInput noedit       label="No Edit" defaultValue="Un-editable, scrollable, selectable content" />
							<UI.LabelSelect label="Flavour" value={this.state.flavourLabelSelect} onChange={this.handleLabelSelectChange.bind(this, 'flavourLabelSelect')} options={FLAVOURS} />
						</UI.GroupBody>
					</UI.Group>
					<UI.Button type="primary" onTap={this.alert.bind(this, 'You clicked the Primary Button')}>
						Primary Button
					</UI.Button>
					<UI.Button onTap={this.alert.bind(this, 'You clicked the Default Button')}>
						Default Button
					</UI.Button>
					<UI.Button type="danger" onTap={this.alert.bind(this, 'You clicked the Danger Button')}>
						Danger Button
					</UI.Button>
					<UI.Button type="danger" onTap={this.alert.bind(this, 'You clicked the Danger Button')} disabled>
						Disabled Button
					</UI.Button>
				</Container>
				{/*<UI.DatePickerPopup visible={this.state.datePicker} date={this.state.date} onChange={this.handleDatePickerChange}/>*/}
			</Container>
		);
	}
});
<|endoftext|>


<commit_before>/*
 * gui-builder - A simple WYSIWYG HTML5 app creator
 * Copyright (c) 2011-2012, Intel Corporation.
 *
 * This program is licensed under the terms and conditions of the
 * Apache License, version 2.0.  The full text of the Apache License is at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 */
"use strict";

// Layout view widget

(function($, undefined) {

    $.widget('gb.liveView', {

        options: {
            model: null,
            iframe: null,
            contentDocument: null,
        },

        _create: function() {
            var o = this.options,
                controlPanel,
                pagePanel,
                devicePanel,
                deviceToolbar,
                deviceSelect,
                addDeviceButton,
                deviceWrapper,
                rotateDeviceButton,
                widget = this;

            widget._sysDevices = {};
            widget._userDevices = {};

            o.designReset = this._designResetHandler;
            o.selectionChanged = null;
            o.activePageChanged = this._activePageChangedHandler;
            o.modelUpdated = this._modelUpdatedHandler;

            // FIXME: This should work, but $.extend of options seems to be
            //        creating a copy of the ADM, which will not containt the
            //        same nodes and events as the master
            //o.model = o.model || ADM || undefined;
            if (o.model) {
                this._bindADMEvents(o.model);
            }

            controlPanel = $('
')
                .addClass('tools')
                .addClass('hbox')
                .addClass('flex0');

            pagePanel = $('
')
                .addClass('panel-section')
                .addClass('vbox')
                .addClass('flex0')
                .css({ "min-width": 300, })  // FIXME: do this in CSS
                .pageView();
            pagePanel.pageView('option', 'model', ADM);

            devicePanel = $('
')
                .addClass('panel-section')
                .addClass('vbox')
                .addClass('flex1');

            $('
')
                .addClass('panel-section-header')
                .append('
devices
')
                .children().last()
                    .addClass('title')
                    .end()
                .end()
                .appendTo(devicePanel);

            deviceToolbar = $('
')
                .addClass('panel-section-contents')
                .appendTo(devicePanel);

            widget._deviceSelect = $('<select></select>')
                .addClass("separated")
                .appendTo(deviceToolbar)
                .change(function () {
                    $("option:selected", this).each(function () {
                        widget._screenHeight.val($(this).data('deviceInfo').screen.height);
                        widget._screenWidth.val($(this).data('deviceInfo').screen.width);
                        widget._rotating = false;
                        widget._setDevice();
                    });
            });

            $.getJSON("src/assets/devices.json", function (data) {
                widget._sysDevices = data;
                widget._refreshDeviceList(widget._deviceSelect);
                $.gb.fsUtils.read("devices.json", function(result) {
                    try {
                        widget._userDevices = $.parseJSON(result);
                        widget._refreshDeviceList(widget._deviceSelect);
                    } catch(e) {
                        alert(e);
                        return false;
                    }
                });
            });
            addDeviceButton = $('')
                .addClass("addDevice separated")
                .appendTo(deviceToolbar)
                .click( function () {
                    $("<form/>")
                        .addClass("deviceSetting")
                        .append('Device Name')
                        .append('<input required name="name"/>')
                        .append('Screen')
                        .append('<input name="screenWidth" type="number" max="10000"  style="width:4em" required size="4"/>').append('x')
                        .append('<input name="screenHeight" type="number" max="10000" style="width:4em" required size="4"/>')
                        .append('
')
                        .append('<input type="submit" class="submit" value="Done"></input>')
                        .append($('Cancel').click( function() { $(this).parent().dialog("close"); }))
                        .submit( function () {
                            var values = {},
                                form = this;
                            try{
                                $.each($(this).serializeArray(), function(i, field) {
                                        values[field.name] = field.value;
                                });
                                widget._userDevices[values.name] = widget._cloneSelectedDeviceInfo();
                                widget._modifyScreenSize(widget._userDevices[values.name], values.screenWidth, values.screenHeight);
                                widget._refreshDeviceList(widget._deviceSelect);
                                $.gb.fsUtils.write("devices.json", JSON.stringify(widget._userDevices), function(fileEntry){
                                    alert("New device " + values.name + " sucessfully created!");
                                    $(form).dialog('close');
                                });
                            }catch (e){
                               alert(e);
                            }
                            return false;
                        })
                        .dialog({title:"Add Device", modal:true, width: 400, height: 285, resizable:false });
                });
            $('Add Device').appendTo(deviceToolbar).click(function () {
                addDeviceButton.trigger('click');
            });

            rotateDeviceButton = $('')
                .addClass("rotateDevice separated")
                .appendTo(deviceToolbar)
                .click( function () {
                    var screenWidth = widget._screenWidth.val();
                    widget._rotating = !widget._rotating;
                    widget._screenWidth.val(widget._screenHeight.val());
                    widget._screenHeight.val(screenWidth);
                    widget._setDevice();
                });
            $('Rotate').appendTo(deviceToolbar).click(function () {
                rotateDeviceButton.trigger('click');
            });

            $('Screen:')
                .addClass("separated")
                .appendTo(deviceToolbar);
            widget._screenWidth =
                $('<input name="screenWidth" type="number" min="0" class="screenCoordinate"/>')
                .change( function () {
                    widget._setDevice();
                })
                .appendTo(deviceToolbar);
            $(' x ').appendTo(deviceToolbar);
            widget._screenHeight =
                $('<input name="screenHeight" type="number" min="0" class="screenCoordinate"/>')
                .change( function () {
                    widget._screenWidth.trigger('change');
                })
                .appendTo(deviceToolbar);

            controlPanel.append(pagePanel)
                .append(devicePanel)
                .appendTo(this.element);

            widget._deviceWrapper = $('
').appendTo(this.element)
                .append('');
            this.options.iframe = $('')
                .attr({id:'deviceScreen'})
                .addClass('flex1')
                .appendTo(widget._deviceWrapper);

            this.options.contentDocument =
                $(this.options.iframe[0].contentDocument);

            this.options.primaryTools = this._createPrimaryTools();
            this.options.secondaryTools = this._createSecondaryTools();

            this.refresh(null, this);

            return this;
        },

        _setOption: function(key, value) {
            switch (key) {
                // Should this REALLY be done here, or plugin registration in
                // the "host"... using the functions mapped in widget options?
                case 'model':
                    this._unbindADMEvents();
                    this._bindADMEvents(value);
                    this.refresh(null, this);
                    break;
                default:
                    break;
            }
        },

        destroy: function() {
            // TODO: unbind any ADM event handlers
            $(this.element).find('.'+this.widgetName).remove();
            this.options.primaryTools.remove();
            this.options.secondaryTools.remove();
        },

        refresh: function(event, widget) {
            var iframe,
                liveDoc;

            widget = widget || this;

            if (!widget.element.data('visible')) return;

            iframe = widget.options.iframe;
            if (iframe.length) {
                // Bind load event first to make sure it will be fired when
                // liveDoc is successfully written.
                iframe.one('load', function () {
                    var page = widget.options.model.getActivePage() || null;
                    if (page) {
                        widget._setPreviewPage(page.getProperty('id'), widget);
                    }

                    // We have to use the "$" in iframe or when you switching
                    // between live view and layout view several times, the
                    // contextMenu will reappear.
                    getOwnerWindow(liveDoc.documentElement).$(liveDoc)
                        .bind('contextmenu', function(e) {
                            e.preventDefault();
                        });
                    $('body', liveDoc).css({
                        // prevent I bar cursor over UI text
                        "cursor": "default",
                        // prevent user drags from selecting UI text
                        "-webkit-user-select": "none"
                    });
                });
                liveDoc = widget.options.contentDocument[0];
                liveDoc.open();
                liveDoc.writeln(generateHTML().html);
                liveDoc.close();
            }
        },

        // Private functions
        _refreshDeviceList: function (deviceSelect) {
            deviceSelect.empty();
            $.each(this._sysDevices, function (key, val) {
                $('<option/>').append( key )
                    .data('deviceInfo', val.Default)
                    .appendTo(deviceSelect);
            });
            $.each(this._userDevices, function (name, info) {
                    $('<option/>').append( name )
                        .data('deviceInfo', info)
                        .appendTo(deviceSelect);
            });
            deviceSelect.trigger('change');
        },

        _cloneSelectedDeviceInfo: function (deviceInfo, screenWidth, screenHeight) {
            return $.extend(true, {}, this._deviceSelect.find("option:selected").data('deviceInfo'));
        },

        _modifyScreenSize: function (deviceInfo, screenWidth, screenHeight) {
            var scaleW =  screenWidth/deviceInfo.screen.width,
            scaleH =  screenHeight/deviceInfo.screen.height;
            deviceInfo.screen.width *= scaleW;
            deviceInfo.screen.offset.left *= scaleW;
            deviceInfo.screen.offset.right *= scaleW;
            deviceInfo.screen.height *= scaleH;
            deviceInfo.screen.offset.top *= scaleH;
            deviceInfo.screen.offset.bottom *= scaleH;

            deviceInfo.skin.width = deviceInfo.screen.width + deviceInfo.screen.offset.left
                + deviceInfo.screen.offset.right ;
            deviceInfo.skin.height = deviceInfo.screen.height + deviceInfo.screen.offset.top
                + deviceInfo.screen.offset.bottom ;
        },

        _setDevice: function () {
            var deviceSkin, scaleW, scaleH,
            //First, we clone a device info and change screen property if rotated
                deviceInfo = this._cloneSelectedDeviceInfo();
            if (this._rotating) {
                $.extend(true, deviceInfo, {
                    screen: {
                        width: deviceInfo.screen.height,
                        height: deviceInfo.screen.width,
                        offset: {
                            top: deviceInfo.screen.offset.right,
                            left: deviceInfo.screen.offset.top,
                            bottom: deviceInfo.screen.offset.left,
                            right: deviceInfo.screen.offset.bottom,
                        }
                    },
                });
            }

            //If modified manully by user, scale screen offsets and recaculate skin size
            this._modifyScreenSize(deviceInfo, this._screenWidth.val(), this._screenHeight.val());

            // TODO: This may be better managed by reading and applying
            //       per-device CSS files from the filesystem at run time.
            //       By finding all *.css files under src/assets/devices, we
            //       could simply change the element "class" or custom
            //       attribute (data-device="<devicename>").  Then these
            //       dimensional properties can be set by the UX team, and
            //       never require changes to devices.json or this code...

            this.options.iframe.css({
                width: deviceInfo.screen.width + 'px',
                minWidth: deviceInfo.screen.width + 'px',
                maxWidth: deviceInfo.screen.width + 'px',
                height: deviceInfo.screen.height + 'px',
                minHeight: deviceInfo.screen.height + 'px',
                maxHeight: deviceInfo.screen.height + 'px',
                position: 'absolute',
                top: deviceInfo.screen.offset.top + 'px',
                left: deviceInfo.screen.offset.left + 'px',
            });

            this._deviceWrapper.css({
                width: deviceInfo.skin.width + 'px',
                minWidth: deviceInfo.skin.width + 'px',
                maxWidth: deviceInfo.skin.width + 'px',
                height: deviceInfo.skin.height + 'px',
                minHeight: deviceInfo.skin.height + 'px',
                maxHeight: deviceInfo.skin.height + 'px',
            });
            deviceSkin = this._deviceWrapper.find('img').attr('src', deviceInfo.skin.href);
            if (this._rotating)
                deviceSkin.css({
                    height: deviceInfo.skin.width + 'px',
                    width: deviceInfo.skin.height + 'px',
                    '-webkit-transform': 'rotate(-90deg)',
                    '-webkit-transform-origin': deviceInfo.skin.height/2 + 'px ' + deviceInfo.skin.height/2 + 'px',
                });
            else
                deviceSkin.css({
                    height: deviceInfo.skin.height + 'px',
                    width: deviceInfo.skin.width + 'px',
                    '-webkit-transform': 'rotate(0deg)',
                });
        },

        _createPrimaryTools: function() {
            return $(null);
        },

        _createSecondaryTools: function() {
            return $(null);
        },

        _bindADMEvents: function(a) {
            var o = this.options,
                d = this.designRoot;

            if (a) {
                o.model = a;

                if (o.designReset) {
                    a.bind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.bind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.bind("activePageChanged", o.activePageChanged, this);
                }

                // Since model changed, need to call our designReset hander
                // to sync up the ADMDesign modelUpdated event handler
                if (o.designReset) {
                    o.designReset({design: a.getDesignRoot()}, this);
                }
            }
        },

        _unbindADMEvents: function() {
            var o = this.options,
                a = this.options.model,
                d = this.designRoot;

            // First unbind our ADMDesign modelUpdated handler, if any...
            if (d && o.modelUpdated) {
                d.designRoot.unbind("modelUpdated", o.modelUpdated, this);
            }

            // Now unbind all ADM model event handlers, if any...
            if (a) {
                if (o.designReset) {
                    a.unbind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.unbind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.unbind("activePageChanged", o.activePageChanged, this);
                }
            }
        },

        _designResetHandler: function(event, widget) {
            var d = event && event.design, o;

            widget = widget || this;
            o = widget.options;
            d = d || o.model.getDesignRoot();

            // Do nothing if the new ADMDesign equals our currently cached one
            if (d === widget.designRoot) {
                return;
            }

            // First, unbind existing modelUpdated hander, if any...
            if (widget.designRoot && o.modelUpdated) {
                widget.designRoot.unbind("modelUpdated", o.modelUpdated,widget);
            }

            // Next, bind to modelUpdated events from new ADMDesign, if any...
            if (d && o.modelUpdated) {
                d.bind("modelUpdated", o.modelUpdated, widget);
            }

            // Then, cache the new ADMDesign reference with this instance
            widget.designRoot = d;

            // Finally, redraw our view since the ADMDesign root has changed
            widget.refresh(event, widget);
        },

        _activePageChangedHandler: function(event, widget) {
            var win,
                newPage = event && event.page,
                id = newPage && newPage.getProperty('id');

            widget = widget || this;

            // Only change if new page is valid
            if (!newPage) {
                return;
            }

            win = widget.options.contentDocument[0].defaultView;

            if (win && win.$ && win.$.mobile) {
                if (win.$.mobile.activePage &&
                    win.$.mobile.activePage.attr('id') !== id) {
                    win.$.mobile.changePage("#"+id, {transition: "none"});
                }
            }
        },

        _modelUpdatedHandler: function(event, widget) {
            widget = widget || this;
            widget.refresh();
        },

        _setPreviewPage: function (pageId, widget) {
            var win;

            widget = widget || this;
            win = widget.options.contentDocument[0].defaultView;

            if (win && win.$ && win.$.mobile) {
                win.$.mobile.changePage("#" + pageId, {transition: "none"});
            }
        },
    });
})(jQuery);
<commit_msg>[Live View] set max width of panel in live view<commit_after>/*
 * gui-builder - A simple WYSIWYG HTML5 app creator
 * Copyright (c) 2011-2012, Intel Corporation.
 *
 * This program is licensed under the terms and conditions of the
 * Apache License, version 2.0.  The full text of the Apache License is at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 */
"use strict";

// Layout view widget

(function($, undefined) {

    $.widget('gb.liveView', {

        options: {
            model: null,
            iframe: null,
            contentDocument: null,
        },

        _create: function() {
            var o = this.options,
                controlPanel,
                pagePanel,
                devicePanel,
                deviceToolbar,
                deviceSelect,
                addDeviceButton,
                deviceWrapper,
                rotateDeviceButton,
                widget = this;

            widget._sysDevices = {};
            widget._userDevices = {};

            o.designReset = this._designResetHandler;
            o.selectionChanged = null;
            o.activePageChanged = this._activePageChangedHandler;
            o.modelUpdated = this._modelUpdatedHandler;

            // FIXME: This should work, but $.extend of options seems to be
            //        creating a copy of the ADM, which will not containt the
            //        same nodes and events as the master
            //o.model = o.model || ADM || undefined;
            if (o.model) {
                this._bindADMEvents(o.model);
            }

            controlPanel = $('
')
                .addClass('tools')
                .addClass('hbox')
                .addClass('flex0');

            pagePanel = $('
')
                .addClass('panel-section')
                .addClass('vbox')
                .addClass('flex0')
                .css({ "min-width": 300,
                       "max-width": 300})  // FIXME: do this in CSS
                .pageView();
            pagePanel.pageView('option', 'model', ADM);

            devicePanel = $('
')
                .addClass('panel-section')
                .addClass('vbox')
                .addClass('flex1');

            $('
')
                .addClass('panel-section-header')
                .append('
devices
')
                .children().last()
                    .addClass('title')
                    .end()
                .end()
                .appendTo(devicePanel);

            deviceToolbar = $('
')
                .addClass('panel-section-contents')
                .appendTo(devicePanel);

            widget._deviceSelect = $('<select></select>')
                .addClass("separated")
                .appendTo(deviceToolbar)
                .change(function () {
                    $("option:selected", this).each(function () {
                        widget._screenHeight.val($(this).data('deviceInfo').screen.height);
                        widget._screenWidth.val($(this).data('deviceInfo').screen.width);
                        widget._rotating = false;
                        widget._setDevice();
                    });
            });

            $.getJSON("src/assets/devices.json", function (data) {
                widget._sysDevices = data;
                widget._refreshDeviceList(widget._deviceSelect);
                $.gb.fsUtils.read("devices.json", function(result) {
                    try {
                        widget._userDevices = $.parseJSON(result);
                        widget._refreshDeviceList(widget._deviceSelect);
                    } catch(e) {
                        alert(e);
                        return false;
                    }
                });
            });
            addDeviceButton = $('')
                .addClass("addDevice separated")
                .appendTo(deviceToolbar)
                .click( function () {
                    $("<form/>")
                        .addClass("deviceSetting")
                        .append('Device Name')
                        .append('<input required name="name"/>')
                        .append('Screen')
                        .append('<input name="screenWidth" type="number" max="10000"  style="width:4em" required size="4"/>').append('x')
                        .append('<input name="screenHeight" type="number" max="10000" style="width:4em" required size="4"/>')
                        .append('
')
                        .append('<input type="submit" class="submit" value="Done"></input>')
                        .append($('Cancel').click( function() { $(this).parent().dialog("close"); }))
                        .submit( function () {
                            var values = {},
                                form = this;
                            try{
                                $.each($(this).serializeArray(), function(i, field) {
                                        values[field.name] = field.value;
                                });
                                widget._userDevices[values.name] = widget._cloneSelectedDeviceInfo();
                                widget._modifyScreenSize(widget._userDevices[values.name], values.screenWidth, values.screenHeight);
                                widget._refreshDeviceList(widget._deviceSelect);
                                $.gb.fsUtils.write("devices.json", JSON.stringify(widget._userDevices), function(fileEntry){
                                    alert("New device " + values.name + " sucessfully created!");
                                    $(form).dialog('close');
                                });
                            }catch (e){
                               alert(e);
                            }
                            return false;
                        })
                        .dialog({title:"Add Device", modal:true, width: 400, height: 285, resizable:false });
                });
            $('Add Device').appendTo(deviceToolbar).click(function () {
                addDeviceButton.trigger('click');
            });

            rotateDeviceButton = $('')
                .addClass("rotateDevice separated")
                .appendTo(deviceToolbar)
                .click( function () {
                    var screenWidth = widget._screenWidth.val();
                    widget._rotating = !widget._rotating;
                    widget._screenWidth.val(widget._screenHeight.val());
                    widget._screenHeight.val(screenWidth);
                    widget._setDevice();
                });
            $('Rotate').appendTo(deviceToolbar).click(function () {
                rotateDeviceButton.trigger('click');
            });

            $('Screen:')
                .addClass("separated")
                .appendTo(deviceToolbar);
            widget._screenWidth =
                $('<input name="screenWidth" type="number" min="0" class="screenCoordinate"/>')
                .change( function () {
                    widget._setDevice();
                })
                .appendTo(deviceToolbar);
            $(' x ').appendTo(deviceToolbar);
            widget._screenHeight =
                $('<input name="screenHeight" type="number" min="0" class="screenCoordinate"/>')
                .change( function () {
                    widget._screenWidth.trigger('change');
                })
                .appendTo(deviceToolbar);

            controlPanel.append(pagePanel)
                .append(devicePanel)
                .appendTo(this.element);

            widget._deviceWrapper = $('
').appendTo(this.element)
                .append('');
            this.options.iframe = $('')
                .attr({id:'deviceScreen'})
                .addClass('flex1')
                .appendTo(widget._deviceWrapper);

            this.options.contentDocument =
                $(this.options.iframe[0].contentDocument);

            this.options.primaryTools = this._createPrimaryTools();
            this.options.secondaryTools = this._createSecondaryTools();

            this.refresh(null, this);

            return this;
        },

        _setOption: function(key, value) {
            switch (key) {
                // Should this REALLY be done here, or plugin registration in
                // the "host"... using the functions mapped in widget options?
                case 'model':
                    this._unbindADMEvents();
                    this._bindADMEvents(value);
                    this.refresh(null, this);
                    break;
                default:
                    break;
            }
        },

        destroy: function() {
            // TODO: unbind any ADM event handlers
            $(this.element).find('.'+this.widgetName).remove();
            this.options.primaryTools.remove();
            this.options.secondaryTools.remove();
        },

        refresh: function(event, widget) {
            var iframe,
                liveDoc;

            widget = widget || this;

            if (!widget.element.data('visible')) return;

            iframe = widget.options.iframe;
            if (iframe.length) {
                // Bind load event first to make sure it will be fired when
                // liveDoc is successfully written.
                iframe.one('load', function () {
                    var page = widget.options.model.getActivePage() || null;
                    if (page) {
                        widget._setPreviewPage(page.getProperty('id'), widget);
                    }

                    // We have to use the "$" in iframe or when you switching
                    // between live view and layout view several times, the
                    // contextMenu will reappear.
                    getOwnerWindow(liveDoc.documentElement).$(liveDoc)
                        .bind('contextmenu', function(e) {
                            e.preventDefault();
                        });
                    $('body', liveDoc).css({
                        // prevent I bar cursor over UI text
                        "cursor": "default",
                        // prevent user drags from selecting UI text
                        "-webkit-user-select": "none"
                    });
                });
                liveDoc = widget.options.contentDocument[0];
                liveDoc.open();
                liveDoc.writeln(generateHTML().html);
                liveDoc.close();
            }
        },

        // Private functions
        _refreshDeviceList: function (deviceSelect) {
            deviceSelect.empty();
            $.each(this._sysDevices, function (key, val) {
                $('<option/>').append( key )
                    .data('deviceInfo', val.Default)
                    .appendTo(deviceSelect);
            });
            $.each(this._userDevices, function (name, info) {
                    $('<option/>').append( name )
                        .data('deviceInfo', info)
                        .appendTo(deviceSelect);
            });
            deviceSelect.trigger('change');
        },

        _cloneSelectedDeviceInfo: function (deviceInfo, screenWidth, screenHeight) {
            return $.extend(true, {}, this._deviceSelect.find("option:selected").data('deviceInfo'));
        },

        _modifyScreenSize: function (deviceInfo, screenWidth, screenHeight) {
            var scaleW =  screenWidth/deviceInfo.screen.width,
            scaleH =  screenHeight/deviceInfo.screen.height;
            deviceInfo.screen.width *= scaleW;
            deviceInfo.screen.offset.left *= scaleW;
            deviceInfo.screen.offset.right *= scaleW;
            deviceInfo.screen.height *= scaleH;
            deviceInfo.screen.offset.top *= scaleH;
            deviceInfo.screen.offset.bottom *= scaleH;

            deviceInfo.skin.width = deviceInfo.screen.width + deviceInfo.screen.offset.left
                + deviceInfo.screen.offset.right ;
            deviceInfo.skin.height = deviceInfo.screen.height + deviceInfo.screen.offset.top
                + deviceInfo.screen.offset.bottom ;
        },

        _setDevice: function () {
            var deviceSkin, scaleW, scaleH,
            //First, we clone a device info and change screen property if rotated
                deviceInfo = this._cloneSelectedDeviceInfo();
            if (this._rotating) {
                $.extend(true, deviceInfo, {
                    screen: {
                        width: deviceInfo.screen.height,
                        height: deviceInfo.screen.width,
                        offset: {
                            top: deviceInfo.screen.offset.right,
                            left: deviceInfo.screen.offset.top,
                            bottom: deviceInfo.screen.offset.left,
                            right: deviceInfo.screen.offset.bottom,
                        }
                    },
                });
            }

            //If modified manully by user, scale screen offsets and recaculate skin size
            this._modifyScreenSize(deviceInfo, this._screenWidth.val(), this._screenHeight.val());

            // TODO: This may be better managed by reading and applying
            //       per-device CSS files from the filesystem at run time.
            //       By finding all *.css files under src/assets/devices, we
            //       could simply change the element "class" or custom
            //       attribute (data-device="<devicename>").  Then these
            //       dimensional properties can be set by the UX team, and
            //       never require changes to devices.json or this code...

            this.options.iframe.css({
                width: deviceInfo.screen.width + 'px',
                minWidth: deviceInfo.screen.width + 'px',
                maxWidth: deviceInfo.screen.width + 'px',
                height: deviceInfo.screen.height + 'px',
                minHeight: deviceInfo.screen.height + 'px',
                maxHeight: deviceInfo.screen.height + 'px',
                position: 'absolute',
                top: deviceInfo.screen.offset.top + 'px',
                left: deviceInfo.screen.offset.left + 'px',
            });

            this._deviceWrapper.css({
                width: deviceInfo.skin.width + 'px',
                minWidth: deviceInfo.skin.width + 'px',
                maxWidth: deviceInfo.skin.width + 'px',
                height: deviceInfo.skin.height + 'px',
                minHeight: deviceInfo.skin.height + 'px',
                maxHeight: deviceInfo.skin.height + 'px',
            });
            deviceSkin = this._deviceWrapper.find('img').attr('src', deviceInfo.skin.href);
            if (this._rotating)
                deviceSkin.css({
                    height: deviceInfo.skin.width + 'px',
                    width: deviceInfo.skin.height + 'px',
                    '-webkit-transform': 'rotate(-90deg)',
                    '-webkit-transform-origin': deviceInfo.skin.height/2 + 'px ' + deviceInfo.skin.height/2 + 'px',
                });
            else
                deviceSkin.css({
                    height: deviceInfo.skin.height + 'px',
                    width: deviceInfo.skin.width + 'px',
                    '-webkit-transform': 'rotate(0deg)',
                });
        },

        _createPrimaryTools: function() {
            return $(null);
        },

        _createSecondaryTools: function() {
            return $(null);
        },

        _bindADMEvents: function(a) {
            var o = this.options,
                d = this.designRoot;

            if (a) {
                o.model = a;

                if (o.designReset) {
                    a.bind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.bind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.bind("activePageChanged", o.activePageChanged, this);
                }

                // Since model changed, need to call our designReset hander
                // to sync up the ADMDesign modelUpdated event handler
                if (o.designReset) {
                    o.designReset({design: a.getDesignRoot()}, this);
                }
            }
        },

        _unbindADMEvents: function() {
            var o = this.options,
                a = this.options.model,
                d = this.designRoot;

            // First unbind our ADMDesign modelUpdated handler, if any...
            if (d && o.modelUpdated) {
                d.designRoot.unbind("modelUpdated", o.modelUpdated, this);
            }

            // Now unbind all ADM model event handlers, if any...
            if (a) {
                if (o.designReset) {
                    a.unbind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.unbind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.unbind("activePageChanged", o.activePageChanged, this);
                }
            }
        },

        _designResetHandler: function(event, widget) {
            var d = event && event.design, o;

            widget = widget || this;
            o = widget.options;
            d = d || o.model.getDesignRoot();

            // Do nothing if the new ADMDesign equals our currently cached one
            if (d === widget.designRoot) {
                return;
            }

            // First, unbind existing modelUpdated hander, if any...
            if (widget.designRoot && o.modelUpdated) {
                widget.designRoot.unbind("modelUpdated", o.modelUpdated,widget);
            }

            // Next, bind to modelUpdated events from new ADMDesign, if any...
            if (d && o.modelUpdated) {
                d.bind("modelUpdated", o.modelUpdated, widget);
            }

            // Then, cache the new ADMDesign reference with this instance
            widget.designRoot = d;

            // Finally, redraw our view since the ADMDesign root has changed
            widget.refresh(event, widget);
        },

        _activePageChangedHandler: function(event, widget) {
            var win,
                newPage = event && event.page,
                id = newPage && newPage.getProperty('id');

            widget = widget || this;

            // Only change if new page is valid
            if (!newPage) {
                return;
            }

            win = widget.options.contentDocument[0].defaultView;

            if (win && win.$ && win.$.mobile) {
                if (win.$.mobile.activePage &&
                    win.$.mobile.activePage.attr('id') !== id) {
                    win.$.mobile.changePage("#"+id, {transition: "none"});
                }
            }
        },

        _modelUpdatedHandler: function(event, widget) {
            widget = widget || this;
            widget.refresh();
        },

        _setPreviewPage: function (pageId, widget) {
            var win;

            widget = widget || this;
            win = widget.options.contentDocument[0].defaultView;

            if (win && win.$ && win.$.mobile) {
                win.$.mobile.changePage("#" + pageId, {transition: "none"});
            }
        },
    });
})(jQuery);
<|endoftext|>