web: use contexts to pass down stores.

Using contexts frees us from the contracts we have
using props - namely, we can assume them to be constant
for the lifetime of the object.
This commit is contained in:
Maximilian Hils
2015-03-27 21:58:04 +01:00
parent f39e6c5c18
commit 1913975fa6
10 changed files with 261 additions and 273 deletions

View File

@@ -7,8 +7,8 @@ var AutoScrollMixin = {
componentWillUpdate: function () {
var node = this.getDOMNode();
this._shouldScrollBottom = (
node.scrollTop !== 0 &&
node.scrollTop + node.clientHeight === node.scrollHeight
node.scrollTop !== 0 &&
node.scrollTop + node.clientHeight === node.scrollHeight
);
},
componentDidUpdate: function () {
@@ -29,32 +29,54 @@ var StickyHeadMixin = {
}
};
var SettingsState = {
contextTypes: {
settingsStore: React.PropTypes.object.isRequired
},
getInitialState: function () {
return {
settings: this.context.settingsStore.dict
};
},
componentDidMount: function () {
this.context.settingsStore.addListener("recalculate", this.onSettingsChange);
},
componentWillUnmount: function () {
this.context.settingsStore.removeListener("recalculate", this.onSettingsChange);
},
onSettingsChange: function () {
this.setState({
settings: this.context.settingsStore.dict
});
},
};
var ChildFocus = {
contextTypes: {
returnFocus: React.PropTypes.func
}
contextTypes: {
returnFocus: React.PropTypes.func
}
};
var Navigation = _.extend({}, ReactRouter.Navigation, {
setQuery: function (dict) {
var q = this.context.router.getCurrentQuery();
for(var i in dict){
if(dict.hasOwnProperty(i)){
for (var i in dict) {
if (dict.hasOwnProperty(i)) {
q[i] = dict[i] || undefined; //falsey values shall be removed.
}
}
this.replaceWith(this.context.router.getCurrentPath(), this.context.router.getCurrentParams(), q);
},
replaceWith: function(routeNameOrPath, params, query) {
if(routeNameOrPath === undefined){
replaceWith: function (routeNameOrPath, params, query) {
if (routeNameOrPath === undefined) {
routeNameOrPath = this.context.router.getCurrentPath();
}
if(params === undefined){
if (params === undefined) {
params = this.context.router.getCurrentParams();
}
if(query === undefined) {
if (query === undefined) {
query = this.context.router.getCurrentQuery();
}
@@ -65,13 +87,13 @@ var Navigation = _.extend({}, ReactRouter.Navigation, {
// react-router is fairly good at changing its API regularly.
// We keep the old method for now - if it should turn out that their changes are permanent,
// we may remove this mixin and access react-router directly again.
var State = _.extend({}, ReactRouter.State, {
getQuery: function(){
var RouterState = _.extend({}, ReactRouter.State, {
getQuery: function () {
// For whatever reason, react-router always returns the same object, which makes comparing
// the current props with nextProps impossible. As a workaround, we just clone the query object.
return _.clone(this.context.router.getCurrentQuery());
},
getParams: function(){
getParams: function () {
return _.clone(this.context.router.getCurrentParams());
}
});
@@ -183,9 +205,10 @@ var Splitter = React.createClass({
module.exports = {
ChildFocus: ChildFocus,
State: State,
RouterState: RouterState,
Navigation: Navigation,
StickyHeadMixin: StickyHeadMixin,
AutoScrollMixin: AutoScrollMixin,
Splitter: Splitter
Splitter: Splitter,
SettingsState: SettingsState
};

View File

@@ -31,46 +31,37 @@ var LogMessage = React.createClass({
});
var EventLogContents = React.createClass({
contextTypes: {
eventStore: React.PropTypes.object.isRequired
},
mixins: [common.AutoScrollMixin, VirtualScrollMixin],
getInitialState: function () {
return {
log: []
};
},
componentWillMount: function () {
this.openView(this.props.eventStore);
},
componentWillUnmount: function () {
this.closeView();
},
openView: function (store) {
var view = new views.StoreView(store, function (entry) {
var filterFn = function (entry) {
return this.props.filter[entry.level];
}.bind(this));
this.setState({
view: view
});
};
var view = new views.StoreView(this.context.eventStore, filterFn.bind(this));
view.addListener("add", this.onEventLogChange);
view.addListener("recalculate", this.onEventLogChange);
return {
log: view.list,
view: view
};
},
closeView: function () {
componentWillUnmount: function () {
this.state.view.close();
},
filter: function (entry) {
return this.props.filter[entry.level];
},
onEventLogChange: function () {
this.setState({
log: this.state.view.list
});
this.forceUpdate();
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.filter !== this.props.filter) {
this.props.filter = nextProps.filter; // Dirty: Make sure that view filter sees the update.
this.state.view.recalculate();
}
if (nextProps.eventStore !== this.props.eventStore) {
this.closeView();
this.openView(nextProps.eventStore);
}
},
getDefaultProps: function () {
return {
@@ -149,7 +140,7 @@ var EventLog = React.createClass({
</div>
</div>
<EventLogContents filter={this.state.filter} eventStore={this.props.eventStore}/>
<EventLogContents filter={this.state.filter}/>
</div>
);
}

View File

@@ -108,33 +108,25 @@ var ROW_HEIGHT = 32;
var FlowTable = React.createClass({
mixins: [common.StickyHeadMixin, common.AutoScrollMixin, VirtualScrollMixin],
contextTypes: {
view: React.PropTypes.object.isRequired
},
getInitialState: function () {
return {
columns: flowtable_columns
};
},
_listen: function(view){
if(!view){
return;
}
view.addListener("add", this.onChange);
view.addListener("update", this.onChange);
view.addListener("remove", this.onChange);
view.addListener("recalculate", this.onChange);
},
componentWillMount: function () {
this._listen(this.props.view);
this.context.view.addListener("add", this.onChange);
this.context.view.addListener("update", this.onChange);
this.context.view.addListener("remove", this.onChange);
this.context.view.addListener("recalculate", this.onChange);
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.view !== this.props.view) {
if (this.props.view) {
this.props.view.removeListener("add");
this.props.view.removeListener("update");
this.props.view.removeListener("remove");
this.props.view.removeListener("recalculate");
}
this._listen(nextProps.view);
}
componentWillUnmount: function(){
this.context.view.removeListener("add", this.onChange);
this.context.view.removeListener("update", this.onChange);
this.context.view.removeListener("remove", this.onChange);
this.context.view.removeListener("recalculate", this.onChange);
},
getDefaultProps: function () {
return {
@@ -150,7 +142,7 @@ var FlowTable = React.createClass({
},
scrollIntoView: function (flow) {
this.scrollRowIntoView(
this.props.view.index(flow),
this.context.view.index(flow),
this.refs.body.getDOMNode().offsetTop
);
},
@@ -158,8 +150,8 @@ var FlowTable = React.createClass({
var selected = (flow === this.props.selected);
var highlighted =
(
this.props.view._highlight &&
this.props.view._highlight[flow.id]
this.context.view._highlight &&
this.context.view._highlight[flow.id]
);
return <FlowRow key={flow.id}
@@ -172,9 +164,7 @@ var FlowTable = React.createClass({
/>;
},
render: function () {
//console.log("render flowtable", this.state.start, this.state.stop, this.props.selected);
var flows = this.props.view ? this.props.view.list : [];
var flows = this.context.view.list;
var rows = this.renderRows(flows);
return (

View File

@@ -14,7 +14,7 @@ var allTabs = {
};
var FlowView = React.createClass({
mixins: [common.StickyHeadMixin, common.Navigation, common.State],
mixins: [common.StickyHeadMixin, common.Navigation, common.RouterState],
getTabs: function (flow) {
var tabs = [];
["request", "response", "error"].forEach(function (e) {

View File

@@ -1,9 +1,11 @@
var React = require("react");
var common = require("./common.js");
var Footer = React.createClass({
mixins: [common.SettingsState],
render: function () {
var mode = this.props.settings.mode;
var intercept = this.props.settings.intercept;
var mode = this.state.settings.mode;
var intercept = this.state.settings.intercept;
return (
<footer>
{mode != "regular" ? <span className="label label-success">{mode} mode</span> : null}

View File

@@ -157,7 +157,7 @@ var FilterInput = React.createClass({
});
var MainMenu = React.createClass({
mixins: [common.Navigation, common.State],
mixins: [common.Navigation, common.RouterState, common.SettingsState],
statics: {
title: "Start",
route: "flows"
@@ -178,7 +178,7 @@ var MainMenu = React.createClass({
render: function () {
var filter = this.getQuery()[Query.FILTER] || "";
var highlight = this.getQuery()[Query.HIGHLIGHT] || "";
var intercept = this.props.settings.intercept || "";
var intercept = this.state.settings.intercept || "";
return (
<div>
@@ -214,7 +214,7 @@ var ViewMenu = React.createClass({
title: "View",
route: "flows"
},
mixins: [common.Navigation, common.State],
mixins: [common.Navigation, common.RouterState],
toggleEventLog: function () {
var d = {};
@@ -379,7 +379,7 @@ var Header = React.createClass({
{header}
</nav>
<div className="menu">
<this.state.active settings={this.props.settings}/>
<this.state.active/>
</div>
</header>
);
@@ -389,4 +389,4 @@ var Header = React.createClass({
module.exports = {
Header: Header
}
};

View File

@@ -10,22 +10,40 @@ FlowTable = require("./flowtable.js");
var FlowView = require("./flowview/index.js");
var MainView = React.createClass({
mixins: [common.Navigation, common.State],
mixins: [common.Navigation, common.RouterState],
contextTypes: {
flowStore: React.PropTypes.object.isRequired,
},
childContextTypes: {
returnFocus: React.PropTypes.func.isRequired
returnFocus: React.PropTypes.func.isRequired,
view: React.PropTypes.object.isRequired,
},
getChildContext: function() {
return { returnFocus: this.returnFocus };
getChildContext: function () {
return {
returnFocus: this.returnFocus,
view: this.state.view
};
},
returnFocus: function(){
returnFocus: function () {
this.getDOMNode().focus();
},
getInitialState: function () {
var sortKeyFun = false;
var view = new views.StoreView(this.context.flowStore, this.getViewFilt(), sortKeyFun);
view.addListener("recalculate", this.onRecalculate);
view.addListener("add", this.onUpdate);
view.addListener("update", this.onUpdate);
view.addListener("remove", this.onUpdate);
view.addListener("remove", this.onRemove);
return {
flows: [],
sortKeyFun: false
view: view,
sortKeyFun: sortKeyFun
};
},
componentWillUnmount: function () {
this.state.view.close();
},
getViewFilt: function () {
try {
var filt = Filt.parse(this.getQuery()[Query.FILTER] || "");
@@ -44,29 +62,12 @@ var MainView = React.createClass({
};
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.flowStore !== this.props.flowStore) {
this.closeView();
this.openView(nextProps.flowStore);
}
var filterChanged = (this.props.query[Query.FILTER] !== nextProps.query[Query.FILTER]);
var highlightChanged = (this.props.query[Query.HIGHLIGHT] !== nextProps.query[Query.HIGHLIGHT]);
if (filterChanged || highlightChanged) {
this.state.view.recalculate(this.getViewFilt(), this.state.sortKeyFun);
}
},
openView: function (store) {
var view = new views.StoreView(store, this.getViewFilt(), this.state.sortKeyFun);
this.setState({
view: view
});
view.addListener("recalculate", this.onRecalculate);
view.addListener("add", this.onUpdate);
view.addListener("update", this.onUpdate);
view.addListener("remove", this.onUpdate);
view.addListener("remove", this.onRemove);
},
onRecalculate: function () {
this.forceUpdate();
var selected = this.getSelected();
@@ -85,16 +86,7 @@ var MainView = React.createClass({
this.selectFlow(flow_to_select);
}
},
closeView: function () {
this.state.view.close();
},
componentWillMount: function () {
this.openView(this.props.flowStore);
},
componentWillUnmount: function () {
this.closeView();
},
setSortKeyFun: function(sortKeyFun){
setSortKeyFun: function (sortKeyFun) {
this.setState({
sortKeyFun: sortKeyFun
});
@@ -221,7 +213,7 @@ var MainView = React.createClass({
e.preventDefault();
},
getSelected: function () {
return this.props.flowStore.get(this.getParams().flowId);
return this.context.flowStore.get(this.getParams().flowId);
},
render: function () {
var selected = this.getSelected();
@@ -239,7 +231,6 @@ var MainView = React.createClass({
return (
<div className="main-view" onKeyDown={this.onKeyDown} tabIndex="0">
<FlowTable ref="flowTable"
view={this.state.view}
selectFlow={this.selectFlow}
setSortKeyFun={this.setSortKeyFun}
selected={selected} />

View File

@@ -20,52 +20,48 @@ var Reports = React.createClass({
var ProxyAppMain = React.createClass({
mixins: [common.State],
mixins: [common.RouterState],
childContextTypes: {
settingsStore: React.PropTypes.object.isRequired,
flowStore: React.PropTypes.object.isRequired,
eventStore: React.PropTypes.object.isRequired
},
getChildContext: function () {
return {
settingsStore: this.state.settingsStore,
flowStore: this.state.flowStore,
eventStore: this.state.eventStore
};
},
getInitialState: function () {
var eventStore = new store.EventLogStore();
var flowStore = new store.FlowStore();
var settings = new store.SettingsStore();
var settingsStore = new store.SettingsStore();
// Default Settings before fetch
_.extend(settings.dict,{
});
_.extend(settingsStore.dict, {});
return {
settings: settings,
settingsStore: settingsStore,
flowStore: flowStore,
eventStore: eventStore
};
},
componentDidMount: function () {
this.state.settings.addListener("recalculate", this.onSettingsChange);
window.app = this;
},
componentWillUnmount: function () {
this.state.settings.removeListener("recalculate", this.onSettingsChange);
},
onSettingsChange: function(){
this.setState({
settings: this.state.settings
});
},
render: function () {
var eventlog;
if (this.getQuery()[Query.SHOW_EVENTLOG]) {
eventlog = [
<common.Splitter key="splitter" axis="y"/>,
<EventLog key="eventlog" eventStore={this.state.eventStore}/>
<EventLog key="eventlog"/>
];
} else {
eventlog = null;
}
return (
<div id="container">
<header.Header settings={this.state.settings.dict}/>
<RouteHandler
settings={this.state.settings.dict}
flowStore={this.state.flowStore}
query={this.getQuery()}/>
<header.Header/>
<RouteHandler query={this.getQuery()}/>
{eventlog}
<Footer settings={this.state.settings.dict}/>
<Footer/>
</div>
);
}

View File

@@ -35,6 +35,7 @@ _.extend(StoreView.prototype, EventEmitter.prototype, {
this.store.removeListener("update", this.update);
this.store.removeListener("remove", this.remove);
this.store.removeListener("recalculate", this.recalculate);
this.removeAllListeners();
},
recalculate: function (filt, sortfun) {
filt = filt || this.filt || default_filt;