diff --git a/src/parse/converters/mustache/readPartial.js b/src/parse/converters/mustache/readPartial.js index 25855c0ab2..d081b5eb4a 100755 --- a/src/parse/converters/mustache/readPartial.js +++ b/src/parse/converters/mustache/readPartial.js @@ -45,6 +45,14 @@ export default function readPartial(parser, tag) { partial.c = {}; refineExpression(context, partial.c); } + + // allow aliases after context + if (parser.matchString(',')) { + aliases = readAliases(parser); + if (aliases && aliases.length) { + partial.z = aliases; + } + } } if (type !== '>' && (!partial.c && !partial.z)) { diff --git a/src/view/Fragment.js b/src/view/Fragment.js index cc8717a402..ce84a94193 100755 --- a/src/view/Fragment.js +++ b/src/view/Fragment.js @@ -8,6 +8,19 @@ import processItems from './helpers/processItems'; import parseJSON from 'utils/parseJSON'; import { createDocumentFragment } from 'utils/dom'; import KeyModel from 'src/model/specials/KeyModel'; +import resolve from './resolvers/resolve'; + +function resolveAliases(aliases, fragment, dest = {}) { + for (let i = 0; i < aliases.length; i++) { + if (!dest[aliases[i].n]) { + const m = resolve(fragment, aliases[i].x); + dest[aliases[i].n] = m; + m.reference(); + } + } + + return dest; +} export default class Fragment { constructor(options) { @@ -46,6 +59,14 @@ export default class Fragment { bind(context) { this.context = context; + + if (this.owner.template.z) { + this.aliases = resolveAliases( + this.owner.template.z, + this.owner.containerFragment || this.parent + ); + } + const len = this.items.length; for (let i = 0; i < len; i++) this.items[i].bind(); this.bound = true; @@ -207,6 +228,19 @@ export default class Fragment { } rebound(update) { + if (this.owner.template.z) { + const aliases = this.aliases; + for (const k in aliases) { + if (aliases[k].rebound) aliases[k].rebound(update); + else { + aliases[k].unreference(); + aliases[k] = 0; + } + } + + resolveAliases(this.owner.template.z, this.owner.containerFragment || this.parent, aliases); + } + this.items.forEach(x => x.rebound(update)); if (update) { if (this.rootModel) this.rootModel.applyValue(this.context.getKeypath(this.ractive.root)); @@ -268,6 +302,14 @@ export default class Fragment { } unbind(view) { + if (this.owner.template.z && !this.owner.yielder) { + for (const k in this.aliases) { + this.aliases[k].unreference(); + } + + this.aliases = {}; + } + this.context = null; const len = this.items.length; for (let i = 0; i < len; i++) this.items[i].unbind(view); diff --git a/src/view/items/Alias.js b/src/view/items/Alias.js deleted file mode 100644 index 646957c557..0000000000 --- a/src/view/items/Alias.js +++ /dev/null @@ -1,74 +0,0 @@ -import Fragment from '../Fragment'; -import { ContainerItem } from './shared/Item'; -import resolve from '../resolvers/resolve'; - -export function resolveAliases(aliases, fragment, dest = {}) { - for (let i = 0; i < aliases.length; i++) { - if (!dest[aliases[i].n]) { - const m = resolve(fragment, aliases[i].x); - dest[aliases[i].n] = m; - m.reference(); - } - } - - return dest; -} - -export default class Alias extends ContainerItem { - constructor(options) { - super(options); - - this.fragment = null; - } - - bind() { - this.fragment = new Fragment({ - owner: this, - template: this.template.f - }); - - this.fragment.aliases = resolveAliases(this.template.z, this.up); - this.fragment.bind(); - } - - rebound(update) { - const aliases = this.fragment.aliases; - for (const k in aliases) { - if (aliases[k].rebound) aliases[k].rebound(update); - else { - aliases[k].unreference(); - aliases[k] = 0; - } - } - - resolveAliases(this.template.z, this.up, aliases); - - if (this.fragment) this.fragment.rebound(update); - } - - render(target, occupants) { - this.rendered = true; - if (this.fragment) this.fragment.render(target, occupants); - } - - unbind(view) { - for (const k in this.fragment.aliases) { - this.fragment.aliases[k].unreference(); - } - - this.fragment.aliases = {}; - if (this.fragment) this.fragment.unbind(view); - } - - unrender(shouldDestroy) { - if (this.rendered && this.fragment) this.fragment.unrender(shouldDestroy); - this.rendered = false; - } - - update() { - if (this.dirty) { - this.dirty = false; - this.fragment.update(); - } - } -} diff --git a/src/view/items/Partial.js b/src/view/items/Partial.js index 58d6f16a0f..41b40fd6b1 100755 --- a/src/view/items/Partial.js +++ b/src/view/items/Partial.js @@ -5,7 +5,6 @@ import noop from 'utils/noop'; import { MustacheContainer } from './shared/Mustache'; import Fragment from '../Fragment'; import getPartialTemplate from './partial/getPartialTemplate'; -import { resolveAliases } from './Alias'; import { warnOnceIfDebug, warnIfDebug } from 'utils/log'; import parser from 'src/Ractive/config/runtime-parser'; import runloop from 'src/global/runloop'; @@ -114,19 +113,6 @@ assign(proto, { }, rebound(update) { - const aliases = this.fragment && this.fragment.aliases; - if (aliases) { - for (const k in aliases) { - if (aliases[k].rebound) aliases[k].rebound(update); - else { - aliases[k].unreference(); - aliases[k] = 0; - } - } - if (this.template.z) { - resolveAliases(this.template.z, this.containerFragment || this.up, aliases); - } - } if (this._attrs) { keys(this._attrs).forEach(k => this._attrs[k].rebound(update)); } @@ -186,8 +172,6 @@ assign(proto, { unbind(view) { this.fragment.unbind(view); - this.fragment.aliases = null; - this.unbindAttrs(view); MustacheContainer.prototype.unbind.call(this, view); @@ -247,17 +231,12 @@ function createFragment(self, partial) { if (self.fn) options.cssIds = self.fn._cssIds; - const fragment = (self.fragment = new Fragment(options)); - - // partials may have aliases that need to be in place before binding - if (self.template.z) { - fragment.aliases = resolveAliases(self.template.z, self.containerFragment || self.up); - } + self.fragment = new Fragment(options); } function contextifyTemplate(self) { if (self.template.c) { - self.partial = [{ t: SECTION, n: SECTION_WITH, f: self.partial }]; + self.partial = [{ t: SECTION, n: SECTION_WITH, f: self.partial, z: self.template.z }]; assign(self.partial[0], self.template.c); if (self.yielder) self.partial[0].y = self; } diff --git a/src/view/items/Section.js b/src/view/items/Section.js index 9bddd30160..4d504e65e0 100755 --- a/src/view/items/Section.js +++ b/src/view/items/Section.js @@ -1,4 +1,5 @@ import { + ALIAS, SECTION_EACH, SECTION_IF, SECTION_IF_WITH, @@ -34,7 +35,8 @@ export default class Section extends MustacheContainer { constructor(options) { super(options); - this.sectionType = options.template.n || null; + this.isAlias = options.template.t === ALIAS; + this.sectionType = options.template.n || (this.isAlias && SECTION_WITH) || null; this.templateSectionType = this.sectionType; this.subordinate = options.template.l === 1; this.fragment = null; @@ -49,7 +51,7 @@ export default class Section extends MustacheContainer { } // if we managed to bind, we need to create children - if (this.model) { + if (this.model || this.isAlias) { this.dirty = true; this.update(); } else if ( @@ -138,7 +140,7 @@ export default class Section extends MustacheContainer { this.fragment.context = this.model; } - if (!this.model && this.sectionType !== SECTION_UNLESS) return; + if (!this.model && this.sectionType !== SECTION_UNLESS && !this.isAlias) return; this.dirty = false; @@ -173,7 +175,9 @@ export default class Section extends MustacheContainer { const fragmentShouldExist = this.sectionType === SECTION_EACH || // each always gets a fragment, which may have no iterations this.sectionType === SECTION_WITH || // with (partial context) always gets a fragment - (siblingFalsey && (this.sectionType === SECTION_UNLESS ? !this.isTruthy() : this.isTruthy())); // if, unless, and if-with depend on siblings and the condition + (siblingFalsey && + (this.sectionType === SECTION_UNLESS ? !this.isTruthy() : this.isTruthy())) || // if, unless, and if-with depend on siblings and the condition + this.isAlias; if (fragmentShouldExist) { if (!this.fragment) this.fragment = this.detached; diff --git a/src/view/items/createItem.js b/src/view/items/createItem.js index 2254cfbd83..eb66504484 100755 --- a/src/view/items/createItem.js +++ b/src/view/items/createItem.js @@ -13,7 +13,6 @@ import { YIELDER } from 'config/types'; import { ATTRIBUTE, BINDING_FLAG, DECORATOR, EVENT, TRANSITION } from 'config/types'; -import Alias from './Alias'; import Attribute from './element/Attribute'; import BindingFlag from './element/BindingFlag'; import Comment from './Comment'; @@ -42,7 +41,7 @@ import Await from './Await'; import { isString, isFunction } from 'utils/is'; const constructors = {}; -constructors[ALIAS] = Alias; +constructors[ALIAS] = Section; constructors[ANCHOR] = Component; constructors[AWAIT] = Await; constructors[DOCTYPE] = Doctype; diff --git a/src/view/resolvers/resolveReference.js b/src/view/resolvers/resolveReference.js index 3c24292d16..36e9a67bbd 100755 --- a/src/view/resolvers/resolveReference.js +++ b/src/view/resolvers/resolveReference.js @@ -145,7 +145,6 @@ export default function resolveReference(fragment, ref) { if (context) { if (context.context) { context = context.context; - if (context.has(base)) return context.joinKey(base).joinAll(keys); } else { // alias block, so get next full context for later context = fragment.findContext(); diff --git a/tests/browser/partials.js b/tests/browser/partials.js index 7b54a68ae8..7e7ebc8a43 100644 --- a/tests/browser/partials.js +++ b/tests/browser/partials.js @@ -1280,4 +1280,21 @@ export default function() { t.htmlEqual(fixture.innerHTML, '5'); }); + + test(`partial with context and aliases`, t => { + new Ractive({ + target: fixture, + template: `{{#with foo}}{{>bar baz, 42 as foo}}{{/with}}`, + partials: { + bar: `{{.foo}} {{foo}}` + }, + data: { + foo: { + baz: { foo: 99 } + } + } + }); + + t.htmlEqual(fixture.innerHTML, '99 42'); + }); }