nuijs
Version:
nui框架
610 lines (577 loc) • 18 kB
JavaScript
/**
* @author Aniu[2017-02-27 23:46]
* @update Aniu[2018-01-02 21:25]
* @version 1.0.2
* @description 路由
*/
Nui.define(function(require) {
var component = require('../core/component');
var template = require('../core/template');
var events = require('../core/events');
var request = require('../core/request');
var statics = {
_paths: {},
_request: {},
_init: function() {
var self = this;
Nui.doc.on('click', '.nui-router-back', function() {
return self.back()
}).on('click', '.nui-router-forward', function() {
return self.forward()
})
},
_setPaths: function(rule, paths) {
if (!this._paths[rule]) {
this._paths[rule] = paths
}
},
_replace: function(hash) {
//IE8-中 A标签href属性会被添加上域名,将其移除
return hash.replace(location.protocol + '//' + location.host, '')
//移除空白
.replace(/\s+/g, '')
//移除 #!前缀
.replace(/^\#\!?/, '')
//开头添加斜杠
.replace(/^([^\/])/, '/$1')
//移除末尾斜杠
.replace(/\/$/, '');
},
_getWrapper: function(object) {
var opts = object._options, getWrapper = opts.getWrapper;
var wrapper = '<div class="nui-router-wrapper"></div>';
if (getWrapper) {
if (typeof getWrapper === 'string') {
wrapper = getWrapper
}
else if (typeof getWrapper === 'function') {
wrapper = getWrapper.call(opts, object)
}
}
return $(wrapper).appendTo(object.container)
},
_split: function(url) {
var ret = {
url: url,
params: {}
}
var match = url.match(/\?[^\/\s]+$/);
if (match) {
var params = match[0];
ret.url = url.replace(params, '');
params = params.replace('?', '').split('&');
Nui.each(params, function(v, k) {
var arr = v.split('=');
ret.params[arr[0]] = arr[1]
})
}
return ret;
},
_change: function() {
var self = this, hashTemp = location.hash,
ret = this._split(hashTemp), hash = self._replace(ret.url),
query = ret.params;
self.isRender = false;
delete self._active;
delete self._options;
Nui.each(self._paths, function(v) {
if (hash === v.path || hash.indexOf(v.path + '/') === 0) {
var _hash = hash.replace(v.path, '').replace(/^\//, '');
var params = _hash ? _hash.split('/') : [];
var object = self.__instances[v.id], opts = object._options;
var match = params.length === v.params.length || opts.level === 3;
if (match) {
var render = function() {
//router.location强制刷新或者公共容器才会重新渲染
var isRender = object._isRender === true || !object._wrapper;
var changed;
delete object._isRender;
if (opts.wrapper && !object._wrapper) {
if (typeof opts.wrapper !== 'boolean') {
object._wrapper = object.container.children(opts.wrapper)
}
else {
object._wrapper = self._getWrapper(object)
}
object._inner = object._wrapper.children();
}
else if (!opts.wrapper && !self._wrapper) {
self._wrapper = self._getWrapper(object);
self._inner = self._wrapper.children();
}
if (isRender) {
if (object.rendered) {
opts.data = Nui.extend(true, {}, object._defaultOptions.data)
}
//取消前一个页面的所有请求
Nui.each(self._request, function(v, i) {
var obj = self.__instances[i];
if (!obj._options.wrapper || obj === object) {
Nui.each(v, function(xhr, url) {
xhr.abort()
})
delete self._request[i]
}
})
}
self._active = {
path: v.path + '/',
url: hash + '/',
params: {},
query: query
}
self._options = opts
Nui.each(v.params, function(val, key) {
self._active.params[val] = params[key]
})
Nui.each(query, function(val, key) {
self._active.params[val] = query[key]
})
opts.data = Nui.extend(true, opts.data, self._active);
opts.element = object._inner || self._inner;
var wrapper = object._wrapper || self._wrapper;
if (!opts.element || !opts.element.length) {
opts.element = wrapper
}
var callback = function() {
if (typeof opts.onAfter === 'function') {
opts.onAfter.call(opts, object)
}
if (Nui.bsie7) {
self._setHistory(hashTemp)
}
self.isRender = true
object._emitListener('change')
}
var showWrapper = function() {
var siblings = wrapper.siblings('.nui-router-wrapper').hide();
wrapper.show()
if (opts.element !== wrapper) {
wrapper.children().show();
siblings.children().hide();
}
}
var sendData;
if (object._sendData === true) {
if (typeof object.sendData === 'object' && typeof opts.onData === 'function') {
opts.data = Nui.extend(true, opts.data, object.sendData);
opts.onData.call(opts, object.sendData, object);
delete object._sendData;
}
else if (typeof object.sendData === 'function') {
sendData = object.sendData;
}
}
if (typeof opts.onChange === 'function') {
changed = opts.onChange.call(opts, object, isRender)
}
//true不渲染,但是执行onAfter
if (typeof changed === 'boolean') {
if (changed === true) {
showWrapper()
callback()
}
else {
self.isRender = true
}
return false
}
showWrapper()
if (isRender) {
opts.element.off();
object.render.call(object);
if (sendData) {
sendData(opts)
}
if (typeof opts.onInit === 'function') {
opts.onInit.call(opts, object);
}
events.call(opts);
object.rendered = true;
} else {
if (sendData) {
sendData(opts)
}
}
callback()
if (object.sendData !== undefined) {
delete object.sendData;
}
}
//异步渲染
if (typeof opts.async === 'function') {
self.isRender = true;
opts.async.call(opts, function(_options) {
delete object._options.async;
delete object._defaultOptions.async;
object._defaultOptions = Nui.extend(true, {}, object._defaultOptions, _options)
object.option(_options, true)
opts = object._options;
render()
})
}
else {
render()
}
return false
}
}
})
if (!self.isRender) {
self._entry(hash)
}
self._oldhash = hashTemp;
},
//检测当前路由地址是否存在,不存在则跳转到入口页面
_entry: function(hash) {
var entry = false
Nui.each(this.__instances, function(v) {
if (v._options.entry === true) {
if (v.target) {
v._render(v.target.eq(0));
}
else if (v.path) {
v._render(v.path);
}
entry = true;
return false
}
})
if (!entry && typeof this._error === 'function') {
this._error(hash)
}
},
_bindHashchange: function() {
var self = this;
if (Nui.bsie7) {
var hashchange = function(ret) {
var hash = location.hash;
if (self._oldhash !== hash) {
return !ret
}
return false
}
setInterval(function() {
if (hashchange()) {
self._change()
}
}, 100);
hashchange(true)
}
else {
Nui.win.on('hashchange', function() {
self._change()
})
}
},
_$ready: null,
_$fn: null,
init: null,
start: function(value) {
this._change()
},
reload: function() {
var active = this.active(true);
if (active) {
active.self._isRender = true;
this._change()
}
},
location: function(url, data, render) {
var self = this;
if (url) {
if (arguments.length <= 2 && typeof data === 'boolean') {
render = data;
data = undefined;
}
var temp, object, query = '';
var match = url.match(/\?[^\/\s]+$/);
if (match) {
query = match[0]
}
url = this._replace(url.replace(/\?[^\/\s]+$/, ''));
Nui.each(this._paths, function(val, rule) {
if (rule === url || (url.indexOf(val.path) === 0 &&
(temp = url.replace(new RegExp('^' + val.path), '').replace(/^\//, '')) &&
temp.split('/').length === val.params.length)) {
object = self.__instances[val.id];
return false
}
})
if (object) {
if (data !== undefined) {
object.sendData = data;
object._sendData = true;
}
object._isRender = render;
object._render(url + query)
}
}
else {
self.start()
}
},
active: function(isOptions) {
if (isOptions) {
return this._options
}
return this._active
},
forward: function(index) {
history.forward(index);
return false
},
back: function(index) {
history.back(index);
return false
},
error: function(callback) {
this._error = callback
}
}
if (Nui.bsie7) {
statics._history = [];
statics._setHistory = function(hash) {
if (!this._isHistory) {
var last = this._history.slice(-1);
if (!last.length || last[0].hash !== hash) {
Nui.each(this._history, function(val) {
val.active = false
});
this._history.push({
hash: hash,
active: true
})
}
}
this._isHistory = false;
}
Nui.each(['forward', 'back'], function(v) {
var value = v === 'forward' ? 1 : -1;
statics[v] = function() {
var self = this, len = self._history.length;
statics._isHistory = true;
Nui.each(self._history, function(val, i) {
var index = i + value;
if (val.active) {
//历史记录在起始或者末尾时,调用原生的记录
if (index === -1 || index === len) {
window.history[v]();
return false
}
var _history = self._history[index];
if (_history) {
location.hash = _history.hash;
_history.active = true;
}
val.active = false;
return false
}
})
return false
}
})
}
return this.extend(component, {
_static: statics,
_options: {
path: '',
template: '',
container: null,
data: {},
entry: false,
wrapper: false,
reload: false,
level: 2,
onBefore: null,
onChange: null,
onData: null,
onRender: null,
onInit: null,
onAfter: null
},
_init: function() {
var self = this, router = self.constructor;
if (self._exec() && !router._bind) {
router._bind = true;
router._bindHashchange();
}
},
_exec: function() {
var self = this, opts = self._options;
if (opts.path && (self.container = self._jquery(opts.container))) {
self._initPath();
self._setPaths();
if (self._getTarget()) {
self._event()
}
else if (opts.relTarget) {
self.target = self._jquery(opts.relTarget)
}
return self
}
},
_setPaths: function() {
var self = this, opts = self._options, router = self.constructor;
var paths = self._getPathData();
var len = paths.params.length;
if ((!len && opts.level === 1) || opts.level !== 1) {
router._setPaths(paths.rule, paths)
}
if (len && opts.level > 0) {
var params = [], split = '/:', param, sub;
while (param = paths.params.shift()) {
params.push(param);
sub = params.join(split);
router._setPaths(paths.rule + split + sub, Nui.extend({}, paths, {
params: sub.split(split)
}))
}
}
},
_initPath: function() {
var self = this, opts = self._options, router = self.constructor;
self.path = router._replace(opts.path)
},
_getPathData: function() {
var self = this, path = self.path, opts = self._options, index = path.indexOf('/:');
var paths = {
id: self.__id,
params: [],
rule: path,
path: path
}
if (index !== -1) {
paths.params = path.substr(index + 2).split('/:');
self.path = paths.path = path.substr(0, index);
if (opts.level > 0) {
paths.rule = paths.path;
}
}
return paths
},
_render: function(url, reload) {
var isClick = url instanceof jQuery;
var self = this, opts = self._options, href = isClick ? url.attr('href') : url, router = self.constructor;
if (href) {
var trigger = false;
var change = function(callback) {
trigger = true;
var hash = '#!' + router._replace(href);
var _hash = location.hash;
if (typeof callback === 'function') {
hash = callback(hash) || hash;
}
if (reload) {
self._isRender = true
}
if (_hash === hash && self._isRender === true) {
router._change()
}
else {
location.hash = hash
}
}
if (typeof opts.onBefore === 'function' && opts.onBefore.call(opts, change, reload) === false) {
return false
}
if (!trigger) {
change()
}
}
},
_event: function() {
var self = this, opts = self._options;;
self._on('click', Nui.doc, self.target, function(e, elem) {
self._render(elem, opts.reload);
e.preventDefault()
})
return self
},
_reset: function() {
var self = this, opts = self._options, element = opts.element, router = self.constructor;
self._off();
if (element) {
component.destroy(element.off());
if (self._wrapper) {
if (self._wrapper !== element) {
self._wrapper.off()
}
if (opts.wrapper === true) {
self._wrapper.remove()
}
delete self._wrapper;
delete self._inner;
}
}
Nui.each(router._paths, function(val, i) {
if (val.id === self.__id) {
delete router._paths[i];
delete router._request[i];
}
})
return self
},
render: function() {
var self = this, opts = self._options, tmpl = opts.template, element = opts.element;
if (element) {
self._callback('RenderBefore');
component.destroy(element);
if (tmpl) {
if (typeof tmpl === 'string') {
element.html(template.render(tmpl, opts.data));
}
else {
element.html(template.render.call(tmpl, tmpl.main, opts.data));
}
}
component.init(element);
self._callback('Render')
}
},
request: function() {
var self = this,
_class = self.constructor,
args = arguments,
type = args[0],
method, url;
if (type) {
if (typeof type === 'string' && request[type]) {
args = Array.prototype.slice.call(arguments, 1);
url = args[0];
method = request[type]
}
else if (typeof type === 'object') {
url = type.url;
method = request
}
var _request = _class._request[self.__id];
if (!_request) {
_request = _class._request[self.__id] = {}
}
if (method && url) {
var xhr = method.apply(request, args);
var callback = function() {
delete _request[url]
}
_request[url] = xhr;
xhr.then(callback, callback);
return xhr
}
}
},
destroy: function() {
var self = this, router = self.constructor;
component.exports.destroy.call(self);
if ($.isEmptyObject(router._paths)) {
router._options = {};
//所有路由全部销毁时,删除公共wrapper数据
if (router._wrapper) {
router._wrapper.off().remove();
delete router._inner;
delete router._wrapper
}
}
}
})
})