UNPKG

5.48 kBJavaScriptView Raw
1/**
2 * Copyright 2014-2015, Yahoo! Inc.
3 * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
4 */
5/*global window */
6'use strict';
7
8var debug = require('debug')('RouterMixin');
9var navigateAction = require('../actions/navigate');
10var History = require('./History');
11var React = require('react');
12var TYPE_CLICK = 'click';
13var TYPE_PAGELOAD = 'pageload';
14var TYPE_POPSTATE = 'popstate';
15var TYPE_DEFAULT = 'default'; // default value if navigation type is missing, for programmatic navigation
16var RouterMixin;
17
18require('setimmediate');
19
20function routesEqual(route1, route2) {
21 route1 = route1 || {};
22 route2 = route2 || {};
23 return (route1.url === route2.url);
24}
25
26function saveScrollPosition(e, history) {
27 var historyState = (history.getState && history.getState()) || {};
28 historyState.scroll = {x: window.scrollX, y: window.scrollY};
29 debug('remember scroll position', historyState.scroll);
30 history.replaceState(historyState);
31}
32
33RouterMixin = {
34 contextTypes: {
35 executeAction: React.PropTypes.func,
36 makePath: React.PropTypes.func
37 },
38 componentDidMount: function() {
39 var self = this;
40 var context;
41 var urlFromHistory;
42 var urlFromState;
43
44 if (self.context && self.context.executeAction) {
45 context = self.context;
46 } else if (self.props.context && self.props.context.executeAction) {
47 context = self.props.context;
48 }
49
50 self._history = ('function' === typeof self.props.historyCreator) ? self.props.historyCreator() : new History();
51 self._enableScroll = (self.props.enableScroll !== false);
52
53 if (self.props.checkRouteOnPageLoad) {
54 // You probably want to enable checkRouteOnPageLoad, if you use a history implementation
55 // that supports hash route:
56 // At page load, for browsers without pushState AND hash is present in the url,
57 // since hash fragment is not sent to the server side, we need to
58 // dispatch navigate action on browser side to load the actual page content
59 // for the route represented by the hash fragment.
60
61 urlFromHistory = self._history.getUrl();
62 urlFromState = self.state && self.state.route && self.state.route.url;
63
64 if (context && (urlFromHistory !== urlFromState)) {
65 // put it in setImmediate, because we need the base component to have
66 // store listeners attached, before navigateAction is executed.
67 debug('pageload navigate to actual route', urlFromHistory, urlFromState);
68 setImmediate(function navigateToActualRoute() {
69 context.executeAction(navigateAction, {type: TYPE_PAGELOAD, url: urlFromHistory});
70 });
71 }
72 }
73
74 self._historyListener = function (e) {
75 if (context) {
76 var url = self._history.getUrl();
77 debug('history listener invoked', e, url, self.state.route.url);
78 if (url !== self.state.route.url) {
79 context.executeAction(navigateAction, {type: TYPE_POPSTATE, url: url, params: (e.state && e.state.params)});
80 }
81 }
82 };
83 self._history.on(self._historyListener);
84
85 if (self._enableScroll) {
86 var scrollTimer;
87 self._scrollListener = function (e) {
88 if (scrollTimer) {
89 window.clearTimeout(scrollTimer);
90 }
91 scrollTimer = window.setTimeout(saveScrollPosition.bind(self, e, self._history), 150);
92 };
93 window.addEventListener('scroll', self._scrollListener);
94 }
95 },
96 componentWillUnmount: function() {
97 this._history.off(this._historyListener);
98 this._historyListener = null;
99
100 if (this._enableScroll) {
101 window.removeEventListener('scroll', this._scrollListener);
102 this._scrollListener = null;
103 }
104
105 this._history = null;
106 },
107 componentDidUpdate: function (prevProps, prevState) {
108 debug('component did update', prevState, this.state);
109
110 var newState = this.state;
111 if (routesEqual(prevState && prevState.route, newState && newState.route)) {
112 return;
113 }
114
115 var nav = newState.route.navigate;
116 var navType = (nav && nav.type) || TYPE_DEFAULT;
117 var historyState;
118
119 switch (navType) {
120 case TYPE_CLICK:
121 case TYPE_DEFAULT:
122 historyState = {params: (nav && nav.params) || {}};
123 if (this._enableScroll) {
124 window.scrollTo(0, 0);
125 historyState.scroll = {x: 0, y: 0};
126 debug('on click navigation, reset scroll position to (0, 0)');
127 }
128 this._history.pushState(historyState, null, newState.route.url);
129 break;
130 case TYPE_POPSTATE:
131 if (this._enableScroll) {
132 historyState = (this._history.getState && this._history.getState()) || {};
133 var scroll = (historyState && historyState.scroll) || {};
134 debug('on popstate navigation, restore scroll position to ', scroll);
135 window.scrollTo(scroll.x || 0, scroll.y || 0);
136 }
137 break;
138 }
139 }
140};
141
142module.exports = RouterMixin;