RubyLouvre/avalon

View on GitHub
src/directives/duplex/updateDataEvents.compact.js

Summary

Maintainability
C
1 day
Test Coverage
/* 
 * 通过绑定事件同步vmodel
 * 总共有三种方式同步视图
 * 1. 各种事件 input, change, click, propertychange, keydown...
 * 2. value属性重写
 * 3. 定时器轮询
 */

import { avalon, getShortID as markID, window, document, msie } from '../../seed/core'
import { updateModel } from './updateDataHandle'


export function updateDataEvents(dom, data) {
    var events = {}
        //添加需要监听的事件
    switch (data.dtype) {
        case 'radio':
        case 'checkbox':
            events.click = updateModel
            break
        case 'select':
            events.change = updateModel
            break
        case 'contenteditable':
            /* istanbul ignore if */
            if (data.isChanged) {
                events.blur = updateModel
                    /* istanbul ignore else */
            } else {
                /* istanbul ignore if*/

                if (avalon.modern) {
                    if (window.webkitURL) {
                        // http://code.metager.de/source/xref/WebKit/LayoutTests/fast/events/
                        // https://bugs.webkit.org/show_bug.cgi?id=110742
                        events.webkitEditableContentChanged = updateModel
                    } else if (window.MutationEvent) {
                        events.DOMCharacterDataModified = updateModel
                    }
                    events.input = updateModel
                        /* istanbul ignore else */
                } else {
                    events.keydown = updateModelKeyDown
                    events.paste = updateModelDelay
                    events.cut = updateModelDelay
                    events.focus = closeComposition
                    events.blur = openComposition
                }

            }
            break
        case 'input':
            /* istanbul ignore if */
            if (data.isChanged) {
                events.change = updateModel
                    /* istanbul ignore else */
            } else {
                //http://www.cnblogs.com/rubylouvre/archive/2013/02/17/2914604.html
                //http://www.matts411.com/post/internet-explorer-9-oninput/
                if (msie < 10) {
                    //IE6-8的propertychange有问题,第一次用JS修改值时不会触发,而且你是全部清空value也不会触发
                    //IE9的propertychange不支持自动完成,退格,删除,复制,贴粘,剪切或点击右边的小X的清空操作
                    events.propertychange = updateModelHack
                    events.paste = updateModelDelay
                    events.cut = updateModelDelay
                        //IE9在第一次删除字符时不会触发oninput
                    events.keyup = updateModelKeyDown
                } else {
                    events.input = updateModel
                    events.compositionstart = openComposition
                        //微软拼音输入法的问题需要在compositionend事件中处理
                    events.compositionend = closeComposition
                        //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
                        //处理低版本的标准浏览器,通过Int8Array进行区分
                    if (!/\[native code\]/.test(window.Int8Array)) {
                        events.keydown = updateModelKeyDown //safari < 5 opera < 11
                        events.paste = updateModelDelay //safari < 5
                        events.cut = updateModelDelay //safari < 5 
                        if (window.netscape) {
                            // Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete
                            events.DOMAutoComplete = updateModel
                        }
                    }
                }
            }
            break
    }

    if (/password|text/.test(dom.type)) {
        events.focus = openCaret //判定是否使用光标修正功能 
        events.blur = closeCaret
        data.getCaret = getCaret
        data.setCaret = setCaret
    }

    for (var name in events) {
        avalon.bind(dom, name, events[name])
    }
}


function updateModelHack(e) {
    if (e.propertyName === 'value') {
        updateModel.call(this, e)
    }
}

function updateModelDelay(e) {
    var elem = this
    setTimeout(function() {
        updateModel.call(elem, e)
    }, 0)
}


function openCaret() {
    this.caret = true
}
/* istanbul ignore next */
function closeCaret() {
    this.caret = false
}
/* istanbul ignore next */
function openComposition() {
    this.composing = true
}
/* istanbul ignore next */
function closeComposition(e) {
    this.composing = false
    updateModelDelay.call(this, e)
}
/* istanbul ignore next */
function updateModelKeyDown(e) {
    var key = e.keyCode
        // ignore
        //    command            modifiers                   arrows
    if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40))
        return
    updateModel.call(this, e)
}

markID(openCaret)
markID(closeCaret)
markID(openComposition)
markID(closeComposition)
markID(updateModel)
markID(updateModelHack)
markID(updateModelDelay)
markID(updateModelKeyDown)

//IE6-8要处理光标时需要异步
var mayBeAsync = function(fn) {
        setTimeout(fn, 0)
    }
    /* istanbul ignore next */
function setCaret(target, cursorPosition) {
    var range
    if (target.createTextRange) {
        mayBeAsync(function() {
            target.focus()
            range = target.createTextRange()
            range.collapse(true)
            range.moveEnd('character', cursorPosition)
            range.moveStart('character', cursorPosition)
            range.select()
        })
    } else {
        target.focus()
        if (target.selectionStart !== undefined) {
            target.setSelectionRange(cursorPosition, cursorPosition)
        }
    }
}
/* istanbul ignore next*/
function getCaret(target) {
    var start = 0
    var normalizedValue
    var range
    var textInputRange
    var len
    var endRange

    if (target.selectionStart + target.selectionEnd > -1) {
        start = target.selectionStart
    } else {
        range = document.selection.createRange()

        if (range && range.parentElement() === target) {
            len = target.value.length
            normalizedValue = target.value.replace(/\r\n/g, '\n')

            textInputRange = target.createTextRange()
            textInputRange.moveToBookmark(range.getBookmark())

            endRange = target.createTextRange()
            endRange.collapse(false)

            if (textInputRange.compareEndPoints('StartToEnd', endRange) > -1) {
                start = len
            } else {
                start = -textInputRange.moveStart('character', -len)
                start += normalizedValue.slice(0, start).split('\n').length - 1
            }
        }
    }

    return start
}