From fb68e5bb39519e484c3d59e142d8d2e2dcf8ecfa Mon Sep 17 00:00:00 2001 From: Kimberley Bezuidenhout Date: Sat, 18 Oct 2025 18:06:07 +0000 Subject: [PATCH 1/3] Extend store.js: add registerReducers and dispatch; keep old store methods --- index.js | 3 ++ modules/reducers/index.js | 7 ++++ modules/reducers/settingsRudecer.js | 8 +++++ modules/reducers/userReducer.js | 13 +++++++ modules/store.js | 54 ++++++++++++++++++++++++++--- 5 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 modules/reducers/index.js create mode 100644 modules/reducers/settingsRudecer.js create mode 100644 modules/reducers/userReducer.js diff --git a/index.js b/index.js index c8fca86..11c9068 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,9 @@ import domManager from './modules/domManager'; import cssManager from './modules/cssManager'; import router from './modules/router'; import store from './modules/store'; +import reducers from './modules/reducers/index.js'; + +store.registerReducers(reducers); cssManager.addRule({ body: 'transition: opacity .25s' }); diff --git a/modules/reducers/index.js b/modules/reducers/index.js new file mode 100644 index 0000000..8370817 --- /dev/null +++ b/modules/reducers/index.js @@ -0,0 +1,7 @@ +import userReducer from './userReducer.js'; +import settingsReducer from './settingsReducer.js'; + +export default { + user: userReducer, + settings: settingsReducer, +}; diff --git a/modules/reducers/settingsRudecer.js b/modules/reducers/settingsRudecer.js new file mode 100644 index 0000000..f5ff033 --- /dev/null +++ b/modules/reducers/settingsRudecer.js @@ -0,0 +1,8 @@ +export default function settingsReducer(state = { theme: 'light' }, action) { + switch (action.type) { + case 'SET_THEME': + return { ...state, theme: action.payload }; + default: + return state; + } +} diff --git a/modules/reducers/userReducer.js b/modules/reducers/userReducer.js new file mode 100644 index 0000000..721cd7b --- /dev/null +++ b/modules/reducers/userReducer.js @@ -0,0 +1,13 @@ +export default function userReducer( + state = { name: '', loggedIn: false }, + action, +) { + switch (action.type) { + case 'LOGIN': + return { ...state, loggedIn: true, name: action.payload }; + case 'LOGOUT': + return { ...state, loggedIn: false, name: '' }; + default: + return state; + } +} diff --git a/modules/store.js b/modules/store.js index 83c42f8..299cfff 100644 --- a/modules/store.js +++ b/modules/store.js @@ -1,13 +1,17 @@ /** * The store module provides a central storage mechanism for managing and sharing data across your application. * It allows you to create, retrieve, and update variables within a private store. + * + * NEW: Added Redux-style support with reducers, dispatch, and state. */ + const store = () => { + // ------------------ Private store for backward-compatible API ------------------ const _store = {}; /** - * Creates the initial store by accepting an object with key-value pairs. This function throws an error - * if invoked more than once. + * Creates the initial store by accepting an object with key-value pairs. + * Throws an error if invoked more than once. * * @param {Object} storeObject - An object containing properties and values to be stored in the store. * @@ -17,7 +21,7 @@ const store = () => { const createStore = (storeObject) => { if ( !( - storeObject !== null && + storeObject && typeof storeObject === 'object' && !Array.isArray(storeObject) ) @@ -58,7 +62,49 @@ const store = () => { _store[key] = newValue; }; - return { createStore, getState, updateState }; + // ------------------ Redux-style additions ------------------ + const state = {}; // new Redux-style state container + const reducers = {}; // object to hold registered reducers + + /** + * Registers reducers for the Redux-style store. + * Each reducer must be a function and will be invoked to initialize its slice of state. + * + * @param {Object} reducersObj - Object of reducers in the format { key: reducerFunction } + * + * @throws {Error} If any reducer is not a function. + */ + const registerReducers = (reducersObj) => { + for (const key in reducersObj) { + if (typeof reducersObj[key] !== 'function') { + throw new Error(`Reducer for key '${key}' must be a function`); + } + reducers[key] = reducersObj[key]; + state[key] = reducers[key](undefined, {}); // initialize state for each reducer + } + }; + + /** + * Dispatches an action to all registered reducers. + * Updates the Redux-style state for each reducer based on the action. + * + * @param {Object} action - Action object in the format { type, payload } + */ + const dispatch = (action) => { + for (const key in reducers) { + state[key] = reducers[key](state[key], action); + } + }; + + // ------------------ Return all functions ------------------ + return { + createStore, // legacy store creation + getState, // legacy get value + updateState, // legacy update value + registerReducers, // new Redux-style reducer registration + dispatch, // new Redux-style dispatch + state, // Redux-style state container + }; }; export default store(); From b0d7430528777dae619aa32d6f260e160e3fc3fe Mon Sep 17 00:00:00 2001 From: Kimberley Bezuidenhout Date: Tue, 21 Oct 2025 13:26:05 +0000 Subject: [PATCH 2/3] Add LabelInput widget to combine existing Label and Input into a single container (I hope its correct, coding in the dark. Will install extentions to test the code live) --- modules/widgets/labelInput.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 modules/widgets/labelInput.js diff --git a/modules/widgets/labelInput.js b/modules/widgets/labelInput.js new file mode 100644 index 0000000..4c25808 --- /dev/null +++ b/modules/widgets/labelInput.js @@ -0,0 +1,27 @@ +/** + * LabelInput widget that combines an existing Label and Input into a single container element. + */ + +import { Label } from './label'; +import { Input } from './input'; + +export const LabelInput = ({ label, input, styles = {} }) => { + if (!label || !input) + throw new TypeError( + "LabelInput requires both 'label' and 'input' elements.", + ); + + const defaultStyles = { + display: 'flex', + flexDirection: 'column', + gap: '4px', + }; + + const containerStyles = { ...defaultStyles, ...styles }; + + return { + tagName: 'div', + options: { style: containerStyles }, + children: [label, input], + }; +}; From f463bfcbc62cd13e1e3989adff52aae23463f111 Mon Sep 17 00:00:00 2001 From: Kimberley Bezuidenhout Date: Tue, 21 Oct 2025 18:44:30 +0000 Subject: [PATCH 3/3] Tried my best to fix tutorial errors (broken imports, syntax issues, missing elements, layout bugs) --- TUTORIAL.md | 117 ++++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index 328169f..0e9254e 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -165,29 +165,29 @@ At this point, your `landingPage.js` should look like this: ```javascript const mainContent = () => { const heading = { - tagName: "h1", - text: "Todo App Made With DOM Wizard", + tagName: 'h1', + text: 'Todo App Made With DOM Wizard', options: { style: { - fontSize: "4rem", - paddingTop: "100px", - marginBottom: "30px", + fontSize: '4rem', + paddingTop: '100px', + marginBottom: '30px', }, }, }; const button = { - tagName: "button", - text: "Get Started", + tagName: 'button', + text: 'Get Started', options: { style: { - padding: "10px 20px", - borderRadius: "17px", - fontSize: "1.2rem", - backgroundColor: "purple", - color: "white", - cursor: "pointer", - border: "none", + padding: '10px 20px', + borderRadius: '17px', + fontSize: '1.2rem', + backgroundColor: 'purple', + color: 'white', + cursor: 'pointer', + border: 'none', }, }, }; @@ -196,12 +196,13 @@ const mainContent = () => { children: [heading, button], options: { style: { - display: "flex", - alignItems: "center", - flexDirection: "column", - justifyContent: "center", + display: 'flex', + alignItems: 'center', + flexDirection: 'column', + justifyContent: 'center', }, - }; + }, + }; }; const landingPage = { @@ -237,30 +238,29 @@ Your `header.js` should look like this: ```javascript const leftDiv = { - text: "ToDo", + text: 'ToDo', options: { style: { - fontSize: "1.2rem", + fontSize: '1.2rem', fontWeight: 800, }, - }; + }, +}; const rightDiv = { - text: "DOM Wizard", + text: 'DOM Wizard', }; const header = { - options: - - { - id: "header", + options: { + id: 'header', style: { - padding: "25px 20px", - display: "flex", - alignItems: "center", - justifyContent: "space-between", - backgroundColor: "purple", - color: "white", + padding: '25px 20px', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + backgroundColor: 'purple', + color: 'white', }, }, children: [leftDiv, rightDiv], @@ -289,7 +289,7 @@ Open the browser again, and you'll see the updated version of the landing page w Now, let's create the home page. Create `home.js` in the `routes` directory and add the following code to your `index.js`: -**_src/routes/index.js:_** +**_src/routes/home.js:_** ```javascript import header from '../components/header'; @@ -480,29 +480,29 @@ To make this work, let's start by creating an 'emptyView.' In 'components,' crea ```javascript const emptyView = () => { const heading = { - tagName: "p", + tagName: 'p', options: { - textContent: "No Todos Yet", + textContent: 'No Todos Yet', style: { - fontSize: "3rem", - paddingTop: "100px", - marginBottom: "30px", + fontSize: '3rem', + paddingTop: '100px', + marginBottom: '30px', }, }, }; const button = { - tagName: "button", + tagName: 'button', options: { - textContent: "Create your first todo", + textContent: 'Create your first todo', style: { - padding: "10px 20px", - borderRadius: "17px", - fontSize: "1.2rem", - backgroundColor: "purple", - color: "white", - cursor: "pointer", - border: "none", + padding: '10px 20px', + borderRadius: '17px', + fontSize: '1.2rem', + backgroundColor: 'purple', + color: 'white', + cursor: 'pointer', + border: 'none', }, }, }; @@ -510,14 +510,15 @@ const emptyView = () => { return { children: [heading, button], options: { - className: "empty-view", + className: 'empty-view', style: { - display: "flex", - alignItems: "center", - flexDirection: "column", - justifyContent: "center", + display: 'flex', + alignItems: 'center', + flexDirection: 'column', + justifyContent: 'center', }, - }; + }, + }; }; export default emptyView(); @@ -541,9 +542,7 @@ export default home; Now, if you open the app in your browser and navigate to the home page, you'll see that it looks different now. Let's add 'todos.' We'll use a form and a dialog for that. So, let's get started. In 'components,' create 'dialog.js.' -Start your 'dialog.js' with this code - -: +Start your 'dialog.js' with this code: **_src/components/dialog.js:_** @@ -568,7 +567,7 @@ export default dialog; In 'header,' we'll have an 'x' icon that will close the modal if clicked. Update 'header' like this: -**\*src/components/header.js:\*\*** +**_src/components/dialog.js:_** ```javascript import xLg from 'bootstrap-icons/icons/x-lg.svg'; @@ -775,7 +774,7 @@ cssManager.createCSSRules([ { '.todo': ` display: flex; - justify-content: space between; + justify-content: space-between; gap: 10px; align-items: center; border-bottom: 1px solid;