Skip to content

Instantly share code, notes, and snippets.

Created July 7, 2016 18:50
Show Gist options
  • Save anonymous/7283f16ad512d6901bec67d26517fff3 to your computer and use it in GitHub Desktop.
Save anonymous/7283f16ad512d6901bec67d26517fff3 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/reduce-reducers"></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:
<select>
</select>
<button>Set</button>
</p>
<p>
<span>foo:</span>
<input id="draggable-chart" type="range" min="-50" max="150" value="25"/>
<span id="draggable-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"
*/
const { createStore, combineReducers } = Redux;
const extractId = (id) => parseInt(id.match(/\d+/g)[0]) // eg. "model12" => 12
/* * * * * * *
* SELECTORS *
* * * * * * */
const draggableChart = document.getElementById('draggable-chart')
, draggableValue = document.getElementById('draggable-value')
, totalValue = document.getElementById('total-value')
const selectYear = document.querySelector('#year select')
, setYear = document.querySelector('#year button')
/* * * * * * *
* REDUCERS *
* * * * * * */
// Reducers are just like [].reduce
// The accumulator is the state, and the action is the next value
const addForecastModel = (state, action) => {
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 || {})
})
}
const updateForecastModel = (state, action) => {
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))
}
/** FORECASTMODEL REDUCER **/
var forecastId = 0;
const forecastModel = (state = [], action) => {
switch (action.type) {
case 'ADD_OR_UPDATE_MODEL':
const activeModel = state.find(e => e.active)
if (activeModel && activeModel.id === 0)
return addForecastModel(state, action)
else if (activeModel)
return updateForecastModel(state, action)
else
return state
break
case 'ADD_MODEL':
return addForecastModel(state, action)
case 'SELECT_MODEL':
return state.map(e => {
e.active = e.id === action.id ? true : false
return e
})
case 'UPDATE_MODEL':
return updateForecastModel(state, action)
case 'DELETE_MODEL':
return modelIndex === 0 ?
state : state.slice(0, modelIndex).concat(state.slice(modelIndex + 1))
default:
return state
}
}
/** SELECTEDYEAR REDUCER **/
const selectedYear = (state = [], action) => {
switch (action.type) {
case 'SELECT_YEAR':
return state.map(e =>
!e.active ? e : Object.assign({}, e, { selectedYear: action.year }))
default:
return state.map(e =>
!e.active ? e : Object.assign({}, e, { selectedYear: e.defaultYear }))
}
}
/** TREFISAPP REDUCER **/
const trefisApp = reduceReducers(forecastModel, selectedYear)
// The store lets you dispatch actions and subscribe to them for rendering
const trefisStore = createStore(trefisApp)
/* * * * * * * *
* RENDER VIEW *
* * * * * * * */
// A function bound to tabs to select a tab/model
function selectModel(event) {
const modelId = extractId(event.target.id)
trefisStore.dispatch({
type: 'SELECT_MODEL',
id: modelId
})
}
const renderTabs = () => {
const tabs = trefisStore.getState().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 renderYearDropdown = (baseModel, activeModel) => {
const years = [0,1,2,3,4].map(e => baseModel.defaultYear + e)
.map(e => `<option value="${e}">${e}</option>`)
.join('')
selectYear.innerHTML = years
selectYear.selectedIndex = activeModel.selectedYear - baseModel.defaultYear
}
const renderModelValues = (baseModel, activeModel) => {
const growthFn = (total) =>
(total * (Math.log(activeModel.selectedYear - baseModel.defaultYear + 1) + 1))
draggableChart.value = activeModel.drivers.foo
draggableValue.innerHTML = activeModel.drivers.foo
totalValue.innerHTML = growthFn(activeModel.drivers.total())
}
const toggleEditYear = (activeModel) => {
if (activeModel.id === 0)
setYear.setAttribute('disabled', 'disabled')
else
setYear.removeAttribute('disabled')
}
// Renders the forecast model view on each forecastModel state change
trefisStore.subscribe(() => {
console.log(trefisStore.getState())
const activeModel = trefisStore.getState().find(e => e.active)
, baseModel = trefisStore.getState()[0]
renderTabs()
renderModelValues(baseModel, activeModel)
toggleEditYear(activeModel)
renderYearDropdown(baseModel, activeModel)
})
/* * * * * * * *
* INITIALIZE *
* * * * * * * */
// Initialize the base model
trefisStore.dispatch({
type: 'ADD_MODEL',
active: true,
defaultYear: 2016,
drivers: {
foo: 22,
total: function() { return this.foo * 3 }
}
})
/* * * * * * * * * *
* EVENT LISTENERS *
* * * * * * * * * */
// Draggable chart arm is let go
draggableChart.addEventListener('mouseup', (event) => {
trefisStore.dispatch({
type: 'ADD_OR_UPDATE_MODEL',
drivers: { foo: parseInt(draggableChart.value) }
})
})
// Year is changed in the dropdown
selectYear.addEventListener('change', (event) => {
trefisStore.dispatch({
type: 'SELECT_YEAR',
year: parseInt(event.target.value)
})
})
// The set year button is clicked
setYear.addEventListener('click', (event) => {
var selectedYear = selectYear.options[selectYear.selectedIndex].value
trefisStore.dispatch({
type: 'UPDATE_MODEL',
defaultYear: parseInt(selectedYear),
drivers: {
foo: parseInt(draggableChart.value)
}
})
})
</script>
<script id="jsbin-source-css" type="text/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;
}</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
/* * * * * * *
* SELECTORS *
* * * * * * */
const draggableChart = document.getElementById('draggable-chart')
, draggableValue = document.getElementById('draggable-value')
, totalValue = document.getElementById('total-value')
const selectYear = document.querySelector('#year select')
, setYear = document.querySelector('#year button')
/* * * * * * *
* REDUCERS *
* * * * * * */
// Reducers are just like [].reduce
// The accumulator is the state, and the action is the next value
const addForecastModel = (state, action) => {
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 || {})
})
}
const updateForecastModel = (state, action) => {
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))
}
/** FORECASTMODEL REDUCER **/
var forecastId = 0;
const forecastModel = (state = [], action) => {
switch (action.type) {
case 'ADD_OR_UPDATE_MODEL':
const activeModel = state.find(e => e.active)
if (activeModel && activeModel.id === 0)
return addForecastModel(state, action)
else if (activeModel)
return updateForecastModel(state, action)
else
return state
break
case 'ADD_MODEL':
return addForecastModel(state, action)
case 'SELECT_MODEL':
return state.map(e => {
e.active = e.id === action.id ? true : false
return e
})
case 'UPDATE_MODEL':
return updateForecastModel(state, action)
case 'DELETE_MODEL':
return modelIndex === 0 ?
state : state.slice(0, modelIndex).concat(state.slice(modelIndex + 1))
default:
return state
}
}
/** SELECTEDYEAR REDUCER **/
const selectedYear = (state = [], action) => {
switch (action.type) {
case 'SELECT_YEAR':
return state.map(e =>
!e.active ? e : Object.assign({}, e, { selectedYear: action.year }))
default:
return state.map(e =>
!e.active ? e : Object.assign({}, e, { selectedYear: e.defaultYear }))
}
}
/** TREFISAPP REDUCER **/
const trefisApp = reduceReducers(forecastModel, selectedYear)
// The store lets you dispatch actions and subscribe to them for rendering
const trefisStore = createStore(trefisApp)
/* * * * * * * *
* RENDER VIEW *
* * * * * * * */
// A function bound to tabs to select a tab/model
function selectModel(event) {
const modelId = extractId(event.target.id)
trefisStore.dispatch({
type: 'SELECT_MODEL',
id: modelId
})
}
const renderTabs = () => {
const tabs = trefisStore.getState().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 renderYearDropdown = (baseModel, activeModel) => {
const years = [0,1,2,3,4].map(e => baseModel.defaultYear + e)
.map(e => `<option value="${e}">${e}</option>`)
.join('')
selectYear.innerHTML = years
selectYear.selectedIndex = activeModel.selectedYear - baseModel.defaultYear
}
const renderModelValues = (baseModel, activeModel) => {
const growthFn = (total) =>
(total * (Math.log(activeModel.selectedYear - baseModel.defaultYear + 1) + 1))
draggableChart.value = activeModel.drivers.foo
draggableValue.innerHTML = activeModel.drivers.foo
totalValue.innerHTML = growthFn(activeModel.drivers.total())
}
const toggleEditYear = (activeModel) => {
if (activeModel.id === 0)
setYear.setAttribute('disabled', 'disabled')
else
setYear.removeAttribute('disabled')
}
// Renders the forecast model view on each forecastModel state change
trefisStore.subscribe(() => {
console.log(trefisStore.getState())
const activeModel = trefisStore.getState().find(e => e.active)
, baseModel = trefisStore.getState()[0]
renderTabs()
renderModelValues(baseModel, activeModel)
toggleEditYear(activeModel)
renderYearDropdown(baseModel, activeModel)
})
/* * * * * * * *
* INITIALIZE *
* * * * * * * */
// Initialize the base model
trefisStore.dispatch({
type: 'ADD_MODEL',
active: true,
defaultYear: 2016,
drivers: {
foo: 22,
total: function() { return this.foo * 3 }
}
})
/* * * * * * * * * *
* EVENT LISTENERS *
* * * * * * * * * */
// Draggable chart arm is let go
draggableChart.addEventListener('mouseup', (event) => {
trefisStore.dispatch({
type: 'ADD_OR_UPDATE_MODEL',
drivers: { foo: parseInt(draggableChart.value) }
})
})
// Year is changed in the dropdown
selectYear.addEventListener('change', (event) => {
trefisStore.dispatch({
type: 'SELECT_YEAR',
year: parseInt(event.target.value)
})
})
// The set year button is clicked
setYear.addEventListener('click', (event) => {
var selectedYear = selectYear.options[selectYear.selectedIndex].value
trefisStore.dispatch({
type: 'UPDATE_MODEL',
defaultYear: parseInt(selectedYear),
drivers: {
foo: parseInt(draggableChart.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"
*/
const { createStore, combineReducers } = Redux;
const extractId = (id) => parseInt(id.match(/\d+/g)[0]) // eg. "model12" => 12
/* * * * * * *
* SELECTORS *
* * * * * * */
const draggableChart = document.getElementById('draggable-chart')
, draggableValue = document.getElementById('draggable-value')
, totalValue = document.getElementById('total-value')
const selectYear = document.querySelector('#year select')
, setYear = document.querySelector('#year button')
/* * * * * * *
* REDUCERS *
* * * * * * */
// Reducers are just like [].reduce
// The accumulator is the state, and the action is the next value
const addForecastModel = (state, action) => {
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 || {})
})
}
const updateForecastModel = (state, action) => {
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))
}
/** FORECASTMODEL REDUCER **/
var forecastId = 0;
const forecastModel = (state = [], action) => {
switch (action.type) {
case 'ADD_OR_UPDATE_MODEL':
const activeModel = state.find(e => e.active)
if (activeModel && activeModel.id === 0)
return addForecastModel(state, action)
else if (activeModel)
return updateForecastModel(state, action)
else
return state
break
case 'ADD_MODEL':
return addForecastModel(state, action)
case 'SELECT_MODEL':
return state.map(e => {
e.active = e.id === action.id ? true : false
return e
})
case 'UPDATE_MODEL':
return updateForecastModel(state, action)
case 'DELETE_MODEL':
return modelIndex === 0 ?
state : state.slice(0, modelIndex).concat(state.slice(modelIndex + 1))
default:
return state
}
}
/** SELECTEDYEAR REDUCER **/
const selectedYear = (state = [], action) => {
switch (action.type) {
case 'SELECT_YEAR':
return state.map(e =>
!e.active ? e : Object.assign({}, e, { selectedYear: action.year }))
default:
return state.map(e =>
!e.active ? e : Object.assign({}, e, { selectedYear: e.defaultYear }))
}
}
/** TREFISAPP REDUCER **/
const trefisApp = reduceReducers(forecastModel, selectedYear)
// The store lets you dispatch actions and subscribe to them for rendering
const trefisStore = createStore(trefisApp)
/* * * * * * * *
* RENDER VIEW *
* * * * * * * */
// A function bound to tabs to select a tab/model
function selectModel(event) {
const modelId = extractId(event.target.id)
trefisStore.dispatch({
type: 'SELECT_MODEL',
id: modelId
})
}
const renderTabs = () => {
const tabs = trefisStore.getState().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 renderYearDropdown = (baseModel, activeModel) => {
const years = [0,1,2,3,4].map(e => baseModel.defaultYear + e)
.map(e => `<option value="${e}">${e}</option>`)
.join('')
selectYear.innerHTML = years
selectYear.selectedIndex = activeModel.selectedYear - baseModel.defaultYear
}
const renderModelValues = (baseModel, activeModel) => {
const growthFn = (total) =>
(total * (Math.log(activeModel.selectedYear - baseModel.defaultYear + 1) + 1))
draggableChart.value = activeModel.drivers.foo
draggableValue.innerHTML = activeModel.drivers.foo
totalValue.innerHTML = growthFn(activeModel.drivers.total())
}
const toggleEditYear = (activeModel) => {
if (activeModel.id === 0)
setYear.setAttribute('disabled', 'disabled')
else
setYear.removeAttribute('disabled')
}
// Renders the forecast model view on each forecastModel state change
trefisStore.subscribe(() => {
console.log(trefisStore.getState())
const activeModel = trefisStore.getState().find(e => e.active)
, baseModel = trefisStore.getState()[0]
renderTabs()
renderModelValues(baseModel, activeModel)
toggleEditYear(activeModel)
renderYearDropdown(baseModel, activeModel)
})
/* * * * * * * *
* INITIALIZE *
* * * * * * * */
// Initialize the base model
trefisStore.dispatch({
type: 'ADD_MODEL',
active: true,
defaultYear: 2016,
drivers: {
foo: 22,
total: function() { return this.foo * 3 }
}
})
/* * * * * * * * * *
* EVENT LISTENERS *
* * * * * * * * * */
// Draggable chart arm is let go
draggableChart.addEventListener('mouseup', (event) => {
trefisStore.dispatch({
type: 'ADD_OR_UPDATE_MODEL',
drivers: { foo: parseInt(draggableChart.value) }
})
})
// Year is changed in the dropdown
selectYear.addEventListener('change', (event) => {
trefisStore.dispatch({
type: 'SELECT_YEAR',
year: parseInt(event.target.value)
})
})
// The set year button is clicked
setYear.addEventListener('click', (event) => {
var selectedYear = selectYear.options[selectYear.selectedIndex].value
trefisStore.dispatch({
type: 'UPDATE_MODEL',
defaultYear: parseInt(selectedYear),
drivers: {
foo: parseInt(draggableChart.value)
}
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment