These are chat archives for canjs/canjs

11th
Jan 2019
Gregg Roemhildt
@roemhildtg
Jan 11 18:02
is there any way to pass events to a component through stache? Like <my-component events:from="this.someEvents" />?
Justin Meyer
@justinbmeyer
Jan 11 18:07
@roemhildtg what do you mean by "events"?
an array of event objects?
Gregg Roemhildt
@roemhildtg
Jan 11 18:08
Justin Meyer
@justinbmeyer
Jan 11 18:08
I don't think it is accessible ... let me check
why do you want to pass it?
(instead of the view model?)
Gregg Roemhildt
@roemhildtg
Jan 11 18:10
I think it would be useful. Take a table component for instance. I could use that table component and register delegated event handlers programatically. Especially useful if my table component renders its cells from partial templates that are passed to the table
Without having to create a new "wrapper" component
Justin Meyer
@justinbmeyer
Jan 11 18:10
Couldn't you do this w/ listenTo() in the connectedCallback()?
Gregg Roemhildt
@roemhildtg
Jan 11 18:11
I'm thinking events like
'button.custom click': function(){ 
// do something when this button is clicked
 }
Justin Meyer
@justinbmeyer
Jan 11 18:11
I'm working on updating can-components docs and planning on deprecating events in favor of listenTo() / connectedCallback
hmmm ... I'm not sure if listenTo can do event delegation ... I could probably make it work if not ...
Gregg Roemhildt
@roemhildtg
Jan 11 18:12
So I could pass a custom connectedCallback function to the component to do this?
Justin Meyer
@justinbmeyer
Jan 11 18:12
connectedCallback(el){
  this.listenTo(el, "button", "click", function(){

  })
}
so do you want to pass the Control or the control instance?
Gregg Roemhildt
@roemhildtg
Jan 11 18:13
<my-component connectedCallback:from="registerEventButtonDelegation"/>
Justin Meyer
@justinbmeyer
Jan 11 18:13
I don't think I understand the use case yet
Gregg Roemhildt
@roemhildtg
Jan 11 18:14
I'll give you an example
So basically, I want to pass in custom content to render and use event delegation on some of that custom content
To do some custom actions
Not necessarily change the viewmodel but maybe do something like show a popup etc
Does that make it a little more clear?
Justin Meyer
@justinbmeyer
Jan 11 18:25
yes, a bit ....
Gregg Roemhildt
@roemhildtg
Jan 11 18:26
Imagine a table component where your rendering a row of data. You can tell the table how to render a particular property, by passing a partial template to the component.
Justin Meyer
@justinbmeyer
Jan 11 18:26
That partial could get passed a function to call
Gregg Roemhildt
@roemhildtg
Jan 11 18:26
And if that partial contains a button, you can tell the table to listen to button clicks
Justin Meyer
@justinbmeyer
Jan 11 18:27
const template = stache(`<button on:click="action()" class="custom">Do stuff!</button>`);
Gregg Roemhildt
@roemhildtg
Jan 11 18:27
Yep, that's what I'm currently doing
Justin Meyer
@justinbmeyer
Jan 11 18:27
<panel-component> would currently have to take action and pass it down
Gregg Roemhildt
@roemhildtg
Jan 11 18:27
But it doesn't use delegation. So if you render 1000 rows it might be slow
The other thing is delegation works with 3rd party libraries. Like if I have a component that inserts some dom elements, I can use delegation to listen to those dom elements even though they're not rendered in stache
Justin Meyer
@justinbmeyer
Jan 11 18:28
you might be able to make helpers that do this ...
<panel-component contentRenderer:from="template" {{doStuff}} />
Gregg Roemhildt
@roemhildtg
Jan 11 18:30
doStuff would be the helper that adds the event delegation?
Justin Meyer
@justinbmeyer
Jan 11 18:30
yeah, or uses can-control directly
I've long wanted to make this a bit easier to setup
this uses a custom attribute
and can-control
const Sortable = can.Control.extend({
  "{element} dropmove": function(el, ev, drop, drag) {
    this.fireEventForDropPosition(ev, drop, drag, "sortableplaceholderat");
  },
  "{element} dropon": function(el, ev, drop, drag) {
    this.fireEventForDropPosition(ev, drop, drag, "sortableinsertat");
  },
  fireEventForDropPosition: function(ev, drop, drag, eventName) {
    const dragData = can.domData.get(drag.element[0], "dragData");

    const sortables = $(this.element).children();

    for (var i = 0; i < sortables.length; i++) {
      //check if cursor is past 1/2 way
      const sortable = $(sortables[i]);
      if (ev.pageY < Math.floor(sortable.offset().top + sortable.height() / 2)) {
        // index at which it needs to be inserted before
        can.domEvents.dispatch(this.element, {
          type: eventName,
          index: i,
          dragData: dragData
        });
        return;
      }
    }
    if (!sortables.length) {
      can.domEvents.dispatch(this.element, {
        type: eventName,
        index: 0,
        dragData: dragData
      });
    } else {
      can.domEvents.dispatch(this.element, {
        type: eventName,
        index: i,
        dragData: dragData
      });
    }
  }
});

can.view.callbacks.attr("sortable", function(el) {
  new Sortable(el);
});
Gregg Roemhildt
@roemhildtg
Jan 11 18:31
ooh I see
So the control is the custom attribute "sortable"
Justin Meyer
@justinbmeyer
Jan 11 18:32
yeah
Gregg Roemhildt
@roemhildtg
Jan 11 18:32
I see a lot of possibilities with that
Justin Meyer
@justinbmeyer
Jan 11 18:32
you can do this with a function that returns a function too
registerHelper("myHelper", function(){
  return function(el){
    new Control(el)
  }
})
if you are using inserted, and don't want to use Control, you could do it like:
<div on:inserted="setupStuff(scope.element)"/>
Gregg Roemhildt
@roemhildtg
Jan 11 18:35
I think I like the can-control approach
Seems pretty flexible
Justin Meyer
@justinbmeyer
Jan 11 18:36
setupStuff(el){
  handler(){ ... }

  domEvents.addDelegateListener( el, "button", "click", handler);

  domMutate.onNodeRemoval(el, function(){ // this cleanup is probably not necessary, but there might be other cleanup
    domEvents.removeDelegateListener( el, "button", "click", handler);
  })
}
Gregg Roemhildt
@roemhildtg
Jan 11 18:38
<panel-component {{clickable('button.custom', clickHandlerFunction)}} contentRenderer:from="template" />
Justin Meyer
@justinbmeyer
Jan 11 18:41
you could even create your own binding
<panel-component delegate:click:button.custom="clickHandlerFunction( ... )"/>
Gregg Roemhildt
@roemhildtg
Jan 11 18:42
That would be cool.
Justin Meyer
@justinbmeyer
Jan 11 18:43
viewCallbacks.attr(/delegate:\w+:[\w.]+/, function(el, attrData){

})
Gregg Roemhildt
@roemhildtg
Jan 11 18:44
Okay!
you will want to copy that function
Gregg Roemhildt
@roemhildtg
Jan 11 18:47
so many args
:)
Justin Meyer
@justinbmeyer
Jan 11 18:48
// delegate:click:button="abc(scope.event)"
viewCallbacks.attr(/delegate:\w+:[\w.]+/, function(el, attrData){
  // PARSE attribute name
  var attributeName = encoder.decode(attrData.attributeName)
  var event, selector;

  domEvents.addDelegateListener( el, event, selector, function(ev){
     var attrVal = el.getAttribute(encoder.encode(attributeName)); 

      var expr = expression.parse(attrVal, { // convert `abc(scope.event)` to something runnable
                lookupRule: function() {
                    return expression.Lookup;
                },
                methodRule: "call"
            });


    runEventCallback(this, ev, attrData, attrData.scope, expr, attributeName, attributeValue)
  })
});
it doesn't seem like data is used
oh expr ..
something like that
but I realized, that this wouldn't work great with selectors with spaces :-(
Gregg Roemhildt
@roemhildtg
Jan 11 18:52
This is really close. I owe you a :beer: or :beers:
Justin Meyer
@justinbmeyer
Jan 11 18:53
<div delegate:click:span div="doSomething()"/> isn't valid HTML :-(
Gregg Roemhildt
@roemhildtg
Jan 11 18:53
Right
Justin Meyer
@justinbmeyer
Jan 11 18:53
You could change the parsing if you wanted ...
<div delegate:click="div span !@#$% doSomething()"/>
where !@#$% would be some special character
it's actually possible to parse out something like
<div delegate:click="delegate('div span', doSomething() )"/>
or you could even do something like <div on:click="and( matches('div span', scope.event.target ), doSomething() )"/>
now that and is lazy
it wont run doSomething() unless matches('div span', scope.event.target ) returns truthy
Gregg Roemhildt
@roemhildtg
Jan 11 18:57
Hmm! wow!
Slightly verbose, but definitly pretty sweet. Hadn't anticipated something like that would've been possible out of the box
where does matches come from?
That'd be a custom helper?
Justin Meyer
@justinbmeyer
Jan 11 19:01
yeah, though I think you could just call el.matches()
Gregg Roemhildt
@roemhildtg
Jan 11 19:01
on:click="delegate('div span.custom', doSomething)"
Justin Meyer
@justinbmeyer
Jan 11 19:01
<div on:click="and( scope.event.target.matches('div span'), doSomething() )"/>
I think that might work right now ...
that's fun
Gregg Roemhildt
@roemhildtg
Jan 11 19:03
That is fun
Gregg Roemhildt
@roemhildtg
Jan 11 19:19
Hmm, looks like at component elements, <my-component on:click="dosomething()" /> doesn't work?
Kevin Phillips
@phillipskevin
Jan 11 19:24
on:el:click="..."
if the element has a viewmodel, it defaults to binding to the viewmodel
Gregg Roemhildt
@roemhildtg
Jan 11 19:26
ah!
:100: