stevenrskelton / sortable-table

Polymer Web Component that generates a sortable <table> from inlined or AJAX JSON, JSON5, and arrays.
https://stevenrskelton.github.io/sortable-table/
MIT License
196 stars 37 forks source link

How to add events to buttons #18

Open optikalefx opened 10 years ago

optikalefx commented 10 years ago

I've added buttons to my table via the rowTemplate. But how am I able to attach events to those? Right now the only thing that comes to mind is to attach an event to the entire element and look for e.target to test if it's that button. Kind of lame.

Thanks

optikalefx commented 10 years ago

Actually that doesn't even work. So not sure what to do here.

optikalefx commented 10 years ago

So for now, in the rowSelect method I've added this

if(e.target.templateInstance) {
    this.fire('rowClick', {
        data: e.target.templateInstance.model.record.row,
        target: e.target
    });
}

To use it

$('.bootstrap').on('rowClick', function(e) {
    console.log(e.originalEvent.detail);
});

If you want this to be a PR, let me know.

stevenrskelton commented 10 years ago

Events definitely need a little work; none have been defined yet but they are necessary so I'm working on adding them.

Another person had this same issue (sortable-table#12) where I've explained the standard web components approach.

optikalefx commented 10 years ago

I think my approach is way simpler then the one mentioned in #12. Right now I ruin the ability to run bower update because of the code change. But the code needed is only like 4 lines. I Made a btnClick event.

if(e.target.templateInstance && e.target.nodeName === "BUTTON") {
    this.fire('btnClick', {
        data: e.target.templateInstance.model.record.row,
        target: e.target
    });
}

Usage is just

$("sortable-table").on("btnClick", function(e) {
    e.originalEvent.detail.data; // row data
    e.originalEvent.detail.target; // actual button element
});

This code works on any button element inside any of my sortable tables. Works really well. I do agree that binding btnClick to the table is a bit contrived.

stevenrskelton commented 10 years ago

Thanks for your input. :)

I was pointing out for tough cases you can fall back on that approach; but I really don't think it should be necessary to wrap every button inside a web component just to capture the event.

I'll likely merge in something close to your first snippet, hard coding node names like in the last one won't work for people using polymer's core-button or paper-button

optikalefx commented 10 years ago

Yea for sure. It just worked for my case. I can write a generic one that delegates a selector if you want. So you could bind (rowClick, "button") or ("rowClick",".somethingInMyTemplate")

stevenrskelton commented 10 years ago

I think I'm going to go this route:

Previously I added an args attribute parameter to sortable-table to pass in extra data needed by the template (meta-data which doesn't belong in the data array).

I'll do the same thing with an eventHandlers attribute. While binding on-* to function variables doesn't work, it is possible to turn those function variables into sortable-table properties....

<polymer-element name="test-element">
    <template>
        <button on-click="{{eventHandlers_stuff}}">Do Stuff</button>
    </template>
    <script>
    "use strict";
    Polymer({
        eventHandlers: {
            stuff: function(){
                console.log('stuff');
            }
        },
        ready: function(){
            var self = this;
            var names = Object.getOwnPropertyNames(self.eventHandlers);
            names.forEach(function(name){
                self['eventHandlers_' + name] = self.eventHandlers[name];
            });
        }
    });
    </script>
</polymer-element>

Notice <button on-click="{{eventHandlers.stuff}}"> doesn't work, but above syntax does and it's super close - so if they ever change polymer to support this it would be a very minimal change.

optikalefx commented 10 years ago

Apologies for being new to polymer but can you show what the use case of that is? Without firing the event I don't see a way to then bind to it to define the click functionality.

Or are you making a new button element to be used which would fire its own click event? On Oct 22, 2014 11:13 PM, "Steven Skelton" notifications@github.com wrote:

I think I'm going to go this route:

Previously I added an args attribute parameter to sortable-table to pass in extra data needed by the template (meta-data which doesn't belong in the data array).

I'll do the same thing with an eventHandlers attribute. While binding on-* to function variables doesn't work, it is possible to turn those function variables into sortable-table properties....

Notice

stevenrskelton commented 10 years ago

Instead of using

$('.bootstrap').on('rowClick', function(e) {
    console.log(e.originalEvent.detail);
});

You would define your event handlers via

<sortable-table eventHandlers="{{myEventHandlers}}"></sortable-table>

where

myEventHandlers = {  
           stuff: function(){
                console.log('stuff');
           }
}

You should be able to reference these handlers on any elements (like buttons) in your templates.

The actual scope and arguments that would be passed when executing myEventHandlers.stuff is a bit of a mystery, but I think i can coax them into something workable.

I might do both - not sure yet I'll have to play around with it for a while.

optikalefx commented 10 years ago

So I really don't like the idea of passing an events object in. When I built OpenJSGrid I started out with property defined events and people felt really limited by that. It was much easier to bind to events on the grid, which would fire in context.

In your example there, where is myEventHandlers defined? Global scope? What about in a framework where I want my events with my other UI manipulation code?

Maybe if a <sortable-table-button name="myFireableButton"> existed and then you bound to $("sortable-table").on("click.myFireableButton"). Or some namespaced convention.

optikalefx commented 10 years ago

The more I look into this, the more we need a combination approach. There is no way I can tell to expose the inner elements for delegation.

So you might be right that the only way to do this is to pass in a functions object. When I'm not on the clock I'll investigate more.

optikalefx commented 10 years ago

I did one other thing that is a bit more useful. In this case, I had to apply events to the buttons without user interaction.

I added this function

modifyRows: function(modifier) {
            if(modifier) {
                var rows = this.shadowRoot.querySelectorAll("tbody tr");
                [].forEach.call(rows, function(row) {
                    modifier(row);
                });
            }
        },

Then fired this event at the end of your ready statement this.fire("loaded");

So I'm able to just bind to loaded, and then do whatever I want with the row

$(".bootstrap").on("loaded", function() {
    this.modifyRows(function(row) {
            $(row).find(".copyUrl").each(function() {