init
commit
90408348bc
|
@ -0,0 +1,17 @@
|
|||
|
||||
.vscode
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
package-lock.json
|
||||
|
||||
|
||||
._*
|
||||
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,2 @@
|
|||
## Anot.js 迷你mvvm框架
|
||||
> 基于**司徒正美**的`avalon2.x`版精简修改而来。
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "@bytedo/anot",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/@bytedo/anot.git"
|
||||
},
|
||||
"version": "2.2.10",
|
||||
"description": "an elegant efficient express mvvm framework",
|
||||
"main": "dist/anot.js",
|
||||
"keywords": ["javascript", "avalon", "mvvm", "virtual dom"],
|
||||
"scripts": {
|
||||
"start": "rollup -w -c ./rollup.config.dev.js",
|
||||
"build": "rollup -c ./rollup.config.prod.js"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@bytedo/rollup-plugin-uglify": "^1.0.0",
|
||||
"iofs": "^1.3.2",
|
||||
"rollup": "^2.23.1"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/RubyLouvre/avalon/issues"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// const uglify = require('@bytedo/rollup-plugin-uglify')
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
input: 'src/anot.js',
|
||||
output: {
|
||||
file: 'dist/anot.js',
|
||||
format: 'es',
|
||||
sourcemap: false
|
||||
},
|
||||
plugins: []
|
||||
},
|
||||
{
|
||||
input: 'src/anot.touch.js',
|
||||
output: {
|
||||
file: 'dist/anot.touch.js',
|
||||
format: 'es',
|
||||
sourcemap: false
|
||||
},
|
||||
plugins: []
|
||||
}
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
const uglify = require('@bytedo/rollup-plugin-uglify')
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
input: 'src/anot.js',
|
||||
output: {
|
||||
file: 'dist/anot.js',
|
||||
format: 'es',
|
||||
sourcemap: false
|
||||
},
|
||||
plugins: [uglify()]
|
||||
},
|
||||
{
|
||||
input: 'src/anot.touch.js',
|
||||
output: {
|
||||
file: 'dist/anot.touch.js',
|
||||
format: 'es',
|
||||
sourcemap: false
|
||||
},
|
||||
plugins: [uglify()]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
import { Anot } from './seed/core'
|
||||
import './seed/lang.modern'
|
||||
|
||||
import './filters/index'
|
||||
import './dom/modern'
|
||||
|
||||
import './vtree/fromString'
|
||||
import './vtree/fromDOM'
|
||||
|
||||
import './vdom/modern'
|
||||
|
||||
import './vmodel/modern'
|
||||
import './vmodel/proxy'
|
||||
|
||||
import './directives/modern'
|
||||
import './renders/domRender'
|
||||
|
||||
import './effect/index'
|
||||
|
||||
export default Anot
|
|
@ -0,0 +1,22 @@
|
|||
import { Anot } from './seed/core'
|
||||
import './seed/lang.modern'
|
||||
|
||||
import './filters/index'
|
||||
import './dom/modern'
|
||||
|
||||
import './vtree/fromString'
|
||||
import './vtree/fromDOM'
|
||||
|
||||
import './vdom/modern'
|
||||
|
||||
import './vmodel/modern'
|
||||
import './vmodel/proxy'
|
||||
|
||||
import './directives/modern'
|
||||
import './renders/domRender'
|
||||
|
||||
import './effect/index'
|
||||
|
||||
import './gesture/tap'
|
||||
|
||||
export default Anot
|
|
@ -0,0 +1,21 @@
|
|||
import { Anot } from '../seed/core'
|
||||
import { cssDiff } from './css'
|
||||
import { updateAttrs } from '../dom/attr/modern'
|
||||
|
||||
Anot.directive('attr', {
|
||||
diff: cssDiff,
|
||||
update: function(vdom, value) {
|
||||
var props = vdom.props
|
||||
for (var i in value) {
|
||||
if (!!value[i] === false) {
|
||||
delete props[i]
|
||||
} else {
|
||||
props[i] = value[i]
|
||||
}
|
||||
}
|
||||
var dom = vdom.dom
|
||||
if (dom && dom.nodeType === 1) {
|
||||
updateAttrs(dom, value)
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,121 @@
|
|||
//根据VM的属性值或表达式的值切换类名,ms-class='xxx yyy zzz:flag'
|
||||
//http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
|
||||
import { Anot, directives, getLongID as markID } from '../seed/core'
|
||||
|
||||
function classNames() {
|
||||
var classes = []
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var arg = arguments[i]
|
||||
var argType = typeof arg
|
||||
if (argType === 'string' || argType === 'number' || arg === true) {
|
||||
classes.push(arg)
|
||||
} else if (Array.isArray(arg)) {
|
||||
classes.push(classNames.apply(null, arg))
|
||||
} else if (argType === 'object') {
|
||||
for (var key in arg) {
|
||||
if (arg.hasOwnProperty(key) && arg[key]) {
|
||||
classes.push(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
}
|
||||
|
||||
Anot.directive('class', {
|
||||
diff: function(newVal, oldVal) {
|
||||
var type = this.type
|
||||
var vdom = this.node
|
||||
var classEvent = vdom.classEvent || {}
|
||||
if (type === 'hover') {
|
||||
//在移出移入时切换类名
|
||||
classEvent.mouseenter = activateClass
|
||||
classEvent.mouseleave = abandonClass
|
||||
} else if (type === 'active') {
|
||||
//在获得焦点时切换类名
|
||||
classEvent.tabIndex = vdom.props.tabindex || -1
|
||||
classEvent.mousedown = activateClass
|
||||
classEvent.mouseup = abandonClass
|
||||
classEvent.mouseleave = abandonClass
|
||||
}
|
||||
vdom.classEvent = classEvent
|
||||
|
||||
var className = classNames(newVal)
|
||||
|
||||
if (typeof oldVal === void 0 || oldVal !== className) {
|
||||
this.value = className
|
||||
|
||||
vdom['change-' + type] = className
|
||||
return true
|
||||
}
|
||||
},
|
||||
update: function(vdom, value) {
|
||||
var dom = vdom.dom
|
||||
if (dom && dom.nodeType == 1) {
|
||||
var dirType = this.type
|
||||
var change = 'change-' + dirType
|
||||
var classEvent = vdom.classEvent
|
||||
if (classEvent) {
|
||||
for (var i in classEvent) {
|
||||
if (i === 'tabIndex') {
|
||||
dom[i] = classEvent[i]
|
||||
} else {
|
||||
Anot.bind(dom, i, classEvent[i])
|
||||
}
|
||||
}
|
||||
vdom.classEvent = {}
|
||||
}
|
||||
var names = ['class', 'hover', 'active']
|
||||
names.forEach(function(type) {
|
||||
if (dirType !== type) return
|
||||
if (type === 'class') {
|
||||
dom && setClass(dom, value)
|
||||
} else {
|
||||
var oldClass = dom.getAttribute(change)
|
||||
if (oldClass) {
|
||||
Anot(dom).removeClass(oldClass)
|
||||
}
|
||||
var name = 'change-' + type
|
||||
dom.setAttribute(name, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
directives.active = directives.hover = directives['class']
|
||||
|
||||
var classMap = {
|
||||
mouseenter: 'change-hover',
|
||||
mouseleave: 'change-hover',
|
||||
mousedown: 'change-active',
|
||||
mouseup: 'change-active'
|
||||
}
|
||||
|
||||
function activateClass(e) {
|
||||
var elem = e.target
|
||||
Anot(elem).addClass(elem.getAttribute(classMap[e.type]) || '')
|
||||
}
|
||||
|
||||
function abandonClass(e) {
|
||||
var elem = e.target
|
||||
var name = classMap[e.type]
|
||||
Anot(elem).removeClass(elem.getAttribute(name) || '')
|
||||
if (name !== 'change-active') {
|
||||
Anot(elem).removeClass(elem.getAttribute('change-active') || '')
|
||||
}
|
||||
}
|
||||
|
||||
function setClass(dom, neo) {
|
||||
var old = dom.getAttribute('change-class')
|
||||
if (old !== neo) {
|
||||
Anot(dom)
|
||||
.removeClass(old)
|
||||
.addClass(neo)
|
||||
dom.setAttribute('change-class', neo)
|
||||
}
|
||||
}
|
||||
|
||||
markID(activateClass)
|
||||
markID(abandonClass)
|
|
@ -0,0 +1,17 @@
|
|||
import { Anot, platform } from '../seed/core'
|
||||
import { impCb } from './important'
|
||||
Anot.directive('controller', {
|
||||
priority: 2,
|
||||
getScope: function(name, scope) {
|
||||
var v = Anot.vmodels[name]
|
||||
if (v) {
|
||||
v.$render = this
|
||||
if (scope && scope !== v) {
|
||||
return platform.fuseFactory(scope, v)
|
||||
}
|
||||
return v
|
||||
}
|
||||
return scope
|
||||
},
|
||||
update: impCb
|
||||
})
|
|
@ -0,0 +1,126 @@
|
|||
import { Anot, platform } from '../seed/core'
|
||||
var arrayWarn = {}
|
||||
var cssDir = Anot.directive('css', {
|
||||
diff: function(newVal, oldVal) {
|
||||
if (Object(newVal) === newVal) {
|
||||
newVal = platform.toJson(newVal) //安全的遍历VBscript
|
||||
if (Array.isArray(newVal)) {
|
||||
//转换成对象
|
||||
var b = {}
|
||||
newVal.forEach(function(el) {
|
||||
el && Anot.shadowCopy(b, el)
|
||||
})
|
||||
newVal = b
|
||||
if (!arrayWarn[this.type]) {
|
||||
Anot.warn('ms-' + this.type + '指令的值不建议使用数组形式了!')
|
||||
arrayWarn[this.type] = 1
|
||||
}
|
||||
}
|
||||
|
||||
var hasChange = false
|
||||
var patch = {}
|
||||
if (!oldVal) {
|
||||
//如果一开始为空
|
||||
patch = newVal
|
||||
hasChange = true
|
||||
} else {
|
||||
if (this.deep) {
|
||||
var deep = typeof this.deep === 'number' ? this.deep : 6
|
||||
for (let i in newVal) {
|
||||
//diff差异点
|
||||
if (!deepEquals(newVal[i], oldVal[i], 4)) {
|
||||
this.value = newVal
|
||||
return true
|
||||
}
|
||||
patch[i] = newVal[i]
|
||||
}
|
||||
} else {
|
||||
for (let i in newVal) {
|
||||
//diff差异点
|
||||
if (newVal[i] !== oldVal[i]) {
|
||||
hasChange = true
|
||||
}
|
||||
patch[i] = newVal[i]
|
||||
}
|
||||
}
|
||||
|
||||
for (let i in oldVal) {
|
||||
if (!(i in patch)) {
|
||||
hasChange = true
|
||||
patch[i] = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasChange) {
|
||||
this.value = patch
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
update: function(vdom, value) {
|
||||
var dom = vdom.dom
|
||||
if (dom && dom.nodeType === 1) {
|
||||
var wrap = Anot(dom)
|
||||
for (var name in value) {
|
||||
wrap.css(name, value[name])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export var cssDiff = cssDir.diff
|
||||
|
||||
export function getEnumerableKeys(obj) {
|
||||
const res = []
|
||||
for (let key in obj) res.push(key)
|
||||
return res
|
||||
}
|
||||
|
||||
export function deepEquals(a, b, level) {
|
||||
if (level === 0) return a === b
|
||||
if (a === null && b === null) return true
|
||||
if (a === undefined && b === undefined) return true
|
||||
const aIsArray = Array.isArray(a)
|
||||
if (aIsArray !== Array.isArray(b)) {
|
||||
return false
|
||||
}
|
||||
if (aIsArray) {
|
||||
return equalArray(a, b, level)
|
||||
} else if (typeof a === 'object' && typeof b === 'object') {
|
||||
return equalObject(a, b, level)
|
||||
}
|
||||
return a === b
|
||||
}
|
||||
|
||||
function equalArray(a, b, level) {
|
||||
if (a.length !== b.length) {
|
||||
return false
|
||||
}
|
||||
for (let i = a.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
if (!deepEquals(a[i], b[i], level - 1)) {
|
||||
return false
|
||||
}
|
||||
} catch (noThisPropError) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function equalObject(a, b, level) {
|
||||
if (a === null || b === null) return false
|
||||
if (getEnumerableKeys(a).length !== getEnumerableKeys(b).length) return false
|
||||
for (let prop in a) {
|
||||
if (!(prop in b)) return false
|
||||
try {
|
||||
if (!deepEquals(a[prop], b[prop], level - 1)) {
|
||||
return false
|
||||
}
|
||||
} catch (noThisPropError) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { Anot } from '../../seed/core'
|
||||
import {
|
||||
duplexBeforeInit,
|
||||
duplexInit,
|
||||
duplexDiff,
|
||||
duplexBind,
|
||||
valueHijack,
|
||||
updateView
|
||||
} from './share'
|
||||
import { updateDataEvents } from './updateDataEvents.modern'
|
||||
|
||||
Anot.directive('duplex', {
|
||||
priority: 2000,
|
||||
beforeInit: duplexBeforeInit,
|
||||
init: duplexInit,
|
||||
diff: duplexDiff,
|
||||
update: function(vdom, value) {
|
||||
if (!this.dom) {
|
||||
duplexBind.call(this, vdom, updateDataEvents)
|
||||
}
|
||||
updateView[this.dtype].call(this)
|
||||
}
|
||||
})
|
|
@ -0,0 +1,55 @@
|
|||
export function lookupOption(vdom, values) {
|
||||
vdom.children && vdom.children.forEach(function (el) {
|
||||
if (el.nodeName === 'option') {
|
||||
setOption(el, values)
|
||||
} else {
|
||||
lookupOption(el, values)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setOption(vdom, values) {
|
||||
var props = vdom.props
|
||||
if (!('disabled' in props)) {
|
||||
var value = getOptionValue(vdom, props)
|
||||
value = String(value || '').trim()
|
||||
if(typeof values === 'string'){
|
||||
props.selected = value === values
|
||||
}else{
|
||||
props.selected = values.indexOf(value) !== -1;
|
||||
}
|
||||
|
||||
if (vdom.dom) {
|
||||
vdom.dom.selected = props.selected
|
||||
var v = vdom.dom.selected //必须加上这个,防止移出节点selected失效
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function getOptionValue(vdom, props) {
|
||||
if (props && 'value' in props) {
|
||||
return props.value+ ''
|
||||
}
|
||||
var arr = []
|
||||
vdom.children.forEach(function (el) {
|
||||
if (el.nodeName === '#text') {
|
||||
arr.push(el.nodeValue)
|
||||
} else if (el.nodeName === '#document-fragment') {
|
||||
arr.push(getOptionValue(el))
|
||||
}
|
||||
})
|
||||
return arr.join('')
|
||||
}
|
||||
|
||||
export function getSelectedValue(vdom, arr) {
|
||||
vdom.children.forEach(function (el) {
|
||||
if (el.nodeName === 'option') {
|
||||
if(el.props.selected === true)
|
||||
arr.push(getOptionValue(el, el.props))
|
||||
} else if (el.children) {
|
||||
getSelectedValue(el,arr)
|
||||
}
|
||||
})
|
||||
return arr
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
import { Anot, createFragment } from '../../seed/core'
|
||||
import { rcheckedType } from '../../dom/rcheckedType'
|
||||
import { lookupOption } from './option'
|
||||
import { addScope, makeHandle } from '../../parser/index'
|
||||
import { fromString } from '../../vtree/fromString'
|
||||
import { updateModel } from './updateDataHandle'
|
||||
|
||||
var rchangeFilter = /\|\s*change\b/
|
||||
var rdebounceFilter = /\|\s*debounce(?:\(([^)]+)\))?/
|
||||
export function duplexBeforeInit() {
|
||||
var expr = this.expr
|
||||
if (rchangeFilter.test(expr)) {
|
||||
this.isChanged = true
|
||||
expr = expr.replace(rchangeFilter, '')
|
||||
}
|
||||
var match = expr.match(rdebounceFilter)
|
||||
if (match) {
|
||||
expr = expr.replace(rdebounceFilter, '')
|
||||
if (!this.isChanged) {
|
||||
this.debounceTime = parseInt(match[1], 10) || 300
|
||||
}
|
||||
}
|
||||
this.expr = expr
|
||||
}
|
||||
export function duplexInit() {
|
||||
var expr = this.expr
|
||||
var node = this.node
|
||||
var etype = node.props.type
|
||||
this.parseValue = parseValue
|
||||
//处理数据转换器
|
||||
var parsers = this.param,
|
||||
dtype
|
||||
var isChecked = false
|
||||
parsers = parsers
|
||||
? parsers.split('-').map(function(a) {
|
||||
if (a === 'checked') {
|
||||
isChecked = true
|
||||
}
|
||||
return a
|
||||
})
|
||||
: []
|
||||
node.duplex = this
|
||||
if (rcheckedType.test(etype) && isChecked) {
|
||||
//如果是radio, checkbox,判定用户使用了checked格式函数没有
|
||||
parsers = []
|
||||
dtype = 'radio'
|
||||
this.isChecked = isChecked
|
||||
}
|
||||
this.parsers = parsers
|
||||
if (!/input|textarea|select/.test(node.nodeName)) {
|
||||
if ('contenteditable' in node.props) {
|
||||
dtype = 'contenteditable'
|
||||
}
|
||||
} else if (!dtype) {
|
||||
dtype =
|
||||
node.nodeName === 'select'
|
||||
? 'select'
|
||||
: etype === 'checkbox'
|
||||
? 'checkbox'
|
||||
: etype === 'radio'
|
||||
? 'radio'
|
||||
: 'input'
|
||||
}
|
||||
this.dtype = dtype
|
||||
|
||||
//判定是否使用了 change debounce 过滤器
|
||||
// this.isChecked = /boolean/.test(parsers)
|
||||
if (dtype !== 'input' && dtype !== 'contenteditable') {
|
||||
delete this.isChanged
|
||||
delete this.debounceTime
|
||||
} else if (!this.isChecked) {
|
||||
this.isString = true
|
||||
}
|
||||
|
||||
var cb = node.props['data-duplex-changed']
|
||||
if (cb) {
|
||||
var arr = addScope(cb, 'xx')
|
||||
var body = makeHandle(arr[0])
|
||||
this.userCb = new Function(
|
||||
'$event',
|
||||
'var __vmodel__ = this\nreturn ' + body
|
||||
)
|
||||
}
|
||||
}
|
||||
export function duplexDiff(newVal, oldVal) {
|
||||
if (Array.isArray(newVal)) {
|
||||
if (newVal + '' !== this.compareVal) {
|
||||
this.compareVal = newVal + ''
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
newVal = this.parseValue(newVal)
|
||||
if (!this.isChecked) {
|
||||
this.value = newVal += ''
|
||||
}
|
||||
if (newVal !== this.compareVal) {
|
||||
this.compareVal = newVal
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function duplexBind(vdom, addEvent) {
|
||||
var dom = vdom.dom
|
||||
this.dom = dom
|
||||
this.vdom = vdom
|
||||
this.duplexCb = updateModel
|
||||
dom._ms_duplex_ = this
|
||||
//绑定事件
|
||||
addEvent(dom, this)
|
||||
}
|
||||
|
||||
export var valueHijack = true
|
||||
try {
|
||||
//#272 IE9-IE11, firefox
|
||||
var setters = {}
|
||||
var aproto = HTMLInputElement.prototype
|
||||
var bproto = HTMLTextAreaElement.prototype
|
||||
var newSetter = function(value) {
|
||||
// jshint ignore:line
|
||||
setters[this.tagName].call(this, value)
|
||||
var data = this._ms_duplex_
|
||||
if (!this.caret && data && data.isString) {
|
||||
data.duplexCb.call(this, { type: 'setter' })
|
||||
}
|
||||
}
|
||||
var inputProto = HTMLInputElement.prototype
|
||||
Object.getOwnPropertyNames(inputProto) //故意引发IE6-8等浏览器报错
|
||||
setters['INPUT'] = Object.getOwnPropertyDescriptor(aproto, 'value').set
|
||||
|
||||
Object.defineProperty(aproto, 'value', {
|
||||
set: newSetter
|
||||
})
|
||||
setters['TEXTAREA'] = Object.getOwnPropertyDescriptor(bproto, 'value').set
|
||||
Object.defineProperty(bproto, 'value', {
|
||||
set: newSetter
|
||||
})
|
||||
valueHijack = false
|
||||
} catch (e) {
|
||||
//在chrome 43中 ms-duplex终于不需要使用定时器实现双向绑定了
|
||||
// http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype
|
||||
// https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1
|
||||
}
|
||||
|
||||
function parseValue(val) {
|
||||
for (var i = 0, k; (k = this.parsers[i++]); ) {
|
||||
var fn = Anot.parsers[k]
|
||||
if (fn) {
|
||||
val = fn.call(this, val)
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
export var updateView = {
|
||||
input: function() {
|
||||
//处理单个value值处理
|
||||
var vdom = this.node
|
||||
var value = this.value + ''
|
||||
vdom.dom.value = vdom.props.value = value
|
||||
},
|
||||
updateChecked: function(vdom, checked) {
|
||||
if (vdom.dom) {
|
||||
vdom.dom.defaultChecked = vdom.dom.checked = checked
|
||||
}
|
||||
},
|
||||
radio: function() {
|
||||
//处理单个checked属性
|
||||
var node = this.node
|
||||
var nodeValue = node.props.value
|
||||
var checked
|
||||
if (this.isChecked) {
|
||||
checked = !!this.value
|
||||
} else {
|
||||
checked = this.value + '' === nodeValue
|
||||
}
|
||||
node.props.checked = checked
|
||||
updateView.updateChecked(node, checked)
|
||||
},
|
||||
checkbox: function() {
|
||||
//处理多个checked属性
|
||||
var node = this.node
|
||||
var props = node.props
|
||||
var value = props.value + ''
|
||||
var values = [].concat(this.value)
|
||||
var checked = values.some(function(el) {
|
||||
return el + '' === value
|
||||
})
|
||||
|
||||
props.defaultChecked = props.checked = checked
|
||||
updateView.updateChecked(node, checked)
|
||||
},
|
||||
select: function() {
|
||||
//处理子级的selected属性
|
||||
var a = Array.isArray(this.value) ? this.value.map(String) : this.value + ''
|
||||
lookupOption(this.node, a)
|
||||
},
|
||||
contenteditable: function() {
|
||||
//处理单个innerHTML
|
||||
|
||||
var vnodes = fromString(this.value)
|
||||
var fragment = createFragment()
|
||||
for (var i = 0, el; (el = vnodes[i++]); ) {
|
||||
var child = Anot.vdom(el, 'toDOM')
|
||||
fragment.appendChild(child)
|
||||
}
|
||||
Anot.clearHTML(this.dom).appendChild(fragment)
|
||||
var list = this.node.children
|
||||
list.length = 0
|
||||
Array.prototype.push.apply(list, vnodes)
|
||||
|
||||
this.duplexCb.call(this.dom)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import { Anot } from '../../seed/core'
|
||||
|
||||
export var updateDataActions = {
|
||||
input: function(prop) {
|
||||
//处理单个value值处理
|
||||
var field = this
|
||||
prop = prop || 'value'
|
||||
var dom = field.dom
|
||||
var rawValue = dom[prop]
|
||||
var parsedValue = field.parseValue(rawValue)
|
||||
|
||||
//有时候parse后一致,vm不会改变,但input里面的值
|
||||
field.value = rawValue
|
||||
field.setValue(parsedValue)
|
||||
duplexCb(field)
|
||||
var pos = field.pos
|
||||
/* istanbul ignore if */
|
||||
if (dom.caret) {
|
||||
field.setCaret(dom, pos)
|
||||
}
|
||||
//vm.aaa = '1234567890'
|
||||
//处理 <input ms-duplex='@aaa|limitBy(8)'/>{{@aaa}} 这种格式化同步不一致的情况
|
||||
},
|
||||
radio: function() {
|
||||
var field = this
|
||||
if (field.isChecked) {
|
||||
var val = !field.value
|
||||
field.setValue(val)
|
||||
duplexCb(field)
|
||||
} else {
|
||||
updateDataActions.input.call(field)
|
||||
field.value = NaN
|
||||
}
|
||||
},
|
||||
checkbox: function() {
|
||||
var field = this
|
||||
var array = field.value
|
||||
if (!Array.isArray(array)) {
|
||||
Anot.warn('ms-duplex应用于checkbox上要对应一个数组')
|
||||
array = [array]
|
||||
}
|
||||
var method = field.dom.checked ? 'ensure' : 'remove'
|
||||
if (array[method]) {
|
||||
var val = field.parseValue(field.dom.value)
|
||||
array[method](val)
|
||||
duplexCb(field)
|
||||
}
|
||||
this.__test__ = array
|
||||
},
|
||||
select: function() {
|
||||
var field = this
|
||||
var val = Anot(field.dom).val() //字符串或字符串数组
|
||||
if (val + '' !== this.value + '') {
|
||||
if (Array.isArray(val)) {
|
||||
//转换布尔数组或其他
|
||||
val = val.map(function(v) {
|
||||
return field.parseValue(v)
|
||||
})
|
||||
} else {
|
||||
val = field.parseValue(val)
|
||||
}
|
||||
field.setValue(val)
|
||||
duplexCb(field)
|
||||
}
|
||||
},
|
||||
contenteditable: function() {
|
||||
updateDataActions.input.call(this, 'innerHTML')
|
||||
}
|
||||
}
|
||||
|
||||
function duplexCb(field) {
|
||||
if (field.userCb) {
|
||||
field.userCb.call(field.vm, {
|
||||
type: 'changed',
|
||||
target: field.dom
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 通过绑定事件同步vmodel
|
||||
* 总共有三种方式同步视图
|
||||
* 1. 各种事件 input, change, click, propertychange, keydown...
|
||||
* 2. value属性重写
|
||||
* 3. 定时器轮询
|
||||
*/
|
||||
import {
|
||||
Anot,
|
||||
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':
|
||||
if (data.isChanged) {
|
||||
events.blur = updateModel
|
||||
} else {
|
||||
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
|
||||
}
|
||||
break
|
||||
case 'input':
|
||||
if (data.isChanged) {
|
||||
events.change = updateModel
|
||||
} else {
|
||||
events.input = updateModel
|
||||
|
||||
//https://github.com/RubyLouvre/Anot/issues/1368#issuecomment-220503284
|
||||
events.compositionstart = openComposition
|
||||
events.compositionend = closeComposition
|
||||
if (Anot.msie) {
|
||||
events.keyup = updateModelKeyDown
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (/password|text/.test(dom.type)) {
|
||||
events.focus = openCaret //判定是否使用光标修正功能
|
||||
events.blur = closeCaret
|
||||
data.getCaret = getCaret
|
||||
data.setCaret = setCaret
|
||||
}
|
||||
for (var name in events) {
|
||||
Anot.bind(dom, name, events[name])
|
||||
}
|
||||
}
|
||||
|
||||
/* 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)
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
function openCaret() {
|
||||
this.caret = true
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
function closeCaret() {
|
||||
this.caret = false
|
||||
}
|
||||
function openComposition() {
|
||||
this.composing = true
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
function closeComposition(e) {
|
||||
this.composing = false
|
||||
var elem = this
|
||||
setTimeout(function() {
|
||||
updateModel.call(elem, e)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
markID(openCaret)
|
||||
markID(closeCaret)
|
||||
markID(openComposition)
|
||||
markID(closeComposition)
|
||||
markID(updateModelKeyDown)
|
||||
markID(updateModel)
|
||||
|
||||
/* istanbul ignore next */
|
||||
function getCaret(field) {
|
||||
var start = NaN
|
||||
if (field.setSelectionRange) {
|
||||
start = field.selectionStart
|
||||
}
|
||||
return start
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
function setCaret(field, pos) {
|
||||
if (!field.value || field.readOnly) return
|
||||
field.selectionStart = pos
|
||||
field.selectionEnd = pos
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { updateDataActions } from './updateDataActions'
|
||||
|
||||
export function updateDataHandle(event) {
|
||||
var elem = this
|
||||
var field = elem._ms_duplex_
|
||||
if (elem.composing) {
|
||||
//防止onpropertychange引发爆栈
|
||||
return
|
||||
}
|
||||
if (elem.value === field.value) {
|
||||
return
|
||||
}
|
||||
/* istanbul ignore if*/
|
||||
if (elem.caret) {
|
||||
try {
|
||||
var pos = field.getCaret(elem)
|
||||
field.pos = pos
|
||||
} catch (e) {}
|
||||
}
|
||||
/* istanbul ignore if*/
|
||||
if (field.debounceTime > 4) {
|
||||
var timestamp = new Date()
|
||||
var left = timestamp - field.time || 0
|
||||
field.time = timestamp
|
||||
/* istanbul ignore if*/
|
||||
if (left >= field.debounceTime) {
|
||||
updateDataActions[field.dtype].call(field)
|
||||
/* istanbul ignore else*/
|
||||
} else {
|
||||
clearTimeout(field.debounceID)
|
||||
field.debounceID = setTimeout(function() {
|
||||
updateDataActions[field.dtype].call(field)
|
||||
}, left)
|
||||
}
|
||||
} else if (field.isChanged) {
|
||||
setTimeout(function() {
|
||||
//https://github.com/RubyLouvre/Anot/issues/1908
|
||||
updateDataActions[field.dtype].call(field)
|
||||
}, 4)
|
||||
} else {
|
||||
updateDataActions[field.dtype].call(field)
|
||||
}
|
||||
}
|
||||
|
||||
export { updateDataHandle as updateModel }
|
|
@ -0,0 +1,10 @@
|
|||
import { Anot } from '../seed/core'
|
||||
|
||||
Anot.directive('expr', {
|
||||
update: function(vdom, value) {
|
||||
value = value == null || value === '' ? '\u200b' : value
|
||||
vdom.nodeValue = value
|
||||
//https://github.com/RubyLouvre/Anot/issues/1834
|
||||
if (vdom.dom) vdom.dom.data = value
|
||||
}
|
||||
})
|
|
@ -0,0 +1,347 @@
|
|||
import { Anot, createFragment, platform, isObject, ap } from '../seed/core'
|
||||
|
||||
import { VFragment } from '../vdom/VFragment'
|
||||
import { $$skipArray } from '../vmodel/reserved'
|
||||
|
||||
import { addScope, makeHandle } from '../parser/index'
|
||||
import { updateView } from './duplex/share'
|
||||
|
||||
var rforAs = /\s+as\s+([$\w]+)/
|
||||
var rident = /^[$a-zA-Z_][$a-zA-Z0-9_]*$/
|
||||
var rinvalid = /^(null|undefined|NaN|window|this|\$index|\$id)$/
|
||||
var rargs = /[$\w_]+/g
|
||||
Anot.directive('for', {
|
||||
delay: true,
|
||||
priority: 3,
|
||||
beforeInit: function() {
|
||||
var str = this.expr,
|
||||
asName
|
||||
str = str.replace(rforAs, function(a, b) {
|
||||
/* istanbul ignore if */
|
||||
if (!rident.test(b) || rinvalid.test(b)) {
|
||||
Anot.error(
|
||||
'alias ' +
|
||||
b +
|
||||
' is invalid --- must be a valid JS identifier which is not a reserved name.'
|
||||
)
|
||||
} else {
|
||||
asName = b
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
var arr = str.split(' in ')
|
||||
var kv = arr[0].match(rargs)
|
||||
if (kv.length === 1) {
|
||||
//确保Anot._each的回调有三个参数
|
||||
kv.unshift('$key')
|
||||
}
|
||||
this.expr = arr[1]
|
||||
this.keyName = kv[0]
|
||||
this.valName = kv[1]
|
||||
this.signature = Anot.makeHashCode('for')
|
||||
if (asName) {
|
||||
this.asName = asName
|
||||
}
|
||||
|
||||
delete this.param
|
||||
},
|
||||
init: function() {
|
||||
var cb = this.userCb
|
||||
if (typeof cb === 'string' && cb) {
|
||||
var arr = addScope(cb, 'for')
|
||||
var body = makeHandle(arr[0])
|
||||
this.userCb = new Function(
|
||||
'$event',
|
||||
'var __vmodel__ = this\nreturn ' + body
|
||||
)
|
||||
}
|
||||
this.node.forDir = this //暴露给component/index.js中的resetParentChildren方法使用
|
||||
this.fragment = [
|
||||
'<div>',
|
||||
this.fragment,
|
||||
'<!--',
|
||||
this.signature,
|
||||
'--></div>'
|
||||
].join('')
|
||||
this.cache = {}
|
||||
},
|
||||
diff: function(newVal, oldVal) {
|
||||
/* istanbul ignore if */
|
||||
if (this.updating) {
|
||||
return
|
||||
}
|
||||
this.updating = true
|
||||
var traceIds = createFragments(this, newVal)
|
||||
|
||||
if (this.oldTrackIds === void 0) return true
|
||||
|
||||
if (this.oldTrackIds !== traceIds) {
|
||||
this.oldTrackIds = traceIds
|
||||
return true
|
||||
}
|
||||
},
|
||||
update: function() {
|
||||
if (!this.preFragments) {
|
||||
this.fragments = this.fragments || []
|
||||
mountList(this)
|
||||
} else {
|
||||
diffList(this)
|
||||
updateList(this)
|
||||
}
|
||||
|
||||
if (this.userCb) {
|
||||
var me = this
|
||||
setTimeout(function() {
|
||||
me.userCb.call(me.vm, {
|
||||
type: 'rendered',
|
||||
target: me.begin.dom,
|
||||
signature: me.signature
|
||||
})
|
||||
}, 0)
|
||||
}
|
||||
delete this.updating
|
||||
},
|
||||
beforeDispose: function() {
|
||||
this.fragments.forEach(function(el) {
|
||||
el.dispose()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function getTraceKey(item) {
|
||||
var type = typeof item
|
||||
return item && type === 'object' ? item.$hashcode : type + ':' + item
|
||||
}
|
||||
|
||||
//创建一组fragment的虚拟DOM
|
||||
function createFragments(instance, obj) {
|
||||
if (isObject(obj)) {
|
||||
var array = Array.isArray(obj)
|
||||
var ids = []
|
||||
var fragments = [],
|
||||
i = 0
|
||||
|
||||
instance.isArray = array
|
||||
if (instance.fragments) {
|
||||
instance.preFragments = instance.fragments
|
||||
Anot.each(obj, function(key, value) {
|
||||
var k = array ? getTraceKey(value) : key
|
||||
|
||||
fragments.push({
|
||||
key: k,
|
||||
val: value,
|
||||
index: i++
|
||||
})
|
||||
ids.push(k)
|
||||
})
|
||||
instance.fragments = fragments
|
||||
} else {
|
||||
Anot.each(obj, function(key, value) {
|
||||
if (!(key in $$skipArray)) {
|
||||
var k = array ? getTraceKey(value) : key
|
||||
fragments.push(new VFragment([], k, value, i++))
|
||||
ids.push(k)
|
||||
}
|
||||
})
|
||||
instance.fragments = fragments
|
||||
}
|
||||
return ids.join(';;')
|
||||
} else {
|
||||
return NaN
|
||||
}
|
||||
}
|
||||
|
||||
function mountList(instance) {
|
||||
var args = instance.fragments.map(function(fragment, index) {
|
||||
FragmentDecorator(fragment, instance, index)
|
||||
saveInCache(instance.cache, fragment)
|
||||
return fragment
|
||||
})
|
||||
var list = instance.parentChildren
|
||||
var i = list.indexOf(instance.begin)
|
||||
list.splice.apply(list, [i + 1, 0].concat(args))
|
||||
}
|
||||
|
||||
function diffList(instance) {
|
||||
var cache = instance.cache
|
||||
var newCache = {}
|
||||
var fuzzy = []
|
||||
var list = instance.preFragments
|
||||
|
||||
list.forEach(function(el) {
|
||||
el._dispose = true
|
||||
})
|
||||
|
||||
instance.fragments.forEach(function(c, index) {
|
||||
var fragment = isInCache(cache, c.key)
|
||||
//取出之前的文档碎片
|
||||
if (fragment) {
|
||||
delete fragment._dispose
|
||||
fragment.oldIndex = fragment.index
|
||||
fragment.index = index // 相当于 c.index
|
||||
|
||||
resetVM(fragment.vm, instance.keyName)
|
||||
fragment.vm[instance.valName] = c.val
|
||||
fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key
|
||||
saveInCache(newCache, fragment)
|
||||
} else {
|
||||
//如果找不到就进行模糊搜索
|
||||
fuzzy.push(c)
|
||||
}
|
||||
})
|
||||
fuzzy.forEach(function(c) {
|
||||
var fragment = fuzzyMatchCache(cache, c.key)
|
||||
if (fragment) {
|
||||
//重复利用
|
||||
fragment.oldIndex = fragment.index
|
||||
fragment.key = c.key
|
||||
var val = (fragment.val = c.val)
|
||||
var index = (fragment.index = c.index)
|
||||
|
||||
fragment.vm[instance.valName] = val
|
||||
fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key
|
||||
delete fragment._dispose
|
||||
} else {
|
||||
c = new VFragment([], c.key, c.val, c.index)
|
||||
fragment = FragmentDecorator(c, instance, c.index)
|
||||
list.push(fragment)
|
||||
}
|
||||
saveInCache(newCache, fragment)
|
||||
})
|
||||
|
||||
instance.fragments = list
|
||||
list.sort(function(a, b) {
|
||||
return a.index - b.index
|
||||
})
|
||||
instance.cache = newCache
|
||||
}
|
||||
|
||||
function updateItemVm(vm, top) {
|
||||
for (var i in top) {
|
||||
if (top.hasOwnProperty(i)) {
|
||||
vm[i] = top[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetVM(vm, a, b) {
|
||||
if (Anot.config.inProxyMode) {
|
||||
vm.$accessors[a].value = NaN
|
||||
} else {
|
||||
vm.$accessors[a].set(NaN)
|
||||
}
|
||||
}
|
||||
|
||||
function updateList(instance) {
|
||||
var before = instance.begin.dom
|
||||
var parent = before.parentNode
|
||||
var list = instance.fragments
|
||||
var end = instance.end.dom
|
||||
|
||||
for (var i = 0, item; (item = list[i]); i++) {
|
||||
if (item._dispose) {
|
||||
list.splice(i, 1)
|
||||
i--
|
||||
item.dispose()
|
||||
continue
|
||||
}
|
||||
if (item.oldIndex !== item.index) {
|
||||
var f = item.toFragment()
|
||||
var isEnd = before.nextSibling === null
|
||||
parent.insertBefore(f, before.nextSibling)
|
||||
if (isEnd && !parent.contains(end)) {
|
||||
parent.insertBefore(end, before.nextSibling)
|
||||
}
|
||||
}
|
||||
before = item.split
|
||||
}
|
||||
var ch = instance.parentChildren
|
||||
var startIndex = ch.indexOf(instance.begin)
|
||||
var endIndex = ch.indexOf(instance.end)
|
||||
|
||||
list.splice.apply(ch, [startIndex + 1, endIndex - startIndex].concat(list))
|
||||
if (parent.nodeName === 'SELECT' && parent._ms_duplex_) {
|
||||
updateView['select'].call(parent._ms_duplex_)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {type} fragment
|
||||
* @param {type} this
|
||||
* @param {type} index
|
||||
* @returns { key, val, index, oldIndex, this, dom, split, vm}
|
||||
*/
|
||||
function FragmentDecorator(fragment, instance, index) {
|
||||
var data = {}
|
||||
data[instance.keyName] = instance.isArray ? index : fragment.key
|
||||
data[instance.valName] = fragment.val
|
||||
if (instance.asName) {
|
||||
data[instance.asName] = instance.value
|
||||
}
|
||||
var vm = (fragment.vm = platform.itemFactory(instance.vm, {
|
||||
data: data
|
||||
}))
|
||||
if (instance.isArray) {
|
||||
vm.$watch(instance.valName, function(a) {
|
||||
if (instance.value && instance.value.set) {
|
||||
instance.value.set(vm[instance.keyName], a)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
vm.$watch(instance.valName, function(a) {
|
||||
instance.value[fragment.key] = a
|
||||
})
|
||||
}
|
||||
|
||||
fragment.index = index
|
||||
fragment.innerRender = Anot.scan(instance.fragment, vm, function() {
|
||||
var oldRoot = this.root
|
||||
ap.push.apply(fragment.children, oldRoot.children)
|
||||
this.root = fragment
|
||||
})
|
||||
return fragment
|
||||
}
|
||||
// 新位置: 旧位置
|
||||
function isInCache(cache, id) {
|
||||
var c = cache[id]
|
||||
if (c) {
|
||||
var arr = c.arr
|
||||
/* istanbul ignore if*/
|
||||
if (arr) {
|
||||
var r = arr.pop()
|
||||
if (!arr.length) {
|
||||
c.arr = 0
|
||||
}
|
||||
return r
|
||||
}
|
||||
delete cache[id]
|
||||
return c
|
||||
}
|
||||
}
|
||||
//[1,1,1] number1 number1_ number1__
|
||||
function saveInCache(cache, component) {
|
||||
var trackId = component.key
|
||||
if (!cache[trackId]) {
|
||||
cache[trackId] = component
|
||||
} else {
|
||||
var c = cache[trackId]
|
||||
var arr = c.arr || (c.arr = [])
|
||||
arr.push(component)
|
||||
}
|
||||
}
|
||||
|
||||
var rfuzzy = /^(string|number|boolean)/
|
||||
var rkfuzzy = /^_*(string|number|boolean)/
|
||||
|
||||
function fuzzyMatchCache(cache) {
|
||||
var key
|
||||
for (var id in cache) {
|
||||
var key = id
|
||||
break
|
||||
}
|
||||
if (key) {
|
||||
return isInCache(cache, key)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { Anot } from '../seed/core'
|
||||
|
||||
Anot.directive('html', {
|
||||
update: function(vdom, value) {
|
||||
this.beforeDispose()
|
||||
|
||||
this.innerRender = Anot.scan(
|
||||
'<div class="ms-html-container">' + value + '</div>',
|
||||
this.vm,
|
||||
function() {
|
||||
var oldRoot = this.root
|
||||
if (vdom.children) vdom.children.length = 0
|
||||
vdom.children = oldRoot.children
|
||||
this.root = vdom
|
||||
if (vdom.dom) Anot.clearHTML(vdom.dom)
|
||||
}
|
||||
)
|
||||
},
|
||||
beforeDispose: function() {
|
||||
if (this.innerRender) {
|
||||
this.innerRender.dispose()
|
||||
}
|
||||
},
|
||||
delay: true
|
||||
})
|
|
@ -0,0 +1,60 @@
|
|||
import { Anot, createAnchor } from '../seed/core'
|
||||
|
||||
Anot.directive('if', {
|
||||
delay: true,
|
||||
priority: 5,
|
||||
init: function() {
|
||||
this.placeholder = createAnchor('if')
|
||||
var props = this.node.props
|
||||
delete props['ms-if']
|
||||
delete props[':if']
|
||||
this.fragment = Anot.vdom(this.node, 'toHTML')
|
||||
},
|
||||
diff: function(newVal, oldVal) {
|
||||
var n = !!newVal
|
||||
if (oldVal === void 0 || n !== oldVal) {
|
||||
this.value = n
|
||||
return true
|
||||
}
|
||||
},
|
||||
update: function(vdom, value) {
|
||||
if (this.isShow === void 0 && value) {
|
||||
continueScan(this, vdom)
|
||||
return
|
||||
}
|
||||
this.isShow = value
|
||||
var placeholder = this.placeholder
|
||||
|
||||
if (value) {
|
||||
var p = placeholder.parentNode
|
||||
continueScan(this, vdom)
|
||||
p && p.replaceChild(vdom.dom, placeholder)
|
||||
} else {
|
||||
//移除DOM
|
||||
this.beforeDispose()
|
||||
vdom.nodeValue = 'if'
|
||||
vdom.nodeName = '#comment'
|
||||
delete vdom.children
|
||||
var dom = vdom.dom
|
||||
var p = dom && dom.parentNode
|
||||
vdom.dom = placeholder
|
||||
if (p) {
|
||||
p.replaceChild(placeholder, dom)
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDispose: function() {
|
||||
if (this.innerRender) {
|
||||
this.innerRender.dispose()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function continueScan(instance, vdom) {
|
||||
var innerRender = (instance.innerRender = Anot.scan(
|
||||
instance.fragment,
|
||||
instance.vm
|
||||
))
|
||||
Anot.shadowCopy(vdom, innerRender.root)
|
||||
delete vdom.nodeValue
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { Anot } from '../seed/core'
|
||||
|
||||
var impDir = Anot.directive('important', {
|
||||
priority: 1,
|
||||
getScope: function(name, scope) {
|
||||
var v = Anot.vmodels[name]
|
||||
if (v) return v
|
||||
throw 'error! no vmodel called ' + name
|
||||
},
|
||||
update: function(node, attrName, $id) {
|
||||
if (!Anot.inBrowser) return
|
||||
var dom = Anot.vdom(node, 'toDOM')
|
||||
if (dom.nodeType === 1) {
|
||||
dom.removeAttribute(attrName)
|
||||
Anot(dom).removeClass('ms-controller')
|
||||
}
|
||||
var vm = Anot.vmodels[$id]
|
||||
if (vm) {
|
||||
vm.$element = dom
|
||||
vm.$render = this
|
||||
vm.$fire('onReady')
|
||||
delete vm.$events.onReady
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export var impCb = impDir.update
|
|
@ -0,0 +1,20 @@
|
|||
import './important'
|
||||
import './controller'
|
||||
|
||||
import './skip'
|
||||
import './visible'
|
||||
import './text'
|
||||
|
||||
import './css'
|
||||
import './expr'
|
||||
|
||||
import './attr.modern'
|
||||
import './html'
|
||||
import './if'
|
||||
import './on'
|
||||
import './for'
|
||||
|
||||
import './class.hover.active'
|
||||
import './duplex/modern'
|
||||
import './rules'
|
||||
import './validate'
|
|
@ -0,0 +1,54 @@
|
|||
import { Anot, inBrowser } from '../seed/core'
|
||||
|
||||
import { addScope, makeHandle } from '../parser/index'
|
||||
|
||||
Anot.directive('on', {
|
||||
beforeInit: function() {
|
||||
this.getter = Anot.noop
|
||||
},
|
||||
init: function() {
|
||||
var vdom = this.node
|
||||
var underline = this.name.replace('ms-on-', 'e').replace('-', '_')
|
||||
var uuid =
|
||||
underline +
|
||||
'_' +
|
||||
this.expr.replace(/\s/g, '').replace(/[^$a-z]/gi, function(e) {
|
||||
return e.charCodeAt(0)
|
||||
})
|
||||
var fn = Anot.eventListeners[uuid]
|
||||
if (!fn) {
|
||||
var arr = addScope(this.expr)
|
||||
var body = arr[0],
|
||||
filters = arr[1]
|
||||
body = makeHandle(body)
|
||||
|
||||
if (filters) {
|
||||
filters = filters.replace(/__value__/g, '$event')
|
||||
filters += '\nif($event.$return){\n\treturn;\n}'
|
||||
}
|
||||
var ret = [
|
||||
'try{',
|
||||
'\tvar __vmodel__ = this;',
|
||||
'\t' + filters,
|
||||
'\treturn ' + body,
|
||||
'}catch(e){Anot.log(e, "in on dir")}'
|
||||
].filter(function(el) {
|
||||
return /\S/.test(el)
|
||||
})
|
||||
fn = new Function('$event', ret.join('\n'))
|
||||
fn.uuid = uuid
|
||||
Anot.eventListeners[uuid] = fn
|
||||
}
|
||||
|
||||
var dom = Anot.vdom(vdom, 'toDOM')
|
||||
dom._ms_context_ = this.vm
|
||||
|
||||
this.eventType = this.param.replace(/\-(\d)$/, '')
|
||||
delete this.param
|
||||
Anot(dom).bind(this.eventType, fn)
|
||||
},
|
||||
|
||||
beforeDispose: function() {
|
||||
Anot(this.node.dom).unbind(this.eventType)
|
||||
}
|
||||
})
|
|
@ -0,0 +1,154 @@
|
|||
import { Anot, isObject, platform } from '../seed/core'
|
||||
|
||||
Anot.directive('rules', {
|
||||
diff: function(rules) {
|
||||
if (isObject(rules)) {
|
||||
var vdom = this.node
|
||||
vdom.rules = platform.toJson(rules)
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
function isRegExp(value) {
|
||||
return Anot.type(value) === 'regexp'
|
||||
}
|
||||
var rmail = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/i
|
||||
var rurl = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/
|
||||
function isCorrectDate(value) {
|
||||
if (typeof value === 'string' && value) {
|
||||
//是字符串但不能是空字符
|
||||
var arr = value.split('-') //可以被-切成3份,并且第1个是4个字符
|
||||
if (arr.length === 3 && arr[0].length === 4) {
|
||||
var year = ~~arr[0] //全部转换为非负整数
|
||||
var month = ~~arr[1] - 1
|
||||
var date = ~~arr[2]
|
||||
var d = new Date(year, month, date)
|
||||
return (
|
||||
d.getFullYear() === year &&
|
||||
d.getMonth() === month &&
|
||||
d.getDate() === date
|
||||
)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
//https://github.com/adform/validator.js/blob/master/validator.js
|
||||
Anot.shadowCopy(Anot.validators, {
|
||||
pattern: {
|
||||
message: '必须匹配{{pattern}}这样的格式',
|
||||
get: function(value, field, next) {
|
||||
var elem = field.dom
|
||||
var data = field.data
|
||||
if (!isRegExp(data.pattern)) {
|
||||
var h5pattern = elem.getAttribute('pattern')
|
||||
data.pattern = new RegExp('^(?:' + h5pattern + ')$')
|
||||
}
|
||||
next(data.pattern.test(value))
|
||||
return value
|
||||
}
|
||||
},
|
||||
digits: {
|
||||
message: '必须整数',
|
||||
get: function(value, field, next) {
|
||||
//整数
|
||||
next(/^\-?\d+$/.test(value))
|
||||
return value
|
||||
}
|
||||
},
|
||||
number: {
|
||||
message: '必须数字',
|
||||
get: function(value, field, next) {
|
||||
//数值
|
||||
next(!!value && isFinite(value)) // isFinite('') --> true
|
||||
return value
|
||||
}
|
||||
},
|
||||
norequired: {
|
||||
message: '',
|
||||
get: function(value, field, next) {
|
||||
next(true)
|
||||
return value
|
||||
}
|
||||
},
|
||||
required: {
|
||||
message: '必须填写',
|
||||
get: function(value, field, next) {
|
||||
next(value !== '')
|
||||
return value
|
||||
}
|
||||
},
|
||||
equalto: {
|
||||
message: '密码输入不一致',
|
||||
get: function(value, field, next) {
|
||||
var id = String(field.data.equalto)
|
||||
var other = Anot(document.getElementById(id)).val() || ''
|
||||
next(value === other)
|
||||
return value
|
||||
}
|
||||
},
|
||||
date: {
|
||||
message: '日期格式不正确',
|
||||
get: function(value, field, next) {
|
||||
var data = field.data
|
||||
if (isRegExp(data.date)) {
|
||||
next(data.date.test(value))
|
||||
} else {
|
||||
next(isCorrectDate(value))
|
||||
}
|
||||
return value
|
||||
}
|
||||
},
|
||||
url: {
|
||||
message: 'URL格式不正确',
|
||||
get: function(value, field, next) {
|
||||
next(rurl.test(value))
|
||||
return value
|
||||
}
|
||||
},
|
||||
email: {
|
||||
message: 'email格式不正确',
|
||||
get: function(value, field, next) {
|
||||
next(rmail.test(value))
|
||||
return value
|
||||
}
|
||||
},
|
||||
minlength: {
|
||||
message: '最少输入{{minlength}}个字',
|
||||
get: function(value, field, next) {
|
||||
var num = parseInt(field.data.minlength, 10)
|
||||
next(value.length >= num)
|
||||
return value
|
||||
}
|
||||
},
|
||||
maxlength: {
|
||||
message: '最多输入{{maxlength}}个字',
|
||||
get: function(value, field, next) {
|
||||
var num = parseInt(field.data.maxlength, 10)
|
||||
next(value.length <= num)
|
||||
return value
|
||||
}
|
||||
},
|
||||
min: {
|
||||
message: '输入值不能小于{{min}}',
|
||||
get: function(value, field, next) {
|
||||
var num = parseInt(field.data.min, 10)
|
||||
next(parseFloat(value) >= num)
|
||||
return value
|
||||
}
|
||||
},
|
||||
max: {
|
||||
message: '输入值不能大于{{max}}',
|
||||
get: function(value, field, next) {
|
||||
var num = parseInt(field.data.max, 10)
|
||||
next(parseFloat(value) <= num)
|
||||
return value
|
||||
}
|
||||
},
|
||||
chs: {
|
||||
message: '必须是中文字符',
|
||||
get: function(value, field, next) {
|
||||
next(/^[\u4e00-\u9fa5]+$/.test(value))
|
||||
return value
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,5 @@
|
|||
import { Anot } from '../seed/core'
|
||||
|
||||
Anot.directive('skip', {
|
||||
delay: true
|
||||
})
|
|
@ -0,0 +1,25 @@
|
|||
import { Anot, inBrowser } from '../seed/core'
|
||||
|
||||
Anot.directive('text', {
|
||||
delay: true,
|
||||
init: function() {
|
||||
var node = this.node
|
||||
if (node.isVoidTag) {
|
||||
Anot.error('自闭合元素不能使用ms-text')
|
||||
}
|
||||
var child = { nodeName: '#text', nodeValue: this.getValue() }
|
||||
node.children.splice(0, node.children.length, child)
|
||||
if (inBrowser) {
|
||||
Anot.clearHTML(node.dom)
|
||||
node.dom.appendChild(Anot.vdom(child, 'toDOM'))
|
||||
}
|
||||
this.node = child
|
||||
var type = 'expr'
|
||||
this.type = this.name = type
|
||||
var directive = Anot.directives[type]
|
||||
var me = this
|
||||
this.callback = function(value) {
|
||||
directive.update.call(me, me.node, value)
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,263 @@
|
|||
import { Anot, isObject, platform } from '../seed/core'
|
||||
var valiDir = Anot.directive('validate', {
|
||||
diff: function(validator) {
|
||||
var vdom = this.node
|
||||
if (vdom.validator) {
|
||||
return
|
||||
}
|
||||
if (isObject(validator)) {
|
||||
//注意,这个Form标签的虚拟DOM有两个验证对象
|
||||
//一个是vmValidator,它是用户VM上的那个原始子对象,也是一个VM
|
||||
//一个是validator,它是vmValidator.$model, 这是为了防止IE6-8添加子属性时添加的hack
|
||||
//也可以称之为safeValidate
|
||||
vdom.validator = validator
|
||||
validator = platform.toJson(validator)
|
||||
validator.vdom = vdom
|
||||
validator.dom = vdom.dom
|
||||
|
||||
for (var name in valiDir.defaults) {
|
||||
if (!validator.hasOwnProperty(name)) {
|
||||
validator[name] = valiDir.defaults[name]
|
||||
}
|
||||
}
|
||||
validator.fields = validator.fields || []
|
||||
vdom.vmValidator = validator
|
||||
return true
|
||||
}
|
||||
},
|
||||
update: function(vdom) {
|
||||
var vmValidator = vdom.vmValidator
|
||||
var validator = vdom.validator
|
||||
var dom = vdom.dom
|
||||
dom._ms_validate_ = vmValidator
|
||||
|
||||
collectFeild(vdom.children, vmValidator.fields, vmValidator)
|
||||
var type = window.netscape ? 'keypress' : 'focusin'
|
||||
Anot.bind(document, type, findValidator)
|
||||
//为了方便用户手动执行验证,我们需要为原始vmValidate上添加一个onManual方法
|
||||
function onManual() {
|
||||
var v = this
|
||||
v && valiDir.validateAll.call(v, v.onValidateAll)
|
||||
}
|
||||
|
||||
try {
|
||||
var fn = (vmValidator.onManual = onManual.bind(vmValidator))
|
||||
validator.onManual = fn
|
||||
} catch (e) {
|
||||
Anot.warn(
|
||||
'要想使用onManual方法,必须在validate对象预定义一个空的onManual函数'
|
||||
)
|
||||
}
|
||||
delete vdom.vmValidator
|
||||
|
||||
dom.setAttribute('novalidate', 'novalidate')
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (vmValidator.validateAllInSubmit) {
|
||||
Anot.bind(dom, 'submit', validateAllInSubmitFn)
|
||||
}
|
||||
},
|
||||
validateAll: function(callback) {
|
||||
var validator = this
|
||||
var vdom = this.vdom
|
||||
var fields = (validator.fields = [])
|
||||
collectFeild(vdom.children, fields, validator)
|
||||
var fn = typeof callback === 'function' ? callback : validator.onValidateAll
|
||||
var promises = validator.fields
|
||||
.filter(function(field) {
|
||||
var el = field.dom
|
||||
return el && !el.disabled && validator.dom.contains(el)
|
||||
})
|
||||
.map(function(field) {
|
||||
return valiDir.validate(field, true)
|
||||
})
|
||||
var uniq = {}
|
||||
return Promise.all(promises).then(function(array) {
|
||||
var reasons = array.concat.apply([], array)
|
||||
if (validator.deduplicateInValidateAll) {
|
||||
reasons = reasons.filter(function(reason) {
|
||||
var el = reason.element
|
||||
var uuid = el.uniqueID || (el.uniqueID = setTimeout('1'))
|
||||
if (uniq[uuid]) {
|
||||
return false
|
||||
} else {
|
||||
return (uniq[uuid] = true)
|
||||
}
|
||||
})
|
||||
}
|
||||
fn.call(vdom.dom, reasons) //这里只放置未通过验证的组件
|
||||
})
|
||||
},
|
||||
|
||||
validate: function(field, isValidateAll, event) {
|
||||
var promises = []
|
||||
var value = field.value
|
||||
var elem = field.dom
|
||||
/* istanbul ignore if */
|
||||
if (typeof Promise !== 'function') {
|
||||
//Anot-promise不支持phantomjs
|
||||
Anot.warn(
|
||||
'浏览器不支持原生Promise,请下载并<script src=url>引入\nhttps://github.com/RubyLouvre/Anot/blob/master/test/promise.js'
|
||||
)
|
||||
}
|
||||
/* istanbul ignore if */
|
||||
if (elem.disabled) return
|
||||
var rules = field.vdom.rules
|
||||
var ngs = [],
|
||||
isOk = true
|
||||
if (!(rules.norequired && value === '')) {
|
||||
for (var ruleName in rules) {
|
||||
var ruleValue = rules[ruleName]
|
||||
if (ruleValue === false) continue
|
||||
var hook = Anot.validators[ruleName]
|
||||
var resolve
|
||||
promises.push(
|
||||
new Promise(function(a, b) {
|
||||
resolve = a
|
||||
})
|
||||
)
|
||||
var next = function(a) {
|
||||
var reason = {
|
||||
element: elem,
|
||||
data: field.data,
|
||||
message:
|
||||
elem.getAttribute('data-' + ruleName + '-message') ||
|
||||
elem.getAttribute('data-message') ||
|
||||
hook.message,
|
||||
validateRule: ruleName,
|
||||
getMessage: getMessage
|
||||
}
|
||||
if (a) {
|
||||
resolve(true)
|
||||
} else {
|
||||
isOk = false
|
||||
ngs.push(reason)
|
||||
resolve(false)
|
||||
}
|
||||
}
|
||||
field.data = {}
|
||||
field.data[ruleName] = ruleValue
|
||||
hook.get(value, field, next)
|
||||
}
|
||||
}
|
||||
|
||||
//如果promises不为空,说明经过验证拦截器
|
||||
return Promise.all(promises).then(function(array) {
|
||||
if (!isValidateAll) {
|
||||
var validator = field.validator
|
||||
if (isOk) {
|
||||
validator.onSuccess.call(
|
||||
elem,
|
||||
[
|
||||
{
|
||||
data: field.data,
|
||||
element: elem
|
||||
}
|
||||
],
|
||||
event
|
||||
)
|
||||
} else {
|
||||
validator.onError.call(elem, ngs, event)
|
||||
}
|
||||
validator.onComplete.call(elem, ngs, event)
|
||||
}
|
||||
return ngs
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
//https://github.com/RubyLouvre/Anot/issues/1977
|
||||
function getValidate(dom) {
|
||||
while (dom.tagName !== 'FORM') {
|
||||
dom = dom.parentNode
|
||||
}
|
||||
return dom._ms_validate_
|
||||
}
|
||||
|
||||
function validateAllInSubmitFn(e) {
|
||||
e.preventDefault()
|
||||
var v = getValidate(e.target)
|
||||
if (v && v.onManual) {
|
||||
v.onManual()
|
||||
}
|
||||
}
|
||||
|
||||
function collectFeild(nodes, fields, validator) {
|
||||
for (var i = 0, vdom; (vdom = nodes[i++]); ) {
|
||||
var duplex = vdom.rules && vdom.duplex
|
||||
if (duplex) {
|
||||
fields.push(duplex)
|
||||
bindValidateEvent(duplex, validator)
|
||||
} else if (vdom.children) {
|
||||
collectFeild(vdom.children, fields, validator)
|
||||
} else if (Array.isArray(vdom)) {
|
||||
collectFeild(vdom, fields, validator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findValidator(e) {
|
||||
var dom = e.target
|
||||
var duplex = dom._ms_duplex_
|
||||
var vdom = (duplex || {}).vdom
|
||||
if (duplex && vdom.rules && !duplex.validator) {
|
||||
var msValidator = getValidate(dom)
|
||||
if (msValidator && Anot.Array.ensure(msValidator.fields, duplex)) {
|
||||
bindValidateEvent(duplex, msValidator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function singleValidate(e) {
|
||||
var dom = e.target
|
||||
var duplex = dom._ms_duplex_
|
||||
var msValidator = getValidate(e.target)
|
||||
msValidator && msValidator.validate(duplex, 0, e)
|
||||
}
|
||||
|
||||
function bindValidateEvent(field, validator) {
|
||||
var node = field.dom
|
||||
if (field.validator) {
|
||||
return
|
||||
}
|
||||
field.validator = validator
|
||||
/* istanbul ignore if */
|
||||
if (validator.validateInKeyup && (!field.isChanged && !field.debounceTime)) {
|
||||
Anot.bind(node, 'keyup', singleValidate)
|
||||
}
|
||||
/* istanbul ignore if */
|
||||
if (validator.validateInBlur) {
|
||||
Anot.bind(node, 'blur', singleValidate)
|
||||
}
|
||||
/* istanbul ignore if */
|
||||
if (validator.resetInFocus) {
|
||||
Anot.bind(node, 'focus', function(e) {
|
||||
var dom = e.target
|
||||
var field = dom._ms_duplex_
|
||||
var validator = getValidate(e.target)
|
||||
validator && validator.onReset.call(dom, e, field)
|
||||
})
|
||||
}
|
||||
}
|
||||
var rformat = /\\?{{([^{}]+)\}}/gm
|
||||
|
||||
function getMessage() {
|
||||
var data = this.data || {}
|
||||
return this.message.replace(rformat, function(_, name) {
|
||||
return data[name] == null ? '' : data[name]
|
||||
})
|
||||
}
|
||||
valiDir.defaults = {
|
||||
validate: valiDir.validate,
|
||||
onError: Anot.noop,
|
||||
onSuccess: Anot.noop,
|
||||
onComplete: Anot.noop,
|
||||
onManual: Anot.noop,
|
||||
onReset: Anot.noop,
|
||||
onValidateAll: Anot.noop,
|
||||
validateInBlur: true, //@config {Boolean} true,在blur事件中进行验证,触发onSuccess, onError, onComplete回调
|
||||
validateInKeyup: true, //@config {Boolean} true,在keyup事件中进行验证,触发onSuccess, onError, onComplete回调
|
||||
validateAllInSubmit: true, //@config {Boolean} true,在submit事件中执行onValidateAll回调
|
||||
resetInFocus: true, //@config {Boolean} true,在focus事件中执行onReset回调,
|
||||
deduplicateInValidateAll: false //@config {Boolean} false,在validateAll回调中对reason数组根据元素节点进行去重
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import { Anot } from '../seed/core'
|
||||
import '../effect/index'
|
||||
|
||||
var none = 'none'
|
||||
function parseDisplay(elem, val) {
|
||||
//用于取得此类标签的默认display值
|
||||
var doc = elem.ownerDocument
|
||||
var nodeName = elem.nodeName
|
||||
var key = '_' + nodeName
|
||||
if (!parseDisplay[key]) {
|
||||
var temp = doc.body.appendChild(doc.createElement(nodeName))
|
||||
val = Anot.css(temp, 'display')
|
||||
doc.body.removeChild(temp)
|
||||
if (val === none) {
|
||||
val = 'block'
|
||||
}
|
||||
parseDisplay[key] = val
|
||||
}
|
||||
return parseDisplay[key]
|
||||
}
|
||||
|
||||
Anot.parseDisplay = parseDisplay
|
||||
Anot.directive('visible', {
|
||||
diff: function(newVal, oldVal) {
|
||||
var n = !!newVal
|
||||
if (oldVal === void 0 || n !== oldVal) {
|
||||
this.value = n
|
||||
return true
|
||||
}
|
||||
},
|
||||
ready: true,
|
||||
update: function(vdom, show) {
|
||||
var dom = vdom.dom
|
||||
if (dom && dom.nodeType === 1) {
|
||||
var display = dom.style.display
|
||||
var value
|
||||
if (show) {
|
||||
if (display === none) {
|
||||
value = vdom.displayValue
|
||||
if (!value) {
|
||||
dom.style.display = ''
|
||||
if (dom.style.cssText === '') {
|
||||
dom.removeAttribute('style')
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
dom.style.display === '' &&
|
||||
Anot(dom).css('display') === none &&
|
||||
// fix firefox BUG,必须挂到页面上
|
||||
Anot.contains(dom.ownerDocument, dom)
|
||||
) {
|
||||
value = parseDisplay(dom)
|
||||
}
|
||||
} else {
|
||||
if (display !== none) {
|
||||
value = none
|
||||
vdom.displayValue = display
|
||||
}
|
||||
}
|
||||
var cb = function() {
|
||||
if (value !== void 0) {
|
||||
dom.style.display = value
|
||||
}
|
||||
}
|
||||
|
||||
Anot.applyEffect(dom, vdom, {
|
||||
hook: show ? 'onEnterDone' : 'onLeaveDone',
|
||||
cb: cb
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,45 @@
|
|||
import { Anot, window, document } from '../../seed/core'
|
||||
import { propMap } from './propMap'
|
||||
|
||||
var rsvg = /^\[object SVG\w*Element\]$/
|
||||
export function updateAttrs(node, attrs) {
|
||||
for (var attrName in attrs) {
|
||||
var val = attrs[attrName]
|
||||
/* istanbul ignore if*/
|
||||
if (attrName.indexOf('data-') === 0 || rsvg.test(node)) {
|
||||
node.setAttribute(attrName, val)
|
||||
} else {
|
||||
var propName = propMap[attrName] || attrName
|
||||
if (typeof node[propName] === 'boolean') {
|
||||
//布尔属性必须使用el.xxx = true|false方式设值
|
||||
//如果为false, IE全系列下相当于setAttribute(xxx,''),
|
||||
//会影响到样式,需要进一步处理
|
||||
node[propName] = !!val
|
||||
}
|
||||
if (val === false) {
|
||||
node.removeAttribute(attrName)
|
||||
continue
|
||||
}
|
||||
|
||||
//SVG只能使用setAttribute(xxx, yyy), VML只能使用node.xxx = yyy ,
|
||||
//HTML的固有属性必须node.xxx = yyy
|
||||
var isInnate = attrName in node.cloneNode(false)
|
||||
if (isInnate) {
|
||||
node[propName] = val + ''
|
||||
} else {
|
||||
node.setAttribute(attrName, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Anot.parseJSON = JSON.parse
|
||||
|
||||
Anot.fn.attr = function(name, value) {
|
||||
if (arguments.length === 2) {
|
||||
this[0].setAttribute(name, value)
|
||||
return this
|
||||
} else {
|
||||
return this[0].getAttribute(name)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
export var propMap = {} //不规则的属性名映射
|
||||
|
||||
//防止压缩时出错
|
||||
'accept-charset,acceptCharset|char,ch|charoff,chOff|class,className|for,htmlFor|http-equiv,httpEquiv'.replace(
|
||||
/[^\|]+/g,
|
||||
function(a) {
|
||||
var k = a.split(',')
|
||||
propMap[k[0]] = k[1]
|
||||
}
|
||||
)
|
||||
/*
|
||||
contenteditable不是布尔属性
|
||||
http://www.zhangxinxu.com/wordpress/2016/01/contenteditable-plaintext-only/
|
||||
contenteditable=''
|
||||
contenteditable='events'
|
||||
contenteditable='caret'
|
||||
contenteditable='plaintext-only'
|
||||
contenteditable='true'
|
||||
contenteditable='false'
|
||||
*/
|
||||
var bools = [
|
||||
'autofocus,autoplay,async,allowTransparency,checked,controls',
|
||||
'declare,disabled,defer,defaultChecked,defaultSelected,',
|
||||
'isMap,loop,multiple,noHref,noResize,noShade',
|
||||
'open,readOnly,selected'
|
||||
].join(',')
|
||||
|
||||
bools.replace(/\w+/g, function(name) {
|
||||
propMap[name.toLowerCase()] = name
|
||||
})
|
||||
|
||||
var anomaly = [
|
||||
'accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan',
|
||||
'dateTime,defaultValue,contentEditable,frameBorder,longDesc,maxLength,' +
|
||||
'marginWidth,marginHeight,rowSpan,tabIndex,useMap,vSpace,valueType,vAlign'
|
||||
].join(',')
|
||||
|
||||
anomaly.replace(/\w+/g, function(name) {
|
||||
propMap[name.toLowerCase()] = name
|
||||
})
|
||||
|
||||
//module.exports = propMap
|
|
@ -0,0 +1,32 @@
|
|||
import { Anot, rnowhite, rword } from '../../seed/core'
|
||||
|
||||
'add,remove'.replace(rword, function(method) {
|
||||
Anot.fn[method + 'Class'] = function(cls) {
|
||||
var el = this[0] || {}
|
||||
//https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26
|
||||
if (cls && typeof cls === 'string' && el.nodeType === 1) {
|
||||
cls.replace(rnowhite, function(c) {
|
||||
el.classList[method](c)
|
||||
})
|
||||
}
|
||||
return this
|
||||
}
|
||||
})
|
||||
|
||||
Anot.shadowCopy(Anot.fn, {
|
||||
hasClass: function(cls) {
|
||||
var el = this[0] || {}
|
||||
//IE10+, chrome8+, firefox3.6+, safari5.1+,opera11.5+支持classList,
|
||||
//chrome24+,firefox26+支持classList2.0
|
||||
return el.nodeType === 1 && el.classList.contains(cls)
|
||||
},
|
||||
toggleClass: function(value, stateVal) {
|
||||
var isBool = typeof stateVal === 'boolean'
|
||||
var me = this
|
||||
String(value).replace(rnowhite, function(c) {
|
||||
var state = isBool ? stateVal : !me.hasClass(c)
|
||||
me[state ? 'addClass' : 'removeClass'](c)
|
||||
})
|
||||
return this
|
||||
}
|
||||
})
|
|
@ -0,0 +1,49 @@
|
|||
import { Anot } from '../../seed/core'
|
||||
import { getWindow } from './share'
|
||||
|
||||
Anot.fn.offset = function() {
|
||||
//取得距离页面左右角的坐标
|
||||
var node = this[0]
|
||||
try {
|
||||
var rect = node.getBoundingClientRect()
|
||||
// Make sure element is not hidden (display: none) or disconnected
|
||||
// https://github.com/jquery/jquery/pull/2043/files#r23981494
|
||||
if (rect.width || rect.height || node.getClientRects().length) {
|
||||
var doc = node.ownerDocument
|
||||
var root = doc.documentElement
|
||||
var win = doc.defaultView
|
||||
return {
|
||||
top: rect.top + win.pageYOffset - root.clientTop,
|
||||
left: rect.left + win.pageXOffset - root.clientLeft
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
left: 0,
|
||||
top: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Anot.each(
|
||||
{
|
||||
scrollLeft: 'pageXOffset',
|
||||
scrollTop: 'pageYOffset'
|
||||
},
|
||||
function(method, prop) {
|
||||
Anot.fn[method] = function(val) {
|
||||
var node = this[0] || {}
|
||||
var win = getWindow(node)
|
||||
var top = method === 'scrollTop'
|
||||
if (!arguments.length) {
|
||||
return win ? win[prop] : node[method]
|
||||
} else {
|
||||
if (win) {
|
||||
win.scrollTo(!top ? val : win[prop], top ? val : win[prop])
|
||||
} else {
|
||||
node[method] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
|
@ -0,0 +1,274 @@
|
|||
import { Anot, oneObject, cssHooks, window } from '../../seed/core'
|
||||
|
||||
var cssMap = oneObject('float', 'cssFloat')
|
||||
export { cssMap, cssHooks }
|
||||
Anot.cssNumber = oneObject(
|
||||
'animationIterationCount,columnCount,order,flex,flexGrow,flexShrink,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom'
|
||||
)
|
||||
var prefixes = ['', '-webkit-', '-o-', '-moz-', '-ms-']
|
||||
/* istanbul ignore next */
|
||||
Anot.cssName = function(name, host, camelCase) {
|
||||
if (cssMap[name]) {
|
||||
return cssMap[name]
|
||||
}
|
||||
host = host || Anot.root.style || {}
|
||||
for (var i = 0, n = prefixes.length; i < n; i++) {
|
||||
camelCase = Anot.camelize(prefixes[i] + name)
|
||||
if (camelCase in host) {
|
||||
return (cssMap[name] = camelCase)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
Anot.css = function(node, name, value, fn) {
|
||||
//读写删除元素节点的样式
|
||||
if (node instanceof Anot) {
|
||||
node = node[0]
|
||||
}
|
||||
if (node.nodeType !== 1) {
|
||||
return
|
||||
}
|
||||
var prop = Anot.camelize(name)
|
||||
name = Anot.cssName(prop) || /* istanbul ignore next*/ prop
|
||||
if (value === void 0 || typeof value === 'boolean') {
|
||||
//获取样式
|
||||
fn = cssHooks[prop + ':get'] || cssHooks['@:get']
|
||||
if (name === 'background') {
|
||||
name = 'backgroundColor'
|
||||
}
|
||||
var val = fn(node, name)
|
||||
return value === true ? parseFloat(val) || 0 : val
|
||||
} else if (value === '') {
|
||||
//请除样式
|
||||
node.style[name] = ''
|
||||
} else {
|
||||
//设置样式
|
||||
if (value == null || value !== value) {
|
||||
return
|
||||
}
|
||||
if (isFinite(value) && !Anot.cssNumber[prop]) {
|
||||
value += 'px'
|
||||
}
|
||||
fn = cssHooks[prop + ':set'] || cssHooks['@:set']
|
||||
fn(node, name, value)
|
||||
}
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
Anot.fn.css = function(name, value) {
|
||||
if (Anot.isPlainObject(name)) {
|
||||
for (var i in name) {
|
||||
Anot.css(this, i, name[i])
|
||||
}
|
||||
} else {
|
||||
var ret = Anot.css(this, name, value)
|
||||
}
|
||||
return ret !== void 0 ? ret : this
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
Anot.fn.position = function() {
|
||||
var offsetParent,
|
||||
offset,
|
||||
elem = this[0],
|
||||
parentOffset = {
|
||||
top: 0,
|
||||
left: 0
|
||||
}
|
||||
if (!elem) {
|
||||
return parentOffset
|
||||
}
|
||||
if (this.css('position') === 'fixed') {
|
||||
offset = elem.getBoundingClientRect()
|
||||
} else {
|
||||
offsetParent = this.offsetParent() //得到真正的offsetParent
|
||||
offset = this.offset() // 得到正确的offsetParent
|
||||
if (offsetParent[0].tagName !== 'HTML') {
|
||||
parentOffset = offsetParent.offset()
|
||||
}
|
||||
parentOffset.top += Anot.css(offsetParent[0], 'borderTopWidth', true)
|
||||
parentOffset.left += Anot.css(offsetParent[0], 'borderLeftWidth', true)
|
||||
|
||||
// Subtract offsetParent scroll positions
|
||||
parentOffset.top -= offsetParent.scrollTop()
|
||||
parentOffset.left -= offsetParent.scrollLeft()
|
||||
}
|
||||
return {
|
||||
top: offset.top - parentOffset.top - Anot.css(elem, 'marginTop', true),
|
||||
left: offset.left - parentOffset.left - Anot.css(elem, 'marginLeft', true)
|
||||
}
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
Anot.fn.offsetParent = function() {
|
||||
var offsetParent = this[0].offsetParent
|
||||
while (offsetParent && Anot.css(offsetParent, 'position') === 'static') {
|
||||
offsetParent = offsetParent.offsetParent
|
||||
}
|
||||
return Anot(offsetParent || Anot.root)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
cssHooks['@:set'] = function(node, name, value) {
|
||||
try {
|
||||
//node.style.width = NaN;node.style.width = 'xxxxxxx';
|
||||
//node.style.width = undefine 在旧式IE下会抛异常
|
||||
node.style[name] = value
|
||||
} catch (e) {}
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
cssHooks['@:get'] = function(node, name) {
|
||||
if (!node || !node.style) {
|
||||
throw new Error('getComputedStyle要求传入一个节点 ' + node)
|
||||
}
|
||||
var ret,
|
||||
styles = window.getComputedStyle(node, null)
|
||||
if (styles) {
|
||||
ret = name === 'filter' ? styles.getPropertyValue(name) : styles[name]
|
||||
if (ret === '') {
|
||||
ret = node.style[name] //其他浏览器需要我们手动取内联样式
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
cssHooks['opacity:get'] = function(node) {
|
||||
var ret = cssHooks['@:get'](node, 'opacity')
|
||||
return ret === '' ? '1' : ret
|
||||
}
|
||||
|
||||
'top,left'.replace(Anot.rword, function(name) {
|
||||
cssHooks[name + ':get'] = function(node) {
|
||||
var computed = cssHooks['@:get'](node, name)
|
||||
return /px$/.test(computed) ? computed : Anot(node).position()[name] + 'px'
|
||||
}
|
||||
})
|
||||
|
||||
var cssShow = {
|
||||
position: 'absolute',
|
||||
visibility: 'hidden',
|
||||
display: 'block'
|
||||
}
|
||||
|
||||
var rdisplayswap = /^(none|table(?!-c[ea]).+)/
|
||||
/* istanbul ignore next */
|
||||
function showHidden(node, array) {
|
||||
//http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
|
||||
if (node.offsetWidth <= 0) {
|
||||
//opera.offsetWidth可能小于0
|
||||
if (rdisplayswap.test(cssHooks['@:get'](node, 'display'))) {
|
||||
var obj = {
|
||||
node: node
|
||||
}
|
||||
for (var name in cssShow) {
|
||||
obj[name] = node.style[name]
|
||||
node.style[name] = cssShow[name]
|
||||
}
|
||||
array.push(obj)
|
||||
}
|
||||
var parent = node.parentNode
|
||||
if (parent && parent.nodeType === 1) {
|
||||
showHidden(parent, array)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* istanbul ignore next*/
|
||||
Anot.each(
|
||||
{
|
||||
Width: 'width',
|
||||
Height: 'height'
|
||||
},
|
||||
function(name, method) {
|
||||
var clientProp = 'client' + name,
|
||||
scrollProp = 'scroll' + name,
|
||||
offsetProp = 'offset' + name
|
||||
cssHooks[method + ':get'] = function(node, which, override) {
|
||||
var boxSizing = -4
|
||||
if (typeof override === 'number') {
|
||||
boxSizing = override
|
||||
}
|
||||
which = name === 'Width' ? ['Left', 'Right'] : ['Top', 'Bottom']
|
||||
var ret = node[offsetProp] // border-box 0
|
||||
if (boxSizing === 2) {
|
||||
// margin-box 2
|
||||
return (
|
||||
ret +
|
||||
Anot.css(node, 'margin' + which[0], true) +
|
||||
Anot.css(node, 'margin' + which[1], true)
|
||||
)
|
||||
}
|
||||
if (boxSizing < 0) {
|
||||
// padding-box -2
|
||||
ret =
|
||||
ret -
|
||||
Anot.css(node, 'border' + which[0] + 'Width', true) -
|
||||
Anot.css(node, 'border' + which[1] + 'Width', true)
|
||||
}
|
||||
if (boxSizing === -4) {
|
||||
// content-box -4
|
||||
ret =
|
||||
ret -
|
||||
Anot.css(node, 'padding' + which[0], true) -
|
||||
Anot.css(node, 'padding' + which[1], true)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
cssHooks[method + '&get'] = function(node) {
|
||||
var hidden = []
|
||||
showHidden(node, hidden)
|
||||
var val = cssHooks[method + ':get'](node)
|
||||
for (var i = 0, obj; (obj = hidden[i++]); ) {
|
||||
node = obj.node
|
||||
for (var n in obj) {
|
||||
if (typeof obj[n] === 'string') {
|
||||
node.style[n] = obj[n]
|
||||
}
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
Anot.fn[method] = function(value) {
|
||||
//会忽视其display
|
||||
var node = this[0]
|
||||
if (arguments.length === 0) {
|
||||
if (node.setTimeout) {
|
||||
//取得窗口尺寸
|
||||
return (
|
||||
node['inner' + name] ||
|
||||
node.document.documentElement[clientProp] ||
|
||||
node.document.body[clientProp]
|
||||
) //IE6下前两个分别为undefined,0
|
||||
}
|
||||
if (node.nodeType === 9) {
|
||||
//取得页面尺寸
|
||||
var doc = node.documentElement
|
||||
//FF chrome html.scrollHeight< body.scrollHeight
|
||||
//IE 标准模式 : html.scrollHeight> body.scrollHeight
|
||||
//IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点?
|
||||
return Math.max(
|
||||
node.body[scrollProp],
|
||||
doc[scrollProp],
|
||||
node.body[offsetProp],
|
||||
doc[offsetProp],
|
||||
doc[clientProp]
|
||||
)
|
||||
}
|
||||
return cssHooks[method + '&get'](node)
|
||||
} else {
|
||||
return this.css(method, value)
|
||||
}
|
||||
}
|
||||
Anot.fn['inner' + name] = function() {
|
||||
return cssHooks[method + ':get'](this[0], void 0, -2)
|
||||
}
|
||||
Anot.fn['outer' + name] = function(includeMargin) {
|
||||
return cssHooks[method + ':get'](
|
||||
this[0],
|
||||
void 0,
|
||||
includeMargin === true ? 2 : 0
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export function getWindow(node) {
|
||||
return node.window || node.defaultView || node.parentWindow || false
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
//http://www.feiesoft.com/html/events.html
|
||||
//http://segmentfault.com/q/1010000000687977/a-1020000000688757
|
||||
export var canBubbleUp = {
|
||||
click: true,
|
||||
dblclick: true,
|
||||
keydown: true,
|
||||
keypress: true,
|
||||
keyup: true,
|
||||
mousedown: true,
|
||||
mousemove: true,
|
||||
mouseup: true,
|
||||
mouseover: true,
|
||||
mouseout: true,
|
||||
wheel: true,
|
||||
mousewheel: true,
|
||||
input: true,
|
||||
change: true,
|
||||
beforeinput: true,
|
||||
compositionstart: true,
|
||||
compositionupdate: true,
|
||||
compositionend: true,
|
||||
select: true,
|
||||
//http://blog.csdn.net/lee_magnum/article/details/17761441
|
||||
cut: true,
|
||||
copy: true,
|
||||
paste: true,
|
||||
beforecut: true,
|
||||
beforecopy: true,
|
||||
beforepaste: true,
|
||||
focusin: true,
|
||||
focusout: true,
|
||||
DOMFocusIn: true,
|
||||
DOMFocusOut: true,
|
||||
DOMActivate: true,
|
||||
dragend: true,
|
||||
datasetchanged: true
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { Anot, document } from '../../seed/core'
|
||||
|
||||
import { avEvent } from './share'
|
||||
export { avEvent }
|
||||
/* istanbul ignore next */
|
||||
Anot._nativeBind = function(el, type, fn, capture) {
|
||||
el.addEventListener(type, fn, !!capture)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
Anot._nativeUnBind = function(el, type, fn, a) {
|
||||
el.removeEventListener(type, fn, !!a)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
Anot.fireDom = function(elem, type, opts) {
|
||||
/* istanbul ignore else */
|
||||
if (document.createEvent) {
|
||||
var hackEvent = document.createEvent('Events')
|
||||
hackEvent.initEvent(type, true, true, opts)
|
||||
Anot.shadowCopy(hackEvent, opts)
|
||||
elem.dispatchEvent(hackEvent)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
import {
|
||||
Anot,
|
||||
_slice,
|
||||
eventHooks,
|
||||
modern,
|
||||
window,
|
||||
document,
|
||||
root,
|
||||
getShortID
|
||||
} from '../../seed/core'
|
||||
import { canBubbleUp } from './canBubbleUp'
|
||||
/* istanbul ignore if */
|
||||
var hackSafari = Anot.modern && document.ontouchstart
|
||||
|
||||
//添加fn.bind, fn.unbind, bind, unbind
|
||||
Anot.fn.bind = function(type, fn, phase) {
|
||||
if (this[0]) {
|
||||
//此方法不会链
|
||||
return Anot.bind(this[0], type, fn, phase)
|
||||
}
|
||||
}
|
||||
|
||||
Anot.fn.unbind = function(type, fn, phase) {
|
||||
if (this[0]) {
|
||||
var args = _slice.call(arguments)
|
||||
args.unshift(this[0])
|
||||
Anot.unbind.apply(0, args)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/*绑定事件*/
|
||||
Anot.bind = function(elem, type, fn) {
|
||||
if (elem.nodeType === 1) {
|
||||
var value = elem.getAttribute('Anot-events') || ''
|
||||
//如果是使用ms-on-*绑定的回调,其uuid格式为e12122324,
|
||||
//如果是使用bind方法绑定的回调,其uuid格式为_12
|
||||
var uuid = getShortID(fn)
|
||||
var hook = eventHooks[type]
|
||||
/* istanbul ignore if */
|
||||
if (type === 'click' && hackSafari) {
|
||||
elem.addEventListener('click', Anot.noop)
|
||||
}
|
||||
/* istanbul ignore if */
|
||||
if (hook) {
|
||||
type = hook.type || type
|
||||
if (hook.fix) {
|
||||
fn = hook.fix(elem, fn)
|
||||
fn.uuid = uuid
|
||||
}
|
||||
}
|
||||
var key = type + ':' + uuid
|
||||
Anot.eventListeners[fn.uuid] = fn
|
||||
/* istanbul ignore if */
|
||||
if (value.indexOf(type + ':') === -1) {
|
||||
//同一种事件只绑定一次
|
||||
if (canBubbleUp[type] || (Anot.modern && focusBlur[type])) {
|
||||
delegateEvent(type)
|
||||
} else {
|
||||
Anot._nativeBind(elem, type, dispatch)
|
||||
}
|
||||
}
|
||||
var keys = value.split(',')
|
||||
/* istanbul ignore if */
|
||||
if (keys[0] === '') {
|
||||
keys.shift()
|
||||
}
|
||||
if (keys.indexOf(key) === -1) {
|
||||
keys.push(key)
|
||||
setEventId(elem, keys.join(','))
|
||||
//将令牌放进Anot-events属性中
|
||||
}
|
||||
return fn
|
||||
} else {
|
||||
/* istanbul ignore next */
|
||||
function cb(e) {
|
||||
fn.call(elem, new avEvent(e))
|
||||
}
|
||||
Anot._nativeBind(elem, type, cb)
|
||||
return cb
|
||||
}
|
||||
}
|
||||
|
||||
function setEventId(node, value) {
|
||||
node.setAttribute('Anot-events', value)
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
Anot.unbind = function(elem, type, fn) {
|
||||
if (elem.nodeType === 1) {
|
||||
var value = elem.getAttribute('Anot-events') || ''
|
||||
switch (arguments.length) {
|
||||
case 1:
|
||||
Anot._nativeUnBind(elem, type, dispatch)
|
||||
elem.removeAttribute('Anot-events')
|
||||
break
|
||||
case 2:
|
||||
value = value
|
||||
.split(',')
|
||||
.filter(function(str) {
|
||||
return str.indexOf(type + ':') === -1
|
||||
})
|
||||
.join(',')
|
||||
setEventId(elem, value)
|
||||
break
|
||||
default:
|
||||
var search = type + ':' + fn.uuid
|
||||
value = value
|
||||
.split(',')
|
||||
.filter(function(str) {
|
||||
return str !== search
|
||||
})
|
||||
.join(',')
|
||||
setEventId(elem, value)
|
||||
delete Anot.eventListeners[fn.uuid]
|
||||
break
|
||||
}
|
||||
} else {
|
||||
Anot._nativeUnBind(elem, type, fn)
|
||||
}
|
||||
}
|
||||
|
||||
var typeRegExp = {}
|
||||
|
||||
function collectHandlers(elem, type, handlers) {
|
||||
var value = elem.getAttribute('Anot-events')
|
||||
if (value && (elem.disabled !== true || type !== 'click')) {
|
||||
var uuids = []
|
||||
var reg =
|
||||
typeRegExp[type] ||
|
||||
(typeRegExp[type] = new RegExp('\\b' + type + '\\:([^,\\s]+)', 'g'))
|
||||
value.replace(reg, function(a, b) {
|
||||
uuids.push(b)
|
||||
return a
|
||||
})
|
||||
if (uuids.length) {
|
||||
handlers.push({
|
||||
elem: elem,
|
||||
uuids: uuids
|
||||
})
|
||||
}
|
||||
}
|
||||
elem = elem.parentNode
|
||||
var g = Anot.gestureEvents || {}
|
||||
if (elem && elem.getAttribute && (canBubbleUp[type] || g[type])) {
|
||||
collectHandlers(elem, type, handlers)
|
||||
}
|
||||
}
|
||||
|
||||
var rhandleHasVm = /^e/
|
||||
|
||||
function dispatch(event) {
|
||||
event = new avEvent(event)
|
||||
var type = event.type
|
||||
var elem = event.target
|
||||
var handlers = []
|
||||
collectHandlers(elem, type, handlers)
|
||||
var i = 0,
|
||||
j,
|
||||
uuid,
|
||||
handler
|
||||
while ((handler = handlers[i++]) && !event.cancelBubble) {
|
||||
var host = (event.currentTarget = handler.elem)
|
||||
j = 0
|
||||
while ((uuid = handler.uuids[j++])) {
|
||||
if (event.stopImmediate) {
|
||||
break
|
||||
}
|
||||
var fn = Anot.eventListeners[uuid]
|
||||
if (fn) {
|
||||
var vm = rhandleHasVm.test(uuid) ? handler.elem._ms_context_ : 0
|
||||
if (vm && vm.$hashcode === false) {
|
||||
return Anot.unbind(elem, type, fn)
|
||||
}
|
||||
var ret = fn.call(vm || elem, event)
|
||||
|
||||
if (ret === false) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var focusBlur = {
|
||||
focus: true,
|
||||
blur: true
|
||||
}
|
||||
|
||||
function delegateEvent(type) {
|
||||
var value = root.getAttribute('delegate-events') || ''
|
||||
if (value.indexOf(type) === -1) {
|
||||
//IE6-8会多次绑定同种类型的同一个函数,其他游览器不会
|
||||
var arr = value.match(Anot.rword) || []
|
||||
arr.push(type)
|
||||
root.setAttribute('delegate-events', arr.join(','))
|
||||
Anot._nativeBind(root, type, dispatch, !!focusBlur[type])
|
||||
}
|
||||
}
|
||||
|
||||
var eventProto = {
|
||||
webkitMovementY: 1,
|
||||
webkitMovementX: 1,
|
||||
keyLocation: 1,
|
||||
fixEvent: function() {},
|
||||
preventDefault: function() {
|
||||
var e = this.originalEvent || {}
|
||||
e.returnValue = this.returnValue = false
|
||||
if (modern && e.preventDefault) {
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
stopPropagation: function() {
|
||||
var e = this.originalEvent || {}
|
||||
e.cancelBubble = this.cancelBubble = true
|
||||
if (modern && e.stopPropagation) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
},
|
||||
stopImmediatePropagation: function() {
|
||||
this.stopPropagation()
|
||||
this.stopImmediate = true
|
||||
},
|
||||
toString: function() {
|
||||
return '[object Event]' //#1619
|
||||
}
|
||||
}
|
||||
|
||||
export function avEvent(event) {
|
||||
if (event.originalEvent) {
|
||||
return event
|
||||
}
|
||||
for (var i in event) {
|
||||
if (!eventProto[i]) {
|
||||
this[i] = event[i]
|
||||
}
|
||||
}
|
||||
if (!this.target) {
|
||||
this.target = event.srcElement
|
||||
}
|
||||
var target = this.target
|
||||
this.fixEvent()
|
||||
this.timeStamp = new Date() - 0
|
||||
this.originalEvent = event
|
||||
}
|
||||
avEvent.prototype = eventProto
|
||||
//针对firefox, chrome修正mouseenter, mouseleave
|
||||
/* istanbul ignore if */
|
||||
if (!('onmouseenter' in root)) {
|
||||
Anot.each(
|
||||
{
|
||||
mouseenter: 'mouseover',
|
||||
mouseleave: 'mouseout'
|
||||
},
|
||||
function(origType, fixType) {
|
||||
eventHooks[origType] = {
|
||||
type: fixType,
|
||||
fix: function(elem, fn) {
|
||||
return function(e) {
|
||||
var t = e.relatedTarget
|
||||
if (!t || (t !== elem && !(elem.compareDocumentPosition(t) & 16))) {
|
||||
delete e.type
|
||||
e.type = origType
|
||||
return fn.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
//针对IE9+, w3c修正animationend
|
||||
Anot.each(
|
||||
{
|
||||
AnimationEvent: 'animationend',
|
||||
WebKitAnimationEvent: 'webkitAnimationEnd'
|
||||
},
|
||||
function(construct, fixType) {
|
||||
if (window[construct] && !eventHooks.animationend) {
|
||||
eventHooks.animationend = {
|
||||
type: fixType
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (!('onmousewheel' in document)) {
|
||||
/* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120
|
||||
firefox DOMMouseScroll detail 下3 上-3
|
||||
firefox wheel detlaY 下3 上-3
|
||||
IE9-11 wheel deltaY 下40 上-40
|
||||
chrome wheel deltaY 下100 上-100 */
|
||||
var fixWheelType = document.onwheel !== void 0 ? 'wheel' : 'DOMMouseScroll'
|
||||
var fixWheelDelta = fixWheelType === 'wheel' ? 'deltaY' : 'detail'
|
||||
eventHooks.mousewheel = {
|
||||
type: fixWheelType,
|
||||
fix: function(elem, fn) {
|
||||
return function(e) {
|
||||
var delta = e[fixWheelDelta] > 0 ? -120 : 120
|
||||
e.wheelDelta = ~~elem._ms_wheel_ + delta
|
||||
elem._ms_wheel_ = e.wheelDeltaY = e.wheelDelta
|
||||
e.wheelDeltaX = 0
|
||||
if (Object.defineProperty) {
|
||||
Object.defineProperty(e, 'type', {
|
||||
value: 'mousewheel'
|
||||
})
|
||||
}
|
||||
return fn.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
import { Anot, Cache, document, createFragment } from '../../seed/core'
|
||||
import { fromString } from '../../vtree/fromString'
|
||||
export { Anot }
|
||||
|
||||
var rhtml = /<|&#?\w+;/
|
||||
var htmlCache = new Cache(128)
|
||||
var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi
|
||||
|
||||
Anot.parseHTML = function(html) {
|
||||
var fragment = createFragment()
|
||||
//处理非字符串
|
||||
if (typeof html !== 'string') {
|
||||
return fragment
|
||||
}
|
||||
//处理非HTML字符串
|
||||
if (!rhtml.test(html)) {
|
||||
return document.createTextNode(html)
|
||||
}
|
||||
|
||||
html = html.replace(rxhtml, '<$1></$2>').trim()
|
||||
var hasCache = htmlCache.get(html)
|
||||
if (hasCache) {
|
||||
return Anot.cloneNode(hasCache)
|
||||
}
|
||||
var vnodes = fromString(html)
|
||||
for (var i = 0, el; (el = vnodes[i++]); ) {
|
||||
var child = Anot.vdom(el, 'toDOM')
|
||||
fragment.appendChild(child)
|
||||
}
|
||||
if (html.length < 1024) {
|
||||
htmlCache.put(html, fragment)
|
||||
}
|
||||
return fragment
|
||||
}
|
||||
|
||||
Anot.innerHTML = function(node, html) {
|
||||
var parsed = Anot.parseHTML(html)
|
||||
this.clearHTML(node)
|
||||
node.appendChild(parsed)
|
||||
}
|
||||
|
||||
//https://github.com/karloespiritu/escapehtmlent/blob/master/index.js
|
||||
Anot.unescapeHTML = function(html) {
|
||||
return String(html)
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&')
|
||||
}
|
||||
|
||||
Anot.clearHTML = function(node) {
|
||||
/* istanbul ignore next */
|
||||
while (node.lastChild) {
|
||||
node.removeChild(node.lastChild)
|
||||
}
|
||||
return node
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*******************************************************************
|
||||
* DOM Api *
|
||||
* shim,class,data,css,val,html,event,ready *
|
||||
********************************************************************/
|
||||
|
||||
import './shim/modern'
|
||||
import './class/modern'
|
||||
import './attr/modern'
|
||||
import './css/modern'
|
||||
import './val/modern'
|
||||
import './html/index'
|
||||
import './event/modern'
|
||||
import './ready/modern'
|
|
@ -0,0 +1,2 @@
|
|||
export var rcheckedType = /^(?:checkbox|radio)$/
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { Anot, window, document, root, inBrowser } from '../../seed/core'
|
||||
|
||||
var readyList = []
|
||||
|
||||
export function fireReady(fn) {
|
||||
Anot.isReady = true
|
||||
while ((fn = readyList.shift())) {
|
||||
fn(Anot)
|
||||
}
|
||||
}
|
||||
|
||||
Anot.ready = function(fn) {
|
||||
readyList.push(fn)
|
||||
if (Anot.isReady) {
|
||||
fireReady()
|
||||
}
|
||||
}
|
||||
|
||||
Anot.ready(function() {
|
||||
Anot.scan && Anot.scan(document.body)
|
||||
})
|
||||
|
||||
/* istanbul ignore next */
|
||||
function bootstrap() {
|
||||
if (document.readyState === 'complete') {
|
||||
setTimeout(fireReady) //如果在domReady之外加载
|
||||
} else {
|
||||
//必须传入三个参数,否则在firefox4-26中报错
|
||||
//caught exception: [Exception... "Not enough arguments" nsresult: "0x80570001 (NS_ERROR_XPC_NOT_ENOUGH_ARGS)"
|
||||
document.addEventListener('DOMContentLoaded', fireReady, false)
|
||||
}
|
||||
|
||||
Anot.bind(window, 'load', fireReady)
|
||||
}
|
||||
|
||||
if (inBrowser) {
|
||||
bootstrap()
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/* istanbul ignore next */
|
||||
export function fixContains(root, el) {
|
||||
try {
|
||||
//IE6-8,游离于DOM树外的文本节点,访问parentNode有时会抛错
|
||||
while ((el = el.parentNode)) {
|
||||
if (el === root) return true
|
||||
}
|
||||
} catch (e) {}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
//safari5+是把contains方法放在Element.prototype上而不是Node.prototype
|
||||
import { Anot, document, window, root } from '../../seed/core'
|
||||
import { fixContains } from './fixContains'
|
||||
export { Anot }
|
||||
|
||||
Anot.contains = fixContains
|
||||
|
||||
Anot.cloneNode = function(a) {
|
||||
return a.cloneNode(true)
|
||||
}
|
||||
|
||||
if (Anot.modern) {
|
||||
if (!document.contains) {
|
||||
Node.prototype.contains = function(child) {
|
||||
//IE6-8没有Node对象
|
||||
return fixContains(this, child)
|
||||
}
|
||||
}
|
||||
function fixFF(prop, cb) {
|
||||
//firefox12 http://caniuse.com/#search=outerHTML
|
||||
if (!(prop in root)) {
|
||||
HTMLElement.prototype.__defineGetter__(prop, cb)
|
||||
}
|
||||
}
|
||||
fixFF('outerHTML', function() {
|
||||
//https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/children
|
||||
var div = document.createElement('div')
|
||||
div.appendChild(this)
|
||||
return div.innerHTML
|
||||
})
|
||||
fixFF('children', function() {
|
||||
var children = []
|
||||
for (var i = 0, el; (el = this.childNodes[i++]); ) {
|
||||
if (el.nodeType === 1) {
|
||||
children.push(el)
|
||||
}
|
||||
}
|
||||
return children
|
||||
})
|
||||
fixFF('innerText', function() {
|
||||
return this.textContent
|
||||
})
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { rcheckedType } from '../rcheckedType'
|
||||
export function getDuplexType(elem) {
|
||||
var ret = elem.tagName.toLowerCase()
|
||||
if (ret === 'input') {
|
||||
return rcheckedType.test(elem.type) ? 'checked' : elem.type
|
||||
}
|
||||
return ret
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import { Anot } from '../../seed/core'
|
||||
import { getDuplexType } from './getDuplexType'
|
||||
export { getDuplexType }
|
||||
|
||||
var valHooks = {
|
||||
'select:get': function self(node, ret, index, singleton) {
|
||||
var nodes = node.children,
|
||||
value,
|
||||
index = ret ? index : node.selectedIndex
|
||||
singleton = ret ? singleton : node.type === 'select-one' || index < 0
|
||||
ret = ret || []
|
||||
for (var i = 0, el; (el = nodes[i++]); ) {
|
||||
if (!el.disabled) {
|
||||
switch (el.nodeName.toLowerCase()) {
|
||||
case 'option':
|
||||
if (el.selected || el.index === index) {
|
||||
value = el.value
|
||||
if (singleton) {
|
||||
return value
|
||||
} else {
|
||||
ret.push(value)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'optgroup':
|
||||
value = self(el, ret, index, singleton)
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return singleton ? null : ret
|
||||
},
|
||||
'select:set': function(node, values, optionSet) {
|
||||
values = [].concat(values) //强制转换为数组
|
||||
for (var i = 0, el; (el = node.options[i++]); ) {
|
||||
if ((el.selected = values.indexOf(el.value) > -1)) {
|
||||
optionSet = true
|
||||
}
|
||||
}
|
||||
if (!optionSet) {
|
||||
node.selectedIndex = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Anot.fn.val = function(value) {
|
||||
var node = this[0]
|
||||
if (node && node.nodeType === 1) {
|
||||
var get = arguments.length === 0
|
||||
var access = get ? ':get' : ':set'
|
||||
var fn = valHooks[getDuplexType(node) + access]
|
||||
if (fn) {
|
||||
var val = fn(node, value)
|
||||
} else if (get) {
|
||||
return (node.value || '').replace(/\r/g, '')
|
||||
} else {
|
||||
node.value = value
|
||||
}
|
||||
}
|
||||
return get ? val : this
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* ------------------------------------------------------------
|
||||
* 检测浏览器对CSS动画的支持与API名
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { window } from '../seed/core'
|
||||
let checker = {
|
||||
TransitionEvent: 'transitionend',
|
||||
WebKitTransitionEvent: 'webkitTransitionEnd',
|
||||
OTransitionEvent: 'oTransitionEnd',
|
||||
otransitionEvent: 'otransitionEnd'
|
||||
}
|
||||
let css3, tran, ani, name, animationEndEvent, transitionEndEvent
|
||||
let transition = false
|
||||
let animation = false
|
||||
//有的浏览器同时支持私有实现与标准写法,比如webkit支持前两种,Opera支持1、3、4
|
||||
for (name in checker) {
|
||||
if (window[name]) {
|
||||
tran = checker[name]
|
||||
break
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
try {
|
||||
let a = document.createEvent(name)
|
||||
tran = checker[name]
|
||||
break
|
||||
} catch (e) {}
|
||||
}
|
||||
if (typeof tran === 'string') {
|
||||
transition = css3 = true
|
||||
transitionEndEvent = tran
|
||||
}
|
||||
|
||||
//animationend有两个可用形态
|
||||
//IE10+, Firefox 16+ & Opera 12.1+: animationend
|
||||
//Chrome/Safari: webkitAnimationEnd
|
||||
//http://blogs.msdn.com/b/davrous/archive/2011/12/06/introduction-to-css3-animat ions.aspx
|
||||
//IE10也可以使用MSAnimationEnd监听,但是回调里的事件 type依然为animationend
|
||||
// el.addEventListener('MSAnimationEnd', function(e) {
|
||||
// alert(e.type)// animationend!!!
|
||||
// })
|
||||
checker = {
|
||||
AnimationEvent: 'animationend',
|
||||
WebKitAnimationEvent: 'webkitAnimationEnd'
|
||||
}
|
||||
for (name in checker) {
|
||||
if (window[name]) {
|
||||
ani = checker[name]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (typeof ani === 'string') {
|
||||
animation = css3 = true
|
||||
animationEndEvent = ani
|
||||
}
|
||||
export { css3, animation, transition, animationEndEvent, transitionEndEvent }
|
|
@ -0,0 +1,327 @@
|
|||
import { Anot, window, Cache } from '../seed/core'
|
||||
import { cssDiff } from '../directives/css'
|
||||
import {
|
||||
css3,
|
||||
animation,
|
||||
transition,
|
||||
animationEndEvent,
|
||||
transitionEndEvent
|
||||
} from './detect'
|
||||
|
||||
var effectDir = Anot.directive('effect', {
|
||||
priority: 5,
|
||||
diff: function(effect) {
|
||||
var vdom = this.node
|
||||
if (typeof effect === 'string') {
|
||||
this.value = effect = {
|
||||
is: effect
|
||||
}
|
||||
Anot.warn('ms-effect的指令值不再支持字符串,必须是一个对象')
|
||||
}
|
||||
this.value = vdom.effect = effect
|
||||
var ok = cssDiff.call(this, effect, this.oldValue)
|
||||
var me = this
|
||||
if (ok) {
|
||||
setTimeout(function() {
|
||||
vdom.animating = true
|
||||
effectDir.update.call(me, vdom, vdom.effect)
|
||||
})
|
||||
vdom.animating = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
update: function(vdom, change, opts) {
|
||||
var dom = vdom.dom
|
||||
if (dom && dom.nodeType === 1) {
|
||||
//要求配置对象必须指定is属性,action必须是布尔或enter,leave,move
|
||||
var option = change || opts
|
||||
var is = option.is
|
||||
|
||||
var globalOption = Anot.effects[is]
|
||||
if (!globalOption) {
|
||||
//如果没有定义特效
|
||||
Anot.warn(is + ' effect is undefined')
|
||||
return
|
||||
}
|
||||
var finalOption = {}
|
||||
var action = actionMaps[option.action]
|
||||
if (typeof Effect.prototype[action] !== 'function') {
|
||||
Anot.warn('action is undefined')
|
||||
return
|
||||
}
|
||||
//必须预定义特效
|
||||
|
||||
var effect = new Anot.Effect(dom)
|
||||
Anot.mix(finalOption, globalOption, option, { action })
|
||||
|
||||
if (finalOption.queue) {
|
||||
animationQueue.push(function() {
|
||||
effect[action](finalOption)
|
||||
})
|
||||
callNextAnimation()
|
||||
} else {
|
||||
effect[action](finalOption)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let move = 'move'
|
||||
let leave = 'leave'
|
||||
let enter = 'enter'
|
||||
var actionMaps = {
|
||||
true: enter,
|
||||
false: leave,
|
||||
enter,
|
||||
leave,
|
||||
move,
|
||||
undefined: enter
|
||||
}
|
||||
|
||||
var animationQueue = []
|
||||
export function callNextAnimation() {
|
||||
var fn = animationQueue[0]
|
||||
if (fn) {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
Anot.effects = {}
|
||||
Anot.effect = function(name, opts) {
|
||||
var definition = (Anot.effects[name] = opts || {})
|
||||
if (css3 && definition.css !== false) {
|
||||
patchObject(definition, 'enterClass', name + '-enter')
|
||||
patchObject(
|
||||
definition,
|
||||
'enterActiveClass',
|
||||
definition.enterClass + '-active'
|
||||
)
|
||||
patchObject(definition, 'leaveClass', name + '-leave')
|
||||
patchObject(
|
||||
definition,
|
||||
'leaveActiveClass',
|
||||
definition.leaveClass + '-active'
|
||||
)
|
||||
}
|
||||
return definition
|
||||
}
|
||||
|
||||
function patchObject(obj, name, value) {
|
||||
if (!obj[name]) {
|
||||
obj[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
var Effect = function(dom) {
|
||||
this.dom = dom
|
||||
}
|
||||
|
||||
Anot.Effect = Effect
|
||||
|
||||
Effect.prototype = {
|
||||
enter: createAction('Enter'),
|
||||
leave: createAction('Leave'),
|
||||
move: createAction('Move')
|
||||
}
|
||||
|
||||
function execHooks(options, name, el) {
|
||||
var fns = [].concat(options[name])
|
||||
for (var i = 0, fn; (fn = fns[i++]); ) {
|
||||
if (typeof fn === 'function') {
|
||||
fn(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
var staggerCache = new Cache(128)
|
||||
|
||||
function createAction(action) {
|
||||
var lower = action.toLowerCase()
|
||||
return function(option) {
|
||||
var dom = this.dom
|
||||
var elem = Anot(dom)
|
||||
//处理与ms-for指令相关的stagger
|
||||
//========BEGIN=====
|
||||
var staggerTime = isFinite(option.stagger) ? option.stagger * 1000 : 0
|
||||
if (staggerTime) {
|
||||
if (option.staggerKey) {
|
||||
var stagger =
|
||||
staggerCache.get(option.staggerKey) ||
|
||||
staggerCache.put(option.staggerKey, {
|
||||
count: 0,
|
||||
items: 0
|
||||
})
|
||||
stagger.count++
|
||||
stagger.items++
|
||||
}
|
||||
}
|
||||
var staggerIndex = (stagger && stagger.count) || 0
|
||||
//=======END==========
|
||||
var stopAnimationID
|
||||
var animationDone = function(e) {
|
||||
var isOk = e !== false
|
||||
if (--dom.__ms_effect_ === 0) {
|
||||
Anot.unbind(dom, transitionEndEvent)
|
||||
Anot.unbind(dom, animationEndEvent)
|
||||
}
|
||||
clearTimeout(stopAnimationID)
|
||||
var dirWord = isOk ? 'Done' : 'Abort'
|
||||
execHooks(option, 'on' + action + dirWord, dom)
|
||||
if (stagger) {
|
||||
if (--stagger.items === 0) {
|
||||
stagger.count = 0
|
||||
}
|
||||
}
|
||||
if (option.queue) {
|
||||
animationQueue.shift()
|
||||
callNextAnimation()
|
||||
}
|
||||
}
|
||||
//执行开始前的钩子
|
||||
execHooks(option, 'onBefore' + action, dom)
|
||||
|
||||
if (option[lower]) {
|
||||
//使用JS方式执行动画
|
||||
option[lower](dom, function(ok) {
|
||||
animationDone(ok !== false)
|
||||
})
|
||||
} else if (css3) {
|
||||
//使用CSS3方式执行动画
|
||||
elem.addClass(option[lower + 'Class'])
|
||||
elem.removeClass(getNeedRemoved(option, lower))
|
||||
|
||||
if (!dom.__ms_effect_) {
|
||||
//绑定动画结束事件
|
||||
elem.bind(transitionEndEvent, animationDone)
|
||||
elem.bind(animationEndEvent, animationDone)
|
||||
dom.__ms_effect_ = 1
|
||||
} else {
|
||||
dom.__ms_effect_++
|
||||
}
|
||||
setTimeout(function() {
|
||||
//用xxx-active代替xxx类名的方式 触发CSS3动画
|
||||
var time = Anot.root.offsetWidth === NaN
|
||||
elem.addClass(option[lower + 'ActiveClass'])
|
||||
//计算动画时长
|
||||
time = getAnimationTime(dom)
|
||||
if (!time === 0) {
|
||||
//立即结束动画
|
||||
animationDone(false)
|
||||
} else if (!staggerTime) {
|
||||
//如果动画超出时长还没有调用结束事件,这可能是元素被移除了
|
||||
//如果强制结束动画
|
||||
stopAnimationID = setTimeout(function() {
|
||||
animationDone(false)
|
||||
}, time + 32)
|
||||
}
|
||||
}, 17 + staggerTime * staggerIndex) // = 1000/60
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Anot.applyEffect = function(dom, vdom, opts) {
|
||||
var cb = opts.cb
|
||||
var curEffect = vdom.effect
|
||||
if (curEffect && dom && dom.nodeType === 1) {
|
||||
var hook = opts.hook
|
||||
var old = curEffect[hook]
|
||||
if (cb) {
|
||||
if (Array.isArray(old)) {
|
||||
old.push(cb)
|
||||
} else if (old) {
|
||||
curEffect[hook] = [old, cb]
|
||||
} else {
|
||||
curEffect[hook] = [cb]
|
||||
}
|
||||
}
|
||||
getAction(opts)
|
||||
Anot.directives.effect.update(vdom, curEffect, Anot.shadowCopy({}, opts))
|
||||
} else if (cb) {
|
||||
cb(dom)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取方向
|
||||
*/
|
||||
export function getAction(opts) {
|
||||
if (!opts.action) {
|
||||
return (opts.action = opts.hook
|
||||
.replace(/^on/, '')
|
||||
.replace(/Done$/, '')
|
||||
.toLowerCase())
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 需要移除的类名
|
||||
*/
|
||||
function getNeedRemoved(options, name) {
|
||||
var name = name === 'leave' ? 'enter' : 'leave'
|
||||
return Array(name + 'Class', name + 'ActiveClass')
|
||||
.map(function(cls) {
|
||||
return options[cls]
|
||||
})
|
||||
.join(' ')
|
||||
}
|
||||
/**
|
||||
* 计算动画长度
|
||||
*/
|
||||
var transitionDuration = Anot.cssName('transition-duration')
|
||||
var animationDuration = Anot.cssName('animation-duration')
|
||||
var rsecond = /\d+s$/
|
||||
export function toMillisecond(str) {
|
||||
var ratio = rsecond.test(str) ? 1000 : 1
|
||||
return parseFloat(str) * ratio
|
||||
}
|
||||
|
||||
export function getAnimationTime(dom) {
|
||||
var computedStyles = window.getComputedStyle(dom, null)
|
||||
var tranDuration = computedStyles[transitionDuration]
|
||||
var animDuration = computedStyles[animationDuration]
|
||||
return toMillisecond(tranDuration) || toMillisecond(animDuration)
|
||||
}
|
||||
/**
|
||||
*
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="dist/Anot.js"></script>
|
||||
<script>
|
||||
Anot.effect('animate')
|
||||
var vm = Anot.define({
|
||||
$id: 'ani',
|
||||
a: true
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
.animate-enter, .animate-leave{
|
||||
width:100px;
|
||||
height:100px;
|
||||
background: #29b6f6;
|
||||
transition:all 2s;
|
||||
-moz-transition: all 2s;
|
||||
-webkit-transition: all 2s;
|
||||
-o-transition:all 2s;
|
||||
}
|
||||
.animate-enter-active, .animate-leave{
|
||||
width:300px;
|
||||
height:300px;
|
||||
}
|
||||
.animate-leave-active{
|
||||
width:100px;
|
||||
height:100px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div :controller='ani' >
|
||||
<p><input type='button' value='click' :click='@a =!@a'></p>
|
||||
<div :effect="{is:'animate',action:@a}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
*
|
||||
*/
|
|
@ -0,0 +1,180 @@
|
|||
import { Anot } from '../seed/core'
|
||||
import { $$skipArray } from '../vmodel/reserved'
|
||||
|
||||
/*
|
||||
https://github.com/hufyhang/orderBy/blob/master/index.js
|
||||
*/
|
||||
|
||||
export function orderBy(array, by, decend) {
|
||||
var type = Anot.type(array)
|
||||
if (type !== 'array' && type !== 'object') throw 'orderBy只能处理对象或数组'
|
||||
var criteria =
|
||||
typeof by == 'string'
|
||||
? function(el) {
|
||||
return el && el[by]
|
||||
}
|
||||
: typeof by === 'function'
|
||||
? by
|
||||
: function(el) {
|
||||
return el
|
||||
}
|
||||
var mapping = {}
|
||||
var temp = []
|
||||
__repeat(array, Array.isArray(array), function(key) {
|
||||
var val = array[key]
|
||||
var k = criteria(val, key)
|
||||
if (k in mapping) {
|
||||
mapping[k].push(key)
|
||||
} else {
|
||||
mapping[k] = [key]
|
||||
}
|
||||
temp.push(k)
|
||||
})
|
||||
|
||||
temp.sort()
|
||||
if (decend < 0) {
|
||||
temp.reverse()
|
||||
}
|
||||
var _array = type === 'array'
|
||||
var target = _array ? [] : {}
|
||||
return recovery(target, temp, function(k) {
|
||||
var key = mapping[k].shift()
|
||||
if (_array) {
|
||||
target.push(array[key])
|
||||
} else {
|
||||
target[key] = array[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function __repeat(array, isArray, cb) {
|
||||
if (isArray) {
|
||||
array.forEach(function(val, index) {
|
||||
cb(index)
|
||||
})
|
||||
} else if (typeof array.$track === 'string') {
|
||||
array.$track.replace(/[^☥]+/g, function(k) {
|
||||
cb(k)
|
||||
})
|
||||
} else {
|
||||
for (var i in array) {
|
||||
if (array.hasOwnProperty(i)) {
|
||||
cb(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export function filterBy(array, search) {
|
||||
var type = Anot.type(array)
|
||||
if (type !== 'array' && type !== 'object') throw 'filterBy只能处理对象或数组'
|
||||
var args = Anot.slice(arguments, 2)
|
||||
var stype = Anot.type(search)
|
||||
if (stype === 'function') {
|
||||
var criteria = search._orig || search
|
||||
} else if (stype === 'string' || stype === 'number') {
|
||||
if (search === '') {
|
||||
return array
|
||||
} else {
|
||||
var reg = new RegExp(Anot.escapeRegExp(search), 'i')
|
||||
criteria = function(el) {
|
||||
return reg.test(el)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return array
|
||||
}
|
||||
var isArray = type === 'array'
|
||||
var target = isArray ? [] : {}
|
||||
__repeat(array, isArray, function(key) {
|
||||
var val = array[key]
|
||||
if (
|
||||
criteria.apply(
|
||||
{
|
||||
key: key
|
||||
},
|
||||
[val, key].concat(args)
|
||||
)
|
||||
) {
|
||||
if (isArray) {
|
||||
target.push(val)
|
||||
} else {
|
||||
target[key] = val
|
||||
}
|
||||
}
|
||||
})
|
||||
return target
|
||||
}
|
||||
|
||||
export function selectBy(data, array, defaults) {
|
||||
if (Anot.isObject(data) && !Array.isArray(data)) {
|
||||
var target = []
|
||||
return recovery(target, array, function(name) {
|
||||
target.push(
|
||||
data.hasOwnProperty(name) ? data[name] : defaults ? defaults[name] : ''
|
||||
)
|
||||
})
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
export function limitBy(input, limit, begin) {
|
||||
var type = Anot.type(input)
|
||||
if (type !== 'array' && type !== 'object') throw 'limitBy只能处理对象或数组'
|
||||
//必须是数值
|
||||
if (typeof limit !== 'number') {
|
||||
return input
|
||||
}
|
||||
//不能为NaN
|
||||
if (limit !== limit) {
|
||||
return input
|
||||
}
|
||||
//将目标转换为数组
|
||||
if (type === 'object') {
|
||||
input = convertArray(input, false)
|
||||
}
|
||||
var n = input.length
|
||||
limit = Math.floor(Math.min(n, limit))
|
||||
begin = typeof begin === 'number' ? begin : 0
|
||||
if (begin < 0) {
|
||||
begin = Math.max(0, n + begin)
|
||||
}
|
||||
var data = []
|
||||
for (var i = begin; i < n; i++) {
|
||||
if (data.length === limit) {
|
||||
break
|
||||
}
|
||||
data.push(input[i])
|
||||
}
|
||||
var isArray = type === 'array'
|
||||
if (isArray) {
|
||||
return data
|
||||
}
|
||||
var target = {}
|
||||
return recovery(target, data, function(el) {
|
||||
target[el.key] = el.value
|
||||
})
|
||||
}
|
||||
|
||||
function recovery(ret, array, callback) {
|
||||
for (var i = 0, n = array.length; i < n; i++) {
|
||||
callback(array[i])
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
//Chrome谷歌浏览器中js代码Array.sort排序的bug乱序解决办法
|
||||
//http://www.cnblogs.com/yzeng/p/3949182.html
|
||||
function convertArray(array, isArray) {
|
||||
var ret = [],
|
||||
i = 0
|
||||
__repeat(array, isArray, function(key) {
|
||||
ret[i] = {
|
||||
oldIndex: i,
|
||||
value: array[key],
|
||||
key: key
|
||||
}
|
||||
i++
|
||||
})
|
||||
return ret
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
|
||||
'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
|
||||
'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
|
||||
'MMMM': Month in year (January-December)
|
||||
'MMM': Month in year (Jan-Dec)
|
||||
'MM': Month in year, padded (01-12)
|
||||
'M': Month in year (1-12)
|
||||
'dd': Day in month, padded (01-31)
|
||||
'd': Day in month (1-31)
|
||||
'EEEE': Day in Week,(Sunday-Saturday)
|
||||
'EEE': Day in Week, (Sun-Sat)
|
||||
'HH': Hour in day, padded (00-23)
|
||||
'H': Hour in day (0-23)
|
||||
'hh': Hour in am/pm, padded (01-12)
|
||||
'h': Hour in am/pm, (1-12)
|
||||
'mm': Minute in hour, padded (00-59)
|
||||
'm': Minute in hour (0-59)
|
||||
'ss': Second in minute, padded (00-59)
|
||||
's': Second in minute (0-59)
|
||||
'a': am/pm marker
|
||||
'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200)
|
||||
format string can also be one of the following predefined localizable formats:
|
||||
|
||||
'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm)
|
||||
'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm)
|
||||
'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010)
|
||||
'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010
|
||||
'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010)
|
||||
'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10)
|
||||
'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm)
|
||||
'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm)
|
||||
*/
|
||||
|
||||
function toInt(str) {
|
||||
return parseInt(str, 10) || 0
|
||||
}
|
||||
|
||||
function padNumber(num, digits, trim) {
|
||||
var neg = ''
|
||||
/* istanbul ignore if*/
|
||||
if (num < 0) {
|
||||
neg = '-'
|
||||
num = -num
|
||||
}
|
||||
num = '' + num
|
||||
while (num.length < digits) num = '0' + num
|
||||
if (trim) num = num.substr(num.length - digits)
|
||||
return neg + num
|
||||
}
|
||||
|
||||
function dateGetter(name, size, offset, trim) {
|
||||
return function(date) {
|
||||
var value = date['get' + name]()
|
||||
if (offset > 0 || value > -offset) value += offset
|
||||
if (value === 0 && offset === -12) {
|
||||
/* istanbul ignore next*/
|
||||
value = 12
|
||||
}
|
||||
return padNumber(value, size, trim)
|
||||
}
|
||||
}
|
||||
|
||||
function dateStrGetter(name, shortForm) {
|
||||
return function(date, formats) {
|
||||
var value = date['get' + name]()
|
||||
var get = (shortForm ? 'SHORT' + name : name).toUpperCase()
|
||||
return formats[get][value]
|
||||
}
|
||||
}
|
||||
|
||||
function timeZoneGetter(date) {
|
||||
var zone = -1 * date.getTimezoneOffset()
|
||||
var paddedZone = zone >= 0 ? '+' : ''
|
||||
paddedZone +=
|
||||
padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
|
||||
padNumber(Math.abs(zone % 60), 2)
|
||||
return paddedZone
|
||||
}
|
||||
//取得上午下午
|
||||
function ampmGetter(date, formats) {
|
||||
return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]
|
||||
}
|
||||
var DATE_FORMATS = {
|
||||
yyyy: dateGetter('FullYear', 4),
|
||||
yy: dateGetter('FullYear', 2, 0, true),
|
||||
y: dateGetter('FullYear', 1),
|
||||
MMMM: dateStrGetter('Month'),
|
||||
MMM: dateStrGetter('Month', true),
|
||||
MM: dateGetter('Month', 2, 1),
|
||||
M: dateGetter('Month', 1, 1),
|
||||
dd: dateGetter('Date', 2),
|
||||
d: dateGetter('Date', 1),
|
||||
HH: dateGetter('Hours', 2),
|
||||
H: dateGetter('Hours', 1),
|
||||
hh: dateGetter('Hours', 2, -12),
|
||||
h: dateGetter('Hours', 1, -12),
|
||||
mm: dateGetter('Minutes', 2),
|
||||
m: dateGetter('Minutes', 1),
|
||||
ss: dateGetter('Seconds', 2),
|
||||
s: dateGetter('Seconds', 1),
|
||||
sss: dateGetter('Milliseconds', 3),
|
||||
EEEE: dateStrGetter('Day'),
|
||||
EEE: dateStrGetter('Day', true),
|
||||
a: ampmGetter,
|
||||
Z: timeZoneGetter
|
||||
}
|
||||
var rdateFormat = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/
|
||||
var raspnetjson = /^\/Date\((\d+)\)\/$/
|
||||
export function dateFilter(date, format) {
|
||||
var locate = dateFilter.locate,
|
||||
text = '',
|
||||
parts = [],
|
||||
fn,
|
||||
match
|
||||
format = format || 'mediumDate'
|
||||
format = locate[format] || format
|
||||
if (typeof date === 'string') {
|
||||
if (/^\d+$/.test(date)) {
|
||||
date = toInt(date)
|
||||
} else if (raspnetjson.test(date)) {
|
||||
date = +RegExp.$1
|
||||
} else {
|
||||
var trimDate = date.trim()
|
||||
var dateArray = [0, 0, 0, 0, 0, 0, 0]
|
||||
var oDate = new Date(0)
|
||||
//取得年月日
|
||||
trimDate = trimDate.replace(/^(\d+)\D(\d+)\D(\d+)/, function(_, a, b, c) {
|
||||
var array = c.length === 4 ? [c, a, b] : [a, b, c]
|
||||
dateArray[0] = toInt(array[0]) //年
|
||||
dateArray[1] = toInt(array[1]) - 1 //月
|
||||
dateArray[2] = toInt(array[2]) //日
|
||||
return ''
|
||||
})
|
||||
var dateSetter = oDate.setFullYear
|
||||
var timeSetter = oDate.setHours
|
||||
trimDate = trimDate.replace(/[T\s](\d+):(\d+):?(\d+)?\.?(\d)?/, function(
|
||||
_,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d
|
||||
) {
|
||||
dateArray[3] = toInt(a) //小时
|
||||
dateArray[4] = toInt(b) //分钟
|
||||
dateArray[5] = toInt(c) //秒
|
||||
if (d) {
|
||||
//毫秒
|
||||
dateArray[6] = Math.round(parseFloat('0.' + d) * 1000)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
var tzHour = 0
|
||||
var tzMin = 0
|
||||
trimDate = trimDate.replace(/Z|([+-])(\d\d):?(\d\d)/, function(
|
||||
z,
|
||||
symbol,
|
||||
c,
|
||||
d
|
||||
) {
|
||||
dateSetter = oDate.setUTCFullYear
|
||||
timeSetter = oDate.setUTCHours
|
||||
if (symbol) {
|
||||
tzHour = toInt(symbol + c)
|
||||
tzMin = toInt(symbol + d)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
dateArray[3] -= tzHour
|
||||
dateArray[4] -= tzMin
|
||||
dateSetter.apply(oDate, dateArray.slice(0, 3))
|
||||
timeSetter.apply(oDate, dateArray.slice(3))
|
||||
date = oDate
|
||||
}
|
||||
}
|
||||
if (typeof date === 'number') {
|
||||
date = new Date(date)
|
||||
}
|
||||
|
||||
while (format) {
|
||||
match = rdateFormat.exec(format)
|
||||
/* istanbul ignore else */
|
||||
if (match) {
|
||||
parts = parts.concat(match.slice(1))
|
||||
format = parts.pop()
|
||||
} else {
|
||||
parts.push(format)
|
||||
format = null
|
||||
}
|
||||
}
|
||||
parts.forEach(function(value) {
|
||||
fn = DATE_FORMATS[value]
|
||||
text += fn
|
||||
? fn(date, locate)
|
||||
: value.replace(/(^'|'$)/g, '').replace(/''/g, "'")
|
||||
})
|
||||
return text
|
||||
}
|
||||
|
||||
var locate = {
|
||||
AMPMS: {
|
||||
0: '上午',
|
||||
1: '下午'
|
||||
},
|
||||
DAY: {
|
||||
0: '星期日',
|
||||
1: '星期一',
|
||||
2: '星期二',
|
||||
3: '星期三',
|
||||
4: '星期四',
|
||||
5: '星期五',
|
||||
6: '星期六'
|
||||
},
|
||||
MONTH: {
|
||||
0: '1月',
|
||||
1: '2月',
|
||||
2: '3月',
|
||||
3: '4月',
|
||||
4: '5月',
|
||||
5: '6月',
|
||||
6: '7月',
|
||||
7: '8月',
|
||||
8: '9月',
|
||||
9: '10月',
|
||||
10: '11月',
|
||||
11: '12月'
|
||||
},
|
||||
SHORTDAY: {
|
||||
'0': '周日',
|
||||
'1': '周一',
|
||||
'2': '周二',
|
||||
'3': '周三',
|
||||
'4': '周四',
|
||||
'5': '周五',
|
||||
'6': '周六'
|
||||
},
|
||||
fullDate: 'y年M月d日EEEE',
|
||||
longDate: 'y年M月d日',
|
||||
medium: 'yyyy-M-d H:mm:ss',
|
||||
mediumDate: 'yyyy-M-d',
|
||||
mediumTime: 'H:mm:ss',
|
||||
short: 'yy-M-d ah:mm',
|
||||
shortDate: 'yy-M-d',
|
||||
shortTime: 'ah:mm'
|
||||
}
|
||||
locate.SHORTMONTH = locate.MONTH
|
||||
dateFilter.locate = locate
|
|
@ -0,0 +1,11 @@
|
|||
//https://github.com/teppeis/htmlspecialchars
|
||||
export function escapeFilter(str) {
|
||||
if (str == null) return ''
|
||||
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
var eventFilters = {
|
||||
stop: function(e) {
|
||||
e.stopPropagation()
|
||||
return e
|
||||
},
|
||||
prevent: function(e) {
|
||||
e.preventDefault()
|
||||
return e
|
||||
}
|
||||
}
|
||||
var keys = {
|
||||
esc: 27,
|
||||
tab: 9,
|
||||
enter: 13,
|
||||
space: 32,
|
||||
del: 46,
|
||||
up: 38,
|
||||
left: 37,
|
||||
right: 39,
|
||||
down: 40
|
||||
}
|
||||
for (var name in keys) {
|
||||
;(function(filter, key) {
|
||||
eventFilters[filter] = function(e) {
|
||||
if (e.which !== key) {
|
||||
e.$return = true
|
||||
}
|
||||
return e
|
||||
}
|
||||
})(name, keys[name])
|
||||
}
|
||||
|
||||
export { eventFilters }
|
|
@ -0,0 +1,72 @@
|
|||
import { Anot } from '../seed/core'
|
||||
|
||||
import { numberFilter } from './number'
|
||||
import { sanitizeFilter } from './sanitize'
|
||||
import { dateFilter } from './date'
|
||||
import { filterBy, orderBy, selectBy, limitBy } from './array'
|
||||
import { eventFilters } from './event'
|
||||
import { escapeFilter } from './escape'
|
||||
var filters = (Anot.filters = {})
|
||||
|
||||
Anot.composeFilters = function() {
|
||||
var args = arguments
|
||||
return function(value) {
|
||||
for (var i = 0, arr; (arr = args[i++]); ) {
|
||||
var name = arr[0]
|
||||
var filter = Anot.filters[name]
|
||||
if (typeof filter === 'function') {
|
||||
arr[0] = value
|
||||
try {
|
||||
value = filter.apply(0, arr)
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
Anot.escapeHtml = escapeFilter
|
||||
|
||||
Anot.mix(
|
||||
filters,
|
||||
{
|
||||
uppercase(str) {
|
||||
return String(str).toUpperCase()
|
||||
},
|
||||
lowercase(str) {
|
||||
return String(str).toLowerCase()
|
||||
},
|
||||
truncate(str, length, end) {
|
||||
//length,新字符串长度,truncation,新字符串的结尾的字段,返回新字符串
|
||||
if (!str) {
|
||||
return ''
|
||||
}
|
||||
str = String(str)
|
||||
if (isNaN(length)) {
|
||||
length = 30
|
||||
}
|
||||
end = typeof end === 'string' ? end : '...'
|
||||
return str.length > length
|
||||
? str.slice(0, length - end.length) + end /* istanbul ignore else*/
|
||||
: str
|
||||
},
|
||||
camelize: Anot.camelize,
|
||||
date: dateFilter,
|
||||
escape: escapeFilter,
|
||||
sanitize: sanitizeFilter,
|
||||
number: numberFilter,
|
||||
currency(amount, symbol, fractionSize) {
|
||||
return (
|
||||
(symbol || '\u00a5') +
|
||||
numberFilter(
|
||||
amount,
|
||||
isFinite(fractionSize) ? /* istanbul ignore else*/ fractionSize : 2
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ filterBy, orderBy, selectBy, limitBy },
|
||||
eventFilters
|
||||
)
|
||||
|
||||
export { Anot }
|
|
@ -0,0 +1,34 @@
|
|||
import { Anot } from '../seed/core'
|
||||
function toFixedFix(n, prec) {
|
||||
var k = Math.pow(10, prec)
|
||||
return '' + (Math.round(n * k) / k).toFixed(prec)
|
||||
}
|
||||
export function numberFilter(number, decimals, point, thousands) {
|
||||
//https://github.com/txgruppi/number_format
|
||||
//form http://phpjs.org/functions/number_format/
|
||||
//number 必需,要格式化的数字
|
||||
//decimals 可选,规定多少个小数位。
|
||||
//point 可选,规定用作小数点的字符串(默认为 . )。
|
||||
//thousands 可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。
|
||||
number = (number + '').replace(/[^0-9+\-Ee.]/g, '')
|
||||
var n = !isFinite(+number) ? 0 : +number,
|
||||
prec = !isFinite(+decimals) ? 3 : Math.abs(decimals),
|
||||
sep = typeof thousands === 'string' ? thousands : ',',
|
||||
dec = point || '.',
|
||||
s = ''
|
||||
|
||||
// Fix for IE parseFloat(0.55).toFixed(0) = 0;
|
||||
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.')
|
||||
if (s[0].length > 3) {
|
||||
s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep)
|
||||
}
|
||||
/** //好像没有用
|
||||
var s1 = s[1] || ''
|
||||
|
||||
if (s1.length < prec) {
|
||||
s1 += new Array(prec - s[1].length + 1).join('0')
|
||||
s[1] = s1
|
||||
}
|
||||
**/
|
||||
return s.join(dec)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
var rscripts = /<script[^>]*>([\S\s]*?)<\/script\s*>/gim
|
||||
var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g
|
||||
var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/gi
|
||||
var rsanitize = {
|
||||
a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/gi,
|
||||
img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/gi,
|
||||
form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/gi
|
||||
}
|
||||
|
||||
//https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
|
||||
// <a href="javasc
ript:alert('XSS')">chrome</a>
|
||||
// <a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">chrome</a>
|
||||
// <a href="jav ascript:alert('XSS');">IE67chrome</a>
|
||||
// <a href="jav	ascript:alert('XSS');">IE67chrome</a>
|
||||
// <a href="jav
ascript:alert('XSS');">IE67chrome</a>
|
||||
export function sanitizeFilter(str) {
|
||||
return str.replace(rscripts, '').replace(ropen, function(a, b) {
|
||||
var match = a.toLowerCase().match(/<(\w+)\s/)
|
||||
if (match) {
|
||||
//处理a标签的href属性,img标签的src属性,form标签的action属性
|
||||
var reg = rsanitize[match[1]]
|
||||
if (reg) {
|
||||
a = a.replace(reg, function(s, name, value) {
|
||||
var quote = value.charAt(0)
|
||||
return name + '=' + quote + 'javascript:void(0)' + quote // jshint ignore:line
|
||||
})
|
||||
}
|
||||
}
|
||||
return a.replace(ron, ' ').replace(/\s+/g, ' ') //移除onXXX事件
|
||||
})
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import { Recognizer } from './recognizer'
|
||||
|
||||
var dragRecognizer = {
|
||||
events: ['dragstart', 'drag', 'dragend'],
|
||||
touchstart: function(event) {
|
||||
Recognizer.start(event, Anot.noop)
|
||||
},
|
||||
touchmove: function(event) {
|
||||
Recognizer.move(event, function(pointer, touch) {
|
||||
var extra = {
|
||||
deltaX: pointer.deltaX,
|
||||
deltaY: pointer.deltaY,
|
||||
touch: touch,
|
||||
touchEvent: event,
|
||||
isVertical: pointer.isVertical
|
||||
}
|
||||
if (pointer.status === 'tapping' && pointer.distance > 10) {
|
||||
pointer.status = 'panning'
|
||||
Recognizer.fire(pointer.element, 'dragstart', extra)
|
||||
} else if (pointer.status === 'panning') {
|
||||
Recognizer.fire(pointer.element, 'drag', extra)
|
||||
}
|
||||
})
|
||||
|
||||
event.preventDefault()
|
||||
},
|
||||
touchend: function(event) {
|
||||
Recognizer.end(event, function(pointer, touch) {
|
||||
if (pointer.status === 'panning') {
|
||||
Recognizer.fire(pointer.element, 'dragend', {
|
||||
deltaX: pointer.deltaX,
|
||||
deltaY: pointer.deltaY,
|
||||
touch: touch,
|
||||
touchEvent: event,
|
||||
isVertical: pointer.isVertical
|
||||
})
|
||||
}
|
||||
})
|
||||
Recognizer.pointers = {}
|
||||
}
|
||||
}
|
||||
dragRecognizer.touchcancel = dragRecognizer.touchend
|
||||
|
||||
Recognizer.add('drag', dragRecognizer)
|
|
@ -0,0 +1,114 @@
|
|||
import { Recognizer } from './recognizer'
|
||||
//https://github.com/manuelstofer/pinchzoom
|
||||
var pinchRecognizer = {
|
||||
events: ['pinchstart', 'pinch', 'pinchin', 'pinchuot', 'pinchend'],
|
||||
getScale: function(x1, y1, x2, y2, x3, y3, x4, y4) {
|
||||
return Math.sqrt(
|
||||
(Math.pow(y4 - y3, 2) + Math.pow(x4 - x3, 2)) /
|
||||
(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2))
|
||||
)
|
||||
},
|
||||
getCommonAncestor: function(arr) {
|
||||
var el = arr[0],
|
||||
el2 = arr[1]
|
||||
while (el) {
|
||||
if (el.contains(el2) || el === el2) {
|
||||
return el
|
||||
}
|
||||
el = el.parentNode
|
||||
}
|
||||
return null
|
||||
},
|
||||
touchstart: function(event) {
|
||||
var pointers = Recognizer.pointers
|
||||
Recognizer.start(event, Anot.noop)
|
||||
var elements = []
|
||||
for (var p in pointers) {
|
||||
if (pointers[p].startTime) {
|
||||
elements.push(pointers[p].element)
|
||||
} else {
|
||||
delete pointers[p]
|
||||
}
|
||||
}
|
||||
pointers.elements = elements
|
||||
if (elements.length === 2) {
|
||||
pinchRecognizer.element = pinchRecognizer.getCommonAncestor(elements)
|
||||
Recognizer.fire(
|
||||
pinchRecognizer.getCommonAncestor(elements),
|
||||
'pinchstart',
|
||||
{
|
||||
scale: 1,
|
||||
touches: event.touches,
|
||||
touchEvent: event
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
touchmove: function(event) {
|
||||
if (pinchRecognizer.element && event.touches.length > 1) {
|
||||
var position = [],
|
||||
current = []
|
||||
for (var i = 0; i < event.touches.length; i++) {
|
||||
var touch = event.touches[i]
|
||||
var gesture = Recognizer.pointers[touch.identifier]
|
||||
position.push([gesture.startTouch.clientX, gesture.startTouch.clientY])
|
||||
current.push([touch.clientX, touch.clientY])
|
||||
}
|
||||
|
||||
var scale = pinchRecognizer.getScale(
|
||||
position[0][0],
|
||||
position[0][1],
|
||||
position[1][0],
|
||||
position[1][1],
|
||||
current[0][0],
|
||||
current[0][1],
|
||||
current[1][0],
|
||||
current[1][1]
|
||||
)
|
||||
pinchRecognizer.scale = scale
|
||||
Recognizer.fire(pinchRecognizer.element, 'pinch', {
|
||||
scale: scale,
|
||||
touches: event.touches,
|
||||
touchEvent: event
|
||||
})
|
||||
|
||||
if (scale > 1) {
|
||||
Recognizer.fire(pinchRecognizer.element, 'pinchout', {
|
||||
scale: scale,
|
||||
touches: event.touches,
|
||||
touchEvent: event
|
||||
})
|
||||
} else {
|
||||
Recognizer.fire(pinchRecognizer.element, 'pinchin', {
|
||||
scale: scale,
|
||||
touches: event.touches,
|
||||
touchEvent: event
|
||||
})
|
||||
}
|
||||
}
|
||||
event.preventDefault()
|
||||
},
|
||||
touchend: function(event) {
|
||||
if (pinchRecognizer.element) {
|
||||
Recognizer.fire(pinchRecognizer.element, 'pinchend', {
|
||||
scale: pinchRecognizer.scale,
|
||||
touches: event.touches,
|
||||
touchEvent: event
|
||||
})
|
||||
pinchRecognizer.element = null
|
||||
}
|
||||
Recognizer.end(event, Anot.noop)
|
||||
}
|
||||
}
|
||||
|
||||
pinchRecognizer.touchcancel = pinchRecognizer.touchend
|
||||
|
||||
Recognizer.add('pinch', pinchRecognizer)
|
||||
|
||||
/*
|
||||
*
|
||||
在iOS中事件分为三类:
|
||||
触摸事件:通过触摸、手势进行触发(例如手指点击、缩放)
|
||||
运动事件:通过加速器进行触发(例如手机晃动)
|
||||
远程控制事件:通过其他远程设备触发(例如耳机控制按钮)
|
||||
*/
|
|
@ -0,0 +1,60 @@
|
|||
import { Recognizer } from './recognizer'
|
||||
|
||||
var pressRecognizer = {
|
||||
events: ['longtap', 'doubletap'],
|
||||
cancelPress: function(pointer) {
|
||||
clearTimeout(pointer.pressingHandler)
|
||||
pointer.pressingHandler = null
|
||||
},
|
||||
touchstart: function(event) {
|
||||
Recognizer.start(event, function(pointer, touch) {
|
||||
pointer.pressingHandler = setTimeout(function() {
|
||||
if (pointer.status === 'tapping') {
|
||||
Recognizer.fire(event.target, 'longtap', {
|
||||
touch: touch,
|
||||
touchEvent: event
|
||||
})
|
||||
}
|
||||
pressRecognizer.cancelPress(pointer)
|
||||
}, 500)
|
||||
if (event.changedTouches.length !== 1) {
|
||||
pointer.status = 0
|
||||
}
|
||||
})
|
||||
},
|
||||
touchmove: function(event) {
|
||||
Recognizer.move(event, function(pointer) {
|
||||
if (pointer.distance > 10 && pointer.pressingHandler) {
|
||||
pressRecognizer.cancelPress(pointer)
|
||||
if (pointer.status === 'tapping') {
|
||||
pointer.status = 'panning'
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
touchend: function(event) {
|
||||
Recognizer.end(event, function(pointer, touch) {
|
||||
pressRecognizer.cancelPress(pointer)
|
||||
if (pointer.status === 'tapping') {
|
||||
pointer.lastTime = Date.now()
|
||||
if (
|
||||
pressRecognizer.lastTap &&
|
||||
pointer.lastTime - pressRecognizer.lastTap.lastTime < 300
|
||||
) {
|
||||
Recognizer.fire(pointer.element, 'doubletap', {
|
||||
touch: touch,
|
||||
touchEvent: event
|
||||
})
|
||||
}
|
||||
|
||||
pressRecognizer.lastTap = pointer
|
||||
}
|
||||
})
|
||||
},
|
||||
touchcancel: function(event) {
|
||||
Recognizer.end(event, function(pointer) {
|
||||
pressRecognizer.cancelPress(pointer)
|
||||
})
|
||||
}
|
||||
}
|
||||
Recognizer.add('press', pressRecognizer)
|
|
@ -0,0 +1,48 @@
|
|||
请使用**webpack**打包,它们不包含在核心库里
|
||||
|
||||
|
||||
这些手势是依赖于reconginzer模块
|
||||
用到什么就包含什么,比如你想用tap模块,
|
||||
|
||||
在src建立一个Anot.tap.js
|
||||
|
||||
在export前引入这个模块
|
||||
```javascript
|
||||
import { Anot } from './seed/core'
|
||||
import './seed/lang.compact'
|
||||
|
||||
|
||||
import './filters/index'
|
||||
import './dom/compact'
|
||||
|
||||
import './vtree/fromString'
|
||||
import './vtree/fromDOM'
|
||||
|
||||
import './vdom/compact'
|
||||
import './vmodel/compact'
|
||||
import './directives/compact'
|
||||
|
||||
import './renders/domRender'
|
||||
|
||||
import './effect/index.js'
|
||||
import './component/index'
|
||||
|
||||
import './gesture/tap'
|
||||
|
||||
export default Anot
|
||||
```
|
||||
|
||||
|
||||
|
||||
然后模仿buildIE6,建议一个buildTap的打包文件
|
||||
|
||||
最后`node buildtap` 就行了
|
||||
|
||||
```
|
||||
|
||||
|
||||
//你的业务代码
|
||||
|
||||
<div ms-on-drag="@drag">xxxx</div>
|
||||
|
||||
```
|
|
@ -0,0 +1,178 @@
|
|||
var ua = navigator.userAgent.toLowerCase()
|
||||
//http://stackoverflow.com/questions/9038625/detect-if-device-is-ios
|
||||
function iOSversion() {
|
||||
//https://developer.apple.com/library/prerelease/mac/releasenotes/General/WhatsNewInSafari/Articles/Safari_9.html
|
||||
//http://mp.weixin.qq.com/s?__biz=MzA3MDQ4MzQzMg==&mid=256900619&idx=1&sn=b29f84cff0b8d7b9742e5d8b3cd8f218&scene=1&srcid=1009F9l4gh9nZ7rcQJEhmf7Q#rd
|
||||
if (/iPad|iPhone|iPod/i.test(ua) && !window.MSStream) {
|
||||
if ('backdropFilter' in document.documentElement.style) {
|
||||
return 9
|
||||
}
|
||||
if (!!window.indexedDB) {
|
||||
return 8
|
||||
}
|
||||
if (!!window.SpeechSynthesisUtterance) {
|
||||
return 7
|
||||
}
|
||||
if (!!window.webkitAudioContext) {
|
||||
return 6
|
||||
}
|
||||
if (!!window.matchMedia) {
|
||||
return 5
|
||||
}
|
||||
if (!!window.history && 'pushState' in window.history) {
|
||||
return 4
|
||||
}
|
||||
return 3
|
||||
}
|
||||
return NaN
|
||||
}
|
||||
|
||||
var deviceIsAndroid = ua.indexOf('android') > 0
|
||||
var deviceIsIOS = iOSversion()
|
||||
Anot.gestureEvents = {}
|
||||
var Recognizer = (Anot.gestureHooks = {
|
||||
isAndroid: ua.indexOf('android') > 0,
|
||||
isIOS: iOSversion(),
|
||||
pointers: {},
|
||||
//以AOP切入touchstart, touchmove, touchend, touchcancel回调
|
||||
start: function(event, callback) {
|
||||
//touches是当前屏幕上所有触摸点的列表;
|
||||
//targetTouches是当前对象上所有触摸点的列表;
|
||||
//changedTouches是涉及当前事件的触摸点的列表。
|
||||
for (var i = 0; i < event.changedTouches.length; i++) {
|
||||
var touch = event.changedTouches[i]
|
||||
var pointer = {
|
||||
startTouch: mixLocations({}, touch),
|
||||
startTime: Date.now(),
|
||||
status: 'tapping',
|
||||
element: event.target
|
||||
}
|
||||
Recognizer.pointers[touch.identifier] = pointer
|
||||
callback(pointer, touch)
|
||||
}
|
||||
},
|
||||
move: function(event, callback) {
|
||||
for (var i = 0; i < event.changedTouches.length; i++) {
|
||||
var touch = event.changedTouches[i]
|
||||
var pointer = Recognizer.pointers[touch.identifier]
|
||||
if (!pointer) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!('lastTouch' in pointer)) {
|
||||
pointer.lastTouch = pointer.startTouch
|
||||
pointer.lastTime = pointer.startTime
|
||||
pointer.deltaX = pointer.deltaY = pointer.duration = pointer.distance = 0
|
||||
}
|
||||
|
||||
var time = Date.now() - pointer.lastTime
|
||||
|
||||
if (time > 0) {
|
||||
var RECORD_DURATION = 70
|
||||
if (time > RECORD_DURATION) {
|
||||
time = RECORD_DURATION
|
||||
}
|
||||
if (pointer.duration + time > RECORD_DURATION) {
|
||||
pointer.duration = RECORD_DURATION - time
|
||||
}
|
||||
|
||||
pointer.duration += time
|
||||
pointer.lastTouch = mixLocations({}, touch)
|
||||
|
||||
pointer.lastTime = Date.now()
|
||||
|
||||
pointer.deltaX = touch.clientX - pointer.startTouch.clientX
|
||||
pointer.deltaY = touch.clientY - pointer.startTouch.clientY
|
||||
var x = pointer.deltaX * pointer.deltaX
|
||||
var y = pointer.deltaY * pointer.deltaY
|
||||
pointer.distance = Math.sqrt(x + y)
|
||||
pointer.isVertical = x < y
|
||||
|
||||
callback(pointer, touch)
|
||||
}
|
||||
}
|
||||
},
|
||||
end: function(event, callback) {
|
||||
for (var i = 0; i < event.changedTouches.length; i++) {
|
||||
var touch = event.changedTouches[i],
|
||||
id = touch.identifier,
|
||||
pointer = Recognizer.pointers[id]
|
||||
|
||||
if (!pointer) continue
|
||||
|
||||
callback(pointer, touch)
|
||||
|
||||
delete Recognizer.pointers[id]
|
||||
}
|
||||
},
|
||||
//人工触发合成事件
|
||||
fire: function(elem, type, props) {
|
||||
if (elem) {
|
||||
var event = document.createEvent('Events')
|
||||
event.initEvent(type, true, true)
|
||||
Anot.mix(event, props)
|
||||
elem.dispatchEvent(event)
|
||||
}
|
||||
},
|
||||
//添加各种识别器
|
||||
add: function(name, recognizer) {
|
||||
function move(event) {
|
||||
recognizer.touchmove(event)
|
||||
}
|
||||
|
||||
function end(event) {
|
||||
recognizer.touchend(event)
|
||||
|
||||
document.removeEventListener('touchmove', move)
|
||||
|
||||
document.removeEventListener('touchend', end)
|
||||
|
||||
document.removeEventListener('touchcancel', cancel)
|
||||
}
|
||||
|
||||
function cancel(event) {
|
||||
recognizer.touchcancel(event)
|
||||
|
||||
document.removeEventListener('touchmove', move)
|
||||
|
||||
document.removeEventListener('touchend', end)
|
||||
|
||||
document.removeEventListener('touchcancel', cancel)
|
||||
}
|
||||
|
||||
recognizer.events.forEach(function(eventName) {
|
||||
Anot.gestureEvents[eventName] = 1
|
||||
Anot.eventHooks[eventName] = {
|
||||
fix: function(el, fn) {
|
||||
if (!el['touch-' + name]) {
|
||||
el['touch-' + name] = 1
|
||||
el.addEventListener('touchstart', function(event) {
|
||||
recognizer.touchstart(event)
|
||||
|
||||
document.addEventListener('touchmove', move)
|
||||
|
||||
document.addEventListener('touchend', end)
|
||||
|
||||
document.addEventListener('touchcancel', cancel)
|
||||
})
|
||||
}
|
||||
return fn
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
var locations = ['screenX', 'screenY', 'clientX', 'clientY', 'pageX', 'pageY']
|
||||
|
||||
// 复制 touch 对象上的有用属性到固定对象上
|
||||
function mixLocations(target, source) {
|
||||
if (source) {
|
||||
locations.forEach(function(key) {
|
||||
target[key] = source[key]
|
||||
})
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
export { Recognizer }
|
|
@ -0,0 +1,86 @@
|
|||
import { Recognizer } from './recognizer'
|
||||
|
||||
var rotateRecognizer = {
|
||||
events: ['rotate', 'rotatestart', 'rotateend'],
|
||||
getAngle180: function(p1, p2) {
|
||||
// 角度, 范围在{0-180}, 用来识别旋转角度
|
||||
var agl =
|
||||
Math.atan(((p2.pageY - p1.pageY) * -1) / (p2.pageX - p1.pageX)) *
|
||||
(180 / Math.PI)
|
||||
return parseInt(agl < 0 ? agl + 180 : agl, 10)
|
||||
},
|
||||
rotate: function(event, status) {
|
||||
var finger = rotateRecognizer.finger
|
||||
var endAngel = rotateRecognizer.getAngle180(
|
||||
rotateRecognizer.center,
|
||||
finger.lastTouch
|
||||
)
|
||||
var diff = rotateRecognizer.startAngel - endAngel
|
||||
var direction = diff > 0 ? 'right' : 'left'
|
||||
var count = 0
|
||||
var __rotation = ~~finger.element.__rotation
|
||||
while (Math.abs(diff - __rotation) > 90 && count++ < 50) {
|
||||
if (__rotation < 0) {
|
||||
diff -= 180
|
||||
} else {
|
||||
diff += 180
|
||||
}
|
||||
}
|
||||
var rotation = (finger.element.__rotation = __rotation = diff)
|
||||
rotateRecognizer.endAngel = endAngel
|
||||
var extra = {
|
||||
touch: event.changedTouches[0],
|
||||
touchEvent: event,
|
||||
rotation: rotation,
|
||||
direction: direction
|
||||
}
|
||||
if (status === 'end') {
|
||||
Recognizer.fire(finger.element, 'rotateend', extra)
|
||||
finger.element.__rotation = 0
|
||||
} else if (finger.status === 'tapping' && diff) {
|
||||
finger.status = 'panning'
|
||||
Recognizer.fire(finger.element, 'rotatestart', extra)
|
||||
} else {
|
||||
Recognizer.fire(finger.element, 'rotate', extra)
|
||||
}
|
||||
},
|
||||
touchstart: function(event) {
|
||||
var pointers = Recognizer.pointers
|
||||
Recognizer.start(event, Anot.noop)
|
||||
var finger
|
||||
for (var p in pointers) {
|
||||
if (pointers[p].startTime) {
|
||||
if (!finger) {
|
||||
finger = pointers[p]
|
||||
} else {
|
||||
//如果超过一个指头就中止旋转
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
rotateRecognizer.finger = finger
|
||||
var el = finger.element
|
||||
var docOff = Anot(el).offset()
|
||||
rotateRecognizer.center = {
|
||||
//求得元素的中心
|
||||
pageX: docOff.left + el.offsetWidth / 2,
|
||||
pageY: docOff.top + el.offsetHeight / 2
|
||||
}
|
||||
rotateRecognizer.startAngel = rotateRecognizer.getAngle180(
|
||||
rotateRecognizer.center,
|
||||
finger.startTouch
|
||||
)
|
||||
},
|
||||
touchmove: function(event) {
|
||||
Recognizer.move(event, Anot.noop)
|
||||
rotateRecognizer.rotate(event)
|
||||
},
|
||||
touchend: function(event) {
|
||||
rotateRecognizer.rotate(event, 'end')
|
||||
Recognizer.end(event, Anot.noop)
|
||||
}
|
||||
}
|
||||
|
||||
rotateRecognizer.touchcancel = rotateRecognizer.touchend
|
||||
|
||||
Recognizer.add('rotate', rotateRecognizer)
|
|
@ -0,0 +1,48 @@
|
|||
import { Recognizer } from './recognizer'
|
||||
|
||||
var swipeRecognizer = {
|
||||
events: ['swipe', 'swipeleft', 'swiperight', 'swipeup', 'swipedown'],
|
||||
getAngle: function(x, y) {
|
||||
return (Math.atan2(y, x) * 180) / Math.PI
|
||||
},
|
||||
getDirection: function(x, y) {
|
||||
if (Math.abs(x) >= Math.abs(y)) {
|
||||
return x < 0 ? 'left' : 'right'
|
||||
}
|
||||
return y < 0 ? 'up' : 'down'
|
||||
},
|
||||
touchstart: function(event) {
|
||||
Recognizer.start(event, Anot.noop)
|
||||
},
|
||||
touchmove: function(event) {
|
||||
Recognizer.move(event, Anot.noop)
|
||||
},
|
||||
touchend: function(event) {
|
||||
if (event.changedTouches.length !== 1) {
|
||||
return
|
||||
}
|
||||
Recognizer.end(event, function(pointer, touch) {
|
||||
var isflick =
|
||||
pointer.distance > 30 && pointer.distance / pointer.duration > 0.65
|
||||
if (isflick) {
|
||||
var extra = {
|
||||
deltaX: pointer.deltaX,
|
||||
deltaY: pointer.deltaY,
|
||||
touch: touch,
|
||||
touchEvent: event,
|
||||
direction: swipeRecognizer.getDirection(
|
||||
pointer.deltaX,
|
||||
pointer.deltaY
|
||||
),
|
||||
isVertical: pointer.isVertical
|
||||
}
|
||||
var target = pointer.element
|
||||
Recognizer.fire(target, 'swipe', extra)
|
||||
Recognizer.fire(target, 'swipe' + extra.direction, extra)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
swipeRecognizer.touchcancel = swipeRecognizer.touchend
|
||||
Recognizer.add('swipe', swipeRecognizer)
|
|
@ -0,0 +1,297 @@
|
|||
import { Recognizer } from './recognizer'
|
||||
var root = Anot.root
|
||||
var supportPointer = !!navigator.pointerEnabled || !!navigator.msPointerEnabled
|
||||
// 支持pointer的设备可用样式来取消click事件的300毫秒延迟
|
||||
if (supportPointer) {
|
||||
root.style.msTouchAction = root.style.touchAction = 'none'
|
||||
}
|
||||
var tapRecognizer = {
|
||||
events: ['tap'],
|
||||
touchBoundary: 10,
|
||||
tapDelay: 90,
|
||||
needClick: function(target) {
|
||||
//判定是否使用原生的点击事件, 否则使用sendClick方法手动触发一个人工的点击事件
|
||||
switch (target.nodeName.toLowerCase()) {
|
||||
case 'button':
|
||||
case 'select':
|
||||
case 'textarea':
|
||||
if (target.disabled) {
|
||||
return true
|
||||
}
|
||||
|
||||
break
|
||||
case 'input':
|
||||
// IOS6 pad 上选择文件,如果不是原生的click,弹出的选择界面尺寸错误
|
||||
if ((Recognizer.isIOS && target.type === 'file') || target.disabled) {
|
||||
return true
|
||||
}
|
||||
|
||||
break
|
||||
case 'label':
|
||||
case 'iframe':
|
||||
case 'video':
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
needFocus: function(target) {
|
||||
switch (target.nodeName.toLowerCase()) {
|
||||
case 'textarea':
|
||||
case 'select': //实测android下select也需要
|
||||
return true
|
||||
case 'input':
|
||||
switch (target.type) {
|
||||
case 'button':
|
||||
case 'checkbox':
|
||||
case 'file':
|
||||
case 'image':
|
||||
case 'radio':
|
||||
case 'submit':
|
||||
return false
|
||||
}
|
||||
//如果是只读或disabled状态,就无须获得焦点了
|
||||
return !target.disabled && !target.readOnly
|
||||
default:
|
||||
return false
|
||||
}
|
||||
},
|
||||
focus: function(targetElement) {
|
||||
var length
|
||||
//在iOS7下, 对一些新表单元素(如date, datetime, time, month)调用focus方法会抛错,
|
||||
//幸好的是,我们可以改用setSelectionRange获取焦点, 将光标挪到文字的最后
|
||||
var type = targetElement.type
|
||||
if (
|
||||
Recognizer.isIOS &&
|
||||
targetElement.setSelectionRange &&
|
||||
type.indexOf('date') !== 0 &&
|
||||
type !== 'time' &&
|
||||
type !== 'month'
|
||||
) {
|
||||
length = targetElement.value.length
|
||||
targetElement.setSelectionRange(length, length)
|
||||
} else {
|
||||
targetElement.focus()
|
||||
}
|
||||
},
|
||||
findControl: function(labelElement) {
|
||||
// 获取label元素所对应的表单元素
|
||||
// 可以能过control属性, getElementById, 或用querySelector直接找其内部第一表单元素实现
|
||||
if (labelElement.control !== undefined) {
|
||||
return labelElement.control
|
||||
}
|
||||
|
||||
if (labelElement.htmlFor) {
|
||||
return document.getElementById(labelElement.htmlFor)
|
||||
}
|
||||
|
||||
return labelElement.querySelector(
|
||||
'button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea'
|
||||
)
|
||||
},
|
||||
fixTarget: function(target) {
|
||||
if (target.nodeType === 3) {
|
||||
return target.parentNode
|
||||
}
|
||||
if (window.SVGElementInstance && target instanceof SVGElementInstance) {
|
||||
return target.correspondingUseElement
|
||||
}
|
||||
|
||||
return target
|
||||
},
|
||||
updateScrollParent: function(targetElement) {
|
||||
//如果事件源元素位于某一个有滚动条的祖父元素中,那么保持其scrollParent与scrollTop值
|
||||
var scrollParent = targetElement.tapScrollParent
|
||||
|
||||
if (!scrollParent || !scrollParent.contains(targetElement)) {
|
||||
var parentElement = targetElement
|
||||
do {
|
||||
if (parentElement.scrollHeight > parentElement.offsetHeight) {
|
||||
scrollParent = parentElement
|
||||
targetElement.tapScrollParent = parentElement
|
||||
break
|
||||
}
|
||||
|
||||
parentElement = parentElement.parentElement
|
||||
} while (parentElement)
|
||||
}
|
||||
|
||||
if (scrollParent) {
|
||||
scrollParent.lastScrollTop = scrollParent.scrollTop
|
||||
}
|
||||
},
|
||||
touchHasMoved: function(event) {
|
||||
//判定是否发生移动,其阀值是10px
|
||||
var touch = event.changedTouches[0],
|
||||
boundary = tapRecognizer.touchBoundary
|
||||
return (
|
||||
Math.abs(touch.pageX - tapRecognizer.pageX) > boundary ||
|
||||
Math.abs(touch.pageY - tapRecognizer.pageY) > boundary
|
||||
)
|
||||
},
|
||||
|
||||
findType: function(targetElement) {
|
||||
// 安卓chrome浏览器上,模拟的 click 事件不能让 select 打开,故使用 mousedown 事件
|
||||
return Recognizer.isAndroid &&
|
||||
targetElement.tagName.toLowerCase() === 'select'
|
||||
? 'mousedown'
|
||||
: 'click'
|
||||
},
|
||||
sendClick: function(targetElement, event) {
|
||||
// 在click之前触发tap事件
|
||||
Recognizer.fire(targetElement, 'tap', {
|
||||
touchEvent: event
|
||||
})
|
||||
var clickEvent, touch
|
||||
//某些安卓设备必须先移除焦点,之后模拟的click事件才能让新元素获取焦点
|
||||
if (document.activeElement && document.activeElement !== targetElement) {
|
||||
document.activeElement.blur()
|
||||
}
|
||||
|
||||
touch = event.changedTouches[0]
|
||||
// 手动触发点击事件,此时必须使用document.createEvent('MouseEvents')来创建事件
|
||||
// 及使用initMouseEvent来初始化它
|
||||
clickEvent = document.createEvent('MouseEvents')
|
||||
clickEvent.initMouseEvent(
|
||||
tapRecognizer.findType(targetElement),
|
||||
true,
|
||||
true,
|
||||
window,
|
||||
1,
|
||||
touch.screenX,
|
||||
touch.screenY,
|
||||
touch.clientX,
|
||||
touch.clientY,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
null
|
||||
)
|
||||
clickEvent.touchEvent = event
|
||||
targetElement.dispatchEvent(clickEvent)
|
||||
},
|
||||
touchstart: function(event) {
|
||||
//忽略多点触摸
|
||||
if (event.targetTouches.length !== 1) {
|
||||
return true
|
||||
}
|
||||
//修正事件源对象
|
||||
var targetElement = tapRecognizer.fixTarget(event.target)
|
||||
var touch = event.targetTouches[0]
|
||||
if (Recognizer.isIOS) {
|
||||
// 判断是否是点击文字,进行选择等操作,如果是,不需要模拟click
|
||||
var selection = window.getSelection()
|
||||
if (selection.rangeCount && !selection.isCollapsed) {
|
||||
return true
|
||||
}
|
||||
var id = touch.identifier
|
||||
//当 alert 或 confirm 时,点击其他地方,会触发touch事件,identifier相同,此事件应该被忽略
|
||||
if (
|
||||
id &&
|
||||
isFinite(tapRecognizer.lastTouchIdentifier) &&
|
||||
tapRecognizer.lastTouchIdentifier === id
|
||||
) {
|
||||
event.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
tapRecognizer.lastTouchIdentifier = id
|
||||
|
||||
tapRecognizer.updateScrollParent(targetElement)
|
||||
}
|
||||
//收集触摸点的信息
|
||||
tapRecognizer.status = 'tapping'
|
||||
tapRecognizer.startTime = Date.now()
|
||||
tapRecognizer.element = targetElement
|
||||
tapRecognizer.pageX = touch.pageX
|
||||
tapRecognizer.pageY = touch.pageY
|
||||
// 如果点击太快,阻止双击带来的放大收缩行为
|
||||
if (
|
||||
tapRecognizer.startTime - tapRecognizer.lastTime <
|
||||
tapRecognizer.tapDelay
|
||||
) {
|
||||
event.preventDefault()
|
||||
}
|
||||
},
|
||||
touchmove: function(event) {
|
||||
if (tapRecognizer.status !== 'tapping') {
|
||||
return true
|
||||
}
|
||||
// 如果事件源元素发生改变,或者发生了移动,那么就取消触发点击事件
|
||||
if (
|
||||
tapRecognizer.element !== tapRecognizer.fixTarget(event.target) ||
|
||||
tapRecognizer.touchHasMoved(event)
|
||||
) {
|
||||
tapRecognizer.status = tapRecognizer.element = 0
|
||||
}
|
||||
},
|
||||
touchend: function(event) {
|
||||
var targetElement = tapRecognizer.element
|
||||
var now = Date.now()
|
||||
//如果是touchstart与touchend相隔太久,可以认为是长按,那么就直接返回
|
||||
//或者是在touchstart, touchmove阶段,判定其不该触发点击事件,也直接返回
|
||||
if (
|
||||
!targetElement ||
|
||||
now - tapRecognizer.startTime > tapRecognizer.tapDelay
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
tapRecognizer.lastTime = now
|
||||
|
||||
var startTime = tapRecognizer.startTime
|
||||
tapRecognizer.status = tapRecognizer.startTime = 0
|
||||
|
||||
var targetTagName = targetElement.tagName.toLowerCase()
|
||||
if (targetTagName === 'label') {
|
||||
//尝试触发label上可能绑定的tap事件
|
||||
Recognizer.fire(targetElement, 'tap', {
|
||||
touchEvent: event
|
||||
})
|
||||
var forElement = tapRecognizer.findControl(targetElement)
|
||||
if (forElement) {
|
||||
tapRecognizer.focus(targetElement)
|
||||
targetElement = forElement
|
||||
}
|
||||
} else if (tapRecognizer.needFocus(targetElement)) {
|
||||
// 如果元素从touchstart到touchend经历时间过长,那么不应该触发点击事
|
||||
// 或者此元素是iframe中的input元素,那么它也无法获点焦点
|
||||
if (
|
||||
now - startTime > 100 ||
|
||||
(deviceIsIOS && window.top !== window && targetTagName === 'input')
|
||||
) {
|
||||
tapRecognizer.element = 0
|
||||
return false
|
||||
}
|
||||
|
||||
tapRecognizer.focus(targetElement)
|
||||
Recognizer.isAndroid && tapRecognizer.sendClick(targetElement, event)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (Recognizer.isIOS) {
|
||||
//如果它的父容器的滚动条发生改变,那么应该识别为划动或拖动事件,不应该触发点击事件
|
||||
var scrollParent = targetElement.tapScrollParent
|
||||
if (
|
||||
scrollParent &&
|
||||
scrollParent.lastScrollTop !== scrollParent.scrollTop
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
//如果这不是一个需要使用原生click的元素,则屏蔽原生事件,避免触发两次click
|
||||
if (!tapRecognizer.needClick(targetElement)) {
|
||||
event.preventDefault()
|
||||
// 触发一次模拟的click
|
||||
tapRecognizer.sendClick(targetElement, event)
|
||||
}
|
||||
},
|
||||
touchcancel: function() {
|
||||
tapRecognizer.startTime = tapRecognizer.element = 0
|
||||
}
|
||||
}
|
||||
|
||||
Recognizer.add('tap', tapRecognizer)
|
|
@ -0,0 +1,71 @@
|
|||
import { Anot, directives } from '../seed/core'
|
||||
export var eventMap = Anot.oneObject(
|
||||
'animationend,blur,change,input,' +
|
||||
'click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,' +
|
||||
'mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit',
|
||||
'on'
|
||||
)
|
||||
export function parseAttributes(dirs, tuple) {
|
||||
var node = tuple[0],
|
||||
uniq = {},
|
||||
bindings = []
|
||||
var hasIf = false
|
||||
for (var name in dirs) {
|
||||
var value = dirs[name]
|
||||
var arr = name.split('-')
|
||||
// ms-click
|
||||
if (name in node.props) {
|
||||
var attrName = name
|
||||
} else {
|
||||
attrName = ':' + name.slice(3)
|
||||
}
|
||||
if (eventMap[arr[1]]) {
|
||||
arr.splice(1, 0, 'on')
|
||||
}
|
||||
//ms-on-click
|
||||
if (arr[1] === 'on') {
|
||||
arr[3] = parseFloat(arr[3]) || 0
|
||||
}
|
||||
|
||||
var type = arr[1]
|
||||
if (type === 'controller' || type === 'important') continue
|
||||
if (directives[type]) {
|
||||
var binding = {
|
||||
type: type,
|
||||
param: arr[2],
|
||||
attrName: attrName,
|
||||
name: arr.join('-'),
|
||||
expr: value,
|
||||
priority: directives[type].priority || type.charCodeAt(0) * 100
|
||||
}
|
||||
if (type === 'if') {
|
||||
hasIf = true
|
||||
}
|
||||
if (type === 'on') {
|
||||
binding.priority += arr[3]
|
||||
}
|
||||
if (!uniq[binding.name]) {
|
||||
uniq[binding.name] = value
|
||||
bindings.push(binding)
|
||||
if (type === 'for') {
|
||||
return [Anot.mix(binding, tuple[3])]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bindings.sort(byPriority)
|
||||
|
||||
if (hasIf) {
|
||||
var ret = []
|
||||
for (var i = 0, el; (el = bindings[i++]); ) {
|
||||
ret.push(el)
|
||||
if (el.type === 'if') {
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
return bindings
|
||||
}
|
||||
export function byPriority(a, b) {
|
||||
return a.priority - b.priority
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
import { Anot, msie, Cache } from '../seed/core'
|
||||
import { clearString, stringPool, fill, rfill, dig } from '../vtree/clearString'
|
||||
|
||||
var keyMap = Anot.oneObject(
|
||||
'break,case,catch,continue,debugger,default,delete,do,else,false,' +
|
||||
'finally,for,function,if,in,instanceof,new,null,return,switch,this,' +
|
||||
'throw,true,try,typeof,var,void,while,with,' /* 关键字*/ +
|
||||
'abstract,boolean,byte,char,class,const,double,enum,export,extends,' +
|
||||
'final,float,goto,implements,import,int,interface,long,native,' +
|
||||
'package,private,protected,public,short,static,super,synchronized,' +
|
||||
'throws,transient,volatile,arguments'
|
||||
)
|
||||
|
||||
export var skipMap = Anot.mix(
|
||||
{
|
||||
Math: 1,
|
||||
Date: 1,
|
||||
$event: 1,
|
||||
window: 1,
|
||||
__vmodel__: 1,
|
||||
Anot: 1
|
||||
},
|
||||
keyMap
|
||||
)
|
||||
|
||||
var rvmKey = /(^|[^\w\u00c0-\uFFFF_])(@|##)(?=[$\w])/g
|
||||
var ruselessSp = /\s*(\.|\|)\s*/g
|
||||
var rshortCircuit = /\|\|/g
|
||||
var brackets = /\(([^)]*)\)/
|
||||
var rpipeline = /\|(?=\?\?)/
|
||||
var rregexp = /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/g
|
||||
var robjectProp = /\.[\w\.\$]+/g //对象的属性 el.xxx 中的xxx
|
||||
var robjectKey = /(\{|\,)\s*([\$\w]+)\s*:/g //对象的键名与冒号 {xxx:1,yyy: 2}中的xxx, yyy
|
||||
var rfilterName = /\|(\w+)/g
|
||||
var rlocalVar = /[$a-zA-Z_][$a-zA-Z0-9_]*/g
|
||||
|
||||
var exprCache = new Cache(300)
|
||||
|
||||
function addScopeForLocal(str) {
|
||||
return str.replace(robjectProp, dig).replace(rlocalVar, function(el) {
|
||||
if (!skipMap[el]) {
|
||||
return '__vmodel__.' + el
|
||||
}
|
||||
return el
|
||||
})
|
||||
}
|
||||
|
||||
export function addScope(expr, type) {
|
||||
var cacheKey = expr + ':' + type
|
||||
var cache = exprCache.get(cacheKey)
|
||||
if (cache) {
|
||||
return cache.slice(0)
|
||||
}
|
||||
|
||||
stringPool.map = {}
|
||||
//https://github.com/RubyLouvre/Anot/issues/1849
|
||||
var input = expr.replace(rregexp, function(a, b) {
|
||||
return b + dig(a.slice(b.length))
|
||||
}) //移除所有正则
|
||||
input = clearString(input) //移除所有字符串
|
||||
input = input
|
||||
.replace(rshortCircuit, dig) //移除所有短路运算符
|
||||
.replace(ruselessSp, '$1') //移除.|两端空白
|
||||
.replace(robjectKey, function(_, a, b) {
|
||||
//移除所有键名
|
||||
return a + dig(b) + ':' //比如 ms-widget="[{is:'ms-address-wrap', $id:'address'}]"这样极端的情况
|
||||
})
|
||||
.replace(rvmKey, '$1__vmodel__.') //转换@与##为__vmodel__
|
||||
.replace(rfilterName, function(a, b) {
|
||||
//移除所有过滤器的名字
|
||||
return '|' + dig(b)
|
||||
})
|
||||
input = addScopeForLocal(input) //在本地变量前添加__vmodel__
|
||||
|
||||
var filters = input.split(rpipeline) //根据管道符切割表达式
|
||||
var body = filters
|
||||
.shift()
|
||||
.replace(rfill, fill)
|
||||
.trim()
|
||||
if (/\?\?\d/.test(body)) {
|
||||
body = body.replace(rfill, fill)
|
||||
}
|
||||
if (filters.length) {
|
||||
filters = filters.map(function(filter) {
|
||||
var bracketArgs = ''
|
||||
filter = filter.replace(brackets, function(a, b) {
|
||||
if (/\S/.test(b)) {
|
||||
bracketArgs += ',' + b //还原字符串,正则,短路运算符
|
||||
}
|
||||
return ''
|
||||
})
|
||||
var arg = '[' + Anot.quote(filter.trim()) + bracketArgs + ']'
|
||||
return arg
|
||||
})
|
||||
filters = 'Anot.composeFilters(' + filters + ')(__value__)'
|
||||
filters = filters.replace(rfill, fill)
|
||||
} else {
|
||||
filters = ''
|
||||
}
|
||||
return exprCache.put(cacheKey, [body, filters])
|
||||
}
|
||||
var rhandleName = /^__vmodel__\.[$\w\.]+$/
|
||||
var rfixIE678 = /__vmodel__\.([^(]+)\(([^)]*)\)/
|
||||
export function makeHandle(body) {
|
||||
if (rhandleName.test(body)) {
|
||||
body = body + '($event)'
|
||||
}
|
||||
/* istanbul ignore if */
|
||||
if (msie < 9) {
|
||||
body = body.replace(rfixIE678, function(a, b, c) {
|
||||
return (
|
||||
'__vmodel__.' +
|
||||
b +
|
||||
'.call(__vmodel__' +
|
||||
(/\S/.test(c) ? ',' + c : '') +
|
||||
')'
|
||||
)
|
||||
})
|
||||
}
|
||||
return body
|
||||
}
|
||||
export function createGetter(expr, type) {
|
||||
var arr = addScope(expr, type),
|
||||
body
|
||||
if (!arr[1]) {
|
||||
body = arr[0]
|
||||
} else {
|
||||
body = arr[1].replace(/__value__\)$/, arr[0] + ')')
|
||||
}
|
||||
try {
|
||||
return new Function('__vmodel__', 'return ' + body + ';')
|
||||
/* istanbul ignore next */
|
||||
} catch (e) {
|
||||
Anot.log('parse getter: [', expr, body, ']error')
|
||||
return Anot.noop
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成表达式设值函数
|
||||
* @param {String} expr
|
||||
*/
|
||||
export function createSetter(expr, type) {
|
||||
var arr = addScope(expr, type)
|
||||
var body =
|
||||
'try{ ' + arr[0] + ' = __value__}catch(e){Anot.log(e, "in on dir")}'
|
||||
try {
|
||||
return new Function('__vmodel__', '__value__', body + ';')
|
||||
/* istanbul ignore next */
|
||||
} catch (e) {
|
||||
Anot.log('parse setter: ', expr, ' error')
|
||||
return Anot.noop
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import { Anot, config } from '../seed/core'
|
||||
import { addScope } from './index'
|
||||
var rimprovePriority = /[+-\?]/
|
||||
var rinnerValue = /__value__\)$/
|
||||
export function parseInterpolate(dir) {
|
||||
var rlineSp = /\n\r?/g
|
||||
var str = dir.nodeValue.trim().replace(rlineSp, '')
|
||||
var tokens = []
|
||||
do {
|
||||
//aaa{{@bbb}}ccc
|
||||
var index = str.indexOf(config.openTag)
|
||||
index = index === -1 ? str.length : index
|
||||
var value = str.slice(0, index)
|
||||
if (/\S/.test(value)) {
|
||||
tokens.push(Anot.quote(Anot._decode(value)))
|
||||
}
|
||||
str = str.slice(index + config.openTag.length)
|
||||
if (str) {
|
||||
index = str.indexOf(config.closeTag)
|
||||
var value = str.slice(0, index)
|
||||
var expr = Anot.unescapeHTML(value)
|
||||
if (/\|\s*\w/.test(expr)) {
|
||||
//如果存在过滤器,优化干掉
|
||||
var arr = addScope(expr, 'expr')
|
||||
if (arr[1]) {
|
||||
expr = arr[1].replace(rinnerValue, arr[0] + ')')
|
||||
}
|
||||
}
|
||||
if (rimprovePriority) {
|
||||
expr = '(' + expr + ')'
|
||||
}
|
||||
tokens.push(expr)
|
||||
|
||||
str = str.slice(index + config.closeTag.length)
|
||||
}
|
||||
} while (str.length)
|
||||
return [
|
||||
{
|
||||
expr: tokens.join('+'),
|
||||
name: 'expr',
|
||||
type: 'expr'
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import { Anot, inBrowser } from '../seed/core'
|
||||
|
||||
import { Action, protectedMenbers } from '../vmodel/Action'
|
||||
|
||||
/**
|
||||
* 一个directive装饰器
|
||||
* @returns {directive}
|
||||
*/
|
||||
// DirectiveDecorator(scope, binding, vdom, this)
|
||||
// Decorator(vm, options, callback)
|
||||
export function Directive(vm, binding, vdom, render) {
|
||||
var type = binding.type
|
||||
var decorator = Anot.directives[type]
|
||||
if (inBrowser) {
|
||||
var dom = Anot.vdom(vdom, 'toDOM')
|
||||
if (dom.nodeType === 1) {
|
||||
dom.removeAttribute(binding.attrName)
|
||||
}
|
||||
vdom.dom = dom
|
||||
}
|
||||
var callback = decorator.update
|
||||
? function(value) {
|
||||
if (!render.mount && /css|visible|duplex/.test(type)) {
|
||||
render.callbacks.push(function() {
|
||||
decorator.update.call(directive, directive.node, value)
|
||||
})
|
||||
} else {
|
||||
decorator.update.call(directive, directive.node, value)
|
||||
}
|
||||
}
|
||||
: Anot.noop
|
||||
for (var key in decorator) {
|
||||
binding[key] = decorator[key]
|
||||
}
|
||||
binding.node = vdom
|
||||
var directive = new Action(vm, binding, callback)
|
||||
if (directive.init) {
|
||||
//这里可能会重写node, callback, type, name
|
||||
directive.init()
|
||||
}
|
||||
directive.update()
|
||||
return directive
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
import {
|
||||
Anot,
|
||||
config,
|
||||
inBrowser,
|
||||
delayCompileNodes,
|
||||
directives
|
||||
} from '../seed/core'
|
||||
import { fromDOM } from '../vtree/fromDOM'
|
||||
import { fromString } from '../vtree/fromString'
|
||||
|
||||
import { VFragment } from '../vdom/VFragment'
|
||||
import { Directive } from './Directive'
|
||||
|
||||
import { orphanTag } from '../vtree/orphanTag'
|
||||
import { parseAttributes, eventMap } from '../parser/attributes'
|
||||
import { parseInterpolate } from '../parser/interpolate'
|
||||
|
||||
import { startWith, groupTree, dumpTree, getRange } from './share'
|
||||
|
||||
/**
|
||||
* 生成一个渲染器,并作为它第一个遇到的ms-controller对应的VM的$render属性
|
||||
* @param {String|DOM} node
|
||||
* @param {ViewModel|Undefined} vm
|
||||
* @param {Function|Undefined} beforeReady
|
||||
* @returns {Render}
|
||||
*/
|
||||
Anot.scan = function(node, vm, beforeReady) {
|
||||
return new Render(node, vm, beforeReady || Anot.noop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Anot.scan 的内部实现
|
||||
*/
|
||||
function Render(node, vm, beforeReady) {
|
||||
this.root = node //如果传入的字符串,确保只有一个标签作为根节点
|
||||
this.vm = vm
|
||||
this.beforeReady = beforeReady
|
||||
this.bindings = [] //收集待加工的绑定属性
|
||||
this.callbacks = []
|
||||
this.directives = []
|
||||
this.init()
|
||||
}
|
||||
|
||||
Render.prototype = {
|
||||
/**
|
||||
* 开始扫描指定区域
|
||||
* 收集绑定属性
|
||||
* 生成指令并建立与VM的关联
|
||||
*/
|
||||
init() {
|
||||
var vnodes
|
||||
if (this.root && this.root.nodeType > 0) {
|
||||
vnodes = fromDOM(this.root) //转换虚拟DOM
|
||||
//将扫描区域的每一个节点与其父节点分离,更少指令对DOM操作时,对首屏输出造成的频繁重绘
|
||||
dumpTree(this.root)
|
||||
} else if (typeof this.root === 'string') {
|
||||
vnodes = fromString(this.root) //转换虚拟DOM
|
||||
} else {
|
||||
return Anot.warn('Anot.scan first argument must element or HTML string')
|
||||
}
|
||||
|
||||
this.root = vnodes[0]
|
||||
this.vnodes = vnodes
|
||||
this.scanChildren(vnodes, this.vm, true)
|
||||
},
|
||||
|
||||
scanChildren(children, scope, isRoot) {
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var vdom = children[i]
|
||||
switch (vdom.nodeName) {
|
||||
case '#text':
|
||||
scope && this.scanText(vdom, scope)
|
||||
break
|
||||
case '#comment':
|
||||
scope && this.scanComment(vdom, scope, children)
|
||||
break
|
||||
case '#document-fragment':
|
||||
this.scanChildren(vdom.children, scope, false)
|
||||
break
|
||||
default:
|
||||
this.scanTag(vdom, scope, children, false)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (isRoot) {
|
||||
this.complete()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 从文本节点获取指令
|
||||
* @param {type} vdom
|
||||
* @param {type} scope
|
||||
* @returns {undefined}
|
||||
*/
|
||||
scanText(vdom, scope) {
|
||||
if (config.rexpr.test(vdom.nodeValue)) {
|
||||
this.bindings.push([
|
||||
vdom,
|
||||
scope,
|
||||
{
|
||||
nodeValue: vdom.nodeValue
|
||||
}
|
||||
])
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 从注释节点获取指令
|
||||
* @param {type} vdom
|
||||
* @param {type} scope
|
||||
* @param {type} parentChildren
|
||||
* @returns {undefined}
|
||||
*/
|
||||
scanComment(vdom, scope, parentChildren) {
|
||||
if (startWith(vdom.nodeValue, 'ms-for:')) {
|
||||
this.getForBinding(vdom, scope, parentChildren)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 从元素节点的nodeName与属性中获取指令
|
||||
* @param {type} vdom
|
||||
* @param {type} scope
|
||||
* @param {type} parentChildren
|
||||
* @param {type} isRoot 用于执行complete方法
|
||||
* @returns {undefined}
|
||||
*/
|
||||
scanTag(vdom, scope, parentChildren, isRoot) {
|
||||
var dirs = {},
|
||||
attrs = vdom.props,
|
||||
hasDir,
|
||||
hasFor
|
||||
for (var attr in attrs) {
|
||||
var value = attrs[attr]
|
||||
var oldName = attr
|
||||
if (attr.charAt(0) === ':') {
|
||||
attr = 'ms-' + attr.slice(1)
|
||||
}
|
||||
if (startWith(attr, 'ms-')) {
|
||||
dirs[attr] = value
|
||||
var type = attr.match(/\w+/g)[1]
|
||||
type = eventMap[type] || type
|
||||
if (!directives[type]) {
|
||||
Anot.warn(attr + ' has not registered!')
|
||||
}
|
||||
hasDir = true
|
||||
}
|
||||
if (attr === 'ms-for') {
|
||||
hasFor = value
|
||||
delete attrs[oldName]
|
||||
}
|
||||
}
|
||||
var $id = dirs['ms-important'] || dirs['ms-controller']
|
||||
if ($id) {
|
||||
/**
|
||||
* 后端渲染
|
||||
* serverTemplates后端给Anot添加的对象,里面都是模板,
|
||||
* 将原来后端渲染好的区域再还原成原始样子,再被扫描
|
||||
*/
|
||||
var templateCaches = Anot.serverTemplates
|
||||
var temp = templateCaches && templateCaches[$id]
|
||||
if (temp) {
|
||||
Anot.log('前端再次渲染后端传过来的模板')
|
||||
var node = fromString(temp)[0]
|
||||
for (var i in node) {
|
||||
vdom[i] = node[i]
|
||||
}
|
||||
delete templateCaches[$id]
|
||||
this.scanTag(vdom, scope, parentChildren, isRoot)
|
||||
return
|
||||
}
|
||||
//推算出指令类型
|
||||
var type = dirs['ms-important'] === $id ? 'important' : 'controller'
|
||||
//推算出用户定义时属性名,是使用ms-属性还是:属性
|
||||
var attrName = 'ms-' + type in attrs ? 'ms-' + type : ':' + type
|
||||
|
||||
if (inBrowser) {
|
||||
delete attrs[attrName]
|
||||
}
|
||||
var dir = directives[type]
|
||||
scope = dir.getScope.call(this, $id, scope)
|
||||
if (!scope) {
|
||||
return
|
||||
} else {
|
||||
var clazz = attrs['class']
|
||||
if (clazz) {
|
||||
attrs['class'] = (' ' + clazz + ' ')
|
||||
.replace(' ms-controller ', '')
|
||||
.trim()
|
||||
}
|
||||
}
|
||||
var render = this
|
||||
scope.$render = render
|
||||
this.callbacks.push(function() {
|
||||
//用于删除ms-controller
|
||||
dir.update.call(render, vdom, attrName, $id)
|
||||
})
|
||||
}
|
||||
if (hasFor) {
|
||||
if (vdom.dom) {
|
||||
vdom.dom.removeAttribute(oldName)
|
||||
}
|
||||
return this.getForBindingByElement(vdom, scope, parentChildren, hasFor)
|
||||
}
|
||||
|
||||
if (/^ms\-/.test(vdom.nodeName)) {
|
||||
attrs.is = vdom.nodeName
|
||||
}
|
||||
|
||||
if (attrs['is']) {
|
||||
if (!dirs['ms-widget']) {
|
||||
dirs['ms-widget'] = '{}'
|
||||
}
|
||||
hasDir = true
|
||||
}
|
||||
if (hasDir) {
|
||||
this.bindings.push([vdom, scope, dirs])
|
||||
}
|
||||
var children = vdom.children
|
||||
//如果存在子节点,并且不是容器元素(script, stype, textarea, xmp...)
|
||||
if (
|
||||
!orphanTag[vdom.nodeName] &&
|
||||
children &&
|
||||
children.length &&
|
||||
!delayCompileNodes(dirs)
|
||||
) {
|
||||
this.scanChildren(children, scope, false)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 将绑定属性转换为指令
|
||||
* 执行各种回调与优化指令
|
||||
* @returns {undefined}
|
||||
*/
|
||||
complete() {
|
||||
this.yieldDirectives()
|
||||
this.beforeReady()
|
||||
if (inBrowser) {
|
||||
var root = this.root
|
||||
if (inBrowser) {
|
||||
var rootDom = Anot.vdom(root, 'toDOM')
|
||||
groupTree(rootDom, root.children)
|
||||
}
|
||||
}
|
||||
|
||||
this.mount = true
|
||||
var fn
|
||||
while ((fn = this.callbacks.pop())) {
|
||||
fn()
|
||||
}
|
||||
this.optimizeDirectives()
|
||||
},
|
||||
|
||||
/**
|
||||
* 将收集到的绑定属性进行深加工,最后转换指令
|
||||
* @returns {Array<tuple>}
|
||||
*/
|
||||
yieldDirectives() {
|
||||
var tuple
|
||||
while ((tuple = this.bindings.shift())) {
|
||||
var vdom = tuple[0],
|
||||
scope = tuple[1],
|
||||
dirs = tuple[2],
|
||||
bindings = []
|
||||
if ('nodeValue' in dirs) {
|
||||
bindings = parseInterpolate(dirs)
|
||||
} else if (!('ms-skip' in dirs)) {
|
||||
bindings = parseAttributes(dirs, tuple)
|
||||
}
|
||||
for (var i = 0, binding; (binding = bindings[i++]); ) {
|
||||
var dir = directives[binding.type]
|
||||
if (!inBrowser && /on|duplex|active|hover/.test(binding.type)) {
|
||||
continue
|
||||
}
|
||||
if (dir.beforeInit) {
|
||||
dir.beforeInit.call(binding)
|
||||
}
|
||||
|
||||
var directive = new Directive(scope, binding, vdom, this)
|
||||
this.directives.push(directive)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 修改指令的update与callback方法,让它们以后执行时更加高效
|
||||
* @returns {undefined}
|
||||
*/
|
||||
optimizeDirectives() {
|
||||
for (var i = 0, el; (el = this.directives[i++]); ) {
|
||||
el.callback = directives[el.type].update
|
||||
el.update = newUpdate
|
||||
el._isScheduled = false
|
||||
}
|
||||
},
|
||||
update: function() {
|
||||
for (var i = 0, el; (el = this.directives[i++]); ) {
|
||||
el.update()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 销毁所有指令
|
||||
* @returns {undefined}
|
||||
*/
|
||||
dispose() {
|
||||
var list = this.directives || []
|
||||
for (let i = 0, el; (el = list[i++]); ) {
|
||||
el.dispose()
|
||||
}
|
||||
//防止其他地方的this.innerRender && this.innerRender.dispose报错
|
||||
for (let i in this) {
|
||||
if (i !== 'dispose') delete this[i]
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 将循环区域转换为for指令
|
||||
* @param {type} begin 注释节点
|
||||
* @param {type} scope
|
||||
* @param {type} parentChildren
|
||||
* @param {type} userCb 循环结束回调
|
||||
* @returns {undefined}
|
||||
*/
|
||||
getForBinding(begin, scope, parentChildren, userCb) {
|
||||
var expr = begin.nodeValue.replace('ms-for:', '').trim()
|
||||
begin.nodeValue = 'ms-for:' + expr
|
||||
var nodes = getRange(parentChildren, begin)
|
||||
var end = nodes.end
|
||||
var fragment = Anot.vdom(nodes, 'toHTML')
|
||||
parentChildren.splice(nodes.start, nodes.length)
|
||||
begin.props = {}
|
||||
this.bindings.push([
|
||||
begin,
|
||||
scope,
|
||||
{
|
||||
'ms-for': expr
|
||||
},
|
||||
{
|
||||
begin,
|
||||
end,
|
||||
expr,
|
||||
userCb,
|
||||
fragment,
|
||||
parentChildren
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
/**
|
||||
* 在带ms-for元素节点旁添加两个注释节点,组成循环区域
|
||||
* @param {type} vdom
|
||||
* @param {type} scope
|
||||
* @param {type} parentChildren
|
||||
* @param {type} expr
|
||||
* @returns {undefined}
|
||||
*/
|
||||
getForBindingByElement(vdom, scope, parentChildren, expr) {
|
||||
var index = parentChildren.indexOf(vdom) //原来带ms-for的元素节点
|
||||
var props = vdom.props
|
||||
var begin = {
|
||||
nodeName: '#comment',
|
||||
nodeValue: 'ms-for:' + expr
|
||||
}
|
||||
if (props.slot) {
|
||||
begin.slot = props.slot
|
||||
delete props.slot
|
||||
}
|
||||
var end = {
|
||||
nodeName: '#comment',
|
||||
nodeValue: 'ms-for-end:'
|
||||
}
|
||||
parentChildren.splice(index, 1, begin, vdom, end)
|
||||
this.getForBinding(begin, scope, parentChildren, props['data-for-rendered'])
|
||||
}
|
||||
}
|
||||
var viewID
|
||||
|
||||
function newUpdate() {
|
||||
var oldVal = this.beforeUpdate()
|
||||
var newVal = (this.value = this.get())
|
||||
if (this.callback && this.diff(newVal, oldVal)) {
|
||||
this.callback(this.node, this.value)
|
||||
var vm = this.vm
|
||||
var $render = vm.$render
|
||||
var list = vm.$events['onViewChange']
|
||||
/* istanbul ignore if */
|
||||
if (list && $render && $render.root && !Anot.viewChanging) {
|
||||
if (viewID) {
|
||||
clearTimeout(viewID)
|
||||
viewID = null
|
||||
}
|
||||
viewID = setTimeout(function() {
|
||||
list.forEach(function(el) {
|
||||
el.callback.call(vm, {
|
||||
type: 'viewchange',
|
||||
target: $render.root,
|
||||
vmodel: vm
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
this._isScheduled = false
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
function serverRender(vm, str) {
|
||||
var nodes = Anot.lexer(str)
|
||||
var templates = {}
|
||||
collectTemplate(nodes, templates)
|
||||
var render = Anot.scan(str)
|
||||
var html = Anot.vdom(render.root, 'toHTML', false)
|
||||
return {
|
||||
templates: templates,
|
||||
html: html
|
||||
}
|
||||
}
|
||||
|
||||
function collectTemplate(vdoms, obj) {
|
||||
for (var i = 0, el; (el = vdoms[i++]); ) {
|
||||
var props = el.props
|
||||
if (props) {
|
||||
var id =
|
||||
props['ms-controller'] ||
|
||||
props[':controller'] ||
|
||||
props['ms-important'] ||
|
||||
props[':important']
|
||||
if (id) {
|
||||
obj[id] = Anot.VElement.prototype.toHTML.call(el, true)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (el.children) {
|
||||
collectTemplate(el.children, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof module === 'object') module.exports = serverRender
|
|
@ -0,0 +1,96 @@
|
|||
import { Anot, createFragment } from '../seed/core'
|
||||
import { lookupOption, getSelectedValue } from '../directives/duplex/option'
|
||||
|
||||
function getChildren(arr) {
|
||||
var count = 0
|
||||
for (var i = 0, el; (el = arr[i++]); ) {
|
||||
if (el.nodeName === '#document-fragment') {
|
||||
count += getChildren(el.children)
|
||||
} else {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
export function groupTree(parent, children) {
|
||||
children &&
|
||||
children.forEach(function(vdom) {
|
||||
if (!vdom) return
|
||||
var vlength = vdom.children && getChildren(vdom.children)
|
||||
if (vdom.nodeName === '#document-fragment') {
|
||||
var dom = createFragment()
|
||||
} else {
|
||||
dom = Anot.vdom(vdom, 'toDOM')
|
||||
var domlength = dom.childNodes && dom.childNodes.length
|
||||
if (domlength && vlength && domlength > vlength) {
|
||||
if (!appendChildMayThrowError[dom.nodeName]) {
|
||||
Anot.clearHTML(dom)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vlength) {
|
||||
groupTree(dom, vdom.children)
|
||||
if (vdom.nodeName === 'select') {
|
||||
var values = []
|
||||
getSelectedValue(vdom, values)
|
||||
lookupOption(vdom, values)
|
||||
}
|
||||
}
|
||||
//高级版本可以尝试 querySelectorAll
|
||||
|
||||
try {
|
||||
if (!appendChildMayThrowError[parent.nodeName]) {
|
||||
parent.appendChild(dom)
|
||||
}
|
||||
} catch (e) {}
|
||||
})
|
||||
}
|
||||
|
||||
export function dumpTree(elem) {
|
||||
if (elem) {
|
||||
var firstChild
|
||||
while ((firstChild = elem.firstChild)) {
|
||||
if (firstChild.nodeType === 1) {
|
||||
dumpTree(firstChild)
|
||||
}
|
||||
elem.removeChild(firstChild)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getRange(childNodes, node) {
|
||||
var i = childNodes.indexOf(node) + 1
|
||||
var deep = 1,
|
||||
nodes = [],
|
||||
end
|
||||
nodes.start = i
|
||||
while ((node = childNodes[i++])) {
|
||||
nodes.push(node)
|
||||
if (node.nodeName === '#comment') {
|
||||
if (startWith(node.nodeValue, 'ms-for:')) {
|
||||
deep++
|
||||
} else if (node.nodeValue === 'ms-for-end:') {
|
||||
deep--
|
||||
if (deep === 0) {
|
||||
end = node
|
||||
nodes.pop()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes.end = end
|
||||
return nodes
|
||||
}
|
||||
|
||||
export function startWith(long, short) {
|
||||
return long.indexOf(short) === 0
|
||||
}
|
||||
|
||||
var appendChildMayThrowError = {
|
||||
'#text': 1,
|
||||
'#comment': 1,
|
||||
script: 1,
|
||||
style: 1,
|
||||
noscript: 1
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
export let win =
|
||||
typeof window === 'object' ? window : typeof global === 'object' ? global : {}
|
||||
|
||||
export let inBrowser = !!win.location && win.navigator
|
||||
/* istanbul ignore if */
|
||||
|
||||
export let document = inBrowser
|
||||
? win.document
|
||||
: {
|
||||
createElement: Object,
|
||||
createElementNS: Object,
|
||||
documentElement: 'xx',
|
||||
contains: Boolean
|
||||
}
|
||||
export var root = inBrowser
|
||||
? document.documentElement
|
||||
: {
|
||||
outerHTML: 'x'
|
||||
}
|
||||
|
||||
let versions = {
|
||||
objectobject: 7, //IE7-8
|
||||
objectundefined: 6, //IE6
|
||||
undefinedfunction: NaN, // other modern browsers
|
||||
undefinedobject: NaN //Mobile Safari 8.0.0 (iOS 8.4.0)
|
||||
//objectfunction chrome 47
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
export var msie =
|
||||
document.documentMode || versions[typeof document.all + typeof XMLHttpRequest]
|
||||
|
||||
export var modern = /NaN|undefined/.test(msie) || msie > 8
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
https://github.com/rsms/js-lru
|
||||
entry entry entry entry
|
||||
______ ______ ______ ______
|
||||
| head |.newer => | |.newer => | |.newer => | tail |
|
||||
| A | | B | | C | | D |
|
||||
|______| <= older.|______| <= older.|______| <= older.|______|
|
||||
|
||||
removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added
|
||||
*/
|
||||
export function Cache(maxLength) {
|
||||
// 标识当前缓存数组的大小
|
||||
this.size = 0
|
||||
// 标识缓存数组能达到的最大长度
|
||||
this.limit = maxLength
|
||||
// head(最不常用的项),tail(最常用的项)全部初始化为undefined
|
||||
|
||||
this.head = this.tail = void 0
|
||||
this._keymap = {}
|
||||
}
|
||||
|
||||
Cache.prototype = {
|
||||
|
||||
put(key, value) {
|
||||
var entry = {
|
||||
key: key,
|
||||
value: value
|
||||
}
|
||||
this._keymap[key] = entry
|
||||
if (this.tail) {
|
||||
// 如果存在tail(缓存数组的长度不为0),将tail指向新的 entry
|
||||
this.tail.newer = entry
|
||||
entry.older = this.tail
|
||||
} else {
|
||||
// 如果缓存数组的长度为0,将head指向新的entry
|
||||
this.head = entry
|
||||
}
|
||||
this.tail = entry
|
||||
// 如果缓存数组达到上限,则先删除 head 指向的缓存对象
|
||||
/* istanbul ignore if */
|
||||
if (this.size === this.limit) {
|
||||
this.shift()
|
||||
} else {
|
||||
this.size++
|
||||
}
|
||||
return value
|
||||
},
|
||||
|
||||
shift() {
|
||||
/* istanbul ignore next */
|
||||
var entry = this.head
|
||||
/* istanbul ignore if */
|
||||
if (entry) {
|
||||
// 删除 head ,并改变指向
|
||||
this.head = this.head.newer
|
||||
// 同步更新 _keymap 里面的属性值
|
||||
this.head.older =
|
||||
entry.newer =
|
||||
entry.older =
|
||||
this._keymap[entry.key] =
|
||||
void 0
|
||||
delete this._keymap[entry.key] //#1029
|
||||
// 同步更新 缓存数组的长度
|
||||
this.size--
|
||||
}
|
||||
},
|
||||
|
||||
get(key) {
|
||||
var entry = this._keymap[key]
|
||||
// 如果查找不到含有`key`这个属性的缓存对象
|
||||
if (entry === void 0)
|
||||
return
|
||||
// 如果查找到的缓存对象已经是 tail (最近使用过的)
|
||||
/* istanbul ignore if */
|
||||
if (entry === this.tail) {
|
||||
return entry.value
|
||||
}
|
||||
// HEAD--------------TAIL
|
||||
// <.older .newer>
|
||||
// <--- add direction --
|
||||
// A B C <D> E
|
||||
if (entry.newer) {
|
||||
// 处理 newer 指向
|
||||
if (entry === this.head) {
|
||||
// 如果查找到的缓存对象是 head (最近最少使用过的)
|
||||
// 则将 head 指向原 head 的 newer 所指向的缓存对象
|
||||
this.head = entry.newer
|
||||
}
|
||||
// 将所查找的缓存对象的下一级的 older 指向所查找的缓存对象的older所指向的值
|
||||
// 例如:A B C D E
|
||||
// 如果查找到的是D,那么将E指向C,不再指向D
|
||||
entry.newer.older = entry.older // C <-- E.
|
||||
}
|
||||
if (entry.older) {
|
||||
// 处理 older 指向
|
||||
// 如果查找到的是D,那么C指向E,不再指向D
|
||||
entry.older.newer = entry.newer // C. --> E
|
||||
}
|
||||
// 处理所查找到的对象的 newer 以及 older 指向
|
||||
entry.newer = void 0 // D --x
|
||||
// older指向之前使用过的变量,即D指向E
|
||||
entry.older = this.tail // D. --> E
|
||||
if (this.tail) {
|
||||
// 将E的newer指向D
|
||||
this.tail.newer = entry // E. <-- D
|
||||
}
|
||||
// 改变 tail 为D
|
||||
this.tail = entry
|
||||
return entry.value
|
||||
}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
import { win, document, msie, inBrowser, root, modern } from './browser'
|
||||
import { Cache } from './cache'
|
||||
import { directive, directives, delayCompileNodes } from './directive'
|
||||
|
||||
export var window = win
|
||||
export function Anot(el) {
|
||||
return new Anot.init(el)
|
||||
}
|
||||
|
||||
Anot.init = function(el) {
|
||||
this[0] = this.element = el
|
||||
}
|
||||
|
||||
Anot.fn = Anot.prototype = Anot.init.prototype
|
||||
|
||||
export function shadowCopy(destination, source) {
|
||||
for (var property in source) {
|
||||
destination[property] = source[property]
|
||||
}
|
||||
return destination
|
||||
}
|
||||
export var rword = /[^, ]+/g
|
||||
export var rnowhite = /\S+/g //存在非空字符
|
||||
export var platform = {} //用于放置平台差异的方法与属性
|
||||
export var isArray = function(target) {
|
||||
return Anot.type(target) === 'array'
|
||||
}
|
||||
|
||||
export function oneObject(array, val) {
|
||||
if (typeof array === 'string') {
|
||||
array = array.match(rword) || []
|
||||
}
|
||||
var result = {},
|
||||
value = val !== void 0 ? val : 1
|
||||
for (var i = 0, n = array.length; i < n; i++) {
|
||||
result[array[i]] = value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var op = Object.prototype
|
||||
export function quote(str) {
|
||||
return Anot._quote(str)
|
||||
}
|
||||
export var inspect = op.toString
|
||||
export var ohasOwn = op.hasOwnProperty
|
||||
export var ap = Array.prototype
|
||||
|
||||
var hasConsole = typeof console === 'object'
|
||||
Anot.config = { debug: true }
|
||||
export function log() {
|
||||
if (hasConsole && Anot.config.debug) {
|
||||
Function.apply.call(console.log, console, arguments)
|
||||
}
|
||||
}
|
||||
export {
|
||||
Cache,
|
||||
directive,
|
||||
directives,
|
||||
delayCompileNodes,
|
||||
document,
|
||||
root,
|
||||
msie,
|
||||
modern,
|
||||
inBrowser
|
||||
}
|
||||
export function warn() {
|
||||
if (hasConsole && Anot.config.debug) {
|
||||
var method = console.warn || console.log
|
||||
// http://qiang106.iteye.com/blog/1721425
|
||||
Function.apply.call(method, console, arguments)
|
||||
}
|
||||
}
|
||||
export function error(str, e) {
|
||||
throw (e || Error)(str)
|
||||
}
|
||||
export function noop() {}
|
||||
export function isObject(a) {
|
||||
return a !== null && typeof a === 'object'
|
||||
}
|
||||
|
||||
export function range(start, end, step) {
|
||||
// 用于生成整数数组
|
||||
step || (step = 1)
|
||||
if (end == null) {
|
||||
end = start || 0
|
||||
start = 0
|
||||
}
|
||||
var index = -1,
|
||||
length = Math.max(0, Math.ceil((end - start) / step)),
|
||||
result = new Array(length)
|
||||
while (++index < length) {
|
||||
result[index] = start
|
||||
start += step
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var rhyphen = /([a-z\d])([A-Z]+)/g
|
||||
export function hyphen(target) {
|
||||
//转换为连字符线风格
|
||||
return target.replace(rhyphen, '$1-$2').toLowerCase()
|
||||
}
|
||||
|
||||
var rcamelize = /[-_][^-_]/g
|
||||
export function camelize(target) {
|
||||
//提前判断,提高getStyle等的效率
|
||||
if (!target || (target.indexOf('-') < 0 && target.indexOf('_') < 0)) {
|
||||
return target
|
||||
}
|
||||
//转换为驼峰风格
|
||||
return target.replace(rcamelize, function(match) {
|
||||
return match.charAt(1).toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
export var _slice = ap.slice
|
||||
export function slice(nodes, start, end) {
|
||||
return _slice.call(nodes, start, end)
|
||||
}
|
||||
|
||||
var rhashcode = /\d\.\d{4}/
|
||||
//生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
|
||||
export function makeHashCode(prefix) {
|
||||
/* istanbul ignore next*/
|
||||
prefix = prefix || 'Anot'
|
||||
/* istanbul ignore next*/
|
||||
return String(Math.random() + Math.random()).replace(rhashcode, prefix)
|
||||
}
|
||||
//生成事件回调的UUID(用户通过ms-on指令)
|
||||
export function getLongID(fn) {
|
||||
/* istanbul ignore next */
|
||||
return fn.uuid || (fn.uuid = makeHashCode('e'))
|
||||
}
|
||||
var UUID = 1
|
||||
//生成事件回调的UUID(用户通过Anot.bind)
|
||||
export function getShortID(fn) {
|
||||
/* istanbul ignore next */
|
||||
return fn.uuid || (fn.uuid = '_' + ++UUID)
|
||||
}
|
||||
|
||||
var rescape = /[-.*+?^${}()|[\]\/\\]/g
|
||||
export function escapeRegExp(target) {
|
||||
//http://stevenlevithan.com/regex/xregexp/
|
||||
//将字符串安全格式化为正则表达式的源码
|
||||
return (target + '').replace(rescape, '\\$&')
|
||||
}
|
||||
|
||||
export var eventHooks = {}
|
||||
export var eventListeners = {}
|
||||
export var validators = {}
|
||||
export var cssHooks = {}
|
||||
|
||||
window.Anot = Anot
|
||||
|
||||
export function createFragment() {
|
||||
/* istanbul ignore next */
|
||||
return document.createDocumentFragment()
|
||||
}
|
||||
|
||||
var rentities = /&[a-z0-9#]{2,10};/
|
||||
var temp = document.createElement('div')
|
||||
shadowCopy(Anot, {
|
||||
Array: {
|
||||
merge: function(target, other) {
|
||||
//合并两个数组 Anot2新增
|
||||
target.push.apply(target, other)
|
||||
},
|
||||
ensure: function(target, item) {
|
||||
//只有当前数组不存在此元素时只添加它
|
||||
if (target.indexOf(item) === -1) {
|
||||
return target.push(item)
|
||||
}
|
||||
},
|
||||
removeAt: function(target, index) {
|
||||
//移除数组中指定位置的元素,返回布尔表示成功与否
|
||||
return !!target.splice(index, 1).length
|
||||
},
|
||||
remove: function(target, item) {
|
||||
//移除数组中第一个匹配传参的那个元素,返回布尔表示成功与否
|
||||
var index = target.indexOf(item)
|
||||
if (~index) return Anot.Array.removeAt(target, index)
|
||||
return false
|
||||
}
|
||||
},
|
||||
evaluatorPool: new Cache(888),
|
||||
parsers: {
|
||||
number: function(a) {
|
||||
return a === '' ? '' : parseFloat(a) || 0
|
||||
},
|
||||
string: function(a) {
|
||||
return a === null || a === void 0 ? '' : a + ''
|
||||
},
|
||||
boolean: function(a) {
|
||||
if (a === '') return a
|
||||
return a === 'true' || a === '1'
|
||||
}
|
||||
},
|
||||
_decode: function _decode(str) {
|
||||
if (rentities.test(str)) {
|
||||
temp.innerHTML = str
|
||||
return temp.innerText || temp.textContent
|
||||
}
|
||||
return str
|
||||
}
|
||||
})
|
||||
|
||||
//============== config ============
|
||||
export function config(settings) {
|
||||
for (var p in settings) {
|
||||
var val = settings[p]
|
||||
if (typeof config.plugins[p] === 'function') {
|
||||
config.plugins[p](val)
|
||||
} else {
|
||||
config[p] = val
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
var plugins = {
|
||||
interpolate: function(array) {
|
||||
var openTag = array[0]
|
||||
var closeTag = array[1]
|
||||
if (openTag === closeTag) {
|
||||
throw new SyntaxError('interpolate openTag cannot equal to closeTag')
|
||||
}
|
||||
var str = openTag + 'test' + closeTag
|
||||
|
||||
if (/[<>]/.test(str)) {
|
||||
throw new SyntaxError('interpolate cannot contains "<" or ">"')
|
||||
}
|
||||
|
||||
config.openTag = openTag
|
||||
config.closeTag = closeTag
|
||||
var o = escapeRegExp(openTag)
|
||||
var c = escapeRegExp(closeTag)
|
||||
|
||||
config.rtext = new RegExp(o + '(.+?)' + c, 'g')
|
||||
config.rexpr = new RegExp(o + '([\\s\\S]*)' + c)
|
||||
}
|
||||
}
|
||||
export function createAnchor(nodeValue) {
|
||||
return document.createComment(nodeValue)
|
||||
}
|
||||
config.plugins = plugins
|
||||
config({
|
||||
interpolate: ['{{', '}}'],
|
||||
debug: true
|
||||
})
|
||||
//============ config ============
|
||||
|
||||
shadowCopy(Anot, {
|
||||
shadowCopy,
|
||||
|
||||
oneObject,
|
||||
inspect,
|
||||
ohasOwn,
|
||||
rword,
|
||||
version: 1,
|
||||
vmodels: {},
|
||||
|
||||
directives,
|
||||
directive,
|
||||
|
||||
eventHooks,
|
||||
eventListeners,
|
||||
validators,
|
||||
cssHooks,
|
||||
|
||||
log,
|
||||
noop,
|
||||
warn,
|
||||
error,
|
||||
config,
|
||||
|
||||
modern,
|
||||
msie,
|
||||
root,
|
||||
document,
|
||||
window,
|
||||
inBrowser,
|
||||
|
||||
isObject,
|
||||
range,
|
||||
slice,
|
||||
hyphen,
|
||||
camelize,
|
||||
escapeRegExp,
|
||||
quote,
|
||||
|
||||
makeHashCode
|
||||
})
|
|
@ -0,0 +1,25 @@
|
|||
var delayCompile = {}
|
||||
|
||||
export var directives = {}
|
||||
|
||||
export function directive(name, opts) {
|
||||
if (directives[name]) {
|
||||
Anot.warn(name, 'directive have defined! ')
|
||||
}
|
||||
directives[name] = opts
|
||||
if (!opts.update) {
|
||||
opts.update = function() {}
|
||||
}
|
||||
if (opts.delay) {
|
||||
delayCompile[name] = 1
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
export function delayCompileNodes(dirs) {
|
||||
for (var i in delayCompile) {
|
||||
if ('ms-' + i in dirs) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
//这里放置存在异议的方法
|
||||
import { Anot, ohasOwn, inspect } from './core'
|
||||
export { Anot }
|
||||
var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/
|
||||
var rarraylike = /(Array|List|Collection|Map|Arguments|Set)\]$/
|
||||
|
||||
// Anot.type
|
||||
var class2type = {}
|
||||
'Boolean Number String Function Array Date RegExp Object Error'.replace(
|
||||
Anot.rword,
|
||||
function(name) {
|
||||
class2type['[object ' + name + ']'] = name.toLowerCase()
|
||||
}
|
||||
)
|
||||
|
||||
Anot.type = function(obj) {
|
||||
//取得目标的类型
|
||||
if (obj == null) {
|
||||
return String(obj)
|
||||
}
|
||||
// 早期的webkit内核浏览器实现了已废弃的ecma262v4标准,可以将正则字面量当作函数使用,因此typeof在判定正则时会返回function
|
||||
return typeof obj === 'object' || typeof obj === 'function'
|
||||
? class2type[inspect.call(obj)] || 'object'
|
||||
: typeof obj
|
||||
}
|
||||
|
||||
Anot._quote = JSON.stringify
|
||||
|
||||
Anot.isFunction = function(fn) {
|
||||
return typeof fn === 'function'
|
||||
}
|
||||
|
||||
Anot.isWindow = function(obj) {
|
||||
return rwindow.test(inspect.call(obj))
|
||||
}
|
||||
|
||||
/*判定是否是一个朴素的javascript对象(Object),不是DOM对象,不是BOM对象,不是自定义类的实例*/
|
||||
Anot.isPlainObject = function(obj) {
|
||||
// 简单的 typeof obj === 'object'检测,会致使用isPlainObject(window)在opera下通不过
|
||||
return (
|
||||
inspect.call(obj) === '[object Object]' &&
|
||||
Object.getPrototypeOf(obj) === Object.prototype
|
||||
)
|
||||
}
|
||||
|
||||
//与jQuery.extend方法,可用于浅拷贝,深拷贝
|
||||
Anot.mix = Anot.fn.mix = function() {
|
||||
var options,
|
||||
name,
|
||||
src,
|
||||
copy,
|
||||
copyIsArray,
|
||||
clone,
|
||||
target = arguments[0] || {},
|
||||
i = 1,
|
||||
length = arguments.length,
|
||||
deep = false
|
||||
|
||||
// 如果第一个参数为布尔,判定是否深拷贝
|
||||
if (typeof target === 'boolean') {
|
||||
deep = target
|
||||
target = arguments[1] || {}
|
||||
i++
|
||||
}
|
||||
|
||||
//确保接受方为一个复杂的数据类型
|
||||
if (typeof target !== 'object' && typeof target !== 'function') {
|
||||
target = {}
|
||||
}
|
||||
|
||||
//如果只有一个参数,那么新成员添加于mix所在的对象上
|
||||
if (i === length) {
|
||||
target = this
|
||||
i--
|
||||
}
|
||||
|
||||
for (; i < length; i++) {
|
||||
//只处理非空参数
|
||||
if ((options = arguments[i]) != null) {
|
||||
for (name in options) {
|
||||
src = target[name]
|
||||
|
||||
copy = options[name]
|
||||
|
||||
// 防止环引用
|
||||
if (target === copy) {
|
||||
continue
|
||||
}
|
||||
if (
|
||||
deep &&
|
||||
copy &&
|
||||
(Anot.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))
|
||||
) {
|
||||
if (copyIsArray) {
|
||||
copyIsArray = false
|
||||
clone = src && Array.isArray(src) ? src : []
|
||||
} else {
|
||||
clone = src && Anot.isPlainObject(src) ? src : {}
|
||||
}
|
||||
|
||||
target[name] = Anot.mix(deep, clone, copy)
|
||||
} else if (copy !== void 0) {
|
||||
target[name] = copy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
/*判定是否类数组,如节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象*/
|
||||
export function isArrayLike(obj) {
|
||||
/* istanbul ignore if*/
|
||||
if (obj && typeof obj === 'object') {
|
||||
var n = obj.length,
|
||||
str = inspect.call(obj)
|
||||
if (rarraylike.test(str)) {
|
||||
return true
|
||||
} else if (str === '[object Object]' && n === n >>> 0) {
|
||||
return true //由于ecma262v5能修改对象属性的enumerable,因此不能用propertyIsEnumerable来判定了
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Anot.each = function(obj, fn) {
|
||||
if (obj) {
|
||||
//排除null, undefined
|
||||
var i = 0
|
||||
if (isArrayLike(obj)) {
|
||||
for (var n = obj.length; i < n; i++) {
|
||||
if (fn(i, obj[i]) === false) break
|
||||
}
|
||||
} else {
|
||||
for (i in obj) {
|
||||
if (obj.hasOwnProperty(i) && fn(i, obj[i]) === false) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { document } from '../seed/core'
|
||||
|
||||
export function VComment(text) {
|
||||
this.nodeName = '#comment'
|
||||
this.nodeValue = text
|
||||
}
|
||||
VComment.prototype = {
|
||||
constructor: VComment,
|
||||
toDOM: function() {
|
||||
if (this.dom) return this.dom
|
||||
return (this.dom = document.createComment(this.nodeValue))
|
||||
},
|
||||
toHTML: function() {
|
||||
return '<!--' + this.nodeValue + '-->'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
import { Anot, document } from '../seed/core'
|
||||
|
||||
export function VElement(type, props, children, isVoidTag) {
|
||||
this.nodeName = type
|
||||
this.props = props
|
||||
this.children = children
|
||||
this.isVoidTag = isVoidTag
|
||||
}
|
||||
VElement.prototype = {
|
||||
constructor: VElement,
|
||||
toDOM() {
|
||||
if (this.dom) return this.dom
|
||||
var dom,
|
||||
tagName = this.nodeName
|
||||
if (Anot.modern && svgTags[tagName]) {
|
||||
dom = createSVG(tagName)
|
||||
} else {
|
||||
dom = document.createElement(tagName)
|
||||
}
|
||||
var props = this.props || {}
|
||||
|
||||
for (var i in props) {
|
||||
var val = props[i]
|
||||
if (skipFalseAndFunction(val)) {
|
||||
dom.setAttribute(i, val + '')
|
||||
}
|
||||
}
|
||||
var c = this.children || []
|
||||
var template = c[0] ? c[0].nodeValue : ''
|
||||
switch (this.nodeName) {
|
||||
case 'xmp':
|
||||
case 'style':
|
||||
case 'script':
|
||||
case 'noscript':
|
||||
dom.innerHTML = template
|
||||
break
|
||||
case 'template':
|
||||
if (supportTemplate) {
|
||||
dom.innerHTML = template
|
||||
} else {
|
||||
/* istanbul ignore next*/
|
||||
dom.textContent = template
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (!this.isVoidTag && this.children) {
|
||||
this.children.forEach(
|
||||
el => el && dom.appendChild(Anot.vdom(el, 'toDOM'))
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
return (this.dom = dom)
|
||||
},
|
||||
toHTML() {
|
||||
var arr = []
|
||||
var props = this.props || {}
|
||||
for (var i in props) {
|
||||
var val = props[i]
|
||||
if (skipFalseAndFunction(val)) {
|
||||
arr.push(i + '=' + Anot.quote(props[i] + ''))
|
||||
}
|
||||
}
|
||||
arr = arr.length ? ' ' + arr.join(' ') : ''
|
||||
var str = '<' + this.nodeName + arr
|
||||
if (this.isVoidTag) {
|
||||
return str + '/>'
|
||||
}
|
||||
str += '>'
|
||||
if (this.children) {
|
||||
str += this.children
|
||||
.map(el => (el ? Anot.vdom(el, 'toHTML') : ''))
|
||||
.join('')
|
||||
}
|
||||
return str + '</' + this.nodeName + '>'
|
||||
}
|
||||
}
|
||||
|
||||
function skipFalseAndFunction(a) {
|
||||
return a !== false && Object(a) !== a
|
||||
}
|
||||
|
||||
function createSVG(type) {
|
||||
return document.createElementNS('http://www.w3.org/2000/svg', type)
|
||||
}
|
||||
|
||||
var svgTags = Anot.oneObject(
|
||||
'circle,defs,ellipse,image,line,' +
|
||||
'path,polygon,polyline,rect,symbol,text,use,g,svg'
|
||||
)
|
||||
|
||||
if (Anot.inBrowser) {
|
||||
var supportTemplate = 'content' in document.createElement('template')
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import { Anot, createFragment } from '../seed/core'
|
||||
|
||||
export function VFragment(children, key, val, index) {
|
||||
this.nodeName = '#document-fragment'
|
||||
this.children = children
|
||||
this.key = key
|
||||
this.val = val
|
||||
this.index = index
|
||||
this.props = {}
|
||||
}
|
||||
VFragment.prototype = {
|
||||
constructor: VFragment,
|
||||
toDOM() {
|
||||
if (this.dom) return this.dom
|
||||
var f = this.toFragment()
|
||||
//IE6-11 docment-fragment都没有children属性
|
||||
this.split = f.lastChild
|
||||
return (this.dom = f)
|
||||
},
|
||||
dispose() {
|
||||
this.toFragment()
|
||||
this.innerRender && this.innerRender.dispose()
|
||||
for (var i in this) {
|
||||
this[i] = null
|
||||
}
|
||||
},
|
||||
toFragment() {
|
||||
var f = createFragment()
|
||||
this.children.forEach(el => f.appendChild(Anot.vdom(el, 'toDOM')))
|
||||
return f
|
||||
},
|
||||
toHTML() {
|
||||
var c = this.children
|
||||
return c.map(el => Anot.vdom(el, 'toHTML')).join('')
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { Anot, document } from '../seed/core'
|
||||
|
||||
export function VText(text) {
|
||||
this.nodeName = '#text'
|
||||
this.nodeValue = text
|
||||
}
|
||||
|
||||
VText.prototype = {
|
||||
constructor: VText,
|
||||
toDOM() {
|
||||
/* istanbul ignore if*/
|
||||
if (this.dom) return this.dom
|
||||
var v = Anot._decode(this.nodeValue)
|
||||
return (this.dom = document.createTextNode(v))
|
||||
},
|
||||
toHTML() {
|
||||
return this.nodeValue
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* 虚拟DOM的4大构造器
|
||||
*/
|
||||
import { Anot, createFragment } from '../seed/core'
|
||||
import { VText } from './VText'
|
||||
import { VComment } from './VComment'
|
||||
import { VElement } from './VElement.modern'
|
||||
import { VFragment } from './VFragment'
|
||||
|
||||
Anot.mix(Anot, {
|
||||
VText,
|
||||
VComment,
|
||||
VElement,
|
||||
VFragment
|
||||
})
|
||||
|
||||
var constNameMap = {
|
||||
'#text': 'VText',
|
||||
'#document-fragment': 'VFragment',
|
||||
'#comment': 'VComment'
|
||||
}
|
||||
|
||||
var vdom = (Anot.vdomAdaptor = Anot.vdom = function(obj, method) {
|
||||
if (!obj) {
|
||||
//obj在ms-for循环里面可能是null
|
||||
return method === 'toHTML' ? '' : createFragment()
|
||||
}
|
||||
var nodeName = obj.nodeName
|
||||
if (!nodeName) {
|
||||
return new Anot.VFragment(obj)[method]()
|
||||
}
|
||||
var constName = constNameMap[nodeName] || 'VElement'
|
||||
return Anot[constName].prototype[method].call(obj)
|
||||
})
|
||||
|
||||
Anot.domize = function(a) {
|
||||
return Anot.vdom(a, 'toDOM')
|
||||
}
|
||||
|
||||
export { vdom, Anot, VText, VComment, VElement, VFragment }
|
|
@ -0,0 +1,176 @@
|
|||
import { Anot } from '../seed/core'
|
||||
|
||||
import { runActions, collectDeps } from './transaction'
|
||||
|
||||
import { createGetter, createSetter } from '../parser/index'
|
||||
|
||||
export var actionUUID = 1
|
||||
//需要重构
|
||||
export function Action(vm, options, callback) {
|
||||
for (var i in options) {
|
||||
if (protectedMenbers[i] !== 1) {
|
||||
this[i] = options[i]
|
||||
}
|
||||
}
|
||||
|
||||
this.vm = vm
|
||||
this.observers = []
|
||||
this.callback = callback
|
||||
this.uuid = ++actionUUID
|
||||
this.ids = ''
|
||||
this.mapIDs = {} //这个用于去重
|
||||
this.isAction = true
|
||||
var expr = this.expr
|
||||
// 缓存取值函数
|
||||
if (typeof this.getter !== 'function') {
|
||||
this.getter = createGetter(expr, this.type)
|
||||
}
|
||||
// 缓存设值函数(双向数据绑定)
|
||||
if (this.type === 'duplex') {
|
||||
this.setter = createSetter(expr, this.type)
|
||||
}
|
||||
// 缓存表达式旧值
|
||||
this.value = NaN
|
||||
// 表达式初始值 & 提取依赖
|
||||
if (!this.node) {
|
||||
this.value = this.get()
|
||||
}
|
||||
}
|
||||
|
||||
Action.prototype = {
|
||||
getValue() {
|
||||
var scope = this.vm
|
||||
try {
|
||||
return this.getter.call(scope, scope)
|
||||
} catch (e) {
|
||||
Anot.log(this.getter + ' exec error')
|
||||
}
|
||||
},
|
||||
|
||||
setValue(value) {
|
||||
var scope = this.vm
|
||||
if (this.setter) {
|
||||
this.setter.call(scope, scope, value)
|
||||
}
|
||||
},
|
||||
|
||||
// get --> getValue --> getter
|
||||
get(fn) {
|
||||
var name = 'action track ' + this.type
|
||||
|
||||
if (this.deep) {
|
||||
Anot.deepCollect = true
|
||||
}
|
||||
|
||||
var value = collectDeps(this, this.getValue)
|
||||
if (this.deep && Anot.deepCollect) {
|
||||
Anot.deepCollect = false
|
||||
}
|
||||
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* 在更新视图前保存原有的value
|
||||
*/
|
||||
beforeUpdate() {
|
||||
return (this.oldValue = getPlainObject(this.value))
|
||||
},
|
||||
|
||||
update(args, uuid) {
|
||||
var oldVal = this.beforeUpdate()
|
||||
var newVal = (this.value = this.get())
|
||||
var callback = this.callback
|
||||
if (callback && this.diff(newVal, oldVal, args)) {
|
||||
callback.call(this.vm, this.value, oldVal, this.expr)
|
||||
}
|
||||
this._isScheduled = false
|
||||
},
|
||||
schedule() {
|
||||
if (!this._isScheduled) {
|
||||
this._isScheduled = true
|
||||
if (!Anot.uniqActions[this.uuid]) {
|
||||
Anot.uniqActions[this.uuid] = 1
|
||||
Anot.pendingActions.push(this)
|
||||
}
|
||||
|
||||
runActions() //这里会还原_isScheduled
|
||||
}
|
||||
},
|
||||
removeDepends() {
|
||||
var self = this
|
||||
this.observers.forEach(function(depend) {
|
||||
Anot.Array.remove(depend.observers, self)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 比较两个计算值是否,一致,在for, class等能复杂数据类型的指令中,它们会重写diff复法
|
||||
*/
|
||||
diff(a, b) {
|
||||
return a !== b
|
||||
},
|
||||
|
||||
/**
|
||||
* 销毁指令
|
||||
*/
|
||||
dispose() {
|
||||
this.value = null
|
||||
this.removeDepends()
|
||||
if (this.beforeDispose) {
|
||||
this.beforeDispose()
|
||||
}
|
||||
for (var i in this) {
|
||||
delete this[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getPlainObject(v) {
|
||||
if (v && typeof v === 'object') {
|
||||
if (v && v.$events) {
|
||||
return v.$model
|
||||
} else if (Array.isArray(v)) {
|
||||
let ret = []
|
||||
for (let i = 0, n = v.length; i < n; i++) {
|
||||
ret.push(getPlainObject(v[i]))
|
||||
}
|
||||
return ret
|
||||
} else {
|
||||
let ret = {}
|
||||
for (let i in v) {
|
||||
ret[i] = getPlainObject(v[i])
|
||||
}
|
||||
return ret
|
||||
}
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
export var protectedMenbers = {
|
||||
vm: 1,
|
||||
callback: 1,
|
||||
|
||||
observers: 1,
|
||||
oldValue: 1,
|
||||
value: 1,
|
||||
getValue: 1,
|
||||
setValue: 1,
|
||||
get: 1,
|
||||
|
||||
removeDepends: 1,
|
||||
beforeUpdate: 1,
|
||||
update: 1,
|
||||
//diff
|
||||
//getter
|
||||
//setter
|
||||
//expr
|
||||
//vdom
|
||||
//type: "for"
|
||||
//name: "ms-for"
|
||||
//attrName: ":for"
|
||||
//param: "click"
|
||||
//beforeDispose
|
||||
dispose: 1
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
import { Anot } from '../seed/core'
|
||||
|
||||
import { obid, Mutation } from './Mutation'
|
||||
import { collectDeps } from './transaction'
|
||||
|
||||
function getBody(fn) {
|
||||
var entire = fn.toString()
|
||||
return entire.substring(entire.indexOf('{}') + 1, entire.lastIndexOf('}'))
|
||||
}
|
||||
//如果不存在三目,if,方法
|
||||
let instability = /(\?|if\b|\(.+\))/
|
||||
|
||||
function __create(o) {
|
||||
var __ = function() {}
|
||||
__.prototype = o
|
||||
return new __()
|
||||
}
|
||||
|
||||
function __extends(child, parent) {
|
||||
if (typeof parent === 'function') {
|
||||
var proto = (child.prototype = __create(parent.prototype))
|
||||
proto.constructor = child
|
||||
}
|
||||
}
|
||||
export var Computed = (function(_super) {
|
||||
__extends(Computed, _super)
|
||||
|
||||
function Computed(name, options, vm) {
|
||||
//构造函数
|
||||
_super.call(this, name, undefined, vm)
|
||||
delete options.get
|
||||
delete options.set
|
||||
|
||||
Anot.mix(this, options)
|
||||
this.deps = {}
|
||||
this.type = 'computed'
|
||||
this.depsVersion = {}
|
||||
this.isComputed = true
|
||||
this.trackAndCompute()
|
||||
if (!('isStable' in this)) {
|
||||
this.isStable = !instability.test(getBody(this.getter))
|
||||
}
|
||||
}
|
||||
var cp = Computed.prototype
|
||||
cp.trackAndCompute = function() {
|
||||
if (this.isStable && this.depsCount > 0) {
|
||||
this.getValue()
|
||||
} else {
|
||||
collectDeps(this, this.getValue.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
cp.getValue = function() {
|
||||
return (this.value = this.getter.call(this.vm))
|
||||
}
|
||||
|
||||
cp.schedule = function() {
|
||||
var observers = this.observers
|
||||
var i = observers.length
|
||||
while (i--) {
|
||||
var d = observers[i]
|
||||
if (d.schedule) {
|
||||
d.schedule()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cp.shouldCompute = function() {
|
||||
if (this.isStable) {
|
||||
//如果变动因子确定,那么只比较变动因子的版本
|
||||
var toComputed = false
|
||||
for (var i in this.deps) {
|
||||
if (this.deps[i].version !== this.depsVersion[i]) {
|
||||
toComputed = true
|
||||
this.depsVersion[i] = this.deps[i].version
|
||||
}
|
||||
}
|
||||
return toComputed
|
||||
}
|
||||
return true
|
||||
}
|
||||
cp.set = function() {
|
||||
if (this.setter) {
|
||||
Anot.transaction(this.setter, this.vm, arguments)
|
||||
}
|
||||
}
|
||||
cp.get = function() {
|
||||
//当被设置了就不稳定,当它被访问了一次就是稳定
|
||||
this.collect()
|
||||
|
||||
if (this.shouldCompute()) {
|
||||
this.trackAndCompute()
|
||||
// console.log('computed 2 分支')
|
||||
this.updateVersion()
|
||||
// this.reportChanged()
|
||||
}
|
||||
|
||||
//下面这一行好像没用
|
||||
return this.value
|
||||
}
|
||||
return Computed
|
||||
})(Mutation)
|
|
@ -0,0 +1,90 @@
|
|||
import {
|
||||
transactionStart,
|
||||
transactionEnd,
|
||||
reportObserved,
|
||||
propagateChanged
|
||||
} from './transaction'
|
||||
import { Anot, platform } from '../seed/core'
|
||||
/**
|
||||
*
|
||||
与Computed等共享UUID
|
||||
*/
|
||||
export let obid = 1
|
||||
export function Mutation(expr, value, vm) {
|
||||
//构造函数
|
||||
this.expr = expr
|
||||
if (value) {
|
||||
var childVm = platform.createProxy(value, this)
|
||||
if (childVm) {
|
||||
value = childVm
|
||||
}
|
||||
}
|
||||
this.value = value
|
||||
this.vm = vm
|
||||
try {
|
||||
vm.$mutations[expr] = this
|
||||
} catch (ignoreIE) {}
|
||||
this.uuid = ++obid
|
||||
this.updateVersion()
|
||||
this.mapIDs = {}
|
||||
this.observers = []
|
||||
}
|
||||
|
||||
Mutation.prototype = {
|
||||
get() {
|
||||
if (Anot.trackingAction) {
|
||||
this.collect() //被收集
|
||||
var childOb = this.value
|
||||
if (childOb && childOb.$events) {
|
||||
if (Array.isArray(childOb)) {
|
||||
childOb.forEach(function(item) {
|
||||
if (item && item.$events) {
|
||||
item.$events.__dep__.collect()
|
||||
}
|
||||
})
|
||||
} else if (Anot.deepCollect) {
|
||||
for (var key in childOb) {
|
||||
if (childOb.hasOwnProperty(key)) {
|
||||
var collectIt = childOb[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.value
|
||||
},
|
||||
|
||||
collect() {
|
||||
Anot.track(name, '被收集')
|
||||
reportObserved(this)
|
||||
},
|
||||
|
||||
updateVersion() {
|
||||
this.version = Math.random() + Math.random()
|
||||
},
|
||||
|
||||
notify() {
|
||||
transactionStart()
|
||||
propagateChanged(this)
|
||||
transactionEnd()
|
||||
},
|
||||
|
||||
set(newValue) {
|
||||
var oldValue = this.value
|
||||
if (newValue !== oldValue) {
|
||||
if (Anot.isObject(newValue)) {
|
||||
var hash = oldValue && oldValue.$hashcode
|
||||
var childVM = platform.createProxy(newValue, this)
|
||||
if (childVM) {
|
||||
if (hash) {
|
||||
childVM.$hashcode = hash
|
||||
}
|
||||
newValue = childVM
|
||||
}
|
||||
}
|
||||
this.value = newValue
|
||||
this.updateVersion()
|
||||
this.notify()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
import { Anot, ap, platform, modern, isObject } from '../seed/core'
|
||||
import { Mutation } from './Mutation'
|
||||
|
||||
var _splice = ap.splice
|
||||
var __array__ = {
|
||||
set: function(index, val) {
|
||||
if (index >>> 0 === index && this[index] !== val) {
|
||||
if (index > this.length) {
|
||||
throw Error(index + 'set方法的第一个参数不能大于原数组长度')
|
||||
}
|
||||
this.splice(index, 1, val)
|
||||
}
|
||||
},
|
||||
toJSON: function() {
|
||||
//为了解决IE6-8的解决,通过此方法显式地求取数组的$model
|
||||
return (this.$model = platform.toJson(this))
|
||||
},
|
||||
contains: function(el) {
|
||||
//判定是否包含
|
||||
return this.indexOf(el) !== -1
|
||||
},
|
||||
ensure: function(el) {
|
||||
if (!this.contains(el)) {
|
||||
//只有不存在才push
|
||||
this.push(el)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
pushArray: function(arr) {
|
||||
return this.push.apply(this, arr)
|
||||
},
|
||||
remove: function(el) {
|
||||
//移除第一个等于给定值的元素
|
||||
return this.removeAt(this.indexOf(el))
|
||||
},
|
||||
removeAt: function(index) {
|
||||
//移除指定索引上的元素
|
||||
if (index >>> 0 === index) {
|
||||
return this.splice(index, 1)
|
||||
}
|
||||
return []
|
||||
},
|
||||
clear: function() {
|
||||
this.removeAll()
|
||||
return this
|
||||
},
|
||||
removeAll: function(all) {
|
||||
//移除N个元素
|
||||
var size = this.length
|
||||
var eliminate = Array.isArray(all)
|
||||
? function(el) {
|
||||
return all.indexOf(el) !== -1
|
||||
}
|
||||
: typeof all === 'function'
|
||||
? all
|
||||
: false
|
||||
|
||||
if (eliminate) {
|
||||
for (var i = this.length - 1; i >= 0; i--) {
|
||||
if (eliminate(this[i], i)) {
|
||||
_splice.call(this, i, 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_splice.call(this, 0, this.length)
|
||||
}
|
||||
this.toJSON()
|
||||
this.$events.__dep__.notify()
|
||||
}
|
||||
}
|
||||
export function hijackMethods(array) {
|
||||
for (var i in __array__) {
|
||||
platform.hideProperty(array, i, __array__[i])
|
||||
}
|
||||
}
|
||||
var __method__ = [
|
||||
'push',
|
||||
'pop',
|
||||
'shift',
|
||||
'unshift',
|
||||
'splice',
|
||||
'sort',
|
||||
'reverse'
|
||||
]
|
||||
|
||||
__method__.forEach(function(method) {
|
||||
var original = ap[method]
|
||||
__array__[method] = function() {
|
||||
// 继续尝试劫持数组元素的属性
|
||||
var core = this.$events
|
||||
|
||||
var args = platform.listFactory(arguments, true, core.__dep__)
|
||||
var result = original.apply(this, args)
|
||||
|
||||
this.toJSON()
|
||||
core.__dep__.notify(method)
|
||||
return result
|
||||
}
|
||||
})
|
||||
|
||||
export function listFactory(array, stop, dd) {
|
||||
if (!stop) {
|
||||
hijackMethods(array)
|
||||
if (modern) {
|
||||
Object.defineProperty(array, '$model', platform.modelAccessor)
|
||||
}
|
||||
platform.hideProperty(array, '$hashcode', Anot.makeHashCode('$'))
|
||||
platform.hideProperty(array, '$events', { __dep__: dd || new Mutation() })
|
||||
}
|
||||
var _dd = array.$events && array.$events.__dep__
|
||||
for (var i = 0, n = array.length; i < n; i++) {
|
||||
var item = array[i]
|
||||
if (isObject(item)) {
|
||||
array[i] = platform.createProxy(item, _dd)
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
platform.listFactory = listFactory
|
|
@ -0,0 +1,85 @@
|
|||
import { Anot, platform, modern } from '../seed/core'
|
||||
import { $$skipArray } from './reserved'
|
||||
import { Action } from './Action'
|
||||
import './share'
|
||||
import './ProxyArray'
|
||||
export { Anot, platform }
|
||||
|
||||
export function hideProperty(host, name, value) {
|
||||
Object.defineProperty(host, name, {
|
||||
value: value,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
})
|
||||
}
|
||||
|
||||
function $fire(expr, a) {
|
||||
var list = this.$events[expr]
|
||||
if (Array.isArray(list)) {
|
||||
for (var i = 0, w; (w = list[i++]); ) {
|
||||
w.callback.call(w.vm, a, w.value, w.expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function $watch(expr, callback, deep) {
|
||||
var core = this.$events
|
||||
var w = new Action(
|
||||
this,
|
||||
{
|
||||
deep: deep,
|
||||
type: 'user',
|
||||
expr: expr
|
||||
},
|
||||
callback
|
||||
)
|
||||
if (!core[expr]) {
|
||||
core[expr] = [w]
|
||||
} else {
|
||||
core[expr].push(w)
|
||||
}
|
||||
return function() {
|
||||
w.dispose()
|
||||
Anot.Array.remove(core[expr], w)
|
||||
if (core[expr].length === 0) {
|
||||
delete core[expr]
|
||||
}
|
||||
}
|
||||
}
|
||||
export function watchFactory(core) {
|
||||
return $watch
|
||||
}
|
||||
|
||||
export function fireFactory(core) {
|
||||
return $fire
|
||||
}
|
||||
|
||||
export function afterCreate(vm, core, keys, bindThis) {
|
||||
var ac = vm.$accessors
|
||||
//隐藏系统属性
|
||||
for (var key in $$skipArray) {
|
||||
hideProperty(vm, key, vm[key])
|
||||
}
|
||||
//为不可监听的属性或方法赋值
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let key = keys[i]
|
||||
if (!(key in ac)) {
|
||||
let val = core[key]
|
||||
if (bindThis && typeof val === 'function') {
|
||||
vm[key] = val.bind(vm)
|
||||
vm[key]._orig = val
|
||||
continue
|
||||
}
|
||||
vm[key] = val
|
||||
}
|
||||
}
|
||||
vm.$track = keys.join('☥')
|
||||
vm.$events.__proxy__ = vm
|
||||
}
|
||||
|
||||
platform.fireFactory = fireFactory
|
||||
platform.watchFactory = watchFactory
|
||||
platform.afterCreate = afterCreate
|
||||
platform.hideProperty = hideProperty
|
||||
platform.createViewModel = Object.defineProperties
|
|
@ -0,0 +1,161 @@
|
|||
import { Anot, platform, isObject, modern } from '../seed/core'
|
||||
import { $$skipArray } from './reserved'
|
||||
import { Mutation } from './Mutation'
|
||||
import { Computed } from './Computed'
|
||||
import { IProxy, canHijack, createProxy } from './share'
|
||||
|
||||
if (typeof Proxy === 'function') {
|
||||
Anot.config.inProxyMode = true
|
||||
|
||||
platform.modelFactory = function modelFactory(definition, dd) {
|
||||
var clone = {}
|
||||
for (let i in definition) {
|
||||
clone[i] = definition[i]
|
||||
delete definition[i]
|
||||
}
|
||||
|
||||
definition.$id = clone.$id
|
||||
var proxy = new IProxy(definition, dd)
|
||||
|
||||
var vm = toProxy(proxy)
|
||||
//先添加普通属性与监控属性
|
||||
for (let i in clone) {
|
||||
vm[i] = clone[i]
|
||||
}
|
||||
var $computed = clone.$computed
|
||||
//再添加计算属性
|
||||
if ($computed) {
|
||||
delete clone.$computed
|
||||
for (let i in $computed) {
|
||||
let val = $computed[i]
|
||||
if (typeof val === 'function') {
|
||||
let _val = val
|
||||
val = { get: _val }
|
||||
}
|
||||
if (val && val.get) {
|
||||
val.getter = val.get
|
||||
//在set方法中的target是IProxy,需要重写成Proxy,才能依赖收集
|
||||
val.vm = vm
|
||||
if (val.set) val.setter = val.set
|
||||
$computed[i] = val
|
||||
delete clone[i] //去掉重名的监控属性
|
||||
} else {
|
||||
delete $computed[i]
|
||||
}
|
||||
}
|
||||
for (let i in $computed) {
|
||||
vm[i] = $computed[i]
|
||||
}
|
||||
}
|
||||
|
||||
return vm
|
||||
}
|
||||
|
||||
//https://developer.mozilla.org/en-US/docs/Archive/Web/Old_Proxy_API
|
||||
function toProxy(definition) {
|
||||
return Proxy.create
|
||||
? Proxy.create(definition, traps)
|
||||
: new Proxy(definition, traps)
|
||||
}
|
||||
|
||||
function wrapIt(str) {
|
||||
return '☥' + str + '☥'
|
||||
}
|
||||
var traps = {
|
||||
deleteProperty(target, name) {
|
||||
if (target.hasOwnProperty(name)) {
|
||||
//移除一个属性,分三昌:
|
||||
//1. 移除监听器
|
||||
//2. 移除真实对象的对应属性
|
||||
//3. 移除$track中的键名
|
||||
delete target.$accessors[name]
|
||||
delete target[name]
|
||||
target.$track = wrapIt(target.$track)
|
||||
.replace(wrapIt(name), '')
|
||||
.slice(1, -1)
|
||||
}
|
||||
return true
|
||||
},
|
||||
get(target, name) {
|
||||
if (name === '$model') {
|
||||
return platform.toJson(target)
|
||||
}
|
||||
//收集依赖
|
||||
var m = target.$accessors[name]
|
||||
if (m && m.get) {
|
||||
return m.get()
|
||||
}
|
||||
|
||||
return target[name]
|
||||
},
|
||||
set(target, name, value) {
|
||||
if (name === '$model' || name === '$track') {
|
||||
return true
|
||||
}
|
||||
if (name in $$skipArray) {
|
||||
target[name] = value
|
||||
return true
|
||||
}
|
||||
|
||||
var ac = target.$accessors
|
||||
var oldValue = ac[name] ? ac[name].value : target[name]
|
||||
|
||||
if (oldValue !== value) {
|
||||
if (!target.hasOwnProperty(name)) {
|
||||
updateTrack(target, name)
|
||||
}
|
||||
if (canHijack(name, value, target.$proxyItemBackdoor)) {
|
||||
var $computed = target.$computed || {}
|
||||
//如果是新属性
|
||||
if (!ac[name]) {
|
||||
target[name] = value //必须设置,用于hasOwnProperty
|
||||
var isComputed = !!$computed[name]
|
||||
var Observable = isComputed ? Computed : Mutation
|
||||
ac[name] = new Observable(name, value, target)
|
||||
return true
|
||||
}
|
||||
var mutation = ac[name]
|
||||
//创建子对象
|
||||
mutation.set(value)
|
||||
target[name] = mutation.value
|
||||
} else {
|
||||
target[name] = value
|
||||
}
|
||||
}
|
||||
// set方法必须返回true, 告诉Proxy已经成功修改了这个值,否则会抛
|
||||
//'set' on proxy: trap returned falsish for property xxx 错误
|
||||
return true
|
||||
}
|
||||
//has 只能用于 in 操作符,没什么用删去
|
||||
}
|
||||
|
||||
function updateTrack(target, name) {
|
||||
var arr = target.$track.match(/[^☥]+/g) || []
|
||||
arr.push(name)
|
||||
target.$track = arr.sort().join('☥')
|
||||
}
|
||||
|
||||
Anot.itemFactory = platform.itemFactory = function itemFactory(
|
||||
before,
|
||||
after
|
||||
) {
|
||||
var definition = before.$model
|
||||
definition.$proxyItemBackdoor = true
|
||||
definition.$id =
|
||||
before.$hashcode + String(after.hashcode || Math.random()).slice(6)
|
||||
definition.$accessors = Anot.mix({}, before.$accessors)
|
||||
var vm = platform.modelFactory(definition)
|
||||
vm.$track = before.$track
|
||||
for (var i in after.data) {
|
||||
vm[i] = after.data[i]
|
||||
}
|
||||
return vm
|
||||
}
|
||||
|
||||
platform.fuseFactory = function fuseFactory(before, after) {
|
||||
var definition = Anot.mix(before.$model, after.$model)
|
||||
definition.$id = before.$hashcode + after.$hashcode
|
||||
definition.$accessors = Anot.mix({}, before.$accessors, after.$accessors)
|
||||
return platform.modelFactory(definition)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
$$skipArray:是系统级通用的不可监听属性
|
||||
$skipArray: 是当前对象特有的不可监听属性
|
||||
|
||||
不同点是
|
||||
$$skipArray被hasOwnProperty后返回false
|
||||
$skipArray被hasOwnProperty后返回true
|
||||
*/
|
||||
var falsy
|
||||
export var $$skipArray = {
|
||||
$id: falsy,
|
||||
$render: falsy,
|
||||
$track: falsy,
|
||||
$element: falsy,
|
||||
$computed: falsy,
|
||||
$watch: falsy,
|
||||
$fire: falsy,
|
||||
$events: falsy,
|
||||
$accessors: falsy,
|
||||
$hashcode: falsy,
|
||||
$mutations: falsy,
|
||||
$vbthis: falsy,
|
||||
$vbsetter: falsy
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
import { Anot, platform, isObject, modern } from '../seed/core'
|
||||
import { $$skipArray } from './reserved'
|
||||
import { Mutation } from './Mutation'
|
||||
import { Computed } from './Computed'
|
||||
|
||||
/**
|
||||
* 这里放置ViewModel模块的共用方法
|
||||
* Anot.define: 全框架最重要的方法,生成用户VM
|
||||
* IProxy, 基本用户数据产生的一个数据对象,基于$model与vmodel之间的形态
|
||||
* modelFactory: 生成用户VM
|
||||
* canHijack: 判定此属性是否该被劫持,加入数据监听与分发的的逻辑
|
||||
* createProxy: listFactory与modelFactory的封装
|
||||
* createAccessor: 实现数据监听与分发的重要对象
|
||||
* itemFactory: ms-for循环中产生的代理VM的生成工厂
|
||||
* fuseFactory: 两个ms-controller间产生的代理VM的生成工厂
|
||||
*/
|
||||
|
||||
Anot.define = function(definition) {
|
||||
var $id = definition.$id
|
||||
if (!$id) {
|
||||
Anot.error('vm.$id must be specified')
|
||||
}
|
||||
if (Anot.vmodels[$id]) {
|
||||
Anot.warn('error:[' + $id + '] had defined!')
|
||||
}
|
||||
var vm = platform.modelFactory(definition)
|
||||
return (Anot.vmodels[$id] = vm)
|
||||
}
|
||||
|
||||
/**
|
||||
* 在未来的版本,Anot改用Proxy来创建VM,因此
|
||||
*/
|
||||
|
||||
export function IProxy(definition, dd) {
|
||||
Anot.mix(this, definition)
|
||||
Anot.mix(this, $$skipArray)
|
||||
this.$hashcode = Anot.makeHashCode('$')
|
||||
this.$id = this.$id || this.$hashcode
|
||||
this.$events = {
|
||||
__dep__: dd || new Mutation(this.$id)
|
||||
}
|
||||
if (Anot.config.inProxyMode) {
|
||||
delete this.$mutations
|
||||
this.$accessors = {}
|
||||
this.$computed = {}
|
||||
this.$track = ''
|
||||
} else {
|
||||
this.$accessors = {
|
||||
$model: modelAccessor
|
||||
}
|
||||
}
|
||||
if (dd === void 0) {
|
||||
this.$watch = platform.watchFactory(this.$events)
|
||||
this.$fire = platform.fireFactory(this.$events)
|
||||
} else {
|
||||
delete this.$watch
|
||||
delete this.$fire
|
||||
}
|
||||
}
|
||||
|
||||
platform.modelFactory = function modelFactory(definition, dd) {
|
||||
var $computed = definition.$computed || {}
|
||||
delete definition.$computed
|
||||
var core = new IProxy(definition, dd)
|
||||
var $accessors = core.$accessors
|
||||
var keys = []
|
||||
|
||||
platform.hideProperty(core, '$mutations', {})
|
||||
|
||||
for (let key in definition) {
|
||||
if (key in $$skipArray) continue
|
||||
var val = definition[key]
|
||||
keys.push(key)
|
||||
if (canHijack(key, val)) {
|
||||
$accessors[key] = createAccessor(key, val)
|
||||
}
|
||||
}
|
||||
for (let key in $computed) {
|
||||
if (key in $$skipArray) continue
|
||||
var val = $computed[key]
|
||||
if (typeof val === 'function') {
|
||||
val = {
|
||||
get: val
|
||||
}
|
||||
}
|
||||
if (val && val.get) {
|
||||
val.getter = val.get
|
||||
val.setter = val.set
|
||||
Anot.Array.ensure(keys, key)
|
||||
$accessors[key] = createAccessor(key, val, true)
|
||||
}
|
||||
}
|
||||
//将系统API以unenumerable形式加入vm,
|
||||
//添加用户的其他不可监听属性或方法
|
||||
//重写$track
|
||||
//并在IE6-8中增添加不存在的hasOwnPropert方法
|
||||
var vm = platform.createViewModel(core, $accessors, core)
|
||||
platform.afterCreate(vm, core, keys, !dd)
|
||||
return vm
|
||||
}
|
||||
var $proxyItemBackdoorMap = {}
|
||||
|
||||
export function canHijack(key, val, $proxyItemBackdoor) {
|
||||
if (key in $$skipArray) return false
|
||||
if (key.charAt(0) === '$') {
|
||||
if ($proxyItemBackdoor) {
|
||||
if (!$proxyItemBackdoorMap[key]) {
|
||||
$proxyItemBackdoorMap[key] = 1
|
||||
Anot.warn(`ms-for中的变量${key}不再建议以$为前缀`)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (val == null) {
|
||||
Anot.warn('定义vmodel时' + key + '的属性值不能为null undefine')
|
||||
return true
|
||||
}
|
||||
if (/error|date|function|regexp/.test(Anot.type(val))) {
|
||||
return false
|
||||
}
|
||||
return !(val && val.nodeName && val.nodeType)
|
||||
}
|
||||
|
||||
export function createProxy(target, dd) {
|
||||
if (target && target.$events) {
|
||||
return target
|
||||
}
|
||||
var vm
|
||||
if (Array.isArray(target)) {
|
||||
vm = platform.listFactory(target, false, dd)
|
||||
} else if (isObject(target)) {
|
||||
vm = platform.modelFactory(target, dd)
|
||||
}
|
||||
return vm
|
||||
}
|
||||
|
||||
platform.createProxy = createProxy
|
||||
|
||||
platform.itemFactory = function itemFactory(before, after) {
|
||||
var keyMap = before.$model
|
||||
var core = new IProxy(keyMap)
|
||||
var state = Anot.shadowCopy(core.$accessors, before.$accessors) //防止互相污染
|
||||
var data = after.data
|
||||
//core是包含系统属性的对象
|
||||
//keyMap是不包含系统属性的对象, keys
|
||||
for (var key in data) {
|
||||
var val = (keyMap[key] = core[key] = data[key])
|
||||
state[key] = createAccessor(key, val)
|
||||
}
|
||||
var keys = Object.keys(keyMap)
|
||||
var vm = platform.createViewModel(core, state, core)
|
||||
platform.afterCreate(vm, core, keys)
|
||||
return vm
|
||||
}
|
||||
|
||||
function createAccessor(key, val, isComputed) {
|
||||
var mutation = null
|
||||
var Accessor = isComputed ? Computed : Mutation
|
||||
return {
|
||||
get: function Getter() {
|
||||
if (!mutation) {
|
||||
mutation = new Accessor(key, val, this)
|
||||
}
|
||||
return mutation.get()
|
||||
},
|
||||
set: function Setter(newValue) {
|
||||
if (!mutation) {
|
||||
mutation = new Accessor(key, val, this)
|
||||
}
|
||||
mutation.set(newValue)
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
}
|
||||
}
|
||||
|
||||
platform.fuseFactory = function fuseFactory(before, after) {
|
||||
var keyMap = Anot.mix(before.$model, after.$model)
|
||||
var core = new IProxy(
|
||||
Anot.mix(keyMap, {
|
||||
$id: before.$id + after.$id
|
||||
})
|
||||
)
|
||||
var state = Anot.mix(core.$accessors, before.$accessors, after.$accessors) //防止互相污染
|
||||
|
||||
var keys = Object.keys(keyMap)
|
||||
//将系统API以unenumerable形式加入vm,并在IE6-8中添加hasOwnPropert方法
|
||||
var vm = platform.createViewModel(core, state, core)
|
||||
platform.afterCreate(vm, core, keys, false)
|
||||
return vm
|
||||
}
|
||||
|
||||
function toJson(val) {
|
||||
var xtype = Anot.type(val)
|
||||
if (xtype === 'array') {
|
||||
var array = []
|
||||
for (var i = 0; i < val.length; i++) {
|
||||
array[i] = toJson(val[i])
|
||||
}
|
||||
return array
|
||||
} else if (xtype === 'object') {
|
||||
if (typeof val.$track === 'string') {
|
||||
var obj = {}
|
||||
var arr = val.$track.match(/[^☥]+/g) || []
|
||||
arr.forEach(function(i) {
|
||||
var value = val[i]
|
||||
obj[i] = value && value.$events ? toJson(value) : value
|
||||
})
|
||||
return obj
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
var modelAccessor = {
|
||||
get: function() {
|
||||
return toJson(this)
|
||||
},
|
||||
set: Anot.noop,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
}
|
||||
|
||||
platform.toJson = toJson
|
||||
platform.modelAccessor = modelAccessor
|
|
@ -0,0 +1,147 @@
|
|||
import { Anot, config } from '../seed/core'
|
||||
|
||||
Anot.pendingActions = []
|
||||
Anot.uniqActions = {}
|
||||
Anot.inTransaction = 0
|
||||
config.trackDeps = false
|
||||
Anot.track = function() {
|
||||
if (config.trackDeps) {
|
||||
Anot.log.apply(Anot, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch is a pseudotransaction, just for purposes of memoizing ComputedValues when nothing else does.
|
||||
* During a batch `onBecomeUnobserved` will be called at most once per observable.
|
||||
* Avoids unnecessary recalculations.
|
||||
*/
|
||||
|
||||
export function runActions() {
|
||||
if (Anot.isRunningActions === true || Anot.inTransaction > 0) return
|
||||
Anot.isRunningActions = true
|
||||
var tasks = Anot.pendingActions.splice(0, Anot.pendingActions.length)
|
||||
for (var i = 0, task; (task = tasks[i++]); ) {
|
||||
task.update()
|
||||
delete Anot.uniqActions[task.uuid]
|
||||
}
|
||||
Anot.isRunningActions = false
|
||||
}
|
||||
|
||||
export function propagateChanged(target) {
|
||||
var list = target.observers
|
||||
for (var i = 0, el; (el = list[i++]); ) {
|
||||
el.schedule() //通知action, computed做它们该做的事
|
||||
}
|
||||
}
|
||||
|
||||
//将自己抛到市场上卖
|
||||
export function reportObserved(target) {
|
||||
var action = Anot.trackingAction || null
|
||||
if (action !== null) {
|
||||
Anot.track('征收到', target.expr)
|
||||
action.mapIDs[target.uuid] = target
|
||||
}
|
||||
}
|
||||
|
||||
var targetStack = []
|
||||
|
||||
export function collectDeps(action, getter) {
|
||||
if (!action.observers) return
|
||||
var preAction = Anot.trackingAction
|
||||
if (preAction) {
|
||||
targetStack.push(preAction)
|
||||
}
|
||||
Anot.trackingAction = action
|
||||
Anot.track('【action】', action.type, action.expr, '开始征收依赖项')
|
||||
//多个observe持有同一个action
|
||||
action.mapIDs = {} //重新收集依赖
|
||||
var hasError = true,
|
||||
result
|
||||
try {
|
||||
result = getter.call(action)
|
||||
hasError = false
|
||||
} finally {
|
||||
if (hasError) {
|
||||
Anot.warn('collectDeps fail', getter + '')
|
||||
action.mapIDs = {}
|
||||
Anot.trackingAction = preAction
|
||||
} else {
|
||||
// 确保它总是为null
|
||||
Anot.trackingAction = targetStack.pop()
|
||||
try {
|
||||
resetDeps(action)
|
||||
} catch (e) {
|
||||
Anot.warn(e)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
function resetDeps(action) {
|
||||
var prev = action.observers,
|
||||
curr = [],
|
||||
checked = {},
|
||||
ids = []
|
||||
for (let i in action.mapIDs) {
|
||||
let dep = action.mapIDs[i]
|
||||
if (!dep.isAction) {
|
||||
if (!dep.observers) {
|
||||
//如果它已经被销毁
|
||||
delete action.mapIDs[i]
|
||||
continue
|
||||
}
|
||||
ids.push(dep.uuid)
|
||||
curr.push(dep)
|
||||
checked[dep.uuid] = 1
|
||||
if (dep.lastAccessedBy === action.uuid) {
|
||||
continue
|
||||
}
|
||||
dep.lastAccessedBy = action.uuid
|
||||
Anot.Array.ensure(dep.observers, action)
|
||||
}
|
||||
}
|
||||
var ids = ids.sort().join(',')
|
||||
if (ids === action.ids) {
|
||||
return
|
||||
}
|
||||
action.ids = ids
|
||||
if (!action.isComputed) {
|
||||
action.observers = curr
|
||||
} else {
|
||||
action.depsCount = curr.length
|
||||
action.deps = Anot.mix({}, action.mapIDs)
|
||||
action.depsVersion = {}
|
||||
for (let i in action.mapIDs) {
|
||||
let dep = action.mapIDs[i]
|
||||
action.depsVersion[dep.uuid] = dep.version
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, dep; (dep = prev[i++]); ) {
|
||||
if (!checked[dep.uuid]) {
|
||||
Anot.Array.remove(dep.observers, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function transaction(action, thisArg, args) {
|
||||
args = args || []
|
||||
var name = 'transaction ' + (action.name || action.displayName || 'noop')
|
||||
transactionStart(name)
|
||||
var res = action.apply(thisArg, args)
|
||||
transactionEnd(name)
|
||||
return res
|
||||
}
|
||||
Anot.transaction = transaction
|
||||
|
||||
export function transactionStart(name) {
|
||||
Anot.inTransaction += 1
|
||||
}
|
||||
|
||||
export function transactionEnd(name) {
|
||||
if (--Anot.inTransaction === 0) {
|
||||
Anot.isRunningActions = false
|
||||
runActions()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 将要检测的字符串的字符串替换成??123这样的格式
|
||||
*/
|
||||
export var stringNum = 0
|
||||
export var stringPool = {
|
||||
map: {}
|
||||
}
|
||||
export var rfill = /\?\?\d+/g
|
||||
export function dig(a) {
|
||||
var key = '??' + stringNum++
|
||||
stringPool.map[key] = a
|
||||
return key + ' '
|
||||
}
|
||||
export function fill(a) {
|
||||
var val = stringPool.map[a]
|
||||
return val
|
||||
}
|
||||
export function clearString(str) {
|
||||
var array = readString(str)
|
||||
for (var i = 0, n = array.length; i < n; i++) {
|
||||
str = str.replace(array[i], dig)
|
||||
}
|
||||
return str
|
||||
}
|
||||
//https://github.com/RubyLouvre/Anot/issues/1944
|
||||
function readString(str, i, ret) {
|
||||
var end = false,
|
||||
s = 0,
|
||||
i = i || 0
|
||||
ret = ret || []
|
||||
for (var n = str.length; i < n; i++) {
|
||||
var c = str.charAt(i)
|
||||
if (!end) {
|
||||
if (c === "'") {
|
||||
end = "'"
|
||||
s = i
|
||||
} else if (c === '"') {
|
||||
end = '"'
|
||||
s = i
|
||||
}
|
||||
} else {
|
||||
if (c === end) {
|
||||
ret.push(str.slice(s, i + 1))
|
||||
end = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (end !== false) {
|
||||
return readString(str, s + 1, ret)
|
||||
}
|
||||
return ret
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import { orphanTag } from './orphanTag'
|
||||
import { voidTag } from './voidTag'
|
||||
import { makeOrphan } from './makeOrphan'
|
||||
|
||||
export function fromDOM(dom) {
|
||||
return [from(dom)]
|
||||
}
|
||||
|
||||
export function from(node) {
|
||||
var type = node.nodeName.toLowerCase()
|
||||
switch (type) {
|
||||
case '#text':
|
||||
case '#comment':
|
||||
return {
|
||||
nodeName: type,
|
||||
dom: node,
|
||||
nodeValue: node.nodeValue
|
||||
}
|
||||
default:
|
||||
var props = markProps(node, node.attributes || [])
|
||||
var vnode = {
|
||||
nodeName: type,
|
||||
dom: node,
|
||||
isVoidTag: !!voidTag[type],
|
||||
props: props
|
||||
}
|
||||
if(type === 'option'){
|
||||
//即便你设置了option.selected = true,
|
||||
//option.attributes也找不到selected属性
|
||||
props.selected = node.selected
|
||||
}
|
||||
if (orphanTag[type] || type === 'option') {
|
||||
makeOrphan(vnode, type, node.text || node.innerHTML)
|
||||
if (node.childNodes.length === 1) {
|
||||
vnode.children[0].dom = node.firstChild
|
||||
}
|
||||
} else if (!vnode.isVoidTag) {
|
||||
vnode.children = []
|
||||
for (var i = 0, el; el = node.childNodes[i++];) {
|
||||
var child = from(el)
|
||||
if (/\S/.test(child.nodeValue)) {
|
||||
vnode.children.push(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
return vnode
|
||||
}
|
||||
}
|
||||
|
||||
var rformElement = /input|textarea|select/i
|
||||
|
||||
function markProps(node, attrs) {
|
||||
var ret = {}
|
||||
for (var i = 0, n = attrs.length; i < n; i++) {
|
||||
var attr = attrs[i]
|
||||
if (attr.specified) {
|
||||
//IE6-9不会将属性名变小写,比如它会将用户的contenteditable变成contentEditable
|
||||
ret[attr.name.toLowerCase()] = attr.value
|
||||
}
|
||||
}
|
||||
if (rformElement.test(node.nodeName)) {
|
||||
ret.type = node.type
|
||||
var a = node.getAttributeNode('value')
|
||||
if (a && /\S/.test(a.value)) { //IE6,7中无法取得checkbox,radio的value
|
||||
ret.value = a.value
|
||||
}
|
||||
|
||||
}
|
||||
var style = node.style.cssText
|
||||
if (style) {
|
||||
ret.style = style
|
||||
}
|
||||
//类名 = 去重(静态类名+动态类名+ hover类名? + active类名)
|
||||
if (ret.type === 'select-one') {
|
||||
ret.selectedIndex = node.selectedIndex
|
||||
}
|
||||
return ret
|
||||
}
|
|
@ -0,0 +1,422 @@
|
|||
/**
|
||||
* ------------------------------------------------------------
|
||||
* Anot2.2.6的新式lexer
|
||||
* 将字符串变成一个虚拟DOM树,方便以后进一步变成模板函数
|
||||
* 此阶段只会生成VElement,VText,VComment
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
import { Anot, Cache, config } from '../seed/core'
|
||||
import { voidTag } from './voidTag'
|
||||
|
||||
import { validateDOMNesting } from './validateDOMNesting'
|
||||
|
||||
var specalTag = {
|
||||
xmp: 1,
|
||||
style: 1,
|
||||
script: 1,
|
||||
noscript: 1,
|
||||
textarea: 1,
|
||||
'#comment': 1,
|
||||
template: 1
|
||||
}
|
||||
var hiddenTag = { style: 1, script: 1, noscript: 1, template: 1 }
|
||||
var rcontent = /\S/ //判定里面有没有内容
|
||||
var rsp = /\s/
|
||||
export function fromString(str) {
|
||||
return from(str)
|
||||
}
|
||||
Anot.lexer = fromString
|
||||
|
||||
var strCache = new Cache(100)
|
||||
|
||||
function from(str) {
|
||||
var cacheKey = str
|
||||
var cached = strCache.get(cacheKey)
|
||||
if (cached) {
|
||||
return Anot.mix(true, [], cached)
|
||||
}
|
||||
|
||||
var ret = parse(str, false)
|
||||
strCache.put(cacheKey, Anot.mix(true, [], ret))
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {any} string
|
||||
* @param {any} getOne 只返回一个节点
|
||||
* @returns
|
||||
*/
|
||||
function parse(string, getOne) {
|
||||
getOne = getOne === void 666 || getOne === true
|
||||
var ret = lexer(string, getOne)
|
||||
if (getOne) {
|
||||
return typeof ret[0] === 'string' ? ret[1] : ret[0]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
function lexer(string, getOne) {
|
||||
var tokens = []
|
||||
var breakIndex = 9990
|
||||
var stack = []
|
||||
var origString = string
|
||||
var origLength = string.length
|
||||
|
||||
stack.last = function() {
|
||||
return stack[stack.length - 1]
|
||||
}
|
||||
var ret = []
|
||||
|
||||
function addNode(node) {
|
||||
var p = stack.last()
|
||||
if (p && p.children) {
|
||||
p.children.push(node)
|
||||
} else {
|
||||
ret.push(node)
|
||||
}
|
||||
}
|
||||
|
||||
var lastNode
|
||||
do {
|
||||
if (--breakIndex === 0) {
|
||||
break
|
||||
}
|
||||
var arr = getCloseTag(string)
|
||||
|
||||
if (arr) {
|
||||
//处理关闭标签
|
||||
string = string.replace(arr[0], '')
|
||||
const node = stack.pop()
|
||||
if (!node) {
|
||||
throw '是不是有属性值没有用引号括起'
|
||||
}
|
||||
//处理下面两种特殊情况:
|
||||
//1. option会自动移除元素节点,将它们的nodeValue组成新的文本节点
|
||||
//2. table会将没有被thead, tbody, tfoot包起来的tr或文本节点,收集到一个新的tbody元素中
|
||||
|
||||
if (node.nodeName === 'option') {
|
||||
node.children = [
|
||||
{
|
||||
nodeName: '#text',
|
||||
nodeValue: getText(node)
|
||||
}
|
||||
]
|
||||
} else if (node.nodeName === 'table') {
|
||||
insertTbody(node.children)
|
||||
}
|
||||
lastNode = null
|
||||
if (getOne && ret.length === 1 && !stack.length) {
|
||||
return [origString.slice(0, origLength - string.length), ret[0]]
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var arr = getOpenTag(string)
|
||||
if (arr) {
|
||||
string = string.replace(arr[0], '')
|
||||
var node = arr[1]
|
||||
addNode(node)
|
||||
var selfClose = !!(node.isVoidTag || specalTag[node.nodeName])
|
||||
if (!selfClose) {
|
||||
//放到这里可以添加孩子
|
||||
stack.push(node)
|
||||
}
|
||||
if (getOne && selfClose && !stack.length) {
|
||||
return [origString.slice(0, origLength - string.length), node]
|
||||
}
|
||||
lastNode = node
|
||||
continue
|
||||
}
|
||||
|
||||
var text = ''
|
||||
do {
|
||||
//处理<div><<<<<<div>的情况
|
||||
const index = string.indexOf('<')
|
||||
if (index === 0) {
|
||||
text += string.slice(0, 1)
|
||||
string = string.slice(1)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} while (string.length)
|
||||
|
||||
//处理<div>{aaa}</div>,<div>xxx{aaa}xxx</div>,<div>xxx</div>{aaa}sss的情况
|
||||
const index = string.indexOf('<') //判定它后面是否存在标签
|
||||
if (index === -1) {
|
||||
text = string
|
||||
string = ''
|
||||
} else {
|
||||
const openIndex = string.indexOf(config.openTag)
|
||||
|
||||
if (openIndex !== -1 && openIndex < index) {
|
||||
if (openIndex !== 0) {
|
||||
text += string.slice(0, openIndex)
|
||||
}
|
||||
var dirString = string.slice(openIndex)
|
||||
var textDir = parseTextDir(dirString)
|
||||
text += textDir
|
||||
string = dirString.slice(textDir.length)
|
||||
} else {
|
||||
text += string.slice(0, index)
|
||||
string = string.slice(index)
|
||||
}
|
||||
}
|
||||
var mayNode = addText(lastNode, text, addNode)
|
||||
if (mayNode) {
|
||||
lastNode = mayNode
|
||||
}
|
||||
} while (string.length)
|
||||
return ret
|
||||
}
|
||||
|
||||
function addText(lastNode, text, addNode) {
|
||||
if (rcontent.test(text)) {
|
||||
if (lastNode && lastNode.nodeName === '#text') {
|
||||
lastNode.nodeValue += text
|
||||
return lastNode
|
||||
} else {
|
||||
lastNode = {
|
||||
nodeName: '#text',
|
||||
nodeValue: text
|
||||
}
|
||||
addNode(lastNode)
|
||||
return lastNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseTextDir(string) {
|
||||
var closeTag = config.closeTag
|
||||
var openTag = config.openTag
|
||||
var closeTagFirst = closeTag.charAt(0)
|
||||
var closeTagLength = closeTag.length
|
||||
var state = 'code',
|
||||
quote,
|
||||
escape
|
||||
for (var i = openTag.length, n = string.length; i < n; i++) {
|
||||
var c = string.charAt(i)
|
||||
switch (state) {
|
||||
case 'code':
|
||||
if (c === '"' || c === "'") {
|
||||
state = 'string'
|
||||
quote = c
|
||||
} else if (c === closeTagFirst) {
|
||||
//如果遇到}
|
||||
if (string.substr(i, closeTagLength) === closeTag) {
|
||||
return string.slice(0, i + closeTagLength)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'string':
|
||||
if (c === '\\' && /"'/.test(string.charAt(i + 1))) {
|
||||
escape = !escape
|
||||
}
|
||||
if (c === quote && !escape) {
|
||||
state = 'code'
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
throw '找不到界定符' + closeTag
|
||||
}
|
||||
|
||||
var rtbody = /^(tbody|thead|tfoot)$/
|
||||
|
||||
function insertTbody(nodes) {
|
||||
var tbody = false
|
||||
for (var i = 0, n = nodes.length; i < n; i++) {
|
||||
var node = nodes[i]
|
||||
if (rtbody.test(node.nodeName)) {
|
||||
tbody = false
|
||||
continue
|
||||
}
|
||||
|
||||
if (node.nodeName === 'tr') {
|
||||
if (tbody) {
|
||||
nodes.splice(i, 1)
|
||||
tbody.children.push(node)
|
||||
n--
|
||||
i--
|
||||
} else {
|
||||
tbody = {
|
||||
nodeName: 'tbody',
|
||||
props: {},
|
||||
children: [node]
|
||||
}
|
||||
nodes.splice(i, 1, tbody)
|
||||
}
|
||||
} else {
|
||||
if (tbody) {
|
||||
nodes.splice(i, 1)
|
||||
tbody.children.push(node)
|
||||
n--
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//<div>{{<div/>}}</div>
|
||||
function getCloseTag(string) {
|
||||
if (string.indexOf('</') === 0) {
|
||||
var match = string.match(/\<\/(\w+[^\s\/\>]*)>/)
|
||||
if (match) {
|
||||
var tag = match[1]
|
||||
string = string.slice(3 + tag.length)
|
||||
return [
|
||||
match[0],
|
||||
{
|
||||
nodeName: tag
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
var ropenTag = /\<(\w[^\s\/\>]*)/
|
||||
|
||||
function getOpenTag(string) {
|
||||
if (string.indexOf('<') === 0) {
|
||||
var i = string.indexOf('<!--') //处理注释节点
|
||||
if (i === 0) {
|
||||
var l = string.indexOf('-->')
|
||||
if (l === -1) {
|
||||
thow('注释节点没有闭合 ' + string.slice(0, 100))
|
||||
}
|
||||
var node = {
|
||||
nodeName: '#comment',
|
||||
nodeValue: string.slice(4, l)
|
||||
}
|
||||
return [string.slice(0, l + 3), node]
|
||||
}
|
||||
var match = string.match(ropenTag) //处理元素节点
|
||||
if (match) {
|
||||
var leftContent = match[0],
|
||||
tag = match[1]
|
||||
var node = {
|
||||
nodeName: tag,
|
||||
props: {},
|
||||
children: []
|
||||
}
|
||||
|
||||
string = string.replace(leftContent, '') //去掉标签名(rightContent)
|
||||
try {
|
||||
var arr = getAttrs(string) //处理属性
|
||||
} catch (e) {}
|
||||
if (arr) {
|
||||
node.props = arr[1]
|
||||
string = string.replace(arr[0], '')
|
||||
leftContent += arr[0]
|
||||
}
|
||||
|
||||
if (string.charAt(0) === '>') {
|
||||
//处理开标签的边界符
|
||||
leftContent += '>'
|
||||
string = string.slice(1)
|
||||
if (voidTag[node.nodeName]) {
|
||||
node.isVoidTag = true
|
||||
}
|
||||
} else if (string.slice(0, 2) === '/>') {
|
||||
//处理开标签的边界符
|
||||
leftContent += '/>'
|
||||
string = string.slice(2)
|
||||
node.isVoidTag = true
|
||||
}
|
||||
|
||||
if (!node.isVoidTag && specalTag[tag]) {
|
||||
//如果是script, style, xmp等元素
|
||||
var closeTag = '</' + tag + '>'
|
||||
var j = string.indexOf(closeTag)
|
||||
var nodeValue = string.slice(0, j)
|
||||
leftContent += nodeValue + closeTag
|
||||
node.children.push({
|
||||
nodeName: '#text',
|
||||
nodeValue: nodeValue
|
||||
})
|
||||
if (tag === 'textarea') {
|
||||
node.props.type = tag
|
||||
node.props.value = nodeValue
|
||||
}
|
||||
}
|
||||
return [leftContent, node]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getText(node) {
|
||||
var ret = ''
|
||||
node.children.forEach(function(el) {
|
||||
if (el.nodeName === '#text') {
|
||||
ret += el.nodeValue
|
||||
} else if (el.children && !hiddenTag[el.nodeName]) {
|
||||
ret += getText(el)
|
||||
}
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
function getAttrs(string) {
|
||||
var state = 'AttrName',
|
||||
attrName = '',
|
||||
attrValue = '',
|
||||
quote,
|
||||
escape,
|
||||
props = {}
|
||||
for (var i = 0, n = string.length; i < n; i++) {
|
||||
var c = string.charAt(i)
|
||||
switch (state) {
|
||||
case 'AttrName':
|
||||
if ((c === '/' && string.charAt(i + 1) === '>') || c === '>') {
|
||||
if (attrName) props[attrName] = attrName
|
||||
return [string.slice(0, i), props]
|
||||
}
|
||||
if (rsp.test(c)) {
|
||||
if (attrName) {
|
||||
state = 'AttrEqual'
|
||||
}
|
||||
} else if (c === '=') {
|
||||
if (!attrName) {
|
||||
throw '必须指定属性名'
|
||||
}
|
||||
state = 'AttrQuote'
|
||||
} else {
|
||||
attrName += c
|
||||
}
|
||||
break
|
||||
case 'AttrEqual':
|
||||
if (c === '=') {
|
||||
state = 'AttrQuote'
|
||||
} else if (rcontent.test(c)) {
|
||||
props[attrName] = attrName
|
||||
attrName = c
|
||||
state = 'AttrName'
|
||||
}
|
||||
break
|
||||
case 'AttrQuote':
|
||||
if (c === '"' || c === "'") {
|
||||
quote = c
|
||||
state = 'AttrValue'
|
||||
escape = false
|
||||
}
|
||||
break
|
||||
case 'AttrValue':
|
||||
if (c === '\\' && /"'/.test(string.charAt(i + 1))) {
|
||||
escape = !escape
|
||||
}
|
||||
if (c === '\n') {
|
||||
break
|
||||
}
|
||||
if (c !== quote) {
|
||||
attrValue += c
|
||||
} else if (c === quote && !escape) {
|
||||
props[attrName] = attrValue
|
||||
attrName = attrValue = ''
|
||||
state = 'AttrName'
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
throw '必须关闭标签'
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 此模块只用于文本转虚拟DOM,
|
||||
* 因为在真实浏览器会对我们的HTML做更多处理,
|
||||
* 如, 添加额外属性, 改变结构
|
||||
* 此模块就是用于模拟这些行为
|
||||
*/
|
||||
export function makeOrphan(node, nodeName, innerHTML) {
|
||||
switch (nodeName) {
|
||||
case 'style':
|
||||
case 'script':
|
||||
case 'noscript':
|
||||
case 'template':
|
||||
case 'xmp':
|
||||
node.children = [
|
||||
{
|
||||
nodeName: '#text',
|
||||
nodeValue: innerHTML
|
||||
}
|
||||
]
|
||||
break
|
||||
case 'textarea':
|
||||
var props = node.props
|
||||
props.type = nodeName
|
||||
props.value = innerHTML
|
||||
node.children = [
|
||||
{
|
||||
nodeName: '#text',
|
||||
nodeValue: innerHTML
|
||||
}
|
||||
]
|
||||
break
|
||||
case 'option':
|
||||
node.children = [
|
||||
{
|
||||
nodeName: '#text',
|
||||
nodeValue: trimHTML(innerHTML)
|
||||
}
|
||||
]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//专门用于处理option标签里面的标签
|
||||
var rtrimHTML = /<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi
|
||||
function trimHTML(v) {
|
||||
return String(v)
|
||||
.replace(rtrimHTML, '')
|
||||
.trim()
|
||||
}
|
||||
|
||||
//widget rule duplex validate
|
|
@ -0,0 +1,35 @@
|
|||
//只有遇到第一个直接放在table下的tr元素,才会插入新tbody,并收集接下来的其他非tbody, thead, tfoot元素
|
||||
|
||||
var rtbody = /^(tbody|thead|tfoot)$/
|
||||
export function makeTbody(nodes) {
|
||||
var tbody = false
|
||||
for (var i = 0, n = nodes.length; i < n; i++) {
|
||||
var node = nodes[i]
|
||||
if (rtbody.test(node.nodeName)) {
|
||||
tbody = false
|
||||
continue
|
||||
}
|
||||
if (node.nodeName === 'tr') {
|
||||
if (tbody) {
|
||||
nodes.splice(i, 1)
|
||||
tbody.children.push(node)
|
||||
n--
|
||||
i--
|
||||
} else {
|
||||
tbody = {
|
||||
nodeName: 'tbody',
|
||||
props: {},
|
||||
children: [node]
|
||||
}
|
||||
nodes.splice(i, 1, tbody)
|
||||
}
|
||||
} else {
|
||||
if (tbody) {
|
||||
nodes.splice(i, 1)
|
||||
tbody.children.push(node)
|
||||
n--
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export var orphanTag = {
|
||||
script: 1,
|
||||
style: 1,
|
||||
textarea: 1,
|
||||
xmp: 1,
|
||||
noscript: 1,
|
||||
template: 1
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import { Anot, oneObject } from '../seed/core'
|
||||
|
||||
export function validateDOMNesting(parent, child) {
|
||||
var parentTag = parent.nodeName
|
||||
var tag = child.nodeName
|
||||
var parentChild = nestObject[parentTag]
|
||||
if (parentChild) {
|
||||
if (parentTag === 'p') {
|
||||
if (pNestChild[tag]) {
|
||||
Anot.warn(
|
||||
'P element can not add these childlren:\n' + Object.keys(pNestChild)
|
||||
)
|
||||
return false
|
||||
}
|
||||
} else if (!parentChild[tag]) {
|
||||
Anot.warn(
|
||||
parentTag.toUpperCase() +
|
||||
'element only add these children:\n' +
|
||||
Object.keys(parentChild) +
|
||||
'\nbut you add ' +
|
||||
tag.toUpperCase() +
|
||||
' !!'
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function makeObject(str) {
|
||||
return oneObject(str + ',template,#document-fragment,#comment')
|
||||
}
|
||||
var pNestChild = oneObject('div,ul,ol,dl,table,h1,h2,h3,h4,h5,h6,form,fieldset')
|
||||
var tNestChild = makeObject('tr,style,script')
|
||||
var nestObject = {
|
||||
p: pNestChild,
|
||||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect
|
||||
select: makeObject('option,optgroup,#text'),
|
||||
optgroup: makeObject('option,#text'),
|
||||
option: makeObject('#text'),
|
||||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd
|
||||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption
|
||||
// No special behavior since these rules fall back to "in body" mode for
|
||||
// all except special table nodes which cause bad parsing behavior anyway.
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr
|
||||
tr: makeObject('th,td,style,script'),
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody
|
||||
tbody: tNestChild,
|
||||
tfoot: tNestChild,
|
||||
thead: tNestChild,
|
||||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup
|
||||
colgroup: makeObject('col'),
|
||||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable
|
||||
// table: oneObject('caption,colgroup,tbody,thead,tfoot,style,script,template,#document-fragment'),
|
||||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead
|
||||
head: makeObject(
|
||||
'base,basefont,bgsound,link,style,script,meta,title,noscript,noframes'
|
||||
),
|
||||
// https://html.spec.whatwg.org/multipage/semantics.html#the-html-element
|
||||
html: oneObject('head,body')
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
export var voidTag = {
|
||||
area: 1,
|
||||
base: 1,
|
||||
basefont: 1,
|
||||
bgsound: 1,
|
||||
br: 1,
|
||||
col: 1,
|
||||
command: 1,
|
||||
embed: 1,
|
||||
frame: 1,
|
||||
hr: 1,
|
||||
img: 1,
|
||||
input: 1,
|
||||
keygen: 1,
|
||||
link: 1,
|
||||
meta: 1,
|
||||
param: 1,
|
||||
source: 1,
|
||||
track: 1,
|
||||
wbr: 1
|
||||
}
|
Reference in New Issue