Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save NullVoxPopuli/468a737efbbf447966dd83ac734f62ad to your computer and use it in GitHub Desktop.
Save NullVoxPopuli/468a737efbbf447966dd83ac734f62ad to your computer and use it in GitHub Desktop.
Parent / Child Component Communication
import Component from '@ember/component';
import { action, computed } from '@ember-decorators/object';
import { check } from 'twiddle/utils/tree-helpers';
export default class extends Component {
options = [{
id: 1,
label: 'burger',
checked: false,
children: [{
id: 3,
label: 'tomato',
checked: false
}, {
id: 4,
label: 'lettus',
checked: false
}, {
id: 5,
label: 'pickle',
checked: false
}]
}, {
id: 2,
label: 'kebab',
checked: false,
children: [{
id: 6,
label: 'ketchup',
checked: false
}, {
id: 7,
label: 'chilli',
checked: false
}]
}, {
id: 8,
label: 'coffee maker',
checked: false,
children: [{
id: 9,
label: 'filter',
checked: false
}, {
id: 10,
label: 'grounds',
checked: false
}]
}];
@action
toggleChecked(id) {
const newTree = check(this.options, id);
this.set('options', newTree);
}
}
import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
items : [{
"title" : "burger",
"id": "bk2",
"subgroup" : [
{"name" : "tomato", "id": "tomo2"}, {"name" : "lettuce", "id": "let2"}, {"name" : "pickle", "id": "pickl3"}]
},
{
"title" : "kebab",
"id": "kb2",
"subgroup" : [{"name" : "ketchup", "id": "ketchu2"}, {"name" : "chilli", "id": "chilli2"}]
}]
});
import Ember from 'ember';
export function not(params/*, hash*/) {
return !params[0];
}
export default Ember.Helper.helper(not);
body {
margin: 12px 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12pt;
}
.checkboxhandler{
position: relative;
margin-left: 10px;
margin-bottom:10px;
border: 1px solid red;
}
<h1>Welcome to {{appName}}</h1>
<br>
<WrappingComponent as |options toggle|>
{{#each options as |item|}}
<CheckboxGroup @item={{item}} @onClick={{toggle}} />
{{/each}}
</WrappingComponent>
<br>
{{outlet}}
<br>
<br>
<div class="checkboxhandler">
<input
type="checkbox"
checked={{@item.checked}}
onclick={{action @onClick @item.id}}
>
<label>{{@item.label}} -- checked: {{@item.checked}}</label>
{{#if @item.children}}
{{#each @item.children as |child|}}
<CheckboxGroup @item={{child}} @onClick={{@onClick}} />
{{/each}}
{{/if}}
</div>
{{yield this.options (action this.toggleChecked)}}
import Application from '../app';
import config from '../config/environment';
import { setApplication } from '@ember/test-helpers';
import { start } from 'ember-qunit';
import { assign } from '@ember/polyfills';
let attributes = assign({ rootElement: '#main' }, config.APP);
setApplication(Application.create(attributes));
start();
import { module, test, skip } from 'qunit';
import { check, didChange } from 'twiddle/utils/tree-helpers';
const options = [{
id: 1,
label: 'burger',
checked: false,
children: [{
id: 3,
label: 'tomato',
checked: false
}, {
id: 4,
label: 'lettus',
checked: false
}, {
id: 5,
label: 'pickle',
checked: false
}]
}, {
id: 2,
label: 'kebab',
checked: false,
children: [{
id: 6,
label: 'ketchup',
checked: false
}, {
id: 7,
label: 'chilli',
checked: false
}]
}];
module('utils:tree-helper | didChange', function() {
test('detects one level of change', function(assert) {
const a = { checked: true };
const b = { checked: false };
const result = didChange(a, b);
assert.ok(result);
});
test('detects a change in a child', function(assert) {
const a = { checked: false, children: [{ checked: true }] };
const b = { checked: false, children: [{ checked: false }] };
const result = didChange(a, b);
assert.ok(result);
});
test('detects a change on an array', function(assert) {
const a = [{ checked: true }, { checked: false }];
const b = [{ checked: true }, { checked: true }];
const result = didChange(a, b);
assert.ok(result);
});
});
module('utils:tree-helper | check', function() {
test('it checks the node', function(assert) {
const result = check(options, 1);
assert.ok(result[0].checked, 'burger is checked');
});
module('a sibling is checked', function(hooks) {
let tree;
hooks.beforeEach(function(assert) {
tree = check(options, 1);
assert.ok(tree[0].checked, 'burger is checked');
});
module('another sibling is checked', function(hooks) {
hooks.beforeEach(function(assert) {
tree = check(tree, 2);
});
test('the first sibling remains checked', function(assert) {
assert.ok(tree[0].checked, 'burger is checked');
});
});
module('and children are checked before checking the other sibling', function(hooks) {
hooks.beforeEach(function(assert) {
tree = check(tree, 3);
assert.ok(tree[0].children[0].checked, 'tomato is checked');
tree = check(tree, 2);
assert.ok(tree[1].checked, 'kebab is checked');
});
skip('unchecks the entire first tree', function(assert) {
assert.notOk(tree[0].checked, 'burger is not checked');
assert.notOk(tree[0].children[0].checked, 'tomato is not checked');
});
});
});
});
{
"version": "0.15.0",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": true
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js",
"ember": "3.4.1",
"ember-template-compiler": "3.4.1",
"ember-testing": "3.2.2"
},
"addons": {
"ember-decorators": "2.0.0"
}
}
const toggle = value => !value;
const disable = () => false;
// the roots / siblings are contained by arrays
export function check(tree, id, transform = toggle) {
if (tree === undefined) return undefined;
if (Array.isArray(tree)) {
return tree.map(t => check(t, id, transform));
}
if (tree.id === id || id === 'all') {
return checkNode(tree, id, transform);
}
if (tree.children) {
return checkChildren(tree, id, transform);
}
return tree;
}
function selectOnlySubtree(tree, id, transform) {
return tree.map(subTree => {
const newTree = check(subTree, id, transform);
if (!newTree.children || (transform !== disable && didChange(newTree, subTree))) {
return newTree;
}
return disableTree(subTree);
});
}
function isTargetAtThisLevel(tree, id) {
return tree.map(t => t.id).includes(id);
}
function checkNode(tree, id, transform) {
return {
...tree,
checked: transform(tree.checked),
children: disableTree(tree.children)
};
}
function disableTree(tree) {
return check(tree, 'all', disable);
}
function checkChildren(tree, id, transform) {
const newChildren = check(tree.children, id, transform);
const changed = didChange(tree.children, newChildren);
const checked = changed ? false : (
id === 'all' ? transform(tree.checked) : tree.checked
);
return {
...tree,
checked: checked,
children: check(tree.children, id, transform)
};
}
export function didChange(treeA, treeB) {
const rootsChanged = treeA.checked !== treeB.checked;
if (rootsChanged) return true;
if (Array.isArray(treeA) && Array.isArray(treeB)) {
return didChangeList(treeA, treeB);
}
if (treeA.children && treeB.children) {
return didChangeList(treeA.children, treeB.children);
}
return false;
}
function didChangeList(a, b) {
const compares = a.map((childA, index) => {
return didChange(childA, b[index]);
});
const nothingChanged = compares.every(v => v === false);
return !nothingChanged;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment