├── .gitignore
├── src
├── example
│ ├── article
│ │ ├── swiper1.png
│ │ ├── swiper2.png
│ │ ├── swiper3.png
│ │ ├── swiper4.png
│ │ ├── article.html
│ │ ├── article.less
│ │ └── article.js
│ ├── app.js
│ ├── index.html
│ ├── lib
│ │ ├── dataManager.js
│ │ └── data.js
│ ├── list
│ │ ├── list.html
│ │ └── list.js
│ └── app.less
├── util.js
└── index.js
├── dist
├── example
│ ├── 47e04b2447b9170b0657988bf64d2126.png
│ ├── 8d009c9d91ea14721eaff19d32237d40.png
│ ├── b8506bdb1a762806f909f5c12cf8d92b.png
│ ├── b87cd3b87e12ff32ce7c52b6c3a13945.png
│ └── index.html
└── router.min.js
├── .babelrc
├── webpack.prod.config.js
├── webpack.dev.config.js
├── package.json
├── README.md
└── test
└── unit
└── util.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | npm-debug.log
4 | publish.sh
5 | *.DS_Store
--------------------------------------------------------------------------------
/src/example/article/swiper1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrape/router/HEAD/src/example/article/swiper1.png
--------------------------------------------------------------------------------
/src/example/article/swiper2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrape/router/HEAD/src/example/article/swiper2.png
--------------------------------------------------------------------------------
/src/example/article/swiper3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrape/router/HEAD/src/example/article/swiper3.png
--------------------------------------------------------------------------------
/src/example/article/swiper4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrape/router/HEAD/src/example/article/swiper4.png
--------------------------------------------------------------------------------
/dist/example/47e04b2447b9170b0657988bf64d2126.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrape/router/HEAD/dist/example/47e04b2447b9170b0657988bf64d2126.png
--------------------------------------------------------------------------------
/dist/example/8d009c9d91ea14721eaff19d32237d40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrape/router/HEAD/dist/example/8d009c9d91ea14721eaff19d32237d40.png
--------------------------------------------------------------------------------
/dist/example/b8506bdb1a762806f909f5c12cf8d92b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrape/router/HEAD/dist/example/b8506bdb1a762806f909f5c12cf8d92b.png
--------------------------------------------------------------------------------
/dist/example/b87cd3b87e12ff32ce7c52b6c3a13945.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrape/router/HEAD/dist/example/b87cd3b87e12ff32ce7c52b6c3a13945.png
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "stage-3"
5 | ],
6 | "plugins": ["transform-object-assign", "transform-class-properties", "add-module-exports"]
7 | }
--------------------------------------------------------------------------------
/src/example/app.js:
--------------------------------------------------------------------------------
1 | import Router from '../index';
2 | import './app.less';
3 | import list from './list/list';
4 | import article from './article/article';
5 |
6 | const router = new Router({
7 | container: '#container',
8 | enterTimeout: 300,
9 | leaveTimeout: 300
10 | });
11 |
12 | router.push(list)
13 | .push(article)
14 | .setDefault('/')
15 | .init();
--------------------------------------------------------------------------------
/src/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | example
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/example/lib/dataManager.js:
--------------------------------------------------------------------------------
1 |
2 | let data = {};
3 |
4 | export default {
5 | /**
6 | * get data
7 | * @param {String} key
8 | * @returns {*}
9 | */
10 | getData: function (key){
11 | return data[key];
12 | },
13 | /**
14 | * set data
15 | * @param {String} key
16 | * @param {*} value
17 | */
18 | setData: function (key, value){
19 | data[key] = value;
20 | }
21 | };
--------------------------------------------------------------------------------
/dist/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | example
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/example/article/article.html:
--------------------------------------------------------------------------------
1 |
2 | {{each items as item i}}
3 |
4 |
5 |
6 | {{/each}}
7 |
8 |
9 |
10 | {{article.title}}
11 |
12 | {{article.summary}}
13 |
14 |
15 | {{article.summary}}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/example/article/article.less:
--------------------------------------------------------------------------------
1 | .swiper {
2 | height: 200px;
3 | overflow: hidden;
4 | transition: all 0.3s ease;
5 | }
6 | .item {
7 | height: 100%;
8 | background-position: center center;
9 | background-size: cover;
10 | position: relative;
11 | overflow: hidden;
12 | float: left;
13 |
14 | &.active{
15 | .animated{
16 | animation-fill-mode: both;
17 | opacity: 1;
18 | }
19 | }
20 | &:not(.active){
21 | .animated{
22 | animation: none;
23 | opacity: 0;
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/example/list/list.html:
--------------------------------------------------------------------------------
1 |
2 |
图文组合列表
3 |
4 | {{each list as item i}}
5 |
6 |
9 |
13 |
14 | {{/each}}
15 |
16 |
--------------------------------------------------------------------------------
/src/example/list/list.js:
--------------------------------------------------------------------------------
1 | import 'weui.js';
2 | import template from 'art-template/dist/template-debug';
3 | import remote from '../lib/data';
4 | import dataManager from '../lib/dataManager';
5 | import tpl from 'raw!./list.html';
6 |
7 | export default {
8 | url: '/',
9 | render: function (callback) {
10 |
11 | const data = dataManager.getData('data');
12 | if (data) {
13 | const html = template.compile(tpl)({list: data});
14 |
15 | callback(null, html);
16 | }
17 | else {
18 | // 模拟异步加载数据
19 | $.weui.loading('加载中...');
20 | setTimeout(() => {
21 | dataManager.setData('data', remote);
22 | const html = template.compile(tpl)({list: remote});
23 | callback(null, html);
24 | $.weui.hideLoading();
25 | }, 800);
26 | }
27 | },
28 | bind: function () {
29 |
30 | }
31 | };
--------------------------------------------------------------------------------
/webpack.prod.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var pkg = require('./package.json');
3 |
4 | module.exports = {
5 | entry: './src/index.js',
6 | output: {
7 | path: './dist',
8 | filename: 'router.min.js',
9 | library: 'Router',
10 | libraryTarget: 'umd',
11 | umdNamedDefine: true
12 | },
13 | module: {
14 | loaders: [
15 | {
16 | test: /\.js$/,
17 | exclude: /node_modules/,
18 | loader: 'babel'
19 | }
20 | ]
21 | },
22 | plugins: [
23 | new webpack.optimize.UglifyJsPlugin({
24 | compress: {
25 | warnings: false
26 | }
27 | }),
28 | new webpack.BannerPlugin([
29 | pkg.name + ' v' + pkg.version + ' (' + pkg.homepage + ')',
30 | 'Copyright ' + new Date().getFullYear(),
31 | 'Licensed under the '+ pkg.license +' license'
32 | ].join('\n'))
33 | ]
34 | };
--------------------------------------------------------------------------------
/src/example/app.less:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | html, body{
7 | height: 100%;
8 | }
9 |
10 | body {
11 | overflow-x: hidden;
12 | }
13 |
14 | .container {
15 | height: 100%;
16 | overflow-y: auto;
17 | -webkit-overflow-scrolling: touch;
18 | & > div {
19 | background-color: #fff;
20 | }
21 | }
22 |
23 | @keyframes slideIn {
24 | from {
25 | transform: translate3d(100%, 0, 0);
26 | opacity: 0;
27 | }
28 | to {
29 | transform: translate3d(0, 0, 0);
30 | opacity: 1;
31 | }
32 | }
33 |
34 | @keyframes slideOut {
35 | from {
36 | transform: translate3d(0, 0, 0);
37 | opacity: 1;
38 | }
39 | to {
40 | transform: translate3d(100%, 0, 0);
41 | opacity: 0;
42 | }
43 | }
44 |
45 | .enter, .leave {
46 | position: absolute;
47 | top: 0;
48 | right: 0;
49 | bottom: 0;
50 | left: 0;
51 | z-index: 1;
52 | }
53 |
54 | .enter {
55 | animation: slideIn .2s forwards;
56 | }
57 |
58 | .leave {
59 | animation: slideOut .25s forwards;
60 | }
--------------------------------------------------------------------------------
/src/example/article/article.js:
--------------------------------------------------------------------------------
1 | import template from 'art-template/dist/template-debug';
2 | import Swiper from 'iswiper';
3 | import dataManager from '../lib/dataManager';
4 | import tpl from 'raw!./article.html';
5 | import './article.less';
6 |
7 | // 图片来自微软 cn.bing.com , 版权归原作者所有
8 | import swiper1 from './swiper1.png';
9 | import swiper2 from './swiper2.png';
10 | import swiper3 from './swiper3.png';
11 | import swiper4 from './swiper4.png';
12 |
13 | export default {
14 | url: '/article/:id',
15 | render: function () {
16 | const id = this.params.id;
17 | const data = dataManager.getData('data');
18 | const article = data.filter(article => article.id == id)[0];
19 | const html = template.compile(tpl)({article: article, items: [swiper1, swiper2, swiper3, swiper4]});
20 |
21 | // 可以返回一个 promise
22 | return new Promise((resolve, reject) => {
23 | resolve(html);
24 | });
25 | // 也可以直接返回 html
26 | // return html;
27 | },
28 | bind: function () {
29 | const swiper = new Swiper({
30 | direction: 'horizontal'
31 | });
32 | swiper.on('swiped', (prev, current) => {
33 | console.log('prev', prev);
34 | console.log('current', current);
35 | });
36 | }
37 | };
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | import pathToRegexp from 'path-to-regexp';
2 |
3 | /**
4 | * get hash by full url
5 | * @param {String} url
6 | * @returns {string}
7 | */
8 | export function getHash(url) {
9 | return url.indexOf('#') !== -1 ? url.substring(url.indexOf('#') + 1) : '/';
10 | }
11 |
12 | /**
13 | * get route from routes filter by url
14 | * @param {Array} routes
15 | * @param {String} url
16 | * @returns {Object}
17 | */
18 | export function getRoute(routes, url) {
19 | for (let i = 0, len = routes.length; i < len; i++) {
20 | let route = routes[i];
21 | let keys = [];
22 | const regex = pathToRegexp(route.url, keys);
23 | const match = regex.exec(url);
24 | if (match) {
25 | route.params = {};
26 | for (let j = 0, l = keys.length; j < l; j++) {
27 | const key = keys[j];
28 | const name = key.name;
29 | route.params[name] = match[j + 1];
30 | }
31 | return route;
32 | }
33 | }
34 | return null;
35 | }
36 |
37 | /**
38 | * has children
39 | * @param {HTMLElement} parent
40 | * @returns {boolean}
41 | */
42 | export function hasChildren(parent) {
43 | const children = parent.children;
44 | return children.length > 0;
45 | }
46 |
47 | /**
48 | * noop
49 | */
50 | export function noop() {
51 |
52 | }
--------------------------------------------------------------------------------
/src/example/lib/data.js:
--------------------------------------------------------------------------------
1 |
2 | export default [{
3 | id: 1,
4 | title: '微信Web App开发最佳实践',
5 | cover: 'https://mmrb.github.io/avatar/kl.jpg',
6 | summary: '组内小伙伴 jf 去 Feday 广州站的分享,有蛮多干货,都是微信 web app 开发者们关注的一些问题。 不做过多介绍,没有去现场听得朋友可以下载ppt来看。 感兴趣的朋友也可以下载广州站的所有ppt,以及关注接下来的 Feday 日程。'
7 | }, {
8 | id: 2,
9 | title: 'X5即将升级内核到Blink',
10 | cover: 'https://mmrb.github.io/avatar/shenfei.jpg',
11 | summary: '我们从QQ浏览器团队得到消息,X5已经完成升级到Blink的开发工作,最近已经开始下发到客户端中,而X5内核的更新是热更新,也就是说不需要用户更新微信客户端,在良好的网络环境下(比如WiFi)会在后台静默更新。 根据我们拿到的版本,X5用的Blink版本是Chrome 37。虽然'
12 | }, {
13 | id: 3,
14 | title: 'WeUI的设计稿开源',
15 | cover: 'https://mmrb.github.io/avatar/bear.jpg',
16 | summary: '自从 WeUI 开源后,已经收到 7000 多 star ,将近 2000 的 fork,我们在欣喜之余,也收到了蛮多有价值的意见与建议,其中之一就是将设计稿开源——好吧,其实设计稿叫『开放下载』更合适一些。 那我们今天开放了基于 WeUI 0.4 版本的设计稿 sketch 文'
17 | }, {
18 | id: 4,
19 | title: 'HTML5+CSS3 loading 效果收集',
20 | cover: 'https://mmrb.github.io/avatar/gaby.jpg',
21 | summary: '用gif图片来做loading的时代已经过去了,它显得太low了,而用HTML5/CSS3以及SVG和canvas来做加载动画显得既炫酷又逼格十足。这已经成为一种趋势。 这里收集了几十个用html5和css3实现的loading效果,以供学习参考。 01. CSS Rainbow'
22 | }, {
23 | id: 5,
24 | title: '微信网页开发者工具发布',
25 | cover: 'https://mmrb.github.io/avatar/xx.jpg',
26 | summary: '兄弟团队内测已久的微信网页开发者工具终于在今天的微信公开课Pro大会上发布了,喜大普奔。 这个工具有主要有3个功能: 使用真实用户身份,调试微信网页授权。 校验页面的 JSSDK 权限,以及模拟大部分 SDK 的输入和输出。 利用集成的 Chrome DevTools 和基本'
27 | }];
--------------------------------------------------------------------------------
/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var autoprefixer = require('autoprefixer');
4 | var HtmlWebpackPlugin = require('html-webpack-plugin');
5 | var OpenBrowserPlugin = require('open-browser-webpack-plugin');
6 |
7 | module.exports = {
8 | context: path.join(__dirname, 'src/example'),
9 | entry: {
10 | js: './app.js'
11 | },
12 | output: {
13 | path: path.join(__dirname, 'dist/example'),
14 | filename: 'example.js'
15 | },
16 | module: {
17 | loaders: [
18 | {
19 | test: /\.jsx?$/,
20 | exclude: /node_modules/,
21 | loader: 'babel'
22 | }, {
23 | test: /\.less$/,
24 | exclude: /node_modules/,
25 | loader: 'style!css!postcss!less'
26 | }, {
27 | test: /\.png/,
28 | exclude: /node_modules/,
29 | loader: 'file'
30 | }
31 | ]
32 | },
33 | postcss: [autoprefixer],
34 | resolve: {
35 | extensions: ['', '.js', '.jsx']
36 | },
37 | plugins: [
38 | new webpack.ProvidePlugin({
39 | $: 'jquery',
40 | jQuery: 'jquery'
41 | }),
42 | new HtmlWebpackPlugin({
43 | template: path.join(__dirname, 'src/example/index.html')
44 | }),
45 | new OpenBrowserPlugin({url: 'http://localhost:8080'})
46 | ],
47 | devServer: {
48 | contentBase: './dist',
49 | hot: true
50 | }
51 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "router",
3 | "version": "0.1.0",
4 | "description": "router",
5 | "main": "dist/router.min.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --config webpack.dev.config.js --hot --inline --progress --colors --port 8080 --host 0.0.0.0",
8 | "build": "npm run build:lib && npm run build:example",
9 | "build:lib": "webpack --config webpack.prod.config.js --progress --colors",
10 | "build:example": "webpack --config webpack.dev.config.js --progress --colors",
11 | "test": "mocha --compilers js:babel-core/register --recursive",
12 | "clean": "rimraf ./dist"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/progrape/router.git"
17 | },
18 | "keywords": [
19 | "router",
20 | "weui",
21 | "demo"
22 | ],
23 | "author": "jf ",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/progrape/router/issues"
27 | },
28 | "homepage": "https://github.com/progrape/router",
29 | "devDependencies": {
30 | "art-template": "^3.0.3",
31 | "autoprefixer": "^6.3.3",
32 | "babel-core": "^6.9.0",
33 | "babel-loader": "^6.2.4",
34 | "babel-plugin-add-module-exports": "^0.1.2",
35 | "babel-plugin-transform-class-properties": "^6.6.0",
36 | "babel-plugin-transform-object-assign": "^6.8.0",
37 | "babel-preset-es2015": "^6.6.0",
38 | "babel-preset-stage-3": "^6.5.0",
39 | "chai": "^3.5.0",
40 | "css-loader": "^0.23.1",
41 | "file-loader": "^0.8.5",
42 | "html-webpack-plugin": "^2.9.0",
43 | "iswiper": "^1.4.1",
44 | "jquery": "^2.2.3",
45 | "jsdom": "^9.1.0",
46 | "less": "^2.6.1",
47 | "less-loader": "^2.2.2",
48 | "mocha": "^2.4.5",
49 | "open-browser-webpack-plugin": "0.0.2",
50 | "postcss-loader": "^0.8.1",
51 | "raw-loader": "^0.5.1",
52 | "rimraf": "^2.5.2",
53 | "style-loader": "^0.13.0",
54 | "url-loader": "^0.5.7",
55 | "webpack": "^1.12.14",
56 | "webpack-dev-server": "^1.14.1",
57 | "weui.js": "^0.2.1"
58 | },
59 | "dependencies": {
60 | "path-to-regexp": "^1.3.0"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Simple Router
2 |
3 | A very simple router, just for the **demo** of [weui](https://github.com/weui/weui)
4 |
5 | ### 预览
6 |
7 | 
8 |
9 | https://progrape.github.io/router
10 |
11 | ### 使用
12 |
13 | HTML
14 |
15 | ```html
16 |
17 |
18 |
19 |
20 |
21 | example
22 |
23 |
24 |
25 |
26 |
27 |
28 | ```
29 |
30 | JavaScript
31 |
32 | ```javascript
33 | var router = new Router({
34 | container: '#container'
35 | });
36 | var home = {
37 | url: '/',
38 | className: 'home',
39 | render: function (){
40 | return 'home
';
41 | }
42 | };
43 | var post = {
44 | url: '/post/:id',
45 | className: 'post',
46 | render: function (){
47 | var id = this.params.id;
48 | return 'post
';
49 | }
50 | };
51 | router.push(home).push(post).setDefault('/').init();
52 | ```
53 |
54 | ### 运行示例
55 |
56 | ```shell
57 | git clone https://github.com/progrape/router
58 | cd router
59 | npm install
60 | npm start
61 | ```
62 |
63 | ### API
64 |
65 | #### Router([option])
66 |
67 | 参数 `option` 是可选的,下面是该参数可选的属性。
68 |
69 | |属性 |类型 |默认值 |描述|
70 | |-------- |--- |--- |---
71 | |container |String |'#container' | `container` 容器的选择器
72 | |enter |String |'enter' | 该页面出现时添加的类名,`enterTimeout` 为 0 时会被忽略
73 | |enterTimeout |Number |0 | 在这个时间之后移除添加的 `enter` 类名
74 | |leave |String |'leave' | 该页面离开时添加的类名,`lieaveTimeout` 为 0 时会被忽略
75 | |leaveTimeout |Number |0 | 在这个时间之后移除该页面的 DOM
76 |
77 |
78 | #### 实例方法
79 |
80 | 以下方法执行完毕后均返回实例本身。
81 |
82 | ##### push(route)
83 |
84 | 添加路由页面的配置。下面是 `route` 参数的属性。
85 |
86 |
87 | |属性 |类型 |描述
88 | |-----------|-------|---
89 | |url |String | 以 `/` 开头的 url,会体现在 hash,支持参数,如:`/user/:userId/post/:posdId`
90 | |className |String | 可选,该页面可以添加的额外类名,以便控制该页面下的样式
91 | |render |function| 页面渲染方法,支持同步和异步, 可以直接返回 html 字符串,可以返回 `promise` 对象,也可以接收 `callback` 参数
92 | |bind |function| 执行绑定事件的方法,`this` 指向当前页面容器
93 |
94 | route 示例如下:
95 |
96 | 同步
97 |
98 | ```javascript
99 | {
100 | url: '/home',
101 | className: 'home',
102 | render: function (){
103 | return '';
104 | },
105 | bind: function (){
106 | $(this).on('click', 'button', function (){
107 | // do something
108 | });
109 | }
110 | }
111 | ```
112 |
113 | promise
114 |
115 | ```javascript
116 | {
117 | url: '/home',
118 | className: 'home',
119 | render: function (){
120 | return new Promise(function (resolve, reject){
121 | resolve('');
122 | });
123 | },
124 | bind: function (){
125 | $(this).on('click', 'button', function (){
126 | // do something
127 | });
128 | }
129 | }
130 | ```
131 |
132 | callback
133 |
134 | ```javascript
135 | {
136 | url: '/home',
137 | className: 'home',
138 | render: function (callback){
139 | callback(null, '');
140 | },
141 | bind: function (){
142 | $(this).on('click', 'button', function (){
143 | // do something
144 | });
145 | }
146 | }
147 | ```
148 |
149 |
150 | ##### setDefault(url)
151 |
152 | 设置页面启动时默认跳转的 url。
153 |
154 | ##### init()
155 |
156 | 启动页面,在调用完 `push` 和 `setDefault` 方法后调用,主要完成 `hashchange` 的事件监听和跳转默认页面的工作。
157 |
158 | ### License
159 |
160 | The MIT License(http://opensource.org/licenses/MIT)
161 |
162 | 请自由地享受和参与开源
163 |
--------------------------------------------------------------------------------
/test/unit/util.js:
--------------------------------------------------------------------------------
1 | import {expect} from 'chai';
2 | import {jsdom} from 'jsdom';
3 | import * as util from '../../src/util';
4 |
5 | describe('util.getHash', () => {
6 | it('should return "/" when no hash', () => {
7 | const url = 'https://weui.com';
8 | const hash = util.getHash(url);
9 | expect(hash).to.be.equal('/');
10 | });
11 |
12 | it('should return "" when empty hash', () => {
13 | const url = 'https://weui.com/#';
14 | const hash = util.getHash(url);
15 | expect(hash).to.be.equal('');
16 | });
17 |
18 | it('should return "/home" with hash', () => {
19 | const url = 'https://weui.com/#/home';
20 | const hash = util.getHash(url);
21 | expect(hash).to.be.equal('/home');
22 | });
23 | });
24 |
25 | describe('util.getRoute', () => {
26 | const routes = [{
27 | url: '/home',
28 | render: function () {
29 | return `home`;
30 | }
31 | }, {
32 | url: '/post/:id?',
33 | render: function () {
34 | const id = this.params.id;
35 | return `post ${id}`;
36 | }
37 | }, {
38 | url: '/user/:userId/post/:postId',
39 | render: function () {
40 | const userId = this.params['userId'];
41 | return `user ${userId}`;
42 | }
43 | }];
44 |
45 | it('should return null', () => {
46 | const route = util.getRoute(routes, '/category');
47 | expect(route).to.be.equal(null);
48 | });
49 |
50 | it('should return route', () => {
51 | const route = util.getRoute(routes, '/home');
52 | expect(route).to.be.not.equal(null);
53 | expect(route.url).to.be.equal(routes[0].url);
54 | expect(route.render).to.be.equal(routes[0].render);
55 | });
56 |
57 | it('should return route', () => {
58 | const route = util.getRoute(routes, `/post`);
59 | expect(route).to.be.not.equal(null);
60 | expect(route.url).to.be.equal(routes[1].url);
61 | expect(route.render).to.be.equal(routes[1].render);
62 | expect(route.params.id).to.be.equal(undefined);
63 | });
64 |
65 | it('should return route', () => {
66 | const id = '2';
67 | const route = util.getRoute(routes, `/post/${id}`);
68 | expect(route).to.be.not.equal(null);
69 | expect(route.url).to.be.equal(routes[1].url);
70 | expect(route.render).to.be.equal(routes[1].render);
71 | expect(route.params.id).to.be.equal(id);
72 | });
73 |
74 | it('should return route', () => {
75 | const userId = '2';
76 | const postId = '3';
77 | const route = util.getRoute(routes, `/user/${userId}/post/${postId}`);
78 | expect(route).to.be.not.equal(null);
79 | expect(route.url).to.be.equal(routes[2].url);
80 | expect(route.render).to.be.equal(routes[2].render);
81 | expect(route.params['userId']).to.be.equal(userId);
82 | expect(route.params['postId']).to.be.equal(postId);
83 | });
84 | });
85 |
86 | describe('util.hasChildren', () => {
87 | it('should have no children when body is empty', () => {
88 | const document = jsdom(' ');
89 | const body = document.body;
90 |
91 | const hasChildren = util.hasChildren(body);
92 |
93 | expect(hasChildren).to.be.equal(false);
94 | });
95 |
96 | it('should have no children when body is empty', () => {
97 | const document = jsdom('hello');
98 | const body = document.body;
99 |
100 | const hasChildren = util.hasChildren(body);
101 |
102 | expect(hasChildren).to.be.equal(false);
103 | });
104 |
105 | it('should have children when body is not empty', () => {
106 | const document = jsdom('router
');
107 | const body = document.body;
108 |
109 | const hasChildren = util.hasChildren(body);
110 |
111 | expect(hasChildren).to.be.equal(true);
112 | });
113 | });
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import * as util from './util';
2 |
3 | /**
4 | * a very simple router for the **demo** of [weui](https://github.com/weui/weui)
5 | */
6 | class Router {
7 |
8 | // default option
9 | _options = {
10 | container: '#container',
11 | enter: 'enter',
12 | enterTimeout: 0,
13 | leave: 'leave',
14 | leaveTimeout: 0
15 | };
16 |
17 | _index = 1;
18 |
19 | // container element
20 | _$container = null;
21 |
22 | // array of route config
23 | _routes = [];
24 |
25 | // default route config
26 | _default = null;
27 |
28 | /**
29 | * constructor
30 | * @param options
31 | */
32 | constructor(options) {
33 | this._options = Object.assign({}, this._options, options);
34 | this._$container = document.querySelector(this._options.container);
35 | }
36 |
37 | /**
38 | * initial
39 | * @returns {Router}
40 | */
41 | init() {
42 |
43 | // why not `history.pushState`? see https://github.com/weui/weui/issues/26, Router in wechat webview
44 | window.addEventListener('hashchange', (event) => {
45 | const old_hash = util.getHash(event.oldURL);
46 | const hash = util.getHash(event.newURL);
47 | // fix '/' repeat see https://github.com/progrape/router/issues/21
48 | if(old_hash === hash) return;
49 | const state = history.state || {};
50 |
51 | this.go(hash, state._index <= this._index);
52 | }, false);
53 |
54 | if (history.state && history.state._index) {
55 | this._index = history.state._index;
56 | }
57 |
58 | this._index--;
59 |
60 | const hash = util.getHash(location.href);
61 | const route = util.getRoute(this._routes, hash);
62 | this.go(route ? hash : this._default);
63 |
64 | return this;
65 | }
66 |
67 | /**
68 | * push route config into routes array
69 | * @param {Object} route
70 | * @returns {Router}
71 | */
72 | push(route) {
73 |
74 | const exist = this._routes.filter(r => r.url === route.url)[0];
75 | if (exist) {
76 | throw new Error(`route ${route.url} is existed`);
77 | }
78 |
79 | route = Object.assign({}, {
80 | url: '*',
81 | className: '',
82 | render: util.noop,
83 | bind: util.noop
84 | }, route);
85 | this._routes.push(route);
86 | return this;
87 | }
88 |
89 | /**
90 | * set default url when no matcher was found
91 | * @param {String} url
92 | * @returns {Router}
93 | */
94 | setDefault(url) {
95 | this._default = url;
96 | return this;
97 | }
98 |
99 | /**
100 | * go to the specify url
101 | * @param {String} url
102 | * @param {Boolean} isBack, default: false
103 | * @returns {Router}
104 | */
105 | go(url, isBack = false) {
106 | const route = util.getRoute(this._routes, url);
107 | if (route) {
108 |
109 | const leave = (hasChildren) => {
110 | // if have child already, then remove it
111 | if (hasChildren) {
112 | let child = this._$container.children[0];
113 | if (isBack) {
114 | child.classList.add(this._options.leave);
115 | }
116 |
117 | if (this._options.leaveTimeout > 0) {
118 | setTimeout(() => {
119 | child.parentNode.removeChild(child);
120 | }, this._options.leaveTimeout);
121 | }
122 | else {
123 | child.parentNode.removeChild(child);
124 | }
125 | }
126 | };
127 |
128 | const enter = (hasChildren, html) => {
129 | let node = document.createElement('div');
130 |
131 | // add class name
132 | if (route.className) {
133 | node.classList.add(route.className);
134 | }
135 |
136 | node.innerHTML = html;
137 | this._$container.appendChild(node);
138 | // add class
139 | if (!isBack && this._options.enter && hasChildren) {
140 | node.classList.add(this._options.enter);
141 | }
142 |
143 | if (this._options.enterTimeout > 0) {
144 | setTimeout(() => {
145 | node.classList.remove(this._options.enter);
146 | }, this._options.enterTimeout);
147 | }
148 | else {
149 | node.classList.remove(this._options.enter);
150 | }
151 |
152 | location.hash = `#${url}`;
153 | try {
154 | isBack ? this._index-- : this._index++;
155 | history.replaceState && history.replaceState({_index: this._index}, '', location.href);
156 | } catch (e) {
157 |
158 | }
159 |
160 | if (typeof route.bind === 'function'/* && !route.__isBind*/) {
161 | route.bind.call(node);
162 | //route.__isBind = true;
163 | }
164 | };
165 |
166 | const hasChildren = util.hasChildren(this._$container);
167 |
168 | // pop current page
169 | leave(hasChildren);
170 |
171 | // callback
172 | const callback = (err, html = '') => {
173 | if (err) {
174 | throw err;
175 | }
176 |
177 | // push next page
178 | enter(hasChildren, html);
179 | };
180 |
181 | const res = route.render(callback);
182 | // promise
183 | if (res && typeof res.then === 'function') {
184 | res.then((html) => {
185 | callback(null, html);
186 | }, callback);
187 | }
188 | // synchronous
189 | else if (route.render.length === 0) {
190 | callback(null, res);
191 | }
192 | // callback
193 | else {
194 |
195 | }
196 | }
197 | else {
198 | throw new Error(`url ${url} was not found`);
199 | }
200 | return this;
201 | }
202 | }
203 |
204 | export default Router;
--------------------------------------------------------------------------------
/dist/router.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * router v0.1.0 (https://github.com/progrape/router)
3 | * Copyright 2016
4 | * Licensed under the MIT license
5 | */
6 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Router",[],t):"object"==typeof exports?exports.Router=t():e.Router=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t["default"]=e,t}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var i=Object.assign||function(e){for(var t=1;t0?setTimeout(function(){e.parentNode.removeChild(e)},t._options.leaveTimeout):e.parentNode.removeChild(e)}()},i=function(o,i){var a=document.createElement("div");r.className&&a.classList.add(r.className),a.innerHTML=i,t._$container.appendChild(a),!n&&t._options.enter&&o&&a.classList.add(t._options.enter),t._options.enterTimeout>0?setTimeout(function(){a.classList.remove(t._options.enter)},t._options.enterTimeout):a.classList.remove(t._options.enter),location.hash="#"+e;try{n?t._index--:t._index++,history.replaceState&&history.replaceState({_index:t._index},"",location.href)}catch(u){}"function"==typeof r.bind&&r.bind.call(a)},a=s.hasChildren(t._$container);o(a);var u=function(e){var t=arguments.length<=1||void 0===arguments[1]?"":arguments[1];if(e)throw e;i(a,t)},l=r.render(u);l&&"function"==typeof l.then?l.then(function(e){u(null,e)},u):0===r.render.length&&u(null,l)}(),this}}]),e}();t["default"]=l,e.exports=t["default"]},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e){return e.indexOf("#")!==-1?e.substring(e.indexOf("#")+1):"/"}function i(e,t){for(var n=0,r=e.length;n0}function u(){}Object.defineProperty(t,"__esModule",{value:!0}),t.getHash=o,t.getRoute=i,t.hasChildren=a,t.noop=u;var s=n(2),l=r(s)},function(e,t,n){function r(e){for(var t,n=[],r=0,o=0,i="";null!=(t=y.exec(e));){var a=t[0],u=t[1],s=t.index;if(i+=e.slice(o,s),o=s+a.length,u)i+=u[1];else{var c=e[o],f=t[2],p=t[3],h=t[4],d=t[5],v=t[6],g=t[7];i&&(n.push(i),i="");var x=null!=f&&null!=c&&c!==f,_="+"===v||"*"===v,m="?"===v||"*"===v,w=t[2]||"/",b=h||d||(g?".*":"[^"+w+"]+?");n.push({name:p||r++,prefix:f||"",delimiter:w,optional:m,repeat:_,partial:x,asterisk:!!g,pattern:l(b)})}}return o