Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deep setting/getting/observing #11

Closed
Rich-Harris opened this issue Nov 21, 2016 · 10 comments
Closed

Deep setting/getting/observing #11

Rich-Harris opened this issue Nov 21, 2016 · 10 comments

Comments

@Rich-Harris
Copy link
Member

Don't quite know how important this is, and whether it justifies the extra complexity it would entail:

component.observe( 'some.deep.keypath', console.log );
component.set( 'some.deep.keypath', 42 );
const answer = component.get( 'some.deep.keypath' );

For now I'm inclined not to bother with it, but I'll leave this issue here anyway in case I change my mind.

@evs-chris
Copy link
Contributor

It took me a little while to find the viewmodel 😄, and as it stands, it's refreshingly simple. Perhaps there could be an option to plug in viewmodels that implement a prescribed API - kinda like ractive adaptors flipped on their heads? That would let some purpose-built library take over the whacky hijinks involved with component.set('foo', { bar: { baz: 123 } }) triggering a foo.bar.baz observer.

@Rich-Harris
Copy link
Member Author

kinda like ractive adaptors flipped on their heads

I think we're thinking on similar lines! Perhaps something like

import FirebaseAdaptor from 'svelte-firebase';
const adaptor = new FirebaseAdaptor( someComponent, firebaseRef );

adaptor.set( 'x', y );
adaptor.observe( 'some.deep.keypath', callback );

Big related unsolved question is whether there's a good way to only trigger needed updates in response to a deep set – right now it updates the entire component, and sort of gets away with it because there's not much overhead in the system, but it's not totally ideal. Is most important with lists – in my initial sketches I wondered about exposing add and remove methods that could take advantage of internal logic...

component.myList = {
  add ( index, items ) {
    // insert new iterations, don't update existing ones
  },

  remove ( index, count ) {
    // remove iterations
  }
}

...the thinking being that you could build an adaptor-like interface around it similar to ractive.merge(...) or ractive.push(...) etc, without needing to include those methods with each component.

@evs-chris
Copy link
Contributor

Was looking into #51, and I think some sort of shared viewmodel-ish thing may become necessary as more complex cases pop up. The nested partials case kinda needs something to handle the nested keypath/data scenario, and if you address that by making partials private pseudo-components, you still have may have issues with SSOT and sibling things pointed at the same data getting out of sync. I don't have a wizard hat though, so I may be a bit off base 😄

@mefjuu
Copy link

mefjuu commented Dec 19, 2016

Any chances for seeing it before 2017? :) Great job with Svelte, but lack of set('so.so.deep.key', val) (especially in templates) hurts...

How do you handle such updates in a current version?

@Rich-Harris
Copy link
Member Author

@mefjuu currently you would have to do it in two steps, like so:

var so = component.get( 'so' );
so.so.deep.key = 'other';
component.set({ so });

Am hesistant to add it to the core API, but it's something that could be done via a wrapper:

function wrap ( component ) {
  return {
    get ( keypath ) {
      const keys = keypath.split( '.' );
      let value = component.get( keys.shift() );
      while ( keys.length ) value = value[ keys.shift() ];
      return value;
    },

    set ( keypath, value ) {
      const keys = keypath.split( '.' );
      const lastKey = keys.pop();
      const object = this.get( keys.join( '.' ) );
      object[ lastKey ] = value;

      const data = {};
      data[ keys[0] ] = component.get( keys[0] );
      component.set( data )
    }
  };
}

const wrapper = wrap( component );
wrapper.set( 'so.so.deep.key', val );

@Rich-Harris
Copy link
Member Author

Going to close this issue as I don't think it's necessary to have this in core — could certainly be put into an external wrapper if there's popular demand

@aubergene
Copy link

Is there a good pattern for deep observing?

@Rich-Harris
Copy link
Member Author

@aubergene I'd probably create a new method for that. Something like this should work:

export default {
  methods: {
    observeDeep (keypath, callback, opts) {
      const parts = keypath.replace(/\[(\d+)\]/g, '.$1').split('.');
      let last = undefined;
      return this.observe(parts.shift(), newValue => {
        for (let i = 0; i < parts.length; i += 1) newValue = newValue[parts[i]];
        if (newValue !== last || typeof newValue === 'object' || typeof newValue === 'function') {
          callback.call(this, newValue, last);
        }
        last = newValue;
      }, opts);
    }
  }
};

We could add that to svelte-extras.

@Rich-Harris
Copy link
Member Author

Update: observeDeep has been added to svelte-extras, in response to a StackOverflow question

@jslegers
Copy link

jslegers commented Aug 3, 2017

Suppose I have a DataTable component, which uses a Table component, which uses a TableRow component, which uses a TableCell component, which uses a TableCellInput component.

Now, suppose I want to observe any changes in any TableCellInput of my DataTable at the DataTable level rather than at the TableCellInput. For example, suppose I want my DataTable to keep (1) track of user input, (2) trigger a valueChanged event at the DataTable level when a value changes, (3) trigger validation, (4) optionally send a PUT or PATCH request with all values in that particular TableRow to update the data in my dataStore on the server (if validation succeeds) and then (5) optionally reposition the TableRow in the Table if (the field that's been changed is a field that's being sorted on).

What would be the proper way to do that in Svelte?

Or to rephrase my question : how do I deal with observing changes to the properties of deeply nested components? Adding individual observers to every this.refs.table.refs.row[y].refs.cell[x].refs.input.value where x and y are respectively the column positions and row positions doesn't seem quite efficient to me. Also, I can't exacly say it looks very elegant!

I suppose a whole lot more elegant approach than a whole bunch of observers would by be to trigger an event in my TableCellInput component and have it bubble / propagate all the way to the DataTable component. That way, I could emit an event at the TableCellInput level whenever a value is changed and capture the event at the DataTable level. However, I don't think that's currently supported, right?

See also #576 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants