<commit_before>'use strict'

import q from 'q'
import fs from 'fs'
import path from 'path'
import merge from 'deepmerge'
import events from 'events'

import RequestHandler from './utils/RequestHandler'
import { RuntimeError } from './utils/ErrorHandler'
import Logger from './utils/Logger'

import safeExecute from './helpers/safeExecute'
import sanitize from './helpers/sanitize'
import mobileDetector from './helpers/mobileDetector'
import detectSeleniumBackend from './helpers/detectSeleniumBackend'
import internalErrorHandler from './helpers/errorHandler'

const INTERNAL_EVENTS = ['init', 'command', 'error', 'result', 'end']
const PROMISE_FUNCTIONS = ['then', 'catch', 'finally']

let EventEmitter = events.EventEmitter

/**
 * WebdriverIO v4
 */
let WebdriverIO = function (args, modifier) {
    let prototype = Object.create(Object.prototype)
    let eventHandler = new EventEmitter()
    let fulFilledPromise = q()
    let stacktrace = []
    let commandList = []

    const EVENTHANDLER_FUNCTIONS = Object.getPrototypeOf(eventHandler)

    /**
     * merge default options with given user options
     */
    let options = merge({
        protocol: 'http',
        waitforTimeout: 500,
        coloredLogs: true,
        logLevel: 'silent',
        baseUrl: null,
        onError: []
    }, typeof args !== 'string' ? args : {})

    /**
     * define Selenium backend given on user options
     */
    options = merge(detectSeleniumBackend(args), options)

    /**
     * only set globals we wouldn't get otherwise
     */
    if (!process.env.WEBDRIVERIO_COLORED_LOGS) {
        process.env.WEBDRIVERIO_COLORED_LOGS = options.coloredLogs
    }

    let logger = new Logger(options, eventHandler)
    let requestHandler = new RequestHandler(options, eventHandler, logger)

    /**
     * assign instance to existing session
     */
    if (typeof args === 'string') {
        requestHandler.sessionID = args
    }

    /**
     * apply error handler
     */
    if (!Array.isArray(options.onError)) {
        options.onError = [options.onError]
    }
    options.onError = options.onError.filter((fn) => {
        return typeof fn === 'function'
    })

    let desiredCapabilities = merge({
        browserName: 'firefox',
        version: '',
        javascriptEnabled: true,
        locationContextEnabled: true,
        handlesAlerts: true,
        rotatable: true,
        platform: 'ANY'
    }, options.desiredCapabilities || {})

    let { isMobile, isIOS, isAndroid } = mobileDetector(desiredCapabilities)

    let resolve = function (result, onFulfilled, onRejected, context) {
        if (typeof result === 'function') {
            this.isExecuted = true
            result = result.call(this)
        }

        /**
         * run error handler if command fails
         */
        if (result instanceof Error) {
            let _result = result
            this.defer.reject(result)

            return Promise.all(internalErrorHandler.map((fn) => {
                return fn.call(context, result)
            })).then((res) => {
                const handlerResponses = res.filter((r) => !!r)

                /**
                 * if no handler was triggered trough actual error
                 */
                if (handlerResponses.length === 0) {
                    return reject.call(context, _result, onRejected)
                }

                return onFulfilled.call(context, handlerResponses[0])
            }, (e) => {
                return reject.call(context, e, onRejected)
            })
        }

        this.defer.resolve(result)
        return this.promise
    }

    /**
     * By using finally in our next method we omit the duty to throw an exception at some
     * point. To avoid propagating rejected promises until everything crashes silently we
     * check if the last and current promise got rejected. If so we can throw the error.
     */
    let reject = function (err, onRejected) {
        if (typeof onRejected === 'function') {
            onRejected(err)
        }

        if (!this) {
            return
        } else if (this.depth !== 0 || typeof onRejected === 'function') {
            return this.promise
        }

        /**
         * take screenshot only if screenshotPath is given
         */
        if (typeof options.screenshotPath !== 'string') {
            return throwException(err, stacktrace)
        }

        let screenshotPath = path.join(process.cwd(), options.screenshotPath)

        /**
         * take screenshot only if directory exists
         */
        if (!fs.existsSync(screenshotPath)) {
            return throwException(err, stacktrace)
        }

        let client = unit()
        client.next(prototype.saveScreenshot, [
            path.join(screenshotPath, 'ERROR_' + sanitize.caps(desiredCapabilities) + '_' + new Date().toJSON() + '.png')
        ], 'saveScreenshot')

        let stack = stacktrace.slice()
        return throwException(err, stack)
    }

    function throwException (e, stack) {
        if (!e.stack) {
            throw new Error(e)
        }

        stack = stack.map(trace => '    at ' + trace)
        e.stack = e.type + ': ' + e.message + '\n' + stack.reverse().join('\n')

        /**
         * ToDo useful feature for standalone mode:
         * option that if true causes script to throw exception if command fails:
         *
         * process.nextTick(() => {
         *     throw e
         * })
         */

        throw e
    }

    /**
     * WebdriverIO Monad
     */
    function unit (lastPromise) {
        let client = Object.create(prototype)
        let defer = q.defer()
        let promise = defer.promise

        client.defer = defer
        client.promise = promise
        client.lastPromise = lastPromise || fulFilledPromise

        client.desiredCapabilities = desiredCapabilities
        client.requestHandler = requestHandler
        client.logger = logger
        client.options = options
        client.commandList = commandList

        client.isMobile = isMobile
        client.isIOS = isIOS
        client.isAndroid = isAndroid

        /**
         * actual bind function
         */
        client.next = function (func, args, name) {
            /**
             * use finally to propagate rejected promises up the chain
             */
            return this.lastPromise.then(() => {
                /**
                 * store command into command list so `getHistory` can return it
                 */
                commandList.push({name, args})

                return resolve.call(this, safeExecute(func, args))
            }, (e) => {
                /**
                 * this will get reached only in standalone mode if the command
                 * fails and doesn't get followed by a then or catch method
                 */
                return resolve.call(this, e, null, null, { depth: 0 })
            })
        }

        client.finally = function (fn) {
            let client = unit(this.promise.finally(() => {
                return resolve.call(client, safeExecute(fn, []).bind(this))
            }))
            return client
        }

        client.call = function (fn) {
            let client = unit(this.promise.done(() => {
                return resolve.call(client, safeExecute(fn, []).bind(this))
            }))
            return client
        }

        client.then = function (onFulfilled, onRejected) {
            if (typeof onFulfilled !== 'function' && typeof onRejected !== 'function') {
                return this
            }

            /**
             * execute then function in context of the new instance
             * but resolve result with this
             */
            let client = unit(this.promise.then((...args) => {
                /**
                 * store result in commandList
                 */
                commandList[commandList.length - 1].result = args[0]

                /**
                 * resolve command
                 */
                return resolve.call(client, safeExecute(onFulfilled, args).bind(this))
            }, (e) => {
                let result = safeExecute(onRejected, [e]).bind(this)

                /**
                 * handle error once command was bubbled up the command chain
                 */
                if (this.depth === 0 && options.isWDIO) {
                    result = e
                }

                return resolve.call(client,
                    result,
                    onFulfilled,
                    onRejected,
                    this
                )
            }))

            return client
        }

        client.catch = function (onRejected) {
            return this.then(undefined, onRejected)
        }

        client.inspect = function () {
            return this.promise.inspect()
        }

        /**
         * internal helper method to handle command results
         *
         * @param  {Promise[]} promises  list of promises
         * @param  {Boolean}   option    if true extract value property from selenium result
         */
        client.unify = function (promises, option) {
            option = option || {}
            promises = Array.isArray(promises) ? promises : [promises]

            return q.all(promises)
                /**
                 * extract value property from result if desired
                 */
                .then((result) => {
                    if (!option.extractValue || !Array.isArray(result)) {
                        return result
                    }

                    return result.map(res =>
                        res.value && typeof res.value === 'string' ? res.value.trim() : res.value)

                /**
                 * sanitize result for better assertion
                 */
                }).then((result) => {
                    if (Array.isArray(result) && result.length === 1) {
                        result = result[0]
                    }

                    if (option.lowercase && typeof result === 'string') {
                        result = result.toLowerCase()
                    }

                    return result
                })
        }

        client.addCommand = function (fnName, fn, forceOverwrite) {
            if (typeof fn === 'string') {
                const namespace = arguments[0]
                fnName = arguments[1]
                fn = arguments[2]
                forceOverwrite = arguments[3]

                switch (typeof client[namespace]) {
                case 'function':
                    throw new RuntimeError(`Command namespace "${namespace}" is used internally, and can't be overwritten!`)
                case 'object':
                    if (client[namespace][fnName] && !forceOverwrite) {
                        throw new RuntimeError(`Command "${fnName}" is already defined!`)
                    }
                    break
                }
                return unit.lift(namespace, fnName, fn)
            }

            if (client[fnName] && !forceOverwrite) {
                throw new RuntimeError(`Command "${fnName}" is already defined!`)
            }
            return unit.lift(fnName, fn)
        }

        client.transferPromiseness = function (target, promise) {
            /**
             * transfer WebdriverIO commands
             */
            let clientFunctions = Object.keys(prototype)
            let functionsToTranfer = clientFunctions.concat(PROMISE_FUNCTIONS)

            for (let fnName of functionsToTranfer) {
                if (typeof promise[fnName] === 'function') {
                    target[fnName] = promise[fnName].bind(promise)
                }
            }
        }

        if (typeof modifier === 'function') {
            client = modifier(client, options)
        }

        return client
    }

    /**
     * enhance base monad prototype with methods
     */
    unit.lift = function (name, func) {
        let commandGroup = prototype

        if (typeof func === 'string') {
            const namespace = arguments[0]
            name = arguments[1]
            func = arguments[2]

            if (!prototype[namespace]) {
                prototype[namespace] = {}
            }

            commandGroup = prototype[namespace]
        }

        commandGroup[name] = function (...args) {
            let nextPromise = this.promise

            /**
             * commands executed inside commands don't have to wait
             * on any promise
             */
            if (this.isExecuted) {
                nextPromise = this.lastPromise
            }

            let client = unit(nextPromise)

            /**
             * catch stack to find information about where the command that causes
             * the error was used (stack line 2) and only save it when it was not
             * within WebdriverIO context
             */
            let stack = new Error().stack
            let lineInTest = stack.split('\n').slice(2, 3).join('\n')
            let fileAndPosition = lineInTest.slice(lineInTest.indexOf('(') + 1, lineInTest.indexOf(')'))
            let atCommand = lineInTest.trim().slice(3).split(' ')[0]

            atCommand = atCommand.slice(atCommand.lastIndexOf('.') + 1)

            let trace = name + '(' + sanitize.args(args) + ') - ' + fileAndPosition.slice(fileAndPosition.lastIndexOf('/') + 1)
            if (Object.keys(prototype).indexOf(atCommand) === -1 && atCommand !== 'exports') {
                stacktrace = [trace]
            } else {
                /**
                 * save trace for nested commands
                 */
                stacktrace.push(trace)
            }

            /**
             * determine execution depth:
             * This little tweak helps us to determine whether the command was executed
             * by the test script or by another command. With that we can make sure
             * that errors are getting thrown once they bubbled up the command chain.
             */
            client.depth = stack.split('\n').filter((line) => !!line.match(/\/lib\/(commands|protocol)\/(\w+)\.js/)).length

            /**
             * queue command
             */
            client.name = name
            client.next(func, args, name)
            return client
        }

        return unit
    }

    /**
     * register event emitter
     */
    for (let eventCommand in EVENTHANDLER_FUNCTIONS) {
        prototype[eventCommand] = function (...args) {
            /**
             * custom commands needs to get emitted and registered in order
             * to prevent race conditions
             */
            if (INTERNAL_EVENTS.indexOf(args[0]) === -1) {
                return this.finally(() => eventHandler[eventCommand].apply(eventHandler, args))
            }

            eventHandler[eventCommand].apply(eventHandler, args)
            return this
        }
    }

    return unit
}

export default WebdriverIO
<commit_msg>implemented custom error handling<commit_after>'use strict'

import q from 'q'
import fs from 'fs'
import path from 'path'
import merge from 'deepmerge'
import events from 'events'

import RequestHandler from './utils/RequestHandler'
import { RuntimeError } from './utils/ErrorHandler'
import Logger from './utils/Logger'

import safeExecute from './helpers/safeExecute'
import sanitize from './helpers/sanitize'
import mobileDetector from './helpers/mobileDetector'
import detectSeleniumBackend from './helpers/detectSeleniumBackend'
import internalErrorHandler from './helpers/errorHandler'

const INTERNAL_EVENTS = ['init', 'command', 'error', 'result', 'end']
const PROMISE_FUNCTIONS = ['then', 'catch', 'finally']

let EventEmitter = events.EventEmitter

/**
 * WebdriverIO v4
 */
let WebdriverIO = function (args, modifier) {
    let prototype = Object.create(Object.prototype)
    let eventHandler = new EventEmitter()
    let fulFilledPromise = q()
    let stacktrace = []
    let commandList = []

    const EVENTHANDLER_FUNCTIONS = Object.getPrototypeOf(eventHandler)

    /**
     * merge default options with given user options
     */
    let options = merge({
        protocol: 'http',
        waitforTimeout: 500,
        coloredLogs: true,
        logLevel: 'silent',
        baseUrl: null,
        onError: []
    }, typeof args !== 'string' ? args : {})

    /**
     * define Selenium backend given on user options
     */
    options = merge(detectSeleniumBackend(args), options)

    /**
     * only set globals we wouldn't get otherwise
     */
    if (!process.env.WEBDRIVERIO_COLORED_LOGS) {
        process.env.WEBDRIVERIO_COLORED_LOGS = options.coloredLogs
    }

    let logger = new Logger(options, eventHandler)
    let requestHandler = new RequestHandler(options, eventHandler, logger)

    /**
     * assign instance to existing session
     */
    if (typeof args === 'string') {
        requestHandler.sessionID = args
    }

    /**
     * sanitize error handler
     */
    if (!Array.isArray(options.onError)) {
        options.onError = [options.onError]
    }
    options.onError = options.onError.filter((fn) => {
        return typeof fn === 'function'
    })

    let desiredCapabilities = merge({
        browserName: 'firefox',
        version: '',
        javascriptEnabled: true,
        locationContextEnabled: true,
        handlesAlerts: true,
        rotatable: true,
        platform: 'ANY'
    }, options.desiredCapabilities || {})

    let { isMobile, isIOS, isAndroid } = mobileDetector(desiredCapabilities)

    let resolve = function (result, onFulfilled, onRejected, context) {
        if (typeof result === 'function') {
            this.isExecuted = true
            result = result.call(this)
        }

        /**
         * run error handler if command fails
         */
        if (result instanceof Error) {
            let _result = result
            this.defer.reject(result)

            return Promise.all(internalErrorHandler.map((fn) => {
                return fn.call(context, result)
            })).then((res) => {
                const handlerResponses = res.filter((r) => !!r)

                /**
                 * if no handler was triggered trough actual error
                 */
                if (handlerResponses.length === 0) {
                    return callErrorHandlerAndReject.call(context, _result, onRejected)
                }

                return onFulfilled.call(context, handlerResponses[0])
            }, (e) => {
                return callErrorHandlerAndReject.call(context, e, onRejected)
            })
        }

        this.defer.resolve(result)
        return this.promise
    }

    /**
     * middleware to call on error handler in wdio mode
     */
    let callErrorHandlerAndReject = function (err, onRejected) {
        /**
         * only call error handler if there is any and if error has bubbled up
         */
        if (this.depth !== 0 || options.onError.length === 0) {
            return reject.call(this, err, onRejected)
        }

        return new Promise((r) => {
            return Promise.all(options.onError.map((fn) => {
                if (!global.wdioSync) {
                    return fn.call(this, err)
                }

                return new Promise((r) => global.wdioSync(fn, r).call(this, err))
            })).then(r, r)
        }).then(() => {
            return reject.call(this, err, onRejected)
        })
    }

    /**
     * By using finally in our next method we omit the duty to throw an exception at some
     * point. To avoid propagating rejected promises until everything crashes silently we
     * check if the last and current promise got rejected. If so we can throw the error.
     */
    let reject = function (err, onRejected) {
        if (typeof onRejected === 'function') {
            onRejected(err)
        }

        if (!this) {
            return
        } else if (this.depth !== 0 || typeof onRejected === 'function') {
            return this.promise
        }

        /**
         * take screenshot only if screenshotPath is given
         */
        if (typeof options.screenshotPath !== 'string') {
            return throwException(err, stacktrace)
        }

        let screenshotPath = path.join(process.cwd(), options.screenshotPath)

        /**
         * take screenshot only if directory exists
         */
        if (!fs.existsSync(screenshotPath)) {
            return throwException(err, stacktrace)
        }

        let client = unit()
        client.next(prototype.saveScreenshot, [
            path.join(screenshotPath, 'ERROR_' + sanitize.caps(desiredCapabilities) + '_' + new Date().toJSON() + '.png')
        ], 'saveScreenshot')

        let stack = stacktrace.slice()
        return throwException(err, stack)
    }

    function throwException (e, stack) {
        if (!e.stack) {
            throw new Error(e)
        }

        stack = stack.map(trace => '    at ' + trace)
        e.stack = e.type + ': ' + e.message + '\n' + stack.reverse().join('\n')

        /**
         * ToDo useful feature for standalone mode:
         * option that if true causes script to throw exception if command fails:
         *
         * process.nextTick(() => {
         *     throw e
         * })
         */

        throw e
    }

    /**
     * WebdriverIO Monad
     */
    function unit (lastPromise) {
        let client = Object.create(prototype)
        let defer = q.defer()
        let promise = defer.promise

        client.defer = defer
        client.promise = promise
        client.lastPromise = lastPromise || fulFilledPromise

        client.desiredCapabilities = desiredCapabilities
        client.requestHandler = requestHandler
        client.logger = logger
        client.options = options
        client.commandList = commandList

        client.isMobile = isMobile
        client.isIOS = isIOS
        client.isAndroid = isAndroid

        /**
         * actual bind function
         */
        client.next = function (func, args, name) {
            /**
             * use finally to propagate rejected promises up the chain
             */
            return this.lastPromise.then(() => {
                /**
                 * store command into command list so `getHistory` can return it
                 */
                commandList.push({name, args})

                return resolve.call(this, safeExecute(func, args))
            }, (e) => {
                /**
                 * this will get reached only in standalone mode if the command
                 * fails and doesn't get followed by a then or catch method
                 */
                return resolve.call(this, e, null, null, { depth: 0 })
            })
        }

        client.finally = function (fn) {
            let client = unit(this.promise.finally(() => {
                return resolve.call(client, safeExecute(fn, []).bind(this))
            }))
            return client
        }

        client.call = function (fn) {
            let client = unit(this.promise.done(() => {
                return resolve.call(client, safeExecute(fn, []).bind(this))
            }))
            return client
        }

        client.then = function (onFulfilled, onRejected) {
            if (typeof onFulfilled !== 'function' && typeof onRejected !== 'function') {
                return this
            }

            /**
             * execute then function in context of the new instance
             * but resolve result with this
             */
            let client = unit(this.promise.then((...args) => {
                /**
                 * store result in commandList
                 */
                commandList[commandList.length - 1].result = args[0]

                /**
                 * resolve command
                 */
                return resolve.call(client, safeExecute(onFulfilled, args).bind(this))
            }, (e) => {
                let result = safeExecute(onRejected, [e]).bind(this)

                /**
                 * handle error once command was bubbled up the command chain
                 */
                if (this.depth === 0 && options.isWDIO) {
                    result = e
                }

                return resolve.call(client,
                    result,
                    onFulfilled,
                    onRejected,
                    this
                )
            }))

            return client
        }

        client.catch = function (onRejected) {
            return this.then(undefined, onRejected)
        }

        client.inspect = function () {
            return this.promise.inspect()
        }

        /**
         * internal helper method to handle command results
         *
         * @param  {Promise[]} promises  list of promises
         * @param  {Boolean}   option    if true extract value property from selenium result
         */
        client.unify = function (promises, option) {
            option = option || {}
            promises = Array.isArray(promises) ? promises : [promises]

            return q.all(promises)
                /**
                 * extract value property from result if desired
                 */
                .then((result) => {
                    if (!option.extractValue || !Array.isArray(result)) {
                        return result
                    }

                    return result.map(res =>
                        res.value && typeof res.value === 'string' ? res.value.trim() : res.value)

                /**
                 * sanitize result for better assertion
                 */
                }).then((result) => {
                    if (Array.isArray(result) && result.length === 1) {
                        result = result[0]
                    }

                    if (option.lowercase && typeof result === 'string') {
                        result = result.toLowerCase()
                    }

                    return result
                })
        }

        client.addCommand = function (fnName, fn, forceOverwrite) {
            if (typeof fn === 'string') {
                const namespace = arguments[0]
                fnName = arguments[1]
                fn = arguments[2]
                forceOverwrite = arguments[3]

                switch (typeof client[namespace]) {
                case 'function':
                    throw new RuntimeError(`Command namespace "${namespace}" is used internally, and can't be overwritten!`)
                case 'object':
                    if (client[namespace][fnName] && !forceOverwrite) {
                        throw new RuntimeError(`Command "${fnName}" is already defined!`)
                    }
                    break
                }
                return unit.lift(namespace, fnName, fn)
            }

            if (client[fnName] && !forceOverwrite) {
                throw new RuntimeError(`Command "${fnName}" is already defined!`)
            }
            return unit.lift(fnName, fn)
        }

        client.transferPromiseness = function (target, promise) {
            /**
             * transfer WebdriverIO commands
             */
            let clientFunctions = Object.keys(prototype)
            let functionsToTranfer = clientFunctions.concat(PROMISE_FUNCTIONS)

            for (let fnName of functionsToTranfer) {
                if (typeof promise[fnName] === 'function') {
                    target[fnName] = promise[fnName].bind(promise)
                }
            }
        }

        if (typeof modifier === 'function') {
            client = modifier(client, options)
        }

        return client
    }

    /**
     * enhance base monad prototype with methods
     */
    unit.lift = function (name, func) {
        let commandGroup = prototype

        if (typeof func === 'string') {
            const namespace = arguments[0]
            name = arguments[1]
            func = arguments[2]

            if (!prototype[namespace]) {
                prototype[namespace] = {}
            }

            commandGroup = prototype[namespace]
        }

        commandGroup[name] = function (...args) {
            let nextPromise = this.promise

            /**
             * commands executed inside commands don't have to wait
             * on any promise
             */
            if (this.isExecuted) {
                nextPromise = this.lastPromise
            }

            let client = unit(nextPromise)

            /**
             * catch stack to find information about where the command that causes
             * the error was used (stack line 2) and only save it when it was not
             * within WebdriverIO context
             */
            let stack = new Error().stack
            let lineInTest = stack.split('\n').slice(2, 3).join('\n')
            let fileAndPosition = lineInTest.slice(lineInTest.indexOf('(') + 1, lineInTest.indexOf(')'))
            let atCommand = lineInTest.trim().slice(3).split(' ')[0]

            atCommand = atCommand.slice(atCommand.lastIndexOf('.') + 1)

            let trace = name + '(' + sanitize.args(args) + ') - ' + fileAndPosition.slice(fileAndPosition.lastIndexOf('/') + 1)
            if (Object.keys(prototype).indexOf(atCommand) === -1 && atCommand !== 'exports') {
                stacktrace = [trace]
            } else {
                /**
                 * save trace for nested commands
                 */
                stacktrace.push(trace)
            }

            /**
             * determine execution depth:
             * This little tweak helps us to determine whether the command was executed
             * by the test script or by another command. With that we can make sure
             * that errors are getting thrown once they bubbled up the command chain.
             */
            client.depth = stack.split('\n').filter((line) => !!line.match(/\/lib\/(commands|protocol)\/(\w+)\.js/)).length

            /**
             * queue command
             */
            client.name = name
            client.next(func, args, name)
            return client
        }

        return unit
    }

    /**
     * register event emitter
     */
    for (let eventCommand in EVENTHANDLER_FUNCTIONS) {
        prototype[eventCommand] = function (...args) {
            /**
             * custom commands needs to get emitted and registered in order
             * to prevent race conditions
             */
            if (INTERNAL_EVENTS.indexOf(args[0]) === -1) {
                return this.finally(() => eventHandler[eventCommand].apply(eventHandler, args))
            }

            eventHandler[eventCommand].apply(eventHandler, args)
            return this
        }
    }

    return unit
}

export default WebdriverIO
<|endoftext|>


<commit_before>'use strict';
var XMLWriter = require('xml-writer');
var default_tag_name = 'row';

exports.dump = function (obj, options) {
  options = options || {};

  if (typeof obj != "object") {
    return obj;
  }
  //    if (typeof xw != XMLWriter)
  var	xw = new XMLWriter(options.indent);

  var getype = function (o) {
    if (!o) {
      return 'undefined';
    }
    else if (Array.isArray(o)) {
      return 'array';
    }
    else {
      return typeof o;
    }
  }

  var getval = function (o) {
    return '' + o; // !TODO
  }

  var getname = function (n) {
    return n.replace('$', ':');
  }

  var sortKeys = function (o) {
    var keys = [];
    for (var key in o) {
      if (Object.hasOwnProperty.call(o, key) && o[key] != null && o[key] !== undefined) {
        keys.push(key);
      }
    }
    function keyComparator(a, b) {
      var a0 = a.charAt(0);
      var b0 = b.charAt(0);
      var compA = typeof o[a] === 'object' ? 1 : -1;
      var compB = typeof o[b] === 'object' ? 1 : -1;
      if (compA - compB !== 0) {
        return compA - compB;
      }
      compA = a0 === '$' || a0 === '#' ? 1 : -1;
      compB = b0 === '$' || b0 === '#' ? 1 : -1;
      return compA - compB;
    }
    keys.sort(keyComparator);
    return keys;
  };

  var parse = function (o) {
    if (getype(o) != 'object') {
      return;
    }
    var keys = sortKeys(o);
    for (var index in keys) {
      var key = keys[index];
      var val = o[key];
      var type1 = getype(val);
      if (type1 == 'object') {
        xw.startElement(getname(key));
        parse(val);
        xw.endElement();
      }
      else if (type1 == 'array' && val.length <= 0) {
        xw.startElement(getname(key)).endElement();
      }
      else if (type1 == 'array' && val.length > 0) {
        var type2 = getype(val[0]);
        if (type2 == 'object') {
          val.forEach(function (item, index) {
              if (key == '$element' || key == '$e' || key == '#element') {
                parse(item);
              }
              else {
                xw.startElement(getname(key));
                parse(item);
                xw.endElement();
              }
            }
          );
        }
        else {
          val.forEach(function (item, index) {
              if (key == '$element' || key == '$e' || key == '#element') {
                xw.startCData().text(getval(item)).endCData();
              }
              else {
                xw.startElement(getname(key));
                xw.startCData().text(getval(item)).endCData();
                xw.endElement();
              }
            }
          );
        }
      }
      else {
        if (key == '$text' || key == '$t' || key == '#text') {
          xw.text(getval(val));
        }
        else if (key == '$comment' || key == '$c' || key == '#comment') {
          xw.startComment().text(getval(val)).endComment();
        }
        else if (key == '$cdata' || key == '$cd' || key == '#cdata' || key == '#cd') {
          xw.startCData().text(getval(val)).endCData();
        }
        else  {
          xw.startAttribute(getname(key)).text(getval(val)).endAttribute();
        }
      }
    }
  }
  var o = {};
  if (Object.keys(obj).length == 1) {
    o = obj;
  }
  else {
    o[default_tag_name] = obj;
  }
  parse(o);

  return xw.toString();

};
exports.load = function (str, options) {
  options = options || {};
  if (typeof str != "string") {
    if (options.throwErrors) {
      throw new Error("Input was " + (typeof str) + ", expected a string");
    }
    return str;
  }
  var parser = require("sax").parser(true, {trim : true, xmlns : false});
  var result = {},
      stack = [],
      cdata = '',
      nested = { check : [], previous : '', memory : [], buffer: '', name: [] },
      path = [];

  function cvalue(n, v, isXmlNode) {
    n = n.replace(':', '$');
    var o = stack[stack.length - 1];
    if (o === undefined) {
      o = {};
      o[n] = v;
      return o[n];
    }
    else if (o[n] === undefined) {
      if (isXmlNode && options.arrays && options.arrays.indexOf("/" + path.join("/")) !== -1) {
        // Create new array only if current item is a XML node,
        // meaning it is not text, cdata or comment
        o[n] = new Array(v);
        return o[n][0];
      }
      else {
        o[n] = v;
        return o[n];
      }
    }
    else if (!Array.isArray(o[n])) {
      var x = o[n];
      o[n] = new Array(x, v);
      return o[n][1];
    }
    else {
      var i = o[n].push(v);
      return o[n][i - 1];
    }
  }
  function cattr(o) {
    var r = {};
    for (var key in o) {
      if (o.hasOwnProperty(key) && o[key]) {
        r[key.replace(':', '$')] = o[key];
      }
    }
    return r;
  }

  if (!options.throwErrors) {
    parser.onerror = function (e) {
      // an error happened.
    };
  }
  parser.onprocessinginstruction = function (pi) {
  };
  parser.ontext = function (v) {
    if (options.nested) {
      if (nested.previous === 'C') {
        nested.check[nested.check.length - 1] = true;
      }
      nested.buffer = v;
      nested.memory.map(function (item) { return item.text(v) });
      nested.previous = 'T';
    }
    else {
      cvalue('$t', v);
    }
  };
  parser.oncomment = function (v) {
    if (options.comments === undefined || options.comments !== false) {
      cvalue('$c', v);
    }
  };
  parser.oncdata = function (v) {
    cdata += v;
  };
  parser.onopencdata = function () {
    cdata = '';
  };
  parser.onclosecdata = function () {
    cvalue('$cd', cdata);
    cdata = '';
  };
  parser.onopentag = function (node) {
    if (options.arrays) {
      path.push(node.name);
    }
    if (options.nested) {
      if (nested.previous === 'T') {
        nested.check[nested.check.length - 1] = true;
      }
      nested.buffer = '';
      nested.name.push(node.name);
      nested.memory.push(new XMLWriter());
      nested.check.push(false);
      nested.previous = 'O';

      nested.memory.map(function(item) {
          return Object.keys(node.attributes).reduce(function(prev, cur) {
              return prev.writeAttribute(cur, node.attributes[cur])
            }, item.startElement(node.name))
      });
    }

    if (stack.length == 1 && node.name == default_tag_name) {
      result = cattr(node.attributes);
      stack.push(result);
    }
    else {
      stack.push(cvalue(node.name, cattr(node.attributes), true));
    }
  };
  parser.onclosetag = function () {
    if (options.arrays) {
      path.pop();
    }
    if (options.nested) {
      nested.memory.map(function(item) { return item.endElement() });
      var ne = nested.name.pop();
      var xv = nested.memory.pop();
      if (nested.check.pop() === true) {
        var r1 = RegExp('(^<' + ne + '[^>]*>)', 'g'),
            r2 = RegExp('(</' + ne + '[^>]*>$)', 'g');
        cvalue('$t', xv.toString().replace(r1, '').replace(r2, ''));
      }
      else {
        if (nested.buffer) {
          cvalue('$t', nested.buffer);
      }
      }
      xv = null;
      nested.buffer = '';
      nested.previous = 'C';
    }

    stack.pop();
  };
  parser.onready = function () {
  };
  parser.onend = function () {
  };

  if (options.throwErrors) {
    stack.push(result);
    parser.write(str);
    var line = parser.line;
    var column = parser.column;
    parser.close();
    stack.pop();

    if (stack.length !== 0) {
      var er = "Unexpected end of input";
      er += "\nLine: " + line +
            "\nColumn: " + column +
            "\nChar: null";
      throw new Error(er);
    }
  } else {
    stack.push(result);
    try {
      parser.write(str).close();
    }
    catch (e) {
      return str;
    }
    stack.pop();
  }

  return result;
}

exports.toxml = exports.dump;
exports.tojson = exports.load;
<commit_msg>Added header option with optional version and encoding<commit_after>'use strict';
var XMLWriter = require('xml-writer');
var default_tag_name = 'row';

exports.dump = function (obj, options) {
  options = options || {};

  if (typeof obj != "object") {
    return obj;
  }
  //    if (typeof xw != XMLWriter)
  var	xw = new XMLWriter(options.indent);
  if (options.header) {
    xw.startDocument(options.version || '1.0', options.encoding || 'utf-8');
  }

  var getype = function (o) {
    if (!o) {
      return 'undefined';
    }
    else if (Array.isArray(o)) {
      return 'array';
    }
    else {
      return typeof o;
    }
  }

  var getval = function (o) {
    return '' + o; // !TODO
  }

  var getname = function (n) {
    return n.replace('$', ':');
  }

  var sortKeys = function (o) {
    var keys = [];
    for (var key in o) {
      if (Object.hasOwnProperty.call(o, key) && o[key] != null && o[key] !== undefined) {
        keys.push(key);
      }
    }
    function keyComparator(a, b) {
      var a0 = a.charAt(0);
      var b0 = b.charAt(0);
      var compA = typeof o[a] === 'object' ? 1 : -1;
      var compB = typeof o[b] === 'object' ? 1 : -1;
      if (compA - compB !== 0) {
        return compA - compB;
      }
      compA = a0 === '$' || a0 === '#' ? 1 : -1;
      compB = b0 === '$' || b0 === '#' ? 1 : -1;
      return compA - compB;
    }
    keys.sort(keyComparator);
    return keys;
  };

  var parse = function (o) {
    if (getype(o) != 'object') {
      return;
    }
    var keys = sortKeys(o);
    for (var index in keys) {
      var key = keys[index];
      var val = o[key];
      var type1 = getype(val);
      if (type1 == 'object') {
        xw.startElement(getname(key));
        parse(val);
        xw.endElement();
      }
      else if (type1 == 'array' && val.length <= 0) {
        xw.startElement(getname(key)).endElement();
      }
      else if (type1 == 'array' && val.length > 0) {
        var type2 = getype(val[0]);
        if (type2 == 'object') {
          val.forEach(function (item, index) {
              if (key == '$element' || key == '$e' || key == '#element') {
                parse(item);
              }
              else {
                xw.startElement(getname(key));
                parse(item);
                xw.endElement();
              }
            }
          );
        }
        else {
          val.forEach(function (item, index) {
              if (key == '$element' || key == '$e' || key == '#element') {
                xw.startCData().text(getval(item)).endCData();
              }
              else {
                xw.startElement(getname(key));
                xw.startCData().text(getval(item)).endCData();
                xw.endElement();
              }
            }
          );
        }
      }
      else {
        if (key == '$text' || key == '$t' || key == '#text') {
          xw.text(getval(val));
        }
        else if (key == '$comment' || key == '$c' || key == '#comment') {
          xw.startComment().text(getval(val)).endComment();
        }
        else if (key == '$cdata' || key == '$cd' || key == '#cdata' || key == '#cd') {
          xw.startCData().text(getval(val)).endCData();
        }
        else  {
          xw.startAttribute(getname(key)).text(getval(val)).endAttribute();
        }
      }
    }
  }
  var o = {};
  if (Object.keys(obj).length == 1) {
    o = obj;
  }
  else {
    o[default_tag_name] = obj;
  }
  parse(o);

  return xw.toString();

};
exports.load = function (str, options) {
  options = options || {};
  if (typeof str != "string") {
    if (options.throwErrors) {
      throw new Error("Input was " + (typeof str) + ", expected a string");
    }
    return str;
  }
  var parser = require("sax").parser(true, {trim : true, xmlns : false});
  var result = {},
      stack = [],
      cdata = '',
      nested = { check : [], previous : '', memory : [], buffer: '', name: [] },
      path = [];

  function cvalue(n, v, isXmlNode) {
    n = n.replace(':', '$');
    var o = stack[stack.length - 1];
    if (o === undefined) {
      o = {};
      o[n] = v;
      return o[n];
    }
    else if (o[n] === undefined) {
      if (isXmlNode && options.arrays && options.arrays.indexOf("/" + path.join("/")) !== -1) {
        // Create new array only if current item is a XML node,
        // meaning it is not text, cdata or comment
        o[n] = new Array(v);
        return o[n][0];
      }
      else {
        o[n] = v;
        return o[n];
      }
    }
    else if (!Array.isArray(o[n])) {
      var x = o[n];
      o[n] = new Array(x, v);
      return o[n][1];
    }
    else {
      var i = o[n].push(v);
      return o[n][i - 1];
    }
  }
  function cattr(o) {
    var r = {};
    for (var key in o) {
      if (o.hasOwnProperty(key) && o[key]) {
        r[key.replace(':', '$')] = o[key];
      }
    }
    return r;
  }

  if (!options.throwErrors) {
    parser.onerror = function (e) {
      // an error happened.
    };
  }
  parser.onprocessinginstruction = function (pi) {
  };
  parser.ontext = function (v) {
    if (options.nested) {
      if (nested.previous === 'C') {
        nested.check[nested.check.length - 1] = true;
      }
      nested.buffer = v;
      nested.memory.map(function (item) { return item.text(v) });
      nested.previous = 'T';
    }
    else {
      cvalue('$t', v);
    }
  };
  parser.oncomment = function (v) {
    if (options.comments === undefined || options.comments !== false) {
      cvalue('$c', v);
    }
  };
  parser.oncdata = function (v) {
    cdata += v;
  };
  parser.onopencdata = function () {
    cdata = '';
  };
  parser.onclosecdata = function () {
    cvalue('$cd', cdata);
    cdata = '';
  };
  parser.onopentag = function (node) {
    if (options.arrays) {
      path.push(node.name);
    }
    if (options.nested) {
      if (nested.previous === 'T') {
        nested.check[nested.check.length - 1] = true;
      }
      nested.buffer = '';
      nested.name.push(node.name);
      nested.memory.push(new XMLWriter());
      nested.check.push(false);
      nested.previous = 'O';

      nested.memory.map(function(item) {
          return Object.keys(node.attributes).reduce(function(prev, cur) {
              return prev.writeAttribute(cur, node.attributes[cur])
            }, item.startElement(node.name))
      });
    }

    if (stack.length == 1 && node.name == default_tag_name) {
      result = cattr(node.attributes);
      stack.push(result);
    }
    else {
      stack.push(cvalue(node.name, cattr(node.attributes), true));
    }
  };
  parser.onclosetag = function () {
    if (options.arrays) {
      path.pop();
    }
    if (options.nested) {
      nested.memory.map(function(item) { return item.endElement() });
      var ne = nested.name.pop();
      var xv = nested.memory.pop();
      if (nested.check.pop() === true) {
        var r1 = RegExp('(^<' + ne + '[^>]*>)', 'g'),
            r2 = RegExp('(</' + ne + '[^>]*>$)', 'g');
        cvalue('$t', xv.toString().replace(r1, '').replace(r2, ''));
      }
      else {
        if (nested.buffer) {
          cvalue('$t', nested.buffer);
      }
      }
      xv = null;
      nested.buffer = '';
      nested.previous = 'C';
    }

    stack.pop();
  };
  parser.onready = function () {
  };
  parser.onend = function () {
  };

  if (options.throwErrors) {
    stack.push(result);
    parser.write(str);
    var line = parser.line;
    var column = parser.column;
    parser.close();
    stack.pop();

    if (stack.length !== 0) {
      var er = "Unexpected end of input";
      er += "\nLine: " + line +
            "\nColumn: " + column +
            "\nChar: null";
      throw new Error(er);
    }
  } else {
    stack.push(result);
    try {
      parser.write(str).close();
    }
    catch (e) {
      return str;
    }
    stack.pop();
  }

  return result;
}

exports.toxml = exports.dump;
exports.tojson = exports.load;
<|endoftext|>


<commit_before>/*
 * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
 */

L.FeatureGroup = L.LayerGroup.extend({
	includes: L.Mixin.Events,

	addLayer: function (layer) {
		this._initEvents(layer);

		L.LayerGroup.prototype.addLayer.call(this, layer);

		if (this._popupContent && layer.bindPopup) {
			layer.bindPopup(this._popupContent);
		}
	},

	removeLayer: function (layer) {
		this._deinitEvents(layer);

		L.LayerGroup.prototype.removeLayer.call(this, layer);

		this.invoke('unbindPopup');
	},

	bindPopup: function (content) {
		this._popupContent = content;
		return this.invoke('bindPopup', content);
	},

	setStyle: function (style) {
		return this.invoke('setStyle', style);
	},

	getBounds: function () {
		var bounds = new L.LatLngBounds();
		this._iterateLayers(function (layer) {
			bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
		}, this);
		return bounds;
	},

	_initEvents: function (layer) {
		layer.on('click dblclick mouseover mouseout', this._propagateEvent, this);
	},

	_deinitEvents: function (layer) {
		var events = ['click', 'dblclick', 'mouseover', 'mouseout'],
			i, len;

		for (i = 0, len = events.length; i < len; i++) {
			layer.off(events[i], this._propagateEvent, this);
		}
	},

	_propagateEvent: function (e) {
		e.layer  = e.target;
		e.target = this;

		this.fire(e.type, e);
	}
});
<commit_msg>Line up FeatureGroup change with master<commit_after>/*
 * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
 */

L.FeatureGroup = L.LayerGroup.extend({
	includes: L.Mixin.Events,

	addLayer: function (layer) {
		this._initEvents(layer);

		L.LayerGroup.prototype.addLayer.call(this, layer);

		if (this._popupContent && layer.bindPopup) {
			layer.bindPopup(this._popupContent);
		}
	},

	removeLayer: function (layer) {
		this._deinitEvents(layer);

		L.LayerGroup.prototype.removeLayer.call(this, layer);

		this.invoke('unbindPopup');
	},

	bindPopup: function (content) {
		this._popupContent = content;
		return this.invoke('bindPopup', content);
	},

	setStyle: function (style) {
		return this.invoke('setStyle', style);
	},

	getBounds: function () {
		var bounds = new L.LatLngBounds();
		this._iterateLayers(function (layer) {
			bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
		}, this);
		return bounds;
	},

	_initEvents: function (layer) {
		layer.on('click dblclick mouseover mouseout', this._propagateEvent, this);
	},

	_deinitEvents: function (layer) {
		layer.off('click dblclick mouseover mouseout', this._propagateEvent, this);
	},

	_propagateEvent: function (e) {
		e.layer  = e.target;
		e.target = this;

		this.fire(e.type, e);
	}
});
<|endoftext|>


<commit_before>/**
* (C) Copyright IBM Corp. 2016. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

require('./styles.css');

var events = require('../../events');
var profile = require('../../profile');
var subscribe = events.subscribe;
var publish = events.publish;
var activeClassName = 'IBMChat-accent-colors';
var inactiveClassName = 'IBMChat-secondary-colors';
var utils = require('../../utils');
var ns = 'IBMChat-form';
var widgets = [];
var templates = {
	base: require('./templates/base.html'),
	field: require('./templates/field.html')
};

var formLayout = {
	init: function() {
		subscribe('layout:form', function(data) {
			var widget = new Form(data);
			widgets[data.uuid] = widget;
		});
	}
};

function Form(data) {
	this.init(data);
}

Form.prototype.init = function(data) {
	this.data = data.message.store || [];
	this.action = data.message.action || '';
	this.uuid = data.uuid;
	this.parentElement = data.element;
	this.layoutElement = data.layoutElement;
	this.msgElement = data.msgElement;
	this.drawForm();
	this.subscribeSend = subscribe('send', this.removeAllEventListeners.bind(this));
	publish('disable-input');
};

Form.prototype.drawForm = function() {
	var base = document.createElement('div');
	var formFields;
	base.innerHTML = templates.base;
	formFields = base.querySelector('.IBMChat-form-fields');
	for (var i = 0; i < this.data.length; i++) {
		var field = document.createElement('div');
		field.innerHTML = utils.compile(templates.field, {
			label: this.data[i].label || '',
			name: this.data[i].name,
			value: ''
		});
		field.className = ns + '-fields-row';
		formFields.appendChild(field);
	}
	this.fields = formFields.querySelectorAll('input');
	this.submitButton = base.querySelector('.' + ns + '-submit');
	this.cancelButton = base.querySelector('.' + ns + '-cancel');
	this.submitButton.classList.add(activeClassName);
	this.cancelButton.classList.add(inactiveClassName);
	this.layoutElement.appendChild(base);
	this.fields[0].focus();
	this.addListeners();
};

Form.prototype.handleSubmit = function() {
	if (this.validateFields() === true) {
		for (var i = 0; i < this.fields.length; i++) {
			var field = this.fields[i];
			profile.set(field.getAttribute('name'), field.value);
		}
		publish('send', {
			silent: true,
			text: 'success'
		});
		publish('enable-input');
	}
};

Form.prototype.validateFields = function() {
	var valid = true;
	for (var i = 0; i < this.data.length; i++) {
		if (this.data[i].required && this.data[i].required != 'false') {
			if (!this.fields[i].value || this.fields[i].value.length === 0) {
				this.showError(this.fields[i].getAttribute('name'), 'This field is required.');
				valid = false;
			}
		}
	}
	return valid;
};

Form.prototype.showError = function(name, error) {
	var el = this.layoutElement.querySelector('[data-name="' + name + '"]');
	el.textContent = error;
	el.style.display = 'block';
};

Form.prototype.hideErrors = function() {
	var els = this.layoutElement.querySelectorAll('[data-name]');
	for (var i = 0; i < els.length; i++) {
		els[i].textContent = '';
		els[i].style.display = 'none';
	}
};

Form.prototype.handleEnter = function(e) {
	if (e.keyCode === 13)
		this.handleSubmit();
};

Form.prototype.handleCancel = function() {
	publish('send', {
		silent: true,
		text: 'cancel'
	});
	publish('enable-input');
};

Form.prototype.addListeners = function() {
	this.cancelButton.addEventListener('click', this.handleCancel.bind(this));
	this.submitButton.addEventListener('click', this.handleSubmit.bind(this));
	for (var i = 0; i < this.fields.length; i++)
		this.fields[i].addEventListener('keypress', this.handleEnter.bind(this));
};

Form.prototype.removeAllEventListeners = function() {
	this.cancelButton.removeEventListener('click', this.handleCancel.bind(this));
	this.cancelButton.setAttribute('disabled', true);
	this.submitButton.removeEventListener('click', this.handleSubmit.bind(this));
	this.submitButton.setAttribute('disabled', true);
	for (var i = 0; i < this.fields.length; i++) {
		this.fields[i].removeEventListener('keypress', this.handleEnter.bind(this));
		this.fields[i].setAttribute('disabled', true);
	}

	this.subscribeSend.remove();
};

module.exports = formLayout;
<commit_msg>make cancel/submit buttons styling consistent with other layouts behavior<commit_after>/**
* (C) Copyright IBM Corp. 2016. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

require('./styles.css');

var events = require('../../events');
var profile = require('../../profile');
var subscribe = events.subscribe;
var publish = events.publish;
var activeClassName = 'IBMChat-accent-colors';
var inactiveClassName = 'IBMChat-secondary-colors';
var utils = require('../../utils');
var ns = 'IBMChat-form';
var widgets = [];
var templates = {
	base: require('./templates/base.html'),
	field: require('./templates/field.html')
};

var formLayout = {
	init: function() {
		subscribe('layout:form', function(data) {
			var widget = new Form(data);
			widgets[data.uuid] = widget;
		});
	}
};

function Form(data) {
	this.init(data);
}

Form.prototype.init = function(data) {
	this.data = data.message.store || [];
	this.action = data.message.action || '';
	this.uuid = data.uuid;
	this.parentElement = data.element;
	this.layoutElement = data.layoutElement;
	this.msgElement = data.msgElement;
	this.drawForm();
	this.subscribeSend = subscribe('send', this.removeAllEventListeners.bind(this));
	publish('disable-input');
};

Form.prototype.drawForm = function() {
	var base = document.createElement('div');
	var formFields;
	base.innerHTML = templates.base;
	formFields = base.querySelector('.IBMChat-form-fields');
	for (var i = 0; i < this.data.length; i++) {
		var field = document.createElement('div');
		field.innerHTML = utils.compile(templates.field, {
			label: this.data[i].label || '',
			name: this.data[i].name,
			value: ''
		});
		field.className = ns + '-fields-row';
		formFields.appendChild(field);
	}
	this.fields = formFields.querySelectorAll('input');
	this.submitButton = base.querySelector('.' + ns + '-submit');
	this.cancelButton = base.querySelector('.' + ns + '-cancel');
	this.submitButton.classList.add(inactiveClassName);
	this.cancelButton.classList.add(inactiveClassName);
	this.layoutElement.appendChild(base);
	this.fields[0].focus();
	this.addListeners();
};

Form.prototype.handleSubmit = function() {
	if (this.validateFields() === true) {
		for (var i = 0; i < this.fields.length; i++) {
			var field = this.fields[i];
			profile.set(field.getAttribute('name'), field.value);
		}
		this.submitButton.classList.add(activeClassName);
		publish('send', {
			silent: true,
			text: 'success'
		});
		publish('enable-input');
	}
};

Form.prototype.validateFields = function() {
	var valid = true;
	for (var i = 0; i < this.data.length; i++) {
		if (this.data[i].required && this.data[i].required != 'false') {
			if (!this.fields[i].value || this.fields[i].value.length === 0) {
				this.showError(this.fields[i].getAttribute('name'), 'This field is required.');
				valid = false;
			}
		}
	}
	return valid;
};

Form.prototype.showError = function(name, error) {
	var el = this.layoutElement.querySelector('[data-name="' + name + '"]');
	el.textContent = error;
	el.style.display = 'block';
};

Form.prototype.hideErrors = function() {
	var els = this.layoutElement.querySelectorAll('[data-name]');
	for (var i = 0; i < els.length; i++) {
		els[i].textContent = '';
		els[i].style.display = 'none';
	}
};

Form.prototype.handleEnter = function(e) {
	if (e.keyCode === 13)
		this.handleSubmit();
};

Form.prototype.handleCancel = function() {
	this.cancelButton.classList.add(activeClassName);
	publish('send', {
		silent: true,
		text: 'cancel'
	});
	publish('enable-input');
};

Form.prototype.addListeners = function() {
	this.cancelButton.addEventListener('click', this.handleCancel.bind(this));
	this.submitButton.addEventListener('click', this.handleSubmit.bind(this));
	for (var i = 0; i < this.fields.length; i++)
		this.fields[i].addEventListener('keypress', this.handleEnter.bind(this));
};

Form.prototype.removeAllEventListeners = function() {
	this.cancelButton.removeEventListener('click', this.handleCancel.bind(this));
	this.cancelButton.setAttribute('disabled', true);
	this.submitButton.removeEventListener('click', this.handleSubmit.bind(this));
	this.submitButton.setAttribute('disabled', true);
	for (var i = 0; i < this.fields.length; i++) {
		this.fields[i].removeEventListener('keypress', this.handleEnter.bind(this));
		this.fields[i].setAttribute('disabled', true);
	}

	this.subscribeSend.remove();
};

module.exports = formLayout;
<|endoftext|>


<commit_before>
// From http://www.tutorialspoint.com/javascript/array_map.htm
if (!Array.prototype.map)
{
  Array.prototype.map = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun !== "function")
      throw new TypeError();

    var res = new Array(len);
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        res[i] = fun.call(thisp, this[i], i, this);
    }

    return res;
  };
}

L.Control.SearchMenu = L.Control.extend({

    includes: L.Mixin.Events,

    options: {
        position: 'topright',
        title: 'Search',
        placeholder: 'Search',
        caseSensitive: false,
        threshold: 0.5,
        maxResultLength: null,
        showInvisibleFeatures: true
    },

    initialize: function(options) {
        L.setOptions(this, options);
        this._panelOnLeftSide = (this.options.position.indexOf("left") !== -1);
    },

    onAdd: function(map) {

        var ctrl = this._createControl();
        this._createPanel(map);
        this._setEventListeners();
        map.invalidateSize();

        return ctrl;
    },

    onRemove: function(map) {

        this.hidePanel(map);
        this._clearEventListeners();
        this._clearPanel(map);
        this._clearControl();

        return this;
    },

    _createControl: function() {

        var className = 'leaflet-searchmenu-control',
            container = L.DomUtil.create('div', className);

        // Control to open the search panel
        var butt = this._openButton = L.DomUtil.create('a', 'button', container);
        butt.href = '#';
        butt.title = this.options.title;
        var stop = L.DomEvent.stopPropagation;
        L.DomEvent.on(butt, 'click', stop)
                  .on(butt, 'mousedown', stop)
                  .on(butt, 'touchstart', stop)
                  .on(butt, 'mousewheel', stop)
                  .on(butt, 'MozMousePixelScroll', stop);
        L.DomEvent.on(butt, 'click', L.DomEvent.preventDefault);
        L.DomEvent.on(butt, 'click', this.showPanel, this);

        return container;
    },

    _clearControl: function() {
        // Unregister events to prevent memory leak
        var butt = this._openButton;
        var stop = L.DomEvent.stopPropagation;
        L.DomEvent.off(butt, 'click', stop)
                  .off(butt, 'mousedown', stop)
                  .off(butt, 'touchstart', stop)
                  .off(butt, 'mousewheel', stop)
                  .off(butt, 'MozMousePixelScroll', stop);
        L.DomEvent.off(butt, 'click', L.DomEvent.preventDefault);
        L.DomEvent.off(butt, 'click', this.showPanel);
    },

    _createPanel: function(map) {
        var _this = this;

        // Create the search panel
        var mapContainer = map.getContainer();
        var className = 'leaflet-searchmenu-panel',
            pane = this._panel = L.DomUtil.create('div', className, mapContainer);

        // Make sure we don't drag the map when we interact with the content
        var stop = L.DomEvent.stopPropagation;
        L.DomEvent.on(pane, 'click', stop)
                .on(pane, 'dblclick', stop)
                .on(pane, 'mousedown', stop)
                .on(pane, 'touchstart', stop)
                .on(pane, 'mousewheel', stop)
                .on(pane, 'MozMousePixelScroll', stop);

        // place the pane on the same side as the control
        if (this._panelOnLeftSide) {
            L.DomUtil.addClass(pane, 'left');
        } else {
            L.DomUtil.addClass(pane, 'right');
        }

        // Intermediate container to get the box sizing right
        var container = L.DomUtil.create('div', 'content', pane);

        // Search image and input field
        L.DomUtil.create('img', 'search-image', container);
        this._input = L.DomUtil.create('input', 'search-input', container);
        this._input.maxLength = 30;
        this._input.placeholder = this.options.placeholder;
        this._input.onkeyup = function(evt) {
            var searchString = evt.currentTarget.value;
            _this.search(searchString);
        };

        // Close button
        var close = this._closeButton = L.DomUtil.create('a', 'close', container);
        close.innerHTML = '×';
        L.DomEvent.on(close, 'click', this.hidePanel, this);

        // Where the result will be listed
        this._resultList = L.DomUtil.create('div', 'result-list', container);

        return pane;
    },

    _clearPanel: function(map) {

        // Unregister event handlers
        var stop = L.DomEvent.stopPropagation;
        L.DomEvent.off(this._panel, 'click', stop)
                  .off(this._panel, 'dblclick', stop)
                  .off(this._panel, 'mousedown', stop)
                  .off(this._panel, 'touchstart', stop)
                  .off(this._panel, 'mousewheel', stop)
                  .off(this._panel, 'MozMousePixelScroll', stop);

        L.DomEvent.off(this._closeButton, 'click', this.hidePanel);

        var mapContainer = map.getContainer();
        mapContainer.removeChild(this._panel);

        this._panel = null;
    },

    _setEventListeners : function() {
        var that = this;
        var input = this._input;
        this._map.addEventListener('overlayadd', function() {
            that.search(input.value);
        });
        this._map.addEventListener('overlayremove', function() {
            that.search(input.value);
        });
    },

    _clearEventListeners: function() {
        this._map.removeEventListener('overlayadd');
        this._map.removeEventListener('overlayremove');
    },

    isPanelVisible: function () {
        return L.DomUtil.hasClass(this._panel, 'visible');
    },

    showPanel: function () {
        if (! this.isPanelVisible()) {
            L.DomUtil.addClass(this._panel, 'visible');
            // Preserve map centre
            this._map.panBy([this.getOffset() * 0.5, 0], {duration: 0.5});
            this.fire('show');
            this._input.select();
            // Search again as visibility of features might have changed
            this.search(this._input.value);
        }
    },

    hidePanel: function (e) {
        if (this.isPanelVisible()) {
            L.DomUtil.removeClass(this._panel, 'visible');
            // Move back the map centre - only if we still hold this._map
            // as this might already have been cleared up by removeFrom()
            if (null !== this._map) {
                this._map.panBy([this.getOffset() * -0.5, 0], {duration: 0.5});
            };
            this.fire('hide');
            if(e) {
                L.DomEvent.stopPropagation(e);
            }
        }
    },

    getOffset: function() {
        if (this._panelOnLeftSide) {
            return - this._panel.offsetWidth;
        } else {
            return this._panel.offsetWidth;
        }
    },

    search: function(string) {
        var _this = this;
        this.options.search(string, function(result){
            // Empty result list
            $(".result-item").remove();

            var resultList = $('.result-list')[0];
            var num = 0;
            var max = _this.options.maxResultLength;
            for (var i in results) {
                var result = results[i];
                _this.createResultItem(result, resultList);
                if (undefined !== max && ++num === max)
                    break;
            }
        });
    },

    createResultItem: function(result, container) {

        var _this = this;

        // Create a container and open the associated popup on click
        var resultItem = L.DomUtil.create('p', 'result-item', container);

        L.DomUtil.addClass(resultItem, 'clickable');
        resultItem.onclick = function() {

            if (window.matchMedia("(max-width:480px)").matches) {
                _this.hidePanel();
            }
            _this.options.popup(result);
        };

        // Fill in the container with the user-supplied function if any,
        // otherwise display the result properties used for the search.
        this.options.showResultFct(result, resultItem);

        return resultItem;
    },
});

L.control.searchMenu = function(options) {
    return new L.Control.SearchMenu(options);
};
<commit_msg>fix typo<commit_after>
// From http://www.tutorialspoint.com/javascript/array_map.htm
if (!Array.prototype.map)
{
  Array.prototype.map = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun !== "function")
      throw new TypeError();

    var res = new Array(len);
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        res[i] = fun.call(thisp, this[i], i, this);
    }

    return res;
  };
}

L.Control.SearchMenu = L.Control.extend({

    includes: L.Mixin.Events,

    options: {
        position: 'topright',
        title: 'Search',
        placeholder: 'Search',
        caseSensitive: false,
        threshold: 0.5,
        maxResultLength: null,
        showInvisibleFeatures: true
    },

    initialize: function(options) {
        L.setOptions(this, options);
        this._panelOnLeftSide = (this.options.position.indexOf("left") !== -1);
    },

    onAdd: function(map) {

        var ctrl = this._createControl();
        this._createPanel(map);
        this._setEventListeners();
        map.invalidateSize();

        return ctrl;
    },

    onRemove: function(map) {

        this.hidePanel(map);
        this._clearEventListeners();
        this._clearPanel(map);
        this._clearControl();

        return this;
    },

    _createControl: function() {

        var className = 'leaflet-searchmenu-control',
            container = L.DomUtil.create('div', className);

        // Control to open the search panel
        var butt = this._openButton = L.DomUtil.create('a', 'button', container);
        butt.href = '#';
        butt.title = this.options.title;
        var stop = L.DomEvent.stopPropagation;
        L.DomEvent.on(butt, 'click', stop)
                  .on(butt, 'mousedown', stop)
                  .on(butt, 'touchstart', stop)
                  .on(butt, 'mousewheel', stop)
                  .on(butt, 'MozMousePixelScroll', stop);
        L.DomEvent.on(butt, 'click', L.DomEvent.preventDefault);
        L.DomEvent.on(butt, 'click', this.showPanel, this);

        return container;
    },

    _clearControl: function() {
        // Unregister events to prevent memory leak
        var butt = this._openButton;
        var stop = L.DomEvent.stopPropagation;
        L.DomEvent.off(butt, 'click', stop)
                  .off(butt, 'mousedown', stop)
                  .off(butt, 'touchstart', stop)
                  .off(butt, 'mousewheel', stop)
                  .off(butt, 'MozMousePixelScroll', stop);
        L.DomEvent.off(butt, 'click', L.DomEvent.preventDefault);
        L.DomEvent.off(butt, 'click', this.showPanel);
    },

    _createPanel: function(map) {
        var _this = this;

        // Create the search panel
        var mapContainer = map.getContainer();
        var className = 'leaflet-searchmenu-panel',
            pane = this._panel = L.DomUtil.create('div', className, mapContainer);

        // Make sure we don't drag the map when we interact with the content
        var stop = L.DomEvent.stopPropagation;
        L.DomEvent.on(pane, 'click', stop)
                .on(pane, 'dblclick', stop)
                .on(pane, 'mousedown', stop)
                .on(pane, 'touchstart', stop)
                .on(pane, 'mousewheel', stop)
                .on(pane, 'MozMousePixelScroll', stop);

        // place the pane on the same side as the control
        if (this._panelOnLeftSide) {
            L.DomUtil.addClass(pane, 'left');
        } else {
            L.DomUtil.addClass(pane, 'right');
        }

        // Intermediate container to get the box sizing right
        var container = L.DomUtil.create('div', 'content', pane);

        // Search image and input field
        L.DomUtil.create('img', 'search-image', container);
        this._input = L.DomUtil.create('input', 'search-input', container);
        this._input.maxLength = 30;
        this._input.placeholder = this.options.placeholder;
        this._input.onkeyup = function(evt) {
            var searchString = evt.currentTarget.value;
            _this.search(searchString);
        };

        // Close button
        var close = this._closeButton = L.DomUtil.create('a', 'close', container);
        close.innerHTML = '×';
        L.DomEvent.on(close, 'click', this.hidePanel, this);

        // Where the result will be listed
        this._resultList = L.DomUtil.create('div', 'result-list', container);

        return pane;
    },

    _clearPanel: function(map) {

        // Unregister event handlers
        var stop = L.DomEvent.stopPropagation;
        L.DomEvent.off(this._panel, 'click', stop)
                  .off(this._panel, 'dblclick', stop)
                  .off(this._panel, 'mousedown', stop)
                  .off(this._panel, 'touchstart', stop)
                  .off(this._panel, 'mousewheel', stop)
                  .off(this._panel, 'MozMousePixelScroll', stop);

        L.DomEvent.off(this._closeButton, 'click', this.hidePanel);

        var mapContainer = map.getContainer();
        mapContainer.removeChild(this._panel);

        this._panel = null;
    },

    _setEventListeners : function() {
        var that = this;
        var input = this._input;
        this._map.addEventListener('overlayadd', function() {
            that.search(input.value);
        });
        this._map.addEventListener('overlayremove', function() {
            that.search(input.value);
        });
    },

    _clearEventListeners: function() {
        this._map.removeEventListener('overlayadd');
        this._map.removeEventListener('overlayremove');
    },

    isPanelVisible: function () {
        return L.DomUtil.hasClass(this._panel, 'visible');
    },

    showPanel: function () {
        if (! this.isPanelVisible()) {
            L.DomUtil.addClass(this._panel, 'visible');
            // Preserve map centre
            this._map.panBy([this.getOffset() * 0.5, 0], {duration: 0.5});
            this.fire('show');
            this._input.select();
            // Search again as visibility of features might have changed
            this.search(this._input.value);
        }
    },

    hidePanel: function (e) {
        if (this.isPanelVisible()) {
            L.DomUtil.removeClass(this._panel, 'visible');
            // Move back the map centre - only if we still hold this._map
            // as this might already have been cleared up by removeFrom()
            if (null !== this._map) {
                this._map.panBy([this.getOffset() * -0.5, 0], {duration: 0.5});
            };
            this.fire('hide');
            if(e) {
                L.DomEvent.stopPropagation(e);
            }
        }
    },

    getOffset: function() {
        if (this._panelOnLeftSide) {
            return - this._panel.offsetWidth;
        } else {
            return this._panel.offsetWidth;
        }
    },

    search: function(string) {
        var _this = this;
        this.options.search(string, function(results){
            // Empty result list
            $(".result-item").remove();

            var resultList = $('.result-list')[0];
            var num = 0;
            var max = _this.options.maxResultLength;
            for (var i in results) {
                var result = results[i];
                _this.createResultItem(result, resultList);
                if (undefined !== max && ++num === max)
                    break;
            }
        });
    },

    createResultItem: function(result, container) {

        var _this = this;

        // Create a container and open the associated popup on click
        var resultItem = L.DomUtil.create('p', 'result-item', container);

        L.DomUtil.addClass(resultItem, 'clickable');
        resultItem.onclick = function() {

            if (window.matchMedia("(max-width:480px)").matches) {
                _this.hidePanel();
            }
            _this.options.popup(result);
        };

        // Fill in the container with the user-supplied function if any,
        // otherwise display the result properties used for the search.
        this.options.showResultFct(result, resultItem);

        return resultItem;
    },
});

L.control.searchMenu = function(options) {
    return new L.Control.SearchMenu(options);
};
<|endoftext|>


<commit_before>var glob = require("glob-whatev"),
    path = require("path"),
    fs = require("fs"),
    _ = require("underscore");

function Package(packageName) {
    this.name = packageName;
    this.version = '';
    this.dependencies = [];
    this.serverFiles = [];
    this.clientFiles = [];
    this.path        = Packager._currentPath;
    this.currentLayer = null;
    Packager.add(this.name, this);
}

Package.prototype.server = function() {
    this.currentLayer = 'server';
    return this;
};

Package.prototype.shared = function() {
    this.currentLayer = 'shared';
    return this;
};

Package.prototype.deps = function(arr) {
    var self = this;
    if(!(arr instanceof Array)) return false;

    arr.forEach(function(dep) {
        self.dependencies.push(dep);
    });

    return this;
};

Package.prototype.dep = function(dep) {
    this.dependencies.push(dep);
    return this;
};

Package.prototype.client = function() {
    this.currentLayer = 'client';
    return this;
};

Package.prototype.add = function(file) {
    switch(this.currentLayer) {
    case "server":
        this.serverFiles.push(file);
        break;
    case "client":
        this.clientFiles.push(file);
        break;
    }
    return this;
};

Packager = {
    _autoload: ['server.js', 'index.js'],
    _packages: {},
    _paths: [
    path.join(__dirname, '..'), path.join(Tower.cwd, 'node_modules'), path.join(process.cwd(), 'packages')],
    _currentPath: null,
    get: function(name) {
        if(this._packages[name]) return this._packages[name]
        else throw Error("Package '" + name + "' was not found.");
    }
};

Packager.add = function(name, instance) {
    this._packages[name] = instance;
};

Packager.create = function(packageName) {
    return new Package(packageName);
};

Packager.run = function(callback) {
    var self = this;
    this.find(function() {
        callback(Object.keys(self._packages).length);
    });
};

Packager.load = function(file) {
    // Load the package:
    this._currentPath = file.replace(/package.js/i, '');
    require(file);
};

Packager.find = function(callback) {
    var self = this;
    var done = false;
    this._paths.forEach(function(p, i) {
        var dir = fs.readdirSync(p);

        dir.forEach(function(_dir) {
            if(_dir.match("tower-packages")) return;

            if(fs.existsSync(path.join(p, _dir, 'package.js'))) {
                self.load(path.join(p, _dir, 'package.js'));
            }

        });

        if(i == (self._paths.length - 1)) {
            if(!done) {
                process.nextTick(
                callback);
                done = true;
            }
        }
    });

};

Packager.require = function(package, explicitfile) {
    var self = this,
        pack = this.get(package),
        file;

    function tryFile(i) {

        if(!explicitfile) {
            if(!i) {
                i = 0;
            }
            file = self._autoload[i];
            if(!file) {
                throw new Error('Cannot autoload any files within the "' + package + '" package. Tried: ' + this._autoload.join(' '));
            }
        } else {
            file = explicitfile;
        }
        var fullPath = path.join(pack.path, file);
        var exists = fs.existsSync(fullPath);
        if(exists) {
            // Check if the package has dependencies:
            pack.dependencies.forEach(function (dep) {
                Packager.require(dep);
            });
            return require(fullPath);
        } else {
            if(self._autoload.length === i) {
                throw new Error('Could not load "' + file + '" from the "' + package + '" package.');
            } else {
                return tryFile(i+1);
            }
        }
    }

    return tryFile(0);
};

Tower.Packager = Packager;<commit_msg>Added caching on packages.<commit_after>var glob = require("glob-whatev"),
    path = require("path"),
    fs = require("fs"),
    _ = require("underscore");

function Package(packageName) {
    this.name = packageName;
    this.version = '';
    this.dependencies = [];
    this.serverFiles = [];
    this.clientFiles = [];
    this.path        = Packager._currentPath;
    this.currentLayer = null;
    Packager.add(this.name, this);
}

Package.prototype.server = function() {
    this.currentLayer = 'server';
    return this;
};

Package.prototype.shared = function() {
    this.currentLayer = 'shared';
    return this;
};

Package.prototype.deps = function(arr) {
    var self = this;
    if(!(arr instanceof Array)) return false;

    arr.forEach(function(dep) {
        self.dependencies.push(dep);
    });

    return this;
};

Package.prototype.dep = function(dep) {
    this.dependencies.push(dep);
    return this;
};

Package.prototype.client = function() {
    this.currentLayer = 'client';
    return this;
};

Package.prototype.add = function(file) {
    switch(this.currentLayer) {
    case "server":
        this.serverFiles.push(file);
        break;
    case "client":
        this.clientFiles.push(file);
        break;
    }
    return this;
};

Packager = {
    _autoload: ['server.js', 'index.js'],
    _packages: {},
    _paths: [
    path.join(__dirname, '..'), path.join(Tower.cwd, 'node_modules'), path.join(process.cwd(), 'packages')],
    _cache: {},
    _currentPath: null,
    get: function(name) {
        if(this._packages[name]) return this._packages[name]
        else throw Error("Package '" + name + "' was not found.");
    }
};

Packager.add = function(name, instance) {
    this._packages[name] = instance;
};

Packager.create = function(packageName) {
    return new Package(packageName);
};

Packager.run = function(callback) {
    var self = this;
    this.find(function() {
        callback(Object.keys(self._packages).length);
    });
};

Packager.load = function(file) {
    // Load the package:
    this._currentPath = file.replace(/package.js/i, '');
    require(file);
};

Packager.find = function(callback) {
    var self = this;
    var done = false;
    this._paths.forEach(function(p, i) {
        var dir = fs.readdirSync(p);

        dir.forEach(function(_dir) {
            if(_dir.match("tower-packages")) return;

            if(fs.existsSync(path.join(p, _dir, 'package.js'))) {
                self.load(path.join(p, _dir, 'package.js'));
            }

        });

        if(i == (self._paths.length - 1)) {
            if(!done) {
                process.nextTick(
                callback);
                done = true;
            }
        }
    });

};

Packager.require = function(package, explicitfile) {
    var self = this,
        pack = this.get(package),
        file;

    function tryFile(i) {

        if(!explicitfile) {
            if(!i) {
                i = 0;
            }
            file = self._autoload[i];
            if(!file) {
                throw new Error('Cannot autoload any files within the "' + package + '" package. Tried: ' + this._autoload.join(' '));
            }
        } else {
            file = explicitfile;
        }
        var fullPath = path.join(pack.path, file);
        var exists = fs.existsSync(fullPath);
        if(exists) {
            // Check if the package has dependencies:
            pack.dependencies.forEach(function (dep) {
                Packager.require(dep);
            });
            self._cache[package] = {
                loaded: true,
                path: fullPath,
                dependencies: pack.dependencies
            };
            return require(fullPath);
        } else {
            if(self._autoload.length === i) {
                throw new Error('Could not load "' + file + '" from the "' + package + '" package.');
            } else {
                return tryFile(i+1);
            }
        }
    }

    return tryFile(0);
};

Tower.Packager = Packager;<|endoftext|>


<commit_before>
import h from '../dom/hyper-hermes'

function tip (node, text, offset) {
  offset = offset || 5
  let el, rect
  let body = document.body
  let onmouseover = () => {
    // let rect = node.getBoundingClientRect()
    if (!rect) rect = node.getBoundingClientRect()
    if (!el) {
      body.appendChild(el =
        h('div', {s: {position: 'absolute'}},
          h('div', {c: 'tooltip-arrow'}),
          h('div', {c: 'tooltip-inner'}, text)
        )
      )
    }
    el.style.display = 'block'
    el.style.top = offset + rect.top + 'px'
    el.style.left = Math.ceil(rect.right - (rect.width / 2)) + 'px'
    el.style.marginLeft = -Math.ceil((el.clientWidth-4) / 2) + 'px'
  }
  let onmouseout = () => {
    el.style.display = 'none'
  }
  node.addEventListener('mouseover', onmouseover)
  node.addEventListener('mouseout', onmouseout)

  return {
    teardown () {
      node.removeEventListener('mouseover', onmouseover)
      node.removeEventListener('mouseout', onmouseout)
      if (el) body.removeChild(el)
    }
  }
}

export default tip
<commit_msg>fix tip<commit_after>
import h from '../dom/hyper-hermes'

function tip (node, text, offset) {
  offset = offset || 5
  let el, rect
  let body = document.body
  let onmouseover = () => {
    let rect = node.getBoundingClientRect()
    if (!el) {
      body.appendChild(el =
        h('div', {s: {position: 'absolute'}},
          h('div', {c: 'tooltip-arrow'}),
          h('div', {c: 'tooltip-inner'}, text)
        )
      )
    }
    el.style.display = 'block'
    el.style.top = node.scrollHeight + offset + rect.top + 'px'
    el.style.left = Math.ceil(rect.right - (rect.width / 2)) + 'px'
    el.style.marginLeft = -Math.ceil((el.clientWidth-4) / 2) + 'px'
  }
  let onmouseout = () => {
    el.style.display = 'none'
  }
  node.addEventListener('mouseover', onmouseover)
  node.addEventListener('mouseout', onmouseout)

  return {
    teardown () {
      node.removeEventListener('mouseover', onmouseover)
      node.removeEventListener('mouseout', onmouseout)
      if (el) body.removeChild(el)
    }
  }
}

export default tip
<|endoftext|>