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;
}

希望这对您有所帮助。