Suggestions for connected consistency and improvement

6 posts, 0 answers
  1. RichardAD
    RichardAD avatar
    133 posts
    Member since:
    Feb 2012

    Posted 21 Sep Link to this post

    The different remove and add sequencing, and the incomplete event firing for state change initiators, makes it difficult to write code for custom processing of items in connected listboxes.

    Event order inconsistencies when list boxes are connected.

    Drag and drop actions fire events source listbox remove followed by target listbox add

    Toolbar command actions for moving fire events target listbox add followed by source listbox remove

    Suggestion: have command actions sequence in the same way as drag and drop.

    Drag and drop actions have an event dragend but there is no event for commandend

    There are no toolbar events.

    Suggestion: add toolbar or command events,

  2. RichardAD
    RichardAD avatar
    133 posts
    Member since:
    Feb 2012

    Posted 21 Sep in reply to RichardAD Link to this post

    Consider the case of custom processing the display of items that need to be selected from a pool and ordered to a preferential rank. 

    Two list boxes are connected. The items remaining in the pool are in the left list box and the selected and ordered items are in the right list box.  As the user moves the items about, the requirement is to mark items that affect the selected ones.

    For example:

    There are many P items remaining in a 'pool' of items
    The Q items represent an ordered set of items selected from the pool
     
    Read remote data source
    Items
    Name status seq seqnew
     A     P    nul  nul 
     B     P    nul  nul 
     C     P    nul  nul 
     D     P    nul  nul 
     E     Q     1    1  
     F     Q     2    2  
     G     Q     3    3   
                      
    Loop over items adding to appropriate list box
                      
    Initial configuration
    P's  Q's
    ---  ---
     A    E   
     B    F   
     C    G   
     D        
     
    operation: Move D to Q
    events: Q.add, P.remove
    P's  Q's
    ---  ---
     A    E
     B    F
     C    G
     .    D*
    (D in Q is always marked because it came from P)
     
    operation: Move E to P
    events: P.add, Q.remove
    P's  Q's
    ---  ---
     A    F*   
     B    G*  
     C    D*
     E*
    (E in P is always marked because it came from Q)
    (All Q's marked because they shifted positionally)
     
    operation: Move f down 1
    events: Q.reorder
    P's  Q's
    ---  ---
     A    G*   
     B    F  
     C    D*
     E*
    (F is back in original position so its flag is removed)
      
    operation: E dragged back to head of P
    events: P.remove, Q.add, P.dragend
    (dragend is useful because it is triggered after both list boxes are modified by kendoui.  No such equivalent for tool bar move commands)
    P's  Q's
    ---  ---
     A    E*
     B    G*  
     C    F*
     .    D*
    (F is marked again because position changed from original)
       
    operation: D* dragged back Q between A and B
    events: Q.remove, P.add, Q.dragend
    P's  Q's
    ---  ---
     A    E*
     D    G*  
     B    F*
     C
    (D is unmarked despite being in different position because reordering of original pool items does not matter)

     

    Save button clicked and P's and Q's sent to server

     

    I can't seem to find any clear path for definitive code that handles the events and tweaks the newseq according to the listbox an item is in and its current position.

  3. Stefan
    Admin
    Stefan avatar
    1137 posts

    Posted 25 Sep Link to this post

    Hello, Richard,

    Thank you for the report and the details.

    The different order in which the events are thrown is not expected and I logged this changed in our GitHub tasks:

    https://github.com/telerik/kendo-ui-core/issues/3616

    The commands do not have an event similar that dranEnd as drag and drop functionality has more phases than a click operation.

    Currently, as a workaround, I can suggest using a flag to determine if the command button was clicked, this will provide the information in which order the events will be executed, so the logic will be applied correctly. The flag can be set to true when the command button is clicked. To attach the event listeners I can suggest the dataBound event:

    dataBound:function(e){
     
                  $("[data-command='transferTo']").click(function(e){
                   //....
                  })
     
                 $("[data-command='transferFrom']").click(function(e){
                   //....
                  })
     
     
    }


    Regards,
    Stefan
    Progress Telerik
    Try our brand new, jQuery-free Angular 2 components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
  4. RichardAD
    RichardAD avatar
    133 posts
    Member since:
    Feb 2012

    Posted 25 Sep Link to this post

    Hi Stefan:

    Thanks for suggesting the use of a custom .click handler.  I find that it my handler is not invoked when the transfer buttons are clicked.  I am adding the handler in $() ready script block.  Are the list box toolbar click handlers updated by the component internals at a time other than widget initialization ?  I ask because I am using the list boxes in a single page web app and perform ajax to obtain new items for a data source and then distributing the items to the connected list boxes based on a state field in the model.

    An additional issue is that .click() is not part of the event stream if the keyboard is used to execute toolbar commands.  Do you have any suggestions for handling those events ?

     

    An MVC helper in cshtml is used to define a data source

    @(Html.Kendo().DataSource<LabReportSpecItemViewModel>()
        .Name("dataSource2")
        .Custom(custom => custom
            .Batch(true)
            .PageSize(750)
            .Transport(t => t
                .Read(R => R @* { lot: the-lot-to-read } must be passed at .read() *@
                    .Action("Spec_Read", "LabReport")
                    .ContentType("application/json")
                    .DataType("json")
                    .Type(HttpVerbs.Post)
                )
                .Update(U => U
                    .Action("Spec_Update", "LabReport")
                    .ContentType("application/json")
                    .DataType("json")
                    .Type(HttpVerbs.Post)
                )
                .ParameterMap("ds2_parameterMap")
            )
            .Schema(s => s
                .Data("Data").Total("Total").Errors("Errors")
                .Model(model => model.Id(p => p.SpecificationID))
            )
            .Events(e => e.Change("ds2_change"))
            .Sort(s => { s.Add("Seq"); s.Add("Name"); s.Add("Revision"); })
        )
    )

     

    which in turn renders as this kendoui

    dataSource2 = new kendo.data.DataSource({"transport":{"read":{"url":"/LabReport/Spec_Read","dataType":"json","type":"POST","contentType":"application/json"},"update":{"url":"/LabReport/Spec_Update","dataType":"json","type":"POST","contentType":"application/json"},"parameterMap":ds2_parameterMap},"pageSize":750,"page":0,"total":0,"sort":[{"field":"Seq","dir":"asc"},{"field":"Name","dir":"asc"},{"field":"Revision","dir":"asc"}],"change":ds2_change,"schema":{"data":"Data","total":"Total","errors":"Errors","model":{"id":"SpecificationID","fields":{"Lot":{"type":"string"},"SpecificationID":{"type":"number"},"Name":{"type":"string"},"Revision":{"type":"string"},"Material":{"type":"string"},"LabReportSpecID":{"type":"number","defaultValue":null},"Seq":{"type":"number","defaultValue":null}}}},"batch":true});

     

    A search term is entered and then the datasource is read() and the datasource change handler repopulates the data of two connected list boxes.

    function ds2_change(e) {
        console.log("ds2_change:" + e.action);
     
        if (!e.action) {
             
            var data = this.data();
     
            if (!labReport_specLb1) {
                labReport_specLb1 = $("#listbox1").data("kendoListBox");
            }
            if (!labReport_specLb2) {
                labReport_specLb2 = $("#listbox2").data("kendoListBox");
            }
     
            labReport_specLb1.dataSource.data([]);
            labReport_specLb2.dataSource.data([]);
     
            for (var i = 0; i < data.length-10; i++) {
                if (data[i].Seq == null) {
                    labReport_specLb1.add(data[i]);
                } else {
                    labReport_specLb2.add(data[i]);
                }
            }
     
            if (labReport_specLb2.dataSource.data().length >= LB2_MAX_ITEM_COUNT) {
                $('.dual-listbox1 [data-command="transferTo"]').css('visibility', 'hidden');
            }
     
            return;
        }
    }

     

    The list boxes themselves are defined via MVC helpers and followed by a jQuery() ready to install the custom additional toolbar button click handlers.

    <div class="dual-listbox-container">
        <div class="dual-listbox1">
            <label for="listbox1">Chemistry Specs</label><br />
            @(Html.Kendo().ListBox()
            .Name("listbox1")
            .HtmlAttributes(new { title = "Chemistry Specs" })
            .DataTextField("Name")
            .DataValueField("SpecificationID")
            .TemplateId("spec-item-template")
            .Draggable(draggable => draggable.Placeholder("specItemDndPlaceholder"))
            .DropSources("listbox2")
            .ConnectWith("listbox2")
            .Toolbar(toolbar =>
            {
                toolbar.Position(Kendo.Mvc.UI.Fluent.ListBoxToolbarPosition.Right);
                toolbar.Tools(tools => tools
                    .TransferTo()
                    .TransferFrom()
                );
            })
            .Events(e => e
                .Drop("lb1_drop")
                .Remove("lb1_remove")
                .Add("lb1_add")
                .DragStart("lb_dragstart")
                .DragEnd("lb1_dragend"))
            .BindTo(new List<dynamic>())
            )
        </div>
        <div class="dual-listbox2">
            <label for="listbox2">Lab Report Specs (3 max.)</label><br/>
            @(Html.Kendo().ListBox()
            .Name("listbox2")
            .HtmlAttributes(new { title = "Lab Report Specs" })
            .DataTextField("Name")
            .DataValueField("SpecificationID")
            .TemplateId("spec-item-template")
            .Draggable(draggable => draggable.Placeholder("specItemDndPlaceholder"))
            .DropSources("listbox1")
            .ConnectWith("listbox1")
            .Toolbar(toolbar =>
            {
                toolbar.Position(Kendo.Mvc.UI.Fluent.ListBoxToolbarPosition.Right);
                toolbar.Tools(tools => tools
                    .MoveUp()
                    .MoveDown()
                );
            })
            .Events(e => e
                .Remove("lb2_remove")
                .Reorder("lb2_reorder")
                .Add("lb2_add")
                .DragStart("lb_dragstart")
                .DragEnd("lb2_dragend"))
     
            .BindTo(new List<dynamic>())
            )
        </div>
    </div>
    <script id="spec-item-template" type="text/x-kendo-template">
        <h3 data-spec-id="#:data.SpecificationID#"><span class="dirty-mark # if (data.dirty) { #dirty-item# } #"></span>#:data.Name# rev. #:data.Revision#</h3>
        <p class="spec-material">#:data.Material#</p>
    </script>
    <script>
        function specItemDndPlaceholder(draggedItem) {
            return draggedItem
                .clone()
                .addClass("custom-placeholder")
                .removeClass("k-ghost");
        }
        jQuery(function () {
            $("#listbox1").closest(".k-listbox").find("[data-command='transferTo']"  ).click(lb1_click_TransferTo);
            $("#listbox1").closest(".k-listbox").find("[data-command='transferFrom']").click(lb1_click_TransferFrom);
        });
    </script>

     

    Prior to the list boxes are the functions for the custom click handling. For now I just want to log the event be handled.  The messages don't appear in the developer tools console log window :( so I presume the widget is somehow causing additional handlers from being invoked (maybe a preventDefault going down within the widget internals?).

    function lb1_click_TransferTo(e) {
        console.log(arguments.callee.name);
    }
     
    function lb1_click_TransferFrom(e) {
        console.log(arguments.callee.name);
    }

     

     

  5. RichardAD
    RichardAD avatar
    133 posts
    Member since:
    Feb 2012

    Posted 25 Sep Link to this post

    Hi Stefan:

    I took a more aggressive approach and it is one that will have to be re-evaluated in later releases due to an override.

    Install an _executeCommand override on the listboxes in order to perform my post command tweaks.

    $("#listbox1").data("kendoListBox")._executeCommand = function (commandName) {
        this.constructor.prototype._executeCommand.call(this, commandName);
        connected_listbox_update();
    }

     

    This listbox update tweaks each one to ensure that data sources align with the rendered element item positions, and tags items that are different than initially seen in data source.

    function connected_listbox_update() {
         console.log(arguments.callee.name);
     
         var items, ds, dataItem, i, pos, item;
     
         items = labReport_specLb1.items();
         ds = labReport_specLb1.dataSource;
         for (i = 0; i < items.length; i++) {
             item = items[i];
             dataItem = labReport_specLb1.dataItem(item);
             pos = ds.indexOf(dataItem);
             if (i != pos) { // element position different than in data source, update data source to match
                 ds.remove(dataItem);
                 ds.insert(i, dataItem);
             }
             if (dataItem.SeqNew != null) {  // left list box contains pool items for the webapp and SeqNew is not tracked position wise
                 dataItem.SeqNew = null;
                 dataItem.dirty = dataItem.Seq != dataItem.SeqNew;
             }
         }
     
         items = labReport_specLb2.items();
         ds = labReport_specLb2.dataSource;
         for (i = 0; i < items.length; i++) {
             item = items[i];
             dataItem = labReport_specLb2.dataItem(item);
             pos = ds.indexOf(dataItem);
             if (i != pos) { // element position different than in data source, update data source to match
                 ds.remove(dataItem);
                 ds.insert(i, dataItem);
             }
             if (dataItem.SeqNew != i + 1) { // a webapp pool item moved in right listbox has its position tracked
                 dataItem.SeqNew = i + 1;
                 dataItem.dirty = dataItem.Seq != dataItem.SeqNew;
             }
         }
         labReport_specLb1.refresh();  // data sources had to be updated so the items are in correct order when refresh invoked.
         labReport_specLb2.refresh();
     
         if (items.length < LB2_MAX_ITEM_COUNT) { // turn off transferTo if max Items in right hand list box.
             $('.dual-listbox1 [data-command="transferTo"]').css('visibility', 'visible');
         } else {
             $('.dual-listbox1 [data-command="transferTo"]').css('visibility', 'hidden');
         }
    }

     

    dragend event handler for each of the listboxes also performs the custom connected_listbox_update

    function lb_dragend(e) {
        console.log(arguments.callee.name);
        spec_listbox_update();
    }

     

    As mentioned earlier in the thread, the drop action into right listbox is also limited via the listbox 1 drop event handler

    function lb1_drop(e) {
        console.log(arguments.callee.name);
     
        if ($("#listbox2").data('kendoListBox').dataSource.data().length >= LB2_MAX_ITEM_COUNT) {
            e.preventDefault();
        }
    }

     

    Things seem to be working as the webapp needs them to.... finally!

  6. Stefan
    Admin
    Stefan avatar
    1137 posts

    Posted 27 Sep Link to this post

    Hello, Richard,

    Thank you for sharing the implementation with the Kendo UI community, it is highly appreciated.

    As this helps in scenarios where the Kendo UI ListBox currently shows inconsistency, I updated your Telerik points for sharing it.

    Regards,
    Stefan
    Progress Telerik
    Try our brand new, jQuery-free Angular 2 components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
Back to Top