mirror of
https://github.com/JeremyLikness/vanillajs-deck
synced 2024-11-14 17:44:56 +00:00
Data-binding implementation
This commit is contained in:
parent
274dc9077b
commit
e7d8aad7ba
@ -4,10 +4,28 @@ body {
|
|||||||
font-size: 3vh;
|
font-size: 3vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
br {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
label > div, input {
|
||||||
|
float: left;
|
||||||
|
width: 40vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
button, input {
|
||||||
font-size: 3vh;
|
font-size: 3vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
@ -44,17 +62,21 @@ slide-deck img.expandable:hover {
|
|||||||
body {
|
body {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
button {
|
button, input {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label > div, input {
|
||||||
|
width: 90vw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 1200px) {
|
@media only screen and (max-width: 1200px) {
|
||||||
body {
|
body {
|
||||||
font-size: 2vh;
|
font-size: 2vh;
|
||||||
}
|
}
|
||||||
button {
|
button, input {
|
||||||
font-size: 14px;
|
font-size: 2vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +120,7 @@ img {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
slide-deck div {
|
slide-deck > div {
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
@ -116,7 +138,7 @@ slide-deck div {
|
|||||||
.left {
|
.left {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
slide-deck div {
|
slide-deck > div {
|
||||||
height: 75vh;
|
height: 75vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
|
import { Observable, Computed } from "./observable.js"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class supports data-binding operations
|
||||||
|
*/
|
||||||
export class DataBinding {
|
export class DataBinding {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple evaluation
|
* Simple evaluation
|
||||||
* @param {string} js The JavaScript to evaluate
|
* @param {string} js The JavaScript to evaluate
|
||||||
@ -16,10 +21,56 @@ export class DataBinding {
|
|||||||
* @param {object} context The context (data) to evaluate with
|
* @param {object} context The context (data) to evaluate with
|
||||||
* @returns {object} The result of the evaluation
|
* @returns {object} The result of the evaluation
|
||||||
*/
|
*/
|
||||||
executeInContext(src, context) {
|
executeInContext(src, context, attachBindingHelpers = false) {
|
||||||
|
if (attachBindingHelpers) {
|
||||||
|
context.observable = this.observable;
|
||||||
|
context.computed = this.computed;
|
||||||
|
context.bindValue = this.bindValue;
|
||||||
|
}
|
||||||
return this.execute.call(context, src);
|
return this.execute.call(context, src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple observable implementation
|
||||||
|
* @param {object} value Any value to observe
|
||||||
|
* @returns {Observable} The observable instance to use
|
||||||
|
*/
|
||||||
|
observable(value) {
|
||||||
|
return new Observable(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an observed computed property
|
||||||
|
* @param {function} calculation Calculated value
|
||||||
|
* @param {Observable[]} deps The list of dependent observables
|
||||||
|
* @returns {Computed} The observable computed value
|
||||||
|
*/
|
||||||
|
computed(calculation, deps) {
|
||||||
|
return new Computed(calculation, deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds an input element to an observable value
|
||||||
|
* @param {HTMLInputElement} input The element to bind to
|
||||||
|
* @param {Observable} observable The observable instance to bind to
|
||||||
|
*/
|
||||||
|
bindValue(input, observable) {
|
||||||
|
let initialValue = observable.value;
|
||||||
|
input.value = initialValue;
|
||||||
|
observable.subscribe(() => input.value = observable.value);
|
||||||
|
/**
|
||||||
|
* Converts the values
|
||||||
|
* @param {object} value
|
||||||
|
*/
|
||||||
|
let converter = value => value;
|
||||||
|
if (typeof initialValue === "number") {
|
||||||
|
converter = num => isNaN(num = parseFloat(num)) ? 0 : num;
|
||||||
|
}
|
||||||
|
input.onkeyup = () => {
|
||||||
|
observable.value = converter(input.value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches for "repeat" attribute to data-bind lists
|
* Searches for "repeat" attribute to data-bind lists
|
||||||
* @param {HTMLElement} elem The parent element to search
|
* @param {HTMLElement} elem The parent element to search
|
||||||
@ -39,12 +90,13 @@ export class DataBinding {
|
|||||||
if (matches) {
|
if (matches) {
|
||||||
matches.forEach(match => {
|
matches.forEach(match => {
|
||||||
match = match.replace("{{", "").replace("}}", "");
|
match = match.replace("{{", "").replace("}}", "");
|
||||||
const value = this.executeInContext(`this.${match}`, {item});
|
const value = this.executeInContext(`this.${match}`, { item });
|
||||||
newTemplate = newTemplate.replace(`{{${match}}}`, value);
|
newTemplate = newTemplate.replace(`{{${match}}}`, value);
|
||||||
});
|
});
|
||||||
parent.innerHTML += newTemplate;
|
parent.innerHTML += newTemplate;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,9 +117,11 @@ export class Navigator extends HTMLElement {
|
|||||||
*/
|
*/
|
||||||
get hasNext() {
|
get hasNext() {
|
||||||
const host = this.querySelector("div");
|
const host = this.querySelector("div");
|
||||||
const appear = host.querySelectorAll(".appear");
|
if (host) {
|
||||||
if (appear && appear.length) {
|
const appear = host.querySelectorAll(".appear");
|
||||||
return true;
|
if (appear && appear.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this._currentIndex < (this.totalSlides - 1);
|
return this._currentIndex < (this.totalSlides - 1);
|
||||||
}
|
}
|
||||||
|
99
js/observable.js
Normal file
99
js/observable.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback ListenerCallback
|
||||||
|
* @param {object} newVal The new value generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an observable value
|
||||||
|
*/
|
||||||
|
export class Observable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new observable and initializes with a value
|
||||||
|
* @param {object} value
|
||||||
|
*/
|
||||||
|
constructor(value) {
|
||||||
|
/**
|
||||||
|
* Subscriptions
|
||||||
|
* @type {ListenerCallback[]}
|
||||||
|
*/
|
||||||
|
this._listeners = [];
|
||||||
|
/**
|
||||||
|
* The value
|
||||||
|
* @type {object}
|
||||||
|
*/
|
||||||
|
this._value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies subscribers of new value
|
||||||
|
*/
|
||||||
|
notify() {
|
||||||
|
this._listeners.forEach(listener => listener(this._value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to listen for changes
|
||||||
|
* @param {ListenerCallback} listener
|
||||||
|
*/
|
||||||
|
subscribe(listener) {
|
||||||
|
this._listeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of the observable
|
||||||
|
* @returns {object} The current value
|
||||||
|
*/
|
||||||
|
get value() {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the observable
|
||||||
|
* @param {object} val The new value
|
||||||
|
*/
|
||||||
|
set value(val) {
|
||||||
|
if (val !== this._value) {
|
||||||
|
this._value = val;
|
||||||
|
this.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable computed properties
|
||||||
|
*/
|
||||||
|
export class Computed extends Observable {
|
||||||
|
/**
|
||||||
|
* Creates a new observable and initializes with a value
|
||||||
|
* @param {Function} value Initial computation
|
||||||
|
* @param {Observable[]} deps Dependencies
|
||||||
|
*/
|
||||||
|
constructor(value, deps) {
|
||||||
|
super(value());
|
||||||
|
const listener = () => {
|
||||||
|
this._value = value();
|
||||||
|
this.notify();
|
||||||
|
}
|
||||||
|
deps.forEach(dep => dep.subscribe(listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of the observable
|
||||||
|
* @returns {object} The value
|
||||||
|
*/
|
||||||
|
get value() {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the observable
|
||||||
|
* @param {object} _ The new value
|
||||||
|
* @throws "Cannot set computed property"
|
||||||
|
*/
|
||||||
|
set value(_) {
|
||||||
|
throw "Cannot set computed property";
|
||||||
|
}
|
||||||
|
}
|
@ -65,7 +65,7 @@ export class Slide {
|
|||||||
// execute any scripts
|
// execute any scripts
|
||||||
const script = this._html.querySelector("script");
|
const script = this._html.querySelector("script");
|
||||||
if (script) {
|
if (script) {
|
||||||
this._dataBinding.executeInContext(script.innerText, this._context);
|
this._dataBinding.executeInContext(script.innerText, this._context, true);
|
||||||
this._dataBinding.bindLists(this._html, this._context);
|
this._dataBinding.bindLists(this._html, this._context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,30 @@
|
|||||||
<title>Data-Binding Example 1</title>
|
<title>Data-Binding Example 1</title>
|
||||||
<h1>Data-Binding Example</h1>
|
<h1>Data-Binding Example</h1>
|
||||||
|
<label for="first"><div>Number:</div><input type="text" id="first"/></label>
|
||||||
|
<label for="second"><div>Multiplied by Number:</div><input type="text" id="second"/></label>
|
||||||
|
<label for="result"><div>Result:</div><input type="text" id="result" disabled/></label>
|
||||||
|
<label for="firstName"><div>First Name:</div><input type="text" id="firstName"/></label>
|
||||||
|
<label for="lastName"><div>Last Name:</div><input type="text" id="lastName"/></label>
|
||||||
|
<label for="fullName"><div>Full Name:</div><input type="text" id="fullName" disabled/></label>
|
||||||
|
<br/>
|
||||||
<ul>
|
<ul>
|
||||||
<li repeat="list" class="appear">{{item.idx}} — {{item.value}}</li>
|
<li repeat="list" class="appear">{{item.idx}} — {{item.value}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<script>
|
<script>
|
||||||
this.list = [{idx: 0, value:"one"}, {idx: 1, value:"two"}, {idx: 2, value:"three"}];
|
this.list = [{idx: 0, value:"one"}, {idx: 1, value:"two"}, {idx: 2, value:"three"}];
|
||||||
|
const bindings = () => {
|
||||||
|
var n1 = this.observable(2);
|
||||||
|
var n2 = this.observable(2);
|
||||||
|
var result = this.computed(() => n1.value*n2.value, [n1, n2]);
|
||||||
|
var first = this.observable("Jeremy");
|
||||||
|
var last = this.observable("");
|
||||||
|
var full = this.computed(() => `${first.value} ${last.value}`.trim(), [first, last]);
|
||||||
|
this.bindValue(document.getElementById("first"), n1);
|
||||||
|
this.bindValue(document.getElementById("second"), n2);
|
||||||
|
this.bindValue(document.getElementById("result"), result);
|
||||||
|
this.bindValue(document.getElementById("firstName"), first);
|
||||||
|
this.bindValue(document.getElementById("lastName"), last);
|
||||||
|
this.bindValue(document.getElementById("fullName"), full);
|
||||||
|
};
|
||||||
|
setTimeout(bindings, 0);
|
||||||
</script>
|
</script>
|
Loading…
Reference in New Issue
Block a user