sebfz1 / wicket-jquery-ui

jQuery UI & Kendo UI integration in Wicket
http://www.7thweb.net/wicket-jquery-ui/
Other
93 stars 58 forks source link

Using checkboxes for selecting (+select all) of items in DataTable #327

Closed pekour closed 3 years ago

pekour commented 3 years ago

Hello

I need rows in DataTable to be multi-selectable, ideally using checkboxes (with additional "Select All" checkbox on the top of checkbox column). What would be the best approach to implementing this feature? Perhaps, there are any easier-to-implement alternatives?

I'm still on 8.x version

haritos30 commented 3 years ago

Hello,

I had the same need for a checkbox column and since wicket doesn't provide one out of the box, I created my own implementation:

See if it helps

public class CheckboxColumn extends AbstractColumn {

    private static final long serialVersionUID = 1L;

    private static final int DEFAULT_WIDTH = 43;

    public CheckboxColumn() {
        super("", DEFAULT_WIDTH);
    }

    public CheckboxColumn(String title) {
        super(title, DEFAULT_WIDTH);
    }

    public CheckboxColumn(String title, int width) {
        super(title, width);
    }

    @Override
    public String getAttributes() {
         return "{ \"class\" : \"custom-checkbox\"}";
    }

    @Override
    public String toString() {

        StringBuilder result = new StringBuilder(super.toString());
        result.append(", ");
        BuilderUtils.append(result, "selectable", true);
        return result.toString();
    }
}
haritos30 commented 3 years ago

Now that I think about it you also need to add this ajax behavior to catch the checked event: This goes on your datatable behaviour and binds with the change event:

 protected JQueryAjaxBehavior onCheckedAjaxBehavior(IJQueryAjaxAware source) {           

     return new JQueryAjaxBehavior(source) {                                             

         private static final long serialVersionUID = 1L;                                

         @Override                                                                       
         protected CallbackParameter[] getCallbackParameters() {                         

             return new CallbackParameter[] {                                            
                 CallbackParameter.context("e"),                                         
                 CallbackParameter.resolved("values", "this.selectedKeyNames()")         
             };                                                                          
         }                                                                               

         @Override                                                                       
         protected JQueryEvent newEvent() {                                              
             return new CheckedEvent();                                                  
         }                                                                               
     };                                                                                  
 }       

And the event:

public class CheckedEvent extends JQueryEvent {                                  

    private final StringValue selectedLines;                                     

    public CheckedEvent() {                                                      

        selectedLines = RequestCycleUtils.getQueryParameterValue("values");      
    }                                                                            

    public String getSelectedLines() {                                           
        return selectedLines.toString();                                         
    }                                                                            
pekour commented 3 years ago

Thank you very much. It really gave be a column of checkboxes! I'm still not sure how to bind the behaviour to change event, but hope it is available somewhere in documentation

haritos30 commented 3 years ago

Hello,

It's a bit complicated. You need to bind a behavior like the following to your (customized) Datatable: This behavior acts for 3 types of events (but you only need one the change event).

public abstract class CustomDataTableBehavior extends DataTableBehavior {

    private static final long serialVersionUID = 1L;

    // Kendo UI Grid event names -- DO NOT CHANGE -- 
    private static final String SORTING_EVENT_NAME = "sort"; 
    private static final String COLUMN_RESIZE_EVENT_NAME = "columnResize"; 
    private static final String ON_CHANGE_EVENT = "change";

    private final JQueryAjaxBehavior onSortingAjaxBehavior;
    private final JQueryAjaxBehavior onColumnResizeAjaxBehavior;
    private final JQueryAjaxBehavior onCheckedAjaxBehavior;

    private final CustomDataTableListener listener;

    /**
     * 
     * @param selector 
     * @param options
     * @param columns
     * @param listener
     */
    public CustomDataTableBehavior(String selector, Options options, IModel<List<IColumn>> columns, CustomDataTableListener listener) {

        super(selector, options, columns, listener);

        this.listener = listener;
        this.onSortingAjaxBehavior = onSortingAjaxBehavior(this);
        this.onColumnResizeAjaxBehavior = onColumnResizeAjaxBehavior(this);
        this.onCheckedAjaxBehavior = onCheckedAjaxBehavior(this);

    }

    @Override
    public void onConfigure(Component component) {

        super.onConfigure(component);

        this.setOption(SORTING_EVENT_NAME, this.onSortingAjaxBehavior.getCallbackFunction());
        this.setOption(COLUMN_RESIZE_EVENT_NAME, this.onColumnResizeAjaxBehavior.getCallbackFunction());
        this.setOption(ON_CHANGE_EVENT, this.onCheckedAjaxBehavior.getCallbackFunction());
    }

    @Override
    public void bind(Component component) {

        super.bind(component);
        component.add(this.onSortingAjaxBehavior);
        component.add(this.onColumnResizeAjaxBehavior);
        component.add(this.onCheckedAjaxBehavior);
    }

    @Override
    public void onAjax(AjaxRequestTarget target, JQueryEvent event) {

        if (event instanceof SortingEvent) {
            SortingEvent e = (SortingEvent) event;
            this.listener.onSorting(target, e.getColumnName(), e.getDirection());
        } else if (event instanceof ColumnResizeEvent) {
            ColumnResizeEvent e = (ColumnResizeEvent) event;
            this.listener.columnResize(target, e.getColumnName(), e.getNewWidth(), e.getOldWidth());
        } else if (event instanceof CheckedEvent) {
            CheckedEvent e = (CheckedEvent) event;
            this.listener.onChecked(target, e.getSelectedLines());
        } else {
            super.onAjax(target, event);
        }
    }

    protected JQueryAjaxBehavior onSortingAjaxBehavior(IJQueryAjaxAware source) {

        return new JQueryAjaxBehavior(source) {

            private static final long serialVersionUID = 1L;

            @Override
            protected CallbackParameter[] getCallbackParameters() {

                return new CallbackParameter[] {    
                    CallbackParameter.context("e"),
                    CallbackParameter.resolved("columnName", "e.sort.field"),
                    CallbackParameter.resolved("direction", "e.sort.dir")
                };
            }

            @Override
            protected JQueryEvent newEvent() {
                return new SortingEvent();
            }
        };
    }

    protected JQueryAjaxBehavior onColumnResizeAjaxBehavior(IJQueryAjaxAware source) {

        return new JQueryAjaxBehavior(source) {

            private static final long serialVersionUID = 1L;

            @Override
            protected CallbackParameter[] getCallbackParameters() {

                return new CallbackParameter[] {
                    CallbackParameter.context("e"),
                    CallbackParameter.resolved("columnName", "e.column.field"),
                    CallbackParameter.resolved("newWidth", "e.newWidth"),
                    CallbackParameter.resolved("oldWidth", "e.oldWidth")
                };
            }

            @Override
            protected JQueryEvent newEvent() {
                return new ColumnResizeEvent();
            }
        };
    }

    protected JQueryAjaxBehavior onCheckedAjaxBehavior(IJQueryAjaxAware source) {

        return new JQueryAjaxBehavior(source) {

            private static final long serialVersionUID = 1L;

            @Override
            protected CallbackParameter[] getCallbackParameters() {

                return new CallbackParameter[] {
                    CallbackParameter.context("e"),
                    CallbackParameter.resolved("values", "this.selectedKeyNames()")
                };
            }

            @Override
            protected JQueryEvent newEvent() {
                return new CheckedEvent();
            }
        };
    }
}

Then in your datatable your need to Implement an IDataTableListener and 'listen' for the change event. This is the interface:

public interface CustomDataTableListener extends IDataTableListener {

    void onSorting(AjaxRequestTarget target, String columnName, String direction);

    void columnResize(AjaxRequestTarget target, String columnName, String newWidth, String oldWidth);

    void onChecked(AjaxRequestTarget target, String selectedLines);
}
sebfz1 commented 3 years ago

Hi Haritos, I had a quick look at your code, seems very good! Congrats! :)

Alexander, the way event binding works is described in the wiki section....

pekour commented 3 years ago

Sebastien, do you mean this Wiki section? https://github.com/sebfz1/wicket-jquery-ui/wiki

haritos30 commented 3 years ago

Hello Sebastien and thank you for the kind words! Perhaps wicket-kendo-ui can provide a checkbox column by default for anyone who needs it in the future. I will be happy to contribute to the code.

sebfz1 commented 3 years ago

Yes, I meant this one... Unfortunately, it is not where I was thinking.... Strange. I will look asap...

Sebastian, do you mean this Wiki section? https://github.com/sebfz1/wicket-jquery-ui/wiki

sebfz1 commented 3 years ago

Yes, absolutely! Thank you very much!

Hello Sebastien and thank you for the kind words! Perhaps wicket-kendo-ui

can provide a checkbox column by default for anyone who needs it in the future. I will be happy to contribute to the code.

haritos30 commented 3 years ago

That's great! One final piece of code is the following behavior that allows the 'checking' of the checkbox when clicking anywhere on the row. Only problem is that the table must have ID=dataTable for the JS to work, but the code can be optimized to work with any kendo table.

public class SelectRowOnClickDataBoundBehavior extends DataBoundBehavior {                                                  

    private static final long serialVersionUID = 1L;                                                                        

    @Override                                                                                                               
    protected String getDataBoundCallback() {                                                                               

        return "function(e) { "+                                                                                            

               "    function checkboxClick(e) {" +                                                                           
               "        if ($(e.target).hasClass('k-checkbox-label') || $(e.target).hasClass('k-button')) {" +              
               "             return;" +                                                                                     
               "        }" +                                                                                                
               "        var row = $(e.target).closest('tr');" +                                                             
               "        var checkbox = $(row).find('.k-checkbox');" +                                                       
               "        checkbox.click();" +                                                                                
               "    };"+                                                                                                    

               "    var grid = jQuery('#dataTable').data('kendoGrid');" +                                                   
               "    var rows = grid.tbody.find(\"[role='row']\");"  +                                                       
               "    rows.unbind('click');" +                                                                                
               "    rows.on('click', checkboxClick);" +                                                                       
               "}";                                                                                                         
    }                                                                                                                       
}       
pekour commented 3 years ago

Thank you, @haritos30. What is the proper approach to replacing the default DataTableBehaviour by a custom one? Should one override newWidgetBehavior method or what?

haritos30 commented 3 years ago

That's correct . You override the newWidgetBehavior method so that it returns your custom behaviour. Make sure your table also implements the CustomDataTableListener and pass this to the behaviour constructor. Eg:


...

    @Override
    public JQueryBehavior newWidgetBehavior(String selector) {

        return new CustomDataTableBehavior(selector, this.options, this.getModel(), this) {

             private static final long serialVersionUID = 1L;

            @Override
            protected long getRowCount() {

                return /* your table */.this.getRowCount();
            }

            @Override
            protected CharSequence getProviderUrl() {

                return /* your table */.this.getCallbackUrl();
            }

            @Override
            protected JQueryAjaxBehavior newCommandAjaxBehavior(IJQueryAjaxAware source, CommandButton button) {

                return /* your table*/.this.newCommandAjaxBehavior(source, button);
            }
        }
    }
pekour commented 3 years ago

That's great! One final piece of code is the following behavior that allows the 'checking' of the checkbox when clicking anywhere on the row.

How do I add properly this behaviour to the table? When I do simple table.add(new SelectRowOnClickDataBoundBehavior()), I receive JS error in Wicket Ajax Debug Window error Wicket.Ajax.Call.processEvaluation: Exception evaluating javascript: TypeError: $w is undefined,: function() {var $w = jQuery('#datatable6f').data('kendoGrid'); $w.bind('dataBound', function(e) { function checkboxClick(e)....

haritos30 commented 3 years ago

that's because JS is looking for a table with id datatable6f (and can't find it) which looks like an ID generated by wicket. Did you set an ID in your table element in the HTML code and used this id in your JS code? eg something like this:

 <div id="dataTable" style="margin-right: 1em;" wicket:id="DataTable">[datatable]</div>

Also you need to add your behavior during initialization so override method onInitialise() and do it there.

pekour commented 3 years ago

Thanks for hint! Nope, I didn't set the id manually. more likely that I set the behaviour too late to the table (after initialization)

mdbergmann commented 3 years ago

Hi @haritos30 , @sebfz1

I would need this feature as well. Would be cool to get this into the main branch. Can you guys get this wrapped up for a PR?

sebfz1 commented 3 years ago

Hi Manfred,

Except if I missed something, I don't think I got the PR yet...

@haritos30 https://github.com/haritos30, will you be able to manage it? Otherwise I will dig into it...

On Mon, Jan 11, 2021, 18:19 Manfred Bergmann notifications@github.com wrote:

Hi @haritos30 https://github.com/haritos30 , @sebfz1 https://github.com/sebfz1

I would need this feature as well. Would be cool to get this into the main branch. Can you guys get this wrapped up for a PR?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sebfz1/wicket-jquery-ui/issues/327#issuecomment-757780184, or unsubscribe https://github.com/notifications/unsubscribe-auth/AANDHGDTV2DAYCR6TBMUHATSZK7D7ANCNFSM4VEBRNKA .

mdbergmann commented 3 years ago

Hi Sebastien.

I don't think I got the PR yet

Yes, I know. :)

@haritos30 could look into it?

haritos30 commented 3 years ago

Hi guys,

I will open a PR today, however we will probably need a wiki page with a working example to make life easier for the developers (done)

haritos30 commented 3 years ago

Pull request is ready.

sebfz1 commented 3 years ago

Thanks! I will have a look asap!

sebfz1 commented 3 years ago

Deployed 8.10.1-SNAPSHOT, please give it a try and I will roll a release...

See https://github.com/sebfz1/wicket-jquery-ui/pull/328 for usage