diff --git a/css/style.css b/css/style.css index 1ab106e..16120f7 100644 --- a/css/style.css +++ b/css/style.css @@ -4,10 +4,28 @@ body { font-size: 3vh; } -button { +br { + clear: both; +} + +label { + display: block; +} + +label > div, input { + float: left; + width: 40vw; +} + + +button, input { font-size: 3vh; } +input { + padding: 1px; +} + pre { background-color: black; overflow-x: auto; @@ -44,17 +62,21 @@ slide-deck img.expandable:hover { body { font-size: 14px; } - button { + button, input { font-size: 14px; } + + label > div, input { + width: 90vw; + } } @media only screen and (max-width: 1200px) { body { font-size: 2vh; } - button { - font-size: 14px; + button, input { + font-size: 2vh; } } @@ -98,7 +120,7 @@ img { text-align: center; } -slide-deck div { +slide-deck > div { height: 80vh; padding: 1em; } @@ -116,7 +138,7 @@ slide-deck div { .left { text-align: center; } - slide-deck div { + slide-deck > div { height: 75vh; } } diff --git a/js/dataBinding.js b/js/dataBinding.js index 7f64211..1f09d4d 100644 --- a/js/dataBinding.js +++ b/js/dataBinding.js @@ -1,7 +1,12 @@ // @ts-check +import { Observable, Computed } from "./observable.js" + +/** + * Class supports data-binding operations + */ export class DataBinding { - + /** * Simple evaluation * @param {string} js The JavaScript to evaluate @@ -16,10 +21,56 @@ export class DataBinding { * @param {object} context The context (data) to evaluate with * @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); } + /** + * 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 * @param {HTMLElement} elem The parent element to search @@ -39,12 +90,13 @@ export class DataBinding { if (matches) { matches.forEach(match => { match = match.replace("{{", "").replace("}}", ""); - const value = this.executeInContext(`this.${match}`, {item}); - newTemplate = newTemplate.replace(`{{${match}}}`, value); + const value = this.executeInContext(`this.${match}`, { item }); + newTemplate = newTemplate.replace(`{{${match}}}`, value); }); parent.innerHTML += newTemplate; - } + } }); }); } -} \ No newline at end of file +} + diff --git a/js/navigator.js b/js/navigator.js index 27a4772..0fde78d 100644 --- a/js/navigator.js +++ b/js/navigator.js @@ -117,9 +117,11 @@ export class Navigator extends HTMLElement { */ get hasNext() { const host = this.querySelector("div"); - const appear = host.querySelectorAll(".appear"); - if (appear && appear.length) { - return true; + if (host) { + const appear = host.querySelectorAll(".appear"); + if (appear && appear.length) { + return true; + } } return this._currentIndex < (this.totalSlides - 1); } diff --git a/js/observable.js b/js/observable.js new file mode 100644 index 0000000..429ea90 --- /dev/null +++ b/js/observable.js @@ -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"; + } +} \ No newline at end of file diff --git a/js/slide.js b/js/slide.js index e04a37b..f0f4688 100644 --- a/js/slide.js +++ b/js/slide.js @@ -65,7 +65,7 @@ export class Slide { // execute any scripts const script = this._html.querySelector("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); } } diff --git a/slides/data-binding.html b/slides/data-binding.html index 1f325af..a6b6308 100644 --- a/slides/data-binding.html +++ b/slides/data-binding.html @@ -1,8 +1,30 @@ Data-Binding Example 1

Data-Binding Example

+ + + + + + +
\ No newline at end of file