Skip to content

Instantly share code, notes, and snippets.

@savage69kr
Forked from faustinoaq/myAngular.html
Created October 31, 2024 11:29
Show Gist options
  • Save savage69kr/6e75e64848199e15155ec571e40bd3e3 to your computer and use it in GitHub Desktop.
Save savage69kr/6e75e64848199e15155ec571e40bd3e3 to your computer and use it in GitHub Desktop.

Revisions

  1. @faustinoaq faustinoaq revised this gist Oct 31, 2024. 2 changed files with 4 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions myReact.html
    Original file line number Diff line number Diff line change
    @@ -3,6 +3,8 @@

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My React from Scratch</title>
    </head>

    <body>
    2 changes: 2 additions & 0 deletions myVue.html
    Original file line number Diff line number Diff line change
    @@ -3,6 +3,8 @@

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Vue from Scratch</title>
    </head>

    <body>
  2. @faustinoaq faustinoaq created this gist Oct 31, 2024.
    144 changes: 144 additions & 0 deletions myAngular.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,144 @@
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Angular from Scratch</title>
    <style>
    .my-component {
    font-family: Arial, sans-serif;
    text-align: center;
    padding: 50px;
    background: #f0f0f0;
    }

    .my-component .container {
    background: white;
    padding: 20px;
    border-radius: 10px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }

    .my-component .message {
    font-size: 24px;
    margin-bottom: 20px;
    }

    .my-component .buttons button {
    font-size: 16px;
    padding: 10px 20px;
    margin: 5px;
    cursor: pointer;
    border: none;
    border-radius: 5px;
    transition: background 0.3s;
    }

    .my-component .buttons button:hover {
    background: #ddd;
    }

    .my-component .buttons button:active {
    background: #ccc;
    }
    </style>
    </head>

    <body>

    <div id="app">
    <div class="my-component">
    <div class="container">
    <p class="message">Count: <span ng-bind="count"></span></p>
    <div class="buttons">
    <button ng-click="increment">Increment</button>
    <button ng-click="decrement">Decrease</button>
    </div>
    </div>
    </div>
    </div>

    <script>
    // Reactive system to track changes
    function reactive(data) {
    const listeners = [];

    const proxy = new Proxy(data, {
    set(target, property, value) {
    target[property] = value;
    listeners.forEach(listener => listener()); // Notify all listeners on data change
    return true;
    }
    });

    proxy.subscribe = function (listener) {
    listeners.push(listener);
    };

    return proxy;
    }

    // Our basic Angular-like app system
    function myAngularApp(rootElement, controller) {
    const data = reactive(controller.data());

    // Bind methods to data
    for (const key in controller.methods) {
    data[key] = function () {
    controller.methods[key].call(data); // Call the controller method in context of data
    data.notify(); // Trigger re-render
    };
    }

    // Notify function to re-render on changes
    data.notify = function () {
    compile(rootElement);
    };

    function compile(element) {
    const bindElements = element.querySelectorAll('[ng-bind]');
    const clickElements = element.querySelectorAll('[ng-click]');

    // Set text content for bound elements
    bindElements.forEach(el => {
    const property = el.getAttribute('ng-bind');
    el.textContent = data[property];
    });

    // Set up click handlers for elements with ng-click
    clickElements.forEach(el => {
    const methodName = el.getAttribute('ng-click');
    el.onclick = data[methodName]; // Assign the method directly to onclick
    });
    }

    // Initial compilation
    compile(rootElement);
    data.subscribe(() => compile(rootElement)); // Subscribe compile function to re-render on data change
    }

    // Define controller with data and methods
    const MyController = {
    data() {
    return { count: 0 };
    },
    methods: {
    increment() {
    this.count++;
    },
    decrement() {
    this.count--;
    }
    }
    };

    // Initialize the app
    document.addEventListener('DOMContentLoaded', function () {
    const rootElement = document.getElementById('app');
    myAngularApp(rootElement, MyController);
    });
    </script>
    </body>

    </html>
    264 changes: 264 additions & 0 deletions myReact.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,264 @@
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    </head>

    <body>
    <script>
    // Stateful logic with hooks
    let currentComponent = null;

    function useState(initialValue) {
    if (!currentComponent) {
    throw new Error('useState must be called within a component');
    }

    const stateIndex = currentComponent.stateIndex;
    if (!currentComponent.state[stateIndex]) {
    currentComponent.state[stateIndex] = [
    initialValue,
    (value) => {
    currentComponent.state[stateIndex][0] = value;
    currentComponent.render();
    },
    ];
    }

    const stateTuple = currentComponent.state[stateIndex];
    currentComponent.stateIndex++;

    return [stateTuple[0], stateTuple[1]];
    }

    function createComponent(renderFn) {
    return function Component() {
    currentComponent = {
    state: [],
    stateIndex: 0,
    renderFn: renderFn,
    render: function () {
    this.stateIndex = 0; // Reset index on each render
    const newVNode = this.renderFn();
    const rootElement = document.getElementById('root') || document.body;

    if (!this.vnode) {
    // Initial render
    this.vnode = newVNode;
    rootElement.appendChild(createElement(newVNode));
    } else {
    const patches = diff(this.vnode, newVNode);
    patch(rootElement, patches);
    this.vnode = newVNode;
    }
    },
    };

    currentComponent.render();
    return currentComponent;
    };
    }

    function h(tag, props, ...children) {
    return { tag, props, children };
    }

    function createElement(vnode) {
    if (typeof vnode === 'string') {
    return document.createTextNode(vnode);
    }

    const { tag, props, children } = vnode;
    const element = document.createElement(tag);

    for (let key in props) {
    element[key] = props[key];
    }

    children.forEach(child => element.appendChild(createElement(child)));

    return element;
    }

    function diff(oldVNode, newVNode) {
    if (!oldVNode) {
    return { type: 'CREATE', newVNode };
    }
    if (!newVNode) {
    return { type: 'REMOVE' };
    }
    if (typeof oldVNode !== typeof newVNode || oldVNode.tag !== newVNode.tag) {
    return { type: 'REPLACE', newVNode };
    }
    if (typeof newVNode === 'string') {
    if (oldVNode !== newVNode) {
    return { type: 'TEXT', newVNode };
    } else {
    return null;
    }
    }

    const patch = {
    type: 'UPDATE',
    props: diffProps(oldVNode.props, newVNode.props),
    children: diffChildren(oldVNode.children, newVNode.children),
    };
    return patch;
    }

    function diffProps(oldProps, newProps) {
    const patches = [];

    for (let key in newProps) {
    if (newProps[key] !== oldProps[key]) {
    patches.push({ key, value: newProps[key] });
    }
    }

    for (let key in oldProps) {
    if (!(key in newProps)) {
    patches.push({ key, value: undefined });
    }
    }

    return patches;
    }

    function diffChildren(oldChildren, newChildren) {
    const patches = [];
    const maxLen = Math.max(oldChildren.length, newChildren.length);

    for (let i = 0; i < maxLen; i++) {
    patches.push(diff(oldChildren[i], newChildren[i]));
    }

    return patches;
    }

    function patch(parent, patchObj, index = 0) {
    if (!patchObj) return;

    const el = parent.childNodes[index];

    switch (patchObj.type) {
    case 'CREATE': {
    const newEl = createElement(patchObj.newVNode);
    parent.appendChild(newEl);
    break;
    }
    case 'REMOVE': {
    if (el) {
    parent.removeChild(el);
    }
    break;
    }
    case 'REPLACE': {
    const newEl = createElement(patchObj.newVNode);
    if (el) {
    parent.replaceChild(newEl, el);
    } else {
    parent.appendChild(newEl);
    }
    break;
    }
    case 'TEXT': {
    if (el) {
    el.textContent = patchObj.newVNode;
    }
    break;
    }
    case 'UPDATE': {
    if (el) {
    const { props, children } = patchObj;

    props.forEach(({ key, value }) => {
    if (value === undefined) {
    el.removeAttribute(key);
    } else {
    el[key] = value;
    }
    });

    children.forEach((childPatch, i) => {
    patch(el, childPatch, i);
    });
    }
    break;
    }
    }
    }

    const MyComponent = createComponent(function () {
    const [count, setCount] = useState(0);

    function increment() {
    setCount(count + 1);
    }

    function decrement() {
    setCount(count - 1);
    }

    return h('div', { className: 'my-component' },
    h('div', { className: 'container' },
    h('p', { className: 'message' }, `Count: ${count}`),
    h('div', { className: 'buttons' },
    h('button', { onclick: () => increment() }, 'Increment'),
    h('button', { onclick: () => decrement() }, 'Decrease')
    )
    )
    );
    });

    // Create an initial root element
    const root = document.createElement('div');
    root.id = 'root';
    document.body.appendChild(root);

    // Initialize App
    const App = MyComponent();
    App.render();

    // Add CSS styling scoped to the component
    const style = document.createElement('style');
    style.textContent = `
    .my-component {
    font-family: Arial, sans-serif;
    text-align: center;
    padding: 50px;
    background: #f0f0f0;
    }
    .my-component .container {
    background: white;
    padding: 20px;
    border-radius: 10px;
    box-shadow: 0 0 10px rgba(0,0,0,0.1);
    }
    .my-component .message {
    font-size: 24px;
    margin-bottom: 20px;
    }
    .my-component .buttons button {
    font-size: 16px;
    padding: 10px 20px;
    margin: 5px;
    cursor: pointer;
    border: none;
    border-radius: 5px;
    transition: background 0.3s;
    }
    .my-component .buttons button:hover {
    background: #ddd;
    }
    .my-component .buttons button:active {
    background: #ccc;
    }
    `;

    document.head.appendChild(style);

    </script>
    </body>

    </html>
    157 changes: 157 additions & 0 deletions myVue.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,157 @@
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    </head>

    <body>
    <script>
    function reactive(obj) {
    const listeners = new Set();

    const proxy = new Proxy(obj, {
    get(target, property, receiver) {
    if (typeof target[property] === 'object' && target[property] !== null) {
    return reactive(target[property]);
    }
    return Reflect.get(target, property, receiver);
    },
    set(target, property, value, receiver) {
    const result = Reflect.set(target, property, value, receiver);
    listeners.forEach(fn => fn());
    return result;
    }
    });

    proxy.subscribe = function (fn) {
    listeners.add(fn);
    };

    proxy.unsubscribe = function (fn) {
    listeners.delete(fn);
    };

    return proxy;
    }

    class Component {
    constructor(options) {
    this.template = options.template;
    this.data = reactive(options.data());
    this.methods = options.methods;
    this.style = options.style;
    this.rootId = options.rootId;

    // Ensure root element exists
    if (!document.getElementById(this.rootId)) {
    const rootElement = document.createElement('div');
    rootElement.id = this.rootId;
    document.body.appendChild(rootElement);
    }

    this.data.subscribe(this.render.bind(this));
    this.render();
    }

    compileTemplate(template) {
    const match = template.match(/{{\s*(\w+)\s*}}/g);
    return () => {
    let compiledTemplate = template;
    if (match) {
    match.forEach(item => {
    const key = item.replace(/{{\s*|\s*}}/g, '');
    compiledTemplate = compiledTemplate.replace(item, this.data[key]);
    });
    }
    return compiledTemplate;
    };
    }

    render() {
    const el = document.getElementById(this.rootId);
    if (el) {
    el.innerHTML = this.compileTemplate(this.template)();
    this.applyMethods(el);
    }
    }

    applyMethods(el) {
    Object.keys(this.methods).forEach(methodName => {
    const matches = el.querySelectorAll(`[data-action="${methodName}"]`);
    matches.forEach(match => {
    match.onclick = this.methods[methodName].bind(this.data);
    });
    });
    }
    }

    const MyComponent = new Component({
    template: `
    <div class="my-component">
    <div class="container">
    <p class="message">Count: {{ count }}</p>
    <div class="buttons">
    <button data-action="increment">Increment</button>
    <button data-action="decrement">Decrease</button>
    </div>
    </div>
    </div>
    `,
    data() {
    return {
    count: 0,
    };
    },
    methods: {
    increment() {
    this.count += 1;
    },
    decrement() {
    this.count -= 1;
    },
    },
    style: `
    .my-component {
    font-family: Arial, sans-serif;
    text-align: center;
    padding: 50px;
    background: #f0f0f0;
    }
    .my-component .container {
    background: white;
    padding: 20px;
    border-radius: 10px;
    box-shadow: 0 0 10px rgba(0,0,0,0.1);
    }
    .my-component .message {
    font-size: 24px;
    margin-bottom: 20px;
    }
    .my-component .buttons button {
    font-size: 16px;
    padding: 10px 20px;
    margin: 5px;
    cursor: pointer;
    border: none;
    border-radius: 5px;
    transition: background 0.3s;
    }
    .my-component .buttons button:hover {
    background: #ddd;
    }
    .my-component .buttons button:active {
    background: #ccc;
    }
    `,
    rootId: 'root' // Specify the ID for the root element
    });

    // Ensure CSS is applied to the component
    const style = document.createElement('style');
    style.textContent = MyComponent.style;
    document.head.appendChild(style);
    </script>
    </body>

    </html>