Skip to content

Commit

Permalink
experimental support for mapping a computed iteration back to a source
Browse files Browse the repository at this point in the history
  • Loading branch information
evs-chris committed Apr 3, 2018
1 parent dc4b49f commit 6947eaa
Show file tree
Hide file tree
Showing 18 changed files with 245 additions and 54 deletions.
1 change: 1 addition & 0 deletions src/model/Computation.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default class Computation extends Model {
this.signature = signature;

this.isReadonly = !this.signature.setter;
this.isComputed = true;

this.dependencies = [];

Expand Down
1 change: 1 addition & 0 deletions src/model/ComputationChild.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default class ComputationChild extends Model {

this.isReadonly = !this.root.ractive.syncComputedChildren;
this.dirty = true;
this.isComputed = true;
}

get setRoot() {
Expand Down
3 changes: 0 additions & 3 deletions src/shared/methodCallers.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ export function marked(x) {
export function markedAll(x) {
x.markedAll();
}
export function rebound(x) {
x.rebound();
}
export function render(x) {
x.render();
}
Expand Down
9 changes: 6 additions & 3 deletions src/view/Fragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { getContext, findParentWithContext } from 'shared/getRactiveContext';
import {
bind,
destroyed,
rebound,
shuffled,
toEscapedString,
toString,
Expand Down Expand Up @@ -225,8 +224,12 @@ export default class Fragment {
this.context = next;
}

rebound() {
this.items.forEach(rebound);
rebound(update) {
this.items.forEach(x => x.rebound(update));
if (update) {
if (this.rootModel) this.rootModel.applyValue(this.context.getKeypath(this.ractive.root));
if (this.pathModel) this.pathModel.applyValue(this.context.getKeypath());
}
}

render(target, occupants) {
Expand Down
68 changes: 58 additions & 10 deletions src/view/RepeatedFragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { getContext } from 'shared/getRactiveContext';
import { keys } from 'utils/object';
import KeyModel from 'src/model/specials/KeyModel';
import { splitKeypath } from '../shared/keypaths';
import resolve from './resolvers/resolve';

const keypathString = /^"(\\"|[^"])+"$/;

Expand Down Expand Up @@ -60,9 +61,9 @@ export default class RepeatedFragment {
this.bound = true;
const value = context.get();

this.aliases = this.owner.template.z && this.owner.template.z.slice();
const aliases = (this.aliases = this.owner.template.z && this.owner.template.z.slice());

const shuffler = this.aliases && this.aliases.find(a => a.n === 'shuffle');
const shuffler = aliases && aliases.find(a => a.n === 'shuffle');
if (shuffler && shuffler.x && shuffler.x.x) {
if (shuffler.x.x.s === 'true') this.shuffler = true;
else if (keypathString.test(shuffler.x.x.s))
Expand All @@ -71,6 +72,22 @@ export default class RepeatedFragment {

if (this.shuffler) this.values = shuffleValues(this, this.shuffler);

if (this.source) this.source.model.unbind(this.source);
const source = context.isComputed && aliases && aliases.find(a => a.n === 'source');
if (source && source.x && source.x.r) {
const model = resolve(this, source.x);
this.source = {
handleChange() {},
rebind(next) {
this.model.unregister(this);
this.model = next;
next.register(this);
}
};
this.source.model = model;
model.register(this.source);
}

// {{#each array}}...
if ((this.isArray = isArray(value))) {
// we can't use map, because of sparse arrays
Expand Down Expand Up @@ -105,7 +122,7 @@ export default class RepeatedFragment {
if (!this.bubbled) this.bubbled = [];
this.bubbled.push(index);

this.owner.bubble();
if (!this.rebounding) this.owner.bubble();
}

createIteration(key, index) {
Expand Down Expand Up @@ -174,16 +191,17 @@ export default class RepeatedFragment {

rebind(next) {
this.context = next;
if (this.source) return;
this.iterations.forEach(fragment => {
swizzleFragment(this, fragment, fragment.key, fragment.index);
});
}

rebound() {
rebound(update) {
this.context = this.owner.model;
this.iterations.forEach((f, i) => {
f.context = this.context.joinKey(i);
f.rebound();
f.context = contextFor(this, f, i);
f.rebound(update);
});
}

Expand Down Expand Up @@ -236,6 +254,7 @@ export default class RepeatedFragment {

unbind() {
this.bound = false;
if (this.source) this.source.model.unregister(this.source);
this.iterations.forEach(unbind);
return this;
}
Expand Down Expand Up @@ -276,6 +295,19 @@ export default class RepeatedFragment {
let i;

if ((this.isArray = isArray(value))) {
// if there's a source to map back to, make sure everything stays bound correctly
if (this.source) {
this.rebounding = 1;
const source = this.source.model.get();
this.iterations.forEach((f, c) => {
if (c < value.length && f.lastValue !== value[c] && ~(i = source.indexOf(value[c]))) {
swizzleFragment(this, f, c, c);
f.rebound(true);
}
});
this.rebounding = 0;
}

if (wasArray) {
reset = false;
if (this.iterations.length > value.length) {
Expand Down Expand Up @@ -393,8 +425,9 @@ export default class RepeatedFragment {
const len = (this.length = this.context.get().length);
const prev = this.previousIterations;
const iters = this.iterations;
const value = this.context.get();
const stash = {};
let idx, dest, pos, next, anchor;
let idx, dest, pos, next, anchor, rebound;

const map = new Array(newIndices.length);
newIndices.forEach((e, i) => (map[e] = i));
Expand All @@ -405,6 +438,7 @@ export default class RepeatedFragment {
while (idx < len) {
dest = newIndices[pos];
next = null;
rebound = false;

if (dest === -1) {
// drop it like it's hot
Expand All @@ -422,6 +456,7 @@ export default class RepeatedFragment {
anchor = (anchor && parentNode && anchor.firstNode()) || nextNode;

if (next) {
rebound = this.source && next.lastValue !== value[idx];
swizzleFragment(this, next, idx, idx);
if (parentNode) parentNode.insertBefore(next.detach(), anchor);
} else {
Expand All @@ -446,6 +481,7 @@ export default class RepeatedFragment {
parentNode.insertBefore(docFrag, anchor);
}
} else if (pos !== idx || stash[idx]) {
rebound = this.source && next.lastValue !== value[idx];
swizzleFragment(this, next, idx, idx);
if (stash[idx] && parentNode) parentNode.insertBefore(next.detach(), anchor);
}
Expand All @@ -455,8 +491,8 @@ export default class RepeatedFragment {
}

if (next && isObjectType(next)) {
if (next.shouldRebind) {
next.rebound();
if (next.shouldRebind || rebound) {
next.rebound(rebound);
next.shouldRebind = 0;
}
next.update();
Expand Down Expand Up @@ -512,11 +548,12 @@ function nextRendered(start, newIndices, frags) {
}

function swizzleFragment(section, fragment, key, idx) {
const model = section.context ? section.context.joinKey(key) : undefined;
const model = section.context ? contextFor(section, fragment, key) : undefined;

fragment.key = key;
fragment.index = idx;
fragment.context = model;
if (section.source) fragment.lastValue = model && model.get();

if (fragment.idxModel) fragment.idxModel.applyValue(idx);
if (fragment.keyModel) fragment.keyModel.applyValue(key);
Expand All @@ -540,3 +577,14 @@ function shuffleValues(section, shuffler) {
return section.context.get().map(v => shuffler.reduce((a, c) => a && a[c], v));
}
}

function contextFor(section, fragment, key) {
if (section.source) {
let idx;
const source = section.source.model.get();
if (source.indexOf && ~(idx = source.indexOf(section.context.joinKey(key).get())))
return section.source.model.joinKey(idx);
}

return section.context.joinKey(key);
}
6 changes: 3 additions & 3 deletions src/view/items/Alias.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ export default class Alias extends ContainerItem {
this.fragment.bind();
}

rebound() {
rebound(update) {
const aliases = this.fragment.aliases;
for (const k in aliases) {
if (aliases[k].rebound) aliases[k].rebound();
if (aliases[k].rebound) aliases[k].rebound(update);
else {
aliases[k].unreference();
aliases[k] = 0;
Expand All @@ -43,7 +43,7 @@ export default class Alias extends ContainerItem {

resolveAliases(this.template.z, this.up, aliases);

if (this.fragment) this.fragment.rebound();
if (this.fragment) this.fragment.rebound(update);
}

render(target, occupants) {
Expand Down
13 changes: 3 additions & 10 deletions src/view/items/Component.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import runloop from 'src/global/runloop';
import { updateAnchors } from 'shared/anchors';
import {
bind,
rebound,
render as callRender,
unbind,
unrender,
update
} from 'shared/methodCallers';
import { bind, render as callRender, unbind, unrender, update } from 'shared/methodCallers';
import { teardown } from 'src/Ractive/prototype/teardown';
import getRactiveContext from 'shared/getRactiveContext';
import { warnIfDebug } from 'utils/log';
Expand Down Expand Up @@ -220,8 +213,8 @@ export default class Component extends Item {
return getRactiveContext.apply(null, assigns);
}

rebound() {
this.attributes.forEach(rebound);
rebound(update) {
this.attributes.forEach(x => x.rebound(update));
}

render(target, occupants) {
Expand Down
10 changes: 5 additions & 5 deletions src/view/items/Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { escapeHtml, voidElementNames } from 'utils/html';
import { createElement, detachNode, matches, safeAttributeString } from 'utils/dom';
import runloop from 'src/global/runloop';
import Context from 'shared/Context';
import { bind, destroyed, rebound, render, unbind, update } from 'shared/methodCallers';
import { bind, destroyed, render, unbind, update } from 'shared/methodCallers';
import { ContainerItem } from './shared/Item';
import Fragment from '../Fragment';
import ConditionalAttribute from './element/ConditionalAttribute';
Expand Down Expand Up @@ -261,10 +261,10 @@ export default class Element extends ContainerItem {
}
}

rebound() {
super.rebound();
if (this.attributes) this.attributes.forEach(rebound);
if (this.binding) this.binding.rebound();
rebound(update) {
super.rebound(update);
if (this.attributes) this.attributes.forEach(x => x.rebound(update));
if (this.binding) this.binding.rebound(update);
}

render(target, occupants) {
Expand Down
6 changes: 3 additions & 3 deletions src/view/items/Partial.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ assign(proto, {
this.bubble();
},

rebound() {
rebound(update) {
const aliases = this.fragment && this.fragment.aliases;
if (aliases) {
for (const k in aliases) {
if (aliases[k].rebound) aliases[k].rebound();
if (aliases[k].rebound) aliases[k].rebound(update);
else {
aliases[k].unreference();
aliases[k] = 0;
Expand All @@ -128,7 +128,7 @@ assign(proto, {
}
}
if (this._attrs) {
keys(this._attrs).forEach(k => this._attrs[k].rebound());
keys(this._attrs).forEach(k => this._attrs[k].rebound(update));
}
MustacheContainer.prototype.rebound.call(this);
},
Expand Down
14 changes: 10 additions & 4 deletions src/view/items/Section.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,24 @@ export default class Section extends MustacheContainer {
}
}

rebound() {
rebound(update) {
if (this.model) {
if (this.model.rebound) this.model.rebound();
if (this.model.rebound) this.model.rebound(update);
else {
super.unbind();
super.bind();
if (this.sectionType === SECTION_WITH || this.sectionType === SECTION_IF_WITH) {
if (
this.sectionType === SECTION_WITH ||
this.sectionType === SECTION_IF_WITH ||
this.sectionType === SECTION_EACH
) {
if (this.fragment) this.fragment.rebind(this.model);
}

if (update) this.bubble();
}
}
if (this.fragment) this.fragment.rebound();
if (this.fragment) this.fragment.rebound(update);
}

render(target, occupants) {
Expand Down
8 changes: 4 additions & 4 deletions src/view/items/component/Mapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ export default class Mapping extends Item {
}
}

rebound() {
if (this.boundFragment) this.boundFragment.rebound();
rebound(update) {
if (this.boundFragment) this.boundFragment.rebound(update);
if (this.link) {
this.model = resolve(this.up, this.template.f[0]);
const viewmodel = this.element.instance.viewmodel;
viewmodel.joinAll(splitKeypath(this.name)).link(this.model, this.name, { mapping: true });
const model = this.element.instance.viewmodel.joinAll(splitKeypath(this.name));
model.link(this.model, this.name, { mapping: true });
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/view/items/element/Decorator.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ export default class Decorator {
if (!safe) this.bubble();
}

rebound() {
rebound(update) {
teardownArgsFn(this, this.template);
setupArgsFn(this, this.template, this.up, { register: true });
if (update) this.bubble();
}

render() {
Expand Down
2 changes: 2 additions & 0 deletions src/view/items/element/binding/Binding.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ export default class Binding {
}

rebound() {
if (this.model) this.model.unregisterTwowayBinding(this);
this.model = this.attribute.interpolator.model;
this.model.registerTwowayBinding(this);
}

render() {
Expand Down
4 changes: 2 additions & 2 deletions src/view/items/element/binding/RadioNameBinding.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ export default class RadioNameBinding extends Binding {
this.updateName();
}

rebound() {
super.rebound();
rebound(update) {
super.rebound(update);
this.updateName();
}

Expand Down
Loading

0 comments on commit 6947eaa

Please sign in to comment.