Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 58 additions & 59 deletions TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},
};
Expand All @@ -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 = {
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -480,44 +480,45 @@ 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',
},
},
};

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();
Expand All @@ -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:_**

Expand All @@ -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';
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' });

Expand Down
7 changes: 7 additions & 0 deletions modules/reducers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import userReducer from './userReducer.js';
import settingsReducer from './settingsReducer.js';

export default {
user: userReducer,
settings: settingsReducer,
};
8 changes: 8 additions & 0 deletions modules/reducers/settingsRudecer.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
13 changes: 13 additions & 0 deletions modules/reducers/userReducer.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
54 changes: 50 additions & 4 deletions modules/store.js
Original file line number Diff line number Diff line change
@@ -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.
*
Expand All @@ -17,7 +21,7 @@ const store = () => {
const createStore = (storeObject) => {
if (
!(
storeObject !== null &&
storeObject &&
typeof storeObject === 'object' &&
!Array.isArray(storeObject)
)
Expand Down Expand Up @@ -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();
27 changes: 27 additions & 0 deletions modules/widgets/labelInput.js
Original file line number Diff line number Diff line change
@@ -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],
};
};