Skip to content

Commit

Permalink
Merge pull request #1344 from sveltejs/gh-1197
Browse files Browse the repository at this point in the history
Implement onstate and onupdate hooks
  • Loading branch information
Rich-Harris authored Apr 15, 2018
2 parents 8a99ce9 + 3d8c768 commit f1e5c1a
Show file tree
Hide file tree
Showing 95 changed files with 1,284 additions and 2,756 deletions.
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

0 comments on commit f1e5c1a

Please sign in to comment.