avalon-mmRouter-rails 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +17 -0
- data/Rakefile +2 -0
- data/avalon-mmRouter-rails.gemspec +23 -0
- data/lib/avalon/mmRouter/rails.rb +13 -0
- data/lib/avalon/mmRouter/rails/version.rb +7 -0
- data/vendor/assets/javascripts/avalon/mmHistory.js +310 -0
- data/vendor/assets/javascripts/avalon/mmPromise.js +221 -0
- data/vendor/assets/javascripts/avalon/mmRouter.js +351 -0
- data/vendor/assets/javascripts/avalon/mmState.js +1027 -0
- data/vendor/assets/javascripts/avalon/old-mmState.js +771 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a296a27f53516f76fa78459c0dbf614e1b30759e
|
4
|
+
data.tar.gz: 7a2c0f6ec0d2b9d38f81e09d396ca58583c4e089
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ccf719babbefbf6d1c891309a78c4f08b33fc8808bf5ba664e343b90fad62b8baa15a0d73fb3b834d78a34d19237f9f0df54f6940b68a5f300a87d50e8b5e561
|
7
|
+
data.tar.gz: 3cd2a801ced381fb890948c89e76338c829bd811af60e8e009cd0e94731ad9d434f43c643bdc2b25f2241f864b5fd45a19b93be1af3dbb7a08cdcb456f2d6419
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 iron
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Avalon::MmRouter::Rails
|
2
|
+
|
3
|
+
Rails 3.1 asset-pipeline gem to provide avalon.js mmRouter
|
4
|
+
|
5
|
+
mmRouter js: https://github.com/RubyLouvre/mmRouter
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'avalon-mmRouter-rails'
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install avalon-mmRouter-rails
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'avalon/mmRouter/rails/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "avalon-mmRouter-rails"
|
8
|
+
spec.version = Avalon::MmRouter::Rails::VERSION
|
9
|
+
spec.authors = ["zj0713001"]
|
10
|
+
spec.email = ["zj0713001@gmail.com"]
|
11
|
+
spec.summary = %q{Use javascript framework Avalon mmRouter with Rails 3+}
|
12
|
+
spec.description = %q{his gem provides javascript framework Avalon mmRouter for your Rails 3+ application.}
|
13
|
+
spec.homepage = "https://github.com/zj0713001/avalon-mmRouter-rails"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "avalon/mmRouter/rails/version"
|
2
|
+
|
3
|
+
module Avalon
|
4
|
+
module MmRouter
|
5
|
+
module Rails
|
6
|
+
if defined?(::Rails) and Gem::Requirement.new('>= 3.1').satisfied_by?(Gem::Version.new ::Rails.version)
|
7
|
+
class Rails::Engine < ::Rails::Engine
|
8
|
+
# this class enables the asset pipeline
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
/*
|
2
|
+
*
|
3
|
+
* version 0.7
|
4
|
+
* built in 2015.10.12
|
5
|
+
*/
|
6
|
+
|
7
|
+
define(["avalon"], function(avalon) {
|
8
|
+
var anchorElement = document.createElement('a')
|
9
|
+
|
10
|
+
var History = avalon.History = function() {
|
11
|
+
this.location = location
|
12
|
+
}
|
13
|
+
|
14
|
+
History.started = false
|
15
|
+
//取得当前IE的真实运行环境
|
16
|
+
History.IEVersion = (function() {
|
17
|
+
var mode = document.documentMode
|
18
|
+
return mode ? mode : window.XMLHttpRequest ? 7 : 6
|
19
|
+
})()
|
20
|
+
|
21
|
+
History.defaults = {
|
22
|
+
basepath: "/",
|
23
|
+
html5Mode: false,
|
24
|
+
hashPrefix: "!",
|
25
|
+
iframeID: null, //IE6-7,如果有在页面写死了一个iframe,这样似乎刷新的时候不会丢掉之前的历史
|
26
|
+
interval: 50, //IE6-7,使用轮询,这是其时间时隔
|
27
|
+
fireAnchor: true,//决定是否将滚动条定位于与hash同ID的元素上
|
28
|
+
routeElementJudger: avalon.noop // 判断a元素是否是触发router切换的链接
|
29
|
+
}
|
30
|
+
|
31
|
+
var oldIE = window.VBArray && History.IEVersion <= 7
|
32
|
+
var supportPushState = !!(window.history.pushState)
|
33
|
+
var supportHashChange = !!("onhashchange" in window && (!window.VBArray || !oldIE))
|
34
|
+
History.prototype = {
|
35
|
+
constructor: History,
|
36
|
+
getFragment: function(fragment) {
|
37
|
+
if (fragment == null) {
|
38
|
+
if (this.monitorMode === "popstate") {
|
39
|
+
fragment = this.getPath()
|
40
|
+
} else {
|
41
|
+
fragment = this.getHash()
|
42
|
+
}
|
43
|
+
}
|
44
|
+
return fragment.replace(/^[#\/]|\s+$/g, "")
|
45
|
+
},
|
46
|
+
getHash: function(window) {
|
47
|
+
// IE6直接用location.hash取hash,可能会取少一部分内容
|
48
|
+
// 比如 http://www.cnblogs.com/rubylouvre#stream/xxxxx?lang=zh_c
|
49
|
+
// ie6 => location.hash = #stream/xxxxx
|
50
|
+
// 其他浏览器 => location.hash = #stream/xxxxx?lang=zh_c
|
51
|
+
// firefox 会自作多情对hash进行decodeURIComponent
|
52
|
+
// 又比如 http://www.cnblogs.com/rubylouvre/#!/home/q={%22thedate%22:%2220121010~20121010%22}
|
53
|
+
// firefox 15 => #!/home/q={"thedate":"20121010~20121010"}
|
54
|
+
// 其他浏览器 => #!/home/q={%22thedate%22:%2220121010~20121010%22}
|
55
|
+
var path = (window || this).location.href
|
56
|
+
return this._getHash(path.slice(path.indexOf("#")))
|
57
|
+
},
|
58
|
+
_getHash: function(path) {
|
59
|
+
if (path.indexOf("#/") === 0) {
|
60
|
+
return decodeURIComponent(path.slice(2))
|
61
|
+
}
|
62
|
+
if (path.indexOf("#!/") === 0) {
|
63
|
+
return decodeURIComponent(path.slice(3))
|
64
|
+
}
|
65
|
+
return ""
|
66
|
+
},
|
67
|
+
getPath: function() {
|
68
|
+
var path = decodeURIComponent(this.location.pathname + this.location.search)
|
69
|
+
var root = this.basepath.slice(0, -1)
|
70
|
+
if (!path.indexOf(root))
|
71
|
+
path = path.slice(root.length)
|
72
|
+
return path.slice(1)
|
73
|
+
},
|
74
|
+
_getAbsolutePath: function(a) {
|
75
|
+
return !a.hasAttribute ? a.getAttribute("href", 4) : a.href
|
76
|
+
},
|
77
|
+
/*
|
78
|
+
* @interface avalon.history.start 开始监听历史变化
|
79
|
+
* @param options 配置参数
|
80
|
+
* @param options.hashPrefix hash以什么字符串开头,默认是 "!",对应实际效果就是"#!"
|
81
|
+
* @param options.routeElementJudger 判断a元素是否是触发router切换的链接的函数,return true则触发切换,默认为avalon.noop,history内部有一个判定逻辑,是先判定a元素的href属性是否以hashPrefix开头,如果是则当做router切换元素,因此综合判定规则是 href.indexOf(hashPrefix) == 0 || routeElementJudger(ele, ele.href),如果routeElementJudger返回true则跳转至href,如果返回的是字符串,则跳转至返回的字符串,如果返回false则返回浏览器默认行为
|
82
|
+
* @param options.html5Mode 是否采用html5模式,即不使用hash来记录历史,默认false
|
83
|
+
* @param options.fireAnchor 决定是否将滚动条定位于与hash同ID的元素上,默认为true
|
84
|
+
* @param options.basepath 根目录,默认为"/"
|
85
|
+
*/
|
86
|
+
start: function(options) {
|
87
|
+
if (History.started)
|
88
|
+
throw new Error("avalon.history has already been started")
|
89
|
+
History.started = true
|
90
|
+
this.options = avalon.mix({}, History.defaults, options)
|
91
|
+
//IE6不支持maxHeight, IE7支持XMLHttpRequest, IE8支持window.Element,querySelector,
|
92
|
+
//IE9支持window.Node, window.HTMLElement, IE10不支持条件注释
|
93
|
+
//确保html5Mode属性存在,并且是一个布尔
|
94
|
+
this.html5Mode = !!this.options.html5Mode
|
95
|
+
//监听模式
|
96
|
+
this.monitorMode = this.html5Mode ? "popstate" : "hashchange"
|
97
|
+
if (!supportPushState) {
|
98
|
+
if (this.html5Mode) {
|
99
|
+
avalon.log("如果浏览器不支持HTML5 pushState,强制使用hash hack!")
|
100
|
+
this.html5Mode = false
|
101
|
+
}
|
102
|
+
this.monitorMode = "hashchange"
|
103
|
+
}
|
104
|
+
if (!supportHashChange) {
|
105
|
+
this.monitorMode = "iframepoll"
|
106
|
+
}
|
107
|
+
this.prefix = "#" + this.options.hashPrefix + "/"
|
108
|
+
//确认前后都存在斜线, 如"aaa/ --> /aaa/" , "/aaa --> /aaa/", "aaa --> /aaa/", "/ --> /"
|
109
|
+
this.basepath = ("/" + this.options.basepath + "/").replace(/^\/+|\/+$/g, "/") // 去最左右两边的斜线
|
110
|
+
|
111
|
+
this.fragment = this.getFragment()
|
112
|
+
|
113
|
+
anchorElement.href = this.basepath
|
114
|
+
this.rootpath = this._getAbsolutePath(anchorElement)
|
115
|
+
var that = this
|
116
|
+
|
117
|
+
var html = '<!doctype html><html><body>@</body></html>'
|
118
|
+
if (this.options.domain) {
|
119
|
+
html = html.replace("<body>", "<script>document.domain =" + this.options.domain + "</script><body>")
|
120
|
+
}
|
121
|
+
this.iframeHTML = html
|
122
|
+
if (this.monitorMode === "iframepoll") {
|
123
|
+
//IE6,7在hash改变时不会产生历史,需要用一个iframe来共享历史
|
124
|
+
avalon.ready(function() {
|
125
|
+
if(that.iframe) return
|
126
|
+
var iframe = that.iframe || document.getElementById(that.iframeID) || document.createElement('iframe')
|
127
|
+
iframe.src = 'javascript:0'
|
128
|
+
iframe.style.display = 'none'
|
129
|
+
iframe.tabIndex = -1
|
130
|
+
document.body.appendChild(iframe)
|
131
|
+
that.iframe = iframe.contentWindow
|
132
|
+
that._setIframeHistory(that.prefix + that.fragment)
|
133
|
+
})
|
134
|
+
|
135
|
+
}
|
136
|
+
|
137
|
+
// 支持popstate 就监听popstate
|
138
|
+
// 支持hashchange 就监听hashchange
|
139
|
+
// 否则的话只能每隔一段时间进行检测了
|
140
|
+
function checkUrl(e) {
|
141
|
+
var iframe = that.iframe
|
142
|
+
if (that.monitorMode === "iframepoll" && !iframe) {
|
143
|
+
return false
|
144
|
+
}
|
145
|
+
var pageHash = that.getFragment(), hash, lastHash = avalon.router.getLastPath()
|
146
|
+
if (iframe) {//IE67
|
147
|
+
var iframeHash = that.getHash(iframe)
|
148
|
+
//与当前页面hash不等于之前的页面hash,这主要是用户通过点击链接引发的
|
149
|
+
if (pageHash !== lastHash) {
|
150
|
+
that._setIframeHistory(that.prefix + pageHash)
|
151
|
+
hash = pageHash
|
152
|
+
//如果是后退按钮触发hash不一致
|
153
|
+
} else if (iframeHash !== lastHash) {
|
154
|
+
that.location.hash = that.prefix + iframeHash
|
155
|
+
hash = iframeHash
|
156
|
+
}
|
157
|
+
|
158
|
+
} else if (pageHash !== lastHash) {
|
159
|
+
hash = pageHash
|
160
|
+
}
|
161
|
+
if (hash !== void 0) {
|
162
|
+
that.fragment = hash
|
163
|
+
that.fireRouteChange(hash, {fromHistory: true})
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
//thanks https://github.com/browserstate/history.js/blob/master/scripts/uncompressed/history.html4.js#L272
|
168
|
+
|
169
|
+
// 支持popstate 就监听popstate
|
170
|
+
// 支持hashchange 就监听hashchange(IE8,IE9,FF3)
|
171
|
+
// 否则的话只能每隔一段时间进行检测了(IE6, IE7)
|
172
|
+
switch (this.monitorMode) {
|
173
|
+
case "popstate":
|
174
|
+
this.checkUrl = avalon.bind(window, "popstate", checkUrl)
|
175
|
+
this._fireLocationChange = checkUrl
|
176
|
+
break
|
177
|
+
case "hashchange":
|
178
|
+
this.checkUrl = avalon.bind(window, "hashchange", checkUrl)
|
179
|
+
break;
|
180
|
+
case "iframepoll":
|
181
|
+
this.checkUrl = setInterval(checkUrl, this.options.interval)
|
182
|
+
break;
|
183
|
+
}
|
184
|
+
//根据当前的location立即进入不同的路由回调
|
185
|
+
avalon.ready(function() {
|
186
|
+
that.fireRouteChange(that.fragment || "/", {replace: true})
|
187
|
+
})
|
188
|
+
},
|
189
|
+
fireRouteChange: function(hash, options) {
|
190
|
+
var router = avalon.router
|
191
|
+
if (router && router.navigate) {
|
192
|
+
router.setLastPath(hash)
|
193
|
+
router.navigate(hash === "/" ? hash : "/" + hash, options)
|
194
|
+
}
|
195
|
+
if (this.options.fireAnchor) {
|
196
|
+
scrollToAnchorId(hash.replace(/\?.*/g,""))
|
197
|
+
}
|
198
|
+
},
|
199
|
+
// 中断URL的监听
|
200
|
+
stop: function() {
|
201
|
+
avalon.unbind(window, "popstate", this.checkUrl)
|
202
|
+
avalon.unbind(window, "hashchange", this.checkUrl)
|
203
|
+
clearInterval(this.checkUrl)
|
204
|
+
History.started = false
|
205
|
+
},
|
206
|
+
updateLocation: function(hash, options, urlHash) {
|
207
|
+
var options = options || {},
|
208
|
+
rp = options.replace,
|
209
|
+
st = options.silent
|
210
|
+
if (this.monitorMode === "popstate") {
|
211
|
+
// html5 mode 第一次加载的时候保留之前的hash
|
212
|
+
var path = this.rootpath + hash + (urlHash || "")
|
213
|
+
// html5 model包含query
|
214
|
+
if(path != this.location.href.split("#")[0]) history[rp ? "replaceState" : "pushState"]({path: path}, document.title, path)
|
215
|
+
if(!st) this._fireLocationChange()
|
216
|
+
} else {
|
217
|
+
var newHash = this.prefix + hash
|
218
|
+
if(st && hash != this.getHash()) {
|
219
|
+
this._setIframeHistory(newHash, rp)
|
220
|
+
if(this.fragment) avalon.router.setLastPath(this.fragment)
|
221
|
+
this.fragment = this._getHash(newHash)
|
222
|
+
}
|
223
|
+
this._setHash(this.location, newHash, rp)
|
224
|
+
}
|
225
|
+
},
|
226
|
+
_setHash: function(location, hash, replace){
|
227
|
+
var href = location.href.replace(/(javascript:|#).*$/, '')
|
228
|
+
if (replace){
|
229
|
+
location.replace(href + hash)
|
230
|
+
}
|
231
|
+
else location.hash = hash
|
232
|
+
},
|
233
|
+
_setIframeHistory: function(hash, replace) {
|
234
|
+
if(!this.iframe) return
|
235
|
+
var idoc = this.iframe.document
|
236
|
+
idoc.open()
|
237
|
+
idoc.write(this.iframeHTML)
|
238
|
+
idoc.close()
|
239
|
+
this._setHash(idoc.location, hash, replace)
|
240
|
+
}
|
241
|
+
}
|
242
|
+
|
243
|
+
avalon.history = new History
|
244
|
+
|
245
|
+
//https://github.com/asual/jquery-address/blob/master/src/jquery.address.js
|
246
|
+
|
247
|
+
//劫持页面上所有点击事件,如果事件源来自链接或其内部,
|
248
|
+
//并且它不会跳出本页,并且以"#/"或"#!/"开头,那么触发updateLocation方法
|
249
|
+
avalon.bind(document, "click", function(event) {
|
250
|
+
var defaultPrevented = "defaultPrevented" in event ? event['defaultPrevented'] : event.returnValue === false
|
251
|
+
|
252
|
+
if (!History.started || defaultPrevented || event.ctrlKey || event.metaKey || event.which === 2)
|
253
|
+
return
|
254
|
+
var target = event.target
|
255
|
+
while (target.nodeName !== "A") {
|
256
|
+
target = target.parentNode
|
257
|
+
if (!target || target.tagName === "BODY") {
|
258
|
+
return
|
259
|
+
}
|
260
|
+
}
|
261
|
+
|
262
|
+
if (targetIsThisWindow(target.target)) {
|
263
|
+
var href = oldIE ? target.getAttribute("href", 2) : target.getAttribute("href") || target.getAttribute("xlink:href")
|
264
|
+
var prefix = avalon.history.prefix
|
265
|
+
if (href === null) { // href is null if the attribute is not present
|
266
|
+
return
|
267
|
+
}
|
268
|
+
var hash = href.replace(prefix, "").trim()
|
269
|
+
if(!(href.indexOf(prefix) === 0 && hash !== "")) {
|
270
|
+
var routeElementJudger = avalon.history.options.routeElementJudger
|
271
|
+
hash = routeElementJudger(target, href)
|
272
|
+
if(hash === true) hash = href
|
273
|
+
}
|
274
|
+
if (hash) {
|
275
|
+
event.preventDefault()
|
276
|
+
avalon.router && avalon.router.navigate(hash)
|
277
|
+
}
|
278
|
+
}
|
279
|
+
})
|
280
|
+
|
281
|
+
//判定A标签的target属性是否指向自身
|
282
|
+
//thanks https://github.com/quirkey/sammy/blob/master/lib/sammy.js#L219
|
283
|
+
function targetIsThisWindow(targetWindow) {
|
284
|
+
if (!targetWindow || targetWindow === window.name || targetWindow === '_self' || (targetWindow === 'top' && window == window.top)) {
|
285
|
+
return true
|
286
|
+
}
|
287
|
+
return false
|
288
|
+
}
|
289
|
+
//得到页面第一个符合条件的A标签
|
290
|
+
function getFirstAnchor(list) {
|
291
|
+
for (var i = 0, el; el = list[i++]; ) {
|
292
|
+
if (el.nodeName === "A") {
|
293
|
+
return el
|
294
|
+
}
|
295
|
+
}
|
296
|
+
}
|
297
|
+
|
298
|
+
function scrollToAnchorId(hash, el) {
|
299
|
+
if ((el = document.getElementById(hash))) {
|
300
|
+
el.scrollIntoView()
|
301
|
+
} else if ((el = getFirstAnchor(document.getElementsByName(hash)))) {
|
302
|
+
el.scrollIntoView()
|
303
|
+
} else {
|
304
|
+
window.scrollTo(0, 0)
|
305
|
+
}
|
306
|
+
}
|
307
|
+
return avalon
|
308
|
+
})
|
309
|
+
|
310
|
+
// 主要参数有 basepath html5Mode hashPrefix interval domain fireAnchor
|
@@ -0,0 +1,221 @@
|
|
1
|
+
define(["avalon"], function (avalon) {
|
2
|
+
//chrome36的原生Promise还多了一个defer()静态方法,允许不通过传参就能生成Promise实例,
|
3
|
+
//另还多了一个chain(onSuccess, onFail)原型方法,意义不明
|
4
|
+
//目前,firefox24, opera19也支持原生Promise(chrome32就支持了,但需要打开开关,自36起直接可用)
|
5
|
+
//本模块提供的Promise完整实现ECMA262v6 的Promise规范
|
6
|
+
//2015.3.12 支持async属性
|
7
|
+
function ok(val) {
|
8
|
+
return val
|
9
|
+
}
|
10
|
+
function ng(e) {
|
11
|
+
throw e
|
12
|
+
}
|
13
|
+
|
14
|
+
function done(onSuccess) {//添加成功回调
|
15
|
+
return this.then(onSuccess, ng)
|
16
|
+
}
|
17
|
+
function fail(onFail) {//添加出错回调
|
18
|
+
return this.then(ok, onFail)
|
19
|
+
}
|
20
|
+
function defer() {
|
21
|
+
var ret = {};
|
22
|
+
ret.promise = new this(function (resolve, reject) {
|
23
|
+
ret.resolve = resolve
|
24
|
+
ret.reject = reject
|
25
|
+
});
|
26
|
+
return ret
|
27
|
+
}
|
28
|
+
var msPromise = function (executor) {
|
29
|
+
this._callbacks = []
|
30
|
+
var me = this
|
31
|
+
if (typeof this !== "object")
|
32
|
+
throw new TypeError("Promises must be constructed via new")
|
33
|
+
if (typeof executor !== "function")
|
34
|
+
throw new TypeError("not a function")
|
35
|
+
|
36
|
+
executor(function (value) {
|
37
|
+
_resolve(me, value)
|
38
|
+
}, function (reason) {
|
39
|
+
_reject(me, reason)
|
40
|
+
})
|
41
|
+
}
|
42
|
+
function fireCallbacks(promise, fn) {
|
43
|
+
if (typeof promise.async === "boolean") {
|
44
|
+
var isAsync = promise.async
|
45
|
+
} else {
|
46
|
+
isAsync = promise.async = true
|
47
|
+
}
|
48
|
+
if (isAsync) {
|
49
|
+
window.setTimeout(fn, 0)
|
50
|
+
} else {
|
51
|
+
fn()
|
52
|
+
}
|
53
|
+
}
|
54
|
+
//返回一个已经处于`resolved`状态的Promise对象
|
55
|
+
msPromise.resolve = function (value) {
|
56
|
+
return new msPromise(function (resolve) {
|
57
|
+
resolve(value)
|
58
|
+
})
|
59
|
+
}
|
60
|
+
//返回一个已经处于`rejected`状态的Promise对象
|
61
|
+
msPromise.reject = function (reason) {
|
62
|
+
return new msPromise(function (resolve, reject) {
|
63
|
+
reject(reason)
|
64
|
+
})
|
65
|
+
}
|
66
|
+
|
67
|
+
msPromise.prototype = {
|
68
|
+
//一个Promise对象一共有3个状态:
|
69
|
+
//- `pending`:还处在等待状态,并没有明确最终结果
|
70
|
+
//- `resolved`:任务已经完成,处在成功状态
|
71
|
+
//- `rejected`:任务已经完成,处在失败状态
|
72
|
+
constructor: msPromise,
|
73
|
+
_state: "pending",
|
74
|
+
_fired: false, //判定是否已经被触发
|
75
|
+
_fire: function (onSuccess, onFail) {
|
76
|
+
if (this._state === "rejected") {
|
77
|
+
if (typeof onFail === "function") {
|
78
|
+
onFail(this._value)
|
79
|
+
} else {
|
80
|
+
throw this._value
|
81
|
+
}
|
82
|
+
} else {
|
83
|
+
if (typeof onSuccess === "function") {
|
84
|
+
onSuccess(this._value)
|
85
|
+
}
|
86
|
+
}
|
87
|
+
},
|
88
|
+
_then: function (onSuccess, onFail) {
|
89
|
+
if (this._fired) {//在已有Promise上添加回调
|
90
|
+
var me = this
|
91
|
+
fireCallbacks(me, function () {
|
92
|
+
me._fire(onSuccess, onFail)
|
93
|
+
});
|
94
|
+
} else {
|
95
|
+
this._callbacks.push({onSuccess: onSuccess, onFail: onFail})
|
96
|
+
}
|
97
|
+
},
|
98
|
+
then: function (onSuccess, onFail) {
|
99
|
+
onSuccess = typeof onSuccess === "function" ? onSuccess : ok
|
100
|
+
onFail = typeof onFail === "function" ? onFail : ng
|
101
|
+
var me = this//在新的Promise上添加回调
|
102
|
+
var nextPromise = new msPromise(function (resolve, reject) {
|
103
|
+
me._then(function (value) {
|
104
|
+
try {
|
105
|
+
value = onSuccess(value)
|
106
|
+
} catch (e) {
|
107
|
+
// https://promisesaplus.com/#point-55
|
108
|
+
reject(e)
|
109
|
+
return
|
110
|
+
}
|
111
|
+
resolve(value)
|
112
|
+
}, function (value) {
|
113
|
+
try {
|
114
|
+
value = onFail(value)
|
115
|
+
} catch (e) {
|
116
|
+
reject(e)
|
117
|
+
return
|
118
|
+
}
|
119
|
+
resolve(value)
|
120
|
+
})
|
121
|
+
})
|
122
|
+
for (var i in me) {
|
123
|
+
if (!personal[i]) {
|
124
|
+
nextPromise[i] = me[i]
|
125
|
+
}
|
126
|
+
}
|
127
|
+
return nextPromise
|
128
|
+
},
|
129
|
+
"done": done,
|
130
|
+
"catch": fail,
|
131
|
+
"fail": fail
|
132
|
+
}
|
133
|
+
var personal = {
|
134
|
+
_state: 1,
|
135
|
+
_fired: 1,
|
136
|
+
_value: 1,
|
137
|
+
_callbacks: 1
|
138
|
+
}
|
139
|
+
function _resolve(promise, value) {//触发成功回调
|
140
|
+
if (promise._state !== "pending")
|
141
|
+
return;
|
142
|
+
if (value && typeof value.then === "function") {
|
143
|
+
//thenable对象使用then,Promise实例使用_then
|
144
|
+
var method = value instanceof msPromise ? "_then" : "then"
|
145
|
+
value[method](function (val) {
|
146
|
+
_transmit(promise, val, true)
|
147
|
+
}, function (reason) {
|
148
|
+
_transmit(promise, reason, false)
|
149
|
+
});
|
150
|
+
} else {
|
151
|
+
_transmit(promise, value, true);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
function _reject(promise, value) {//触发失败回调
|
155
|
+
if (promise._state !== "pending")
|
156
|
+
return
|
157
|
+
_transmit(promise, value, false)
|
158
|
+
}
|
159
|
+
//改变Promise的_fired值,并保持用户传参,触发所有回调
|
160
|
+
function _transmit(promise, value, isResolved) {
|
161
|
+
promise._fired = true;
|
162
|
+
promise._value = value;
|
163
|
+
promise._state = isResolved ? "fulfilled" : "rejected"
|
164
|
+
fireCallbacks(promise, function () {
|
165
|
+
promise._callbacks.forEach(function (data) {
|
166
|
+
promise._fire(data.onSuccess, data.onFail);
|
167
|
+
})
|
168
|
+
})
|
169
|
+
}
|
170
|
+
function _some(any, iterable) {
|
171
|
+
iterable = Array.isArray(iterable) ? iterable : []
|
172
|
+
var n = 0, result = [], end
|
173
|
+
return new msPromise(function (resolve, reject) {
|
174
|
+
// 空数组直接resolve
|
175
|
+
if (!iterable.length)
|
176
|
+
resolve(result)
|
177
|
+
function loop(a, index) {
|
178
|
+
a.then(function (ret) {
|
179
|
+
if (!end) {
|
180
|
+
result[index] = ret//保证回调的顺序
|
181
|
+
n++
|
182
|
+
if (any || n >= iterable.length) {
|
183
|
+
resolve(any ? ret : result)
|
184
|
+
end = true
|
185
|
+
}
|
186
|
+
}
|
187
|
+
}, function (e) {
|
188
|
+
end = true
|
189
|
+
reject(e)
|
190
|
+
})
|
191
|
+
}
|
192
|
+
for (var i = 0, l = iterable.length; i < l; i++) {
|
193
|
+
loop(iterable[i], i)
|
194
|
+
}
|
195
|
+
})
|
196
|
+
}
|
197
|
+
|
198
|
+
msPromise.all = function (iterable) {
|
199
|
+
return _some(false, iterable)
|
200
|
+
}
|
201
|
+
msPromise.race = function (iterable) {
|
202
|
+
return _some(true, iterable)
|
203
|
+
}
|
204
|
+
msPromise.defer = defer
|
205
|
+
|
206
|
+
|
207
|
+
|
208
|
+
avalon.Promise = msPromise
|
209
|
+
var nativePromise = window.Promise
|
210
|
+
if (/native code/.test(nativePromise)) {
|
211
|
+
nativePromise.prototype.done = done
|
212
|
+
nativePromise.prototype.fail = fail
|
213
|
+
if (!nativePromise.defer) { //chrome实现的私有方法
|
214
|
+
nativePromise.defer = defer
|
215
|
+
}
|
216
|
+
}
|
217
|
+
return window.Promise = nativePromise || msPromise
|
218
|
+
|
219
|
+
})
|
220
|
+
//https://github.com/ecomfe/er/blob/master/src/Deferred.js
|
221
|
+
//http://jser.info/post/77696682011/es6-promises
|