avalon-mmRouter-rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|