<commit_before>/*
 * Copyright (c) 2012-2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

YUI.add('selenium-tests', function (Y, NAME) {

    var path = require('path'),
        arrowRoot = path.join(__dirname, '../../../..'),
        DriverClass = require(arrowRoot + '/lib/driver/selenium.js'),
        suite = new Y.Test.Suite(NAME),
        A = Y.Assert;

    DriverClass.wdAppPath = arrowRoot + '/tests/unit/stub/webdriver.js';

    function testDriver(sendReport) {
        var driver,
            DriverClass = require(arrowRoot + '/lib/driver/node.js'),
            config,
            testParams,
            nodeProcess,
            pNodeArgs,
            pTestParams,
            reports,
            report,
            executed = false;

        config = {testSeed: 'seed', testRunner: 'runner'};
        testParams = {test: 'test.js', param: 'value'};
        driver = new DriverClass(config, {});
        driver.executeTest({}, testParams, function (errMsg) {
            executed = true;
            if(sendReport) {
                A.isTrue(!errMsg, 'Should have executed driver');
            } else {
                A.isString(errMsg, 'Should have failed to execute driver');
            }
        });

        nodeProcess = stubProcess.curProcess;
        nodeProcess.stdout.notify('data', 'stdout log data');
        nodeProcess.stderr.notify('data', 'stderr log data');
        if(sendReport) {
            nodeProcess.stdout.notify('data',
                'log data -- TEST RESULT: {"name": "unittest", "failed": 0, "passed": 0}');
        }
        nodeProcess.notify('exit');

        reports = driver.getReports();
        if(sendReport) {
            report = JSON.parse(reports[0]);
            A.areEqual(report.name, 'unittest', 'Report should be added');
        } else {
            A.areEqual(reports.length, 0, 'No report should be added');
        }

        pNodeArgs = JSON.parse(decodeURI(nodeProcess.args[1]));
        A.areEqual(pNodeArgs.seed, 'seed', 'Seed should be passed');
        A.areEqual(pNodeArgs.runner, 'runner', 'Runner should be passed');
        A.areEqual(pNodeArgs.test, 'test.js', 'Test should be passed');
        pTestParams = JSON.parse(decodeURI(nodeProcess.args[2]));
        A.areEqual(pTestParams.param, 'value', 'Params should have been passed');

        A.isTrue(executed, 'Should have executed driver');
    }

    suite.add(new Y.Test.Case({

//        'test driver start stop': function () {
//            var driver,
//                config,
//                started = false,
//                stopped = false;
//
//            // test without selenium host
//            config = {browser: 'mybrowser'};
//            driver = new DriverClass(config, {});
//            driver.start(function (errMsg) {
//                started = true;
//                A.isString(errMsg, 'Should have failed to start driver');
//            });
//            A.isTrue(started, 'Should have started driver');
//
//            started = false;
//            config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'};
//            driver = new DriverClass(config, {});
//            driver.start(function (errMsg) {
//                started = true;
//                A.isTrue(!errMsg, 'Should successfully start driver');
//                driver.stop(function (errMsg) {
//                    stopped = true;
//                    A.isTrue(!errMsg, 'Should successfully stop driver');
//                });
//            });
//
//            A.isTrue(started, 'Should have started driver');
//            A.isTrue(stopped, 'Should have stopped driver');
//        },

        'test driver capabilities': function () {
            var driver,
                config = {},
                caps;

            driver = new DriverClass(config, {});
            caps = driver.getCapabilities();
            A.isString(caps.error, 'Should fail for no browser');

            config = {browser: 'mybrowser'};
            driver = new DriverClass(config, {});
            caps = driver.getCapabilities();
            A.areEqual(caps.browserName, 'mybrowser', 'Should return browser name');

            config = {browser: 'mybrowser-1.0'};
            driver = new DriverClass(config, {});
            caps = driver.getCapabilities();
            A.areEqual(caps.browserName, 'mybrowser', 'Should return browser name');
            A.areEqual(caps.version, '1.0', 'Should return browser version');

        },
//
//        'test navigation': function () {
//            var driver,
//                config,
//                actions;
//
//            config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'};
//            driver = new DriverClass(config, {});
//            driver.start(function (errMsg) {
//                driver.navigate('http://mypage', function () {
//                    driver.stop(function (errMsg) {
//                    });
//                });
//            });
//
//            var actions = driver.getWebDriver().actions;
//            A.isTrue('http://mypage' === actions[0].value, 'Must navigate to the page');
//        },

        'test driver error': function () {
            var driver,
                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
                executed = false;

            driver = new DriverClass(config, {});
            driver.executeTest({}, {}, function (errMsg) {
                executed = true;
                A.isString(errMsg, 'Should have failed for no test file');
            });

            A.isTrue(executed, 'Should have executed driver');
        },

//        'test execute with page load': function () {
//            var self = this,
//                driver,
//                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
//                executed = false;
//
//            driver = new DriverClass(config, {});
//            driver.createDriverJs = function () {
//                return 'driverjs';
//            };
//
//            function validate() {
//                var actions = driver.getWebDriver().actions;
//                A.isTrue(executed, 'Should have successfully executed test');
//                A.areEqual(actions[0].value, 'http://page', 'Should have navigated before test');
//            }
//
//            driver.start(function (errMsg) {
//                var webdriver = driver.getWebDriver();
//                webdriver.scriptResults['return ARROW.testReport;'] = '{"name": "functest", "failed": 0, "passed": 0}';
//
//                driver.executeTest({}, {page: 'http://page', test : 'test.js', customController : false}, function (errMsg) {
//                    executed = !errMsg;
//                    self.resume(validate);
//                });
//                self.wait();
//            });
//        },

//        'test execute with no page load': function () {
//            var self = this,
//                driver,
//                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
//                executed = false;
//
//            driver = new DriverClass(config, {});
//            driver.createDriverJs = function () {
//                return 'driverjs';
//            };
//
//            function validate() {
//                A.isTrue(executed, 'Should have successfully executed test');
//            }
//
//            driver.start(function (errMsg) {
//                var webdriver = driver.getWebDriver();
//                webdriver.scriptResults['return ARROW.testReport;'] = '{"name": "functest", "failed": 0, "passed": 0}';
//
//                driver.executeTest({}, {test: 'test.js'}, function (errMsg) {
//                    executed = !errMsg;
//                    self.resume(validate);
//                });
//                self.wait();
//            });
//        },
//
//        'test action': function () {
//            var self = this,
//                driver,
//                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
//                executed = false;
//
//            function createDriverJs() {
//                return 'driverjs';
//            }
//
//            function validate() {
//                var actions = driver.getWebDriver().actions;
//                A.isTrue(executed, 'Should have successfully executed action');
//                A.areEqual(actions[0].value, 'http://page', 'Should have navigated before test');
//            }
//
//            driver = new DriverClass(config, {});
//            driver.createDriverJs = createDriverJs;
//            driver.start(function (errMsg) {
//                var webdriver = driver.getWebDriver();
//                webdriver.scriptResults['return ARROW.actionReport;'] = '{}';
//
//                driver.executeAction({}, {page: 'http://page', action: 'action.js'}, function (errMsg) {
//                    executed = !errMsg;
//                    self.resume(validate);
//                });
//                self.wait();
//            });
//        },

//        'test action error': function () {
//            var self = this,
//                driver,
//                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
//                executed = false;
//
//            function createDriverJs() {
//                return 'driverjs';
//            }
//
//            function validate() {
//                var actions = driver.getWebDriver().actions;
//                A.isTrue(executed, 'Should have successfully executed action');
//            }
//
//            driver = new DriverClass(config, {});
//            driver.createDriverJs = createDriverJs;
//            driver.start(function (errMsg) {
//                var webdriver = driver.getWebDriver();
//                webdriver.scriptResults['return ARROW.actionReport;'] = '{"error": "action error"}';
//
//                driver.executeAction({}, {action: 'action.js'}, function (errMsg) {
//console.log("executed: " + errMsg);
//                    executed = !!errMsg; // error must not be empty
//                    self.resume(validate);
//                });
//                self.wait();
//            });
//        },

        'test getArrowServerBase': function () {
            var self = this,
                driver,
                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
                executed = false;

            driver = new DriverClass(config, {});
            A.isFalse(driver.getArrowServerBase(),"When Arrow Server is not running, getArrowServerBase should return false");
        },

        'test createDriverJs with bad testJs': function () {
            var self = this,
                driver,
                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub',testRunner: arrowRoot + '/lib/client/yuitest-runner.js', testSeed: arrowRoot + '/lib/client/yuitest-seed.js', shareLibMetaPath: arrowRoot + '/tests/unit/stub/', clientConfigName : 'client_seed.js'},

            driver = new DriverClass(config, {});
            A.isFalse(driver.createDriverJs({"test" : "not-found.js"},function(e){
                A.areEqual("Error: ENOENT, no such file or directory 'not-found.js'", e.toString(), "File not found error should be caught");
            }),"createDriverJs should return false for blank testParams");
        }
    }));

    Y.Test.Runner.add(suite);
}, '0.0.1' ,{requires:['test']});

<commit_msg>update fixed tests<commit_after>/*
 * Copyright (c) 2012-2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

YUI.add('selenium-tests', function (Y, NAME) {

    var path = require('path'),
        arrowRoot = path.join(__dirname, '../../../..'),
        DriverClass = require(arrowRoot + '/lib/driver/selenium.js'),
        suite = new Y.Test.Suite(NAME),
        A = Y.Assert;

    DriverClass.wdAppPath = arrowRoot + '/tests/unit/stub/webdriver.js';

    function testDriver(sendReport) {
        var driver,
            DriverClass = require(arrowRoot + '/lib/driver/node.js'),
            config,
            testParams,
            nodeProcess,
            pNodeArgs,
            pTestParams,
            reports,
            report,
            executed = false;

        config = {testSeed: 'seed', testRunner: 'runner'};
        testParams = {test: 'test.js', param: 'value'};
        driver = new DriverClass(config, {});
        driver.executeTest({}, testParams, function (errMsg) {
            executed = true;
            if(sendReport) {
                A.isTrue(!errMsg, 'Should have executed driver');
            } else {
                A.isString(errMsg, 'Should have failed to execute driver');
            }
        });

        nodeProcess = stubProcess.curProcess;
        nodeProcess.stdout.notify('data', 'stdout log data');
        nodeProcess.stderr.notify('data', 'stderr log data');
        if(sendReport) {
            nodeProcess.stdout.notify('data',
                'log data -- TEST RESULT: {"name": "unittest", "failed": 0, "passed": 0}');
        }
        nodeProcess.notify('exit');

        reports = driver.getReports();
        if(sendReport) {
            report = JSON.parse(reports[0]);
            A.areEqual(report.name, 'unittest', 'Report should be added');
        } else {
            A.areEqual(reports.length, 0, 'No report should be added');
        }

        pNodeArgs = JSON.parse(decodeURI(nodeProcess.args[1]));
        A.areEqual(pNodeArgs.seed, 'seed', 'Seed should be passed');
        A.areEqual(pNodeArgs.runner, 'runner', 'Runner should be passed');
        A.areEqual(pNodeArgs.test, 'test.js', 'Test should be passed');
        pTestParams = JSON.parse(decodeURI(nodeProcess.args[2]));
        A.areEqual(pTestParams.param, 'value', 'Params should have been passed');

        A.isTrue(executed, 'Should have executed driver');
    }

    suite.add(new Y.Test.Case({

//        'test driver start stop': function () {
//            var driver,
//                config,
//                started = false,
//                stopped = false;
//
//            // test without selenium host
//            config = {browser: 'mybrowser'};
//            driver = new DriverClass(config, {});
//            driver.start(function (errMsg) {
//                started = true;
//                A.isString(errMsg, 'Should have failed to start driver');
//            });
//            A.isTrue(started, 'Should have started driver');
//
//            started = false;
//            config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'};
//            driver = new DriverClass(config, {});
//            driver.start(function (errMsg) {
//                started = true;
//                A.isTrue(!errMsg, 'Should successfully start driver');
//                driver.stop(function (errMsg) {
//                    stopped = true;
//                    A.isTrue(!errMsg, 'Should successfully stop driver');
//                });
//            });
//
//            A.isTrue(started, 'Should have started driver');
//            A.isTrue(stopped, 'Should have stopped driver');
//        },

        'test driver capabilities': function () {
            var driver,
                config = {},
                caps;

            driver = new DriverClass(config, {});
            caps = driver.getCapabilities();
            A.isString(caps.error, 'Should fail for no browser');

            config = {browser: 'mybrowser'};
            driver = new DriverClass(config, {});
            caps = driver.getCapabilities();
            A.areEqual(caps.browserName, 'mybrowser', 'Should return browser name');

            config = {browser: 'mybrowser-1.0'};
            driver = new DriverClass(config, {});
            caps = driver.getCapabilities();
            A.areEqual(caps.browserName, 'mybrowser', 'Should return browser name');
            A.areEqual(caps.version, '1.0', 'Should return browser version');

        },
//
//        'test navigation': function () {
//            var driver,
//                config,
//                actions;
//
//            config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'};
//            driver = new DriverClass(config, {});
//            driver.start(function (errMsg) {
//                driver.navigate('http://mypage', function () {
//                    driver.stop(function (errMsg) {
//                    });
//                });
//            });
//
//            var actions = driver.getWebDriver().actions;
//            A.isTrue('http://mypage' === actions[0].value, 'Must navigate to the page');
//        },

        'test driver error': function () {
            var driver,
                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
                executed = false;

            driver = new DriverClass(config, {});
            driver.executeTest({}, {}, function (errMsg) {
                executed = true;
                A.isString(errMsg, 'Should have failed for no test file');
            });

            A.isTrue(executed, 'Should have executed driver');
        },

//        'test execute with page load': function () {
//            var self = this,
//                driver,
//                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
//                executed = false;
//
//            driver = new DriverClass(config, {});
//            driver.createDriverJs = function () {
//                return 'driverjs';
//            };
//
//            function validate() {
//                var actions = driver.getWebDriver().actions;
//                A.isTrue(executed, 'Should have successfully executed test');
//                A.areEqual(actions[0].value, 'http://page', 'Should have navigated before test');
//            }
//
//            driver.start(function (errMsg) {
//                var webdriver = driver.getWebDriver();
//                webdriver.scriptResults['return ARROW.testReport;'] = '{"name": "functest", "failed": 0, "passed": 0}';
//
//                driver.executeTest({}, {page: 'http://page', test : 'test.js', customController : false}, function (errMsg) {
//                    executed = !errMsg;
//                    self.resume(validate);
//                });
//                self.wait();
//            });
//        },

//        'test execute with no page load': function () {
//            var self = this,
//                driver,
//                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
//                executed = false;
//
//            driver = new DriverClass(config, {});
//            driver.createDriverJs = function () {
//                return 'driverjs';
//            };
//
//            function validate() {
//                A.isTrue(executed, 'Should have successfully executed test');
//            }
//
//            driver.start(function (errMsg) {
//                var webdriver = driver.getWebDriver();
//                webdriver.scriptResults['return ARROW.testReport;'] = '{"name": "functest", "failed": 0, "passed": 0}';
//
//                driver.executeTest({}, {test: 'test.js'}, function (errMsg) {
//                    executed = !errMsg;
//                    self.resume(validate);
//                });
//                self.wait();
//            });
//        },
//
//        'test action': function () {
//            var self = this,
//                driver,
//                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
//                executed = false;
//
//            function createDriverJs() {
//                return 'driverjs';
//            }
//
//            function validate() {
//                var actions = driver.getWebDriver().actions;
//                A.isTrue(executed, 'Should have successfully executed action');
//                A.areEqual(actions[0].value, 'http://page', 'Should have navigated before test');
//            }
//
//            driver = new DriverClass(config, {});
//            driver.createDriverJs = createDriverJs;
//            driver.start(function (errMsg) {
//                var webdriver = driver.getWebDriver();
//                webdriver.scriptResults['return ARROW.actionReport;'] = '{}';
//
//                driver.executeAction({}, {page: 'http://page', action: 'action.js'}, function (errMsg) {
//                    executed = !errMsg;
//                    self.resume(validate);
//                });
//                self.wait();
//            });
//        },

//        'test action error': function () {
//            var self = this,
//                driver,
//                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
//                executed = false;
//
//            function createDriverJs() {
//                return 'driverjs';
//            }
//
//            function validate() {
//                var actions = driver.getWebDriver().actions;
//                A.isTrue(executed, 'Should have successfully executed action');
//            }
//
//            driver = new DriverClass(config, {});
//            driver.createDriverJs = createDriverJs;
//            driver.start(function (errMsg) {
//                var webdriver = driver.getWebDriver();
//                webdriver.scriptResults['return ARROW.actionReport;'] = '{"error": "action error"}';
//
//                driver.executeAction({}, {action: 'action.js'}, function (errMsg) {
//console.log("executed: " + errMsg);
//                    executed = !!errMsg; // error must not be empty
//                    self.resume(validate);
//                });
//                self.wait();
//            });
//        },

        'test getArrowServerBase': function () {
            var self = this,
                driver,
                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub'},
                executed = false;

            driver = new DriverClass(config, {});
            A.isFalse(driver.getArrowServerBase(),"When Arrow Server is not running, getArrowServerBase should return false");
        },

        'test createDriverJs with bad testJs': function () {
            var self = this,
                driver,
                config = {browser: 'mybrowser', seleniumHost: 'http://wdhub',testRunner: arrowRoot + '/lib/client/yuitest-runner.js', testSeed: arrowRoot + '/lib/client/yuitest-seed.js', clientConfigName : 'client_seed.js'},

            driver = new DriverClass(config, {});
            A.isFalse(driver.createDriverJs({"test" : "not-found.js"},function(e){
                A.areEqual("Error: ENOENT, no such file or directory 'not-found.js'", e.toString(), "File not found error should be caught");
            }),"createDriverJs should return false for blank testParams");
        }
    }));

    Y.Test.Runner.add(suite);
}, '0.0.1' ,{requires:['test']});

<|endoftext|>


<commit_before>import fs from 'fs'
import { writeToFile } from '../../utility/file'

const allVotes = JSON.parse(fs.readFileSync('data/all-votes.json', 'utf8'))

function createReducedLookup() {
  const reducedLookup = {}
  allVotes.forEach(mp => {
    mp.votes.forEach(category => {
      const topic = category.topic
      if (!reducedLookup[topic]) {
        reducedLookup[topic] = {
          proposals: {}
        }
      }

      category.votes.forEach(vote => {
        if (!reducedLookup[topic].proposals[vote.proposal]) {
          reducedLookup[topic].proposals[vote.proposal] = {}
        }

        const voteType = vote.vote

        if (!reducedLookup[topic].proposals[vote.proposal][voteType]) {
          reducedLookup[topic].proposals[vote.proposal][voteType] = []
        }

        reducedLookup[topic].proposals[vote.proposal][voteType].push(mp.mpId)
      })
    })
  })

  writeToFile(reducedLookup, 'data/reduced-lookup.json')
  return reducedLookup
}

function createNameLookup() {
  const lookup = {}
  allVotes.forEach(mp => {
    lookup[mp.mpId] = mp.mpName
  })
  return lookup
}

export default function createFriendLookup() {
  const nameLookup = createNameLookup()
  const reducedLookup = createReducedLookup()
  const friendLookup = {}

  allVotes.forEach(mp => {
    friendLookup[mp.mpId] = {
      name: mp.mpName,
      numberOfVotes: 0,
      voteTypes: {},
      similiarMpVoteCount: {}
    }
    mp.votes.forEach(category => {
      const topic = category.topic

      category.votes.forEach(vote => {
        const voteType = vote.vote

        reducedLookup[topic].proposals[vote.proposal][voteType].forEach(voterId => {
          if (voterId !== mp.mpId) {
            if (!friendLookup[mp.mpId].similiarMpVoteCount[voterId]) {
              friendLookup[mp.mpId].similiarMpVoteCount[voterId] = 0
            }

            friendLookup[mp.mpId].similiarMpVoteCount[voterId] += 1
          }
        })

        friendLookup[mp.mpId].numberOfVotes += 1
        if (!friendLookup[mp.mpId].voteTypes[voteType]) {
          friendLookup[mp.mpId].voteTypes[voteType] = 0
        }

        friendLookup[mp.mpId].voteTypes[voteType] += 1
      })
    })

    const friendIds = Object.keys(friendLookup[mp.mpId].similiarMpVoteCount)
    const sortedFriendIds = friendIds.sort((a, b) => {
      const aValue = friendLookup[mp.mpId].similiarMpVoteCount[a]
      const bValue = friendLookup[mp.mpId].similiarMpVoteCount[b]

      return bValue - aValue
    })
    const bestFriends = sortedFriendIds.slice(0, 5)
    friendLookup[mp.mpId].bestFriends = bestFriends.map(mpId => {
      const numberOfSimilarVotes = friendLookup[mp.mpId].similiarMpVoteCount[mpId]

      return `${nameLookup[mpId]}: ${numberOfSimilarVotes}`
    })

    const enemies = sortedFriendIds.slice(sortedFriendIds.length - 6, sortedFriendIds.length)
    friendLookup[mp.mpId].enemies = enemies.map(mpId => {
      const numberOfSimilarVotes = friendLookup[mp.mpId].similiarMpVoteCount[mpId]

      return `${nameLookup[mpId]}: ${numberOfSimilarVotes}`
    })
  })

  return friendLookup
}
<commit_msg>Remove dead code<commit_after><|endoftext|>


<commit_before>serveRSS = function() {
  var feed = new RSS({
    title: getSetting('title'),
    description: getSetting('tagline'),
    feed_url: Meteor.absoluteUrl()+'feed.xml',
    site_url: Meteor.absoluteUrl(),
    image_url: Meteor.absoluteUrl()+'img/favicon.png',
  });

  Posts.find({status: STATUS_APPROVED}, {sort: {submitted: -1}, limit: 20}).forEach(function(post) {
    feed.item({
     title: post.headline,
     description: post.body+'

 Comments',
     author: post.author,
     date: post.submitted,
     url: (post.url ? post.url : getPostUrl(post._id)),
     guid: post._id
    });
  });

  return feed.xml();
}<commit_msg>Use the outgoing click tracking for rss<commit_after>serveRSS = function() {
  var feed = new RSS({
    title: getSetting('title'),
    description: getSetting('tagline'),
    feed_url: Meteor.absoluteUrl()+'feed.xml',
    site_url: Meteor.absoluteUrl(),
    image_url: Meteor.absoluteUrl()+'img/favicon.png',
  });

  Posts.find({status: STATUS_APPROVED}, {sort: {submitted: -1}, limit: 20}).forEach(function(post) {
    feed.item({
     title: post.headline,
     description: post.body+'

 Comments',
     author: post.author,
     date: post.submitted,
     url: (post.url ? getOutgoingUrl(post.url) : getPostUrl(post._id)),
     guid: post._id
    });
  });

  return feed.xml();
}
<|endoftext|>


<commit_before>
node.Ruleset = function Ruleset(selectors, rules) {
    this.selectors = selectors;
    this.rules = rules;
};
node.Ruleset.prototype = {
    variables: function () {
        return this.rules.filter(function (r) {
            if (r instanceof node.Rule && r.variable === true) { return r }
        });
    },
    toCSS: function (path, env) {
        var css = [], rules = [], rulesets = [];

        if (! this.root) path.push(this.selectors.map(function (s) { return s.toCSS(env) }));
        env.frames.unshift(this);

        for (var i = 0; i < this.rules.length; i++) {
            if (this.rules[i] instanceof node.Ruleset) { continue }

            if (this.rules[i].toCSS) {
                rules.push(this.rules[i].toCSS(env));
            } else {
                if (this.rules[i].value) {
                    rules.push(this.rules[i].value.toString());
                }
            }
        }

        for (var i = 0; i < this.rules.length; i++) {
            if (! (this.rules[i] instanceof node.Ruleset)) { continue }
            rulesets.push(this.rules[i].toCSS(path, env));
        }
        if (rules.length > 0) {
            if (path.length > 0) {
                css.push(path.join('').trim(),
                         " {\n  " + rules.join('\n  ') + "\n}\n",
                         rulesets.join(''));
            } else {
                css.push(rules.join('\n'), rulesets.join(''));
            }
        }
        path.pop();
        env.frames.shift();

        return css.join('');
    }
};

<commit_msg>simplified code-gen loop<commit_after>
node.Ruleset = function Ruleset(selectors, rules) {
    this.selectors = selectors;
    this.rules = rules;
};
node.Ruleset.prototype = {
    variables: function () {
        return this.rules.filter(function (r) {
            if (r instanceof node.Rule && r.variable === true) { return r }
        });
    },
    toCSS: function (path, env) {
        var css = [], rules = [], rulesets = [];

        if (! this.root) path.push(this.selectors.map(function (s) { return s.toCSS(env) }));
        env.frames.unshift(this);

        for (var i = 0; i < this.rules.length; i++) {
            if (this.rules[i] instanceof node.Ruleset) {
                rulesets.push(this.rules[i].toCSS(path, env));
            } else {
                if (this.rules[i].toCSS) {
                    rules.push(this.rules[i].toCSS(env));
                } else if (this.rules[i].value) {
                    rules.push(this.rules[i].value.toString());
                }
            }
        }

        if (rules.length > 0) {
            if (path.length > 0) {
                css.push(path.join('').trim(),
                         " {\n  " + rules.join('\n  ') + "\n}\n",
                         rulesets.join(''));
            } else {
                css.push(rules.join('\n'), rulesets.join(''));
            }
        }
        path.pop();
        env.frames.shift();

        return css.join('');
    }
};

<|endoftext|>


<commit_before>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2012 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Text input field.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

goog.provide('Blockly.FieldTextInput');

goog.require('Blockly.DropDownDiv');
goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
goog.require('Blockly.Field');
goog.require('Blockly.Msg');
goog.require('Blockly.utils');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.userAgent');

/**
 * Class for an editable text field.
 * @param {string=} opt_value The initial value of the field. Should cast to a
 *    string. Defaults to an empty string if null or undefined.
 * @param {Function=} opt_validator A function that is called to validate
 *    changes to the field's value. Takes in a string & returns a validated
 *    string, or null to abort the change.
 * @extends {Blockly.Field}
 * @constructor
 */
Blockly.FieldTextInput = function(opt_value, opt_validator) {
  opt_value = this.doClassValidation_(opt_value);
  if (opt_value === null) {
    opt_value = '';
  }
  Blockly.FieldTextInput.superClass_.constructor.call(this, opt_value,
      opt_validator);
};
goog.inherits(Blockly.FieldTextInput, Blockly.Field);

/**
 * Construct a FieldTextInput from a JSON arg object,
 * dereferencing any string table references.
 * @param {!Object} options A JSON object with options (text, class, and
 *                          spellcheck).
 * @return {!Blockly.FieldTextInput} The new field instance.
 * @package
 * @nocollapse
 */
Blockly.FieldTextInput.fromJson = function(options) {
  var text = Blockly.utils.replaceMessageReferences(options['text']);
  var field = new Blockly.FieldTextInput(text, options['class']);
  if (typeof options['spellcheck'] === 'boolean') {
    field.setSpellcheck(options['spellcheck']);
  }
  return field;
};

/**
 * Serializable fields are saved by the XML renderer, non-serializable fields
 * are not. Editable fields should also be serializable.
 * @type {boolean}
 * @const
 */
Blockly.FieldTextInput.prototype.SERIALIZABLE = true;

/**
 * Point size of text.  Should match blocklyText's font-size in CSS.
 */
Blockly.FieldTextInput.FONTSIZE = 11;

/**
 * The HTML input element for the user to type, or null if no FieldTextInput
 * editor is currently open.
 * @type {HTMLInputElement}
 * @protected
 */
Blockly.FieldTextInput.htmlInput_ = null;

/**
 * Mouse cursor style when over the hotspot that initiates the editor.
 */
Blockly.FieldTextInput.prototype.CURSOR = 'text';

/**
 * Allow browser to spellcheck this field.
 * @private
 */
Blockly.FieldTextInput.prototype.spellcheck_ = true;

/**
 * Ensure that the input value casts to a valid string.
 * @param {string=} newValue The input value.
 * @return {?string} A valid string, or null if invalid.
 * @protected
 */
Blockly.FieldTextInput.prototype.doClassValidation_ = function(newValue) {
  if (newValue === null || newValue === undefined) {
    return null;
  }
  return String(newValue);
};

/**
 * Called by setValue if the text input is not valid. If the field is
 * currently being edited it reverts value of the field to the previous
 * value while allowing the display text to be handled by the htmlInput_.
 * @protected
 */
Blockly.FieldTextInput.prototype.doValueInvalid_ = function() {
  if (this.isBeingEdited_) {
    this.isTextValid_ = false;
    var oldValue = this.value_;
    // Revert value when the text becomes invalid.
    this.value_ = this.htmlInput_.untypedDefaultValue_;
    if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
      Blockly.Events.fire(new Blockly.Events.BlockChange(
          this.sourceBlock_, 'field', this.name, oldValue, this.value_));
    }
  }
};

/**
 * Called by setValue if the text input is valid. Updates the value of the
 * field, and updates the text of the field if it is not currently being
 * edited (i.e. handled by the htmlInput_).
 * @param {!string} newValue The new validated value of the field.
 * @protected
 */
Blockly.FieldTextInput.prototype.doValueUpdate_ = function(newValue) {
  this.isTextValid_ = true;
  this.value_ = newValue;
  if (!this.isBeingEdited_) {
    // This should only occur if setValue is triggered programmatically.
    this.text_ = String(newValue);
    this.isDirty_ = true;
  }
};

/**
 * Updates the colour of the htmlInput given the current validity of the
 * field's value.
 * @protected
 */
Blockly.FieldTextInput.prototype.render_ = function() {
  Blockly.FieldTextInput.superClass_.render_.call(this);
  // This logic is done in render_ rather than doValueInvalid_ or
  // doValueUpdate_ so that the code is more centralized.
  if (this.isBeingEdited_) {
    this.resizeEditor_();
    if (!this.isTextValid_) {
      Blockly.utils.dom.addClass(this.htmlInput_, 'blocklyInvalidInput');
    } else {
      Blockly.utils.dom.removeClass(this.htmlInput_, 'blocklyInvalidInput');
    }
  }
};

/**
 * Set whether this field is spellchecked by the browser.
 * @param {boolean} check True if checked.
 */
Blockly.FieldTextInput.prototype.setSpellcheck = function(check) {
  this.spellcheck_ = check;
};

/**
 * Show the inline free-text editor on top of the text.
 * @param {boolean=} opt_quietInput True if editor should be created without
 *     focus.  Defaults to false.
 * @protected
 */
Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
  this.workspace_ = this.sourceBlock_.workspace;
  var quietInput = opt_quietInput || false;
  if (!quietInput && (Blockly.utils.userAgent.MOBILE ||
                      Blockly.utils.userAgent.ANDROID ||
                      Blockly.utils.userAgent.IPAD)) {
    this.showPromptEditor_();
  } else {
    this.showInlineEditor_(quietInput);
  }
};

/**
 * Create and show a text input editor that is a prompt (usually a popup).
 * Mobile browsers have issues with in-line textareas (focus and keyboards).
 * @private
 */
Blockly.FieldTextInput.prototype.showPromptEditor_ = function() {
  var fieldText = this;
  Blockly.prompt(Blockly.Msg['CHANGE_VALUE_TITLE'], this.text_,
      function(newValue) {
        fieldText.setValue(newValue);
      });
};

/**
 * Create and show a text input editor that sits directly over the text input.
 * @param {boolean} quietInput True if editor should be created without
 *     focus.
 * @private
 */
Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
  this.isBeingEdited_ = true;
  Blockly.WidgetDiv.show(
      this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this));
  this.htmlInput_ = this.widgetCreate_();

  if (!quietInput) {
    this.htmlInput_.focus();
    this.htmlInput_.select();
  }

  this.bindInputEvents_();
};

Blockly.FieldTextInput.prototype.widgetCreate_ = function() {
  var div = Blockly.WidgetDiv.DIV;

  var htmlInput = document.createElement('input');
  htmlInput.className = 'blocklyHtmlInput';
  htmlInput.setAttribute('spellcheck', this.spellcheck_);
  var fontSize =
    (Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt';
  div.style.fontSize = fontSize;
  htmlInput.style.fontSize = fontSize;
  div.appendChild(htmlInput);

  htmlInput.value = htmlInput.defaultValue = this.value_;
  htmlInput.untypedDefaultValue_ = this.value_;
  htmlInput.oldValue_ = null;
  this.resizeEditor_();

  this.bindInputEvents_(htmlInput);

  return htmlInput;
};

/**
 * Bind handlers for user input on the text input field's editor..
 * @private
 */
Blockly.FieldTextInput.prototype.bindInputEvents_ = function() {
  // Trap Enter without IME and Esc to hide.
  this.onKeyDownWrapper_ =
      Blockly.bindEventWithChecks_(
          this.htmlInput_, 'keydown', this, this.onHtmlInputKeyDown_);
  // Resize after every keystroke.
  this.onKeyUpWrapper_ =
      Blockly.bindEventWithChecks_(
          this.htmlInput_, 'keyup', this, this.onHtmlInputChange_);
  // Repeatedly resize when holding down a key.
  this.onKeyPressWrapper_ =
      Blockly.bindEventWithChecks_(
          this.htmlInput_, 'keypress', this, this.onHtmlInputChange_);

  // TODO: Figure out if this is necessary.
  this.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this);
  this.workspace_.addChangeListener(this.onWorkspaceChangeWrapper_);
};

/**
 * Unbind handlers for user input and workspace size changes.
 * @private
 */
Blockly.FieldTextInput.prototype.unbindInputEvents_ = function() {
  Blockly.unbindEvent_(this.onKeyDownWrapper_);
  Blockly.unbindEvent_(this.onKeyUpWrapper_);
  Blockly.unbindEvent_(this.onKeyPressWrapper_);
  this.workspace_.removeChangeListener(
      this.onWorkspaceChangeWrapper_);
};

/**
 * Handle key down to the editor.
 * @param {!Event} e Keyboard event.
 * @private
 */
Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) {
  var tabKey = 9, enterKey = 13, escKey = 27;
  if (e.keyCode == enterKey) {
    Blockly.WidgetDiv.hide();
    Blockly.DropDownDiv.hideIfOwner(this);
  } else if (e.keyCode == escKey) {
    this.htmlInput_.value = this.htmlInput_.defaultValue;
    Blockly.WidgetDiv.hide();
    Blockly.DropDownDiv.hideIfOwner(this);
  } else if (e.keyCode == tabKey) {
    Blockly.WidgetDiv.hide();
    Blockly.DropDownDiv.hideIfOwner(this);
    this.sourceBlock_.tab(this, !e.shiftKey);
    e.preventDefault();
  }
};

/**
 * Handle a change to the editor.
 * @param {!Event} _e Keyboard event.
 * @private
 */
Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) {
  var text = this.htmlInput_.value;
  if (text !== this.htmlInput_.oldValue_) {
    this.htmlInput_.oldValue_ = text;

    // TODO(#2169): Once issue is fixed the setGroup functionality could be
    //              moved up to the Field setValue method. This would create a
    //              broader fix for all field types.
    Blockly.Events.setGroup(true);
    this.setValue(text);
    // Always render the input text.
    this.text_ = this.htmlInput_.value;
    this.forceRerender();
    Blockly.Events.setGroup(false);
  }
};

/**
 * Resize the editor to fit the text.
 * @protected
 */
Blockly.FieldTextInput.prototype.resizeEditor_ = function() {
  var div = Blockly.WidgetDiv.DIV;
  var bBox = this.getScaledBBox_();
  div.style.width = bBox.right - bBox.left + 'px';
  div.style.height = bBox.bottom - bBox.top + 'px';

  // In RTL mode block fields and LTR input fields the left edge moves,
  // whereas the right edge is fixed.  Reposition the editor.
  var x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left;
  var xy = new Blockly.utils.Coordinate(x, bBox.top);

  // Shift by a few pixels to line up exactly.
  xy.y += 1;
  if (Blockly.utils.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) {
    // Firefox mis-reports the location of the border by a pixel
    // once the WidgetDiv is moved into position.
    xy.x -= 1;
    xy.y -= 1;
  }
  if (Blockly.utils.userAgent.WEBKIT) {
    xy.y -= 3;
  }
  div.style.left = xy.x + 'px';
  div.style.top = xy.y + 'px';
};

/**
 * Close the editor, save the results, and dispose of the editable
 * text field's elements.
 * @private
 */
Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
  // Finalize value.
  this.isBeingEdited_ = false;
  // No need to call setValue because if the widget is being closed the
  // latest input text has already been validated.
  if (this.value_ !== this.text_) {
    // At the end of an edit the text should be the same as the value. It
    // may not be if the input text is different than the validated text.
    // We should fix that.
    this.text_ = String(this.value_);
    this.isTextValid_ = true;
    this.forceRerender();
  }
  // Otherwise don't rerender.

  // Call onFinishEditing
  // TODO: Get rid of this or make it less of a hack.
  if (this.onFinishEditing_) {
    this.onFinishEditing_(this.value_);
  }

  // Remove htmlInput.
  this.unbindInputEvents_();
  this.htmlInput_ = null;

  // Delete style properties.
  var style = Blockly.WidgetDiv.DIV.style;
  style.width = 'auto';
  style.height = 'auto';
  style.fontSize = '';
};

/**
 * Ensure that only a number may be entered.
 * @param {string} text The user's text.
 * @return {?string} A string representing a valid number, or null if invalid.
 * @deprecated
 */
Blockly.FieldTextInput.numberValidator = function(text) {
  console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' +
               'Use Blockly.FieldNumber instead.');
  if (text === null) {
    return null;
  }
  text = String(text);
  // TODO: Handle cases like 'ten', '1.203,14', etc.
  // 'O' is sometimes mistaken for '0' by inexperienced users.
  text = text.replace(/O/ig, '0');
  // Strip out thousands separators.
  text = text.replace(/,/g, '');
  var n = parseFloat(text || 0);
  return isNaN(n) ? null : String(n);
};

/**
 * Ensure that only a nonnegative integer may be entered.
 * @param {string} text The user's text.
 * @return {?string} A string representing a valid int, or null if invalid.
 * @deprecated
 */
Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) {
  var n = Blockly.FieldTextInput.numberValidator(text);
  if (n) {
    n = String(Math.max(0, Math.floor(n)));
  }
  return n;
};

Blockly.Field.register('field_input', Blockly.FieldTextInput);
<commit_msg>Fixed some formatting in text input.<commit_after>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2012 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Text input field.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

goog.provide('Blockly.FieldTextInput');

goog.require('Blockly.DropDownDiv');
goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
goog.require('Blockly.Field');
goog.require('Blockly.Msg');
goog.require('Blockly.utils');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.userAgent');

/**
 * Class for an editable text field.
 * @param {string=} opt_value The initial value of the field. Should cast to a
 *    string. Defaults to an empty string if null or undefined.
 * @param {Function=} opt_validator A function that is called to validate
 *    changes to the field's value. Takes in a string & returns a validated
 *    string, or null to abort the change.
 * @extends {Blockly.Field}
 * @constructor
 */
Blockly.FieldTextInput = function(opt_value, opt_validator) {
  opt_value = this.doClassValidation_(opt_value);
  if (opt_value === null) {
    opt_value = '';
  }
  Blockly.FieldTextInput.superClass_.constructor.call(this, opt_value,
      opt_validator);
};
goog.inherits(Blockly.FieldTextInput, Blockly.Field);

/**
 * Construct a FieldTextInput from a JSON arg object,
 * dereferencing any string table references.
 * @param {!Object} options A JSON object with options (text, class, and
 *                          spellcheck).
 * @return {!Blockly.FieldTextInput} The new field instance.
 * @package
 * @nocollapse
 */
Blockly.FieldTextInput.fromJson = function(options) {
  var text = Blockly.utils.replaceMessageReferences(options['text']);
  var field = new Blockly.FieldTextInput(text, options['class']);
  if (typeof options['spellcheck'] === 'boolean') {
    field.setSpellcheck(options['spellcheck']);
  }
  return field;
};

/**
 * Serializable fields are saved by the XML renderer, non-serializable fields
 * are not. Editable fields should also be serializable.
 * @type {boolean}
 * @const
 */
Blockly.FieldTextInput.prototype.SERIALIZABLE = true;

/**
 * Point size of text.  Should match blocklyText's font-size in CSS.
 */
Blockly.FieldTextInput.FONTSIZE = 11;

/**
 * The HTML input element for the user to type, or null if no FieldTextInput
 * editor is currently open.
 * @type {HTMLInputElement}
 * @protected
 */
Blockly.FieldTextInput.htmlInput_ = null;

/**
 * Mouse cursor style when over the hotspot that initiates the editor.
 */
Blockly.FieldTextInput.prototype.CURSOR = 'text';

/**
 * Allow browser to spellcheck this field.
 * @private
 */
Blockly.FieldTextInput.prototype.spellcheck_ = true;

/**
 * Ensure that the input value casts to a valid string.
 * @param {string=} newValue The input value.
 * @return {?string} A valid string, or null if invalid.
 * @protected
 */
Blockly.FieldTextInput.prototype.doClassValidation_ = function(newValue) {
  if (newValue === null || newValue === undefined) {
    return null;
  }
  return String(newValue);
};

/**
 * Called by setValue if the text input is not valid. If the field is
 * currently being edited it reverts value of the field to the previous
 * value while allowing the display text to be handled by the htmlInput_.
 * @protected
 */
Blockly.FieldTextInput.prototype.doValueInvalid_ = function() {
  if (this.isBeingEdited_) {
    this.isTextValid_ = false;
    var oldValue = this.value_;
    // Revert value when the text becomes invalid.
    this.value_ = this.htmlInput_.untypedDefaultValue_;
    if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
      Blockly.Events.fire(new Blockly.Events.BlockChange(
          this.sourceBlock_, 'field', this.name, oldValue, this.value_));
    }
  }
};

/**
 * Called by setValue if the text input is valid. Updates the value of the
 * field, and updates the text of the field if it is not currently being
 * edited (i.e. handled by the htmlInput_).
 * @param {!string} newValue The new validated value of the field.
 * @protected
 */
Blockly.FieldTextInput.prototype.doValueUpdate_ = function(newValue) {
  this.isTextValid_ = true;
  this.value_ = newValue;
  if (!this.isBeingEdited_) {
    // This should only occur if setValue is triggered programmatically.
    this.text_ = String(newValue);
    this.isDirty_ = true;
  }
};

/**
 * Updates the colour of the htmlInput given the current validity of the
 * field's value.
 * @protected
 */
Blockly.FieldTextInput.prototype.render_ = function() {
  Blockly.FieldTextInput.superClass_.render_.call(this);
  // This logic is done in render_ rather than doValueInvalid_ or
  // doValueUpdate_ so that the code is more centralized.
  if (this.isBeingEdited_) {
    this.resizeEditor_();
    if (!this.isTextValid_) {
      Blockly.utils.dom.addClass(this.htmlInput_, 'blocklyInvalidInput');
    } else {
      Blockly.utils.dom.removeClass(this.htmlInput_, 'blocklyInvalidInput');
    }
  }
};

/**
 * Set whether this field is spellchecked by the browser.
 * @param {boolean} check True if checked.
 */
Blockly.FieldTextInput.prototype.setSpellcheck = function(check) {
  this.spellcheck_ = check;
};

/**
 * Show the inline free-text editor on top of the text.
 * @param {boolean=} opt_quietInput True if editor should be created without
 *     focus.  Defaults to false.
 * @protected
 */
Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
  this.workspace_ = this.sourceBlock_.workspace;
  var quietInput = opt_quietInput || false;
  if (!quietInput && (Blockly.utils.userAgent.MOBILE ||
                      Blockly.utils.userAgent.ANDROID ||
                      Blockly.utils.userAgent.IPAD)) {
    this.showPromptEditor_();
  } else {
    this.showInlineEditor_(quietInput);
  }
};

/**
 * Create and show a text input editor that is a prompt (usually a popup).
 * Mobile browsers have issues with in-line textareas (focus and keyboards).
 * @private
 */
Blockly.FieldTextInput.prototype.showPromptEditor_ = function() {
  var fieldText = this;
  Blockly.prompt(Blockly.Msg['CHANGE_VALUE_TITLE'], this.text_,
      function(newValue) {
        fieldText.setValue(newValue);
      });
};

/**
 * Create and show a text input editor that sits directly over the text input.
 * @param {boolean} quietInput True if editor should be created without
 *     focus.
 * @private
 */
Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
  this.isBeingEdited_ = true;
  Blockly.WidgetDiv.show(
      this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this));
  this.htmlInput_ = this.widgetCreate_();

  if (!quietInput) {
    this.htmlInput_.focus();
    this.htmlInput_.select();
  }
};

Blockly.FieldTextInput.prototype.widgetCreate_ = function() {
  var div = Blockly.WidgetDiv.DIV;

  var htmlInput = document.createElement('input');
  htmlInput.className = 'blocklyHtmlInput';
  htmlInput.setAttribute('spellcheck', this.spellcheck_);
  var fontSize =
    (Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt';
  div.style.fontSize = fontSize;
  htmlInput.style.fontSize = fontSize;
  div.appendChild(htmlInput);

  htmlInput.value = htmlInput.defaultValue = this.value_;
  htmlInput.untypedDefaultValue_ = this.value_;
  htmlInput.oldValue_ = null;
  this.resizeEditor_();

  this.bindInputEvents_(htmlInput);

  return htmlInput;
};

/**
 * Bind handlers for user input on the text input field's editor.
 * @param {!HTMLInputElement} htmlInput The htmlInput to which event
 *    handlers will be bound.
 * @private
 */
Blockly.FieldTextInput.prototype.bindInputEvents_ = function(htmlInput) {
  // Trap Enter without IME and Esc to hide.
  this.onKeyDownWrapper_ =
      Blockly.bindEventWithChecks_(
          htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
  // Resize after every keystroke.
  this.onKeyUpWrapper_ =
      Blockly.bindEventWithChecks_(
          htmlInput, 'keyup', this, this.onHtmlInputChange_);
  // Repeatedly resize when holding down a key.
  this.onKeyPressWrapper_ =
      Blockly.bindEventWithChecks_(
          htmlInput, 'keypress', this, this.onHtmlInputChange_);

  // TODO: Figure out if this is necessary.
  this.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this);
  this.workspace_.addChangeListener(this.onWorkspaceChangeWrapper_);
};

/**
 * Unbind handlers for user input and workspace size changes.
 * @private
 */
Blockly.FieldTextInput.prototype.unbindInputEvents_ = function() {
  Blockly.unbindEvent_(this.onKeyDownWrapper_);
  Blockly.unbindEvent_(this.onKeyUpWrapper_);
  Blockly.unbindEvent_(this.onKeyPressWrapper_);
  this.workspace_.removeChangeListener(
      this.onWorkspaceChangeWrapper_);
};

/**
 * Handle key down to the editor.
 * @param {!Event} e Keyboard event.
 * @private
 */
Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) {
  var tabKey = 9, enterKey = 13, escKey = 27;
  if (e.keyCode == enterKey) {
    Blockly.WidgetDiv.hide();
    Blockly.DropDownDiv.hideIfOwner(this);
  } else if (e.keyCode == escKey) {
    this.htmlInput_.value = this.htmlInput_.defaultValue;
    Blockly.WidgetDiv.hide();
    Blockly.DropDownDiv.hideIfOwner(this);
  } else if (e.keyCode == tabKey) {
    Blockly.WidgetDiv.hide();
    Blockly.DropDownDiv.hideIfOwner(this);
    this.sourceBlock_.tab(this, !e.shiftKey);
    e.preventDefault();
  }
};

/**
 * Handle a change to the editor.
 * @param {!Event} _e Keyboard event.
 * @private
 */
Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) {
  var text = this.htmlInput_.value;
  if (text !== this.htmlInput_.oldValue_) {
    this.htmlInput_.oldValue_ = text;

    // TODO(#2169): Once issue is fixed the setGroup functionality could be
    //              moved up to the Field setValue method. This would create a
    //              broader fix for all field types.
    Blockly.Events.setGroup(true);
    this.setValue(text);
    // Always render the input text.
    this.text_ = this.htmlInput_.value;
    this.forceRerender();
    Blockly.Events.setGroup(false);
  }
};

/**
 * Resize the editor to fit the text.
 * @protected
 */
Blockly.FieldTextInput.prototype.resizeEditor_ = function() {
  var div = Blockly.WidgetDiv.DIV;
  var bBox = this.getScaledBBox_();
  div.style.width = bBox.right - bBox.left + 'px';
  div.style.height = bBox.bottom - bBox.top + 'px';

  // In RTL mode block fields and LTR input fields the left edge moves,
  // whereas the right edge is fixed.  Reposition the editor.
  var x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left;
  var xy = new Blockly.utils.Coordinate(x, bBox.top);

  // Shift by a few pixels to line up exactly.
  xy.y += 1;
  if (Blockly.utils.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) {
    // Firefox mis-reports the location of the border by a pixel
    // once the WidgetDiv is moved into position.
    xy.x -= 1;
    xy.y -= 1;
  }
  if (Blockly.utils.userAgent.WEBKIT) {
    xy.y -= 3;
  }
  div.style.left = xy.x + 'px';
  div.style.top = xy.y + 'px';
};

/**
 * Close the editor, save the results, and dispose of the editable
 * text field's elements.
 * @private
 */
Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
  // Finalize value.
  this.isBeingEdited_ = false;
  // No need to call setValue because if the widget is being closed the
  // latest input text has already been validated.
  if (this.value_ !== this.text_) {
    // At the end of an edit the text should be the same as the value. It
    // may not be if the input text is different than the validated text.
    // We should fix that.
    this.text_ = String(this.value_);
    this.isTextValid_ = true;
    this.forceRerender();
  }
  // Otherwise don't rerender.

  // Call onFinishEditing
  // TODO: Get rid of this or make it less of a hack.
  if (this.onFinishEditing_) {
    this.onFinishEditing_(this.value_);
  }

  // Remove htmlInput.
  this.unbindInputEvents_();
  this.htmlInput_ = null;

  // Delete style properties.
  var style = Blockly.WidgetDiv.DIV.style;
  style.width = 'auto';
  style.height = 'auto';
  style.fontSize = '';
};

/**
 * Ensure that only a number may be entered.
 * @param {string} text The user's text.
 * @return {?string} A string representing a valid number, or null if invalid.
 * @deprecated
 */
Blockly.FieldTextInput.numberValidator = function(text) {
  console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' +
               'Use Blockly.FieldNumber instead.');
  if (text === null) {
    return null;
  }
  text = String(text);
  // TODO: Handle cases like 'ten', '1.203,14', etc.
  // 'O' is sometimes mistaken for '0' by inexperienced users.
  text = text.replace(/O/ig, '0');
  // Strip out thousands separators.
  text = text.replace(/,/g, '');
  var n = parseFloat(text || 0);
  return isNaN(n) ? null : String(n);
};

/**
 * Ensure that only a nonnegative integer may be entered.
 * @param {string} text The user's text.
 * @return {?string} A string representing a valid int, or null if invalid.
 * @deprecated
 */
Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) {
  var n = Blockly.FieldTextInput.numberValidator(text);
  if (n) {
    n = String(Math.max(0, Math.floor(n)));
  }
  return n;
};

Blockly.Field.register('field_input', Blockly.FieldTextInput);
<|endoftext|>


<commit_before>(function (tree) {

tree.Ruleset = function Ruleset(selectors, rules) {
    this.selectors = selectors;
    this.rules = rules;
    this._lookups = {};
};
tree.Ruleset.prototype = {
    eval: function () { return this },
    evalRules: function (context) {
        var rules = [];

        this.rules.forEach(function (rule) {
            if (rule.evalRules) {
                rules.push(rule.evalRules(context));
            } else if (rule instanceof tree.mixin.Call) {
                Array.prototype.push.apply(rules, rule.eval(context));
            } else {
                rules.push(rule.eval(context));
            }
        });
        this.rules = rules;
        return this;
    },
    match: function (args) {
        return !args || args.length === 0;
    },
    variable: function (name) {
        if (this._variables) { return this._variables[name] }
        else {
            return (this._variables = this.rules.reduce(function (hash, r) {
                if (r instanceof tree.Rule && r.variable === true) {
                    hash[r.name] = r;
                }
                return hash;
            }, {}))[name];
        }
    },
    rulesets: function () {
        if (this._rulesets) { return this._rulesets }
        else {
            return this._rulesets = this.rules.filter(function (r) {
                if (r instanceof tree.Ruleset || r instanceof tree.mixin.Definition) { return r }
            });
        }
    },
    find: function (selector, self) {
        self = self || this;
        var rules = [], rule, match,
            key = selector.toCSS();

        if (key in this._lookups) { return this._lookups[key] }

        this.rulesets().forEach(function (rule) {
            if (rule !== self) {
                for (var j = 0; j < rule.selectors.length; j++) {
                    if (match = selector.match(rule.selectors[j])) {
                        if (selector.elements.length > 1) {
                            Array.prototype.push.apply(rules, rule.find(
                                new(tree.Selector)(selector.elements.slice(1)), self));
                        } else {
                            rules.push(rule);
                        }
                        break;
                    }
                }
            }
        });
        return this._lookups[key] = rules;
    },
    //
    // Entry point for code generation
    //
    //     `context` holds an array of arrays.
    //
    toCSS: function (context, env) {
        var css = [],      // The CSS output
            rules = [],    // node.Rule instances
            rulesets = [], // node.Ruleset instances
            paths = [],    // Current selectors
            selector,      // The fully rendered selector
            rule;

        if (! this.root) {
            if (context.length === 0) {
                paths = this.selectors.map(function (s) { return [s] });
            } else {
                for (var s = 0; s < this.selectors.length; s++) {
                    for (var c = 0; c < context.length; c++) {
                        paths.push(context[c].concat([this.selectors[s]]));
                    }
                }
            }
        } else {
            for (var i = 0; i < this.rules.length; i++) {
                if (this.rules[i] instanceof tree.Import) {
                    Array.prototype.splice
                         .apply(this.rules, [i, 1].concat(this.rules[i].eval(env)));
                }
            }
        }

        // push the current ruleset to the frames stack
        env.frames.unshift(this);

        // Evaluate mixins
        for (var i = 0; i < this.rules.length; i++) {
            if (this.rules[i] instanceof tree.mixin.Call) {
                Array.prototype.splice
                     .apply(this.rules, [i, 1].concat(this.rules[i].eval(env)));
            }
        }

        // Evaluate rules and rulesets
        for (var i = 0; i < this.rules.length; i++) {
            rule = this.rules[i];

            if (rule instanceof tree.Directive) {
                rulesets.push(rule.eval(env).toCSS(paths, env));
            } else if (rule.rules) {
                rulesets.push(rule.toCSS(paths, env));
            } else if (rule instanceof tree.Comment) {
                if (!rule.silent) {
                    if (this.root) {
                        rulesets.push(rule.toCSS(env));
                    } else {
                        rules.push(rule.toCSS(env));
                    }
                }
            } else {
                if (rule.toCSS && !rule.variable) {
                    rules.push(rule.eval(env).toCSS(env));
                } else if (rule.value && !rule.variable) {
                    rules.push(rule.value.toString());
                }
            }
        }

        rulesets = rulesets.join('');

        // If this is the root node, we don't render
        // a selector, or {}.
        // Otherwise, only output if this ruleset has rules.
        if (this.root) {
            css.push(rules.join(env.compress ? '' : '\n'));
        } else {
            if (rules.length > 0) {
                selector = paths.map(function (p) {
                    return p.map(function (s) {
                        return s.toCSS(env);
                    }).join('').trim();
                }).join(env.compress ? ',' : (paths.length > 3 ? ',\n' : ', '));
                css.push(selector,
                        (env.compress ? '{' : ' {\n  ') +
                        rules.join(env.compress ? '' : '\n  ') +
                        (env.compress ? '}' : '\n}\n'));
            }
        }
        css.push(rulesets);

        // Pop the stack
        env.frames.shift();

        return css.join('') + (env.compress ? '\n' : '');
    }
};
})(require('less/tree'));
<commit_msg>don't break when a node doesn't have an eval() function<commit_after>(function (tree) {

tree.Ruleset = function Ruleset(selectors, rules) {
    this.selectors = selectors;
    this.rules = rules;
    this._lookups = {};
};
tree.Ruleset.prototype = {
    eval: function () { return this },
    evalRules: function (context) {
        var rules = [];

        this.rules.forEach(function (rule) {
            if (rule.evalRules) {
                rules.push(rule.evalRules(context));
            } else if (rule instanceof tree.mixin.Call) {
                Array.prototype.push.apply(rules, rule.eval(context));
            } else {
                rules.push(rule.eval ? rule.eval(context) : '');
            }
        });
        this.rules = rules;
        return this;
    },
    match: function (args) {
        return !args || args.length === 0;
    },
    variable: function (name) {
        if (this._variables) { return this._variables[name] }
        else {
            return (this._variables = this.rules.reduce(function (hash, r) {
                if (r instanceof tree.Rule && r.variable === true) {
                    hash[r.name] = r;
                }
                return hash;
            }, {}))[name];
        }
    },
    rulesets: function () {
        if (this._rulesets) { return this._rulesets }
        else {
            return this._rulesets = this.rules.filter(function (r) {
                if (r instanceof tree.Ruleset || r instanceof tree.mixin.Definition) { return r }
            });
        }
    },
    find: function (selector, self) {
        self = self || this;
        var rules = [], rule, match,
            key = selector.toCSS();

        if (key in this._lookups) { return this._lookups[key] }

        this.rulesets().forEach(function (rule) {
            if (rule !== self) {
                for (var j = 0; j < rule.selectors.length; j++) {
                    if (match = selector.match(rule.selectors[j])) {
                        if (selector.elements.length > 1) {
                            Array.prototype.push.apply(rules, rule.find(
                                new(tree.Selector)(selector.elements.slice(1)), self));
                        } else {
                            rules.push(rule);
                        }
                        break;
                    }
                }
            }
        });
        return this._lookups[key] = rules;
    },
    //
    // Entry point for code generation
    //
    //     `context` holds an array of arrays.
    //
    toCSS: function (context, env) {
        var css = [],      // The CSS output
            rules = [],    // node.Rule instances
            rulesets = [], // node.Ruleset instances
            paths = [],    // Current selectors
            selector,      // The fully rendered selector
            rule;

        if (! this.root) {
            if (context.length === 0) {
                paths = this.selectors.map(function (s) { return [s] });
            } else {
                for (var s = 0; s < this.selectors.length; s++) {
                    for (var c = 0; c < context.length; c++) {
                        paths.push(context[c].concat([this.selectors[s]]));
                    }
                }
            }
        } else {
            for (var i = 0; i < this.rules.length; i++) {
                if (this.rules[i] instanceof tree.Import) {
                    Array.prototype.splice
                         .apply(this.rules, [i, 1].concat(this.rules[i].eval(env)));
                }
            }
        }

        // push the current ruleset to the frames stack
        env.frames.unshift(this);

        // Evaluate mixins
        for (var i = 0; i < this.rules.length; i++) {
            if (this.rules[i] instanceof tree.mixin.Call) {
                Array.prototype.splice
                     .apply(this.rules, [i, 1].concat(this.rules[i].eval(env)));
            }
        }

        // Evaluate rules and rulesets
        for (var i = 0; i < this.rules.length; i++) {
            rule = this.rules[i];

            if (rule instanceof tree.Directive) {
                rulesets.push(rule.eval(env).toCSS(paths, env));
            } else if (rule.rules) {
                rulesets.push(rule.toCSS(paths, env));
            } else if (rule instanceof tree.Comment) {
                if (!rule.silent) {
                    if (this.root) {
                        rulesets.push(rule.toCSS(env));
                    } else {
                        rules.push(rule.toCSS(env));
                    }
                }
            } else {
                if (rule.toCSS && !rule.variable) {
                    rules.push(rule.eval(env).toCSS(env));
                } else if (rule.value && !rule.variable) {
                    rules.push(rule.value.toString());
                }
            }
        }

        rulesets = rulesets.join('');

        // If this is the root node, we don't render
        // a selector, or {}.
        // Otherwise, only output if this ruleset has rules.
        if (this.root) {
            css.push(rules.join(env.compress ? '' : '\n'));
        } else {
            if (rules.length > 0) {
                selector = paths.map(function (p) {
                    return p.map(function (s) {
                        return s.toCSS(env);
                    }).join('').trim();
                }).join(env.compress ? ',' : (paths.length > 3 ? ',\n' : ', '));
                css.push(selector,
                        (env.compress ? '{' : ' {\n  ') +
                        rules.join(env.compress ? '' : '\n  ') +
                        (env.compress ? '}' : '\n}\n'));
            }
        }
        css.push(rulesets);

        // Pop the stack
        env.frames.shift();

        return css.join('') + (env.compress ? '\n' : '');
    }
};
})(require('less/tree'));
<|endoftext|>


<commit_before>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2012 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Text input field.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

goog.provide('Blockly.FieldTextInput');

goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
goog.require('Blockly.Field');
goog.require('Blockly.Msg');
goog.require('Blockly.utils');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.userAgent');

/**
 * Class for an editable text field.
 * @param {string=} opt_value The initial value of the field. Should cast to a
 *    string. Defaults to an empty string if null or undefined.
 * @param {Function=} opt_validator A function that is called to validate
 *    changes to the field's value. Takes in a string & returns a validated
 *    string, or null to abort the change.
 * @extends {Blockly.Field}
 * @constructor
 */
Blockly.FieldTextInput = function(opt_value, opt_validator) {
  opt_value = this.doClassValidation_(opt_value);
  if (opt_value === null) {
    opt_value = '';
  }
  Blockly.FieldTextInput.superClass_.constructor.call(this, opt_value,
      opt_validator);
};
goog.inherits(Blockly.FieldTextInput, Blockly.Field);

/**
 * Construct a FieldTextInput from a JSON arg object,
 * dereferencing any string table references.
 * @param {!Object} options A JSON object with options (text, class, and
 *                          spellcheck).
 * @return {!Blockly.FieldTextInput} The new field instance.
 * @package
 * @nocollapse
 */
Blockly.FieldTextInput.fromJson = function(options) {
  var text = Blockly.utils.replaceMessageReferences(options['text']);
  var field = new Blockly.FieldTextInput(text, options['class']);
  if (typeof options['spellcheck'] === 'boolean') {
    field.setSpellcheck(options['spellcheck']);
  }
  return field;
};

/**
 * Serializable fields are saved by the XML renderer, non-serializable fields
 * are not. Editable fields should also be serializable.
 * @type {boolean}
 * @const
 */
Blockly.FieldTextInput.prototype.SERIALIZABLE = true;

/**
 * Point size of text.  Should match blocklyText's font-size in CSS.
 */
Blockly.FieldTextInput.FONTSIZE = 11;

/**
 * Mouse cursor style when over the hotspot that initiates the editor.
 */
Blockly.FieldTextInput.prototype.CURSOR = 'text';

/**
 * Allow browser to spellcheck this field.
 * @private
 */
Blockly.FieldTextInput.prototype.spellcheck_ = true;

/**
 * Ensure that the input value casts to a valid string.
 * @param {string=} newValue The input value.
 * @return {?string} A valid string, or null if invalid.
 * @protected
 */
Blockly.FieldTextInput.prototype.doClassValidation_ = function(newValue) {
  if (newValue === null || newValue === undefined) {
    return null;
  }
  return String(newValue);
};

/**
 * Called by setValue if the text input is not valid. If the field is
 * currently being edited it reverts value of the field to the previous
 * value while allowing the display text to be handled by the htmlInput_.
 * @protected
 */
Blockly.FieldTextInput.prototype.doValueInvalid_ = function() {
  if (this.isBeingEdited_) {
    this.isTextValid_ = false;
    var oldValue = this.value_;
    // Revert value when the text becomes invalid.
    this.value_ = this.htmlInput_.untypedDefaultValue_;
    if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
      Blockly.Events.fire(new Blockly.Events.BlockChange(
          this.sourceBlock_, 'field', this.name, oldValue, this.value_));
    }
  }
};

/**
 * Called by setValue if the text input is valid. Updates the value of the
 * field, and updates the text of the field if it is not currently being
 * edited (i.e. handled by the htmlInput_).
 * @param {string} newValue The new validated value of the field.
 * @protected
 */
Blockly.FieldTextInput.prototype.doValueUpdate_ = function(newValue) {
  this.isTextValid_ = true;
  this.value_ = newValue;
  if (!this.isBeingEdited_) {
    // This should only occur if setValue is triggered programmatically.
    this.text_ = String(newValue);
    this.isDirty_ = true;
  }
};

/**
 * Updates the colour of the htmlInput given the current validity of the
 * field's value.
 * @protected
 */
Blockly.FieldTextInput.prototype.render_ = function() {
  Blockly.FieldTextInput.superClass_.render_.call(this);
  // This logic is done in render_ rather than doValueInvalid_ or
  // doValueUpdate_ so that the code is more centralized.
  if (this.isBeingEdited_) {
    this.resizeEditor_();
    if (!this.isTextValid_) {
      Blockly.utils.dom.addClass(this.htmlInput_, 'blocklyInvalidInput');
    } else {
      Blockly.utils.dom.removeClass(this.htmlInput_, 'blocklyInvalidInput');
    }
  }
};

/**
 * Set whether this field is spellchecked by the browser.
 * @param {boolean} check True if checked.
 */
Blockly.FieldTextInput.prototype.setSpellcheck = function(check) {
  this.spellcheck_ = check;
};

/**
 * Show the inline free-text editor on top of the text.
 * @param {boolean=} opt_quietInput True if editor should be created without
 *     focus.  Defaults to false.
 * @protected
 */
Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
  this.workspace_ = this.sourceBlock_.workspace;
  var quietInput = opt_quietInput || false;
  if (!quietInput && (Blockly.utils.userAgent.MOBILE ||
                      Blockly.utils.userAgent.ANDROID ||
                      Blockly.utils.userAgent.IPAD)) {
    this.showPromptEditor_();
  } else {
    this.showInlineEditor_(quietInput);
  }
};

/**
 * Create and show a text input editor that is a prompt (usually a popup).
 * Mobile browsers have issues with in-line textareas (focus and keyboards).
 * @private
 */
Blockly.FieldTextInput.prototype.showPromptEditor_ = function() {
  var fieldText = this;
  Blockly.prompt(Blockly.Msg['CHANGE_VALUE_TITLE'], this.text_,
      function(newValue) {
        fieldText.setValue(newValue);
      });
};

/**
 * Create and show a text input editor that sits directly over the text input.
 * @param {boolean} quietInput True if editor should be created without
 *     focus.
 * @private
 */
Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
  this.isBeingEdited_ = true;
  Blockly.WidgetDiv.show(
      this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this));
  this.htmlInput_ = this.widgetCreate_();

  if (!quietInput) {
    this.htmlInput_.focus();
    this.htmlInput_.select();
  }
};

/**
 * Create the text input editor widget.
 * @return {!HTMLInputElement} The newly created text input editor.
 * @private
 */
Blockly.FieldTextInput.prototype.widgetCreate_ = function() {
  var div = Blockly.WidgetDiv.DIV;

  var htmlInput = document.createElement('input');
  htmlInput.className = 'blocklyHtmlInput';
  htmlInput.setAttribute('spellcheck', this.spellcheck_);
  var fontSize =
      (Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt';
  div.style.fontSize = fontSize;
  htmlInput.style.fontSize = fontSize;
  div.appendChild(htmlInput);

  htmlInput.value = htmlInput.defaultValue = this.value_;
  htmlInput.untypedDefaultValue_ = this.value_;
  htmlInput.oldValue_ = null;
  this.resizeEditor_();

  this.bindInputEvents_(htmlInput);

  return htmlInput;
};

/**
 * Close the editor, save the results, and dispose any events bound to the
 * text input's editor.
 * @private
 */
Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
  // Finalize value.
  this.isBeingEdited_ = false;
  // No need to call setValue because if the widget is being closed the
  // latest input text has already been validated.
  if (this.value_ !== this.text_) {
    // At the end of an edit the text should be the same as the value. It
    // may not be if the input text is different than the validated text.
    // We should fix that.
    this.text_ = String(this.value_);
    this.isTextValid_ = true;
    this.forceRerender();
  }
  // Otherwise don't rerender.

  // Call onFinishEditing
  // TODO: Get rid of this or make it less of a hack.
  if (this.onFinishEditing_) {
    this.onFinishEditing_(this.value_);
  }

  // Remove htmlInput events.
  this.unbindInputEvents_();

  // Delete style properties.
  var style = Blockly.WidgetDiv.DIV.style;
  style.width = 'auto';
  style.height = 'auto';
  style.fontSize = '';
};

/**
 * Bind handlers for user input on the text input field's editor.
 * @param {!HTMLInputElement} htmlInput The htmlInput to which event
 *    handlers will be bound.
 * @private
 */
Blockly.FieldTextInput.prototype.bindInputEvents_ = function(htmlInput) {
  // Trap Enter without IME and Esc to hide.
  this.onKeyDownWrapper_ =
      Blockly.bindEventWithChecks_(
          htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
  // Resize after every keystroke.
  this.onKeyUpWrapper_ =
      Blockly.bindEventWithChecks_(
          htmlInput, 'keyup', this, this.onHtmlInputChange_);
  // Repeatedly resize when holding down a key.
  this.onKeyPressWrapper_ =
      Blockly.bindEventWithChecks_(
          htmlInput, 'keypress', this, this.onHtmlInputChange_);

  // TODO: Figure out if this is necessary.
  this.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this);
  this.workspace_.addChangeListener(this.onWorkspaceChangeWrapper_);
};

/**
 * Unbind handlers for user input and workspace size changes.
 * @private
 */
Blockly.FieldTextInput.prototype.unbindInputEvents_ = function() {
  Blockly.unbindEvent_(this.onKeyDownWrapper_);
  Blockly.unbindEvent_(this.onKeyUpWrapper_);
  Blockly.unbindEvent_(this.onKeyPressWrapper_);
  this.workspace_.removeChangeListener(this.onWorkspaceChangeWrapper_);
};

/**
 * Handle key down to the editor.
 * @param {!Event} e Keyboard event.
 * @private
 */
Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) {
  var tabKey = 9, enterKey = 13, escKey = 27;
  if (e.keyCode == enterKey) {
    Blockly.WidgetDiv.hide();
  } else if (e.keyCode == escKey) {
    this.htmlInput_.value = this.htmlInput_.defaultValue;
    Blockly.WidgetDiv.hide();
  } else if (e.keyCode == tabKey) {
    Blockly.WidgetDiv.hide();
    this.sourceBlock_.tab(this, !e.shiftKey);
    e.preventDefault();
  }
};

/**
 * Handle a change to the editor.
 * @param {!Event} _e Keyboard event.
 * @private
 */
Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) {
  var text = this.htmlInput_.value;
  if (text !== this.htmlInput_.oldValue_) {
    this.htmlInput_.oldValue_ = text;

    // TODO(#2169): Once issue is fixed the setGroup functionality could be
    //              moved up to the Field setValue method. This would create a
    //              broader fix for all field types.
    Blockly.Events.setGroup(true);
    this.setValue(text);
    // Always render the input text.
    this.text_ = this.htmlInput_.value;
    this.forceRerender();
    Blockly.Events.setGroup(false);
  }
};

/**
 * Resize the editor to fit the text.
 * @protected
 */
Blockly.FieldTextInput.prototype.resizeEditor_ = function() {
  var div = Blockly.WidgetDiv.DIV;
  var bBox = this.getScaledBBox_();
  div.style.width = bBox.right - bBox.left + 'px';
  div.style.height = bBox.bottom - bBox.top + 'px';

  // In RTL mode block fields and LTR input fields the left edge moves,
  // whereas the right edge is fixed.  Reposition the editor.
  var x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left;
  var xy = new Blockly.utils.Coordinate(x, bBox.top);

  // Shift by a few pixels to line up exactly.
  xy.y += 1;
  if (Blockly.utils.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) {
    // Firefox mis-reports the location of the border by a pixel
    // once the WidgetDiv is moved into position.
    xy.x -= 1;
    xy.y -= 1;
  }
  if (Blockly.utils.userAgent.WEBKIT) {
    xy.y -= 3;
  }
  div.style.left = xy.x + 'px';
  div.style.top = xy.y + 'px';
};

/**
 * Ensure that only a number may be entered.
 * @param {string} text The user's text.
 * @return {?string} A string representing a valid number, or null if invalid.
 * @deprecated
 */
Blockly.FieldTextInput.numberValidator = function(text) {
  console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' +
               'Use Blockly.FieldNumber instead.');
  if (text === null) {
    return null;
  }
  text = String(text);
  // TODO: Handle cases like 'ten', '1.203,14', etc.
  // 'O' is sometimes mistaken for '0' by inexperienced users.
  text = text.replace(/O/ig, '0');
  // Strip out thousands separators.
  text = text.replace(/,/g, '');
  var n = parseFloat(text || 0);
  return isNaN(n) ? null : String(n);
};

/**
 * Ensure that only a nonnegative integer may be entered.
 * @param {string} text The user's text.
 * @return {?string} A string representing a valid int, or null if invalid.
 * @deprecated
 */
Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) {
  var n = Blockly.FieldTextInput.numberValidator(text);
  if (n) {
    n = String(Math.max(0, Math.floor(n)));
  }
  return n;
};

Blockly.Field.register('field_input', Blockly.FieldTextInput);
<commit_msg>Removed Class Option Check from Text Input fromJson (#2588)<commit_after>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2012 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Text input field.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

goog.provide('Blockly.FieldTextInput');

goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
goog.require('Blockly.Field');
goog.require('Blockly.Msg');
goog.require('Blockly.utils');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.userAgent');

/**
 * Class for an editable text field.
 * @param {string=} opt_value The initial value of the field. Should cast to a
 *    string. Defaults to an empty string if null or undefined.
 * @param {Function=} opt_validator A function that is called to validate
 *    changes to the field's value. Takes in a string & returns a validated
 *    string, or null to abort the change.
 * @extends {Blockly.Field}
 * @constructor
 */
Blockly.FieldTextInput = function(opt_value, opt_validator) {
  opt_value = this.doClassValidation_(opt_value);
  if (opt_value === null) {
    opt_value = '';
  }
  Blockly.FieldTextInput.superClass_.constructor.call(this, opt_value,
      opt_validator);
};
goog.inherits(Blockly.FieldTextInput, Blockly.Field);

/**
 * Construct a FieldTextInput from a JSON arg object,
 * dereferencing any string table references.
 * @param {!Object} options A JSON object with options (text, class, and
 *                          spellcheck).
 * @return {!Blockly.FieldTextInput} The new field instance.
 * @package
 * @nocollapse
 */
Blockly.FieldTextInput.fromJson = function(options) {
  var text = Blockly.utils.replaceMessageReferences(options['text']);
  var field = new Blockly.FieldTextInput(text);
  if (typeof options['spellcheck'] === 'boolean') {
    field.setSpellcheck(options['spellcheck']);
  }
  return field;
};

/**
 * Serializable fields are saved by the XML renderer, non-serializable fields
 * are not. Editable fields should also be serializable.
 * @type {boolean}
 * @const
 */
Blockly.FieldTextInput.prototype.SERIALIZABLE = true;

/**
 * Point size of text.  Should match blocklyText's font-size in CSS.
 */
Blockly.FieldTextInput.FONTSIZE = 11;

/**
 * Mouse cursor style when over the hotspot that initiates the editor.
 */
Blockly.FieldTextInput.prototype.CURSOR = 'text';

/**
 * Allow browser to spellcheck this field.
 * @private
 */
Blockly.FieldTextInput.prototype.spellcheck_ = true;

/**
 * Ensure that the input value casts to a valid string.
 * @param {string=} newValue The input value.
 * @return {?string} A valid string, or null if invalid.
 * @protected
 */
Blockly.FieldTextInput.prototype.doClassValidation_ = function(newValue) {
  if (newValue === null || newValue === undefined) {
    return null;
  }
  return String(newValue);
};

/**
 * Called by setValue if the text input is not valid. If the field is
 * currently being edited it reverts value of the field to the previous
 * value while allowing the display text to be handled by the htmlInput_.
 * @protected
 */
Blockly.FieldTextInput.prototype.doValueInvalid_ = function() {
  if (this.isBeingEdited_) {
    this.isTextValid_ = false;
    var oldValue = this.value_;
    // Revert value when the text becomes invalid.
    this.value_ = this.htmlInput_.untypedDefaultValue_;
    if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
      Blockly.Events.fire(new Blockly.Events.BlockChange(
          this.sourceBlock_, 'field', this.name, oldValue, this.value_));
    }
  }
};

/**
 * Called by setValue if the text input is valid. Updates the value of the
 * field, and updates the text of the field if it is not currently being
 * edited (i.e. handled by the htmlInput_).
 * @param {string} newValue The new validated value of the field.
 * @protected
 */
Blockly.FieldTextInput.prototype.doValueUpdate_ = function(newValue) {
  this.isTextValid_ = true;
  this.value_ = newValue;
  if (!this.isBeingEdited_) {
    // This should only occur if setValue is triggered programmatically.
    this.text_ = String(newValue);
    this.isDirty_ = true;
  }
};

/**
 * Updates the colour of the htmlInput given the current validity of the
 * field's value.
 * @protected
 */
Blockly.FieldTextInput.prototype.render_ = function() {
  Blockly.FieldTextInput.superClass_.render_.call(this);
  // This logic is done in render_ rather than doValueInvalid_ or
  // doValueUpdate_ so that the code is more centralized.
  if (this.isBeingEdited_) {
    this.resizeEditor_();
    if (!this.isTextValid_) {
      Blockly.utils.dom.addClass(this.htmlInput_, 'blocklyInvalidInput');
    } else {
      Blockly.utils.dom.removeClass(this.htmlInput_, 'blocklyInvalidInput');
    }
  }
};

/**
 * Set whether this field is spellchecked by the browser.
 * @param {boolean} check True if checked.
 */
Blockly.FieldTextInput.prototype.setSpellcheck = function(check) {
  this.spellcheck_ = check;
};

/**
 * Show the inline free-text editor on top of the text.
 * @param {boolean=} opt_quietInput True if editor should be created without
 *     focus.  Defaults to false.
 * @protected
 */
Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
  this.workspace_ = this.sourceBlock_.workspace;
  var quietInput = opt_quietInput || false;
  if (!quietInput && (Blockly.utils.userAgent.MOBILE ||
                      Blockly.utils.userAgent.ANDROID ||
                      Blockly.utils.userAgent.IPAD)) {
    this.showPromptEditor_();
  } else {
    this.showInlineEditor_(quietInput);
  }
};

/**
 * Create and show a text input editor that is a prompt (usually a popup).
 * Mobile browsers have issues with in-line textareas (focus and keyboards).
 * @private
 */
Blockly.FieldTextInput.prototype.showPromptEditor_ = function() {
  var fieldText = this;
  Blockly.prompt(Blockly.Msg['CHANGE_VALUE_TITLE'], this.text_,
      function(newValue) {
        fieldText.setValue(newValue);
      });
};

/**
 * Create and show a text input editor that sits directly over the text input.
 * @param {boolean} quietInput True if editor should be created without
 *     focus.
 * @private
 */
Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
  this.isBeingEdited_ = true;
  Blockly.WidgetDiv.show(
      this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this));
  this.htmlInput_ = this.widgetCreate_();

  if (!quietInput) {
    this.htmlInput_.focus();
    this.htmlInput_.select();
  }
};

/**
 * Create the text input editor widget.
 * @return {!HTMLInputElement} The newly created text input editor.
 * @private
 */
Blockly.FieldTextInput.prototype.widgetCreate_ = function() {
  var div = Blockly.WidgetDiv.DIV;

  var htmlInput = document.createElement('input');
  htmlInput.className = 'blocklyHtmlInput';
  htmlInput.setAttribute('spellcheck', this.spellcheck_);
  var fontSize =
      (Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt';
  div.style.fontSize = fontSize;
  htmlInput.style.fontSize = fontSize;
  div.appendChild(htmlInput);

  htmlInput.value = htmlInput.defaultValue = this.value_;
  htmlInput.untypedDefaultValue_ = this.value_;
  htmlInput.oldValue_ = null;
  this.resizeEditor_();

  this.bindInputEvents_(htmlInput);

  return htmlInput;
};

/**
 * Close the editor, save the results, and dispose any events bound to the
 * text input's editor.
 * @private
 */
Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
  // Finalize value.
  this.isBeingEdited_ = false;
  // No need to call setValue because if the widget is being closed the
  // latest input text has already been validated.
  if (this.value_ !== this.text_) {
    // At the end of an edit the text should be the same as the value. It
    // may not be if the input text is different than the validated text.
    // We should fix that.
    this.text_ = String(this.value_);
    this.isTextValid_ = true;
    this.forceRerender();
  }
  // Otherwise don't rerender.

  // Call onFinishEditing
  // TODO: Get rid of this or make it less of a hack.
  if (this.onFinishEditing_) {
    this.onFinishEditing_(this.value_);
  }

  // Remove htmlInput events.
  this.unbindInputEvents_();

  // Delete style properties.
  var style = Blockly.WidgetDiv.DIV.style;
  style.width = 'auto';
  style.height = 'auto';
  style.fontSize = '';
};

/**
 * Bind handlers for user input on the text input field's editor.
 * @param {!HTMLInputElement} htmlInput The htmlInput to which event
 *    handlers will be bound.
 * @private
 */
Blockly.FieldTextInput.prototype.bindInputEvents_ = function(htmlInput) {
  // Trap Enter without IME and Esc to hide.
  this.onKeyDownWrapper_ =
      Blockly.bindEventWithChecks_(
          htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
  // Resize after every keystroke.
  this.onKeyUpWrapper_ =
      Blockly.bindEventWithChecks_(
          htmlInput, 'keyup', this, this.onHtmlInputChange_);
  // Repeatedly resize when holding down a key.
  this.onKeyPressWrapper_ =
      Blockly.bindEventWithChecks_(
          htmlInput, 'keypress', this, this.onHtmlInputChange_);

  // TODO: Figure out if this is necessary.
  this.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this);
  this.workspace_.addChangeListener(this.onWorkspaceChangeWrapper_);
};

/**
 * Unbind handlers for user input and workspace size changes.
 * @private
 */
Blockly.FieldTextInput.prototype.unbindInputEvents_ = function() {
  Blockly.unbindEvent_(this.onKeyDownWrapper_);
  Blockly.unbindEvent_(this.onKeyUpWrapper_);
  Blockly.unbindEvent_(this.onKeyPressWrapper_);
  this.workspace_.removeChangeListener(this.onWorkspaceChangeWrapper_);
};

/**
 * Handle key down to the editor.
 * @param {!Event} e Keyboard event.
 * @private
 */
Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) {
  var tabKey = 9, enterKey = 13, escKey = 27;
  if (e.keyCode == enterKey) {
    Blockly.WidgetDiv.hide();
  } else if (e.keyCode == escKey) {
    this.htmlInput_.value = this.htmlInput_.defaultValue;
    Blockly.WidgetDiv.hide();
  } else if (e.keyCode == tabKey) {
    Blockly.WidgetDiv.hide();
    this.sourceBlock_.tab(this, !e.shiftKey);
    e.preventDefault();
  }
};

/**
 * Handle a change to the editor.
 * @param {!Event} _e Keyboard event.
 * @private
 */
Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) {
  var text = this.htmlInput_.value;
  if (text !== this.htmlInput_.oldValue_) {
    this.htmlInput_.oldValue_ = text;

    // TODO(#2169): Once issue is fixed the setGroup functionality could be
    //              moved up to the Field setValue method. This would create a
    //              broader fix for all field types.
    Blockly.Events.setGroup(true);
    this.setValue(text);
    // Always render the input text.
    this.text_ = this.htmlInput_.value;
    this.forceRerender();
    Blockly.Events.setGroup(false);
  }
};

/**
 * Resize the editor to fit the text.
 * @protected
 */
Blockly.FieldTextInput.prototype.resizeEditor_ = function() {
  var div = Blockly.WidgetDiv.DIV;
  var bBox = this.getScaledBBox_();
  div.style.width = bBox.right - bBox.left + 'px';
  div.style.height = bBox.bottom - bBox.top + 'px';

  // In RTL mode block fields and LTR input fields the left edge moves,
  // whereas the right edge is fixed.  Reposition the editor.
  var x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left;
  var xy = new Blockly.utils.Coordinate(x, bBox.top);

  // Shift by a few pixels to line up exactly.
  xy.y += 1;
  if (Blockly.utils.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) {
    // Firefox mis-reports the location of the border by a pixel
    // once the WidgetDiv is moved into position.
    xy.x -= 1;
    xy.y -= 1;
  }
  if (Blockly.utils.userAgent.WEBKIT) {
    xy.y -= 3;
  }
  div.style.left = xy.x + 'px';
  div.style.top = xy.y + 'px';
};

/**
 * Ensure that only a number may be entered.
 * @param {string} text The user's text.
 * @return {?string} A string representing a valid number, or null if invalid.
 * @deprecated
 */
Blockly.FieldTextInput.numberValidator = function(text) {
  console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' +
               'Use Blockly.FieldNumber instead.');
  if (text === null) {
    return null;
  }
  text = String(text);
  // TODO: Handle cases like 'ten', '1.203,14', etc.
  // 'O' is sometimes mistaken for '0' by inexperienced users.
  text = text.replace(/O/ig, '0');
  // Strip out thousands separators.
  text = text.replace(/,/g, '');
  var n = parseFloat(text || 0);
  return isNaN(n) ? null : String(n);
};

/**
 * Ensure that only a nonnegative integer may be entered.
 * @param {string} text The user's text.
 * @return {?string} A string representing a valid int, or null if invalid.
 * @deprecated
 */
Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) {
  var n = Blockly.FieldTextInput.numberValidator(text);
  if (n) {
    n = String(Math.max(0, Math.floor(n)));
  }
  return n;
};

Blockly.Field.register('field_input', Blockly.FieldTextInput);
<|endoftext|>