Skip to content

Instantly share code, notes, and snippets.

Created July 6, 2016 19:10
Show Gist options
  • Save anonymous/e21089301463dbf73521b717d14537cc to your computer and use it in GitHub Desktop.
Save anonymous/e21089301463dbf73521b717d14537cc to your computer and use it in GitHub Desktop.
JS Bin // source http://jsbin.com/jozihiluki
<!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>
#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;
}
/* 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