From 3049ff53ff368f0e9f7fc9db2ddac9eb7627c8f6 Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Wed, 14 Dec 2016 17:48:45 -0500 Subject: [PATCH 01/14] Boilerplate for beginning of Flux lesson --- package.json | 6 +- public/control-panel.html | 18 ----- public/index.html | 8 --- public/message-board.html | 52 --------------- public/tasks.html | 50 -------------- src/control-panel.js | 79 +--------------------- src/flux/Dispatcher.js | 11 --- src/flux/ReduceStore.js | 24 ------- src/flux/Store.js | 20 ------ src/flux/index.js | 3 - src/http/index.js | 7 -- src/message-board.js | 133 ------------------------------------- src/server/index.js | 11 --- src/tasks.js | 136 -------------------------------------- webpack.config.js | 20 +----- 15 files changed, 3 insertions(+), 575 deletions(-) delete mode 100644 public/message-board.html delete mode 100644 public/tasks.html delete mode 100644 src/flux/Dispatcher.js delete mode 100644 src/flux/ReduceStore.js delete mode 100644 src/flux/Store.js delete mode 100644 src/flux/index.js delete mode 100644 src/http/index.js delete mode 100644 src/message-board.js delete mode 100644 src/server/index.js delete mode 100644 src/tasks.js diff --git a/package.json b/package.json index a44985e..0206330 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,7 @@ "keywords": [], "author": "", "license": "ISC", - "dependencies": { - "redux": "^3.6.0", - "redux-logger": "^2.7.4", - "shortid": "^2.2.6" - }, + "dependencies": { }, "engine": "node 7.2.0", "devDependencies": { "babel-core": "^6.18.2", diff --git a/public/control-panel.html b/public/control-panel.html index ba810fd..0f74943 100644 --- a/public/control-panel.html +++ b/public/control-panel.html @@ -10,12 +10,6 @@

Control Panel

-

- Welcome, Jim! -

-
- -
-
-

- Font size: -

- Large
- Small
diff --git a/public/index.html b/public/index.html index 60701a4..a0a3ac1 100644 --- a/public/index.html +++ b/public/index.html @@ -12,12 +12,6 @@

Welcome to the Flux-Redux Productivity App!

diff --git a/public/message-board.html b/public/message-board.html deleted file mode 100644 index 2b4e91c..0000000 --- a/public/message-board.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - Productivity App - Message Board - - - - - -
-
-
-
- - - - - - - -
-
- - -
- -
-
- -
-
- - \ No newline at end of file diff --git a/public/tasks.html b/public/tasks.html deleted file mode 100644 index 58e3fc4..0000000 --- a/public/tasks.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - Productivity App - Tasks - - - - - - - -

Tasks

-

- Welcome, Jim! -

-
-
- - -
-
- -
- - -
- -
- -
-
- - - \ No newline at end of file diff --git a/src/control-panel.js b/src/control-panel.js index 6a977ec..083cdfa 100644 --- a/src/control-panel.js +++ b/src/control-panel.js @@ -1,78 +1 @@ -import { Dispatcher, Store } from './flux'; - -const controlPanelDispatcher = new Dispatcher(); - -export const UPDATE_USERNAME = `UPDATE_USERNAME`; -export const UPDATE_FONT_SIZE_PREFERENCE = `UPDATE_FONT_SIZE_PREFERENCE`; - -const userNameUpdateAction = (name)=>{ - return { - type: UPDATE_USERNAME, - value: name - } -} - -const fontSizePreferenceUpdateAction = (size)=>{ - return { - type: UPDATE_FONT_SIZE_PREFERENCE, - value: size - } -} -// -class UserPrefsStore extends Store { - getInitialState() { - return localStorage[`preferences`] ? JSON.parse(localStorage[`preferences`]) : { - userName: "Jim", - fontSize: "small" - }; - } - __onDispatch(action){ - switch(action.type) { - case UPDATE_USERNAME: - this.__state.userName = action.value; - this.__emitChange(); - break; - case UPDATE_FONT_SIZE_PREFERENCE: - this.__state.fontSize = action.value; - this.__emitChange(); - break; - } - } - getUserPreferences(){ - return this.__state; - } -} - -const userPrefsStore = new UserPrefsStore(controlPanelDispatcher); -const userNameInput = document.getElementById(`userNameInput`); -userNameInput.addEventListener("input",({target})=>{ - const name = target.value; - controlPanelDispatcher.dispatch(userNameUpdateAction(name)); -}); - -const fontSizeForm = document.forms.fontSizeForm; - -fontSizeForm.fontSize.forEach(element=>{ - element.addEventListener("change",({target})=>{ - console.log("Buton change...",target.value); - controlPanelDispatcher.dispatch(fontSizePreferenceUpdateAction(target.value)); - }) -}); - -const render = ({userName,fontSize})=>{ - document.getElementById("userName").innerText = userName; - document.getElementsByClassName("container")[0].style.fontSize = fontSize === "small" ? "16px" : "24px"; - fontSizeForm.fontSize.value = fontSize; - -} - -userPrefsStore.addListener((state)=>{ - render(state); - saveUserPreferences(state); -}); - -const saveUserPreferences =(state)=>{ - localStorage[`preferences`] = JSON.stringify(state); -} - -render(userPrefsStore.getUserPreferences()); \ No newline at end of file +console.log(`control-panel.js`); \ No newline at end of file diff --git a/src/flux/Dispatcher.js b/src/flux/Dispatcher.js deleted file mode 100644 index 1636b8f..0000000 --- a/src/flux/Dispatcher.js +++ /dev/null @@ -1,11 +0,0 @@ -export class Dispatcher { - constructor(){ - this.__listeners = []; - } - dispatch(action){ - this.__listeners.forEach(listener=>listener(action)); - } - register(listener){ - this.__listeners.push(listener); - } -} \ No newline at end of file diff --git a/src/flux/ReduceStore.js b/src/flux/ReduceStore.js deleted file mode 100644 index 0392af1..0000000 --- a/src/flux/ReduceStore.js +++ /dev/null @@ -1,24 +0,0 @@ -import {Store} from './Store'; -export class ReduceStore extends Store { - constructor(dispatcher){ - super(dispatcher); - this.__history = []; - } - reduce(state,action){ - throw new Error("Subclasses must implement reduce method of a Flux ReduceStore"); - } - __onDispatch(action){ - const newState = this.reduce(this.__state,action); - if (newState !== this.__state) { - this.__history.push(this.__state); - this.__state = newState; - this.__emitChange(); - } - } - revertLastState(){ - if (this.__history.length > 0) - this.__state = this.__history.pop(); - this.__emitChange(); - } - -} \ No newline at end of file diff --git a/src/flux/Store.js b/src/flux/Store.js deleted file mode 100644 index 0f3d25a..0000000 --- a/src/flux/Store.js +++ /dev/null @@ -1,20 +0,0 @@ -export class Store { - constructor(dispatcher){ - this.__listeners = []; - this.__state = this.getInitialState(); - dispatcher.register(this.__onDispatch.bind(this)); - } - getInitialState(){ - throw new Error("Subclasses must override getInitialState method of a Flux Store"); - } - __onDispatch(){ - throw new Error("Subclasses must override __onDispatch method of a Flux Store"); - } - addListener(listener){ - this.__listeners.push(listener); - } - __emitChange(){ - this.__listeners.forEach(listener=>listener(this.__state)); - } - -} \ No newline at end of file diff --git a/src/flux/index.js b/src/flux/index.js deleted file mode 100644 index cda507c..0000000 --- a/src/flux/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export {Dispatcher} from './Dispatcher'; -export {Store} from './Store'; -export {ReduceStore} from './ReduceStore'; diff --git a/src/http/index.js b/src/http/index.js deleted file mode 100644 index 599e21b..0000000 --- a/src/http/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import { generate as id } from 'shortid'; -const asyncAwaitTime = 500; -export const get = (url, cb)=>{ - setTimeout(()=>{ - cb(id()); - },asyncAwaitTime); -} \ No newline at end of file diff --git a/src/message-board.js b/src/message-board.js deleted file mode 100644 index b256c23..0000000 --- a/src/message-board.js +++ /dev/null @@ -1,133 +0,0 @@ -import { createStore, combineReducers, applyMiddleware } from 'redux' -import { get } from './http'; -import logger from 'redux-logger'; - -export const ONLINE = `ONLINE`; -export const AWAY = `AWAY`; -export const BUSY = `BUSY`; -export const CREATE_NEW_MESSAGE = `CREATE_NEW_MESSAGE`; -export const NEW_MESSAGE_SERVER_ACCEPTED = `NEW_MESSAGE_SERVER_ACCEPTED`; -export const UPDATE_STATUS = `UPDATE_STATUS`; -export const OFFLINE = `OFFLINE`; -export const READY = `READY`; -export const WAITING = `WAITING`; - - - -const defaultState = { - messages:[{ - date:new Date('2016-10-10 10:11:55'), - postedBy:`Stan`, - content:`I <3 the new productivity app!` - },{ - date:new Date('2016-10-10 10:12:00'), - postedBy:`Jerry`, - content:`I don't know if the new version of Bootstrap is really better...` - },{ - date:new Date('2016-10-10 12:06:04'), - postedBy:`Llewlyn`, - content:`Anyone got tickets to ng-conf?` - }], - userStatus: ONLINE, - apiCommunicationStatus: READY -} - - -const newMessageAction = (content, postedBy, dispatch)=>{ - const date = new Date(); - - // TODO... add asnychronicity to this action creator - - get('/api/create',(id=>{ - store.dispatch({ - type: NEW_MESSAGE_SERVER_ACCEPTED, - value: content, - postedBy, - date, - id - }) - })); - - return { - type: CREATE_NEW_MESSAGE, - value: content, - postedBy, - date - } -} - -const statusUpdateAction = (value)=>{ - return { - type: UPDATE_STATUS, - value - } -} - -const userStatusReducer = (state = ONLINE, {type, value}) => { - switch (type) { - case UPDATE_STATUS: - return value; - } - return state; -} - -const apiCommunicationStatusReducer = (state = READY, {type}) => { - switch (type) { - case CREATE_NEW_MESSAGE: - return WAITING; - case NEW_MESSAGE_SERVER_ACCEPTED: - return READY; - } - return state; -} - - -const messageReducer = (state = defaultState.messages, {type, value, postedBy, date}) => { - switch (type) { - case CREATE_NEW_MESSAGE: - const newState = [ { date: date, postedBy, content: value } , ... state ] - return newState; - } - return state; -} - -const combinedReducer = combineReducers({ - userStatus: userStatusReducer, - messages: messageReducer, - apiCommunicationStatus: apiCommunicationStatusReducer -}) - -const store = createStore( - combinedReducer, - applyMiddleware(logger()) -); - -const render = ()=>{ - const {messages, userStatus, apiCommunicationStatus} = store.getState(); - document.getElementById("messages").innerHTML = messages - .sort((a,b)=>b.date - a.date) - .map(message=>(` -
- ${message.postedBy} : ${message.content} -
` - )).join(""); - - document.forms.newMessage.newMessage.value = ""; - document.forms.newMessage.fields.disabled = (userStatus === OFFLINE) || (apiCommunicationStatus === WAITING); - -} - -document.forms.newMessage.addEventListener("submit",(e)=>{ - e.preventDefault(); - const value = e.target.newMessage.value; - const username = localStorage[`preferences`] ? JSON.parse(localStorage[`preferences`]).userName : "Jim"; - store.dispatch(newMessageAction(value, username)); -}); - -document.forms.selectStatus.status.addEventListener("change",(e)=>{ - store.dispatch(statusUpdateAction(e.target.value)); -}) - -render(); - -store.subscribe(render) \ No newline at end of file diff --git a/src/server/index.js b/src/server/index.js deleted file mode 100644 index 542a0d5..0000000 --- a/src/server/index.js +++ /dev/null @@ -1,11 +0,0 @@ -const express = require('express'); -const app = express(); -app.get('/createMessage',(req,res)=>{ - res.json({"abc":"def"}); -}); - -app.use(require('cors')()); -app.listen(`7777`,()=>{ - console.log("Server initialize"); -}) - diff --git a/src/tasks.js b/src/tasks.js deleted file mode 100644 index 62fb293..0000000 --- a/src/tasks.js +++ /dev/null @@ -1,136 +0,0 @@ -import { generate as id } from 'shortid'; -import { Dispatcher, ReduceStore } from './flux'; -// import $ from 'cheerio'; - -const tasksDispatcher = new Dispatcher(); - -const CREATE_TASK = `CREATE_TASK`; -const COMPLETE_TASK = `COMPLETE_TASK` -const SHOW_TASKS = `SHOW_TASKS`; - -const createNewTaskAction = (content)=>{ - return { - type: CREATE_TASK, - value: content - } -} - -const completeTaskAction = (id,isComplete)=>{ - return { - type: COMPLETE_TASK, - id, - value: isComplete - } -} - -const showTasksAction = (show)=>{ - return { - type: SHOW_TASKS, - value: show - } -} - -class TasksStore extends ReduceStore { - getInitialState() { - return { - tasks: [{ - id: id(), - content: "Update CSS styles", - complete: false - }, { - id: id(), - content: "Add unit tests", - complete: false - }, { - id: id(), - content: "Post to social media", - complete: false - },{ - id: id(), - content: "Install hard drive", - complete: true - }], - showComplete:true - }; - } - reduce(state,action){ - let newState; - switch(action.type) { - case CREATE_TASK: - newState = { ...state, tasks: [ ... state.tasks ]}; - newState.tasks.push({ - id:id(), - content:action.value, - complete: false - }) - return newState; - case COMPLETE_TASK: - newState = { ... state, tasks: [ ... state.tasks ]}; - const affectedElementIndex = newState.tasks.findIndex(t=>t.id === action.id); - newState.tasks[affectedElementIndex] = { ... state.tasks[affectedElementIndex], complete: action.value } - return newState; - case SHOW_TASKS: - newState = { ... state, showComplete: action.value }; - return newState; - } - return state; - } - getState(){ - return this.__state; - } -} - -const tasksStore = new TasksStore(tasksDispatcher); - -const TaskComponent = ({content,complete,id})=>( - `
- ${content} -
` -) - -const render = () => { - const tasksSection = document.getElementById(`tasks`); - const state = tasksStore.getState(); - const rendered = tasksStore.getState().tasks - .filter(task=>state.showComplete ? true : !task.complete) - .map(TaskComponent).join(""); - tasksSection.innerHTML = rendered; - - /* Add listeners to newly generated checkboxes */ - document.getElementsByName('taskCompleteCheck').forEach(element=>{ - element.addEventListener('change',(e)=>{ - const id = e.target.attributes['data-taskid'].value; - const checked= e.target.checked; - tasksDispatcher.dispatch(completeTaskAction(id,checked)); - }) - }); - - if (localStorage[`preferences`]) { - document.getElementById('userNameDisplay').innerHTML = JSON.parse(localStorage[`preferences`]).userName; - } -} - -document.forms.newTask.addEventListener('submit',(e)=>{ - e.preventDefault(); - const name = e.target.newTaskName.value; - if (name) { - tasksDispatcher.dispatch(createNewTaskAction(name)); - e.target.newTaskName.value = null; - } -}) - -document.forms.undo.addEventListener('submit',(e)=>{ - e.preventDefault(); - tasksStore.revertLastState(); -}) - -document.getElementById(`showComplete`).addEventListener('change',({target})=>{ - const showComplete = target.checked; - tasksDispatcher.dispatch(showTasksAction(showComplete)); -}) - -tasksStore.addListener(()=>{ - render(); -}) - -render(); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 326fc90..8559520 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,16 +1,4 @@ const path = require('path'); -// function MyPlugin() { -// // Configure your plugin with options... -// } -// -// MyPlugin.prototype.apply = function(compiler) { -// compiler.plugin("compile", function(params) { -// require('./src/server'); -// }); -// }; -// -// module.exports = MyPlugin; -// require('./src/server'); module.exports = { module: { loaders: [ @@ -32,9 +20,7 @@ module.exports = { ] }, entry: { - cpanel: ["./src/control-panel.js"], - "message-board": ["./src/message-board.js"], - tasks: ["./src/tasks.js"] + "cpanel": ["./src/control-panel.js"] }, output: { path: path.resolve(__dirname, "public"), @@ -43,8 +29,4 @@ module.exports = { }, devServer: { inline: true }, devtool: 'source-map', - // plugins: [ - // new MyPlugin() - // ] - } \ No newline at end of file From 88ef9d2a2eab889c06a945f0c464d444d0d7efea Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sat, 17 Dec 2016 11:01:00 -0500 Subject: [PATCH 02/14] Add HTML scaffold --- public/control-panel.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/public/control-panel.html b/public/control-panel.html index 0f74943..3247f0d 100644 --- a/public/control-panel.html +++ b/public/control-panel.html @@ -17,7 +17,20 @@

Control Panel

+

+ Welcome, Jim! +

+
+ +
+
+

+ Font size: +

+ Large
+ Small
+
From 820138d15a3138301f3c6feb9dabe8e5c47e88c1 Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sat, 17 Dec 2016 11:03:32 -0500 Subject: [PATCH 03/14] Implement dispatcher --- src/flux/Dispatcher.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/flux/Dispatcher.js diff --git a/src/flux/Dispatcher.js b/src/flux/Dispatcher.js new file mode 100644 index 0000000..1636b8f --- /dev/null +++ b/src/flux/Dispatcher.js @@ -0,0 +1,11 @@ +export class Dispatcher { + constructor(){ + this.__listeners = []; + } + dispatch(action){ + this.__listeners.forEach(listener=>listener(action)); + } + register(listener){ + this.__listeners.push(listener); + } +} \ No newline at end of file From 15d21c5216bd1dd9799c32dbd31239114d85cc25 Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sat, 17 Dec 2016 11:22:54 -0500 Subject: [PATCH 04/14] Implemented store --- src/control-panel.js | 42 +++++++++++++++++++++++++++++++++++++++++- src/flux/Store.js | 20 ++++++++++++++++++++ src/flux/index.js | 2 ++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/flux/Store.js create mode 100644 src/flux/index.js diff --git a/src/control-panel.js b/src/control-panel.js index 083cdfa..3062049 100644 --- a/src/control-panel.js +++ b/src/control-panel.js @@ -1 +1,41 @@ -console.log(`control-panel.js`); \ No newline at end of file +import { Dispatcher, Store } from './flux'; + +const controlPanelDispatcher = new Dispatcher(); + +export const UPDATE_USERNAME = `UPDATE_USERNAME`; +export const UPDATE_FONT_SIZE_PREFERENCE = `UPDATE_FONT_SIZE_PREFERENCE`; + +document.forms.fontSizeForm.fontSize.forEach(element=>{ + element.addEventListener("change",({target})=>{ + controlPanelDispatcher.dispatch(UPDATE_USERNAME); + }) +}); + +document.getElementById(`userNameInput`).addEventListener("input",({target})=>{ + const name = target.value; + controlPanelDispatcher.dispatch(UPDATE_FONT_SIZE_PREFERENCE); +}); + +class UserPrefsStore extends Store { + getInitialState() { + return { + userName: "Jim", + fontSize: "small" + }; + } + __onDispatch(action){ + console.info(`Received dispatch`, action); + this.__emitChange(); + } + getUserPreferences(){ + return this.__state; + } +} + +const userPrefsStore = new UserPrefsStore(controlPanelDispatcher); + +userPrefsStore.addListener((state)=>{ + console.log(`Updated Store`,state); +}); + + diff --git a/src/flux/Store.js b/src/flux/Store.js new file mode 100644 index 0000000..0f3d25a --- /dev/null +++ b/src/flux/Store.js @@ -0,0 +1,20 @@ +export class Store { + constructor(dispatcher){ + this.__listeners = []; + this.__state = this.getInitialState(); + dispatcher.register(this.__onDispatch.bind(this)); + } + getInitialState(){ + throw new Error("Subclasses must override getInitialState method of a Flux Store"); + } + __onDispatch(){ + throw new Error("Subclasses must override __onDispatch method of a Flux Store"); + } + addListener(listener){ + this.__listeners.push(listener); + } + __emitChange(){ + this.__listeners.forEach(listener=>listener(this.__state)); + } + +} \ No newline at end of file diff --git a/src/flux/index.js b/src/flux/index.js new file mode 100644 index 0000000..d233f75 --- /dev/null +++ b/src/flux/index.js @@ -0,0 +1,2 @@ +export {Dispatcher} from './Dispatcher'; +export {Store} from './Store'; From e2713e53c9093372bc40bd2913863b079667a063 Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sat, 17 Dec 2016 11:32:45 -0500 Subject: [PATCH 05/14] Add actions, storage, render. Complete control panel. --- src/control-panel.js | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/control-panel.js b/src/control-panel.js index 3062049..be3faad 100644 --- a/src/control-panel.js +++ b/src/control-panel.js @@ -5,27 +5,49 @@ const controlPanelDispatcher = new Dispatcher(); export const UPDATE_USERNAME = `UPDATE_USERNAME`; export const UPDATE_FONT_SIZE_PREFERENCE = `UPDATE_FONT_SIZE_PREFERENCE`; +const userNameUpdateAction = (name)=>{ + return { + type: UPDATE_USERNAME, + value: name + } +}; + +const fontSizePreferenceUpdateAction = (size)=>{ + return { + type: UPDATE_FONT_SIZE_PREFERENCE, + value: size + } +}; + document.forms.fontSizeForm.fontSize.forEach(element=>{ element.addEventListener("change",({target})=>{ - controlPanelDispatcher.dispatch(UPDATE_USERNAME); + controlPanelDispatcher.dispatch(fontSizePreferenceUpdateAction(target.value)); }) }); document.getElementById(`userNameInput`).addEventListener("input",({target})=>{ const name = target.value; - controlPanelDispatcher.dispatch(UPDATE_FONT_SIZE_PREFERENCE); + controlPanelDispatcher.dispatch(userNameUpdateAction(name)); }); class UserPrefsStore extends Store { getInitialState() { - return { + return localStorage[`preferences`] ? JSON.parse(localStorage[`preferences`]) : { userName: "Jim", fontSize: "small" }; } __onDispatch(action){ - console.info(`Received dispatch`, action); - this.__emitChange(); + switch(action.type) { + case UPDATE_USERNAME: + this.__state.userName = action.value; + this.__emitChange(); + break; + case UPDATE_FONT_SIZE_PREFERENCE: + this.__state.fontSize = action.value; + this.__emitChange(); + break; + } } getUserPreferences(){ return this.__state; @@ -35,7 +57,15 @@ class UserPrefsStore extends Store { const userPrefsStore = new UserPrefsStore(controlPanelDispatcher); userPrefsStore.addListener((state)=>{ - console.log(`Updated Store`,state); + console.info(`Updated Store`,state); + render(state); + localStorage[`preferences`] = JSON.stringify(state); }); +const render = ({userName,fontSize})=>{ + document.getElementById("userName").innerText = userName; + document.getElementsByClassName("container")[0].style.fontSize = fontSize === "small" ? "16px" : "24px"; + document.forms.fontSizeForm.fontSize.value = fontSize; +} +render(userPrefsStore.getUserPreferences()); From 241236b6c6d7d206680e757cc3d4996900a505f1 Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sat, 17 Dec 2016 12:06:01 -0500 Subject: [PATCH 06/14] Implemented reduce store, minus undo. --- package.json | 4 +- public/tasks.html | 40 ++++++++++++ src/flux/ReduceStore.js | 26 ++++++++ src/flux/index.js | 1 + src/tasks.js | 140 ++++++++++++++++++++++++++++++++++++++++ webpack.config.js | 3 +- 6 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 public/tasks.html create mode 100644 src/flux/ReduceStore.js create mode 100644 src/tasks.js diff --git a/package.json b/package.json index 0206330..81cf203 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "keywords": [], "author": "", "license": "ISC", - "dependencies": { }, + "dependencies": { + "shortid": "^2.2.6" + }, "engine": "node 7.2.0", "devDependencies": { "babel-core": "^6.18.2", diff --git a/public/tasks.html b/public/tasks.html new file mode 100644 index 0000000..debf7ba --- /dev/null +++ b/public/tasks.html @@ -0,0 +1,40 @@ + + + + + Productivity App - Control Panel + + + + + +
+

Tasks

+
+
+
+ + +
+
+ +
+
+ +
+ +
+
+ + + + \ No newline at end of file diff --git a/src/flux/ReduceStore.js b/src/flux/ReduceStore.js new file mode 100644 index 0000000..65b920e --- /dev/null +++ b/src/flux/ReduceStore.js @@ -0,0 +1,26 @@ +import {Store} from './Store'; +export class ReduceStore extends Store { + constructor(dispatcher) { + super(dispatcher); + this.__history = []; + } + + reduce(state, action) { + throw new Error("Subclasses must implement reduce method of a Flux ReduceStore"); + } + + __onDispatch(action) { + const newState = this.reduce(this.__state, action); + if (newState !== this.__state) { + this.__history.push(this.__state); + this.__state = newState; + this.__emitChange(); + } + } + + revertLastState() { + if (this.__history.length > 0) + this.__state = this.__history.pop(); + this.__emitChange(); + } +} \ No newline at end of file diff --git a/src/flux/index.js b/src/flux/index.js index d233f75..cda507c 100644 --- a/src/flux/index.js +++ b/src/flux/index.js @@ -1,2 +1,3 @@ export {Dispatcher} from './Dispatcher'; export {Store} from './Store'; +export {ReduceStore} from './ReduceStore'; diff --git a/src/tasks.js b/src/tasks.js new file mode 100644 index 0000000..e0f1142 --- /dev/null +++ b/src/tasks.js @@ -0,0 +1,140 @@ +import { Dispatcher, ReduceStore } from './flux'; +import { generate as id } from 'shortid'; + + +const tasksDispatcher = new Dispatcher(); + +const CREATE_TASK = `CREATE_TASK`; +const COMPLETE_TASK = `COMPLETE_TASK`; +const SHOW_TASKS = `SHOW_TASKS`; + +const createNewTaskAction = (content)=>{ + return { + type: CREATE_TASK, + value: content + } +}; + +const completeTaskAction = (id,isComplete)=>{ + return { + type: COMPLETE_TASK, + id, + value: isComplete + } +}; + +const showTasksAction = (show)=>{ + return { + type: SHOW_TASKS, + value: show + } +}; + +const initialState = { + tasks: [{ + id: id(), + content: "Update CSS styles", + complete: false + }, { + id: id(), + content: "Add unit tests", + complete: false + }, { + id: id(), + content: "Post to social media", + complete: false + },{ + id: id(), + content: "Install hard drive", + complete: true + }], + showComplete:true +}; + +class TasksStore extends ReduceStore { + getInitialState() { + return initialState; + } + reduce(state,action){ + let newState; + console.log("Processing action",action.type); + switch(action.type) { + case CREATE_TASK: + newState = { ...state, tasks: [ ... state.tasks ]}; + newState.tasks.push({ + id:id(), + content:action.value, + complete: false + }); + return newState; + case COMPLETE_TASK: + newState = { ... state, tasks: [ ... state.tasks ]}; + const affectedElementIndex = newState.tasks.findIndex(t=>t.id === action.id); + newState.tasks[affectedElementIndex] = { ... state.tasks[affectedElementIndex], complete: action.value } + return newState; + case SHOW_TASKS: + newState = { ... state, showComplete: action.value }; + return newState; + } + return state; + } + getState(){ + return this.__state; + } +} + +const tasksStore = new TasksStore(tasksDispatcher); + +const render = () => { + + const TaskComponent = ({content,complete,id})=>( + `
+ ${content} +
` + ) + + const tasksSection = document.getElementById(`tasks`); + const state = tasksStore.getState(); + const rendered = tasksStore.getState().tasks + .filter(task=>state.showComplete ? true : !task.complete) + .map(TaskComponent).join(""); + tasksSection.innerHTML = rendered; + + /* Add listeners to newly generated checkboxes */ + document.getElementsByName('taskCompleteCheck').forEach(element=>{ + element.addEventListener('change',(e)=>{ + const id = e.target.attributes['data-taskid'].value; + const checked= e.target.checked; + tasksDispatcher.dispatch(completeTaskAction(id,checked)); + }) + }); + + // if (localStorage[`preferences`]) { + // document.getElementById('userNameDisplay').innerHTML = JSON.parse(localStorage[`preferences`]).userName; + // } +}; + +document.forms.newTask.addEventListener('submit',(e)=>{ + e.preventDefault(); + const name = e.target.newTaskName.value; + if (name) { + tasksDispatcher.dispatch(createNewTaskAction(name)); + e.target.newTaskName.value = null; + } +}); + +// document.forms.undo.addEventListener('submit',(e)=>{ +// e.preventDefault(); +// tasksStore.revertLastState(); +// }) + +document.getElementById(`showComplete`).addEventListener('change',({target})=>{ + const showComplete = target.checked; + tasksDispatcher.dispatch(showTasksAction(showComplete)); +}); + +tasksStore.addListener(()=>{ + render(); +}); + +render(); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 8559520..c2b0e4b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -20,7 +20,8 @@ module.exports = { ] }, entry: { - "cpanel": ["./src/control-panel.js"] + "cpanel": ["./src/control-panel.js"], + "tasks": ["./src/tasks.js"] }, output: { path: path.resolve(__dirname, "public"), From 0c43a8eef1855ea19709137e1698d9312d6a8ef5 Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sat, 17 Dec 2016 12:19:10 -0500 Subject: [PATCH 07/14] Complete flux application. --- public/control-panel.html | 3 +++ public/index.html | 4 ++++ public/tasks.html | 5 ++++- src/tasks.js | 12 ++++-------- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/public/control-panel.html b/public/control-panel.html index 3247f0d..fe4ea29 100644 --- a/public/control-panel.html +++ b/public/control-panel.html @@ -13,6 +13,9 @@ +
diff --git a/public/index.html b/public/index.html index a0a3ac1..ded70f5 100644 --- a/public/index.html +++ b/public/index.html @@ -15,11 +15,15 @@ +

Welcome to the Flux-Redux Productivity App!

diff --git a/public/tasks.html b/public/tasks.html index debf7ba..71b61f6 100644 --- a/public/tasks.html +++ b/public/tasks.html @@ -10,8 +10,11 @@ diff --git a/src/tasks.js b/src/tasks.js index e0f1142..a7a473c 100644 --- a/src/tasks.js +++ b/src/tasks.js @@ -108,10 +108,6 @@ const render = () => { tasksDispatcher.dispatch(completeTaskAction(id,checked)); }) }); - - // if (localStorage[`preferences`]) { - // document.getElementById('userNameDisplay').innerHTML = JSON.parse(localStorage[`preferences`]).userName; - // } }; document.forms.newTask.addEventListener('submit',(e)=>{ @@ -123,10 +119,10 @@ document.forms.newTask.addEventListener('submit',(e)=>{ } }); -// document.forms.undo.addEventListener('submit',(e)=>{ -// e.preventDefault(); -// tasksStore.revertLastState(); -// }) +document.forms.undo.addEventListener('submit',(e)=>{ + e.preventDefault(); + tasksStore.revertLastState(); +}) document.getElementById(`showComplete`).addEventListener('change',({target})=>{ const showComplete = target.checked; From d1e21e3a41b1eac513a04bb4aaa941963f18e5a4 Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sun, 18 Dec 2016 10:03:41 -0500 Subject: [PATCH 08/14] Scaffold for Redux app complete --- package.json | 1 + webpack.config.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 81cf203..5cdae00 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "author": "", "license": "ISC", "dependencies": { + "redux": "^3.6.0", "shortid": "^2.2.6" }, "engine": "node 7.2.0", diff --git a/webpack.config.js b/webpack.config.js index c2b0e4b..e21da13 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,7 +21,8 @@ module.exports = { }, entry: { "cpanel": ["./src/control-panel.js"], - "tasks": ["./src/tasks.js"] + "tasks": ["./src/tasks.js"], + "message-board": ["./src/message-board.js"] }, output: { path: path.resolve(__dirname, "public"), From e7a66303a01b48ae915660566190c3368ba4a338 Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sun, 18 Dec 2016 10:04:08 -0500 Subject: [PATCH 09/14] Scaffold for Redux app complete --- public/message-board.html | 45 +++++++++++++++++++++++++++++++++++++++ src/message-board.js | 1 + 2 files changed, 46 insertions(+) create mode 100644 public/message-board.html create mode 100644 src/message-board.js diff --git a/public/message-board.html b/public/message-board.html new file mode 100644 index 0000000..865bd78 --- /dev/null +++ b/public/message-board.html @@ -0,0 +1,45 @@ + + + + + Productivity App - Control Panel + + + + + +
+

Message Board

+
+
+
+ +
+
+ + +
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/src/message-board.js b/src/message-board.js new file mode 100644 index 0000000..83f0849 --- /dev/null +++ b/src/message-board.js @@ -0,0 +1 @@ +console.log(`Message board`); \ No newline at end of file From df13c130fd1d0451bfd788648c962a9b32981fe3 Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sun, 18 Dec 2016 10:22:07 -0500 Subject: [PATCH 10/14] Message board implemented with single reducer --- src/message-board.js | 70 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/message-board.js b/src/message-board.js index 83f0849..fca9f0d 100644 --- a/src/message-board.js +++ b/src/message-board.js @@ -1 +1,69 @@ -console.log(`Message board`); \ No newline at end of file +console.log(`Message board`); +import { createStore } from 'redux' + +export const ONLINE = `ONLINE`; +export const AWAY = `AWAY`; +export const BUSY = `BUSY`; +export const UPDATE_STATUS = `UPDATE_STATUS`; +export const OFFLINE = `OFFLINE`; + +const statusUpdateAction = (value)=>{ + return { + type: UPDATE_STATUS, + value + } +} + + + +const defaultState = { + messages:[{ + date:new Date('2016-10-10 10:11:55'), + postedBy:`Stan`, + content:`I <3 the new productivity app!` + },{ + date:new Date('2016-10-10 10:12:00'), + postedBy:`Jerry`, + content:`I don't know if the new version of Bootstrap is really better...` + },{ + date:new Date('2016-10-10 12:06:04'), + postedBy:`Llewlyn`, + content:`Anyone got tickets to ng-conf?` + }], + userStatus: ONLINE, +} + + + +const reducer = (state=defaultState,{type,value})=>{ + switch (type) { + case UPDATE_STATUS: + return {...state, userStatus: value}; + break; + } + return state; +} + +const store = createStore(reducer); + +const render = ()=>{ + const {messages, userStatus, apiCommunicationStatus} = store.getState(); + document.getElementById("messages").innerHTML = messages + .sort((a,b)=>b.date - a.date) + .map(message=>(` +
+ ${message.postedBy} : ${message.content} +
` + )).join(""); + + document.forms.newMessage.newMessage.value = ""; + document.forms.newMessage.fields.disabled = (userStatus === OFFLINE); +} + +document.forms.selectStatus.status.addEventListener("change",(e)=>{ + store.dispatch(statusUpdateAction(e.target.value)); +}) + +render(); + +store.subscribe(render); From 5d3adf702aa9e9b695d90847fde75d2d5dbf2885 Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sun, 18 Dec 2016 10:36:43 -0500 Subject: [PATCH 11/14] Complete synchronous message board component --- src/message-board.js | 45 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/message-board.js b/src/message-board.js index fca9f0d..ee0960a 100644 --- a/src/message-board.js +++ b/src/message-board.js @@ -1,11 +1,13 @@ console.log(`Message board`); -import { createStore } from 'redux' +import { createStore, combineReducers } from 'redux' export const ONLINE = `ONLINE`; export const AWAY = `AWAY`; export const BUSY = `BUSY`; export const UPDATE_STATUS = `UPDATE_STATUS`; export const OFFLINE = `OFFLINE`; +export const CREATE_NEW_MESSAGE = `CREATE_NEW_MESSAGE`; + const statusUpdateAction = (value)=>{ return { @@ -14,6 +16,17 @@ const statusUpdateAction = (value)=>{ } } +const newMessageAction = (content, postedBy)=>{ + const date = new Date(); + + return { + type: CREATE_NEW_MESSAGE, + value: content, + postedBy, + date + } +} + const defaultState = { @@ -35,16 +48,29 @@ const defaultState = { -const reducer = (state=defaultState,{type,value})=>{ +const userStatusReducer = (state = defaultState.userStatus, {type, value}) => { switch (type) { case UPDATE_STATUS: - return {...state, userStatus: value}; - break; + return value; + } + return state; +}; + +const messagesReducer = (state = defaultState.messages, {type,value,postedBy,date})=>{ + switch (type) { + case CREATE_NEW_MESSAGE: + const newState = [ { date: date, postedBy, content: value } , ... state ]; + return newState; } return state; } -const store = createStore(reducer); +const combinedReducer = combineReducers({ + userStatus: userStatusReducer, + messages: messagesReducer +}); + +const store = createStore(combinedReducer); const render = ()=>{ const {messages, userStatus, apiCommunicationStatus} = store.getState(); @@ -62,7 +88,14 @@ const render = ()=>{ document.forms.selectStatus.status.addEventListener("change",(e)=>{ store.dispatch(statusUpdateAction(e.target.value)); -}) +}); + +document.forms.newMessage.addEventListener("submit",(e)=>{ + e.preventDefault(); + const value = e.target.newMessage.value; + const username = localStorage[`preferences`] ? JSON.parse(localStorage[`preferences`]).userName : "Jim"; + store.dispatch(newMessageAction(value, username)); +}); render(); From 38de7b20a296ff6cf6bb8376b7f426de60235162 Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sun, 18 Dec 2016 10:39:42 -0500 Subject: [PATCH 12/14] Update HTML templates --- public/control-panel.html | 3 +++ public/index.html | 5 +++++ public/message-board.html | 7 +++++-- public/tasks.html | 3 +++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/public/control-panel.html b/public/control-panel.html index fe4ea29..2e0d978 100644 --- a/public/control-panel.html +++ b/public/control-panel.html @@ -16,6 +16,9 @@ +
diff --git a/public/index.html b/public/index.html index ded70f5..9cb3a1b 100644 --- a/public/index.html +++ b/public/index.html @@ -18,12 +18,17 @@ + +

Welcome to the Flux-Redux Productivity App!

diff --git a/public/message-board.html b/public/message-board.html index 865bd78..f003cdd 100644 --- a/public/message-board.html +++ b/public/message-board.html @@ -11,11 +11,14 @@ Productivity App
diff --git a/public/tasks.html b/public/tasks.html index 71b61f6..f360f40 100644 --- a/public/tasks.html +++ b/public/tasks.html @@ -16,6 +16,9 @@ +
From eafacd3936382b81f6554f3cacc0d37edc4c094e Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sun, 18 Dec 2016 11:25:07 -0500 Subject: [PATCH 13/14] Application complete --- package.json | 1 + src/http/index.js | 7 +++++++ src/message-board.js | 46 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 src/http/index.js diff --git a/package.json b/package.json index 5cdae00..a44985e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "license": "ISC", "dependencies": { "redux": "^3.6.0", + "redux-logger": "^2.7.4", "shortid": "^2.2.6" }, "engine": "node 7.2.0", diff --git a/src/http/index.js b/src/http/index.js new file mode 100644 index 0000000..599e21b --- /dev/null +++ b/src/http/index.js @@ -0,0 +1,7 @@ +import { generate as id } from 'shortid'; +const asyncAwaitTime = 500; +export const get = (url, cb)=>{ + setTimeout(()=>{ + cb(id()); + },asyncAwaitTime); +} \ No newline at end of file diff --git a/src/message-board.js b/src/message-board.js index ee0960a..d627f12 100644 --- a/src/message-board.js +++ b/src/message-board.js @@ -1,5 +1,8 @@ console.log(`Message board`); -import { createStore, combineReducers } from 'redux' +import { createStore, combineReducers, applyMiddleware } from 'redux' +import { get } from './http'; +import logger from 'redux-logger'; + export const ONLINE = `ONLINE`; export const AWAY = `AWAY`; @@ -7,6 +10,11 @@ export const BUSY = `BUSY`; export const UPDATE_STATUS = `UPDATE_STATUS`; export const OFFLINE = `OFFLINE`; export const CREATE_NEW_MESSAGE = `CREATE_NEW_MESSAGE`; +export const NEW_MESSAGE_SERVER_ACCEPTED = `NEW_MESSAGE_SERVER_ACCEPTED`; +export const READY = `READY`; +export const WAITING = `WAITING`; + + const statusUpdateAction = (value)=>{ @@ -19,6 +27,16 @@ const statusUpdateAction = (value)=>{ const newMessageAction = (content, postedBy)=>{ const date = new Date(); + get('/api/create',(id=>{ + store.dispatch({ + type: NEW_MESSAGE_SERVER_ACCEPTED, + value: content, + postedBy, + date, + id + }) + })); + return { type: CREATE_NEW_MESSAGE, value: content, @@ -44,6 +62,7 @@ const defaultState = { content:`Anyone got tickets to ng-conf?` }], userStatus: ONLINE, + apiCommunicationStatus: READY } @@ -56,6 +75,17 @@ const userStatusReducer = (state = defaultState.userStatus, {type, value}) => { return state; }; +const apiCommunicationStatusReducer = (state = defaultState.apiCommunicationStatus, {type}) => { + switch (type) { + case CREATE_NEW_MESSAGE: + return WAITING; + case NEW_MESSAGE_SERVER_ACCEPTED: + return READY; + } + return state; +} + + const messagesReducer = (state = defaultState.messages, {type,value,postedBy,date})=>{ switch (type) { case CREATE_NEW_MESSAGE: @@ -67,10 +97,14 @@ const messagesReducer = (state = defaultState.messages, {type,value,postedBy,dat const combinedReducer = combineReducers({ userStatus: userStatusReducer, - messages: messagesReducer + messages: messagesReducer, + apiCommunicationStatus: apiCommunicationStatusReducer }); -const store = createStore(combinedReducer); +const store = createStore( + combinedReducer, + applyMiddleware(logger()) +); const render = ()=>{ const {messages, userStatus, apiCommunicationStatus} = store.getState(); @@ -83,7 +117,7 @@ const render = ()=>{ )).join(""); document.forms.newMessage.newMessage.value = ""; - document.forms.newMessage.fields.disabled = (userStatus === OFFLINE); + document.forms.newMessage.fields.disabled = (userStatus === OFFLINE) || (apiCommunicationStatus === WAITING); } document.forms.selectStatus.status.addEventListener("change",(e)=>{ @@ -100,3 +134,7 @@ document.forms.newMessage.addEventListener("submit",(e)=>{ render(); store.subscribe(render); + +get('/test',(id)=>{ + console.log("Tested async...",id); +}) \ No newline at end of file From fffe81cb0fab3d3af2387dc50f2c4b9d7450dcad Mon Sep 17 00:00:00 2001 From: Daniel Stern Date: Sun, 18 Dec 2016 11:25:21 -0500 Subject: [PATCH 14/14] Application complete --- src/message-board.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/message-board.js b/src/message-board.js index d627f12..1c6a8e2 100644 --- a/src/message-board.js +++ b/src/message-board.js @@ -1,4 +1,3 @@ -console.log(`Message board`); import { createStore, combineReducers, applyMiddleware } from 'redux' import { get } from './http'; import logger from 'redux-logger'; @@ -133,8 +132,4 @@ document.forms.newMessage.addEventListener("submit",(e)=>{ render(); -store.subscribe(render); - -get('/test',(id)=>{ - console.log("Tested async...",id); -}) \ No newline at end of file +store.subscribe(render); \ No newline at end of file