Skip to content

Instantly share code, notes, and snippets.

@doeixd
Last active January 22, 2024 20:36
Show Gist options
  • Save doeixd/bd70a6dac0b981d505e7f360532b0bdb to your computer and use it in GitHub Desktop.
Save doeixd/bd70a6dac0b981d505e7f360532b0bdb to your computer and use it in GitHub Desktop.
framework.js
import { createContext as unCreateContext } from 'unctx'
import { createFallback, isString } from './utilities.mjs'
// Setup
export function init() {
window.__registry = {
initial_component_contexts: {},
component_instances: {}
}
ensureRunAfterDOM(_ => {
const everyElement = [...document.querySelectorAll('*')]
for (let element of everyElement) {
addFrameworkListenersToElement(element)
}
createObserver((mutations) => {
const addedElements = getAddedElements(mutations)
for (let addedElement of addedElements) {
addFrameworkListenersToElement(addedElement)
}
}, {childList: true, subtree: true})(document)
})
}
export function getContext () {
return {
context: {},
args: {}
}
}
export function initialComponentContexts() {
return window?.__registry?.context?.initial_component_contexts
}
export function getInstance(name, key) {
let isNameAndKey = isString(name) && isString(key)
if (isNameAndKey) return window?.__registry?.component_instances?.[name]?.[key]
if (name) return window?.__registry?.component_instances?.[name]
return window?.__registry?.component_instances
}
export function createInitialContextObject({name, type = 'single', data}) {
return {
...data,
state: {},
props: {},
events: {},
roles: {},
mount: [],
name,
parent: undefined,
components: [],
type,
condition: (element) => {
return element.tagName.toLowerCase() == name.toLowerCase()
},
}
}
export function createComponent(name, constructor, data) {
const ctx = unCreateContext(name)
getContext = ctx.use
let componentContext = createInitialContextObject({name, data})
function call(args = {}, fn) {
let context = {
context: componentContext,
args,
ctx,
call
}
// ctx.set(context, true)
return ctx.call(context, fn)
}
call({}, constructor)
getContext().__registry
}
export function component(keyNameOrFn, key) {
let component_context = getContext()?.context
let createReturnValue = (_instance = instances) => createFallback(_instance, _instance?.element || {})
const isNameAndKey = isString(keyNameOrFn) && isString(key)
const isFn = keyNameOrFn instanceof Function
// const isKey = isString(keyNameOrFn) && !key
if (isNameAndKey) {
const instance = getInstance(keyNameOrFn, key)
return createReturnValue(instance)
}
if (isFn) {
let instances = Object.entries(window?.__registry?.component_instances)
let toReturn = []
for (let instance of instances) {
if(keyNameOrFn?.(instance)) {
toReturn.push(createReturnValue(instance))
}
}
}
let name = component_context?.name || (isString(keyNameOrFn) ? keyNameOrFn : undefined)
let type = component_context?.type || 'single'
let instances = getInstance(name)
let initial_component_contexts = initialComponentContexts()
let is_single = type.toLowerCase().includes('single')
if (is_single) return createReturnValue()
return instances.map(createReturnValue)
}
export function find() {
}
export function condition (cb) {
let context = getContext()?.context
context['condition'] = cb
}
export function key (cb) {
let context = getContext()?.context
context['key'] = cb
}
export function mount (cb) {
let context = getContext()?.context
context['mount'] ||= []
context['mount'].push(cb)
}
export function prop(key, value) {
let context = getContext()?.context
context[key] = value
}
export function on (condition, cb) {
let context = getContext()?.context
context['events'] ||= []
context['events'].push({condition, cb})
}
export function attr (name, cb, initial) {
let context = getContext()?.context
context['attributes'] ||= []
context['attributes'].push({name, cb, initial})
}
export function dispatch(name, cb) {
let context = getContext()?.context
context['dispatch'] ||= []
context['dispatch'].push({name, cb})
}
export function unsubscribe(condition, cb) {
let context = getContext()?.context
context['unsubscribe'] ||= []
context['unsubscribe'].push({condition, cb})
}
export function state () {
let context = getContext()?.context
let _state = {}
context['state'] ||= _state
return _state
}
export const role_symbol = Symbol('role')
export const role_name_symbol = Symbol('role_name')
export function role(name, cb) {
let context = getContext()?.context
let full_context = getContext()
let isRole = Object.hasOwn(role_symbol, full_context)
let isCondition = name && !(cb instanceof Function)
if (isCondition) {
}
if (isRole) {
let role_context = getContext()
return role_context
}
if (cb instanceof Function) {
let _role = {}
context['role'] ||= _role
context['role'][name] = {
[role_symbol]: true,
[role_name_symbol]: name,
}
let ctx = unCreateContext()
let og_getContext = getContext
getContext = ctx.use
ctx.call(createFallback(context['role'][name], context), cb)
getContext = og_getContext
}
}
export function el (condition) {
const context = getContext()
let element = context?.element || context?.context?.element
if (!condition) {
return context?.element || context?.context?.element
}
if (isString(condition) && element) {
return element.querySelector(string)
}
if (condition instanceof Function && element) {
return [...element.querySelectorAll('*')].find(condition)
}
}
@doeixd
Copy link
Author

doeixd commented Oct 23, 2023

On removed from dom needs to be implimeted

@doeixd
Copy link
Author

doeixd commented Oct 23, 2023

roles, need default properties

@doeixd
Copy link
Author

doeixd commented Oct 23, 2023

roles need default condition

@doeixd
Copy link
Author

doeixd commented Oct 24, 2023

every instance of a component needs its own version of state and roles and such

you can just store this on the element object itself?

@doeixd
Copy link
Author

doeixd commented Oct 24, 2023

netsted roles

@doeixd
Copy link
Author

doeixd commented Oct 24, 2023

access all components of type from the be object and do stuff to them

@doeixd
Copy link
Author

doeixd commented Oct 24, 2023

when you call be.componentName(#id) you can get a specific component?

@doeixd
Copy link
Author

doeixd commented Oct 24, 2023

add context to event listener

@doeixd
Copy link
Author

doeixd commented Oct 24, 2023

add proxys to element objects

@doeixd
Copy link
Author

doeixd commented Oct 24, 2023

values api.

component.values.count = {
initial: 0,
get() {},
set() {},
}

some how scope this to the element

@doeixd
Copy link
Author

doeixd commented Oct 25, 2023

I am writing a front end javascript framework, and am debating between the following two apis, help me decide which one to go with.

Api 1:

const counter = be.counter

counter.values.count = instance => ({
	get(oldValue) {
		return oldValue
	},
	set(newValue) {
		instance.values.count = newValue
		instance.roles.count.innerText = newValue
	}
})

counter.roles.inc.onClick = (e, instance) => {
	instance.values.count += 1
}

Api 2:

const counter = createComponent('counter', () => {
	value('count', {
		get(oldValue) {
			return oldValue
		},
		set(newValue) {
			role('count').innerText = newValue
			return newValue
		}
	})

	role('inc', () => {
		on('click', () => {
			value('count') += 1
                        value('count').set(
                            value('count') + 1
                         )
                        set_value('count', 0 + 1)
		})
	})

})

@doeixd
Copy link
Author

doeixd commented Oct 26, 2023

Make it so custom events can return a value, and can have a callback.

@doeixd
Copy link
Author

doeixd commented Oct 26, 2023

add .find to component instance, and roles.

add .roles to component instance, and find.

@doeixd
Copy link
Author

doeixd commented Oct 26, 2023

when a new item is added to the dom, and it is of a component type / or a role type, generate a new key for it, and check it against the list of already hydrated elements

@doeixd
Copy link
Author

doeixd commented Oct 30, 2023

import { createContext as unCreateContext } from 'unctx'
const isObj = o => (typeof o === 'object' && o !== null)
// Faux Class Proxy Composable Hooks
// FC Proxy Context 
let getContext = () => {return {
  context: {},
  args: {}
}}

export function createContext(contextName, initialObject ={}) {
  const ctx = unCreateContext(contextName)
  getContext = ctx.use
  const context = {
    context: initialObject,
    args: {}
  }

  function call(args = {}, fn) {
    context.args = args
    ctx.set(context, true)
    return ctx.call(context, fn)
  }

  const initialProxy = new Proxy(initialObject, {
    get(target, propName) {
      const catchAllGetter = target?.__values?.[propName]?.__get
      const oldValue = target?.__values?.[propName]?.__value

      if (catchAllGetter) return call({propName, isInitial:true}, _ => catchAllGetter?.({target, propName, oldValue}))

      // if (!target?.__values?.[keyName])
      console.log({target, propName})
      return target.__values[propName].__value
    },

    set(target, propName, value) {

      const catchAllSetter = target?.__values?.[propName]?.__set
      const oldValue = target?.__values?.[propName]?.__value
 


      let newValue = value
      if (catchAllSetter) {
        const result = call({ propName, value, isInitial: true, oldValue, newValue: value }, _ => catchAllSetter?.({ target, propName, value, newValue:value, oldValue }))
        if (result) newValue = result
      }

      target.__values[propName].__value = newValue

      return true
    }
  })

  return function getKeyProxyFromContext (keyName, fauxProxyFn, initialValue) {
    // getContext = ctx.use


    initialValue = {
      __value: initialValue
    }

    if(!initialObject?.__values) initialObject.__values = {}
    initialObject.__values[keyName] = initialValue;
    //const thisValue = initialObject?.__values[keyName]
    
    const thisKeyOfContext = initialProxy?.[keyName]
    
    if(!fauxProxyFn && keyName) return thisKeyOfContext
    if(!fauxProxyFn && !keyName) return initialProxy
    
    //Else, there is a faux proxy fn, and a key name
    
    const proxyHandler = {
      get(target, propName) {
        const getPropHandler = thisKeyOfContext?.__getProp?.[propName]
        const args = { keyName, getContext, target, propName, value: thisKeyOfContext?.[propName] }
        if (getPropHandler) return call(args, _ => getPropHandler(args))

        const catchAllGetter = thisKeyOfContext?.[keyName]?.__get
        if (catchAllGetter) return call(args, _ => catchAllGetter?.(args))

        return Reflect.get(target?.__value, prop)
      },

      defineProperty(target, propName, descriptor) {
        const definePropHandler = thisKeyOfContext?.__defineProp?.[propName]
        if (definePropHandler) return call({propName}, _ => definePropHandler({ target, propName, descriptor }))

        return Reflect.defineProperty(target?.__value, propName, descriptor)
      },

      has(target, propName) {
        const hasPropHandler = thisKeyOfContext?.__has?.[propName]
        if (hasPropHandler) return call({ propName}, _ => hasPropHandler({ target, propName }))

        return Reflect.has(target?.__value, propName)
      },

      set(target, propName, value) {
        const setPropHandler = thisKeyOfContext?.__setProp?.[propName]
        const args = { keyName, getContext, target, propName, value, oldValue: thisKeyOfContext[propName], newValue: value }
        if (setPropHandler) {

          let newValue = call({args}, _ => setPropHandler(args))
          if(newValue === undefined) newValue = value
          thisKeyOfContext[newValue] = newValue
          return true
        }

        const catchAllSetter = thisKeyOfContext?.[keyName]?.__set
        debugger;
        if (catchAllSetter) {
          let newValue = call({propName}, _ => catchAllSetter?.(args))
          if(newValue === undefined) newValue = value
          thisKeyOfContext[newValue] = newValue
          return true
        }

        return Reflect.set(target?.__value, prop, value)
      },

      apply(target, thisArg, argumentsList) {
        const applyPropHandler = thisKeyOfContext?.__apply?.[propName]
        if (applyPropHandler) return call({propName}, _ => applyPropHandler?.())

        return Reflect.apply(target?.__value, thisArg, argumentsList)
      }

    }

    const proxy = new Proxy(initialValue, proxyHandler)

    if(!initialObject?.__proxies) initialObject.__proxies = {}
    if(!initialObject?.__proxies?.[keyName]) initialObject.__proxies[keyName] = {}
    initialObject.__proxies[keyName] = proxy

    context.args ||={}
    context.args.propName = keyName
    ctx.call(context, fauxProxyFn)
    getContext = () => {return {
      context: {},
      args: {}
    }}
    
    return (args) => {
      if (args !== undefined) {
        initialProxy[keyName] = args
        return initialProxy[keyName]
      } 
      return initialProxy[keyName]
    }
  }
}


export function get(fn) {
  const ctx = getContext()

  // if (ctx.propName) {
    ctx.context.__values[propName] ??= {}
    ctx.context.__values[propName].__get = fn
  // }
}


export function set(fn) {
  const ctx = getContext()

  console.log({ctx})
  if (ctx.args.propName) {
    ctx.context.__values[ctx.args.propName] ??= {}
    ctx.context.__values[ctx.args.propName].__set = fn
  }
}


// const CounterContext = createContext('countercontext')

// const Counter = CounterContext('counter', () => {
//   set(args => {
//     console.log(`old: ${args.oldValue}, new: ${args.newValue} `)
//   })
// }, 0)

// Counter() = 5

// function createComponent (name, constructor, initialData = {}) {
//   const ComponentContext = createContext(name, {
//     constructor, 
//     example: constructor(initialData),
//     initialData,
//     instances: {}
//   })
  
//   component.forEach(component => {
//     const key = determineKey(component)
//     ComponentContext().instances[key] = component
//     constructor(component)
//   })
// }

@doeixd
Copy link
Author

doeixd commented Oct 31, 2023

Here are some suggestions for how this faux class context system could handle arrays and deep nested objects:

Arrays:

Wrap array mutations like push/pop/splice in proxy set handlers to trigger reactivity.
Use something like immer to produce a new copy of the array when it changes to avoid proxy observeability issues.
Allow registering custom handlers for array functions like map/filter/reduce that can trigger updates.
Support observable arrays out of the box with built-in handling for common mutations.
Deep objects:

Recursively proxy nested objects to maintain reactivity all the way down.
Use a "reactive" function that ensures object is proxied before adding to state.
Track proxies by target object reference to avoid duplicated nesting.
Proxy objects lazily on first get to avoid overhead until needed.
Provide utility functions like setDeep and getDeep that work for proxies.
Allow customizing depth of reactive nesting.
Batch nested updates to avoid over-rendering.
Explicitly proxy sub-objects when needed instead of always recursing.
In general:

Minimize performance impact by keeping proxies lean.
Educate on best practices like avoiding loops over proxies.
Provide escape hatches to drop down to raw values when needed.
Focus the reactivity on app state rather than all data.
The goal would be keeping full reactivity while optimizing performance. Some clever caching, batching and avoiding unnecessary proxies could help significantly.

@doeixd
Copy link
Author

doeixd commented Jan 22, 2024

export function createObserver(fn, options) {
  const observer = new MutationObserver(fn)
  return function (element) {
    if (isString(element)) element = document.querySelector(element)
    observer.observe(element, options)
  }
}

function getAddedElements (mutations) {
  let addedElements = []

  for (let mutation of mutations) {
    for (let element of mutation.addedNodes) {
      addedElements.push(element)
      const descendants = element?.querySelectorAll?.('*') || []
      addedElements.push(...descendants)
    }
  }

  return addedElements
}



export function ensureRunAfterDOM (fn) {
  let handleDOMLoaded = Fn(fn)
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', handleDOMLoaded);
  } else {
    handleDOMLoaded();
  }
}

export function Fn (fn) {
  return function (...args) {
    return fn(...args)
  }
}

export function isString (value) {
  return typeof value === 'string' || value instanceof String
}

export function isObject (o) { 
  return (typeof o === 'object' && o !== null)
}

export function makeEventName (name) {
  name = name.replace(/^on/, '')
  // name = name[0].toLowerCase() + name.substring(1)
  return name.toLowerCase()
}

export function createEvent (eventName, data) {
	return new CustomEvent(eventName, data)
}

export function el(e) {
  return e.currentTarget
}

export function createFallback (...objs) {

  const select = (field, ...moreObjs) => {
    for (let obj of [...objs, ...moreObjs]) {
      if (obj?.[field] || Object.hasOwn(obj, field)) return {
        obj,
        value: obj[field]
      }
    }
  }

  return new Proxy ({}, {
    get(target, prop, receiver) {
      return select(prop, target)?.value
    },
    set(target, prop, value) {
      let obj = select(prop, target)?.obj
      if (obj) {
        obj[prop] = value
      } else {
        target[prop] = value
      }
    }
  })
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment