aurelia-v-grid / vGrid

Aurelia-v-grid - npm source
MIT License
49 stars 10 forks source link

aurelia vNext: bound passes only a binding flag and no IScope #100

Closed vegarringdal closed 5 years ago

vegarringdal commented 6 years ago

@fkleuver Answer if and when you have time

Now $bind and $attach etc are placed on the component's prototype by the decorator and you don't need to touch those. The lifecycles are separated out in terms of responsibility. The internal $bind will call the bound(scope: IScope) on your component if you implemented it, and same for attached(). IScope has bindingContext and overrideContext. So bind -> bound and attach -> attached. The ones starting with the dollar are internal.

Sample

My question is to how bound works

 public bound(scope: IScope){
    // arg is a number, maybe a map ID??
    console.log('bound scope', scope);
    // I would have expected App scope here (binding content)
  }

Sample: https://stackblitz.com/edit/aurelia-vnext-bug002?file=helloWorld.ts

From aurelia source

Been looking at this: https://github.com/aurelia/aurelia/blob/master/packages/runtime/src/templating/custom-element.ts#L167-L184

I would have expected bind(x,x, parentScope) here to have scope. Then in the last bound funtion to pass in the parentScope But maybe Im miss understanding the flags? What do they do ? πŸ˜„

Is like this:

function bind(this: IInternalCustomElementImplementation, flags: BindingFlags): void {
  if (this.$isBound) {
    return;
  }

  const scope = this.$scope;
  const bindables = this.$bindables;

  for (let i = 0, ii = bindables.length; i < ii; ++i) {
    bindables[i].$bind(flags | BindingFlags.fromBind, scope);
  }

  this.$isBound = true;

  if (this.$behavior.hasBound) {
    (this as any).bound(flags | BindingFlags.fromBind);
  }
}

Would have expected something like this:

function bind(this: IInternalCustomElementImplementation, flags: BindingFlags, parentScope: IScope): void {
  if (this.$isBound) {
    return;
  }

  const scope = this.$scope;
  const bindables = this.$bindables;

  for (let i = 0, ii = bindables.length; i < ii; ++i) {
    bindables[i].$bind(flags | BindingFlags.fromBind, scope);
  }

  this.$isBound = true;

  if (this.$behavior.hasBound) {
    (this as any).bound(parentScope);
  }
}
fkleuver commented 6 years ago

Sure we could pass the scope into bound, there's no harm in that. We're not currently doing it because it's just not needed. The scope is created and assigned before $bind even begins. So during created, bound, attaching and attached (and their undoing counterparts) you can always access this.$scope.

I have some work to do on explaining those flags, and perhaps keeping them out of the public API a bit better. But at the fundamental level, they replace the subscriber contexts e.g. sourceContext, targetContext and others. It's a bit like passing a state object around but much more performant. It allows us to give bindings and observers more information about the current state of a component, for example:

There are other interesting optimizations we can do with this. For example during $unbind in the repeater, we'll know whether the repeater is being disposed (e.g. get rid of all views) or if the unbind is simply called because $bind is called with a different scope. In this case, do not dispose anything, reuse the views.

Honestly, best way to understand them is see how they're used in the code. But don't worry too much about it, I don't imagine anyone will ever really need to deal with them.

vegarringdal commented 6 years ago

Sure we could pass the scope into bound, there's no harm in that. We're not currently doing it because it's just not needed. The scope is created and assigned before $bind even begins. So during created, bound, attaching and attached (and their undoing counterparts) you can always access this.$scope.

Ill try and explain why I think its needed in custom elements/attributes. Then you will understand why Im asking and maybe tell me a better way to it πŸ‘


When I use this this.$scope in my grid methods it have the context of the grid it self not parent. I think this looks nice and parentOverride should be nothing by default like it is. image

In the grid I generate the correct markup/view from settings I get with <v-grid-col, but I also let user pass in own html that they want to use. Here is a docs how users could define header and row template of column and use the grid default sort: https://vgrid.gitbook.io/docs/less-than-v-grid-col-greater-than-custom/v-sort

To do this I used the viewCompiler/factory to create & bind views. I also set correct bindingContext, parentOverridecontext with bindingContext and its `parentOverrideContext manually depending on what it is (row/footer etc).

This way user can add buttons that call triggers/interpolate etc to their custom html.

Maybe Im designing the grid weird. I know I have a lot I want to improve/clean up. but this part worked pretty well I think.

But again I really need to be better at naming variables with some prefix when I do something like this, since override can work against me if Im not careful (it does in the menus) So this time I will use more prefixes on naming of internal variables.

Its maybe not as fancy/good looking as some of the big ones as ag-grid, but it works... mostly πŸ˜„ (love screenToGif) coolio


those flags... It's a bit like passing a state object around but much more performant. .... There are other interesting optimizations we can do with this. For example during $unbind in the repeater, we'll know whether the repeater is being disposed (e.g. get rid of all views) or if the unbind is simply called because $bind is called with a different scope. In this case, do not dispose anything, reuse the views. Honestly, best way to understand them is see how they're used in the code. But don't worry too much about it, I don't imagine anyone will ever really need to deal with them.

I will try and dig more into it, but atm main focus is just getting the main parts so I can get to display something.. maybe πŸ˜‚ And maybe report a bug/ try and get a feature that can help me in the long run.

fkleuver commented 6 years ago

I think if you look at the repeater code and try to understand that, you'll be 80% there. A grid on a basic level is just two repeaters. I have some ideas for two-way binding with html node collections for drag-dropping rows and columns, and this is probably the best use case for that. I've got other stuff to finish first though :)

Btw you should also check this: https://github.com/aurelia-contrib/aurelia-vnext-starter

I think compose is a good component to look into. That'll give you your dynamic html compilation so you don't need to manually work with the template compiler.

vegarringdal commented 6 years ago

ok, thx. Ill have a look during the weekend.