Beta/Evaluation-Warning ⚠️
Proxy-JS is an experimental library intended for my personal technical education/training only. I started Proxy-JS as an endeavour to evaluate the utilizability of ES6-Proxies for Javascript data binding. BUT it is NOT safe, tested or final for production environment or any other purpose.
Proxy-JS - A primitive Javascript data binding library
- Few APIs
- No buildsteps
- Based on well known Javascript/HTML5 Concepts
- WebComponents
- Proxies
- Others...
- Binding Html-Resources to WebComponents
- Managing resources like
Webcomponent example
<!--hello-world.html-->
<div class="text-clickable">
<span @click="onClick(e)">{{model.title}}<br>Clicked {{model.count}} times (Click me)</span>
</div>
/*hello-world.css*/
.text-clickable {
cursor: pointer;
user-select: none;
transition: text-shadow 0.25s;
}
.text-clickable:hover {
text-shadow: 0 0 5px lightgrey;
}
import { $p, $pLoad } from './lib/proxy-js/core.js';
import { $pResource } from './lib/proxy-js/request.js';
class HelloWorld extends HTMLElement {
model = {
title: 'WebComponent - Hello World',
count: 0
};
constructor() {
super();
this.attachShadow({ mode: 'open', delegatesFocus: true });
$p.dom.appendSheet(this.shadowRoot, 'hello');
}
async connectedCallback() {
var $template = await $pResource('./resource/hello-world.html');
$template.bind(this)
.appendTo(this.shadowRoot);
}
onClick(e) {
++this.model.count;
}
}
$pLoad(() => {
customElements.define('hello-world', HelloWorld);
$p.dom.styleSheet('./css/hello-world.css', 'hello');
document.querySelector('.layout-center')
.appendChild(document.createElement('hello-world'));
});
API Calls (most relevant)
Import/Call |
Description |
Parameter |
$pLoad(callback ) |
callback called when document is ready and proxy-js is load |
callback - parameterless function |
$template |
Type |
Represents a html-resource |
$pResource(url ) returns - promise resolving to a $template
|
Fetch and cache a html-resource (awaitable method) |
url - address to the html-resource |
$template.bind(webcomponent ) returns - the $template
|
Binds a $template to the model of a webcomponent |
webcomponent - the webcomponent object |
$template.appendTo($element ) returns - the $template
|
Appends the $template to a DOM $element
|
$element - DOM element |
$p |
Global library object |
Provides access to the libraries functions and core |
$p.bind(webcomponent , $template ) |
Bind $template to the model of a webcomponent |
webcomponent - the webcomponent object $template - html-resource |
$p.dom.styleSheet(url , name ) |
Maps the stylesheet at url to a name
|
url - adress to a stylesheet name - name to map the stylesheet |
$p.dom.appendSheet(shadowRoot , name1, ...nameX ) |
Append stylesheets to the shadowRoot of a webcomponent |
shadowRoot - shadowRoot of a Webcomponent name1...nameX - Names of the mapped stylesheets to append |
Binding Attributes
Syntax |
Type |
Description |
Parameter |
#name
|
Element binding |
Creates member name referencing the declaring element |
- |
:class="{ 'class-name' : model.selected , ... }" |
Class binding |
class-name applies when right hand expression evaluates true |
'class-name' : expression
|
:style="{ 'display' : model.show ? 'block' : 'none' }" |
Style binding |
style evaluates to right hand expression |
'styleproperty' : expression
|
:disabled="model.disabled ? 'disabled' : undefined " |
Attribute binding |
Evaluates to the right hand expression, removed when expression is undefined |
expression |
:value="model.title " |
Input binding for input of type text |
Binds the value of a text input element |
- |
:checked="model.check " |
Input binding for input of type checkbox |
Binds the checked status of a checkbox input element |
- |
p-for="book in model.books " |
Iterative binding |
Repeats an element from the objects of an array. The objects in the array will be the models of the webcomponents created by the binding |
name in array
|
@event ="callback ($e , e , item )" |
Event binding |
Binds an event to a callback with optional parameters |
@event - name of the event (e.g. click) callback - name of the callback methd $e - target or delegate $element e - event item - name of a for-item (e.g. book) |
@event .modifier &selector =... |
Event binding |
Binds an event with optional modifiers and/or selector |
@event - name of the event (e.g. click) modifier - can be - once - capture selector - a valid css selector |
API Calls (advanced)
Call |
Description |
Parameter |
$p.unbind($e ) - removing elements without unbinding causes memory leaks
|
Recursively removes all bindings within $e and its descendants |
$e - element to unbind |
... = new SilentValue(value , listener ) |
Assigning a value without triggering the watchers |
value - the value to assign listener - optional filter array to ignore only specific listener |
$p.array.removeAll(array , predicate , thisArg ) |
Calls predicate passing every item from array , removing the item if predicate returns true |
array - the array predicate - filter function of signature f(item , idx , array ) thisArg - the context, the filter function is called on |
$p.events.one($e , eventName , handler , options ) |
Registers an event handler which is unregistered after occuring once |
$e - the element to register an event for eventName - the name of the event to listen for handler - event receiver function options - event listening options |
$p.dom.value($e ) |
Returns the raw binding object data |
$e - the element to get the binding data for |
$p.components.core(f ) ⚠️💀⚠️ - core implementation can be changed, overriden or manipulated using this call
|
Calls f passing the core implementation |
f - a function of signature f(core) - where core is the inner core implementation |
Core API
Call (on core ) |
Description |
Parameter |
defineConstant(name ) returns - the names constant id
|
Maps a constant id to name
|
name - name of the constant |
traverse($e , callback ) |
Recursively passes $e and all descendant nodes to callback($e) where $e is the currently traversed node. Return values of callback($e) : TRAVERSE_NODE_SKIP - Skip descendants of $e TRAVERSE_NODE_STOP - Stop traversing |
$e - the element to traverse callback - a function of signature callback($e) |
inverse($e , callback ) |
Innermost first recursively passes all descendant nodes of $e to callback($e) where $e is the currently inversed node. After all inversing, $e is passed to callback
|
$e - the element to inverse callback - a function of signature callback($e) |
copy(value ) returns - deep copy of value
|
Recursively creates a deep copy of value trying to resolve circular references |
value - the object or array to deep copy |
more... |
|
|
Current work in progress...experimental features i am working at
I/O Handler - Define binding behaviour
$p.components.core(function (core) { //add a core component
core.addIoHandler({ //define io handler
name: 'dateformat',
out: function (value, ...params) { //out to dom
//TODO date value to string
},
in: function (value, ...params) { //in from dom
//TODO string date to date
}
});
core.addIoHandler({
name: 'filesize',
out: function (value, ...params) {
if (value < 1024) {
return value + ' byte';
}
var i = 1;
while (i <= 4 && (value / Math.pow(1024, i)) > 1024) {
i++;
}
var units = [null, 'KB', 'MB', 'GB', 'TB'];
return parseFloat((value / Math.pow(1024, i))).toFixed(2) + ' ' + units[i];
},
in: function (value, ...params) {
}
});
});
<div class="file-item-size">{{$filesize(model.size)}}</div>
<div class="file-item-date">{{$dateformat(model.changed)}}</div>
Dynamic For Templates - Determine for binding webcomponent at runtime
<div class="pnl-task">
<!--template is not rendered, onTaskComponent returns the name of a webcomponent-->
<template p-for="task in model.tasks" p-component="onTaskComponent()"></template>
<!--onTaskComponent is called for every for-item-->
</div>
//return the name of a webcomponent to use for the current for binding item
onTaskComponent(task) {
if (task.type == TASK_TYPE_NOTE) {
return 'task-note'; //use task-note Webcomponent
}
else if (task.type == TASK_TYPE_TODO) {
return 'task-todo'; //use task-todo Webcomponent
}
}