Skip to content

Commit add4b60

Browse files
committed
Add "preferDom" option to bindings that should always use a DOM merge# Please enter the commit message for your changes. Lines starting
1 parent f41e552 commit add4b60

File tree

3 files changed

+85
-35
lines changed

3 files changed

+85
-35
lines changed

bindings/default.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,16 @@
5050
priority: -10,
5151
canBind: {},
5252
html: function (model, tag, attrs, context) {
53-
var html = '<span class="json-links">';
54-
model.links().forEach(function (link) {
55-
var stateUrl = context.urlForState(link.href, {});
56-
html += '<a class="json-link" ajax href="' + stateUrl.escapeHtml() + '">' + link.rel.escapeHtml() + ': ' + link.href.escapeHtml() + '</a> ';
57-
});
58-
html += '</span>';
53+
var html = '';
54+
var links = model.links();
55+
if (links.length) {
56+
html += '<span class="json-links">';
57+
links.forEach(function (link) {
58+
var stateUrl = context.urlForState(link.href, {});
59+
html += '<a class="json-link" ajax href="' + stateUrl.escapeHtml() + '">' + link.rel.escapeHtml() + ': ' + link.href.escapeHtml() + '</a> ';
60+
});
61+
html += '</span>';
62+
}
5963
html += model.html();
6064
return html;
6165
}

model-bind.js

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@
237237
if (!(this instanceof Binding)) return new Binding(bindObj, registerIndex);
238238
var thisBinding = this;
239239
this.registerIndex = registerIndex;
240+
241+
this.preferDom = !!bindObj.preferDom;
240242

241243
if (typeof bindObj.canBind === 'string' || Array.isArray(bindObj.canBind)) {
242244
bindObj.canBind = {schema: bindObj.canBind};
@@ -395,11 +397,11 @@
395397
}
396398
return this._concatOptions;
397399
},
398-
addHtml: function (canBind, htmlFunc) {
399-
return this.add({
400-
canBind: canBind,
401-
html: htmlFunc
402-
});
400+
addHtml: function (canBind, htmlFunc, extra) {
401+
var bindObj = Object.create(extra || {});
402+
bindObj.canBind = canBind;
403+
bindObj.html = htmlFunc;
404+
return this.add(bindObj);
403405
},
404406
add: function (bindObj) {
405407
this._state++;
@@ -520,24 +522,14 @@
520522
api.bindings = new Bindings();
521523

522524
api.navigateTo = function (href) {
523-
href = resolveUrl(window.location.href, href);
524-
var loc = window.location.href;
525-
if (href === loc) return;
526-
var locParsed = parseUrl(loc);
527-
var domain = locParsed.protocol + locParsed.authority;
528-
var path = window.location.href.replace(/[#?].*/g, '');
529-
if (href.substring(0, path.length) === path) {
530-
href = href.substring(path.length);
531-
} else if (href.substring(0, domain.length) === domain) {
532-
href = href.substring(domain.length);
533-
} else {
525+
var relative = api.util.url.relative(window.location.href, href);
526+
if (relative === href) {
534527
window.location.href = href;
535528
return;
536-
}
537-
if (typeof history === 'object' && typeof history.pushState === 'function') {
538-
history.pushState(null, null, href);
529+
} else if (typeof history === 'object' && typeof history.pushState === 'function') {
530+
history.pushState(null, null, relative);
539531
} else {
540-
window.location.href = '#' + encodeURI(href).replace(/#/g, '%23');
532+
window.location.href = '#' + encodeURI(relative).replace(/#/g, '%23');
541533
}
542534
};
543535

@@ -561,7 +553,10 @@
561553
this.includeDataProperties = false;
562554

563555
this.urlForState = function (resourceUrl, newUiState) {
564-
return '?json=' + encodeURIComponent(resourceUrl);
556+
if (typeof window === 'object' && window.location && typeof window.location.href === 'string') {
557+
resourceUrl = api.util.url.relative(window.location.href, resourceUrl);
558+
}
559+
return api.util.url.encodeQuery({json: resourceUrl}) || '?';
565560
};
566561
this.stateForUrl = function (url) {
567562
var parsed = api.util.url.parse(url);
@@ -588,12 +583,17 @@
588583
monitorLocation: function (element) {
589584
var thisContext = this;
590585

586+
var emitter = new api.EventEmitter();
587+
591588
var getHref = function () {
592589
return window.location.href;
593590
};
594591

595592
var oldHref = null;
593+
var pending = false;
594+
var isFirst = true;
596595
var updateFromLocation = function () {
596+
if (pending) return;
597597
var href = getHref();
598598
if (href === oldHref) return;
599599
oldHref = href;
@@ -603,26 +603,31 @@
603603
console.log('New URL:', newHref);
604604

605605
var state = thisContext.stateForUrl(newHref);
606-
var resourceUrl = state[0], uiState = state[1];
606+
var resourceUrl = state[0], uiState = state[1] || {};
607607
console.log('New state:', resourceUrl, uiState);
608608
if (resourceUrl) {
609+
pending = true;
610+
emitter.emit('change', resourceUrl, uiState, isFirst);
609611
// Reset the UI state, don't bother trying to change it
610-
thisContext.ui = thisContext._dataStore.create(state[1] || {});
611-
thisContext.bind(state[0], element);
612+
thisContext.ui = thisContext._dataStore.create(uiState);
613+
thisContext.bind(resourceUrl, element, function () {
614+
emitter.emit('change-done', resourceUrl, uiState);
615+
pending = false;
616+
});
612617
} else {
618+
emitter.emit('ui', uiState);
613619
// Update the UI state, leaving the URL the same
614620
thisContext.ui.set('', uiState);
615621
}
622+
isFirst = false;
616623
}
617624

618625
if (typeof history === 'object' && typeof history.pushState === 'function') {
619626
window.onpopstate = updateFromLocation;
620627
}
621628

622629
var interval = setInterval(updateFromLocation, 100);
623-
return function () {
624-
clearInterval(interval);
625-
};
630+
return emitter;
626631
},
627632
navigateTo: function (href) {
628633
api.navigateTo(href);
@@ -804,6 +809,20 @@
804809
element.boundBinding = binding;
805810
element.boundContext = context;
806811

812+
if (binding.preferDom) {
813+
model.whenReady(function () {
814+
thisContext._updateDom(element, tag, attrs, function (error) {
815+
binding.bindDom(context, model, element);
816+
if (oldState !== model._root.state) {
817+
console.log('Model changed during initial render:', model.url());
818+
return thisContext._updateDom(element, tag, attrs, callback);
819+
}
820+
callback(error);
821+
});
822+
});
823+
return;
824+
}
825+
807826
function htmlReady(error) {
808827
scanForChildBindings(element, context, function (err) {
809828
binding.bindDom(context, model, element);

model.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,24 @@
4848
}
4949
function parseQuery(queryString) {
5050
var result = {};
51-
(queryString.match(/(^\?|&)([^&]+)/g) || []).forEach(function (part) {
51+
(queryString.match(/(^\??|&)([^&]+)/g) || []).forEach(function (part) {
5252
part = part.substring(1);
5353
var key = part.split('=', 1)[0];
5454
var value = part.substring(key.length + 1);
5555
result[decodeURIComponent(key)] = decodeURIComponent(value);
5656
});
5757
return result;
5858
}
59+
function encodeQuery(query) {
60+
var parts = [];
61+
for (var key in query) {
62+
parts.push(encodeURIComponent(key) + '=' + encodeQueryComponent(query[key]));
63+
}
64+
return parts.length ? ('?' + parts.join('&')) : '';
65+
}
66+
function encodeQueryComponent(str) {
67+
return encodeURIComponent(str).replace(/%2F/gi, '/');
68+
}
5969
var asap = (typeof process === 'object' && typeof process.nextTick === 'function') ? process.nextTick.bind(process) : function (func) {
6070
setTimeout(func, 0);
6171
};
@@ -84,6 +94,20 @@
8494

8595
var parseUrl = schema2js.util.parseUrl;
8696
var resolveUrl = schema2js.util.resolveUrl;
97+
var relativeUrl = function (base, href) {
98+
href = resolveUrl(window.location.href, href);
99+
var loc = window.location.href;
100+
if (href === loc) return;
101+
var locParsed = parseUrl(loc);
102+
var domain = locParsed.protocol + locParsed.authority;
103+
var path = window.location.href.replace(/[#?].*/g, '').replace(/\/$/, '');
104+
if (href.substring(0, path.length) === path) {
105+
href = href.substring(path.length);
106+
} else if (href.substring(0, domain.length) === domain) {
107+
href = href.substring(domain.length);
108+
}
109+
return href;
110+
};
87111

88112
api.util = {
89113
pointerEscape: pointerEscape,
@@ -93,7 +117,10 @@
93117
url: {
94118
parse: parseUrl,
95119
resolve: resolveUrl,
96-
parseQuery: parseQuery
120+
relative: relativeUrl,
121+
parseQuery: parseQuery,
122+
encodeQuery: encodeQuery,
123+
encodeQueryComponent: encodeQueryComponent
97124
},
98125
timer: {
99126
asap: asap,

0 commit comments

Comments
 (0)