2016-05-12 01:03:11 +00:00
|
|
|
"use strict";
|
|
|
|
// UI (DOM + event) support for Syndicate
|
|
|
|
//
|
|
|
|
// The previous dom-driver.js + jquery-driver.js approach worked kind
|
|
|
|
// of OK, but started to fall down in a couple of areas: Added UI
|
|
|
|
// fragments lacked identity, so would sometimes move around the tree
|
|
|
|
// unexpectedly as they were updated; and there was no convenient
|
|
|
|
// means of scoping event selectors to within a particular UI
|
|
|
|
// fragment, despite various attempts at this.
|
|
|
|
//
|
|
|
|
// The design of this module aims to take these lessons into account.
|
|
|
|
|
2016-05-12 19:19:37 +00:00
|
|
|
var Immutable = require('immutable');
|
2016-05-12 01:03:11 +00:00
|
|
|
var Patch = require("./patch.js");
|
|
|
|
var Trie = require("./trie.js");
|
|
|
|
var DemandMatcher = require('./demand-matcher.js').DemandMatcher;
|
|
|
|
var Struct = require('./struct.js');
|
|
|
|
var RandomID = require('./randomid.js');
|
|
|
|
|
|
|
|
var Dataspace_ = require("./dataspace.js");
|
|
|
|
var Dataspace = Dataspace_.Dataspace;
|
|
|
|
var __ = Dataspace_.__;
|
|
|
|
var _$ = Dataspace_._$;
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Protocol
|
|
|
|
|
|
|
|
// Message. Interest in this causes event listeners to be added for
|
|
|
|
// the given eventType to all nodes matching the given selector *at
|
|
|
|
// the time of the subscription*. As nodes *from this library* come
|
|
|
|
// and go, they will have event handlers installed and removed as
|
|
|
|
// well. WARNING: The simple implementation below currently scans the
|
|
|
|
// whole document anytime a change is signalled; in future, it may not
|
|
|
|
// do such a scan.
|
|
|
|
var globalEvent = Struct.makeConstructor('globalEvent', ['selector', 'eventType', 'event']);
|
|
|
|
|
2016-05-12 19:56:42 +00:00
|
|
|
// Message. As globalEvent, but instead of using a selector to choose
|
|
|
|
// target DOM nodes, attaches an event handler to the browser "window"
|
|
|
|
// object itself.
|
|
|
|
var windowEvent = Struct.makeConstructor('windowEvent', ['eventType', 'event']);
|
|
|
|
|
2016-05-12 01:03:11 +00:00
|
|
|
// Message. Like globalEvent, but applies only within the scope of the
|
|
|
|
// UI fragment identified.
|
|
|
|
var uiEvent = Struct.makeConstructor('uiEvent', ['fragmentId', 'selector', 'eventType', 'event']);
|
|
|
|
|
|
|
|
// Assertion. Causes the setup of DOM nodes corresponding to the given
|
|
|
|
// HTML fragment, as immediate children of all nodes named by the
|
|
|
|
// given selector that exist at the time of assertion.
|
|
|
|
var uiFragment = Struct.makeConstructor('uiFragment', ['fragmentId', 'selector', 'html']);
|
|
|
|
|
|
|
|
// Assertion. Asserted by respondent to a given uiFragment.
|
|
|
|
var uiFragmentExists = Struct.makeConstructor('uiFragmentExists', ['fragmentId']);
|
|
|
|
|
2016-05-13 00:11:22 +00:00
|
|
|
// Assertion. Current "location hash" -- the "#/path/part" fragment at
|
|
|
|
// the end of window.location.
|
|
|
|
var locationHash = Struct.makeConstructor('locationHash', ['value']);
|
|
|
|
|
|
|
|
// Message. Causes window.location to be updated to have the given new
|
|
|
|
// "location hash" value.
|
|
|
|
var setLocationHash = Struct.makeConstructor('setLocationHash', ['value']);
|
|
|
|
|
2016-05-12 01:03:11 +00:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// ID allocators
|
|
|
|
|
|
|
|
var moduleInstance = RandomID.randomId(16, true);
|
|
|
|
|
|
|
|
var nextFragmentIdNumber = 0;
|
|
|
|
function newFragmentId() {
|
|
|
|
return 'ui_' + moduleInstance + '_' + (nextFragmentIdNumber++);
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2016-05-13 00:11:22 +00:00
|
|
|
function spawnUIDriver(options) {
|
|
|
|
options = options || {};
|
|
|
|
|
2016-05-12 01:03:11 +00:00
|
|
|
var globalEventProj = globalEvent(_$('selector'), _$('eventType'), __);
|
|
|
|
Dataspace.spawn(
|
|
|
|
new DemandMatcher([Patch.observe(globalEventProj)],
|
|
|
|
[Patch.advertise(globalEventProj)],
|
|
|
|
{
|
|
|
|
onDemandIncrease: function (c) {
|
|
|
|
Dataspace.spawn(new GlobalEventSupply(c.selector, c.eventType));
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
2016-05-12 19:56:42 +00:00
|
|
|
var windowEventProj = windowEvent(_$('eventType'), __);
|
|
|
|
Dataspace.spawn(
|
|
|
|
new DemandMatcher([Patch.observe(windowEventProj)],
|
|
|
|
[Patch.advertise(windowEventProj)],
|
|
|
|
{
|
|
|
|
onDemandIncrease: function (c) {
|
|
|
|
Dataspace.spawn(new WindowEventSupply(c.eventType));
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
2016-05-12 01:03:11 +00:00
|
|
|
Dataspace.spawn(
|
|
|
|
new DemandMatcher([uiFragment(_$('fragmentId'), __, __)],
|
|
|
|
[uiFragmentExists(_$('fragmentId'))],
|
|
|
|
{
|
|
|
|
onDemandIncrease: function (c) {
|
|
|
|
Dataspace.spawn(new UIFragment(c.fragmentId));
|
|
|
|
}
|
|
|
|
}));
|
2016-05-13 00:11:22 +00:00
|
|
|
|
|
|
|
Dataspace.spawn(new LocationHashTracker(options.defaultLocationHash || '/'));
|
2016-05-12 01:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
function GlobalEventSupply(selector, eventType) {
|
|
|
|
this.selector = selector;
|
|
|
|
this.eventType = eventType;
|
|
|
|
this.demandPat = Patch.observe(globalEvent(this.selector, this.eventType, __));
|
|
|
|
}
|
|
|
|
|
|
|
|
GlobalEventSupply.prototype.boot = function () {
|
|
|
|
var self = this;
|
|
|
|
this.handlerClosure = Dataspace.wrap(function(e) { return self.handleDomEvent(e); });
|
|
|
|
this.updateEventListeners(true);
|
|
|
|
|
|
|
|
return Patch.sub(this.demandPat) // track demand
|
|
|
|
.andThen(Patch.sub(uiFragmentExists(__))) // track new fragments
|
|
|
|
.andThen(Patch.pub(globalEvent(this.selector, this.eventType, __))) // indicate our presence
|
|
|
|
;
|
|
|
|
};
|
|
|
|
|
|
|
|
GlobalEventSupply.prototype.updateEventListeners = function (install) {
|
|
|
|
var nodes = document.querySelectorAll(this.selector);
|
|
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
|
|
var n = nodes[i];
|
|
|
|
// addEventListener and removeEventListener are apparently idempotent.
|
|
|
|
if (install) {
|
|
|
|
n.addEventListener(cleanEventType(this.eventType), this.handlerClosure);
|
|
|
|
} else {
|
|
|
|
n.removeEventListener(cleanEventType(this.eventType), this.handlerClosure);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
GlobalEventSupply.prototype.trapexit = function () {
|
|
|
|
console.log('GlobalEventSupply trapexit running', this.selector, this.eventType);
|
|
|
|
this.updateEventListeners(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
GlobalEventSupply.prototype.handleDomEvent = function (event) {
|
|
|
|
Dataspace.send(globalEvent(this.selector, this.eventType, event));
|
|
|
|
return dealWithPreventDefault(this.eventType, event);
|
|
|
|
};
|
|
|
|
|
|
|
|
GlobalEventSupply.prototype.handleEvent = function (e) {
|
|
|
|
this.updateEventListeners(true);
|
|
|
|
// TODO: don't be so crude about this ^. On the one hand, this lets
|
|
|
|
// us ignore uiFragmentExists records coming and going; on the other
|
|
|
|
// hand, we do potentially a lot of redundant work.
|
|
|
|
if (e.type === 'stateChange' && e.patch.project(this.demandPat).hasRemoved()) {
|
|
|
|
Dataspace.exit(); // trapexit will uninstall event listeners
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2016-05-12 19:56:42 +00:00
|
|
|
function WindowEventSupply(eventType) {
|
|
|
|
this.eventType = eventType;
|
|
|
|
this.demandPat = Patch.observe(windowEvent(this.eventType, __));
|
|
|
|
}
|
|
|
|
|
|
|
|
WindowEventSupply.prototype.boot = function () {
|
|
|
|
var self = this;
|
|
|
|
this.handlerClosure = Dataspace.wrap(function(e) { return self.handleDomEvent(e); });
|
|
|
|
this.updateEventListeners(true);
|
|
|
|
|
|
|
|
return Patch.sub(this.demandPat) // track demand
|
|
|
|
.andThen(Patch.pub(windowEvent(this.eventType, __))) // indicate our presence
|
|
|
|
;
|
|
|
|
};
|
|
|
|
|
|
|
|
WindowEventSupply.prototype.updateEventListeners = function (install) {
|
|
|
|
if (install) {
|
|
|
|
window.addEventListener(cleanEventType(this.eventType), this.handlerClosure);
|
|
|
|
} else {
|
|
|
|
window.removeEventListener(cleanEventType(this.eventType), this.handlerClosure);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
WindowEventSupply.prototype.trapexit = function () {
|
|
|
|
console.log('WindowEventSupply trapexit running', this.eventType);
|
|
|
|
this.updateEventListeners(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
WindowEventSupply.prototype.handleDomEvent = function (event) {
|
|
|
|
Dataspace.send(windowEvent(this.eventType, event));
|
|
|
|
return dealWithPreventDefault(this.eventType, event);
|
|
|
|
};
|
|
|
|
|
|
|
|
WindowEventSupply.prototype.handleEvent = function (e) {
|
|
|
|
if (e.type === 'stateChange' && e.patch.project(this.demandPat).hasRemoved()) {
|
|
|
|
Dataspace.exit(); // trapexit will uninstall event listeners
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2016-05-12 01:03:11 +00:00
|
|
|
function UIFragment(fragmentId) {
|
|
|
|
this.fragmentId = fragmentId;
|
|
|
|
this.demandProj = uiFragment(this.fragmentId, _$('selector'), _$('html'));
|
|
|
|
this.eventDemandProj =
|
|
|
|
Patch.observe(uiEvent(this.fragmentId, _$('selector'), _$('eventType'), __));
|
|
|
|
|
|
|
|
this.currentAnchorNodes = [];
|
|
|
|
this.currentSelector = null;
|
|
|
|
this.currentHtml = null;
|
|
|
|
|
2016-05-12 19:19:37 +00:00
|
|
|
this.currentEventRegistrations = Immutable.Map();
|
|
|
|
// ^ Map from (Map of selector/eventType) to closure.
|
2016-05-12 01:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
UIFragment.prototype.boot = function () {
|
|
|
|
return Patch.sub(Trie.projectionToPattern(this.demandProj)) // track demand
|
|
|
|
.andThen(Patch.assert(uiFragmentExists(this.fragmentId))) // assert presence
|
|
|
|
.andThen(Patch.sub(Trie.projectionToPattern(this.eventDemandProj)))
|
|
|
|
// ^ track demand for fragment-specific events
|
|
|
|
;
|
|
|
|
};
|
|
|
|
|
|
|
|
UIFragment.prototype.trapexit = function () {
|
|
|
|
console.log('UIFragment trapexit running', this.fragmentId);
|
|
|
|
this.updateContent(null, null);
|
|
|
|
};
|
|
|
|
|
|
|
|
function brandNode(n, fragmentId, brandValue) {
|
|
|
|
if ('dataset' in n) {
|
|
|
|
// html element nodes etc.
|
|
|
|
n.dataset[fragmentId] = brandValue;
|
|
|
|
} else {
|
|
|
|
// text nodes, svg nodes, etc etc.
|
|
|
|
n[fragmentId] = brandValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getBrand(n, fragmentId) {
|
|
|
|
if ('dataset' in n && n.dataset[fragmentId]) return n.dataset[fragmentId];
|
|
|
|
if (n[fragmentId]) return n[fragmentId];
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function findInsertionPoint(n, fragmentId) {
|
|
|
|
for (var i = 0; i < n.childNodes.length; i++) {
|
|
|
|
var c = n.childNodes[i];
|
|
|
|
if (getBrand(c, fragmentId)) return c;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function htmlToNodes(html) {
|
|
|
|
var e = document.createElement('arbitrarycontainer');
|
|
|
|
e.innerHTML = html;
|
|
|
|
return Array.prototype.slice.call(e.childNodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
UIFragment.prototype.updateContent = function (newSelector, newHtml) {
|
|
|
|
var self = this;
|
|
|
|
var newBrand = '' + (Date.now());
|
|
|
|
|
|
|
|
var newAnchors = (newSelector !== null)
|
|
|
|
? Array.prototype.slice.call(document.querySelectorAll(newSelector))
|
|
|
|
: [];
|
|
|
|
|
|
|
|
newAnchors.forEach(function (anchorNode) {
|
|
|
|
var insertionPoint = findInsertionPoint(anchorNode, self.fragmentId);
|
|
|
|
htmlToNodes(newHtml).forEach(function (newNode) {
|
|
|
|
brandNode(newNode, self.fragmentId, newBrand);
|
|
|
|
anchorNode.insertBefore(newNode, insertionPoint);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
self.currentAnchorNodes.forEach(function (anchorNode) {
|
|
|
|
var insertionPoint = findInsertionPoint(anchorNode, self.fragmentId);
|
|
|
|
while (insertionPoint) {
|
|
|
|
var nextNode = insertionPoint.nextSibling;
|
|
|
|
var b = getBrand(insertionPoint, self.fragmentId);
|
|
|
|
if (!b) break; // we know all our-brand nodes will be adjacent
|
|
|
|
if (b !== newBrand) {
|
|
|
|
insertionPoint.parentNode.removeChild(insertionPoint);
|
|
|
|
}
|
|
|
|
insertionPoint = nextNode;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
self.currentAnchorNodes = newAnchors;
|
|
|
|
self.currentSelector = newSelector;
|
|
|
|
self.currentHtml = newHtml;
|
2016-05-12 19:19:37 +00:00
|
|
|
|
|
|
|
self.currentEventRegistrations.forEach(function (_handlerClosure, key) {
|
|
|
|
self.updateEventListeners(key.toObject(), true); // (re)install event listeners
|
|
|
|
});
|
2016-05-12 01:03:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
UIFragment.prototype.handleEvent = function (e) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
if (e.type === 'stateChange') {
|
|
|
|
var fragmentChanges = e.patch.projectObjects(self.demandProj);
|
|
|
|
fragmentChanges[0].forEach(function (c) { self.updateContent(c.selector, c.html); });
|
|
|
|
fragmentChanges[1].forEach(function (c) {
|
|
|
|
if (c.selector === self.currentSelector && c.html === self.currentHtml) {
|
|
|
|
Dataspace.exit(); // trapexit will remove nodes
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var eventDemand = e.patch.projectObjects(self.eventDemandProj);
|
|
|
|
eventDemand[0].forEach(function (c) { self.updateEventListeners(c, true); })
|
|
|
|
eventDemand[1].forEach(function (c) { self.updateEventListeners(c, false); })
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
UIFragment.prototype.getEventClosure = function (c) {
|
|
|
|
var self = this;
|
2016-05-12 19:19:37 +00:00
|
|
|
var key = Immutable.Map(c);
|
|
|
|
if (!self.currentEventRegistrations.has(key)) {
|
|
|
|
var handlerClosure = Dataspace.wrap(function (e) { return self.handleDomEvent(c, e); });
|
|
|
|
self.currentEventRegistrations = self.currentEventRegistrations.set(key, handlerClosure);
|
2016-05-12 01:03:11 +00:00
|
|
|
}
|
2016-05-12 19:19:37 +00:00
|
|
|
return self.currentEventRegistrations.get(key);
|
2016-05-12 01:03:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
UIFragment.prototype.clearEventClosure = function (c) {
|
2016-05-12 19:19:37 +00:00
|
|
|
var key = Immutable.Map(c);
|
|
|
|
this.currentEventRegistrations = this.currentEventRegistrations.remove(key);
|
2016-05-12 01:03:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
UIFragment.prototype.updateEventListeners = function (c, install) {
|
|
|
|
var self = this;
|
|
|
|
var handlerClosure = self.getEventClosure(c);
|
|
|
|
|
|
|
|
self.currentAnchorNodes.forEach(function (anchorNode) {
|
|
|
|
var uiNode = findInsertionPoint(anchorNode, self.fragmentId);
|
|
|
|
while (uiNode && getBrand(uiNode, self.fragmentId)) {
|
2016-05-12 18:46:39 +00:00
|
|
|
if ('querySelectorAll' in uiNode) {
|
|
|
|
var nodes = uiNode.querySelectorAll(c.selector);
|
|
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
|
|
var n = nodes[i];
|
|
|
|
// addEventListener and removeEventListener are apparently idempotent.
|
|
|
|
if (install) {
|
|
|
|
n.addEventListener(cleanEventType(c.eventType), handlerClosure);
|
|
|
|
} else {
|
|
|
|
n.removeEventListener(cleanEventType(c.eventType), handlerClosure);
|
|
|
|
}
|
2016-05-12 01:03:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
uiNode = uiNode.nextSibling;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!install) {
|
2016-05-12 19:19:37 +00:00
|
|
|
this.clearEventClosure(c);
|
2016-05-12 01:03:11 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
UIFragment.prototype.handleDomEvent = function (c, e) {
|
|
|
|
Dataspace.send(uiEvent(this.fragmentId, c.selector, c.eventType, e));
|
2016-05-12 18:49:09 +00:00
|
|
|
return dealWithPreventDefault(c.eventType, e);
|
2016-05-12 01:03:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
function escapeDataAttributeName(s) {
|
|
|
|
// Per https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset,
|
|
|
|
// the rules seem to be:
|
|
|
|
//
|
|
|
|
// 1. Must not contain a dash immediately followed by an ASCII lowercase letter
|
|
|
|
// 2. Must not contain anything other than:
|
|
|
|
// - letters
|
|
|
|
// - numbers
|
|
|
|
// - dash, dot, colon, underscore
|
|
|
|
//
|
|
|
|
// I'm not implementing this exactly - I'm escaping some things that
|
|
|
|
// don't absolutely need escaping, because it's simpler and I don't
|
|
|
|
// yet need to undo this transformation.
|
|
|
|
|
|
|
|
var result = '';
|
|
|
|
for (var i = 0; i < s.length; i++) {
|
|
|
|
var c = s[i];
|
|
|
|
if (c >= 'a' && c <= 'z') { result = result + c; continue; }
|
|
|
|
if (c >= 'A' && c <= 'Z') { result = result + c; continue; }
|
|
|
|
if (c >= '0' && c <= '9') { result = result + c; continue; }
|
|
|
|
if (c === '.' || c === ':') { result = result + c; continue; }
|
|
|
|
|
|
|
|
c = c.charCodeAt(0);
|
|
|
|
result = result + '_' + c + '_';
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function dealWithPreventDefault(eventType, event) {
|
|
|
|
var shouldPreventDefault = eventType.charAt(0) !== '+';
|
|
|
|
if (shouldPreventDefault) event.preventDefault();
|
|
|
|
return !shouldPreventDefault;
|
|
|
|
}
|
|
|
|
|
|
|
|
function cleanEventType(eventType) {
|
|
|
|
return (eventType.charAt(0) === '+') ? eventType.slice(1) : eventType;
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
function Anchor(explicitFragmentId) {
|
|
|
|
this.fragmentId =
|
|
|
|
(typeof explicitFragmentId === 'undefined') ? newFragmentId() : explicitFragmentId;
|
|
|
|
this.htmlPattern = uiFragment(this.fragmentId, __, __);
|
|
|
|
this.eventPattern = uiEvent(this.fragmentId, __, __, __);
|
|
|
|
}
|
|
|
|
|
2016-05-12 02:13:35 +00:00
|
|
|
Anchor.prototype.context = function (/* ... */) {
|
|
|
|
var extn = Array.prototype.slice.call(arguments).map(escapeDataAttributeName).join('__');
|
|
|
|
return new Anchor(this.fragmentId + '__' + extn);
|
2016-05-12 01:03:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Anchor.prototype.html = function (selector, html) {
|
|
|
|
return uiFragment(this.fragmentId, selector, html);
|
|
|
|
};
|
|
|
|
|
|
|
|
Anchor.prototype.event = function (selector, eventType, event) {
|
|
|
|
return uiEvent(this.fragmentId, selector, eventType, event);
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2016-05-13 00:11:22 +00:00
|
|
|
function LocationHashTracker(defaultLocationHash) {
|
|
|
|
this.defaultLocationHash = defaultLocationHash;
|
|
|
|
this.hashValue = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
LocationHashTracker.prototype.boot = function () {
|
|
|
|
var self = this;
|
|
|
|
this.loadHash();
|
|
|
|
this.handlerClosure = Dataspace.wrap(function (e) { self.handleDomEvent(e); });
|
|
|
|
window.addEventListener('hashchange', this.handlerClosure);
|
|
|
|
|
|
|
|
return Patch.assert(locationHash(this.hashValue))
|
|
|
|
.andThen(Patch.sub(setLocationHash(__)));
|
|
|
|
};
|
|
|
|
|
|
|
|
LocationHashTracker.prototype.trapexit = function () {
|
|
|
|
window.removeEventListener('hashchange', this.handlerClosure);
|
|
|
|
};
|
|
|
|
|
|
|
|
LocationHashTracker.prototype.loadHash = function () {
|
|
|
|
this.hashValue = window.location.hash;
|
|
|
|
if (this.hashValue.length && this.hashValue[0] === '#') {
|
|
|
|
this.hashValue = this.hashValue.slice(1);
|
|
|
|
}
|
|
|
|
if (!this.hashValue) {
|
|
|
|
this.hashValue = this.defaultLocationHash;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
LocationHashTracker.prototype.handleDomEvent = function (e) {
|
|
|
|
this.loadHash();
|
|
|
|
Dataspace.stateChange(Patch.retract(locationHash(__))
|
|
|
|
.andThen(Patch.assert(locationHash(this.hashValue))));
|
|
|
|
};
|
|
|
|
|
|
|
|
LocationHashTracker.prototype.handleEvent = function (e) {
|
|
|
|
if (e.type === 'message' && setLocationHash.isClassOf(e.message)) {
|
|
|
|
window.location.hash = e.message[0];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2016-05-12 01:03:11 +00:00
|
|
|
module.exports.newFragmentId = newFragmentId;
|
|
|
|
module.exports.spawnUIDriver = spawnUIDriver;
|
|
|
|
module.exports.Anchor = Anchor;
|
|
|
|
module.exports.globalEvent = globalEvent;
|
2016-05-12 19:56:42 +00:00
|
|
|
module.exports.windowEvent = windowEvent;
|
2016-05-12 01:03:11 +00:00
|
|
|
module.exports.uiEvent = uiEvent;
|
|
|
|
module.exports.uiFragment = uiFragment;
|
|
|
|
module.exports.uiFragmentExists = uiFragmentExists;
|
2016-05-13 00:11:22 +00:00
|
|
|
module.exports.locationHash = locationHash;
|
|
|
|
module.exports.setLocationHash = setLocationHash;
|