KendoUI: Grid row details


Many times we have much more information to show in a Grid than what is physically possible. For those cases KendoUI provides a mechanism through an option in Grid initialization called detailTemplate.

In this post I will show you how to use, the standard way but also how to show that same detailed information not inside the grid but in a tooltip displayed when mouse is over a row.

KendoUI: detailTemplate

Once again KendoUI engineers have  read our minds and thought about our needs, implementing a feature for showing detailed information.

The idea behind is defining a template  that will be displayed inside the Grid (as an expanded row of the original one) including the details (or -actually- whatever you want to include).

This is our DataSource:

var dataSource = new kendo.data.DataSource({
    data    : [
        { symbol: "AAPL", company: "Apple Inc.", price: 509.5892 },
        { symbol: "AMZN", company: "Amazon.com Inc.", price: 245.18  },
        { symbol: "IBM", company: "International Business Machines Corp.", price: 189.83 },
        { symbol: "CSCO", company: "Cisco Systems, Inc", price: 19.4494 },
        { symbol: "MSFT", company: "Microsoft Corp.", price: 26.55 },
        { symbol: "INTC", company: "Intel Corp.", price: 20.23 },
        { symbol: "QCOM", company: "QUALCOMM Inc.", price: 60.6382 },
        { symbol: "ORCL", company: "Oracle Corp.", price: 33.02 },
        { symbol: "HPQ", company: "Hewlett-Packard Company", price: 13.68 },
        { symbol: "CRM", company: "salesforce.com, Inc.", price: 164.18 }
    ],
    pageSize: 5
});

and we want to show in the Grid just the symbol and price (but not company).

This is our Grid initialization:

var grid = $("#stocks_tbl").kendoGrid({
    dataSource    : dataSource,
    columns       : [
        { field: "symbol", title: "Symbol", width: 100 },
        { field: "price", title: "Price", width: 100, format: "{0:c2}", attributes: {style: "text-align:right;"} }
    ],
    pageable      : true
}).data("kendoGrid");

That looks like this:
Capture1
But, I’d like to show the details, in this example, the company name. So, what I do is define a template that displays this additional information.

This template is something like:

<script id="template" type="text/x-kendo-template">
<div>Company: <span style="font-weight: bold;">#= company #</span></div>
</script>

And when the row is expanded it looks like:
Capture2

Display details in a tooltip

That’s ok but I have to click to open the details and in some case I just want to display it as a tooltip. In those case what I’m going to do is something similar (define a template) but instead of displaying it inside the Grid table, I’m going to show it as a tooltip.

The very first thing is defining an HTML block on which we I’m going to display the detailed information.

<div class="k-tooltip ob-hidden" id="tooltip" data-template="template" data-bind="source: this"></div>

Where ob-hidden is defined as:

.ob-hidden {
    display: none;
}

Used for hiding the tooltip.

Then in the JavaScript I’m going to display this tooltip when the mouse is over a grid row and hide it when it exits.

    // Show tooltip when mouse is over a row
    $(grid.element).on("mouseover", "tr", function() {
        $("#tooltip").removeClass("ob-hidden");
    });
    // Hide tooltip when mouse exits a row
    $(grid.element).on("mouseout", "tr", function() {
        $("#tooltip").addClass("ob-hidden");
    });

In addition, when I’m about to display the tooltip, I have to bind the row data to the template and that’s why I have include data-bind=”source: this” in the tooltip definition:

$(grid.element).on("mouseover", "tr", function () {
    // Get the item from the grid
    var item = grid.dataItem(this);
    if (item) {
        // Bind it to the tooltip
        kendo.bind("#tooltip", item);
        // Make tooltip visible
        $("#tooltip").removeClass("ob-hidden");
    }
});

Right now, the tooltip looks like:

Where the tooltip is displayed always in the same position and far from where the cursor is. So the final question would be displaying the tooltip near the cursor.

In this case and for a more precise tooltip position, what I’m going to do is replace the mouseover by a mousemove, this means much more work but the tooltip will follow very precisely the mouse.

The new code for display the tooltip will be:

// Show tooltip when mouse moves over a row
$(grid.element).on("mousemove", "tr", function (e) {
    var item = { item: grid.dataItem(this) };
    if (item.item) {
        kendo.bind("#tooltip", item);
        $("#tooltip")
                .css({top: e.pageY + 5, left: e.pageX + 5})
                .removeClass("ob-hidden");
    }
});

Where I get the position of the cursor from the event argument of the mousemove handler.

And this looks like:

Advertisements

KendoUI: readonly rows (how to and command template)


A couple days ago Piyush, a frequent reader and commenter on this blog, asked me about how to make a row in a KendoUI grid read-only.

What he tried was handling edit event and if the row was read-only then he exited the edit mode but this introduced and undesired effect: a flickering (due to entering and exiting edit popup/inline mode).

KendoUI: read-only rows

Flickering

What Piyush originally asked me was how to avoid flickering but this is actually not possible since by the time edit event is fired, the row is already in edit mode.

You can easily demonstrate this introducing and alert(“editing”); in the code and you see that the popup window for editing fields is active while alert window is waiting for ok so, you will always have the flickering.

Lets have the following DataSource definition:

var dataSource = new kendo.data.DataSource({
    data    : [
        { symbol: "AAPL", company: "Apple Inc.", readonly: true },
        { symbol: "AMZN", company: "Amazon.com Inc.", readonly: true },
        { symbol: "IBM", company: "International Business Machines Corp." },
        { symbol: "CSCO", company: "Amazon.com Inc", readonly: true },
        { symbol: "MSFT", company: "Microsoft Corp.", readonly:false },
        { symbol: "INTC", company: "Intel Corp." },
        { symbol: "QCOM", company: "QUALCOMM Inc." },
        { symbol: "ORCL", company: "Oracle Corp." },
        { symbol: "HPQ", company: "Hewlett-Packard Company" },
        { symbol: "CRM", company: "salesforce.com, Inc." }
    ],
    pageSize: 5
});

And the grid defined as:

var grid = $("#stocks_tbl").kendoGrid({
    dataSource: dataSource,
    columns   : [
        { command: "edit", width: 100 },
        { field: "symbol", title: "Symbol", width: 100 },
        { field: "company", title: "Company", width: 300 }
    ],
    editable  : "popup",
    edit      : function () {
        alert("editing");
        grid.closeCell();
    },
    pageable  : true
}).data("kendoGrid");

If you run this you will see:

Capture1

So, there is nothing that we can do for avoiding the flickering but there are some things that I can do for getting the desired result.

Template based solution

This solution is based on defining a template that creates an Edit button for each row but controlled by readonly field.

The template would be something like:

<script id="edit-template" type="text/x-kendo-template">
    # if (!data.readonly) { #
    <a class="k-button k-button-icontext k-grid-edit" href="\#"><span class="k-icon k-edit"></span>Edit</a>
    # } #
</script>

and the new JavaScript code:

var editTemplate = kendo.template($("#edit-template").html());
var grid = $("#stocks_tbl").kendoGrid({
    dataSource: dataSource,
    columns   : [
        { field: "readonly", title: "", width: 100, template: editTemplate },
        { field: "symbol", title: "Symbol", width: 100 },
        { field: "company", title: "Company", width: 300 }
    ],
    editable  : "popup",
    pageable  : true
}).data("kendoGrid");

And this looks like:

Capture2

Done! … Actually almost, if I click on Edit button, I get:

Capture3

Where the field readonly is editable.

I might try to define a Schema for the DataSource and define readonly as not editable. But this does not prevent the field from being rendered.

Capture4

What else may I try?

  1. Remove the title of the field by defining it to a white-space using title: “ “ (note that there is a blank between both “).
  2. Define an editor function that actually does nothing.

What I have now is:

var grid = $("#stocks_tbl").kendoGrid({
    dataSource: dataSource,
    columns   : [
        { field: "readonly", title: " ", width: 100, template: editTemplate, editor: function() {} },
        { field: "symbol", title: "Symbol", width: 100 },
        { field: "company", title: "Company", width: 300 }
    ],
    editable  : "popup",
    pageable  : true
}).data("kendoGrid");

And when I edit the field I finally get:

Screen Shot 2012-12-26 at 00.43.28

KendoUI: TreeView tricks


One of the things that I like most about Open-Source is that you learn about what others did and how they have solved problems. Here I’m presenting some tricks learnt about KendoUI TreeView.

KendoUI: TreeView tricks

Don´t use parent as dataTextField

Recently, I saw somebody using a feature in KendoUI TreeView that I was pretty sure that it was a mistake. They wrote:

$("#treeview").kendoTreeview({
    dataSource: parent,
    expanded: true,
    dataBound: ondata,
    dataTextField: ["Parent", "Child"]
}).data("kendoTreeView");

And according with the documentation of kendoTreeView.dataTextField it says:

TreeView dataTextField documentation
TreeView dataTextField documentation

Um! It has to a be a string!. Is that array a mistake…? But this code has been commented by one of KendoUI engineers and he didn’t raised any question on it… Let me check what does it mean…

The very first experiment is writing a piece of code that defines a HierarchicalDataSource where all nodes are labeled as parent and not using the array.

    var content = [
        {
            parent:"parent",
            items :[
                { parent:"Child1" },
                { parent:"Child2" },
                { parent:"Child3" },
                { parent:"Child4" }
            ]
        }
    ];

    var tree = $("#tree").kendoTreeView({
        dataSource   :content,
        dataTextField:"parent"
    }).data("kendoTreeView");
    tree.expand(".k-item");

And this is what I get:

Don't user parent as dataTextField
Don’t user parent as dataTextField

Oops! Something is wrong. Seems that parent is a function. Ok! Don’t use parent and use instad Parent (P uppercase).

    var content = [
        {
            Parent:"Parent",
            items :[
                { Parent:"Child1" },
                { Parent:"Child2" },
                { Parent:"Child3" },
                { Parent:"Child4" }
            ]
        }
    ];

    var tree = $("#tree").kendoTreeView({
        dataSource   :content,
        dataTextField:"Parent"
    }).data("kendoTreeView");
    tree.expand(".k-item");

And now it looks like:

Use Parent as dataTextFiled
Use Parent as dataTextFiled

TreeView dataTextField as an array

Now, lets change the definition from a string to an array and in the DataSource use Child as dataTextField.

    var content = [
        {
            Parent:"Parent",
            items :[
                { Child:"Child1" },
                { Child:"Child2" },
                { Child:"Child3" },
                { Child:"Child4" }
            ]
        }
    ];

    var tree = $("#tree").kendoTreeView({
        dataSource   :content,
        dataTextField:[ "Parent", "Child" ]
    }).data("kendoTreeView");
    tree.expand(".k-item");

NOTE: I might use child instead of Child, here there is no problem using lowercase.

Yeah! It works! So seems that it uses the first value of the array (Parent) for the root level and the second value for the children.

TreeView dataTextField with more than two levels

But what happens if children also have children? Should they be Parent, Child or something else?

The answer is they have to have a new value in the array.

  1. It is not a value for nodes that have children and one value for nodes that do not.
  2. It is not that the last value is repeated as many levels as needed.

So the code now looks like:

    var content = [
        {
            Parent:"Parent",
            items :[
                { Child:"Child1" },
                { Child:"Child2" },
                {
                    Child:"Child3",
                    items: [
                        { GrandChild:"GrandChild1" },
                        { GrandChild:"GrandChild2" },
                        { GrandChild:"GrandChild3" }
                    ]
                },
                { Child:"Child4" }
            ]
        }
    ];

    var tree = $("#tree").kendoTreeView({
        dataSource   :content,
        dataTextField:[ "Parent", "Child", "GrandChild"]
    }).data("kendoTreeView");
    tree.expand(".k-item");

Conclusions

Yes, you can use an array and the name of the field used for text is the value on the array corresponding to the level of the node in the tree.