Comparing version 0.0.8 to 0.0.9
@@ -43,2 +43,2 @@ export declare type ForgoRef<T> = { | ||
export declare function mount(forgoNode: ForgoNode, parentElement: HTMLElement | null): void; | ||
export declare function rerender(element: ForgoElementArg | undefined): void; | ||
export declare function rerender(element: ForgoElementArg | undefined, props?: undefined, fullRerender?: boolean): void; |
@@ -30,10 +30,10 @@ "use strict"; | ||
*/ | ||
function render(forgoNode, node, pendingAttachStates) { | ||
function render(forgoNode, node, pendingAttachStates, fullRerender) { | ||
// Just a string | ||
if (!isForgoElement(forgoNode)) { | ||
return renderString(stringOfPrimitiveNode(forgoNode), node, pendingAttachStates); | ||
return renderString(stringOfPrimitiveNode(forgoNode), node, pendingAttachStates, fullRerender); | ||
} | ||
// HTML Element | ||
else if (typeof forgoNode.type === "string") { | ||
return renderDOMElement(forgoNode, node, pendingAttachStates); | ||
return renderDOMElement(forgoNode, node, pendingAttachStates, fullRerender); | ||
} | ||
@@ -43,3 +43,3 @@ // Custom Component. | ||
else { | ||
return renderCustomComponent(forgoNode, node, pendingAttachStates); | ||
return renderCustomComponent(forgoNode, node, pendingAttachStates, fullRerender); | ||
} | ||
@@ -59,3 +59,3 @@ } | ||
*/ | ||
function renderString(text, node, pendingAttachStates) { | ||
function renderString(text, node, pendingAttachStates, fullRerender) { | ||
var _a; | ||
@@ -88,3 +88,3 @@ // Text nodes will always be recreated | ||
*/ | ||
function renderDOMElement(forgoElement, node, pendingAttachStates) { | ||
function renderDOMElement(forgoElement, node, pendingAttachStates, fullRerender) { | ||
var _a; | ||
@@ -111,3 +111,3 @@ if (node) { | ||
attachProps(forgoElement, nodeToBindTo, pendingAttachStates); | ||
renderChildNodes(forgoElement, nodeToBindTo); | ||
renderChildNodes(forgoElement, nodeToBindTo, fullRerender); | ||
return { node: nodeToBindTo }; | ||
@@ -122,3 +122,3 @@ } | ||
attachProps(forgoElement, newElement, pendingAttachStates); | ||
renderChildNodes(forgoElement, newElement); | ||
renderChildNodes(forgoElement, newElement, fullRerender); | ||
return { node: newElement }; | ||
@@ -131,3 +131,3 @@ } | ||
*/ | ||
function renderCustomComponent(forgoElement, node, pendingAttachStates) { | ||
function renderCustomComponent(forgoElement, node, pendingAttachStates, fullRerender) { | ||
if (node) { | ||
@@ -156,14 +156,22 @@ const state = getExistingForgoState(node); | ||
// Pass it on for rendering... | ||
return render(newForgoElement, node, statesToAttach); | ||
return render(newForgoElement, node, statesToAttach, fullRerender); | ||
} | ||
// We have compatible state, and this is a rerender | ||
else { | ||
const args = { element: { componentIndex: pendingAttachStates.length } }; | ||
// Since we have compatible state already stored, | ||
// we'll push the savedComponentState into pending states for later attachment. | ||
const statesToAttach = pendingAttachStates.concat(Object.assign(Object.assign({}, savedComponentState), { props: forgoElement.props })); | ||
// Get a new element by calling render on existing component. | ||
const newForgoElement = savedComponentState.component.render(forgoElement.props, args); | ||
// Pass it on for rendering... | ||
return render(newForgoElement, node, statesToAttach); | ||
if (fullRerender || | ||
havePropsChanged(savedComponentState.props, forgoElement.props)) { | ||
const args = { | ||
element: { componentIndex: pendingAttachStates.length }, | ||
}; | ||
// Since we have compatible state already stored, | ||
// we'll push the savedComponentState into pending states for later attachment. | ||
const statesToAttach = pendingAttachStates.concat(Object.assign(Object.assign({}, savedComponentState), { props: forgoElement.props })); | ||
// Get a new element by calling render on existing component. | ||
const newForgoElement = savedComponentState.component.render(forgoElement.props, args); | ||
// Pass it on for rendering... | ||
return render(newForgoElement, node, statesToAttach, fullRerender); | ||
} | ||
else { | ||
return { node }; | ||
} | ||
} | ||
@@ -188,3 +196,3 @@ } | ||
// We have no node to render to yet. So pass undefined for the node. | ||
return render(newForgoElement, undefined, statesToAttach); | ||
return render(newForgoElement, undefined, statesToAttach, fullRerender); | ||
} | ||
@@ -205,3 +213,3 @@ } | ||
*/ | ||
function renderChildNodes(forgoElement, parentElement) { | ||
function renderChildNodes(forgoElement, parentElement, fullRerender) { | ||
const { children: forgoChildrenObj } = forgoElement.props; | ||
@@ -225,7 +233,7 @@ const childNodes = parentElement.childNodes; | ||
childNodes[forgoChildIndex].nodeType === TEXT_NODE_TYPE) { | ||
render(stringOfPrimitiveNode(forgoChild), childNodes[forgoChildIndex], []); | ||
render(stringOfPrimitiveNode(forgoChild), childNodes[forgoChildIndex], [], fullRerender); | ||
} | ||
// But otherwise, don't pass a replacement node. Just insert instead. | ||
else { | ||
const { node } = render(stringOfPrimitiveNode(forgoChild), undefined, []); | ||
const { node } = render(stringOfPrimitiveNode(forgoChild), undefined, [], fullRerender); | ||
parentElement.insertBefore(node, childNodes[forgoChildIndex]); | ||
@@ -242,6 +250,6 @@ } | ||
} | ||
render(forgoChild, childNodes[forgoChildIndex], []); | ||
render(forgoChild, childNodes[forgoChildIndex], [], fullRerender); | ||
} | ||
else { | ||
const { node } = render(forgoChild, undefined, []); | ||
const { node } = render(forgoChild, undefined, [], fullRerender); | ||
if (childNodes.length > forgoChildIndex) { | ||
@@ -401,2 +409,12 @@ parentElement.insertBefore(node, childNodes[forgoChildIndex]); | ||
/* | ||
Compare old props and new props. | ||
We don't rerender if props remain the same. | ||
*/ | ||
function havePropsChanged(oldProps, newProps) { | ||
const oldKeys = Object.keys(oldProps); | ||
const newKeys = Object.keys(newProps); | ||
return (oldKeys.length !== newKeys.length || | ||
oldKeys.some((key) => oldProps[key] !== newProps[key])); | ||
} | ||
/* | ||
Mount will render the DOM as a child of the specified container element. | ||
@@ -406,3 +424,3 @@ */ | ||
if (parentElement) { | ||
const { node } = render(forgoNode, undefined, []); | ||
const { node } = render(forgoNode, undefined, [], true); | ||
parentElement.appendChild(node); | ||
@@ -424,3 +442,3 @@ } | ||
*/ | ||
function rerender(element) { | ||
function rerender(element, props = undefined, fullRerender = true) { | ||
if (element && element.node) { | ||
@@ -430,5 +448,8 @@ const state = getForgoState(element.node); | ||
const component = state.components[element.componentIndex]; | ||
const forgoNode = component.component.render(component.props, component.args); | ||
const statesToAttach = state.components.slice(0, element.componentIndex + 1); | ||
render(forgoNode, element.node, statesToAttach); | ||
const effectiveProps = typeof props !== "undefined" ? props : component.props; | ||
const forgoNode = component.component.render(effectiveProps, component.args); | ||
const statesToAttach = state.components | ||
.slice(0, element.componentIndex) | ||
.concat(Object.assign(Object.assign({}, component), { props: effectiveProps })); | ||
render(forgoNode, element.node, statesToAttach, fullRerender); | ||
} | ||
@@ -435,0 +456,0 @@ else { |
{ | ||
"name": "forgo", | ||
"version": "0.0.8", | ||
"version": "0.0.9", | ||
"main": "./dist", | ||
@@ -5,0 +5,0 @@ "devDependencies": { |
@@ -5,3 +5,3 @@ # forgo | ||
Unlike React, apps are plain JS with very little framework specific code. Everything you already know about DOM APIs and JavaScript will easily carry over. | ||
Unlike React, there are very few framework specific patterns and lingo to learn. Everything you already know about DOM APIs and JavaScript will easily carry over. | ||
@@ -11,7 +11,9 @@ - Use HTML DOM APIs for accessing elements | ||
- Use closures for maintaining component state | ||
- Use any singleton pattern for managing app-wide state | ||
- There's no vDOM or DOM diffing. Renders are manually triggered | ||
- There's no vDOM or DOM diffing | ||
- Renders are manually triggered | ||
Forgo is basically just one small JS file (actually TypeScript). It's somewhat decently documented, but I could use some help here. A stated goal of the project is to always remain within that single file. | ||
## We'll be tiny. Always. | ||
All of Forgo is in one small JS file (actually it's TypeScript). It is a goal of the project is to remain within that single file. | ||
## Installation | ||
@@ -87,7 +89,7 @@ | ||
You're expected to read form input control values with regular DOM APIs. | ||
You can read form input element values with regular DOM APIs. | ||
There's a small hurdle though - how do you we get a reference to these nodes? Well, that's where the ref attribute comes in. An object bound to the ref attribute in the markup will have its value property set to the DOM element. | ||
There's a small hurdle though - how do we get a reference to the actual DOM element? That's where the ref attribute comes in. An object referenced by the ref attribute in an element's markup will have its value property set to the actual DOM element. | ||
See the usage below: | ||
Better explained with an example: | ||
@@ -116,4 +118,54 @@ ```jsx | ||
## Multiple Components, passing props etc. | ||
## Unmount | ||
When a component is unmounted, Forgo will invoke the unmount() function if defined for a component. This is of course, totally optional. | ||
```jsx | ||
function Greeter(props) { | ||
return { | ||
render(props, args) { | ||
return <div>Hello {props.firstName}</div>; | ||
}, | ||
unmount() { | ||
console.log("Got unloaded."); | ||
}, | ||
}; | ||
} | ||
``` | ||
## Additional Rerender options | ||
The most straight forward way to do rerender is by invoking it with `args.element` as follows. | ||
```tsx | ||
function TodoList(props) { | ||
let todos = []; | ||
return { | ||
render(props, args) { | ||
function addTodos(text) { | ||
todos.push(text); | ||
rerender(args.element); | ||
} | ||
return <div>markup goes here...</div>; | ||
}, | ||
}; | ||
} | ||
``` | ||
But there are a couple of handy options to rerender, 'newProps' and 'forceRerender'. | ||
newProps let you pass a new set of props while rerendering. If you'd like previous props to be used, pass undefined here. | ||
forceRerender defaults to true, but when set to false skips child component rendering if props haven't changed. | ||
```js | ||
const newProps = { name: "Kai" }; | ||
const forceRerender = false; | ||
rerender(args.element, newProps, forceRerender); | ||
``` | ||
## Recap with a complete example | ||
Finally, let's do a recap with a more complete example. Let's make a Todo List app in TypeScript. | ||
@@ -137,3 +189,3 @@ | ||
render(props: TodoListProps, args: ForgoRenderArgs) { | ||
function onTodoAdd(text: string) { | ||
function addTodos(text: string) { | ||
todos.push(text); | ||
@@ -151,3 +203,3 @@ rerender(args.element); | ||
</ul> | ||
<AddTodo onAdd={onTodoAdd} /> | ||
<AddTodo onAdd={addTodos} /> | ||
</div> | ||
@@ -160,3 +212,3 @@ ); | ||
Here's the TodoList item, which simply displays a Todo. | ||
Here's the TodoListItem component, which simply displays a Todo. | ||
@@ -187,3 +239,3 @@ ```tsx | ||
function onClick() { | ||
function saveTodo() { | ||
const inputEl = input.value; | ||
@@ -197,2 +249,9 @@ if (inputEl) { | ||
// Add the todo when Enter is pressed | ||
function onKeyPress(e: KeyboardEvent) { | ||
if (e.key === "Enter") { | ||
saveTodo(); | ||
} | ||
} | ||
return { | ||
@@ -202,4 +261,4 @@ render() { | ||
<div> | ||
<input type="text" ref={input} /> | ||
<button onclick={onClick}>Add me!</button> | ||
<input onkeypress={onKeyPress} type="text" ref={input} /> | ||
<button onclick={saveTodo}>Add me!</button> | ||
</div> | ||
@@ -251,1 +310,5 @@ ); | ||
``` | ||
## Getting Help | ||
You can reach out to me via twitter or email. If you find issues, please file a bug on [Github](https://github.com/forgojs/forgo/issues). |
120
src/index.ts
@@ -145,3 +145,4 @@ /* | ||
node: ChildNode | undefined, | ||
pendingAttachStates: NodeAttachedComponentState<any>[] | ||
pendingAttachStates: NodeAttachedComponentState<any>[], | ||
fullRerender: boolean | ||
): { node: ChildNode } { | ||
@@ -153,3 +154,4 @@ // Just a string | ||
node, | ||
pendingAttachStates | ||
pendingAttachStates, | ||
fullRerender | ||
); | ||
@@ -162,3 +164,4 @@ } | ||
node, | ||
pendingAttachStates | ||
pendingAttachStates, | ||
fullRerender | ||
); | ||
@@ -172,3 +175,4 @@ } | ||
node, | ||
pendingAttachStates | ||
pendingAttachStates, | ||
fullRerender | ||
); | ||
@@ -193,3 +197,4 @@ } | ||
node: ChildNode | undefined, | ||
pendingAttachStates: NodeAttachedComponentState<any>[] | ||
pendingAttachStates: NodeAttachedComponentState<any>[], | ||
fullRerender: boolean | ||
): { node: ChildNode } { | ||
@@ -227,3 +232,4 @@ // Text nodes will always be recreated | ||
node: ChildNode | undefined, | ||
pendingAttachStates: NodeAttachedComponentState<any>[] | ||
pendingAttachStates: NodeAttachedComponentState<any>[], | ||
fullRerender: boolean | ||
): { node: ChildNode } { | ||
@@ -254,3 +260,3 @@ if (node) { | ||
renderChildNodes(forgoElement, nodeToBindTo as HTMLElement); | ||
renderChildNodes(forgoElement, nodeToBindTo as HTMLElement, fullRerender); | ||
return { node: nodeToBindTo }; | ||
@@ -264,3 +270,3 @@ } else { | ||
attachProps(forgoElement, newElement, pendingAttachStates); | ||
renderChildNodes(forgoElement, newElement); | ||
renderChildNodes(forgoElement, newElement, fullRerender); | ||
return { node: newElement }; | ||
@@ -277,3 +283,4 @@ } | ||
node: ChildNode | undefined, | ||
pendingAttachStates: NodeAttachedComponentState<any>[] | ||
pendingAttachStates: NodeAttachedComponentState<any>[], | ||
fullRerender: boolean | ||
): { node: ChildNode } { | ||
@@ -309,23 +316,32 @@ if (node) { | ||
// Pass it on for rendering... | ||
return render(newForgoElement, node, statesToAttach); | ||
return render(newForgoElement, node, statesToAttach, fullRerender); | ||
} | ||
// We have compatible state, and this is a rerender | ||
else { | ||
const args = { element: { componentIndex: pendingAttachStates.length } }; | ||
if ( | ||
fullRerender || | ||
havePropsChanged(savedComponentState.props, forgoElement.props) | ||
) { | ||
const args = { | ||
element: { componentIndex: pendingAttachStates.length }, | ||
}; | ||
// Since we have compatible state already stored, | ||
// we'll push the savedComponentState into pending states for later attachment. | ||
const statesToAttach = pendingAttachStates.concat({ | ||
...savedComponentState, | ||
props: forgoElement.props, | ||
}); | ||
// Since we have compatible state already stored, | ||
// we'll push the savedComponentState into pending states for later attachment. | ||
const statesToAttach = pendingAttachStates.concat({ | ||
...savedComponentState, | ||
props: forgoElement.props, | ||
}); | ||
// Get a new element by calling render on existing component. | ||
const newForgoElement = savedComponentState.component.render( | ||
forgoElement.props, | ||
args | ||
); | ||
// Get a new element by calling render on existing component. | ||
const newForgoElement = savedComponentState.component.render( | ||
forgoElement.props, | ||
args | ||
); | ||
// Pass it on for rendering... | ||
return render(newForgoElement, node, statesToAttach); | ||
// Pass it on for rendering... | ||
return render(newForgoElement, node, statesToAttach, fullRerender); | ||
} else { | ||
return { node }; | ||
} | ||
} | ||
@@ -353,3 +369,3 @@ } | ||
// We have no node to render to yet. So pass undefined for the node. | ||
return render(newForgoElement, undefined, statesToAttach); | ||
return render(newForgoElement, undefined, statesToAttach, fullRerender); | ||
} | ||
@@ -373,3 +389,4 @@ } | ||
forgoElement: ForgoElement<string | ForgoComponentCtor<TProps>, TProps>, | ||
parentElement: HTMLElement | ||
parentElement: HTMLElement, | ||
fullRerender: boolean | ||
) { | ||
@@ -407,3 +424,4 @@ const { children: forgoChildrenObj } = forgoElement.props; | ||
childNodes[forgoChildIndex], | ||
[] | ||
[], | ||
fullRerender | ||
); | ||
@@ -416,3 +434,4 @@ } | ||
undefined, | ||
[] | ||
[], | ||
fullRerender | ||
); | ||
@@ -439,5 +458,5 @@ parentElement.insertBefore(node, childNodes[forgoChildIndex]); | ||
} | ||
render(forgoChild, childNodes[forgoChildIndex], []); | ||
render(forgoChild, childNodes[forgoChildIndex], [], fullRerender); | ||
} else { | ||
const { node } = render(forgoChild, undefined, []); | ||
const { node } = render(forgoChild, undefined, [], fullRerender); | ||
if (childNodes.length > forgoChildIndex) { | ||
@@ -625,2 +644,15 @@ parentElement.insertBefore(node, childNodes[forgoChildIndex]); | ||
/* | ||
Compare old props and new props. | ||
We don't rerender if props remain the same. | ||
*/ | ||
function havePropsChanged(oldProps: any, newProps: any) { | ||
const oldKeys = Object.keys(oldProps); | ||
const newKeys = Object.keys(newProps); | ||
return ( | ||
oldKeys.length !== newKeys.length || | ||
oldKeys.some((key) => oldProps[key] !== newProps[key]) | ||
); | ||
} | ||
/* | ||
Mount will render the DOM as a child of the specified container element. | ||
@@ -630,3 +662,3 @@ */ | ||
if (parentElement) { | ||
const { node } = render(forgoNode, undefined, []); | ||
const { node } = render(forgoNode, undefined, [], true); | ||
parentElement.appendChild(node); | ||
@@ -647,3 +679,7 @@ } else { | ||
*/ | ||
export function rerender(element: ForgoElementArg | undefined) { | ||
export function rerender( | ||
element: ForgoElementArg | undefined, | ||
props = undefined, | ||
fullRerender = true | ||
) { | ||
if (element && element.node) { | ||
@@ -653,11 +689,19 @@ const state = getForgoState(element.node); | ||
const component = state.components[element.componentIndex]; | ||
const effectiveProps = | ||
typeof props !== "undefined" ? props : component.props; | ||
const forgoNode = component.component.render( | ||
component.props, | ||
effectiveProps, | ||
component.args | ||
); | ||
const statesToAttach = state.components.slice( | ||
0, | ||
element.componentIndex + 1 | ||
); | ||
render(forgoNode, element.node, statesToAttach); | ||
const statesToAttach = state.components | ||
.slice(0, element.componentIndex) | ||
.concat({ | ||
...component, | ||
props: effectiveProps, | ||
}); | ||
render(forgoNode, element.node, statesToAttach, fullRerender); | ||
} else { | ||
@@ -664,0 +708,0 @@ throw new Error( |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
71478
1194
304