UNPKG

5.55 kBJavaScriptView Raw
1import { global, runtime } from './contexts';
2import crossroads from 'crossroads';
3import React from 'react';
4
5let _stateRouteMapping = {};
6
7class UrlDefinition {
8 constructor(state, controller) {
9 this._state = state;
10 this._controller = controller;
11 }
12
13 get state() {
14 return this._state;
15 }
16
17 get controller() {
18 return this._controller;
19 }
20}
21
22class IncludeDefinition {
23 constructor(router) {
24 this._router = router;
25 }
26
27 get router() {
28 return this._router;
29 }
30}
31
32export function url(state, controller) {
33 return new UrlDefinition(state, controller);
34}
35
36export function include(router) {
37 return new IncludeDefinition(router);
38}
39
40export class RouteUtils {
41 static listen(routerClass) {
42 new routerClass(); //eslint-disable-line no-unused-vars, new-cap, no-new
43 if (runtime.isClient) {
44 RouteUtils._listenClient();
45 } else {
46 RouteUtils._listenServer();
47 }
48 }
49
50 static _listenServer() {
51 var http = require('http');
52 var urlModule = require('url');
53 crossroads.ignoreState = true;
54 crossroads.bypassed.add((req, res) => {
55 res.writeHead(404, {'Content-Type': 'text/html'});
56 res.end('<html><body><h1>HTTP 404 - Page Not Found</h1><hr/><p>OutlineJS Server</p></body></html>');
57 });
58 http.createServer((req, res) => {
59 var requestedUrl = urlModule.parse(req.url).pathname;
60 RouteUtils.parseUrl(requestedUrl, req, res);
61 }).listen(1337, '0.0.0.0');
62 }
63
64 static _listenClient() {
65 require('html5-history-api');
66 var location = window.history.location || window.location;
67 var eventDef = window.addEventListener ? ['addEventListener', ''] : ['attachEvent', 'on'];
68 window[eventDef[0]](`${eventDef[1]}popstate`, () => {
69 RouteUtils.parseUrl(location.pathname);
70 }, false);
71 RouteUtils.parseUrl(location.pathname);
72 }
73
74 static parseUrl(path, req, res) {
75 crossroads.parse(path, [req, res]);
76 }
77
78 static reverse(state, params = {}) {
79 var url = _stateRouteMapping[state].interpolate(params); //eslint-disable-line no-shadow
80 return `/${url}`;
81 }
82
83 static navigate(state, params = {}) {
84 var url = RouteUtils.reverse(state, params); //eslint-disable-line no-shadow
85 if (runtime.isClient) {
86 var history = require('html5-history-api');
87 history.pushState(null, null, url);
88 RouteUtils.parseUrl(url);
89 }
90 }
91
92 static query() {
93 //TODO: server side
94 var href = window.location.href;
95 var result = {};
96 if (href.includes('#')) {
97 href = href.split('#')[1];
98 }
99 href = href.split('?')[1];
100 if (href) {
101 for (let param of href.split('&')) {
102 let [k, v] = param.split('=');
103 result[decodeURIComponent(k)] = decodeURIComponent(v);
104 }
105 }
106 return result;
107 }
108
109 static isState(state, className = 'active') {
110 if (global.state.indexOf(state) === 0) {
111 return className;
112 }
113 }
114}
115
116export class BaseRouter {
117 constructor(prefix = '') {
118 if (prefix !== '') {
119 prefix = `${prefix}/`;
120 }
121 //find subRoutes
122 for (let rt of Object.keys(this.urlPatterns)) {
123 let rtObj = this.urlPatterns[rt];
124 if (rt !== '') {
125 rt = `${rt}/`;
126 }
127 if (rtObj instanceof IncludeDefinition) {
128 //instantiate sub router
129 new rtObj.router(`${prefix}${rt}`); //eslint-disable-line new-cap, no-new
130 } else {
131 var rUrl = `${prefix}${rt}`;
132 _stateRouteMapping[rtObj.state] = crossroads.addRoute(rUrl, (...args) => { //eslint-disable-line no-loop-func
133 this.routeTo(rtObj, ...args);
134 });
135 }
136 }
137 }
138
139 routeTo(urlDef, req, res, ...args) {
140 var Controller = urlDef.controller;
141 var midPromises = [];
142 for (var mid of runtime.middleware) {
143 if (mid.preControllerInit) {
144 midPromises.push(mid.preControllerInit());
145 }
146 }
147 Promise.all(midPromises).then(() => {
148 global.state = urlDef.state;
149 let controller = new Controller(req, res);
150 if (runtime.isClient) {
151 controller.reconcileWithServer();
152 }
153 controller.init(...args);
154 }, (error) => {
155 if (error) {
156 console.log(error);
157 }
158 });
159 }
160
161 get urlPatterns() {
162 throw 'NotImplemented';
163 }
164}
165
166export class Link extends React.Component {
167 static isLeftClickEvent(event) {
168 return event.button === 0;
169 }
170
171 static isModifiedEvent(event) {
172 return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
173 }
174
175 handleClick(event) {
176 let allowTransition = true;
177
178 if (this.props.onClick) {
179 this.props.onClick(event);
180 }
181
182 if (Link.isModifiedEvent(event) || !Link.isLeftClickEvent(event)) {
183 return;
184 }
185
186 if (event.defaultPrevented === true) {
187 allowTransition = false;
188 }
189
190 // If target prop is set (e.g. to "_blank") let browser handle link.
191 if (this.props.target) {
192 if (!allowTransition) {
193 event.preventDefault();
194 }
195 return;
196 }
197
198 event.preventDefault();
199
200 if (allowTransition) {
201 const { state, params } = this.props;
202 RouteUtils.navigate(state, params);
203 }
204 }
205
206 render() {
207 var props = {};
208 props.href = RouteUtils.reverse(this.props.state, this.props.params);
209 if (this.props.activeClassName) {
210 if (RouteUtils.isState(this.props.state)) {
211 props.className = this.props.className === '' ? this.props.activeClassName : `${this.props.className} ${this.props.activeClassName}`;
212 }
213 }
214 props.children = this.props.children;
215 return <a {...props} onClick={this.handleClick.bind(this)} />;
216 }
217}
218
\No newline at end of file