Created
July 6, 2016 19:10
-
-
Save anonymous/e21089301463dbf73521b717d14537cc to your computer and use it in GitHub Desktop.
JS Bin // source http://jsbin.com/jozihiluki
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width"> | |
<title>JS Bin</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.5.2/redux.min.js"></script> | |
<script src="https://wzrd.in/standalone/redux-watch"></script> | |
<style id="jsbin-css"> | |
#tabs span { | |
border: 1px solid black; | |
border-radius: 7px 7px 0 0; | |
padding: 3px; | |
cursor: pointer; | |
} | |
#tabs span.active { | |
background-color: lightgray; | |
} | |
#year input { | |
width: 40px; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="tabs"></div> | |
<p id="year"> | |
Year: | |
<input type="text" maxlength="4"/> | |
<button>Set</button> | |
</p> | |
<p> | |
<span>foo:</span> | |
<input id="sankey-chart" type="range" min="-50" max="150" value="25"/> | |
<span id="sankey-value"></span> | |
</p> | |
<p> | |
<span>total:</span> | |
<span id="total-value"></span> | |
</p> | |
<script id="jsbin-javascript"> | |
/* jshint esnext: true */ | |
/* jshint asi: true */ | |
/* jshint laxcomma: true */ | |
/* | |
ES6: (state = [], action) => { ... } | |
ES5: function(state, action) { if (!state) state = []; ... } | |
Functions with a single expression using ES6 arrow functions don't need {}: | |
[1,2,3].map(elem => elem * 2) // returns [2,4,6] | |
The const keyword declares a constant. Reusing the same name will throw an error: | |
const foo = 'bar' | |
const foo = 'baz' // throws "SyntaxError: Identifier 'foo' has already been declared" | |
`template '${foo}' string` // equivalent to: "template 'bar' string" | |
*/ | |
'use strict'; | |
var _Redux = Redux; | |
var createStore = _Redux.createStore; | |
var combineReducers = _Redux.combineReducers; | |
var extractId = function extractId(id) { | |
return parseInt(id.match(/\d+/g)[0]); | |
}; // eg. "model12" => 12 | |
// Some selectors that are used | |
var sankeyChart = document.getElementById('sankey-chart'), | |
sankeyValue = document.getElementById('sankey-value'), | |
totalValue = document.getElementById('total-value'); | |
var defaultYear = document.querySelector('#year input'), | |
setYear = document.querySelector('#year button'); | |
// Reducers are just like [].reduce, except the accumulator is the state, and the action | |
// is the next value | |
// ForecastModel reducer | |
var forecastId = 0; | |
var forecastModel = function forecastModel(state, action) { | |
if (state === undefined) state = []; | |
switch (action.type) { | |
case 'ADD_MODEL': | |
return state.map(function (e) { | |
e.active = false;return e; | |
}).concat({ | |
id: forecastId++, | |
active: true, | |
defaultYear: action.defaultYear || state[0].defaultYear, | |
drivers: !state[0] ? action.drivers : Object.assign({}, state[0].drivers, action.drivers || {}) | |
}); | |
case 'SELECT_MODEL': | |
return state.map(function (e) { | |
e.active = e.id === action.id ? true : false; | |
return e; | |
}); | |
case 'UPDATE_MODEL': | |
var modelIndex = state.findIndex(function (e) { | |
return e.active; | |
}); | |
return state.slice(0, modelIndex).concat([{ | |
id: state[modelIndex].id, | |
active: true, | |
defaultYear: action.defaultYear || state[modelIndex].defaultYear, | |
drivers: Object.assign({}, state[0].drivers, action.drivers || {}) | |
}]).concat(state.slice(modelIndex + 1)); | |
case 'DELETE_MODEL': | |
return modelIndex === 0 ? state : state.slice(0, modelIndex).concat(state.slice(modelIndex + 1)); | |
default: | |
return state; | |
} | |
}; | |
// Sankey reducer | |
var sankey = function sankey(state, action) { | |
if (state === undefined) state = sankeyChart.value; | |
switch (action.type) { | |
case 'SANKEY_DRAG': | |
return action.sankeyValue; | |
default: | |
return state; | |
} | |
}; | |
// Combined reducer | |
var trefisApp = combineReducers({ | |
forecastModel: forecastModel, | |
sankey: sankey | |
}); | |
// The store lets you dispatch actions and subscribe to them for rendering | |
var trefisStore = createStore(trefisApp); | |
// A function bound to tabs that triggers a SELECT_MODEL action | |
function selectModel(event) { | |
var modelId = extractId(event.target.id); | |
trefisStore.dispatch({ | |
type: 'SELECT_MODEL', | |
id: modelId | |
}); | |
} | |
// Rendering functions split up by responsibilities | |
var renderTabs = function renderTabs() { | |
var tabs = trefisStore.getState().forecastModel.map(function (e) { | |
return '<span id="model' + e.id + '" class="' + (e.active ? 'active' : '') + '" onClick="selectModel(event)">' + ('model' + e.id) + '</span>'; | |
}).join(); | |
document.getElementById('tabs').innerHTML = tabs; | |
}; | |
var renderModelValues = function renderModelValues(activeModel) { | |
sankeyChart.value = activeModel.drivers.foo; | |
sankeyValue.innerHTML = activeModel.drivers.foo; | |
defaultYear.value = activeModel.defaultYear; | |
totalValue.innerHTML = activeModel.drivers.total(); | |
}; | |
var toggleEditYear = function toggleEditYear(activeModel) { | |
if (activeModel.id === 0) { | |
setYear.setAttribute('disabled', 'disabled'); | |
defaultYear.setAttribute('disabled', 'disabled'); | |
} else { | |
setYear.removeAttribute('disabled'); | |
defaultYear.removeAttribute('disabled'); | |
} | |
}; | |
// Renders the forecast model view on each forecastModel state change | |
var forecastModelWatch = reduxWatch(trefisStore.getState, 'forecastModel'); | |
trefisStore.subscribe(forecastModelWatch(function () { | |
var activeModel = trefisStore.getState().forecastModel.find(function (e) { | |
return e.active; | |
}); | |
renderTabs(); | |
renderModelValues(activeModel); | |
toggleEditYear(activeModel); | |
})); | |
// Dispatch the initial base model | |
trefisStore.dispatch({ | |
type: 'ADD_MODEL', | |
active: true, | |
defaultYear: 2016, | |
drivers: { | |
foo: 22, | |
total: function total() { | |
return this.foo * 3; | |
} | |
} | |
}); | |
// Renders the sankey arm view on each sankey state change | |
var sankeyWatch = reduxWatch(trefisStore.getState, 'sankey'); | |
trefisStore.subscribe(sankeyWatch(function () { | |
sankeyValue.innerHTML = trefisStore.getState().sankey; | |
})); | |
// Sankey chart arm is dragging | |
sankeyChart.addEventListener('input', function (event) { | |
trefisStore.dispatch({ | |
type: 'SANKEY_DRAG', | |
sankeyValue: event.target.value | |
}); | |
}); | |
// Sankey chart arm is let go | |
sankeyChart.addEventListener('mouseup', function (event) { | |
if (trefisStore.getState().forecastModel.find(function (e) { | |
return e.active; | |
}).id === 0) trefisStore.dispatch({ | |
type: 'ADD_MODEL', | |
drivers: { foo: parseInt(sankeyChart.value) } | |
});else trefisStore.dispatch({ | |
type: 'UPDATE_MODEL', | |
drivers: { foo: parseInt(sankeyChart.value) } | |
}); | |
}); | |
// The Set year button is clicked | |
setYear.addEventListener('click', function (event) { | |
trefisStore.dispatch({ | |
type: 'UPDATE_MODEL', | |
defaultYear: defaultYear.value, | |
drivers: { | |
foo: parseInt(sankeyChart.value) | |
} | |
}); | |
}); | |
</script> | |
<script id="jsbin-source-javascript" type="text/javascript">/* jshint esnext: true */ | |
/* jshint asi: true */ | |
/* jshint laxcomma: true */ | |
/* | |
ES6: (state = [], action) => { ... } | |
ES5: function(state, action) { if (!state) state = []; ... } | |
Functions with a single expression using ES6 arrow functions don't need {}: | |
[1,2,3].map(elem => elem * 2) // returns [2,4,6] | |
The const keyword declares a constant. Reusing the same name will throw an error: | |
const foo = 'bar' | |
const foo = 'baz' // throws "SyntaxError: Identifier 'foo' has already been declared" | |
`template '${foo}' string` // equivalent to: "template 'bar' string" | |
*/ | |
const { createStore, combineReducers } = Redux; | |
const extractId = (id) => parseInt(id.match(/\d+/g)[0]) // eg. "model12" => 12 | |
// Some selectors that are used | |
const sankeyChart = document.getElementById('sankey-chart') | |
, sankeyValue = document.getElementById('sankey-value') | |
, totalValue = document.getElementById('total-value') | |
const defaultYear = document.querySelector('#year input') | |
, setYear = document.querySelector('#year button') | |
// Reducers are just like [].reduce, except the accumulator is the state, and the action | |
// is the next value | |
// ForecastModel reducer | |
var forecastId = 0; | |
const forecastModel = (state = [], action) => { | |
switch (action.type) { | |
case 'ADD_MODEL': | |
return state.map(e => { e.active = false; return e }).concat({ | |
id: forecastId++, | |
active: true, | |
defaultYear: action.defaultYear || state[0].defaultYear, | |
drivers: !state[0] ? action.drivers : | |
Object.assign({}, state[0].drivers, action.drivers || {}) | |
}) | |
case 'SELECT_MODEL': | |
return state.map(e => { | |
e.active = e.id === action.id ? true : false | |
return e | |
}) | |
case 'UPDATE_MODEL': | |
const modelIndex = state.findIndex(e => e.active) | |
return state.slice(0, modelIndex) | |
.concat([{ | |
id: state[modelIndex].id, | |
active: true, | |
defaultYear: action.defaultYear || | |
state[modelIndex].defaultYear, | |
drivers: Object.assign( {} | |
, state[0].drivers | |
, action.drivers || {} ) | |
}]) | |
.concat(state.slice(modelIndex + 1)) | |
case 'DELETE_MODEL': | |
return modelIndex === 0 ? state | |
: state.slice(0, modelIndex) | |
.concat(state.slice(modelIndex + 1)) | |
default: | |
return state | |
} | |
} | |
// Sankey reducer | |
const sankey = (state = sankeyChart.value, action) => { | |
switch (action.type) { | |
case 'SANKEY_DRAG': | |
return action.sankeyValue | |
default: | |
return state | |
} | |
} | |
// Combined reducer | |
const trefisApp = combineReducers({ | |
forecastModel: forecastModel, | |
sankey: sankey | |
}) | |
// The store lets you dispatch actions and subscribe to them for rendering | |
const trefisStore = createStore(trefisApp) | |
// A function bound to tabs that triggers a SELECT_MODEL action | |
function selectModel(event) { | |
const modelId = extractId(event.target.id) | |
trefisStore.dispatch({ | |
type: 'SELECT_MODEL', | |
id: modelId | |
}) | |
} | |
// Rendering functions split up by responsibilities | |
const renderTabs = () => { | |
const tabs = trefisStore.getState().forecastModel.map(e => | |
`<span id="model${e.id}" class="${e.active ? 'active' : ''}" onClick="selectModel(event)">` + | |
`model${e.id}` + | |
`</span>` | |
).join() | |
document.getElementById('tabs').innerHTML = tabs | |
} | |
const renderModelValues = (activeModel) => { | |
sankeyChart.value = activeModel.drivers.foo | |
sankeyValue.innerHTML = activeModel.drivers.foo | |
defaultYear.value = activeModel.defaultYear | |
totalValue.innerHTML = activeModel.drivers.total() | |
} | |
const toggleEditYear = (activeModel) => { | |
if (activeModel.id === 0) { | |
setYear.setAttribute('disabled', 'disabled') | |
defaultYear.setAttribute('disabled', 'disabled') | |
} | |
else { | |
setYear.removeAttribute('disabled') | |
defaultYear.removeAttribute('disabled') | |
} | |
} | |
// Renders the forecast model view on each forecastModel state change | |
let forecastModelWatch = reduxWatch(trefisStore.getState, 'forecastModel') | |
trefisStore.subscribe(forecastModelWatch(() => { | |
const activeModel = trefisStore.getState().forecastModel.find(e => e.active) | |
renderTabs() | |
renderModelValues(activeModel) | |
toggleEditYear(activeModel) | |
})) | |
// Dispatch the initial base model | |
trefisStore.dispatch({ | |
type: 'ADD_MODEL', | |
active: true, | |
defaultYear: 2016, | |
drivers: { | |
foo: 22, | |
total: function() { return this.foo * 3 } | |
} | |
}) | |
// Renders the sankey arm view on each sankey state change | |
const sankeyWatch = reduxWatch(trefisStore.getState, 'sankey') | |
trefisStore.subscribe(sankeyWatch(() => { | |
sankeyValue.innerHTML = trefisStore.getState().sankey | |
})) | |
// Sankey chart arm is dragging | |
sankeyChart.addEventListener('input', (event) => { | |
trefisStore.dispatch({ | |
type: 'SANKEY_DRAG', | |
sankeyValue: event.target.value | |
}) | |
}) | |
// Sankey chart arm is let go | |
sankeyChart.addEventListener('mouseup', (event) => { | |
if (trefisStore.getState().forecastModel.find(e => e.active).id === 0) | |
trefisStore.dispatch({ | |
type: 'ADD_MODEL', | |
drivers: { foo: parseInt(sankeyChart.value) } | |
}) | |
else | |
trefisStore.dispatch({ | |
type: 'UPDATE_MODEL', | |
drivers: { foo: parseInt(sankeyChart.value) } | |
}) | |
}) | |
// The Set year button is clicked | |
setYear.addEventListener('click', (event) => { | |
trefisStore.dispatch({ | |
type: 'UPDATE_MODEL', | |
defaultYear: defaultYear.value, | |
drivers: { | |
foo: parseInt(sankeyChart.value) | |
} | |
}) | |
})</script></body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#tabs span { | |
border: 1px solid black; | |
border-radius: 7px 7px 0 0; | |
padding: 3px; | |
cursor: pointer; | |
} | |
#tabs span.active { | |
background-color: lightgray; | |
} | |
#year input { | |
width: 40px; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* jshint esnext: true */ | |
/* jshint asi: true */ | |
/* jshint laxcomma: true */ | |
/* | |
ES6: (state = [], action) => { ... } | |
ES5: function(state, action) { if (!state) state = []; ... } | |
Functions with a single expression using ES6 arrow functions don't need {}: | |
[1,2,3].map(elem => elem * 2) // returns [2,4,6] | |
The const keyword declares a constant. Reusing the same name will throw an error: | |
const foo = 'bar' | |
const foo = 'baz' // throws "SyntaxError: Identifier 'foo' has already been declared" | |
`template '${foo}' string` // equivalent to: "template 'bar' string" | |
*/ | |
'use strict'; | |
var _Redux = Redux; | |
var createStore = _Redux.createStore; | |
var combineReducers = _Redux.combineReducers; | |
var extractId = function extractId(id) { | |
return parseInt(id.match(/\d+/g)[0]); | |
}; // eg. "model12" => 12 | |
// Some selectors that are used | |
var sankeyChart = document.getElementById('sankey-chart'), | |
sankeyValue = document.getElementById('sankey-value'), | |
totalValue = document.getElementById('total-value'); | |
var defaultYear = document.querySelector('#year input'), | |
setYear = document.querySelector('#year button'); | |
// Reducers are just like [].reduce, except the accumulator is the state, and the action | |
// is the next value | |
// ForecastModel reducer | |
var forecastId = 0; | |
var forecastModel = function forecastModel(state, action) { | |
if (state === undefined) state = []; | |
switch (action.type) { | |
case 'ADD_MODEL': | |
return state.map(function (e) { | |
e.active = false;return e; | |
}).concat({ | |
id: forecastId++, | |
active: true, | |
defaultYear: action.defaultYear || state[0].defaultYear, | |
drivers: !state[0] ? action.drivers : Object.assign({}, state[0].drivers, action.drivers || {}) | |
}); | |
case 'SELECT_MODEL': | |
return state.map(function (e) { | |
e.active = e.id === action.id ? true : false; | |
return e; | |
}); | |
case 'UPDATE_MODEL': | |
var modelIndex = state.findIndex(function (e) { | |
return e.active; | |
}); | |
return state.slice(0, modelIndex).concat([{ | |
id: state[modelIndex].id, | |
active: true, | |
defaultYear: action.defaultYear || state[modelIndex].defaultYear, | |
drivers: Object.assign({}, state[0].drivers, action.drivers || {}) | |
}]).concat(state.slice(modelIndex + 1)); | |
case 'DELETE_MODEL': | |
return modelIndex === 0 ? state : state.slice(0, modelIndex).concat(state.slice(modelIndex + 1)); | |
default: | |
return state; | |
} | |
}; | |
// Sankey reducer | |
var sankey = function sankey(state, action) { | |
if (state === undefined) state = sankeyChart.value; | |
switch (action.type) { | |
case 'SANKEY_DRAG': | |
return action.sankeyValue; | |
default: | |
return state; | |
} | |
}; | |
// Combined reducer | |
var trefisApp = combineReducers({ | |
forecastModel: forecastModel, | |
sankey: sankey | |
}); | |
// The store lets you dispatch actions and subscribe to them for rendering | |
var trefisStore = createStore(trefisApp); | |
// A function bound to tabs that triggers a SELECT_MODEL action | |
function selectModel(event) { | |
var modelId = extractId(event.target.id); | |
trefisStore.dispatch({ | |
type: 'SELECT_MODEL', | |
id: modelId | |
}); | |
} | |
// Rendering functions split up by responsibilities | |
var renderTabs = function renderTabs() { | |
var tabs = trefisStore.getState().forecastModel.map(function (e) { | |
return '<span id="model' + e.id + '" class="' + (e.active ? 'active' : '') + '" onClick="selectModel(event)">' + ('model' + e.id) + '</span>'; | |
}).join(); | |
document.getElementById('tabs').innerHTML = tabs; | |
}; | |
var renderModelValues = function renderModelValues(activeModel) { | |
sankeyChart.value = activeModel.drivers.foo; | |
sankeyValue.innerHTML = activeModel.drivers.foo; | |
defaultYear.value = activeModel.defaultYear; | |
totalValue.innerHTML = activeModel.drivers.total(); | |
}; | |
var toggleEditYear = function toggleEditYear(activeModel) { | |
if (activeModel.id === 0) { | |
setYear.setAttribute('disabled', 'disabled'); | |
defaultYear.setAttribute('disabled', 'disabled'); | |
} else { | |
setYear.removeAttribute('disabled'); | |
defaultYear.removeAttribute('disabled'); | |
} | |
}; | |
// Renders the forecast model view on each forecastModel state change | |
var forecastModelWatch = reduxWatch(trefisStore.getState, 'forecastModel'); | |
trefisStore.subscribe(forecastModelWatch(function () { | |
var activeModel = trefisStore.getState().forecastModel.find(function (e) { | |
return e.active; | |
}); | |
renderTabs(); | |
renderModelValues(activeModel); | |
toggleEditYear(activeModel); | |
})); | |
// Dispatch the initial base model | |
trefisStore.dispatch({ | |
type: 'ADD_MODEL', | |
active: true, | |
defaultYear: 2016, | |
drivers: { | |
foo: 22, | |
total: function total() { | |
return this.foo * 3; | |
} | |
} | |
}); | |
// Renders the sankey arm view on each sankey state change | |
var sankeyWatch = reduxWatch(trefisStore.getState, 'sankey'); | |
trefisStore.subscribe(sankeyWatch(function () { | |
sankeyValue.innerHTML = trefisStore.getState().sankey; | |
})); | |
// Sankey chart arm is dragging | |
sankeyChart.addEventListener('input', function (event) { | |
trefisStore.dispatch({ | |
type: 'SANKEY_DRAG', | |
sankeyValue: event.target.value | |
}); | |
}); | |
// Sankey chart arm is let go | |
sankeyChart.addEventListener('mouseup', function (event) { | |
if (trefisStore.getState().forecastModel.find(function (e) { | |
return e.active; | |
}).id === 0) trefisStore.dispatch({ | |
type: 'ADD_MODEL', | |
drivers: { foo: parseInt(sankeyChart.value) } | |
});else trefisStore.dispatch({ | |
type: 'UPDATE_MODEL', | |
drivers: { foo: parseInt(sankeyChart.value) } | |
}); | |
}); | |
// The Set year button is clicked | |
setYear.addEventListener('click', function (event) { | |
trefisStore.dispatch({ | |
type: 'UPDATE_MODEL', | |
defaultYear: defaultYear.value, | |
drivers: { | |
foo: parseInt(sankeyChart.value) | |
} | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment