Extend bindings with one-way content update bind
This commit is contained in:
parent
9dd8ad007f
commit
0745bfc92e
@ -31,7 +31,6 @@ export default class ConfigViewModel extends ViewModel {
|
||||
constructor(state) {
|
||||
super(config);
|
||||
this.state = state;
|
||||
this.intervalEl = document.querySelector('#interval');
|
||||
this.model.onSetInterval = () => this.setAutoReloadInterval();
|
||||
this.bindAll();
|
||||
this.onChanged('mapApi', (api) => {
|
||||
@ -46,7 +45,6 @@ export default class ConfigViewModel extends ViewModel {
|
||||
ConfigViewModel.reload();
|
||||
});
|
||||
this.onChanged('interval', (interval) => {
|
||||
this.intervalEl.innerHTML = interval.toString();
|
||||
uUtils.setCookie('interval', interval);
|
||||
});
|
||||
}
|
||||
|
@ -45,6 +45,8 @@ export default class TrackViewModel extends ViewModel {
|
||||
autoReload: false,
|
||||
/** @type {string} */
|
||||
inputFile: false,
|
||||
/** @type {string} */
|
||||
summary: false,
|
||||
// click handlers
|
||||
/** @type {function} */
|
||||
onReload: null,
|
||||
@ -58,7 +60,6 @@ export default class TrackViewModel extends ViewModel {
|
||||
this.setClickHandlers();
|
||||
/** @type HTMLSelectElement */
|
||||
const listEl = document.querySelector('#track');
|
||||
this.summaryEl = document.querySelector('#summary');
|
||||
this.importEl = document.querySelector('#input-file');
|
||||
this.select = new uSelect(listEl);
|
||||
this.state = state;
|
||||
@ -279,7 +280,7 @@ export default class TrackViewModel extends ViewModel {
|
||||
|
||||
renderSummary() {
|
||||
if (!this.state.currentTrack || !this.state.currentTrack.hasPositions) {
|
||||
this.summaryEl.innerHTML = '';
|
||||
this.model.summary = '';
|
||||
return;
|
||||
}
|
||||
const last = this.state.currentTrack.positions[this.state.currentTrack.length - 1];
|
||||
@ -290,12 +291,12 @@ export default class TrackViewModel extends ViewModel {
|
||||
const dateTime = uUtils.getTimeString(date);
|
||||
const dateString = (date.toDateString() !== today.toDateString()) ? `${dateTime.date}<br>` : '';
|
||||
const timeString = `${dateTime.time}<span style="font-weight:normal">${dateTime.zone}</span>`;
|
||||
this.summaryEl.innerHTML = `
|
||||
this.model.summary = `
|
||||
<div class="menu-title">${lang.strings['latest']}:</div>
|
||||
${dateString}
|
||||
${timeString}`;
|
||||
} else {
|
||||
this.summaryEl.innerHTML = `
|
||||
this.model.summary = `
|
||||
<div class="menu-title">${lang.strings['summary']}</div>
|
||||
<div><img class="icon" alt="${lang.strings['tdistance']}" title="${lang.strings['tdistance']}" src="images/distance.svg"> ${lang.getLocaleDistanceMajor(last.totalMeters, true)}</div>
|
||||
<div><img class="icon" alt="${lang.strings['ttime']}" title="${lang.strings['ttime']}" src="images/time.svg"> ${lang.getLocaleDuration(last.totalSeconds)}</div>`;
|
||||
|
@ -54,7 +54,7 @@ export default class ViewModel {
|
||||
* Creates bidirectional binding between model property and DOM element.
|
||||
* For input elements model property value change triggers change in DOM element and vice versa.
|
||||
* In case of anchor element binding is one way. Model property is callback that will receive click event.
|
||||
* @param key
|
||||
* @param {string} key
|
||||
*/
|
||||
bind(key) {
|
||||
const dataProp = 'bind';
|
||||
@ -63,6 +63,37 @@ export default class ViewModel {
|
||||
const name = element.dataset[dataProp];
|
||||
if (name === key) {
|
||||
if (element instanceof HTMLInputElement || element instanceof HTMLSelectElement) {
|
||||
this.onChangeBind(element, key);
|
||||
} else if (element instanceof HTMLAnchorElement) {
|
||||
this.onClickBind(element, key);
|
||||
} else {
|
||||
this.viewUpdateBind(element, key);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* One way bind: view element click event to view model event handler
|
||||
* @param {HTMLAnchorElement} element
|
||||
* @param {string} key
|
||||
*/
|
||||
onClickBind(element, key) {
|
||||
element.addEventListener('click', (event) => {
|
||||
if (typeof this._model[key] !== 'function') {
|
||||
throw new Error(`Property ${key} is not a callback`);
|
||||
}
|
||||
this._model[key](event);
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Two way bind: view element change event to view model property
|
||||
* @param {(HTMLInputElement|HTMLSelectElement)} element
|
||||
* @param {string} key
|
||||
*/
|
||||
onChangeBind(element, key) {
|
||||
let prop = 'value';
|
||||
let getVal = (val) => val;
|
||||
if (element.type === 'checkbox') {
|
||||
@ -78,15 +109,17 @@ export default class ViewModel {
|
||||
element[prop] = val;
|
||||
}
|
||||
});
|
||||
} else if (element instanceof HTMLAnchorElement) {
|
||||
element.addEventListener('click', (event) => {
|
||||
if (typeof this._model[key] !== 'function') {
|
||||
throw new Error(`Property ${key} is not a callback`);
|
||||
}
|
||||
this._model[key](event);
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* One way bind: view model property to view element content
|
||||
* @param {HTMLElement} element
|
||||
* @param {string} key
|
||||
*/
|
||||
viewUpdateBind(element, key) {
|
||||
uObserve.observe(this.model, key, (content) => {
|
||||
if (element.innerHTML !== content) {
|
||||
element.innerHTML = content;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -106,5 +139,4 @@ export default class ViewModel {
|
||||
unsubscribe(property, callback) {
|
||||
uObserve.unobserve(this.model, property, callback);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ describe('ConfigViewModel tests', () => {
|
||||
|
||||
const fixture = `<div id="fixture">
|
||||
<div class="section">
|
||||
<a id="set-interval" data-bind="onSetInterval"><span id="interval">${config.interval}</span></a>
|
||||
<a id="set-interval" data-bind="onSetInterval"><span id="interval" data-bind="interval">${config.interval}</span></a>
|
||||
</div>
|
||||
<div>
|
||||
<label for="api">api</label>
|
||||
|
@ -65,7 +65,7 @@ describe('TrackViewModel tests', () => {
|
||||
<input id="auto-reload" type="checkbox" data-bind="autoReload">
|
||||
<a id="force-reload" data-bind="onReload">reload</a>
|
||||
</div>
|
||||
<div id="summary" class="section"></div>
|
||||
<div id="summary" class="section" data-bind="summary"></div>
|
||||
<div class="section">
|
||||
<a id="export-kml" class="menu-link" data-bind="onExportKml">kml</a>
|
||||
<a id="export-gpx" class="menu-link" data-bind="onExportGpx">gpx</a>
|
||||
@ -113,7 +113,6 @@ describe('TrackViewModel tests', () => {
|
||||
const trackViewModel = new TrackViewModel(state);
|
||||
// then
|
||||
expect(trackViewModel).toBeInstanceOf(ViewModel);
|
||||
expect(trackViewModel.summaryEl).toBeInstanceOf(HTMLDivElement);
|
||||
expect(trackViewModel.importEl).toBeInstanceOf(HTMLInputElement);
|
||||
expect(trackViewModel.select.element).toBeInstanceOf(HTMLSelectElement);
|
||||
expect(trackViewModel.state).toBe(state);
|
||||
|
@ -152,6 +152,22 @@ describe('ViewModel tests', () => {
|
||||
expect(model[propertyFunction].calls.mostRecent().args[0].target).toBe(anchorElement);
|
||||
});
|
||||
|
||||
it('should bind DOM div element to model property', () => {
|
||||
// given
|
||||
/** @type {HTMLDivElement} */
|
||||
const divElement = uUtils.nodeFromHtml(`<div data-bind="${propertyString}"></div>`);
|
||||
document.body.appendChild(divElement);
|
||||
const newContent = '<span>new value</span>';
|
||||
// when
|
||||
vm.bind(propertyString);
|
||||
// then
|
||||
expect(uObserve.isObserved(vm.model, propertyString)).toBe(true);
|
||||
// when
|
||||
model[propertyString] = newContent;
|
||||
// then
|
||||
expect(divElement.innerHTML).toBe(newContent);
|
||||
});
|
||||
|
||||
it('should start observing model property', () => {
|
||||
// given
|
||||
// eslint-disable-next-line no-empty-function
|
||||
|
Loading…
x
Reference in New Issue
Block a user