1.0.8 • Published 10 months ago

pmk-nh-framework v1.0.8

Weekly downloads
-
License
MIT
Repository
-
Last release
10 months ago

Instructions to Test the App

  1. Folder Structure Overview

    • The project contains two main folders:
      • framework: This folder contains the core framework code.
      • example: This folder contains the example application built using the framework.
  2. Navigate to the Example App

    • Open a terminal or command prompt.
    • Navigate to the example folder:

      cd path/to/your/project/example

  3. Install Dependencies

    • Ensure you have npm installed on your machine. If you haven't installed it yet, please do so from Node.js official website.
    • In the example directory, run the following command to install the required dependencies:

      npm install

  4. Ensure Parcel is Installed

    • Parcel is a web application bundler. Make sure it's installed. You can check this by running:
    `npx parcel --version`

-   If Parcel is not installed, you can install it globally using:


    `npm install -g parcel`
  1. Run the Application

    • Start the development server using the following command:

      npm run start

    • This command will launch the application and typically open it in your default web browser. If it doesn't open automatically, you can navigate to http://localhost:1234 (or the port specified in your package.json).

  2. Explore the Main Page

    • Once the application is running, you will see the main page with a menu consisting of:
      • Todo App: This demonstrates the todo app functionality built using the framework.
      • Bonus Part: This section demonstrates the framework's HTTP client capabilities.
      • Lazy Rendering Dropdown: This section shows how lazy rendering is implemented, especially for dropdown lists with a large number of options (e.g., 10,000).
  3. Testing Each Feature

    • Todo App: Click on the "Todo App" link in the menu to interact with the todo application. You can add, modify, and mark tasks as done.
    • Bonus Part: Click on the "Bonus Part" to see how the framework handles HTTP requests, fetching data from an external API or service.
    • Lazy Rendering Dropdown: Click on the "Lazy Rendering Dropdown" to test the dropdown feature that efficiently handles rendering a large number of options (e.g., 10,000). You should notice performance improvements compared to traditional rendering methods.

Additional Notes

  • If you encounter any issues during installation or while running the application, ensure that you have a compatible version of Node.js and npm.
  • Feel free to explore the code in both the framework and example folders to better understand how the application is structured and functions.

By following these instructions, you should be able to successfully test the application built with our framework.

Architecture and Design Principles

Overview

The architecture of the framework is designed to be modular, maintainable, and scalable, providing a clear separation of concerns. Each component and functionality is organized into specific folders, each serving a distinct purpose. This structure enhances code readability, ease of maintenance, and the ability to extend or replace parts of the framework with minimal impact on the rest of the system.

Core Principles

  1. Modularity:

    • The framework is organized into distinct modules, each responsible for a specific aspect of the application. This modular approach allows for better code organization and easier debugging.
    • Components, DOM operations, event handling, and core functionalities are separated into their respective folders, making the codebase more manageable.
  2. Separation of Concerns:

    • Core Functionalities are isolated from UI components and event management. This separation ensures that the core logic (like HTTP requests, routing, and state management) remains independent from the UI rendering and event handling mechanisms.
    • DOM Interactions and Event Handling are treated as separate concerns, ensuring that changes in the UI logic or event system do not affect the core functionalities.
  3. Scalability:

    • The framework is designed to accommodate growth. New features and components can be added with minimal disruption to existing code.
    • By following a clear folder structure, extending the framework with additional functionalities (like new components or utilities) is straightforward.
  4. Maintainability:

    • Clear Folder Structure: Each folder has a specific purpose, making it easy for developers to locate and modify code.
    • Consistent Naming Conventions: Descriptive file and folder names enhance readability and help in quickly understanding the role of each part of the framework.
  5. Reusability:

    • Components and utilities are designed to be reusable across different parts of the application. This encourages the development of modular and composable code.
    • The framework supports adding new features and components without altering the core logic, promoting code reuse.

Folder and File Descriptions

1. Core Folder

  • Purpose: Contains essential modules that form the backbone of the application.
  • Contents:
    • httpClient.js: Manages HTTP requests, providing a unified way to interact with APIs.
    • router.js: Handles routing and navigation, allowing for dynamic page transitions.
    • stateManager.js: Manages global state, facilitating state updates and retrieval across the application.

2. Components Folder

  • Purpose: Houses the UI components and their related logic.
  • Contents:
    • Button.js, Input.js, Modal.js: Defines reusable UI components that can be composed to build the user interface.

3. DOM Folder

  • Purpose: Handles operations related to the Document Object Model (DOM), including creation and rendering.
  • Contents:
    • createElement.js: Provides functionality for creating virtual DOM elements.
    • render.js: Renders virtual DOM elements to the actual DOM.
    • diff.js: Manages the process of updating the DOM by computing differences between virtual DOM states.

4. Event System Folder

  • Purpose: Manages event handling and the lifecycle of the application.
  • Contents:
    • mount.js: Handles the initial rendering of elements and mounting them to the DOM.
    • unmount.js: Manages the removal of elements and cleanup of event listeners.
    • renderApp.js: Orchestrates the rendering process, including updates and initial renders.
    • createEventDelegator.js: Sets up event delegation to handle various events efficiently.

5. Features Folder

  • Purpose: Contains additional features or utilities that enhance the framework's capabilities.
  • Contents:

    • testComponents.js: Provides testing utilities or example components for testing purposes.
    • lazyDropdown.js: Implements specific features such as lazy-loading dropdowns, adding extended functionality to the core framework.

    Installation Instructions for PMK-NH Framework


Prerequisites

Before you begin, ensure you have the following installed:

  • Node.js (version 14 or later)
  • npm (Node Package Manager)

Step 1: Create a New Project

  1. Open your terminal or command prompt.

  2. Create a new directory for your project:

`mkdir my-app
cd my-app`
  1. Initialize a new npm project:

    npm init -y

Step 2: Install the PMK-NH Framework

You can install the PMK-NH framework directly from npm:

npm i pmk-nh-framework

Step 3: Set Up Your Project Structure

Create the necessary files for your application:

mkdir src touch src/index.html src/main.js

Step 4: Update Your index.html

Add the following code to your index.html file:

`

Step 5: Set Up Your main.js

In your main.js, import and configure the framework:

`import { setRootComponent } from "pmk-nh-framework/src/eventSysytem/renderApp"; import { createElement } from 'pmk-nh-framework/src/dom/createElem' import { renderApp } from 'pmk-nh-framework/src/eventSysytem/renderApp'

function App({ path, queryParams }) { return createElement('div', {

        // Main Content
        createElement('div', {
            children: [
                createElement('h1', {
                    attrs: {
                        props: { id: 'header', class: 'class' }
                    },
                    children: ['My App'] 
                })
            ]
        })
    
});

}

setRootComponent(App); //re-render on state update(for example new item added to todo list) store.subscribe(() => { renderApp(); // Ensure the current route is re-rendered router.handleRouteChange(); }); // Trigger the first render renderApp(); `

Step 6: Run Your Application

  1. Use Parcel to serve your application. You may need to install Parcel globally if you haven't done so:
`npm install -g parcel-bundler`
  1. Now, run your application:

`parcel src/index.html`
  1. Open your browser and navigate to http://localhost:1234 to see your application in action!

Additional Notes

  • For production builds, you can use:

`parcel build src/index.html`   

Features and Code Examples

1. Component System

Description: The component system allows you to define reusable UI elements. Components encapsulate their own structure, styling, and behavior, making it easy to compose and manage the user interface.

Usage:

Button({ id: 'my-button', onClick: () => alert('Button clicked!'), children: 'Click Me' });

2. DOM Management

Description: The DOM management module handles creating, rendering, and updating DOM elements. It uses a virtual DOM approach to efficiently update the UI.

Usage:

createElement('div', { attrs: { props: { id: 'my-div' } }, children: [ createElement('p', { children: ['Hello, World!'] }) ] });

3. Event System

Description: The event system manages event delegation, allowing events to be handled efficiently. It attaches event listeners to the root element and delegates events to appropriate handlers.

Usage:

createEventDelegator(document.getElementById('app'));

4. HTTP Client

Description: The HTTP client handles making HTTP requests and interacting with APIs. It abstracts away the complexities of network operations.

Usage:

HttpClient.get('https://api.example.com/data') .then(data => console.log(data)) .catch(error => console.error('Error:', error));

5. Router

Description: The router manages navigation and URL handling, enabling single-page application (SPA) routing.

Usage:

router.navigate('/home'); // Navigate to the home page

6. State Manager

Description: The state manager handles global state for the application, allowing state to be updated and accessed throughout the app.

Usage:

store.setState({ count: store.getState().count + 1 });

7. Testing Components

Description: Testing components ensures that UI elements and their behavior work as expected. The testComponents.js file can contain mock components or testing utilities.

Usage:

MockButton({ onClick: () => console.log('Mock button clicked!') });

8. Additional Features

Description: Additional features such as lazy-loading dropdowns can be included as part of the extended functionality of the framework.

Usage:

LazyDropdown({ options: ['Option 1', 'Option 2', 'Option 3'] });

Building Applications with framework: Best Practices

Welcome to the official guide for building applications with framework! This guide provides best practices for developers to follow when using the framework. These practices ensure your app is efficient, scalable, and easy to maintain.

1. Organize Code for Scalability

When building large applications, organizing your code is key. Structure your project in a way that separates concerns, making it easier to manage and scale.

Suggested Project Structure:

/src /components # Reusable components (e.g., Button, Input) /pages # Page-level components (e.g., TodoComponent, HomePage) /store # State management files (if the framework uses a store) /services # API handlers or utility functions (e.g., HttpClient) /core # Core framework logic like routing, rendering /assets # Static assets like images, fonts, etc.

  • Components should be reusable and stateless wherever possible.
  • Page Components (e.g., TodoComponent, HomePage) handle layout and high-level logic, while child components (like buttons and forms) should handle specific tasks.

2. Manage Global State Efficiently

Framework provides a global store to manage application state. Be mindful of how state updates are handled to avoid unnecessary re-renders and performance issues.

Best Practices for State Management:

  • Minimize State Updates: Only update the state when absolutely necessary. Frequent updates can lead to unnecessary re-renders.
  • Immutable Updates: Ensure that you update the state immutably to avoid unwanted side effects. For example, use array methods like .map() or .filter() rather than directly modifying state arrays.
  • Selective Re-Renders: Subscribe to specific slices of the state when rendering components. Avoid re-rendering the entire app on every state change.

Example:

store.subscribe(() => { const { todo } = store.getState(); // Re-render only the Todo component when the todo list changes if (todo !== previousState.todo) { renderTodoComponent(); } });

3. Component Design and Reusability

Design components to be reusable and flexible. This allows you to maintain consistency across your app and reduces duplication.

Component Design Guidelines:

  • Props-Driven: Components should accept props to dynamically change their behavior or appearance. This helps make them adaptable.
  • Stateless Components: When possible, make components stateless. They should only focus on rendering UI and rely on external props or global state for their data.
  • Event Handling: Delegate event handling logic to the parent components where possible. For instance, a button component should call a passed-in event handler rather than handling the logic directly inside it.

Example Reusable Button Component:

function Button({ id, onClick, children }) { return createElement('button', { attrs: { props: { id, class: 'btn' }, events: { onClick } }, children: [children] }); }

4. Optimize Routing for Performance

Framework's router provides a way to map routes to specific components. To keep routing efficient and user-friendly:

  • Use Lazy Loading for Routes: Load components dynamically only when they are needed. This reduces the initial load time and makes the app feel faster.
  • 404 Handling: Always include a NotFoundComponent to handle invalid routes. This improves the user experience and ensures the app behaves gracefully when navigating to non-existent pages.

Example Router Setup:

`const routes = { '/': HomePage, '/todo': TodoComponent, '/items/:id': ItemsComponent, '/404': NotFoundComponent };

const router = new Router(routes);`

5. Lazy Loading and Virtualization for Large Lists

Rendering large datasets can be a performance bottleneck. When working with large lists (e.g., a dropdown with thousands of options), use lazy loading or virtualization to load only visible items.

Lazy Loading Example:

`function LazyDropdown({ options, startIndex = 0, endIndex = 20 }) { const visibleOptions = options.slice(startIndex, endIndex);

const loadMore = () => { store.setState({ startIndex: endIndex, endIndex: endIndex + 20 }); renderApp(); };

return createElement('div', { children: [ ...visibleOptions.map(option => createElement('p', { children: option })), Button({ id: 'load-more', onClick: loadMore, children: 'Load More' }) ] }); }`

6. Handle Side Effects Safely (e.g., API Requests)

When performing side effects such as HTTP requests (like fetching data from an API), make sure you handle errors and edge cases properly.

Guidelines for Side Effects:

  • Error Handling: Always wrap API requests in try-catch blocks and update the UI to reflect any errors that occur.
  • Debouncing API Calls: Avoid triggering multiple requests at once by implementing debouncing or throttling when necessary.
  • State Updates: When the response is received, update the state in an immutable manner.

Example of HTTP Request Handling:

const fetchCatFact = async () => { try { const data = await HttpClient.get('https://catfact.ninja/fact'); if (data && data.fact) { store.setState({ fact: data.fact }); } else { store.setState({ fact: 'Failed to fetch fact.' }); } } catch (error) { store.setState({ fact: 'Error fetching cat fact.' }); } };

7. Use Event Delegation

For performance optimization, especially in list components, it's better to use event delegation rather than attaching event listeners to every item.

Example of Event Delegation:

document.getElementById('todo-list').addEventListener('click', (event) => { if (event.target.matches('.todo-item')) { const itemId = event.target.dataset.id; // Handle item click } });

8. Optimize for Re-rendering

Avoid re-rendering the entire app when only a small part of the state changes. Use a strategy to selectively re-render only the components that need updating.

Example of Optimized Re-Rendering:

store.subscribe(() => { const { todos, selectedTodoId } = store.getState(); if (todos !== previousState.todos) { renderTodoList(); // Only re-render the todo list } if (selectedTodoId !== previousState.selectedTodoId) { renderTodoDetails(); // Only re-render the selected todo details } });

9. Error Boundaries

In a production application, unexpected errors might happen. Use error boundaries to catch errors in the UI and prevent them from crashing the whole app.

Basic Error Handling Pattern:

`function ErrorBoundary({ children }) { try { return children; } catch (error) { return createElement('div', { children: 'Something went wrong.' }); } }

// Use error boundary ErrorBoundary({ children: createElement('TodoComponent'), createElement('CatFactComponent') });`

10. Write Tests and Document Your Components

Testing is essential to ensure your app behaves as expected. Write unit tests for your components, particularly reusable ones, and document their expected behavior.

  • Document expected inputs/outputs: Use comments or documentation tools to explain what props a component expects and how it behaves.
  • Test for edge cases: Ensure components handle different states properly, like empty inputs or invalid data.
  • Snapshot testing: Use snapshot testing to ensure components render the same output when given the same inputs.

Example Component Documentation:

`/**

  • Button Component - A reusable button component.
  • @param {string} id - The ID of the button.
  • @param {Function} onClick - The function to call when the button is clicked.
  • @param {string} children - The text or elements to render inside the button. */ function Button({ id, onClick, children }) { ... }`

By following these best practices, you'll be able to build maintainable, performant, and scalable applications using framework. Happy coding!

Getting Started with framework

Welcome to the Getting Started Guide for framework. This guide will walk you through the initial setup, the basic structure of your application, and how to build a simple app using the framework. We'll use a Todo App as an example to demonstrate the basic features.

1. Prerequisites

Before getting started, make sure you have the following installed on your system:

  • Node.js (v14 or higher)
  • npm (or yarn)

2. Set Up Your Project

Start by creating a directory for your new project:

mkdir todo-app cd todo-app

Next, initialize a new Node.js project:

npm init -y

3. Set Up the HTML File

The HTML file will serve as the entry point for your app. The framework will attach the main app component to an app div.

Create an index.html file inside the todo-app directory:

`

<script src="./main.js"></script>

4. Set Up the App Structure

Create a src directory inside your project for organizing your code:

mkdir src

Inside the src folder, create the following files:

touch src/main.js src/store.js src/components.js src/router.js

5. Set Up the Store

The store will manage the application's state. In this simple Todo app, the store will keep track of the todos and other app-related data.

Inside src/store.js, define your initial state and store:

`// src/store.js let state = { todo: [], fact: "", // Cat fact for external API demo startIndex: 0, // Lazy dropdown endIndex: 20 // Lazy dropdown };

// Store initialization import {store} from "../framework/src/core/stateManager" import { initializeStore } from "../framework/src/core/stateManager"

initializeStore(state);`

6. Set Up the Router

The router will handle navigating between different pages in your app. Define your routes in src/router.js:

`// src/router.js import { App } from './components'; import { TodoComponent } from './components'; import { ItemsComponent } from './components'; import { CatFactComponent } from './components'; import { NotFoundComponent } from './components'; import { TestComponent } from './components'; import { Router } from "../framework/src/core/router";

export const routes = { '/': App, '/todo': TodoComponent, '/items/:id': ItemsComponent, '/404': NotFoundComponent, '/cats': CatFactComponent, '/test': TestComponent };

// Initialize the router const router = new Router(routes); `

7. Create Components

Now, let's define some components. You'll create a few different components like the App, TodoComponent, and CatFactComponent.

Inside src/components.js, define these components:

`// src/components.js import { store } from './store'; import { router } from './router';

// Main App component (Home Page) export function App() { return createElement('div', { attrs: { props: { id: 'main-app' } }, children: [ createElement('h1', { children: 'Welcome to the Todo App' }), Button({ id: 'todo-button', onClick: () => router.navigate('/todo'), children: 'Go to Todo List' }), Button({ id: 'cat-fact-button', onClick: () => router.navigate('/cats'), children: 'Get Cat Facts' }) ] }); }

// Todo Page Component export function TodoComponent() { const { todo } = store.getState(); let localNewTodo = '';

const handleInputChange = (event) => {
    localNewTodo = event.target.value;
};

const handleSubmit = (event) => {
    event.preventDefault();
    if (localNewTodo.trim()) {
        store.setState({ todo: [...todo, { text: localNewTodo, done: false }] });
    }
};

return createElement('div', {
    children: [
        createElement('h2', { children: ['Todo List'] }),
        createElement('form', {
            events: { onSubmit: handleSubmit },
            children: [
                createElement('input', {
                    attrs: { props: { placeholder: 'New todo' } },
                    events: { onInput: handleInputChange }
                }),
                createElement('button', { children: ['Add Todo'] })
            ]
        }),
        createElement('ul', {
            children: todo.map((item, index) =>
                createElement('li', { children: [item.text] })
            )
        })
    ]
});

}

// Cat Fact Component export async function CatFactComponent() { const { fact } = store.getState();

const fetchCatFact = async () => {
    const response = await fetch('https://catfact.ninja/fact');
    const data = await response.json();
    store.setState({ fact: data.fact });
};

return createElement('div', {
    children: [
        createElement('h2', { children: ['Cat Fact'] }),
        createElement('p', { children: [fact || 'No fact yet'] }),
        Button({
            id: 'fetch-cat-fact',
            onClick: fetchCatFact,
            children: 'Get Random Cat Fact'
        })
    ]
});

}

`

8. Bootstrap the App

Finally, let's create the main.js file that will initialize the app and start the router.

javascript

Copy code

`// src/main.js import { store } from './store'; import { routes, Router } from './router';

// Initialize the router const router = new Router(routes);

// Re-render on state updates store.subscribe(() => { renderApp(); // Ensure the current route is re-rendered router.handleRouteChange(); }); // Trigger the first render renderApp();`

9. Run the App

Now, open your terminal, navigate to your project directory, and run a local development server. You can use live-server or any other tool that serves your index.html file.

If you don't have a local server installed, install live-server globally and run it:

npm install -g live-server live-server

Once the server is running, open http://localhost:8080 in your browser. You should see the Todo App home page, and you can navigate to the Todo List or Cat Facts pages.


10. Customizing the App

Now that your app is up and running, feel free to customize it:

  • Add more pages by defining more routes and components.
  • Improve UI by adding more styles.
  • Integrate additional APIs as demonstrated in the Cat Facts component.

Happy coding!

1.0.8

10 months ago

1.0.7

10 months ago

1.0.6

10 months ago

1.0.5

10 months ago

1.0.4

10 months ago

1.0.3

10 months ago

1.0.2

10 months ago

1.0.1

10 months ago

1.0.0

10 months ago