Ember - 嵌套递归模块
Ember - nested recursive modules
我正在开发点击和放置功能 --- 在页面上以递归方式使用模块的位置,因此它具有 parent 和 children。
我遇到了一个问题,如果用户开始 select children - 然后 selects parent - 我想 deselectchild仁。尽管我不确定如何存储或监控 parent/child 项目中的变化 selected 以进行全局 deselection.
因此用户已经 select 编辑了 bacon3 的 child.. 如果他们 select parent - 则需要删除 select children -- 但我感觉我目前被锁定在模块的范围内
我想这个例子会对你有所帮助https://canary.ember-twiddle.com/468a737efbbf447966dd83ac734f62ad?openFiles=utils.tree-helpers.js%2C
所以,这是一个有趣的问题。结果证明这是一个递归问题,而不是与 ember、javascript 或复选框行为有关的任何问题。
这是我所拥有的(使用更新的语法等(如果您可以选择升级到 3.4,您绝对应该——这是一个梦想))
// wrapping-component.js
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
}]
}];
@action
toggleChecked(id) {
const newTree = check(this.options, id);
this.set('options', newTree);
}
}
模板:
{{yield this.options (action this.toggleChecked)}}
和用法:
// application.hbs
<WrappingComponent as |options toggle|>
{{#each options as |item|}}
<CheckboxGroup @item={{item}} @onClick={{toggle}} />
{{/each}}
</WrappingComponent>
CheckboxGroup 是一个仅用于模板的组件:
// checkbox-group.hbs
<div class="checkboxhandler">
<input
type="checkbox"
checked={{@item.checked}}
onclick={{action @onClick @item.id}}
>
<label>{{@item.label}}</label>
{{#if @item.children}}
{{#each @item.children as |child|}}
<CheckboxGroup @item={{child}} @onClick={{@onClick}} />
{{/each}}
{{/if}}
</div>
和递归助手(这是一团糟,但我刚刚制作了原型):
// utils/tree-helpers.js
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 selectOnlySubtree(tree, 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) {
return {
...tree,
checked: id === 'all' ? transform(tree.checked) : tree.checked,
children: check(tree.children, id, transform)
};
}
export function didChange(treeA, treeB) {
const rootsChanged = treeA.checked !== treeB.checked;
if (rootsChanged) return true;
if (treeA.children && treeB.children) {
const compares = treeA.children.map((childA, index) => {
return didChange(childA, treeB.children[index]);
});
const nothingChanged = compares.every(v => v === false);
return !nothingChanged;
}
return false;
}
希望这对您有所帮助。
我正在开发点击和放置功能 --- 在页面上以递归方式使用模块的位置,因此它具有 parent 和 children。
我遇到了一个问题,如果用户开始 select children - 然后 selects parent - 我想 deselectchild仁。尽管我不确定如何存储或监控 parent/child 项目中的变化 selected 以进行全局 deselection.
因此用户已经 select 编辑了 bacon3 的 child.. 如果他们 select parent - 则需要删除 select children -- 但我感觉我目前被锁定在模块的范围内
我想这个例子会对你有所帮助https://canary.ember-twiddle.com/468a737efbbf447966dd83ac734f62ad?openFiles=utils.tree-helpers.js%2C
所以,这是一个有趣的问题。结果证明这是一个递归问题,而不是与 ember、javascript 或复选框行为有关的任何问题。
这是我所拥有的(使用更新的语法等(如果您可以选择升级到 3.4,您绝对应该——这是一个梦想))
// wrapping-component.js
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
}]
}];
@action
toggleChecked(id) {
const newTree = check(this.options, id);
this.set('options', newTree);
}
}
模板:
{{yield this.options (action this.toggleChecked)}}
和用法:
// application.hbs
<WrappingComponent as |options toggle|>
{{#each options as |item|}}
<CheckboxGroup @item={{item}} @onClick={{toggle}} />
{{/each}}
</WrappingComponent>
CheckboxGroup 是一个仅用于模板的组件:
// checkbox-group.hbs
<div class="checkboxhandler">
<input
type="checkbox"
checked={{@item.checked}}
onclick={{action @onClick @item.id}}
>
<label>{{@item.label}}</label>
{{#if @item.children}}
{{#each @item.children as |child|}}
<CheckboxGroup @item={{child}} @onClick={{@onClick}} />
{{/each}}
{{/if}}
</div>
和递归助手(这是一团糟,但我刚刚制作了原型):
// utils/tree-helpers.js
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 selectOnlySubtree(tree, 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) {
return {
...tree,
checked: id === 'all' ? transform(tree.checked) : tree.checked,
children: check(tree.children, id, transform)
};
}
export function didChange(treeA, treeB) {
const rootsChanged = treeA.checked !== treeB.checked;
if (rootsChanged) return true;
if (treeA.children && treeB.children) {
const compares = treeA.children.map((childA, index) => {
return didChange(childA, treeB.children[index]);
});
const nothingChanged = compares.every(v => v === false);
return !nothingChanged;
}
return false;
}
希望这对您有所帮助。