This is a migrated thread and some comments may be shown as answers.

Suggestions for connected consistency and improvement

5 Answers 100 Views
ListBox
This is a migrated thread and some comments may be shown as answers.
Richard
Top achievements
Rank 1
Richard asked on 21 Sep 2017, 06:23 AM

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,

5 Answers, 1 is accepted

Sort by
0
Richard
Top achievements
Rank 1
answered on 21 Sep 2017, 02:19 PM

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.

0
Stefan
Telerik team
answered on 25 Sep 2017, 06:17 AM
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.
0
Richard
Top achievements
Rank 1
answered on 25 Sep 2017, 03:40 PM

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);
}

 

 

0
Richard
Top achievements
Rank 1
answered on 25 Sep 2017, 08:07 PM

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!

0
Stefan
Telerik team
answered on 27 Sep 2017, 07:42 AM
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.
Tags
ListBox
Asked by
Richard
Top achievements
Rank 1
Answers by
Richard
Top achievements
Rank 1
Stefan
Telerik team
Share this question
or