Skip to content

Commit

Permalink
Implement basic functionality for 3.7
Browse files Browse the repository at this point in the history
  • Loading branch information
rwjblue committed Jan 27, 2019
1 parent 1f38ebf commit 2d1ff0c
Show file tree
Hide file tree
Showing 2 changed files with 325 additions and 33 deletions.
203 changes: 171 additions & 32 deletions tests/integration/components/modifier-manager-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,176 @@ import Ember from 'ember';
module('Integration | Component | modifier-manager', function(hooks) {
setupRenderingTest(hooks);

class ModifierManager {
constructor(owner) {
this.owner = owner;
}

createModifier(factory, args) {
return factory.create(args);
}

installModifier(instance, element, args) {
instance.element = element;
let { positional, named } = args;
instance.didInsertElement(positional, named);
}

updateModifier(instance, args) {
let { positional, named } = args;
instance.didUpdate(positional, named);
}

destroyModifier(instance) {
instance.willDestroyElement();
}
}

test('it basically works', async function(assert) {
Ember._setModifierManager(owner => new ModifierManager(owner), class DidInsertModifier {});
this.owner.register('modifier-manager:did-insert');

await render(hbs`<div {{did-insert}}></div>`);

assert.equal(this.element.textContent.trim(), '');
module('installModifier', function(hooks) {
hooks.beforeEach(function() {
class DidInsertModifier {}

Ember._setModifierManager(DidInsertModifier, () => ({
createModifier(_factory, args) {
return args.positional[0];
},

installModifier(_state, element, args) {
let [fn, ...positional] = args.positional;

fn(element, positional, args.named);
},

updateModifier() {},
destroyModifier() {},
}));
this.owner.register('modifier:did-insert', DidInsertModifier);
});

test('it basically works', async function(assert) {
assert.expect(2);

this.someMethod = element => {
assert.equal(element.tagName, 'DIV', 'correct element tagName');
assert.dom(element).hasAttribute('data-foo', 'some-thing');
};
await render(hbs`<div data-foo="some-thing" {{did-insert this.someMethod}}></div>`);
});

test('it can accept arguments', async function(assert) {
assert.expect(4);

this.someMethod = (element, positional, named) => {
assert.equal(element.tagName, 'DIV', 'correct element tagName');
assert.dom(element).hasAttribute('data-foo', 'some-thing');

assert.deepEqual(named, { some: 'hash-value' }, 'named args match');
assert.deepEqual(positional, ['some-positional-value'], 'positional args match');
};

await render(
hbs`<div data-foo="some-thing" {{did-insert this.someMethod "some-positional-value" some="hash-value"}}></div>`
);
});

test('it is not invoked again when arguments change', async function(assert) {
assert.expect(4);

this.someMethod = (element, positional, named) => {
assert.equal(element.tagName, 'DIV', 'correct element tagName');
assert.dom(element).hasAttribute('data-foo', 'some-thing');

assert.deepEqual(named, {}, 'named args match');
assert.deepEqual(positional, ['initial'], 'positional args match');
};

this.set('firstArg', 'initial');
await render(
hbs`<div data-foo="some-thing" {{did-insert this.someMethod this.firstArg}}></div>`
);
this.set('firstArg', 'updated');
});
});

module('updateModifier', function(hooks) {
hooks.beforeEach(function() {
class DidUpdateModifier {}

Ember._setModifierManager(DidUpdateModifier, () => ({
createModifier() {
return {};
},
installModifier(state, element) {
state.element = element;
},

updateModifier({ element }, args) {
let [fn, ...positional] = args.positional;

fn(element, positional, args.named);
},

destroyModifier() {},
}));
this.owner.register('modifier:did-update', DidUpdateModifier);
});

test('it basically works', async function(assert) {
assert.expect(4);

this.someMethod = (element, positional, named) => {
assert.equal(element.tagName, 'DIV', 'correct element tagName');
assert.dom(element).hasAttribute('data-foo', 'some-thing');

assert.deepEqual(named, {}, 'named args match');
assert.deepEqual(positional, ['update'], 'positional args match');
};

this.set('boundValue', 'initial');
await render(
hbs`<div data-foo="some-thing" {{did-update this.someMethod this.boundValue}}></div>`
);

this.set('boundValue', 'update');
});
});

module('destroyModifier', function(hooks) {
hooks.beforeEach(function() {
class WillDestroyModifier {}

Ember._setModifierManager(WillDestroyModifier, () => ({
createModifier() {
return {};
},

installModifier(state, element) {
state.element = element;
},

updateModifier() {},

destroyModifier({ element }, args) {
let [fn, ...positional] = args.positional;

fn(element, positional, args.named);
},
}));

this.owner.register('modifier:will-destroy', WillDestroyModifier);
});

test('it basically works', async function(assert) {
assert.expect(2);

this.someMethod = element => {
assert.equal(element.tagName, 'DIV', 'correct element tagName');
assert.dom(element).hasAttribute('data-foo', 'some-thing');
};
this.set('show', true);

await render(
hbs`{{#if show}}<div data-foo="some-thing" {{will-destroy this.someMethod}}></div>{{/if}}`
);

// trigger destroy
this.set('show', false);
});

test('it can accept arguments', async function(assert) {
assert.expect(4);

this.someMethod = (element, positional, named) => {
assert.equal(element.tagName, 'DIV', 'correct element tagName');
assert.dom(element).hasAttribute('data-foo', 'some-thing');

assert.deepEqual(named, { some: 'hash-value' }, 'named args match');
assert.deepEqual(positional, ['some-positional-value'], 'positional args match');
};

this.set('show', true);

await render(
hbs`{{#if show}}<div data-foo="some-thing" {{will-destroy this.someMethod "some-positional-value" some="hash-value"}}></div>{{/if}}`
);

// trigger destroy
this.set('show', false);
});
});
});
155 changes: 154 additions & 1 deletion vendor/ember-modifier-manager-polyfill.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,159 @@
import { lte, gte } from 'ember-compatibility-helpers';
/* globals Ember */
/* eslint-disable ember/new-module-imports */

import { gte } from 'ember-compatibility-helpers';

(() => {
'use strict';

const getPrototypeOf = Object.getPrototypeOf;
const { Application } = Ember;
let MODIFIER_MANAGERS = new WeakMap();
Ember._setModifierManager = function Polyfilled_setModifierManager(modifier, managerFactory) {
MODIFIER_MANAGERS.set(modifier, managerFactory);
};

let getModifierManager = obj => {
let pointer = obj;
while (pointer !== undefined && pointer !== null) {
if (MODIFIER_MANAGERS.has(pointer)) {
return MODIFIER_MANAGERS.get(pointer);
}

pointer = getPrototypeOf(pointer);
}

return;
};

if (gte('3.1.0-beta.1')) {
let valueForCapturedArgs = function valueForCapturedArgs(args) {
return {
named: args.named.value(),
positional: args.positional.value(),
};
};

Application.reopenClass({
buildRegistry() {
let registry = this._super(...arguments);

let containerModule = gte('3.6.0-alpha.1') ? '@ember/-internals/container' : 'container';
const P = Ember.__loader.require(containerModule).privatize;

let compilerName = gte('3.2.0-alpha.1')
? P`template-compiler:main`
: P`template-options:main`;
let TemplateCompiler = registry.resolve(compilerName);

let ORIGINAL_TEMPLATE_COMPILER_CREATE = TemplateCompiler.create;
if (ORIGINAL_TEMPLATE_COMPILER_CREATE.__MODIFIER_MANAGER_PATCHED === true) {
return registry;
}

TemplateCompiler.create = function() {
let compiler = ORIGINAL_TEMPLATE_COMPILER_CREATE(...arguments);
let compileTimeLookup = compiler.resolver;
let runtimeResolver = compileTimeLookup.resolver;

let CustomModifierDefinition;
if (gte('3.6.0-alpha.1')) {
class CustomModifierState {
constructor(element, delegate, modifier, args) {
this.element = element;
this.delegate = delegate;
this.modifier = modifier;
this.args = args;
}

destroy() {
const { delegate, modifier, args } = this;
let modifierArgs = valueForCapturedArgs(args);
delegate.destroyModifier(modifier, modifierArgs);
}
}

class Polyfilled_CustomModifierManager {
//create(element: Simple.Element, state: ModifierDefinitionState, args: IArguments, dynamicScope: DynamicScope, dom: DOMChanges): ModifierInstanceState;
create(element, definition, args) {
const capturedArgs = args.capture();
let modifierArgs = valueForCapturedArgs(capturedArgs);
let instance = definition.delegate.createModifier(
definition.ModifierClass,
modifierArgs
);

return new CustomModifierState(
element,
definition.delegate,
instance,
capturedArgs
);
}

//getTag(modifier: ModifierInstanceState): Tag;
getTag({ args }) {
return args.tag;
}

//install(modifier: ModifierInstanceState): void;
install(state) {
let { element, args, delegate, modifier } = state;
let modifierArgs = valueForCapturedArgs(args);
delegate.installModifier(modifier, element, modifierArgs);
}

//update(modifier: ModifierInstanceState): void;
update(state) {
let { args, delegate, modifier } = state;
let modifierArgs = valueForCapturedArgs(args);
delegate.updateModifier(modifier, modifierArgs);
}

//getDestructor(modifier: ModifierInstanceState): Option<Destroyable>;
getDestructor(state) {
return state;
}
}

CustomModifierDefinition = class Polyfilled_CustomModifierDefinition {
constructor(name, ModifierClass, delegate) {
this.name = name;
this.state = {
ModifierClass,
name,
delegate,
};
this.manager = new Polyfilled_CustomModifierManager();
}
};
} else {
// TODO: rwjblue implement 3.1 - 3.5
}

runtimeResolver._lookupModifier = function(name, meta) {
let builtin = this.builtInModifiers[name];

if (builtin === undefined) {
let { owner } = meta;
let modifier = owner.factoryFor(`modifier:${name}`);
if (modifier !== undefined) {
let managerFactory = getModifierManager(modifier.class);
let manager = managerFactory(owner);

return new CustomModifierDefinition(name, modifier.class, manager);
}
}

return builtin;
};

return compiler;
};
TemplateCompiler.create.__MODIFIER_MANAGER_PATCHED = true;

return registry;
},
});
}
})();

0 comments on commit 2d1ff0c

Please sign in to comment.