by Andrzej Kowal

Datatables.net – ajax pagination on scroll

Listing and paginating data in a web application can be implemented in many different ways. One technique I really like is automatic ajax pagination on window scroll. It provides great user experience and a clean UI without any paging controls. It requires loading just enough items to force the browser to show the vertical scrollbar. When user decides to scroll the window – more items are loaded with ajax as soon as the scroll reaches around 80% of the window height. This approach is widely used by many websites: Facebook, Twitter, Pinterest, LinkedIn and many more. However when it comes to tabular data plain old paging is still implemented quite often, Time for a change.

Recently, when working on insights feature in Ping.it, I had to implement “expand on scroll” behavior in an HTML table driven by jQuery datatables plugin. It is a very handy utility, which converts any HTML table into a powerful client-side enabled grid. It has two basic operation modes: client and server side. To my surprise in the server mode the grid cannot be easily extended with new rows loaded from the server. It supports only the traditional paging. When I tried to force the grid to load more rows, it ended up refreshing the whole grid. After a bit of research and experiments I came up with a solution. In the scroll event handler more data is loaded from the server with ajax:

[javascript]
var scrollForMoreData = function() {
$.ajax({
url: ‘/LoadMoreData’,
data: { … },
success: ajaxSuccess
});
};
[/javascript]

The server JSON response contains formatted HTML table rows:

[javascript]
var result = {
"content": "<tr>
<td>Mary Jane</td>
<td>26</td>
</tr>
<tr>
<td>Peter Parker</td>
<td>28</td>
</tr>…",
"hasMore": true
};
[/javascript]

On ajax success the <tr>s are simply appended to the end of the existing HTML table:

[javascript]
var ajaxSuccess = function (result) {
$(‘#my-table tbody’).append(result.content)
};
[/javascript]

The datatables sorting and filtering still can be used – in such case full grid refresh is the correct behavior (since sorting or filtering can fetch data currently not visible in the grid).

There is one important thing to consider – when loading more data with ajax it is essential to copy the datatable’s auto-generated ajax request data in order to send correct sorting and filtering parameters to the server:

[javascript]
$(‘#my-table’)
.on(‘xhr.dt’, function () {
var ajaxData = $(this).api().ajax.params();
$(this).data(‘custom-ajax-parameters’, ajaxData);
});
[/javascript]

Note that xhr event must be bound with the .dt namespace. Later when user scrolls the page I can retrieve the ajaxData object stored within the #my-table DOM object. And send this object as input to my “scroll” ajax request. This ensures that the server will actually return correctly sorted and filtered result. Since we don’t use datatables paging feature we need to handle it ourselves e.g. by adding additional parameter to “scroll” ajax request – the amount of already displayed rows (or page number, depending on your paging implementation). The adjusted scrollForMoreData function looks like this:

[javascript highlight=”2,3″]
var scrollForMoreData = function() {
var ajaxData = $(‘#my-table’).data(‘custom-ajax-parameters’);
ajaxData.skipRows = $(‘#my-table tbody tr’).length;
$.ajax({
url: ‘/LoadMoreData’,
data: ajaxData,
success: ajaxSuccess
});
};
[/javascript]

Happy data-listing!

by Stian

Check if element is hidden with jQuery

 

Expert solutionJust another quick jQuery help post; how can I check if an element is visible or hidden using jQuery?

To check this for a single element, you can write:

[csharp]// Checks for display:[none|block], ignores visible:[true|false]
$(element).is(“:visible”)[/csharp]

You can also have a selector which gives you all hidden elements:

[csharp]// Matches all elements that are hidden
$(‘element:hidden’)[/csharp]

or of course all visible elements:

[csharp]// Matches all elements that are visible.
$(‘element:visible’)[/csharp]

 

by Njål

String replaceAll in Javascript

imageIn Javascript the string.replace(“target”, ”replacement”) function only replaces the first occurrence of the target…

Here’s how you can add a replaceAll() metod to all Strings (similar to Extension Methods in C#).

String.prototype.replaceAll = function (orig, replacement) {
    return this.split(orig).join(replacement);
}

 

You can then write (anywhere in your code):

"a a b c".replaceAll("a","X"); //==> "X X b c"
by Andreas

JQuery / KnockoutJS – define default button within a DIV

We’ve previously covered several aspects of using Default buttons in ASP.NET and Silverlight (the latter is in Norwegian, but should be semi-readable through an automagically translated version – sponsored by Google Translate).

This time I thought I’d show you how I did this without the need of server side controls. Why? First of all, adding server side controls adds unnecessary overhead by increasing the view state. Second, in my complex client side user interface I’ve stuck to clean HTML, javascript / jQuery and KnockoutJS. I didn’t want to clog it up, and since the solution was as simple as it turned out to be I am glad I stuck to my guns.

I had a look at what ASP.NET generates when a default button is defined for a Panel control. As you may or may not know, the server side Panel control is rendered as a DIV when the page is returned to the client browser. And as expected, Javascript is used to invoke the defined default button click event so I thought I’d just steal this javascript function and call it myself from within my pure HTML DIV element. Not surprisingly, this worked and why shouldn’t it? I just did the exact same thing as the .NET framework does, and there’s not much voodoo or black magic in that.

So, I wanted to invoke the click event for a “Save Changes” button displayed within a Fancybox dialog, simply by clicking ENTER. First, I added the javascript function that I stole from the .NET server control rendering engine (I am pretty sure that’s not what it’s called):

function WebForm_FireDefaultButton(event, target) {
    if (event.keyCode == 13) {
        // this variable has to be added
        var __nonMSDOMBrowser =
            (window.navigator.appName.toLowerCase().indexOf('explorer') == -1);

        var src = event.srcElement || event.target;
        if (!src || (src.tagName.toLowerCase() != "textarea")) {
            var defaultButton;
            if (__nonMSDOMBrowser) {
                defaultButton = document.getElementById(target);
            }
            else {
                defaultButton = document.all[target];
            }
            if (defaultButton && typeof (defaultButton.click) != "undefined") {
                defaultButton.click();
                event.cancelBubble = true;
                if (event.stopPropagation) event.stopPropagation();
                return false;
            }
        }
    }
    return true;
}

Note that I had to add the __nonMSDOMBrowser variable. This is usually added as a global variable on the page, but I saw no reason to do so because none of my other functions care about it.

Then, within my dialog box DIV element I make sure the onkeypress calls the new function with the ID of the button as a parameter:

<div id="addContactDialogBody" class="modal-body" onkeypress="javascript:return WebForm_FireDefaultButton(event, 'btnSaveContact')">
      <!-- This is my user input form, containing a couple of input text fields -->
</div>
<div id="addContactDialogFooter" class="modal-footer">
    <!-- dont be confused, the data-bind attribute is KnockoutJS related and has nothing to do with this example -->
    <input id="btnSaveContact" type="button" value="Save" data-bind="click: addNewContact, visible: true" />
    <input id="btnCancelContact" type="button" value="Cancel" onclick="javascript: $.fancybox.close();" />
</div>

 

When the focus is on an element within the first DIV (removed for clarty in the example code), pressing the enter Key will invoke the click event (or in my case, a KnockoutJS click binding) for the button with id btnSaveContact.

by Andreas

KnockoutJS – how to force binding refresh / re-evaluation

Observable bindings in KnockoutJS get re-evaluated when the focus is changed in a form, for instance when you click a button or use TAB to move from one input field to the next. When KnockoutJS is used in combination with an ASP.NET panel control where a DefaultButton is defined, re-evaluation of observable bindings is not performed when the user clicks ENTER to trigger the button click event. As a result of this, a value entered in an input field that is bound to the view model will not be registered if the user doesn’t navigate (move focus) away from the field first. 

A work-around for this is to use jQuery (or pure javascript) and programmatically set the focus to a different control, for instance the button itself when the event is fired.

self.addNewTimeRegEntry = function () {
    // set focus to button control to force re-evaluation
    $("#<%= btnAddRegEntry.ClientID %>").focus();

    ... // continue as normal

The button click event is bound to the addNewTimeRegEntry function above (through the KnockoutJS framework), and by moving the focus all variables will be refreshed before further processing is done.

by Andreas

Disable drag and drop in elFinder 2.x

The drag and drop functionality in elFinder 2.x can at be a bit of a problem. You might experience a bit of a lag, and sometimes there is a delay in the button-up event which causes you to drag folders and files around unintentionally.

Currently there is no property allowing drag and drop to be disabled, so you’ll have to make some minor changes in the .js files. My original solution to this was commenting out some delegates in tree.js and cwd.js but I will be the first to admit that this was a dirty hack with possible side effects – not pretty at all!

So here’s the real solution:

If you want to disable draggable animation and functionality, locate the Draggable options in elFinder.js (around line 465). Remove the appendTo parameter from ‘body’ to ‘’ and draggable plugin will be disabled completely.

If you want to keep the animated dragging functionality, but prevent the user from actually doing any move operations, locate the Droppable options in elFinder.js (around line 502), and change the accept parameter. Leave the types of droppable you want to support (file, folder or nav dir) and just remove the others. I didn’t want anything to be droppable to I changed it from this:

    /**
     * Base droppable options
     *
     * @type Object
     **/
    this.droppable = {
            tolerance  : 'pointer',
            accept     : '.elfinder-cwd-file-wrapper,.elfinder-navbar-dir,.elfinder-cwd-file',
            hoverClass : this.res('class', 'adroppable'),

to this:

    /**
     * Base droppable options
     *
     * @type Object
     **/
    this.droppable = {
            tolerance  : 'pointer',
            accept: '',
            hoverClass : this.res('class', 'adroppable'),
by Stian

Redirect page with jQuery / JavaScript

There are several different ways to do a clean redirect from one URL to another with some simple jQuery / javascript. I’ll start out here showing what in my opinion is the best way to simulate a HTTP redirect and then show a few other options that you have.

Pure javascript
// similar behavior as an HTTP redirect
window.location.replace("http://blog.degree.no");

This method is in my opinion the best, because the window.location.replace() is redirecting the page without putting the original page in the browser history.

// similar behavior as clicking on a link
window.location.href = "http://blog.degree.no";

This method works fine as well, but it will throw the user in to a back button loop if they would want to go back in history.

jQuery

My impression is that developers sometimes search for the “jQuery way” to do things and maybe you found this blog post searching for “redirect page with jQuery”? While there is a way to do this in jQuery, there is really no reason why you would want to do this as it could not be easier to with normal javascript. But since you searched for it, here it is.

$(location).attr("href",http://blog.degree.no);

 

image

by Andreas

Microsoft JScript error: jQuery.tmpl is too old

If you ever encounter the following error message when using string-based templates (or a component that uses it, like simpleGrid) for KnockoutJS:

Microsoft JScript runtime error: Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.

you’d expect to just download the latest jQuery.tmpl plugin and get on with your life. This is not the case however, because from KnockoutJS version 2.0.0 jquery.templ are no longer supported. You’ve got two options:

1. Avoid using string-based templates (use native control flow bindings instead)

2. Download this version of jquery.tmpl: jquery-tmpl 1.0.0pre from github. And make sure you add the script reference before the knockout library:

<script src="../Scripts/jquery-ui-1.8.23.js"></script>
<script src="../Scripts/jquery.tmpl.js"></script>
<script src="../Scripts/knockout-2.1.0.js"></script>
<script src="../Scripts/knockout.simpleGrid.js"></script>
by Andreas

Jquery + JSON: IE8/IE9 treats response as downloadable file

We’ve recently been working with the pretty brilliant web based elFinder file manager. This component has a truckload of features, but for a very specific function (uploading files through the web browser) we came across a tricky problem. Upon receiving a JSON response from an HTTP handler (.asxh) Internet Explorer 8 triggered the following warning:

To Help Protect Your Security, Internet Explorer has blocked this website from displaying content with security certificate errors. Click here for options…

image

After opting for “Download file”, subsequent responses just triggered the “Save file” dialog box. The contents of this file was the perfectly fine JSON response, but this was not understood by Internet Explorer 8. In Internet Explorer 9 the warning was supressed and the response seemed to simply be ignored, leaving our jQuery component waiting for its JSON reply in vain. Chrome, Opera and Firefox had no issues, and processed the response as pure JSON.

After a fair bit of research trying to find the bug both server and client side, it turned out that Internet Explorer didn’t like that the Content-Type in the response header was "application/json”. Setting the Content-Type to “text/html” specifically for IE in the .asxh handler before returning the JSON response did the trick. It now works as expected across all browsers.

A simple method checks the browser and updates the content type if applicable (note: this is just an extract):

        private static string SetContentTypeBasedOnBrowserAndCommand(HttpContext context)
        {
            var contentType = "application/json"; // default
            // if browser is IE
            if (DegreeCore.Util.BrowserUtil.IsIE(context.Request.Browser))
            {
                // override response
                 contentType = "text/html";
            }

            return contentType;

        }

 

The non-reversible hair loss from occational problems like this has yet again been put on hold – for now.

Tags: ,