ractivejs / ractive

Next-generation DOM manipulation
http://ractive.js.org
MIT License
5.94k stars 396 forks source link

Push and splice do not work in filtered arrays #3247

Closed kouts closed 6 years ago

kouts commented 6 years ago

Description:

Disclaimer: I don't now if this is a bug or not. Ractive's push and splice do not seem to wok when arrays are filtered. Check the playground link, I have a method for filtering that's called filter_list. When I'm passing data to the component using the filtering method, then Ractive's push n splice methods do not seem to work (click 1st button). Instead I have to use a workaround to make it work (2nd button).

This only happens when the arrays are filtered.

Versions affected:

0.10.3

Platforms affected:

All Browsers

Reproduction:

Playground link

evs-chris commented 6 years ago

This is one of those things that looks like it should work but can't, because you're effectively operating on an ephemeral clone of the array that gets recomputed when the array changes. Even the workaround is probably not doing what you expect because of the computation - there's actually data getting initialized on the component from the computation and when you do the set, it's only affecting data in the component not at the source.

The way I usually handle this sort of thing is to let the component handle the filtering, so that it has a handle to the original array to mutate it. Any approach that lets the component know what the source array(s) should be should work.

kouts commented 6 years ago

I don't want to mutate the original array, maybe the isolated: false setting is confusing. I only want it to operate on the copy. So in order to do that I have to create the filtering method on the component?

evs-chris commented 6 years ago

Ah! I misunderstood your intent. I think what you want then is to just pass the data in, which you can do with a static mapping e.g. <mycomponent left="[[filter(...)]]" right="[[filter(...)]]" />.

kouts commented 6 years ago

Thank you @evs-chris that works, but what I want to achieve is that when data changes outside of the component then the data inside the component updates too. I understand that if I want the data passed inside the component to be filtered then I should use another way and maybe not a generic filter method I'm using right now? How could I achieve this?

fskreuz commented 6 years ago

This feels like a data architecture exercise. In particular, removing data duplication. In this case, what I would do is have one big array of the items involved. Anything else that can be derived from this array would then just be computed properties.

For instance, the items that initially show on the left and right could be derived from this one big array and two other arrays containing the IDs of what goes to the left and right. To move them around, I just move the IDs around while the items of the big array stay put. Here's a demo

const data = {
  clauses: [{
    "ZZEXTCLAUSE_ID": 16,
    "ZZCLAUSEPRFX": "",
    "ZZBAL_KLDBEZ": "Desc 1",
    "ZZDESCRIPTION": "Desc 1",
    "KLUSING": "101",
    "KLUKEY1": "00910017114",
    "KLUKEY2": "",
    "KLUKEY3": "",
    "KLDCODE": "051",
    "ALGBEGD": "2017-09-07",
    "ALGVERS": 1,
    "ZZSELECTED": false,
    "ZZLOB_ID": "1"
  }, {
    "ZZEXTCLAUSE_ID": 17,
    "ZZCLAUSEPRFX": "",
    "ZZBAL_KLDBEZ": "Desc 2",
    "ZZDESCRIPTION": "Desc 2",
    "KLUSING": "102",
    "KLUKEY1": "00910017114",
    "KLUKEY2": "910017122",
    "KLUKEY3": "",
    "KLDCODE": "042",
    "ALGBEGD": "2017-09-07",
    "ALGVERS": 1,
    "ZZSELECTED": false,
    "ZZLOB_ID": "1"
  }, {
    "ZZEXTCLAUSE_ID": 18,
    "ZZCLAUSEPRFX": "",
    "ZZBAL_KLDBEZ": "Desc 3",
    "ZZDESCRIPTION": "Desc 3",
    "KLUSING": "150",
    "KLUKEY1": "00910017114",
    "KLUKEY2": "910017115",
    "KLUKEY3": "",
    "KLDCODE": "038",
    "ALGBEGD": "2017-09-07",
    "ALGVERS": 1,
    "ZZSELECTED": false,
    "ZZLOB_ID": "2"
  }, {
    "ZZEXTCLAUSE_ID": 1,
    "ZZCLAUSEPRFX": "",
    "ZZBAL_KLDBEZ": "Desc 4",
    "ZZDESCRIPTION": "Desc 4",
    "KLUSING": "101",
    "KLUKEY1": "00910017114",
    "KLUKEY2": "",
    "KLUKEY3": "",
    "KLDCODE": "003",
    "ALGBEGD": "0000-00-00",
    "ALGVERS": 0,
    "ZZSELECTED": false,
    "ZZLOB_ID": "1"
  }, {
    "ZZEXTCLAUSE_ID": 2,
    "ZZCLAUSEPRFX": "",
    "ZZBAL_KLDBEZ": "Desc 5",
    "ZZDESCRIPTION": "Desc 5",
    "KLUSING": "101",
    "KLUKEY1": "00910017114",
    "KLUKEY2": "",
    "KLUKEY3": "",
    "KLDCODE": "004",
    "ALGBEGD": "0000-00-00",
    "ALGVERS": 0,
    "ZZSELECTED": false,
    "ZZLOB_ID": "1"
  }, {
    "ZZEXTCLAUSE_ID": 3,
    "ZZCLAUSEPRFX": "",
    "ZZBAL_KLDBEZ": "Desc 6",
    "ZZDESCRIPTION": "Desc 6",
    "KLUSING": "101",
    "KLUKEY1": "00910017114",
    "KLUKEY2": "",
    "KLUKEY3": "",
    "KLDCODE": "006",
    "ALGBEGD": "0000-00-00",
    "ALGVERS": 0,
    "ZZSELECTED": false,
    "ZZLOB_ID": "2"
  }],
  top: [16, 17],
  bottom: [1, 2]
}

const TwoRowSelect = Ractive.extend({
  template: `
    <ul>
      {{#each topDisplay }}
        <li class-active="@this.isActive(ZZEXTCLAUSE_ID)" on-click="@this.select(ZZEXTCLAUSE_ID)">{{ ZZDESCRIPTION }}</li>
      {{/each}}
    </ul>
    <button on-click="@this.moveSelections()">Move</button>
    <ul>
      {{#each bottomDisplay }}
        <li class-active="@this.isActive(ZZEXTCLAUSE_ID)" on-click="@this.select(ZZEXTCLAUSE_ID)">{{ ZZDESCRIPTION }}</li>
      {{/each}}
    </ul>
  `,
  css: `
    .active{ color: red }
  `,
  data: () => ({
    items: [],
    top: [],
    bottom: [],
    selected: []
  }),
  computed: {
    topDisplay() {
      const items = this.get('items')
      const top = this.get('top')
      return items.filter(i => top.indexOf(i.ZZEXTCLAUSE_ID) > -1)
    },
    bottomDisplay() {
      const items = this.get('items')
      const bottom = this.get('bottom')
      return items.filter(i => bottom.indexOf(i.ZZEXTCLAUSE_ID) > -1)
    }
  },
  isActive(id) {
    return this.get('selected').indexOf(id) > -1
  },
  select(id) {
    const index = this.get('selected').indexOf(id)
    if (index > -1) {
      this.splice('selected', index, 1)
    } else {
      this.push('selected', id)
    }
  },
  moveSelections() {
    const top = this.get('top')
    const bottom = this.get('bottom')
    const selected = this.get('selected')

    this.set('top', this.smoosh(top, selected))
    this.set('bottom', this.smoosh(bottom, selected))
    this.set('selected', [])
  },
  smoosh(arr1, arr2) {
    const stuffInArr1 = arr1.filter(v => arr2.indexOf(v) === -1)
    const stuffInArr2 = arr2.filter(v => arr1.indexOf(v) === -1)
    return [...stuffInArr1, ...stuffInArr2]
  }
})

const App = Ractive.extend({
  components: { TwoRowSelect },
  data: () => ({
    clauses: [],
    top: [],
    bottom: []
  }),
  template: `
    <TwoRowSelect items="{{ clauses }}" top="{{ top }}" bottom="{{ bottom }}" />
    <p>Top: {{ JSON.stringify(top) }}</p>
    <p>Bottom: {{ JSON.stringify(bottom) }}</p>
  `
})

App({ target: '#target', data })
kouts commented 6 years ago

Ok so the correct way to achieve what I want is with computed properties, got it, thank you @fskreuz .