1 Star 0 Fork 0

zhengchen/avalon

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
avalon.mobile.js 142.98 KB
一键复制 编辑 原始数据 按行查看 历史
司徒正美 提交于 2014-03-17 18:11 . fix parseExpr bug
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609
//==================================================
// avalon.mobile 1.2.3 2014.3.14,mobile 注意: 只能用于IE10及高版本的标准浏览器
//==================================================
(function(DOC) {
var Registry = {} //将函数曝光到此对象上,方便访问器收集依赖
var expose = Date.now()
var subscribers = "$" + expose
var window = this || (0, eval)('this')
var otherRequire = window.require
var otherDefine = window.define
var stopRepeatAssign = false
var rword = /[^, ]+/g //切割字符串为一个个小块,以空格或豆号分开它们,结合replace实现字符串的forEach
var class2type = {}
var oproto = Object.prototype
var ohasOwn = oproto.hasOwnProperty
var prefix = "ms-"
var root = DOC.documentElement
var serialize = oproto.toString
var ap = Array.prototype
var aslice = ap.slice
var head = DOC.head //HEAD元素
var documentFragment = DOC.createDocumentFragment()
"Boolean Number String Function Array Date RegExp Object Error".replace(rword, function(name) {
class2type["[object " + name + "]"] = name.toLowerCase()
})
var rchecktype = /^(?:object|array)$/
var rwindow = /^\[object (Window|DOMWindow|global)\]$/
function noop() {
}
function log(a) {
window.console && console.log(a)
}
/*********************************************************************
* 命名空间与工具函数 *
**********************************************************************/
window.avalon = function(el) { //创建jQuery式的无new 实例化结构
return new avalon.init(el)
}
avalon.init = function(el) {
this[0] = this.element = el
}
avalon.fn = avalon.prototype = avalon.init.prototype
//率先添加三个判定类型的方法
function getType(obj) { //取得类型
if (obj == null) {
return String(obj)
}
// 早期的webkit内核浏览器实现了已废弃的ecma262v4标准,可以将正则字面量当作函数使用,因此typeof在判定正则时会返回function
return typeof obj === "object" || typeof obj === "function" ?
class2type[serialize.call(obj)] || "object" :
typeof obj
}
avalon.type = getType
avalon.isWindow = function(obj) {
return rwindow.test(serialize.call(obj))
}
//判定是否是一个朴素的javascript对象(Object),不是DOM对象,不是BOM对象,不是自定义类的实例
avalon.isPlainObject = function(obj) {
return !!obj && typeof obj === "object" && Object.getPrototypeOf(obj) === oproto
}
avalon.mix = avalon.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" && getType(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 && (avalon.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false
clone = src && Array.isArray(src) ? src : []
} else {
clone = src && avalon.isPlainObject(src) ? src : {}
}
target[name] = avalon.mix(deep, clone, copy)
} else if (copy !== void 0) {
target[name] = copy
}
}
}
}
return target
}
var eventMap = avalon.eventMap = {}
function resetNumber(a, n, end) { //用于模拟slice, splice的效果
if ((a === +a) && !(a % 1)) { //如果是整数
if (a < 0) { //范围调整为 [-a, a]
a = a * -1 >= n ? 0 : a + n
} else {
a = a > n ? n : a
}
} else {
a = end ? n : 0
}
return a
}
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
}
avalon.mix({
rword: rword,
subscribers: subscribers,
ui: {},
models: {},
log: log,
noop: noop,
error: function(str, e) { //如果不用Error对象封装一下,str在控制台下可能会乱码
throw new (e || Error)(str)
},
oneObject: oneObject,
/* avalon.range(10)
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
avalon.range(1, 11)
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
avalon.range(0, 30, 5)
=> [0, 5, 10, 15, 20, 25]
avalon.range(0, -10, -1)
=> [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
avalon.range(0)
=> []*/
range: function(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 = Array(length)
while (++index < length) {
result[index] = start
start += step
}
return result
},
slice: function(nodes, start, end) {
return aslice.call(nodes, start, end)
},
contains: function(a, b) {
return a.contains(b)
},
bind: function(el, type, fn, phase) {
el.addEventListener(eventMap[type] || type, fn, !!phase)
return fn
},
unbind: function(el, type, fn, phase) {
el.removeEventListener(eventMap[type] || type, fn || noop, !!phase)
},
fire: function(el, name) {
var event = DOC.createEvent("Event")
event.initEvent(name, true, true)
el.dispatchEvent(event)
},
css: function(node, name, value) {
if (node instanceof avalon) {
node = node[0]
}
var prop = /[_-]/.test(name) ? camelize(name) : name
name = avalon.cssName(prop) || prop
if (value === void 0 || typeof value === "boolean") { //获取样式
var fn = cssHooks[prop + ":get"] || cssHooks["@:get"]
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) && !avalon.cssNumber[prop]) {
value += "px"
}
fn = cssHooks[prop + ":set"] || cssHooks["@:set"]
fn(node, name, value)
}
},
each: function(obj, fn) {
if (obj) { //排除null, undefined
var i = 0
if (isArrayLike(obj)) {
for (var n = obj.length; i < n; i++) {
fn(i, obj[i])
}
} else {
for (i in obj) {
if (obj.hasOwnProperty(i)) {
fn(i, obj[i])
}
}
}
}
},
getWidgetData: function(elem, prefix) {
var raw = avalon(elem).data()
var result = {}
for (var i in raw) {
if (i.indexOf(prefix) === 0) {
result[i.replace(prefix, "").replace(/\w/, function(a) {
return a.toLowerCase()
})] = raw[i]
}
}
return result
},
getVModel: function(prop, vmodels) {//得到当前属性prop所在的VM
for (var i = 0, el; el = vmodels[i++]; ) {
if (el.hasOwnProperty(prop)) {
return el
}
}
},
Array: {
ensure: function(target, item) {
//只有当前数组不存在此元素时只添加它
if (target.indexOf(item) === -1) {
target.push(item)
}
return target
},
removeAt: function(target, index) {
//移除数组中指定位置的元素,返回布尔表示成功与否。
return !!target.splice(index, 1).length
},
remove: function(target, item) {
//移除数组中第一个匹配传参的那个元素,返回布尔表示成功与否。
var index = target.indexOf(item)
if (~index)
return avalon.Array.removeAt(target, index)
return false
}
}
})
function generateID() {
//生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
return "avalon" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
}
//只让节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象通过
function isArrayLike(obj) {
if (obj && typeof obj === "object") {
var n = obj.length,
str = serialize.call(obj)
if (/Array|NodeList|Arguments|CSSRuleList/.test(str)) {
return true
} else if (str === "[object Object]" && (+n === n && !(n % 1) && n >= 0)) {
return true //由于ecma262v5能修改对象属性的enumerable,因此不能用propertyIsEnumerable来判定了
}
}
return false
}
//视浏览器情况采用最快的异步回调
avalon.nextTick = window.setImmediate ? setImmediate.bind(window) : function(callback) {
setTimeout(callback, 0)
}
if (!root.contains) { //safari5+是把contains方法放在Element.prototype上而不是Node.prototype
Node.prototype.contains = function(arg) {
return !!(this.compareDocumentPosition(arg) & 16)
}
}
/*********************************************************************
* modelFactory *
**********************************************************************/
var VMODELS = avalon.vmodels = {}
avalon.define = function(name, factory) {
if (typeof name !== "string") {
avalon.error("必须指定ID")
}
if (typeof factory !== "function") {
avalon.error("factory必须是函数")
}
var scope = {
$watch: noop
}
factory(scope) //得到所有定义
var model = modelFactory(scope) //偷天换日,将scope换为model
stopRepeatAssign = true
factory(model)
stopRepeatAssign = false
model.$id = name
return VMODELS[name] = model
}
function modelFactory(scope, model) {
if (Array.isArray(scope)) {
var arr = scope.concat()//原数组的作为新生成的监控数组的$model而存在
scope.length = 0
var collection = Collection(scope)
collection.push.apply(collection, arr)
return collection
}
if (typeof scope.nodeType === "number") {
return scope
}
var vmodel = {} //要返回的对象
model = model || {} //放置$model上的属性
var accessingProperties = {} //监控属性
var normalProperties = {} //普通属性
var computedProperties = [] //计算属性
var watchProperties = arguments[2] || {} //强制要监听的属性
var skipArray = scope.$skipArray //要忽略监控的属性
for (var i = 0, name; name = skipProperties[i++]; ) {
delete scope[name]
normalProperties[name] = true
}
if (Array.isArray(skipArray)) {
for (var i = 0, name; name = skipArray[i++]; ) {
normalProperties[name] = true
}
}
for (var i in scope) {
loopModel(i, scope[i], model, normalProperties, accessingProperties, computedProperties, watchProperties)
}
vmodel = Object.defineProperties(vmodel, descriptorFactory(accessingProperties)) //生成一个空的ViewModel
for (var name in normalProperties) {
vmodel[name] = normalProperties[name]
}
watchProperties.vmodel = vmodel
vmodel.$model = model
vmodel.$events = {}
vmodel.$id = generateID()
vmodel.$accessors = accessingProperties
vmodel[subscribers] = []
for (var i in Observable) {
vmodel[i] = Observable[i]
}
Object.defineProperty(vmodel, "hasOwnProperty", {
value: function(name) {
return name in vmodel.$model
},
writable: false,
enumerable: false,
configurable: true
})
for (var i = 0, fn; fn = computedProperties[i++]; ) { //最后强逼计算属性 计算自己的值
Registry[expose] = fn
fn()
collectSubscribers(fn)
delete Registry[expose]
}
return vmodel
}
var skipProperties = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray,$accessors," + subscribers).match(rword)
function isEqual(x, y) {
if (x === y) {
return x instanceof Date ? x - 0 === y - 0 : !0
}
return x !== x && y !== y
}
function safeFire(a, b, c, d) {
if (a.$events) {
Observable.$fire.call(a, b, c, d)
}
}
function descriptorFactory(obj) {
var descriptors = {}
for (var i in obj) {
descriptors[i] = {
get: obj[i],
set: obj[i],
enumerable: true,
configurable: true
}
}
return descriptors
}
function loopModel(name, val, model, normalProperties, accessingProperties, computedProperties, watchProperties) {
model[name] = val
if (normalProperties[name] || (val && val.nodeType)) { //如果是指明不用监控的系统属性或元素节点,或放到 $skipArray里面
return normalProperties[name] = val
}
if (name.charAt(0) === "$" && !watchProperties[name]) { //如果是$开头,并且不在watchMore里面的
return normalProperties[name] = val
}
var valueType = getType(val)
if (valueType === "function") { //如果是函数,也不用监控
return normalProperties[name] = val
}
var accessor, oldArgs
if (valueType === "object" && typeof val.get === "function" && Object.keys(val).length <= 2) {
var setter = val.set,
getter = val.get
accessor = function(newValue) { //创建计算属性,因变量,基本上由其他监控属性触发其改变
var vmodel = watchProperties.vmodel
var value = model[name],
preValue = value
if (arguments.length) {
if (stopRepeatAssign) {
return //阻止重复赋值
}
if (typeof setter === "function") {
var backup = vmodel.$events[name]
vmodel.$events[name] = [] //清空回调,防止内部冒泡而触发多次$fire
setter.call(vmodel, newValue)
vmodel.$events[name] = backup
}
if (!isEqual(oldArgs, newValue)) { //只检测用户的传参是否与上次是否一致
oldArgs = newValue
newValue = model[name] = getter.call(vmodel)
withProxyCount && updateWithProxy(vmodel.$id, name, newValue)
notifySubscribers(accessor) //通知顶层改变
safeFire(vmodel, name, newValue, preValue)
}
} else {
if (avalon.openComputedCollect) { // 收集视图刷新函数
collectSubscribers(accessor)
}
newValue = model[name] = getter.call(vmodel)
if (!isEqual(value, newValue)) {
oldArgs = void 0
safeFire(vmodel, name, newValue, preValue)
}
return newValue
}
}
accessor[subscribers] = [] //订阅者数组
computedProperties.push(accessor)
} else {
accessor = function(newValue) { //创建监控属性或数组,自变量,由用户触发其改变
var vmodel = watchProperties.vmodel
var preValue = model[name],
simpleType
if (arguments.length) {
if (stopRepeatAssign) {
return //阻止重复赋值
}
if (!isEqual(preValue, newValue)) {
if (rchecktype.test(valueType)) {
var value = accessor.$vmodel = updateVModel(accessor.$vmodel, newValue, valueType)
var fn = rebindings[value.$id]
fn && fn()
withProxyCount && updateWithProxy(vmodel.$id, name, value)
safeFire(vmodel, name, value.$model, preValue)
accessor[subscribers] = value[subscribers]
model[name] = value.$model
} else { //如果是其他数据类型
model[name] = newValue //更新$model中的值
simpleType = true
withProxyCount && updateWithProxy(vmodel.$id, name, newValue)
}
notifySubscribers(accessor) //通知顶层改变
if (simpleType) {
safeFire(vmodel, name, newValue, preValue)
}
}
} else {
collectSubscribers(accessor) //收集视图函数
return accessor.$vmodel || preValue
}
}
accessor[subscribers] = [] //订阅者数组
if (rchecktype.test(valueType)) {
var complexValue = val.$model ? val : modelFactory(val, val)
accessor.$vmodel = complexValue
accessor[subscribers] = complexValue[subscribers]
model[name] = complexValue.$model
} else {
model[name] = val
}
}
accessingProperties[name] = accessor
}
//with绑定生成的代理对象储存池
var withProxyPool = {}
var withProxyCount = 0
var rebindings = {}
function updateWithProxy($id, name, val) {
var pool = withProxyPool[$id]
if (pool && pool[name]) {
pool[name].$val = val
}
}
function updateVModel(a, b, valueType) {
//a为原来的VM, b为新数组或新对象
if (valueType === "array") {
if (!Array.isArray(b)) {
return a //fix https://github.com/RubyLouvre/avalon/issues/261
}
var bb = b.concat()
a.clear()
a.push.apply(a, bb)
return a
} else {
var iterators = a[subscribers]
if (withProxyPool[a.$id]) {
withProxyCount--
delete withProxyPool[a.$id]
}
iterators.forEach(function(data) {
data.rollback && data.rollback()
})
var ret = modelFactory(b)
rebindings[ret.$id] = function(data) {
while (data = iterators.shift()) {
(function(el) {
if (el.type) {
avalon.nextTick(function() {
bindingHandlers[el.type](el, el.vmodels)
})
}
})(data)
}
delete rebindings[ret.$id]
}
return ret
}
}
/*********************************************************************
* 配置模块 *
**********************************************************************/
function kernel(settings) {
for (var p in settings) {
if (!ohasOwn.call(settings, p))
continue
var val = settings[p]
if (typeof kernel.plugins[p] === "function") {
kernel.plugins[p](val)
} else if (typeof kernel[p] === "object") {
avalon.mix(kernel[p], val)
} else {
kernel[p] = val
}
}
return this
}
var openTag, closeTag, rexpr, rexprg, rbind, rregexp = /[-.*+?^${}()|[\]\/\\]/g
function escapeRegExp(target) {
//http://stevenlevithan.com/regex/xregexp/
//将字符串安全格式化为正则表达式的源码
return (target + "").replace(rregexp, "\\$&")
}
var plugins = {
alias: function(val) {
log("alias方法已经被废弃")
for (var c in val) {
if (ohasOwn.call(val, c)) {
var currValue = val[c]
switch (getType(currValue)) {
case "string":
kernel.paths[c] = currValue
break;
case "object":
if (currValue.src) {
kernel.paths[c] = currValue.src
delete currValue.src
}
kernel.shim[c] = currValue
break;
}
}
}
},
loader: function(bool) {
if (bool) {
window.define = innerRequire.define
window.require = innerRequire
} else {
window.define = otherDefine
window.require = otherRequire
}
},
interpolate: function(array) {
if (Array.isArray(array) && array[0] && array[1] && array[0] !== array[1]) {
openTag = array[0]
closeTag = array[1]
var o = escapeRegExp(openTag),
c = escapeRegExp(closeTag)
rexpr = new RegExp(o + "(.*?)" + c)
rexprg = new RegExp(o + "(.*?)" + c, "g")
rbind = new RegExp(o + ".*?" + c + "|\\sms-")
}
}
}
kernel.plugins = plugins
kernel.plugins['interpolate'](["{{", "}}"])
kernel.paths = {}
kernel.shim = {}
avalon.config = kernel
/*********************************************************************
* DOM API的高级封装 *
**********************************************************************/
function hyphen(target) {
//转换为连字符线风格
return target.replace(/([a-z\d])([A-Z]+)/g, "$1-$2").toLowerCase()
}
function camelize(target) {
//转换为驼峰风格
if (target.indexOf("-") < 0 && target.indexOf("_") < 0) {
return target //提前判断,提高getStyle等的效率
}
return target.replace(/[-_][^-_]/g, function(match) {
return match.charAt(1).toUpperCase()
})
}
var rnospaces = /\S+/g
avalon.fn.mix({
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 state = stateVal,
className, i = 0
var classNames = value.match(rnospaces) || []
var isBool = typeof stateVal === "boolean"
var node = this[0] || {}, classList
if (classList = node.classList) {
while ((className = classNames[i++])) {
state = isBool ? state : !classList.contains(className)
classList[state ? "add" : "remove"](className)
}
}
return this
},
attr: function(name, value) {
if (arguments.length === 2) {
this[0].setAttribute(name, value)
return this
} else {
return this[0].getAttribute(name)
}
},
data: function(name, value) {
name = "data-" + hyphen(name || "")
switch (arguments.length) {
case 2:
this.attr(name, value)
return this
case 1:
var val = this.attr(name)
return parseData(val)
case 0:
var attrs = this[0].attributes,
ret = {}
for (var i = 0, attr; attr = attrs[i++]; ) {
name = attr.name
if (!name.indexOf("data-")) {
name = camelize(name.slice(5))
ret[name] = parseData(attr.value)
}
}
return ret
}
},
removeData: function(name) {
name = "data-" + hyphen(name)
this[0].removeAttribute(name)
return this
},
css: function(name, value) {
if (avalon.isPlainObject(name)) {
for (var i in name) {
avalon.css(this, i, name[i])
}
} else {
var ret = avalon.css(this, name, value)
}
return ret !== void 0 ? ret : this
},
position: function() {
var offsetParent, offset,
elem = this[0],
parentOffset = {
top: 0,
left: 0
};
if (!elem) {
return
}
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 += avalon.css(offsetParent[0], "borderTopWidth", true)
parentOffset.left += avalon.css(offsetParent[0], "borderLeftWidth", true)
}
return {
top: offset.top - parentOffset.top - avalon.css(elem, "marginTop", true),
left: offset.left - parentOffset.left - avalon.css(elem, "marginLeft", true)
}
},
offsetParent: function() {
var offsetParent = this[0].offsetParent || root
while (offsetParent && (offsetParent.tagName !== "HTML") && avalon.css(offsetParent, "position") === "static") {
offsetParent = offsetParent.offsetParent
}
return avalon(offsetParent || root)
},
bind: function(type, fn, phase) {
if (this[0]) { //此方法不会链
return avalon.bind(this[0], type, fn, phase)
}
},
unbind: function(type, fn, phase) {
if (this[0]) {
avalon.unbind(this[0], type, fn, phase)
}
return this
},
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[getValType(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
}
})
"add,remove".replace(rword, function(method) {
avalon.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 && el.nodeType == 1) {
cls.replace(rnospaces, function(c) {
el.classList[method](c)
})
}
return this
}
})
if (root.dataset) {
avalon.data = function(name, val) {
var dataset = this[0].dataset
switch (arguments.length) {
case 2:
dataset[name] = val
return this
case 1:
val = dataset[name]
return parseData(val)
case 0:
var ret = {}
for (var name in dataset) {
ret[name] = parseData(dataset[name])
}
return ret
}
}
}
var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/
function parseData(data) {
try {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
+data + "" === data ? +data : rbrace.test(data) ? JSON.parse(data) : data
} catch (e) {
}
return data
}
avalon.each({
scrollLeft: "pageXOffset",
scrollTop: "pageYOffset"
}, function(method, prop) {
avalon.fn[method] = function(val) {
var node = this[0] || {}, win = getWindow(node),
top = method === "scrollTop"
if (!arguments.length) {
return win ? win[prop] : node[method]
} else {
if (win) {
win.scrollTo(!top ? val : avalon(win).scrollLeft(), top ? val : avalon(win).scrollTop())
} else {
node[method] = val
}
}
}
})
function getWindow(node) {
return node.window && node.document ? node : node.nodeType === 9 ? node.defaultView : false
}
//=============================css相关==================================
var cssHooks = avalon.cssHooks = {}
var prefixes = ["", "-webkit-", "-o-", "-moz-", "-ms-"]
var cssMap = {
"float": "cssFloat",
background: "backgroundColor"
}
avalon.cssNumber = oneObject("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom")
avalon.cssName = function(name, host, camelCase) {
if (cssMap[name]) {
return cssMap[name]
}
host = host || root.style
for (var i = 0, n = prefixes.length; i < n; i++) {
camelCase = camelize(prefixes[i] + name)
if (camelCase in host) {
return (cssMap[name] = camelCase)
}
}
return null
}
cssHooks["@:set"] = function(node, name, value) {
node.style[name] = value
}
cssHooks["@:get"] = function(node, name) {
var ret, styles = window.getComputedStyle(node, null)
if (styles) {
ret = styles.getPropertyValue(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(rword, function(name) {
cssHooks[name + ":get"] = function(node) {
var computed = cssHooks["@:get"](node, name)
return /px$/.test(computed) ? computed :
avalon(node).position()[name] + "px"
}
})
var cssShow = {
position: "absolute",
visibility: "hidden",
display: "block"
}
var rdisplayswap = /^(none|table(?!-c[ea]).+)/
function showHidden(node, array) {
//http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
if (node.offsetWidth <= 0) { //opera.offsetWidth可能小于0
var styles = window.getComputedStyle(node, null)
if (rdisplayswap.test(styles["display"])) {
var obj = {
node: node
}
for (var name in cssShow) {
obj[name] = styles[name]
node.style[name] = cssShow[name]
}
array.push(obj)
}
var parent = node.parentNode
if (parent && parent.nodeType == 1) {
showHidden(parent, array)
}
}
}
"Width,Height".replace(rword, function(name) {
var method = name.toLowerCase(),
clientProp = "client" + name,
scrollProp = "scroll" + name,
offsetProp = "offset" + name
cssHooks[method + "::get"] = function(node) {
var hidden = [];
showHidden(node, hidden);
var val = avalon.css(node, method, true)
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;
}
avalon.fn[method] = function(value) {
var node = this[0]
if (arguments.length === 0) {
if (node.setTimeout) { //取得窗口尺寸,IE9后可以用node.innerWidth /innerHeight代替
//https://developer.mozilla.org/en-US/docs/Web/API/window.innerHeight
return node["inner" + name]
}
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)
}
}
})
avalon.fn.offset = function() { //取得距离页面左右角的坐标
var node = this[0]
var doc = node && node.ownerDocument
var pos = {
left: 0,
top: 0
}
if (!doc) {
return pos
}
//http://hkom.blog1.fc2.com/?mode=m&no=750 body的偏移量是不包含margin的
//我们可以通过getBoundingClientRect来获得元素相对于client的rect.
//http://msdn.microsoft.com/en-us/library/ms536433.aspx
var box = node.getBoundingClientRect(),
//chrome1+, firefox3+, ie4+, opera(yes) safari4+
win = doc.defaultView || doc.parentWindow,
root = (navigator.vendor || doc.compatMode === "BackCompat") ? doc.body : doc.documentElement,
clientTop = root.clientTop >> 0,
clientLeft = root.clientLeft >> 0,
scrollTop = win.pageYOffset || root.scrollTop,
scrollLeft = win.pageXOffset || root.scrollLeft
// 把滚动距离加到left,top中去。
// IE一些版本中会自动为HTML元素加上2px的border,我们需要去掉它
// http://msdn.microsoft.com/en-us/library/ms533564(VS.85).aspx
pos.top = box.top + scrollTop - clientTop
pos.left = box.left + scrollLeft - clientLeft
return pos
}
//=============================val相关=======================
function getValType(el) {
var ret = el.tagName.toLowerCase()
return ret === "input" && /checkbox|radio/.test(el.type) ? "checked" : ret
}
var valHooks = {
"select:get": function(node, value) {
var option, options = node.options,
index = node.selectedIndex,
one = node.type === "select-one" || index < 0,
values = one ? null : [],
max = one ? index + 1 : options.length,
i = index < 0 ? max : one ? index : 0
for (; i < max; i++) {
option = options[i]
//旧式IE在reset后不会改变selected,需要改用i === index判定
//我们过滤所有disabled的option元素,但在safari5下,如果设置select为disable,那么其所有孩子都disable
//因此当一个元素为disable,需要检测其是否显式设置了disable及其父节点的disable情况
if ((option.selected || i === index) && !option.disabled) {
value = option.value
if (one) {
return value
}
//收集所有selected值组成数组返回
values.push(value)
}
}
return values
},
"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) >= 0)) {
optionSet = true
}
}
if (!optionSet) {
node.selectedIndex = -1
}
}
}
/************************************************************************
* parseHTML *
****************************************************************************/
var rtagName = /<([\w:]+)/,
//取得其tagName
rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
scriptTypes = oneObject("text/javascript", "text/ecmascript", "application/ecmascript", "application/javascript", "text/vbscript"),
//需要处理套嵌关系的标签
rnest = /<(?:tb|td|tf|th|tr|col|opt|leg|cap|area)/
//parseHTML的辅助变量
var tagHooks = new function() {
var object = {
option: DOC.createElement("select"),
thead: DOC.createElement("table"),
td: DOC.createElement("tr"),
area: DOC.createElement("map"),
tr: DOC.createElement("tbody"),
col: DOC.createElement("colgroup"),
legend: DOC.createElement("fieldset"),
"*": DOC.createElement("div")
}
object.optgroup = object.option
object.tbody = object.tfoot = object.colgroup = object.caption = object.thead
object.th = object.td
return object
}
avalon.clearHTML = function(node) {
node.textContent = "" //它能在IE10+,firefox, chrome中迅速清空元素节点,文档碎片的孩子
return node
}
var script = DOC.createElement("script")
avalon.parseHTML = function(html) {
if (typeof html !== "string") {
html = html + ""
}
html = html.replace(rxhtml, "<$1></$2>").trim()
if (deleteRange.createContextualFragment && !rnest.test(html) && !/<script/.test(html)) {
var range = DOC.createRange()
range.selectNodeContents(root)
return range.createContextualFragment(html)
}
var fragment = documentFragment.cloneNode(false)
var tag = (rtagName.exec(html) || ["", ""])[1].toLowerCase()
if (!(tag in tagHooks)) {
tag = "*"
}
var parent = tagHooks[tag]
parent.innerHTML = html
var els = parent.getElementsByTagName("script"),
firstChild, neo
if (els.length) { //使用innerHTML生成的script节点不会发出请求与执行text属性
for (var i = 0, el; el = els[i++]; ) {
if (!el.type || scriptTypes[el.type]) { //如果script节点的MIME能让其执行脚本
neo = script.cloneNode(false) //FF不能省略参数
for (var j = 0, attr; attr = el.attributes[j++]; ) {
neo[attr.name] = attr.value //复制其属性
}
neo.text = el.text //必须指定,因为无法在attributes中遍历出来
el.parentNode.replaceChild(neo, el) //替换节点
}
}
}
while (firstChild = parent.firstChild) { // 将wrapper上的节点转移到文档碎片上!
fragment.appendChild(firstChild)
}
return fragment
}
avalon.innerHTML = function(node, html) {
if (rnest.test(html)) {
var a = this.parseHTML(html)
this.clearHTML(node).appendChild(a)
} else {
node.innerHTML = html
}
}
/*********************************************************************
* Observable *
**********************************************************************/
var Observable = {
$watch: function(type, callback) {
if (typeof callback === "function") {
var callbacks = this.$events[type]
if (callbacks) {
callbacks.push(callback)
} else {
this.$events[type] = [callback]
}
} else { //重新开始监听此VM的第一重简单属性的变动
this.$events = this.$watch.backup
}
return this
},
$unwatch: function(type, callback) {
var n = arguments.length
if (n === 0) { //让此VM的所有$watch回调无效化
this.$watch.backup = this.$events
this.$events = {}
} else if (n === 1) {
this.$events[type] = []
} else {
var callbacks = this.$events[type] || []
var i = callbacks.length
while (~--i < 0) {
if (callbacks[i] === callback) {
return callbacks.splice(i, 1)
}
}
}
return this
},
$fire: function(type) {
var callbacks = this.$events[type] || [] //防止影响原数组
var all = this.$events.$all || []
var args = aslice.call(arguments, 1)
for (var i = 0, callback; callback = callbacks[i++]; ) {
callback.apply(this, args)
}
for (var i = 0, callback; callback = all[i++]; ) {
callback.apply(this, arguments)
}
}
}
/*********************************************************************
* 依赖收集与触发 *
**********************************************************************/
function registerSubscriber(data) {
Registry[expose] = data //暴光此函数,方便collectSubscribers收集
avalon.openComputedCollect = true
var fn = data.evaluator
if (fn) { //如果是求值函数
if (data.type === "duplex") {
data.handler()
} else {
data.handler(fn.apply(0, data.args), data.element, data)
}
} else { //如果是计算属性的accessor
data()
}
avalon.openComputedCollect = false
delete Registry[expose]
}
function collectSubscribers(accessor) { //收集依赖于这个访问器的订阅者
if (Registry[expose]) {
var list = accessor[subscribers]
list && avalon.Array.ensure(list, Registry[expose]) //只有数组不存在此元素才push进去
}
}
function notifySubscribers(accessor) { //通知依赖于这个访问器的订阅者更新自身
var list = accessor[subscribers]
if (list && list.length) {
var args = aslice.call(arguments, 1)
for (var i = list.length, fn; fn = list[--i]; ) {
var el = fn.element
if (el && !ifSanctuary.contains(el) && (!root.contains(el))) {
list.splice(i, 1)
log("remove " + fn.name)
} else if (typeof fn === "function") {
fn.apply(0, args) //强制重新计算自身
} else if (fn.getter) {
fn.handler.apply(fn, args) //强制重新计算自身
} else {
fn.handler(fn.evaluator.apply(0, fn.args || []), el, fn)
}
}
}
}
/*********************************************************************
* 扫描系统 *
**********************************************************************/
avalon.scan = function(elem, vmodel) {
elem = elem || root
var vmodels = vmodel ? [].concat(vmodel) : []
scanTag(elem, vmodels)
}
//http://www.w3.org/TR/html5/syntax.html#void-elements
var stopScan = oneObject("area,base,basefont,br,col,command,embed,hr,img,input,link,meta,param,source,track,wbr,noscript,noscript,script,style,textarea".toUpperCase())
//确保元素的内容被完全扫描渲染完毕才调用回调
function checkScan(elem, callback) {
var innerHTML = NaN,
id = setInterval(function() {
var currHTML = elem.innerHTML
if (currHTML === innerHTML) {
clearInterval(id)
callback()
} else {
innerHTML = currHTML
}
}, 15)
}
function scanTag(elem, vmodels, node) {
//扫描顺序 ms-skip(0) --> ms-important(1) --> ms-controller(2) --> ms-if(10) --> ms-repeat(100)
//--> ms-if-loop(110) --> ms-attr(970) ...--> ms-each(1400)-->ms-with(1500)--〉ms-duplex(2000)垫后
var a = elem.getAttribute(prefix + "skip")
var b = elem.getAttributeNode(prefix + "important")
var c = elem.getAttributeNode(prefix + "controller")
if (typeof a === "string") {
return
} else if (node = b || c) {
var newVmodel = VMODELS[node.value]
if (!newVmodel) {
return
}
//ms-important不包含父VM,ms-controller相反
vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels)
elem.removeAttribute(node.name) //removeAttributeNode不会刷新[ms-controller]样式规则
elem.classList.remove(node.name)
}
scanAttr(elem, vmodels) //扫描特性节点
}
function scanNodes(parent, vmodels) {
var node = parent.firstChild
while (node) {
var nextNode = node.nextSibling
if (node.nodeType === 1) {
scanTag(node, vmodels) //扫描元素节点
} else if (node.nodeType === 3 && rexpr.test(node.data)) {
scanText(node, vmodels) //扫描文本节点
}
node = nextNode
}
}
function scanText(textNode, vmodels) {
var bindings = [],
tokens = scanExpr(textNode.data)
if (tokens.length) {
for (var i = 0, token; token = tokens[i++]; ) {
var node = DOC.createTextNode(token.value) //将文本转换为文本节点,并替换原来的文本节点
if (token.expr) {
var filters = token.filters
var binding = {
type: "text",
node: node,
nodeType: 3,
value: token.value,
filters: filters
}
if (filters && filters.indexOf("html") !== -1) {
avalon.Array.remove(filters, "html")
binding.type = "html"
binding.replaceNodes = [node]
if (!filters.length) {
delete bindings.filters
}
}
bindings.push(binding) //收集带有插值表达式的文本
}
documentFragment.appendChild(node)
}
textNode.parentNode.replaceChild(documentFragment, textNode)
executeBindings(bindings, vmodels)
}
}
var rmsAttr = /ms-(\w+)-?(.*)/
var priorityMap = {
"if": 10,
"repeat": 100,
"each": 1400,
"with": 1500,
"duplex": 2000
}
function scanAttr(elem, vmodels) {
var attributes = elem.attributes
var bindings = [], msData = {},
match
for (var i = 0, attr; attr = attributes[i++]; ) {
if (attr.specified) {
if (match = attr.name.match(rmsAttr)) {
//如果是以指定前缀命名的
var type = match[1]
msData[attr.name] = attr.value
if (typeof bindingHandlers[type] === "function") {
var param = match[2] || ""
var binding = {
type: type,
param: param,
element: elem,
name: match[0],
value: attr.value,
priority: type in priorityMap ? priorityMap[type] : type.charCodeAt(0) * 10 + (Number(param) || 0)
}
if (type === "if" && param === "loop") {
binding.priority += 100
}
if (type === "widget") {
bindings.push(binding)
elem.msData = elem.msData || msData
} else if (vmodels.length) {
bindings.push(binding)
}
}
}
}
}
if (msData["ms-checked"] && msData["ms-duplex"]) {
avalon.log("warning!一个元素上不能同时定义ms-checked与ms-duplex")
}
bindings.sort(function(a, b) {
return a.priority - b.priority
})
var firstBinding = bindings[0] || {}
switch (firstBinding.type) {
case "if":
case "repeat":
executeBindings([firstBinding], vmodels)
break
default:
executeBindings(bindings, vmodels)
if (!stopScan[elem.tagName] && rbind.test(elem.innerHTML)) {
scanNodes(elem, vmodels) //扫描子孙元素
}
break;
}
if (elem.patchRepeat) {
elem.patchRepeat()
elem.patchRepeat = null
}
}
function executeBindings(bindings, vmodels) {
for (var i = 0, data; data = bindings[i++]; ) {
data.vmodels = vmodels
bindingHandlers[data.type](data, vmodels)
if (data.evaluator && data.name) { //移除数据绑定,防止被二次解析
//chrome使用removeAttributeNode移除不存在的特性节点时会报错 https://github.com/RubyLouvre/avalon/issues/99
data.element.removeAttribute(data.name)
}
}
bindings.length = 0
}
var rfilters = /\|\s*(\w+)\s*(\([^)]*\))?/g,
r11a = /\|\|/g,
r11b = /U2hvcnRDaXJjdWl0/g
function scanExpr(str) {
var tokens = [],
value, start = 0,
stop
do {
stop = str.indexOf(openTag, start)
if (stop === -1) {
break
}
value = str.slice(start, stop)
if (value) { // {{ 左边的文本
tokens.push({
value: value,
expr: false
})
}
start = stop + openTag.length
stop = str.indexOf(closeTag, start)
if (stop === -1) {
break
}
value = str.slice(start, stop)
if (value) { //处理{{ }}插值表达式
var leach = []
if (value.indexOf("|") > 0) { // 抽取过滤器 先替换掉所有短路与
value = value.replace(r11a, "U2hvcnRDaXJjdWl0") //btoa("ShortCircuit")
value = value.replace(rfilters, function(c, d, e) {
leach.push(d + (e || ""))
return ""
})
value = value.replace(r11b, "||") //还原短路与
}
tokens.push({
value: value,
expr: true,
filters: leach.length ? leach : void 0
})
}
start = stop + closeTag.length
} while (1)
value = str.slice(start)
if (value) { //}} 右边的文本
tokens.push({
value: value,
expr: false
})
}
return tokens
}
/*********************************************************************
* 编译模块 *
**********************************************************************/
var keywords =
// 关键字
"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"
// ECMA 5 - use strict
+ ",arguments,let,yield"
+ ",undefined"
var rrexpstr = /\/\*(?:.|\n)*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|'[^']*'|"[^"]*"|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g
var rsplit = /[^\w$]+/g
var rkeywords = new RegExp(["\\b" + keywords.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g')
var rnumber = /\b\d[^,]*/g
var rcomma = /^,+|,+$/g
var getVariables = function(code) {
code = code
.replace(rrexpstr, "")
.replace(rsplit, ",")
.replace(rkeywords, "")
.replace(rnumber, "")
.replace(rcomma, "")
return code ? code.split(/,+/) : []
}
//添加赋值语句
function addAssign(vars, scope, name, duplex) {
var ret = [],
prefix = " = " + name + "."
for (var i = vars.length, prop; prop = vars[--i]; ) {
if (scope.hasOwnProperty(prop)) {
ret.push(prop + prefix + prop)
if (duplex === "duplex") {
vars.get = name + "." + prop
}
vars.splice(i, 1)
}
}
return ret
}
function uniqArray(arr, vm) {
var length = arr.length
if (length <= 1) {
return arr
} else if (length === 2) {
return arr[0] !== arr[1] ? arr : [arr[0]]
}
var uniq = {}
return arr.filter(function(el) {
var key = vm ? el && el.$id : el
if (!uniq[key]) {
uniq[key] = 1
return true
}
return false
})
}
//缓存求值函数,以便多次利用
function createCache(maxLength) {
var keys = []
function cache(key, value) {
if (keys.push(key) > maxLength) {
delete cache[keys.shift()]
}
return cache[key] = value;
}
return cache;
}
var cacheExpr = createCache(256)
//根据一段文本与一堆VM,转换为对应的求值函数及匹配的VM(解释器模式)
var rduplex = /\w\[.*\]|\w\.\w/
var rproxy = /(\$proxy\$[a-z]+)\d+$/
function parseExpr(code, scopes, data, four) {
var dataType = data.type
var filters = dataType == "html" || dataType === "text" ? data.filters : ""
var exprId = scopes.map(function(el) {
return el.$id.replace(rproxy, "$1")
}) + code + dataType + filters
var vars = getVariables(code),
assigns = [],
names = [],
args = [],
prefix = ""
//args 是一个对象数组, names 是将要生成的求值函数的参数
vars = uniqArray(vars), scopes = uniqArray(scopes, 1)
for (var i = 0, sn = scopes.length; i < sn; i++) {
if (vars.length) {
var name = "vm" + expose + "_" + i
names.push(name)
args.push(scopes[i])
assigns.push.apply(assigns, addAssign(vars, scopes[i], name, four))
}
}
//---------------args----------------
if (filters) {
args.push(avalon.filters)
}
data.args = args
//---------------cache----------------
var fn = cacheExpr[exprId] //直接从缓存,免得重复生成
if (fn) {
data.evaluator = fn
return
}
var prefix = assigns.join(", ")
if (prefix) {
prefix = "var " + prefix
}
if (filters) {//文本绑定,双工绑定才有过滤器
code = "\nvar ret" + expose + " = " + code
var textBuffer = [],
fargs
textBuffer.push(code, "\r\n")
for (var i = 0, fname; fname = data.filters[i++]; ) {
var start = fname.indexOf("(")
if (start !== -1) {
fargs = fname.slice(start + 1, fname.lastIndexOf(")")).trim()
fargs = "," + fargs
fname = fname.slice(0, start).trim()
} else {
fargs = ""
}
textBuffer.push(" if(filters", expose, ".", fname, "){\n\ttry{\nret", expose,
" = filters", expose, ".", fname, "(ret", expose, fargs, ")\n\t}catch(e){} \n}\n")
}
code = textBuffer.join("")
code += "\nreturn ret" + expose
names.push("filters" + expose)
} else if (dataType === "duplex") {//双工绑定
var _body = "'use strict';\nreturn function(vvv){\n\t" +
prefix +
";\n\tif(!arguments.length){\n\t\treturn " +
code +
"\n\t}\n\t" + (!rduplex.test(code) ? vars.get : code) +
"= vvv;\n} "
try {
fn = Function.apply(noop, names.concat(_body))
data.evaluator = cacheExpr(exprId, fn)
} catch (e) {
}
return
} else if (dataType === "on") {//事件绑定
code = code.replace("(", ".call(this,")
if (four === "$event") {
names.push(four)
}
code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
var lastIndex = code.lastIndexOf("\nreturn")
var header = code.slice(0, lastIndex)
var footer = code.slice(lastIndex)
code = header + "\nif(avalon.openComputedCollect) return ;" + footer
} else {//其他绑定
code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
}
try {
fn = Function.apply(noop, names.concat("'use strict';\n" + prefix + code))
if (data.type !== "on") {
fn.apply(fn, args)
}
data.evaluator = cacheExpr(exprId, fn)
} catch (e) {
} finally {
vars = textBuffer = names = null //释放内存
}
}
//parseExpr的智能引用代理
function parseExprProxy(code, scopes, data, tokens) {
if (Array.isArray(tokens)) {
var array = tokens.map(function(token) {
var tmpl = {}
return token.expr ? parseExpr(token.value, scopes, tmpl) || tmpl : token.value
})
data.evaluator = function() {
var ret = ""
for (var i = 0, el; el = array[i++]; ) {
ret += typeof el === "string" ? el : el.evaluator.apply(0, el.args)
}
return ret
}
data.args = []
} else {
parseExpr(code, scopes, data, tokens)
}
if (data.evaluator) {
data.handler = bindingExecutors[data.handlerName || data.type]
data.evaluator.toString = function() {
return data.type + " binding to eval(" + code + ")"
}
//方便调试
//这里非常重要,我们通过判定视图刷新函数的element是否在DOM树决定
//将它移出订阅者列表
registerSubscriber(data)
}
}
avalon.parseExprProxy = parseExprProxy
/*********************************************************************
*绑定模块(实现“操作数据即操作DOM”的关键,将DOM操作放逐出前端开发人员的视野,让它交由框架自行处理,开发人员专致于业务本身) * *
**********************************************************************/
var cacheDisplay = oneObject("a,abbr,b,span,strong,em,font,i,kbd", "inline")
avalon.mix(cacheDisplay, oneObject("div,h1,h2,h3,h4,h5,h6,section,p", "block"))
function parseDisplay(nodeName, val) {
//用于取得此类标签的默认display值
nodeName = nodeName.toLowerCase()
if (!cacheDisplay[nodeName]) {
var node = DOC.createElement(nodeName)
root.appendChild(node)
val = window.getComputedStyle(node, null).display
root.removeChild(node)
cacheDisplay[nodeName] = val
}
return cacheDisplay[nodeName]
}
avalon.parseDisplay = parseDisplay
var supportDisplay = (function(td) {
return window.getComputedStyle ?
window.getComputedStyle(td, null).display == "table-cell" : true
})(DOC.createElement("td"))
var rdash = /\(([^)]*)\)/
head.insertAdjacentHTML("afterBegin", '<style id="avalonStyle">.avalonHide{ display: none!important }</style>')
var getBindingCallback = function(elem, name, vmodels) {
var callback = elem.getAttribute(name)
if (callback) {
for (var i = 0, vm; vm = vmodels[i++]; ) {
if (vm.hasOwnProperty(callback) && typeof vm[callback] === "function") {
return vm[callback]
}
}
}
}
var includeContents = {}
var ifSanctuary = DOC.createElement("div")
var rwhitespace = /^\s+$/
//这里的函数每当VM发生改变后,都会被执行(操作方为notifySubscribers)
var bindingExecutors = avalon.bindingExecutors = {
"attr": function(val, elem, data) {
var method = data.type,
attrName = data.param
function scanTemplate(text) {
if (loaded) {
text = loaded.apply(elem, [text].concat(vmodels))
}
avalon.innerHTML(elem, text)
scanNodes(elem, vmodels)
rendered && checkScan(elem, function() {
rendered.call(elem)
})
}
if (method === "css") {
avalon(elem).css(attrName, val)
} else if (method === "attr") {
// ms-attr-class="xxx" vm.xxx="aaa bbb ccc"将元素的className设置为aaa bbb ccc
// ms-attr-class="xxx" vm.xxx=false 清空元素的所有类名
// ms-attr-name="yyy" vm.yyy="ooo" 为元素设置name属性
var toRemove = (val === false) || (val === null) || (val === void 0)
if (toRemove) {
elem.removeAttribute(attrName)
} else {
elem.setAttribute(attrName, val)
}
} else if (method === "include" && val) {
var vmodels = data.vmodels
var rendered = getBindingCallback(elem, "data-include-rendered", vmodels)
var loaded = getBindingCallback(elem, "data-include-loaded", vmodels)
if (data.param === "src") {
if (includeContents[val]) {
scanTemplate(includeContents[val])
} else {
var xhr = new window.XMLHttpRequest
xhr.onload = function() {
var s = xhr.status
if (s >= 200 && s < 300 || s === 304) {
scanTemplate(includeContents[val] = xhr.responseText)
}
}
xhr.open("GET", val, true)
xhr.withCredentials = true
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
xhr.send(null)
}
} else {
//IE系列与够新的标准浏览器支持通过ID取得元素(firefox14+)
//http://tjvantoll.com/2012/07/19/dom-element-references-as-global-variables/
var el = val && val.nodeType == 1 ? val : DOC.getElementById(val)
avalon.nextTick(function() {
scanTemplate(el.innerText || el.innerHTML)
})
}
} else {
elem[method] = val
}
},
"class": function(val, elem, data) {
var $elem = avalon(elem),
method = data.type
if (method === "class" && data.param) { //如果是旧风格
$elem.toggleClass(data.param, !!val)
} else {
var toggle = data._evaluator ? !!data._evaluator.apply(elem, data._args) : true
var className = data._class || val
switch (method) {
case "class":
if (toggle && data.oldClass) {
$elem.removeClass(data.oldClass)
}
$elem.toggleClass(className, toggle)
data.oldClass = className
break;
case "hover":
case "active":
if (!data.init) {
if (method === "hover") { //在移出移入时切换类名
var event1 = "mouseenter",
event2 = "mouseleave"
} else { //在聚焦失焦中切换类名
elem.tabIndex = elem.tabIndex || -1
event1 = "mousedown", event2 = "mouseup"
$elem.bind("mouseleave", function() {
toggle && $elem.removeClass(className)
})
}
$elem.bind(event1, function() {
toggle && $elem.addClass(className)
})
$elem.bind(event2, function() {
toggle && $elem.removeClass(className)
})
data.init = 1
}
break;
}
}
},
"data": function(val, elem, data) {
var key = "data-" + data.param
if (val && typeof val === "object") {
elem[key] = val
} else {
elem.setAttribute(key, String(val))
}
},
"checked": function(val, elem, data) {
var name = data.type;
if (name === "enabled") {
elem.disabled = !val
} else {
var propName = name === "readonly" ? "readOnly" : name
elem[propName] = !!val
}
},
"each": function(method, pos, el) {
var data = this
var group = data.group
var pp = data.startRepeat && data.startRepeat.parentNode
if (pp) {//fix #300 #307
data.parent = pp
}
var parent = data.parent
var proxies = data.proxies
if (method == "del" || method == "move") {
var locatedNode = getLocatedNode(parent, data, pos)
}
switch (method) {
case "add":
//在pos位置后添加el数组(pos为数字,el为数组)
var arr = el
var last = data.getter().length - 1
var transation = documentFragment.cloneNode(false)
var spans = [], lastFn = {}
for (var i = 0, n = arr.length; i < n; i++) {
var ii = i + pos
var proxy = createEachProxy(ii, arr[i], data, last)
proxies.splice(ii, 0, proxy)
lastFn = shimController(data, transation, spans, proxy)
}
locatedNode = getLocatedNode(parent, data, pos)
lastFn.node = locatedNode
lastFn.parent = parent
parent.insertBefore(transation, locatedNode)
for (var i = 0, el; el = spans[i++]; ) {
scanTag(el, data.vmodels)
}
spans = null
break
case "del"://将pos后的el个元素删掉(pos, el都是数字)
proxies.splice(pos, el)
removeFromSanctuary(removeView(locatedNode, group, el))
break
case "index"://将proxies中的第pos个起的所有元素重新索引(pos为数字,el用作循环变量)
var last = proxies.length - 1
for (; el = proxies[pos]; pos++) {
el.$index = pos
el.$first = pos === 0
el.$last = pos === last
}
break
case "clear":
var deleteFragment = documentFragment.cloneNode(false)
if (data.startRepeat) {
while (true) {
var node = data.startRepeat.nextSibling
if (node && node !== data.endRepeat) {
deleteFragment.appendChild(node)
} else {
break
}
}
} else {
while (parent.firstChild) {
deleteFragment.appendChild(parent.firstChild)
}
}
removeFromSanctuary(deleteFragment)
if (proxies)
proxies.length = 0
break
case "move"://将proxies中的第pos个元素移动el位置上(pos, el都是数字)
var t = proxies.splice(pos, 1)[0]
if (t) {
proxies.splice(el, 0, t)
var moveNode = removeView(locatedNode, group)
locatedNode = getLocatedNode(parent, data, el)
parent.insertBefore(moveNode, locatedNode)
}
break
case "set"://将proxies中的第pos个元素的VM设置为el(pos为数字,el任意)
var proxy = proxies[pos]
if (proxy) {
proxy[proxy.$itemName] = el
}
break
case "append"://将pos的键值对从el中取出(pos为一个普通对象,el为预先生成好的代理VM对象池)
var pool = el
var transation = documentFragment.cloneNode(false)
var callback = getBindingCallback(data.callbackElement, "data-with-sorted", data.vmodels)
var keys = [], spans = [], lastFn = {}
for (var key in pos) { //得到所有键名
if (pos.hasOwnProperty(key)) {
keys.push(key)
}
}
if (callback) { //如果有回调,则让它们排序
var keys2 = callback.call(parent, keys)
if (keys2 && Array.isArray(keys2) && keys2.length) {
keys = keys2
}
}
for (var i = 0, key; key = keys[i++]; ) {
lastFn = shimController(data, transation, spans, pool[key])
}
lastFn.parent = parent
lastFn.node = data.endRepeat || null
parent.insertBefore(transation, lastFn.node)
for (var i = 0, el; el = spans[i++]; ) {
scanTag(el, data.vmodels)
}
spans = null
break
}
iteratorCallback.call(data, arguments)
},
"html": function(val, elem, data) {
val = val == null ? "" : val
if (!elem) {
elem = data.element = data.node.parentNode
}
if (data.replaceNodes) {
var fragment, nodes
if (val.nodeType === 11) {
fragment = val
} else if (val.nodeType === 1 || val.item) {
nodes = val.nodeType === 1 ? val.childNodes : val.item ? val : []
fragment = documentFragment.cloneNode(true)
while (nodes[0]) {
fragment.appendChild(nodes[0])
}
} else {
fragment = avalon.parseHTML(val)
}
var replaceNodes = avalon.slice(fragment.childNodes)
elem.insertBefore(fragment, data.replaceNodes[0] || null)
for (var i = 0, node; node = data.replaceNodes[i++]; ) {
elem.removeChild(node)
}
data.replaceNodes = replaceNodes
} else {
avalon.innerHTML(elem, val)
}
avalon.nextTick(function() {
scanNodes(elem, data.vmodels)
})
},
"if": function(val, elem, data) {
var placehoder = data.placehoder
if (val) { //插回DOM树
if (!data.msInDocument) {
data.msInDocument = true
try {
placehoder.parentNode.replaceChild(elem, placehoder)
} catch (e) {
avalon.log("ms-if errer" + e.message)
}
}
if (rbind.test(elem.outerHTML)) {
scanAttr(elem, data.vmodels)
// if (data.param.indexOf("once") >= 0) {
// data.handler = noop
// }
}
} else { //移出DOM树,放进ifSanctuary DIV中,并用注释节点占据原位置
if (data.msInDocument) {
data.msInDocument = false
elem.parentNode.replaceChild(placehoder, elem)
placehoder.elem = elem
ifSanctuary.appendChild(elem)
}
}
},
"on": function(val, elem, data) {
var fn = data.evaluator
var args = data.args
var vmodels = data.vmodels
if (!data.hasArgs) {
callback = function(e) {
return fn.apply(0, args).call(this, e)
}
} else {
callback = function(e) {
return fn.apply(this, args.concat(e))
}
}
elem.$vmodel = vmodels[0]
elem.$vmodels = vmodels
if (typeof data.specialBind === "function") {
data.specialBind(elem, callback)
} else {
var removeFn = avalon.bind(elem, data.param, callback)
}
data.rollback = function() {
if (typeof data.specialUnbind === "function") {
data.specialUnbind()
} else {
avalon.unbind(elem, data.param, removeFn)
}
}
data.evaluator = data.handler = noop
},
"text": function(val, elem, data) {
val = val == null ? "" : val //不在页面上显示undefined null
if (data.nodeType === 3) { //绑定在文本节点上
data.node.data = val
} else { //绑定在特性节点上
if (!elem) {
elem = data.element = data.node.parentNode
}
elem.textContent = val
}
},
"visible": function(val, elem, data) {
elem.style.display = val ? data.display : "none"
},
"widget": noop
}
//这里的函数只会在第一次被扫描后被执行一次,并放进行对应VM属性的subscribers数组内(操作方为registerSubscriber)
var bindingHandlers = avalon.bindingHandlers = {
//这是一个字符串属性绑定的范本, 方便你在title, alt, src, href, include, css添加插值表达式
//<a ms-href="{{url.hostname}}/{{url.pathname}}.html">
"attr": function(data, vmodels) {
var text = data.value.trim(),
simple = true
if (text.indexOf(openTag) > -1 && text.indexOf(closeTag) > 2) {
simple = false
if (rexpr.test(text) && RegExp.rightContext === "" && RegExp.leftContext === "") {
simple = true
text = RegExp.$1
}
}
data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况
parseExprProxy(text, vmodels, data, (simple ? null : scanExpr(data.value)))
},
//根据VM的属性值或表达式的值切换类名,ms-class="xxx yyy zzz:flag"
//http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
"class": function(data, vmodels) {
var oldStyle = data.param,
text = data.value,
rightExpr
data.handlerName = "class"
if (!oldStyle || isFinite(oldStyle)) {
data.param = "" //去掉数字
var noExpr = text.replace(rexprg, function(a) {
return Math.pow(10, a.length - 1) //将插值表达式插入10的N-1次方来占位
})
var colonIndex = noExpr.indexOf(":") //取得第一个冒号的位置
if (colonIndex === -1) { // 比如 ms-class="aaa bbb ccc" 的情况
var className = text
} else { // 比如 ms-class-1="ui-state-active:checked" 的情况
className = text.slice(0, colonIndex)
rightExpr = text.slice(colonIndex + 1)
parseExpr(rightExpr, vmodels, data) //决定是添加还是删除
if (!data.evaluator) {
log("'" + (rightExpr || "").trim() + "' 不存在于VM中")
return false
} else {
data._evaluator = data.evaluator
data._args = data.args
}
}
var hasExpr = rexpr.test(className) //比如ms-class="width{{w}}"的情况
if (!hasExpr) {
data._class = className
}
parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : null))
} else if (data.type === "class") {
parseExprProxy(text, vmodels, data)
}
},
"checked": function(data, vmodels) {
data.handlerName = "checked"
parseExprProxy(data.value, vmodels, data)
},
"duplex": function(data, vmodels) {
var elem = data.element,
tagName = elem.tagName
if (typeof modelBinding[tagName] === "function") {
data.changed = getBindingCallback(elem, "data-duplex-changed", vmodels)
//由于情况特殊,不再经过parseExprProxy
parseExpr(data.value, vmodels, data, "duplex")
if (data.evaluator && data.args) {
var form = elem.form
if (form && form.msValidate) {
form.msValidate(elem)
}
modelBinding[elem.tagName](elem, data.evaluator.apply(null, data.args), data)
}
}
},
"each": function(data, vmodels) {
var type = data.type,
elem = data.element,
list
parseExpr(data.value, vmodels, data)
data.getter = function() {
return this.evaluator.apply(0, this.args || [])
}
data.handler = bindingExecutors.each
data.callbackName = "data-" + (type || "each") + "-rendered"
data.callbackElement = data.parent = elem
var freturn = true
try {
list = data.getter()
if (rchecktype.test(getType(list))) {
freturn = false
}
} catch (e) {
}
var check0 = "$key",
check1 = "$val"
if (Array.isArray(list)) {
check0 = "$first"
check1 = "$last"
}
for (var i = 0, p; p = vmodels[i++]; ) {
if (p.hasOwnProperty(check0) && p.hasOwnProperty(check1)) {
data.$outer = p
break
}
}
data.$outer = data.$outer || {}
var template = documentFragment.cloneNode(false)
if (type === "repeat") {
var startRepeat = DOC.createComment("ms-repeat-start")
var endRepeat = DOC.createComment("ms-repeat-end")
data.element = data.parent = elem.parentNode
data.startRepeat = startRepeat
data.endRepeat = endRepeat
elem.removeAttribute(data.name)
data.parent.replaceChild(endRepeat, elem)
data.parent.insertBefore(startRepeat, endRepeat)
template.appendChild(elem)
} else {
var node
while (node = elem.firstChild) {
if (node.nodeType === 3 && rwhitespace.test(node.data)) {
elem.removeChild(node)
} else {
template.appendChild(node)
}
}
}
data.template = template
node = template.firstChild
data.fastRepeat = node.nodeType === 1 && template.lastChild === node && !node.attributes["ms-controller"] && !node.attributes["ms-important"]
if (freturn) {
return
}
list[subscribers] && list[subscribers].push(data)
if (!Array.isArray(list) && type !== "each") {
var pool = withProxyPool[list.$id]
if (!pool) {
withProxyCount++
pool = withProxyPool[list.$id] = {}
for (var key in list) {
if (list.hasOwnProperty(key)) {
(function(k, v) {
pool[k] = createWithProxy(k, v, data.$outer)
pool[k].$watch("$val", function(val) {
list[k] = val//#303
})
})(key, list[key])
}
}
}
data.rollback = function() {
bindingExecutors.each.call(data, "clear")
var endRepeat = data.endRepeat
var parent = data.parent
parent.insertBefore(data.template, endRepeat || null)
if (endRepeat) {
parent.removeChild(endRepeat)
parent.removeChild(this.startRepeat)
data.element = data.callbackElement
}
}
data.handler("append", list, pool)
} else {
data.proxies = []
data.handler("add", 0, list)
}
},
"html": function(data, vmodels) {
parseExprProxy(data.value, vmodels, data)
},
"if": function(data, vmodels) {
var elem = data.element
elem.removeAttribute(data.name)
if (!data.placehoder) {
data.msInDocument = data.placehoder = DOC.createComment("ms-if")
}
data.vmodels = vmodels
parseExprProxy(data.value, vmodels, data)
},
"on": function(data, vmodels) {
var value = data.value,
four = "$event"
if (value.indexOf("(") > 0 && value.indexOf(")") > -1) {
var matched = (value.match(rdash) || ["", ""])[1].trim()
if (matched === "" || matched === "$event") { // aaa() aaa($event)当成aaa处理
four = void 0
value = value.replace(rdash, "")
}
} else {
four = void 0
}
data.type = "on"
data.hasArgs = four
data.handlerName = "on"
parseExprProxy(value, vmodels, data, four)
},
"visible": function(data, vmodels) {
var elem = data.element
if (!supportDisplay && !root.contains(elem)) { //fuck firfox 全家!
var display = parseDisplay(elem.tagName)
}
display = display || avalon(elem).css("display")
data.display = display === "none" ? parseDisplay(elem.tagName) : display
parseExprProxy(data.value, vmodels, data)
},
"widget": function(data, vmodels) {
var args = data.value.match(rword),
element = data.element,
widget = args[0],
vmOptions = {}
if (args[1] === "$") {
args[1] = void 0
}
if (!args[1]) {
args[1] = widget + setTimeout("1")
}
data.value = args.join(",")
var constructor = avalon.ui[widget]
if (typeof constructor === "function") { //ms-widget="tabs,tabsAAA,optname"
vmodels = element.vmodels || vmodels
for (var i = 0, v; v = vmodels[i++]; ) {
if (VMODELS[v.$id]) { //取得离它最近由用户定义的VM
var nearestVM = v
break
}
}
var optName = args[2] || widget //尝试获得配置项的名字,没有则取widget的名字
if (nearestVM && typeof nearestVM[optName] === "object") {
vmOptions = nearestVM[optName]
vmOptions = vmOptions.$model || vmOptions
var id = vmOptions[widget + "Id"]
if (typeof id === "string") {
args[1] = id
}
}
var widgetData = avalon.getWidgetData(element, args[0]) //抽取data-tooltip-text、data-tooltip-attr属性,组成一个配置对象
data[widget + "Id"] = args[1]
data[widget + "Options"] = avalon.mix({}, constructor.defaults, vmOptions, widgetData)
element.removeAttribute("ms-widget")
var widgetVM = constructor(element, data, vmodels)
data.evaluator = noop
var callback = getBindingCallback(element, "data-widget-defined", vmodels)
if (callback) {
callback.call(element, widgetVM)
}
} else if (vmodels.length) {//如果该组件还没有加载,那么保存当前的vmodels
element.vmodels = vmodels
}
return true
}
}
//============================ class preperty binding =======================
"hover,active".replace(rword, function(method) {
bindingHandlers[method] = bindingHandlers["class"]
})
"with,repeat".replace(rword, function(name) {
bindingHandlers[name] = bindingHandlers.each
})
//============================= boolean preperty binding =======================
"disabled,enabled,readonly,selected".replace(rword, function(name) {
bindingHandlers[name] = bindingHandlers.checked
})
bindingHandlers.data = bindingHandlers.text = bindingHandlers.html
//============================= string preperty binding =======================
//与href绑定器 用法差不多的其他字符串属性的绑定器
//建议不要直接在src属性上修改,这样会发出无效的请求,请使用ms-src
"title,alt,src,value,css,include,href".replace(rword, function(name) {
bindingHandlers[name] = bindingHandlers.attr
})
//============================= model binding =======================
//将模型中的字段与input, textarea的value值关联在一起
var modelBinding = bindingHandlers.duplex
//如果一个input标签添加了model绑定。那么它对应的字段将与元素的value连结在一起
//字段变,value就变;value变,字段也跟着变。默认是绑定input事件,
modelBinding.INPUT = function(element, evaluator, data) {
var fixType = data.param
var type = element.type,
$elem = avalon(element)
if (type === "checkbox" && fixType === "radio") {
type = "radio"
}
var valueAccessor = data.changed ? function() {
return data.changed.call(element, element.value)
} : function() {
return element.value
}
//当value变化时改变model的值
var updateVModel = function() {
element.oldValue = element.vlaue
if ($elem.data("duplex-observe") !== false) {
evaluator(valueAccessor())
}
}
//当model变化时,它就会改变value的值
data.handler = function() {
var curValue = evaluator()
if (curValue !== element.value) {
element.value = curValue
}
}
if (type === "radio") {
data.handler = function() {
//IE6是通过defaultChecked来实现打勾效果
element.defaultChecked = (element.checked = /bool|text/.test(fixType) ? evaluator() + "" === element.value : !!evaluator())
}
updateVModel = function() {
if ($elem.data("duplex-observe") !== false) {
var value = element.value
if (fixType === "text") {
evaluator(value)
} else if (fixType === "bool") {
evaluator(value === "true")
} else {
var val = !element.defaultChecked
evaluator(val)
element.checked = val
}
}
}
element.addEventListener("click", updateVModel)
data.rollback = function() {
element.removeEventListener("click", updateVModel)
}
} else if (type === "checkbox") {
updateVModel = function() {
if ($elem.data("duplex-observe") !== false) {
var method = element.checked ? "ensure" : "remove"
var array = evaluator()
if (Array.isArray(array)) {
avalon.Array[method](array, element.value)
} else {
avalon.error("ms-duplex位于checkbox时要求对应一个数组")
}
}
}
data.handler = function() {
var array = [].concat(evaluator()) //强制转换为数组
element.checked = array.indexOf(element.value) >= 0
}
element.addEventListener("click", updateVModel)
data.rollback = function() {
element.removeEventListener("click", updateVModel)
}
} else {
var event = element.attributes["data-duplex-event"] || element.attributes["data-event"] || {}
event = event.value
var eventType = event === "change" ? event : "input"
element.addEventListener(eventType, updateVModel)
data.rollback = function() {
element.removeEventListener(eventType, updateVModel)
}
}
element.oldValue = element.value
launch(element)
registerSubscriber(data)
}
var TimerID, ribbon = [], launch = noop
function ticker() {
for (var n = ribbon.length - 1; n >= 0; n--) {
var el = ribbon[n]
if (avalon.contains(root, el)) {
if (el.oldValue !== el.value) {
avalon.fire(el, "input")
}
} else {
ribbon.splice(n, 1)
}
}
if (!ribbon.length) {
clearInterval(TimerID)
}
}
function launchImpl(el) {
if (ribbon.push(el) === 1) {
TimerID = setInterval(ticker, 30)
}
}
//http://msdn.microsoft.com/en-us/library/dd229916(VS.85).aspx
//https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1
function newSetter(newValue) {
oldSetter.call(this, newValue)
if (newValue !== this.oldValue) {
avalon.fire(this, "input")
}
}
try {
var inputProto = HTMLInputElement.prototype, oldSetter
oldSetter = Object.getOwnPropertyDescriptor(inputProto, "value").set//屏蔽chrome, safari,opera
Object.defineProperty(inputProto, "value", {
set: newSetter
})
} catch (e) {
launch = launchImpl
}
modelBinding.SELECT = function(element, evaluator, data, oldValue) {
var $elem = avalon(element)
function updateVModel() {
if ($elem.data("duplex-observe") !== false) {
var curValue = $elem.val() //字符串或字符串数组
if (curValue + "" !== oldValue) {
evaluator(curValue)
oldValue = curValue + ""
}
}
}
data.handler = function() {
var curValue = evaluator()
curValue = curValue && curValue.$model || curValue
curValue = Array.isArray(curValue) ? curValue.map(String) : curValue + ""
if (curValue + "" !== oldValue) {
$elem.val(curValue)
oldValue = curValue + ""
}
}
element.addEventListener("change", updateVModel)
data.rollback = function() {
element.removeEventListener("click", updateVModel)
}
var innerHTML = NaN
var id = setInterval(function() {
var currHTML = element.innerHTML
if (currHTML === innerHTML) {
clearInterval(id)
//先等到select里的option元素被扫描后,才根据model设置selected属性
registerSubscriber(data)
} else {
innerHTML = currHTML
}
}, 20)
}
modelBinding.TEXTAREA = modelBinding.INPUT
//========================= event binding ====================
var eventName = {
AnimationEvent: 'animationend',
WebKitAnimationEvent: 'webkitAnimationEnd'
}
for (var name in eventName) {
if (/object|function/.test(typeof window[name])) {
eventMap.animationend = eventName[name]
break
}
}
"dblclick,mouseout,click,mouseover,mouseenter,mouseleave,mousemove,mousedown,mouseup,keypress,keydown,keyup,blur,focus,change,animationend".
replace(rword, function(name) {
bindingHandlers[name] = (function(dataParam) {
return function(data) {
data.param = dataParam
bindingHandlers.on.apply(0, arguments)
}
})(name)
})
if (!("onmouseenter" in root)) { //chrome 30 终于支持mouseenter
var oldBind = avalon.bind
var events = {
mouseenter: "mouseover",
mouseleave: "mouseout"
}
avalon.bind = function(elem, type, fn) {
if (events[type]) {
return oldBind(elem, events[type], function(e) {
var t = e.relatedTarget
if (!t || (t !== elem && !(elem.compareDocumentPosition(t) & 16))) {
delete e.type
e.type = type
return fn.call(elem, e)
}
})
} else {
return oldBind(elem, type, fn)
}
}
}
/*********************************************************************
* 监控数组(与ms-each, ms-repeat配合使用) *
**********************************************************************/
function Collection(model) {
var array = []
array.$id = generateID()
array[subscribers] = []
array.$model = model
array.$events = {}
array._ = modelFactory({
length: model.length
})
array._.$watch("length", function(a, b) {
array.$fire("length", a, b)
})
for (var i in Observable) {
array[i] = Observable[i]
}
avalon.mix(array, CollectionPrototype)
return array
}
var _splice = ap.splice
var CollectionPrototype = {
_splice: _splice,
_add: function(arr, pos) {
var oldLength = this.length
pos = typeof pos === "number" ? pos : oldLength
var added = []
for (var i = 0, n = arr.length; i < n; i++) {
added[i] = convert(arr[i])
}
_splice.apply(this, [pos, 0].concat(added))
notifySubscribers(this, "add", pos, added)
if (!this._stopFireLength) {
return this._.length = this.length
}
},
_del: function(pos, n) {
var ret = this._splice(pos, n)
if (ret.length) {
notifySubscribers(this, "del", pos, n)
if (!this._stopFireLength) {
this._.length = this.length
}
}
return ret
},
push: function() {
ap.push.apply(this.$model, arguments)
var n = this._add(arguments)
notifySubscribers(this, "index", n > 2 ? n - 2 : 0)
return n
},
unshift: function() {
ap.unshift.apply(this.$model, arguments)
var ret = this._add(arguments, 0) //返回长度
notifySubscribers(this, "index", arguments.length)
return ret
},
shift: function() {
var el = this.$model.shift()
this._del(0, 1)
notifySubscribers(this, "index", 0)
return el //返回被移除的元素
},
pop: function() {
var el = this.$model.pop()
this._del(this.length - 1, 1)
return el //返回被移除的元素
},
splice: function(a, b) {
// 必须存在第一个参数,需要大于-1, 为添加或删除元素的基点
a = resetNumber(a, this.length)
var removed = _splice.apply(this.$model, arguments),
ret = []
this._stopFireLength = true //确保在这个方法中 , $watch("length",fn)只触发一次
if (removed.length) {
ret = this._del(a, removed.length)
if (arguments.length <= 2) { //如果没有执行添加操作,需要手动resetIndex
notifySubscribers(this, "index", 0)
}
}
if (arguments.length > 2) {
this._add(aslice.call(arguments, 2), a)
}
this._stopFireLength = false
this._.length = this.length
return ret //返回被移除的元素
},
contains: function(el) { //判定是否包含
return this.indexOf(el) !== -1
},
size: function() { //取得数组长度,这个函数可以同步视图,length不能
return this._.length
},
remove: function(el) { //移除第一个等于给定值的元素
var index = this.indexOf(el)
if (index >= 0) {
return this.removeAt(index)
}
},
removeAt: function(index) { //移除指定索引上的元素
this.splice(index, 1)
},
clear: function() {
this.$model.length = this.length = this._.length = 0 //清空数组
notifySubscribers(this, "clear", 0)
return this
},
removeAll: function(all) { //移除N个元素
if (Array.isArray(all)) {
all.forEach(function(el) {
this.remove(el)
}, this)
} else if (typeof all === "function") {
for (var i = this.length - 1; i >= 0; i--) {
var el = this[i]
if (all(el, i)) {
this.splice(i, 1)
}
}
} else {
this.clear()
}
},
ensure: function(el) {
if (!this.contains(el)) { //只有不存在才push
this.push(el)
}
return this
},
set: function(index, val) {
if (index >= 0 && index < this.length) {
var valueType = getType(val)
if (val && val.$model) {
val = val.$model
}
var target = this[index]
if (valueType === "object") {
for (var i in val) {
if (target.hasOwnProperty(i)) {
target[i] = val[i]
}
}
} else if (valueType === "array") {
target.clear().push.apply(target, val)
} else if (target !== val) {
this[index] = val
notifySubscribers(this, "set", index, val)
}
}
return this
}
}
"sort,reverse".replace(rword, function(method) {
CollectionPrototype[method] = function() {
var aaa = this.$model,
bbb = aaa.slice(0),
sorted = false
ap[method].apply(aaa, arguments) //先移动model
for (var i = 0, n = bbb.length; i < n; i++) {
var a = aaa[i],
b = bbb[i]
if (!isEqual(a, b)) {
sorted = true
var index = getIndex(a, bbb, i)
var remove = this._splice(index, 1)[0]
var remove2 = bbb.splice(index, 1)[0]
this._splice(i, 0, remove)
bbb.splice(i, 0, remove2)
notifySubscribers(this, "move", index, i)
}
}
bbb = void 0
if (sorted) {
notifySubscribers(this, "index", 0)
}
return this
}
})
function convert(val) {
var type = getType(val)
if (rchecktype.test(type)) {
val = val.$id ? val : modelFactory(val, val)
}
return val
}
//取得el在array的位置
function getIndex(a, array, start) {
for (var i = start, n = array.length; i < n; i++) {
if (isEqual(a, array[i])) {
return i
}
}
return -1
}
//============ each/repeat/with binding 用到的辅助函数与对象 ======================
//得到某一元素节点或文档碎片对象下的所有注释节点
var queryComments = function(parent) {
var tw = DOC.createTreeWalker(parent, NodeFilter.SHOW_COMMENT, null, null),
comment, ret = []
while (comment = tw.nextNode()) {
ret.push(comment)
}
return ret
}
var deleteRange = DOC.createRange()
//将通过ms-if移出DOM树放进ifSanctuary的元素节点移出来,以便垃圾回收
function removeFromSanctuary(parent) {
var comments = queryComments(parent)
for (var i = 0, comment; comment = comments[i++]; ) {
if (comment.nodeValue == "ms-if") {
var msIfEl = comment.elem
if (msIfEl.parentNode) {
msIfEl.parentNode.removeChild(msIfEl)
}
}
}
parent.textContent = ""
}
function iteratorCallback(args) {
var callback = getBindingCallback(this.callbackElement, this.callbackName, this.vmodels)
if (callback) {
var parent = this.parent
checkScan(parent, function() {
callback.apply(parent, args)
})
}
}
//为ms-each, ms-with, ms-repeat要循环的元素外包一个msloop临时节点,ms-controller的值为代理VM的$id
function shimController(data, transation, spans, proxy) {
var tview = data.template.cloneNode(true)
var id = proxy.$id
var span = tview.firstChild
if (!data.fastRepeat) {
span = DOC.createElement("msloop")
span.style.display = "none"
span.appendChild(tview)
}
span.setAttribute("ms-controller", id)
spans.push(span)
transation.appendChild(span)
VMODELS[id] = proxy
function fn() {
delete VMODELS[id]
data.group = 1
if (!data.fastRepeat) {
data.group = span.childNodes.length
span.parentNode.removeChild(span)
while (span.firstChild) {
transation.appendChild(span.firstChild)
}
if (fn.node !== void 0) {
fn.parent.insertBefore(transation, fn.node)
}
}
}
return span.patchRepeat = fn
}
// 取得用于定位的节点。在绑定了ms-each, ms-with属性的元素里,它的整个innerHTML都会视为一个子模板先行移出DOM树,
// 然后如果它的元素有多少个(ms-each)或键值对有多少双(ms-with),就将它复制多少份(多少为N),再经过扫描后,重新插入该元素中。
// 这时该元素的孩子将分为N等分,每等份的第一个节点就是这个用于定位的节点,
// 方便我们根据它算出整个等分的节点们,然后整体移除或移动它们。
function getLocatedNode(parent, data, pos) {
if (data.startRepeat) {
var ret = data.startRepeat,
end = data.endRepeat
pos += 1
for (var i = 0; i < pos; i++) {
ret = ret.nextSibling
if (ret == end)
return end
}
return ret
} else {
return parent.childNodes[data.group * pos] || null
}
}
function removeView(node, group, n) {
var length = group * (n || 1)
var view = documentFragment.cloneNode(false)
while (--length >= 0) {
var nextSibling = node.nextSibling
view.appendChild(node)
node = nextSibling
if (!node) {
break
}
}
return view
}
// 为ms-each, ms-repeat创建一个代理对象,通过它们能使用一些额外的属性与功能($index,$first,$last,$remove,$key,$val,$outer)
var watchEachOne = oneObject("$index,$first,$last")
function createWithProxy(key, val, $outer) {
var proxy = modelFactory({
$key: key,
$outer: $outer,
$val: val
}, 0, {
$val: 1,
$key: 1
})
proxy.$id = "$proxy$with" + Math.random()
return proxy
}
function createEachProxy(index, item, data, last) {
var param = data.param || "el"
var source = {
$index: index,
$itemName: param,
$outer: data.$outer,
$first: index === 0,
$last: index === last
}
source[param] = item
source.$remove = function() {
return data.getter().removeAt(proxy.$index)
}
var proxy = modelFactory(source, 0, watchEachOne)
proxy.$id = "$proxy$" + data.type + Math.random()
return proxy
}
/*********************************************************************
* 文本绑定里默认可用的过滤器 *
**********************************************************************/
var filters = avalon.filters = {
uppercase: function(str) {
return str.toUpperCase()
},
lowercase: function(str) {
return str.toLowerCase()
},
truncate: function(target, length, truncation) {
//length,新字符串长度,truncation,新字符串的结尾的字段,返回新字符串
length = length || 30
truncation = truncation === void(0) ? "..." : truncation
return target.length > length ? target.slice(0, length - truncation.length) + truncation : String(target)
},
camelize: camelize,
escape: function(html) {
//将字符串经过 html 转义得到适合在页面中显示的内容, 例如替换 < 为 &lt
return String(html)
.replace(/&(?!\w+;)/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
},
currency: function(number, symbol) {
symbol = symbol || ""
return symbol + avalon.filters.number(number)
},
number: function(number, decimals, dec_point, thousands_sep) {
//与PHP的number_format完全兼容
//number 必需,要格式化的数字
//decimals 可选,规定多少个小数位。
//dec_point 可选,规定用作小数点的字符串(默认为 . )。
//thousands_sep 可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。
// http://kevin.vanzonneveld.net
number = (number + "").replace(/[^0-9+\-Ee.]/g, "")
var n = !isFinite(+number) ? 0 : +number,
prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
sep = thousands_sep || ",",
dec = dec_point || ".",
s = "",
toFixedFix = function(n, prec) {
var k = Math.pow(10, prec)
return "" + Math.round(n * k) / k
}
// 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)
}
if ((s[1] || "").length < prec) {
s[1] = s[1] || ""
s[1] += new Array(prec - s[1].length + 1).join("0")
}
return s.join(dec)
}
}
/*
'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)
*/
new function() {
function toInt(str) {
return parseInt(str, 10)
}
function padNumber(num, digits, trim) {
var neg = ""
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) {
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 DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
NUMBER_STRING = /^\d+$/
var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/
// 1 2 3 4 5 6 7 8 9 10 11
function jsonStringToDate(string) {
var match
if (match = string.match(R_ISO8601_STR)) {
var date = new Date(0),
tzHour = 0,
tzMin = 0,
dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
timeSetter = match[8] ? date.setUTCHours : date.setHours
if (match[9]) {
tzHour = toInt(match[9] + match[10])
tzMin = toInt(match[9] + match[11])
}
dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]))
var h = toInt(match[4] || 0) - tzHour
var m = toInt(match[5] || 0) - tzMin
var s = toInt(match[6] || 0)
var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000)
timeSetter.call(date, h, m, s, ms)
return date
}
return string
}
var rfixFFDate = /^(\d+)-(\d+)-(\d{4})$/
var rfixIEDate = /^(\d+)\s+(\d+),(\d{4})$/
filters.date = function(date, format) {
var locate = filters.date.locate,
text = "",
parts = [],
fn, match
format = format || "mediumDate"
format = locate[format] || format
if (typeof date === "string") {
if (NUMBER_STRING.test(date)) {
date = toInt(date)
} else {
var trimDate = date.trim()
if (trimDate.match(rfixFFDate) || trimDate.match(rfixIEDate)) {
date = RegExp.$3 + "/" + RegExp.$1 + "/" + RegExp.$2
}
date = jsonStringToDate(date)
}
date = new Date(date)
}
if (typeof date === "number") {
date = new Date(date)
}
if (getType(date) !== "date") {
return
}
while (format) {
match = DATE_FORMATS_SPLIT.exec(format)
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 ah:mm:ss",
mediumDate: "yyyy-M-d",
mediumTime: "ah:mm:ss",
"short": "yy-M-d ah:mm",
shortDate: "yy-M-d",
shortTime: "ah:mm"
}
locate.SHORTMONTH = locate.MONTH
filters.date.locate = locate
}
/*********************************************************************
* AMD Loader *
**********************************************************************/
var innerRequire
var modules = avalon.modules = {
"ready!": {
exports: avalon
},
"avalon": {
exports: avalon,
state: 2
}
}
new function() {
var loadings = [] //正在加载中的模块列表
var factorys = [] //储存需要绑定ID与factory对应关系的模块(标准浏览器下,先parse的script节点会先onload)
var basepath
function cleanUrl(url) {
return (url || "").replace(/[?#].*/, "")
}
plugins.js = function(url, shim) {
var id = cleanUrl(url)
if (!modules[id]) { //如果之前没有加载过
modules[id] = {
id: id,
parent: parent,
exports: {}
}
if (shim) { //shim机制
innerRequire(shim.deps || "", function() {
loadJS(url, id, function() {
modules[id].state = 2
if (shim.exports)
modules[id].exports = typeof shim.exports === "function" ?
shim.exports() : window[shim.exports]
innerRequire.checkDeps()
})
})
} else {
loadJS(url, id)
}
}
return id
}
plugins.css = function(url) {
var id = url.replace(/(#.+|\W)/g, "") ////用于处理掉href中的hash与所有特殊符号
if (!DOC.getElementById(id)) {
var node = DOC.createElement("link")
node.rel = "stylesheet"
node.href = url
node.id = id
head.insertBefore(node, head.firstChild)
}
}
plugins.css.ext = ".css"
plugins.js.ext = ".js"
var cur = getCurrentScript(true)
if (!cur) { //处理window safari的Error没有stack的问题
cur = avalon.slice(document.scripts).pop().src
}
var url = cleanUrl(cur)
basepath = kernel.base = url.slice(0, url.lastIndexOf("/") + 1)
function getCurrentScript(base) {
// 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
var stack
try {
a.b.c() //强制报错,以便捕获e.stack
} catch (e) { //safari的错误对象只有line,sourceId,sourceURL
stack = e.stack
}
if (stack) {
/**e.stack最后一行在所有支持的浏览器大致如下:
*chrome23:
* at http://113.93.50.63/data.js:4:1
*firefox17:
*@http://113.93.50.63/query.js:4
*opera12:http://www.oldapps.com/opera.php?system=Windows_XP
*@http://113.93.50.63/data.js:4
*IE10:
* at Global code (http://113.93.50.63/data.js:4:1)
* //firefox4+ 可以用document.currentScript
*/
stack = stack.split(/[@ ]/g).pop() //取得最后一行,最后一个空格或@之后的部分
stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉换行符
return stack.replace(/(:\d+)?:\d+$/i, "") //去掉行号与或许存在的出错字符起始位置
}
var nodes = (base ? DOC : head).getElementsByTagName("script") //只在head标签中寻找
for (var i = nodes.length, node; node = nodes[--i]; ) {
if ((base || node.className === subscribers) && node.readyState === "interactive") {
return node.className = node.src
}
}
}
function checkCycle(deps, nick) {
//检测是否存在循环依赖
for (var id in deps) {
if (deps[id] === "司徒正美" && modules[id].state !== 2 && (id === nick || checkCycle(modules[id].deps, nick))) {
return true
}
}
}
function checkDeps() {
//检测此JS模块的依赖是否都已安装完毕,是则安装自身
loop: for (var i = loadings.length, id; id = loadings[--i]; ) {
var obj = modules[id],
deps = obj.deps
for (var key in deps) {
if (ohasOwn.call(deps, key) && modules[key].state !== 2) {
continue loop
}
}
//如果deps是空对象或者其依赖的模块的状态都是2
if (obj.state !== 2) {
loadings.splice(i, 1) //必须先移除再安装,防止在IE下DOM树建完后手动刷新页面,会多次执行它
fireFactory(obj.id, obj.args, obj.factory)
checkDeps() //如果成功,则再执行一次,以防有些模块就差本模块没有安装好
}
}
}
function checkFail(node, onError) {
var id = cleanUrl(node.src) //检测是否死链
node.onload = node.onerror = null
if (onError) {
setTimeout(function() {
head.removeChild(node)
})
log("加载 " + id + " 失败" + onError + " " + (!modules[id].state))
} else {
return true
}
}
var rdeuce = /\/\w+\/\.\./
function loadResources(url, parent, ret, shim) {
//1. 特别处理mass|ready标识符
if (url === "ready!" || (modules[url] && modules[url].state === 2)) {
return url
}
//2. 转化为完整路径
if (typeof kernel.shim[url] === "object") {
shim = kernel.shim[url]
}
if (kernel.paths[url]) { //别名机制
url = kernel.paths[url]
}
//3. 处理text! css! 等资源
var plugin
url = url.replace(/^\w+!/, function(a) {
plugin = a.slice(0, -1)
return ""
})
plugin = plugin || "js"
plugin = plugins[plugin] || noop
//4. 补全路径
if (/^(\w+)(\d)?:.*/.test(url)) {
ret = url
} else {
parent = parent.substr(0, parent.lastIndexOf('/'))
var tmp = url.charAt(0)
if (tmp !== "." && tmp !== "/") { //相对于根路径
ret = basepath + url
} else if (url.slice(0, 2) === "./") { //相对于兄弟路径
ret = parent + url.slice(1)
} else if (url.slice(0, 2) === "..") { //相对于父路径
ret = parent + "/" + url
while (rdeuce.test(ret)) {
ret = ret.replace(rdeuce, "")
}
} else if (tmp === "/") {
ret = parent + url //相对于兄弟路径
} else {
avalon.error("不符合模块标识规则: " + url)
}
}
//5. 补全扩展名
url = cleanUrl(ret)
var ext = plugin.ext
if (ext) {
if (url.slice(0 - ext.length) !== ext) {
ret += ext
}
}
//6. 缓存处理
if (kernel.nocache) {
ret += (ret.indexOf("?") === -1 ? "?" : "&") + Date.now()
}
return plugin(ret, shim)
}
function loadJS(url, id, callback) {
//通过script节点加载目标模块
var node = DOC.createElement("script")
node.className = subscribers //让getCurrentScript只处理类名为subscribers的script节点
node.onload = function() {
var factory = factorys.pop()
factory && factory.delay(id)
if (callback) {
callback()
}
log("已成功加载 " + url)
}
node.onerror = function() {
checkFail(node, true)
}
node.src = url //插入到head的第一个节点前,防止IE6下head标签没闭合前使用appendChild抛错
head.appendChild(node) //chrome下第二个参数不能为null
log("正准备加载 " + url) //更重要的是IE6下可以收窄getCurrentScript的寻找范围
}
innerRequire = avalon.require = function(list, factory, parent) {
// 用于检测它的依赖是否都为2
var deps = {},
// 用于保存依赖模块的返回值
args = [],
// 需要安装的模块数
dn = 0,
// 已安装完的模块数
cn = 0,
id = parent || "callback" + setTimeout("1")
parent = parent || basepath
String(list).replace(rword, function(el) {
var url = loadResources(el, parent)
if (url) {
dn++
if (modules[url] && modules[url].state === 2) {
cn++
}
if (!deps[url]) {
args.push(url)
deps[url] = "司徒正美" //去重
}
}
})
modules[id] = {//创建一个对象,记录模块的加载情况与其他信息
id: id,
factory: factory,
deps: deps,
args: args,
state: 1
}
if (dn === cn) { //如果需要安装的等于已安装好的
fireFactory(id, args, factory) //安装到框架中
} else {
//放到检测列队中,等待checkDeps处理
loadings.unshift(id)
}
checkDeps()
}
/**
* 定义模块
* @param {String} id ? 模块ID
* @param {Array} deps ? 依赖列表
* @param {Function} factory 模块工厂
* @api public
*/
innerRequire.define = function(id, deps, factory) { //模块名,依赖列表,模块本身
var args = avalon.slice(arguments)
if (typeof id === "string") {
var _id = args.shift()
}
if (typeof args[0] === "boolean") { //用于文件合并, 在标准浏览器中跳过补丁模块
if (args[0]) {
return
}
args.shift()
}
if (typeof args[0] === "function") {
args.unshift([])
} //上线合并后能直接得到模块ID,否则寻找当前正在解析中的script节点的src作为模块ID
//现在除了safari外,我们都能直接通过getCurrentScript一步到位得到当前执行的script节点,
//safari可通过onload+delay闭包组合解决
var name = modules[_id] && modules[_id].state >= 1 ? _id : cleanUrl(getCurrentScript())
if (!modules[name] && _id) {
modules[name] = {
id: name,
factory: factory,
state: 1
}
}
factory = args[1]
factory.id = _id //用于调试
factory.delay = function(d) {
args.push(d)
var isCycle = true
try {
isCycle = checkCycle(modules[d].deps, d)
} catch (e) {
}
if (isCycle) {
avalon.error(d + "模块与之前的某些模块存在循环依赖")
}
delete factory.delay //释放内存
innerRequire.apply(null, args) //0,1,2 --> 1,2,0
}
if (name) {
factory.delay(name, args)
} else { //先进先出
factorys.push(factory)
}
}
innerRequire.define.amd = modules
function fireFactory(id, deps, factory) {
for (var i = 0, array = [], d; d = deps[i++]; ) {
array.push(modules[d].exports)
}
var module = Object(modules[id]),
ret = factory.apply(window, array)
module.state = 2
if (ret !== void 0) {
modules[id].exports = ret
}
return ret
}
innerRequire.config = kernel
innerRequire.checkDeps = checkDeps
}
/*********************************************************************
* Touch Event *
**********************************************************************/
if ("ontouchstart" in window) {
void
function() {
var touchProxy = {}, touchTimeout, tapTimeout, swipeTimeout, holdTimeout,
now, firstTouch, _isPointerType, delta, deltaX = 0,
deltaY = 0,
touchNames = []
function swipeDirection(x1, x2, y1, y2) {
return Math.abs(x1 - x2) >=
Math.abs(y1 - y2) ? (x1 - x2 > 0 ? "left" : "right") : (y1 - y2 > 0 ? "up" : "down")
}
function longTap() {
if (touchProxy.last) {
touchProxy.fire("hold")
touchProxy = {}
}
}
function cancelHold() {
clearTimeout(holdTimeout)
}
function cancelAll() {
clearTimeout(touchTimeout)
clearTimeout(tapTimeout)
clearTimeout(swipeTimeout)
clearTimeout(holdTimeout)
touchProxy = {}
}
if (window.navigator.pointerEnabled) { //IE11 与 W3C
touchNames = ["pointerdown", "pointermove", "pointerup", "pointercancel"]
} else if (window.navigator.msPointerEnabled) { //IE9-10
touchNames = ["MSPointerDown", "MSPointerMove", "MSPointerUp", "MSPointerCancel"]
} else {
touchNames = ["touchstart", "touchmove", "touchend", "touchcancel"]
}
function isPrimaryTouch(event) { //是否纯净的触摸事件,非mousemove等模拟的事件,也不是手势事件
return (event.pointerType == "touch" ||
event.pointerType == event.MSPOINTER_TYPE_TOUCH) && event.isPrimary
}
function isPointerEventType(e, type) { //是否最新发布的PointerEvent
return (e.type == "pointer" + type ||
e.type.toLowerCase() == "mspointer" + type)
}
DOC.addEventListener(touchNames[0], function(e) {
if ((_isPointerType = isPointerEventType(e, "down")) && !isPrimaryTouch(e))
return
firstTouch = _isPointerType ? e : e.touches[0]
if (e.touches && e.touches.length === 1 && touchProxy.x2) {
touchProxy.x2 = touchProxy.y2 = void 0
}
now = Date.now()
delta = now - (touchProxy.last || now)
var el = firstTouch.target
touchProxy.el = "tagName" in el ? el : el.parentNode
clearTimeout(touchTimeout)
touchProxy.x1 = firstTouch.pageX
touchProxy.y1 = firstTouch.pageY
touchProxy.fire = function(name) {
avalon.fire(this.el, name)
}
if (delta > 0 && delta <= 250) { //双击
touchProxy.isDoubleTap = true
}
touchProxy.last = now
holdTimeout = setTimeout(longTap, 750)
})
DOC.addEventListener(touchNames[1], function(e) {
if ((_isPointerType = isPointerEventType(e, "move")) && !isPrimaryTouch(e))
return
firstTouch = _isPointerType ? e : e.touches[0]
cancelHold()
touchProxy.x2 = firstTouch.pageX
touchProxy.y2 = firstTouch.pageY
deltaX += Math.abs(touchProxy.x1 - touchProxy.x2)
deltaY += Math.abs(touchProxy.y1 - touchProxy.y2)
})
DOC.addEventListener(touchNames[2], function(e) {
if ((_isPointerType = isPointerEventType(e, "up")) && !isPrimaryTouch(e))
return
cancelHold()
// swipe
if ((touchProxy.x2 && Math.abs(touchProxy.x1 - touchProxy.x2) > 30) ||
(touchProxy.y2 && Math.abs(touchProxy.y1 - touchProxy.y2) > 30)) {
//如果是滑动,根据最初与最后的位置判定其滑动方向
swipeTimeout = setTimeout(function() {
touchProxy.fire("swipe")
touchProxy.fire("swipe" + (swipeDirection(touchProxy.x1, touchProxy.x2, touchProxy.y1, touchProxy.y2)))
touchProxy = {}
}, 0)
// normal tap
} else if ("last" in touchProxy) {
if (deltaX < 30 && deltaY < 30) { //如果移动的距离太小
tapTimeout = setTimeout(function() {
touchProxy.fire("tap")
if (touchProxy.isDoubleTap) {
touchProxy.fire('doubletap')
touchProxy = {}
} else {
touchTimeout = setTimeout(function() {
touchProxy.fire('singletap')
touchProxy = {}
}, 250)
}
}, 0)
} else {
touchProxy = {}
}
}
deltaX = deltaY = 0
})
DOC.addEventListener(touchNames[3], cancelAll)
}()
//http://quojs.tapquo.com/ http://code.baidu.com/
//'swipe', 'swipeleft', 'swiperight', 'swipeup', 'swipedown', 'doubletap', 'tap', 'singletap', 'hold'
}
/*********************************************************************
* DOMReady *
**********************************************************************/
function fireReady() {
modules["ready!"].state = 2
innerRequire.checkDeps()
fireReady = noop //隋性函数,防止IE9二次调用_checkDeps
}
if (DOC.readyState === "complete") {
setTimeout(fireReady) //如果在domReady之外加载
} else {
DOC.addEventListener("DOMContentLoaded", fireReady)
window.addEventListener("load", fireReady)
}
avalon.ready = function(fn) {
innerRequire("ready!", fn)
}
avalon.config({
loader: true
})
var msSelector = "[ms-controller],[ms-important],[ms-widget]"
avalon.ready(function() {
var elems = DOC.querySelectorAll(msSelector),
nodes = []
for (var i = 0, elem; elem = elems[i++]; ) {
if (!elem.__root__) {
var array = elem.querySelectorAll(msSelector)
for (var j = 0, el; el = array[j++]; ) {
el.__root__ = true
}
nodes.push(elem)
}
}
for (var i = 0, elem; elem = nodes[i++]; ) {
avalon.scan(elem)
}
})
})(document)
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/cowboy13/avalon.git
git@gitee.com:cowboy13/avalon.git
cowboy13
avalon
avalon
master

搜索帮助

D67c1975 1850385 1daf7b77 1850385