Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement onstate and onupdate hooks #1344

Merged
merged 9 commits into from
Apr 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/generators/Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,14 @@ export default class Generator {
addDeclaration('ondestroy', templateProperties.ondestroy.value);
}

if (templateProperties.onstate && dom) {
addDeclaration('onstate', templateProperties.onstate.value);
}

if (templateProperties.onupdate && dom) {
addDeclaration('onupdate', templateProperties.onupdate.value);
}

if (templateProperties.preload) {
addDeclaration('preload', templateProperties.preload.value);
}
Expand Down
23 changes: 18 additions & 5 deletions src/generators/dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ export default function dom(

initialState.push(`options.data`);

const hasInitHooks = !!(templateProperties.oncreate || templateProperties.onstate || templateProperties.onupdate);

const constructorBody = deindent`
${options.dev && `this._debugName = '${debugName}';`}
${options.dev && !generator.customElement &&
Expand Down Expand Up @@ -199,6 +201,9 @@ export default function dom(
${generator.bindingGroups.length &&
`this._bindingGroups = [${Array(generator.bindingGroups.length).fill('[]').join(', ')}];`}

${templateProperties.onstate && `this._handlers.state = [%onstate];`}
${templateProperties.onupdate && `this._handlers.update = [%onupdate];`}

${(templateProperties.ondestroy || storeProps.length) && (
`this._handlers.destroy = [${
[templateProperties.ondestroy && `%ondestroy`, storeProps.length && `@removeFromStore`].filter(Boolean).join(', ')
Expand All @@ -216,9 +221,17 @@ export default function dom(
`if (!document.getElementById("${generator.stylesheet.id}-style")) @add_css();`)
}

${templateProperties.oncreate && `var _oncreate = %oncreate.bind(this);`}
${hasInitHooks && deindent`
var self = this;
var _oncreate = function() {
var changed = { ${expectedProperties.map(p => `${p}: 1`).join(', ')} };
${templateProperties.onstate && `%onstate.call(self, { changed: changed, current: self._state });`}
${templateProperties.oncreate && `%oncreate.call(self);`}
self.fire("update", { changed: changed, current: self._state });
};
`}

${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent`
${(hasInitHooks || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent`
if (!options.root) {
this._oncreate = [];
${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`}
Expand All @@ -230,7 +243,7 @@ export default function dom(

this._fragment = @create_main_fragment(this, this._state);

${(templateProperties.oncreate) && deindent`
${hasInitHooks && deindent`
this.root._oncreate.push(_oncreate);
`}

Expand All @@ -253,10 +266,10 @@ export default function dom(
`}
this._mount(options.target, options.anchor);

${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent`
${(generator.hasComponents || generator.hasComplexBindings || hasInitHooks || generator.hasIntroTransitions) && deindent`
${generator.hasComponents && `this._lock = true;`}
${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`}
${(generator.hasComponents || templateProperties.oncreate) && `@callAll(this._oncreate);`}
${(generator.hasComponents || hasInitHooks) && `@callAll(this._oncreate);`}
${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`}
${generator.hasComponents && `this._lock = false;`}
`}
Expand Down
1 change: 1 addition & 0 deletions src/generators/nodes/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ export default class Component extends Node {
hasStoreBindings && 'newStoreState = {}',
].filter(Boolean).join(', ');

// TODO use component.on('state', ...) instead of _bind
componentInitProperties.push(deindent`
_bind: function(changed, childState) {
var ${initialisers};
Expand Down
55 changes: 16 additions & 39 deletions src/shared/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,34 +35,19 @@ export function _differsImmutable(a, b) {
return a != a ? b == b : a !== b;
}

export function dispatchObservers(component, group, changed, newState, oldState) {
for (var key in group) {
if (!changed[key]) continue;

var newValue = newState[key];
var oldValue = oldState[key];

var callbacks = group[key];
if (!callbacks) continue;

for (var i = 0; i < callbacks.length; i += 1) {
var callback = callbacks[i];
if (callback.__calling) continue;

callback.__calling = true;
callback.call(component, newValue, oldValue);
callback.__calling = false;
}
}
}

export function fire(eventName, data) {
var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return;

for (var i = 0; i < handlers.length; i += 1) {
handlers[i].call(this, data);
var handler = handlers[i];

if (!handler.__calling) {
handler.__calling = true;
handler.call(this, data);
handler.__calling = false;
}
}
}

Expand All @@ -71,7 +56,6 @@ export function get(key) {
}

export function init(component, options) {
component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject();
component._bind = options._bind;

Expand All @@ -81,27 +65,20 @@ export function init(component, options) {
}

export function observe(key, callback, options) {
var group = options && options.defer
? this._observers.post
: this._observers.pre;

(group[key] || (group[key] = [])).push(callback);
var fn = callback.bind(this);

if (!options || options.init !== false) {
callback.__calling = true;
callback.call(this, this._state[key]);
callback.__calling = false;
fn(this.get()[key], undefined);
}

return {
cancel: function() {
var index = group[key].indexOf(callback);
if (~index) group[key].splice(index, 1);
}
};
return this.on(options && options.defer ? 'update' : 'state', function(event) {
if (event.changed[key]) fn(event.current[key], event.previous && event.previous[key]);
});
}

export function observeDev(key, callback, options) {
console.warn("this.observe(key, (newValue, oldValue) => {...}) is deprecated. Use\n\n // runs before DOM updates\n this.on('state', ({ changed, current, previous }) => {...});\n\n // runs after DOM updates\n this.on('update', ...);\n\n...or add the observe method from the svelte-extras package");

var c = (key = '' + key).search(/[.[]/);
if (c > -1) {
var message =
Expand Down Expand Up @@ -169,9 +146,9 @@ export function _set(newState) {
if (this._bind) this._bind(changed, this._state);

if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this.fire("state", { changed: changed, current: this._state, previous: oldState });
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
this.fire("update", { changed: changed, current: this._state, previous: oldState });
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/validate/js/propValidators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import actions from './actions';
import computed from './computed';
import oncreate from './oncreate';
import ondestroy from './ondestroy';
import onstate from './onstate';
import onupdate from './onupdate';
import onrender from './onrender';
import onteardown from './onteardown';
import helpers from './helpers';
Expand All @@ -24,6 +26,8 @@ export default {
computed,
oncreate,
ondestroy,
onstate,
onupdate,
onrender,
onteardown,
helpers,
Expand Down
14 changes: 14 additions & 0 deletions src/validate/js/propValidators/onstate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import usesThisOrArguments from '../utils/usesThisOrArguments';
import { Validator } from '../../index';
import { Node } from '../../../interfaces';

export default function onstate(validator: Validator, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) {
validator.error(prop, {
code: `invalid-onstate-property`,
message: `'onstate' should be a function expression, not an arrow function expression`
});
}
}
}
14 changes: 14 additions & 0 deletions src/validate/js/propValidators/onupdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import usesThisOrArguments from '../utils/usesThisOrArguments';
import { Validator } from '../../index';
import { Node } from '../../../interfaces';

export default function onupdate(validator: Validator, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) {
validator.error(prop, {
code: `invalid-onupdate-property`,
message: `'onupdate' should be a function expression, not an arrow function expression`
});
}
}
}
41 changes: 24 additions & 17 deletions store.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import {
_differsImmutable,
dispatchObservers,
get,
observe
observe,
on,
fire
} from './shared.js';

function Store(state, options) {
this._observers = { pre: blankObject(), post: blankObject() };
this._changeHandlers = [];
this._handlers = {};
this._dependents = [];

this._computed = blankObject();
Expand Down Expand Up @@ -105,21 +107,22 @@ assign(Store.prototype, {
this._sortComputedProperties();
},

fire: fire,

get: get,

// TODO remove this method
observe: observe,

onchange: function(callback) {
this._changeHandlers.push(callback);
on: on,

var store = this;
onchange: function(callback) {
// TODO remove this method
console.warn("store.onchange is deprecated in favour of store.on('state', event => {...})");

return {
cancel: function() {
var index = store._changeHandlers.indexOf(callback);
if (~index) store._changeHandlers.splice(index, 1);
}
};
return this.on('state', function(event) {
callback(event.current, event.changed);
});
},

set: function(newState) {
Expand All @@ -139,11 +142,11 @@ assign(Store.prototype, {
this._sortedComputedProperties[i].update(this._state, changed);
}

for (var i = 0; i < this._changeHandlers.length; i += 1) {
this._changeHandlers[i](this._state, changed);
}

dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this.fire('state', {
changed: changed,
current: this._state,
previous: oldState
});

var dependents = this._dependents.slice(); // guard against mutations
for (var i = 0; i < dependents.length; i += 1) {
Expand All @@ -162,7 +165,11 @@ assign(Store.prototype, {
if (dirty) dependent.component.set(componentState);
}

dispatchObservers(this, this._observers.post, changed, this._state, oldState);
this.fire('update', {
changed: changed,
current: this._state,
previous: oldState
});
}
});

Expand Down
53 changes: 14 additions & 39 deletions test/js/samples/action/expected-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,34 +35,19 @@ function _differs(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}

function dispatchObservers(component, group, changed, newState, oldState) {
for (var key in group) {
if (!changed[key]) continue;

var newValue = newState[key];
var oldValue = oldState[key];

var callbacks = group[key];
if (!callbacks) continue;

for (var i = 0; i < callbacks.length; i += 1) {
var callback = callbacks[i];
if (callback.__calling) continue;

callback.__calling = true;
callback.call(component, newValue, oldValue);
callback.__calling = false;
}
}
}

function fire(eventName, data) {
var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return;

for (var i = 0; i < handlers.length; i += 1) {
handlers[i].call(this, data);
var handler = handlers[i];

if (!handler.__calling) {
handler.__calling = true;
handler.call(this, data);
handler.__calling = false;
}
}
}

Expand All @@ -71,7 +56,6 @@ function get(key) {
}

function init(component, options) {
component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject();
component._bind = options._bind;

Expand All @@ -81,24 +65,15 @@ function init(component, options) {
}

function observe(key, callback, options) {
var group = options && options.defer
? this._observers.post
: this._observers.pre;

(group[key] || (group[key] = [])).push(callback);
var fn = callback.bind(this);

if (!options || options.init !== false) {
callback.__calling = true;
callback.call(this, this._state[key]);
callback.__calling = false;
fn(this.get()[key], undefined);
}

return {
cancel: function() {
var index = group[key].indexOf(callback);
if (~index) group[key].splice(index, 1);
}
};
return this.on(options && options.defer ? 'update' : 'state', function(event) {
if (event.changed[key]) fn(event.current[key], event.previous && event.previous[key]);
});
}

function on(eventName, handler) {
Expand Down Expand Up @@ -140,9 +115,9 @@ function _set(newState) {
if (this._bind) this._bind(changed, this._state);

if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this.fire("state", { changed: changed, current: this._state, previous: oldState });
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
this.fire("update", { changed: changed, current: this._state, previous: oldState });
}
}

Expand Down
Loading