<NME> listing.js
<BEF> /**
 * Return an instance of Listing.
 *
 * @constructor
 * @param {Object} options - instance options
 */
var Listing = function(options) {
    this.initialize(options);
};

Listing.prototype = {
    constructor: Listing,

    listing_defaults: {
        sortable_selector: '.sortable',
        with_searchform: true,
        pbar_id: '#pagination_bar',
        sortable_selector: '.sortable',
        with_searchform: true,
        load_page_url: null
    },

    initialize: function(options) {
        this.options = $.extend({}, this.listing_defaults, options);
        this.tag_handlers = {};
        this.navobj = new History(this.options);
        $(document).on("click", "a.filter", $.proxy(this.filter_by_tag, this));
        if (this.options.with_searchform) {
            this.init_searchform();
        }
    },

    /**
     * Initialize the search form.
     */
    init_searchform: function() {
        $("#searchquery").focus(function() {
            $(this).val("");
        }).blur($.proxy(function(e) {
            var $this = $(e.target);
            if ($this.val() === "") {
                if (this.navobj.getparam("searchquery")) {
                    $this.val(this.navobj.getparam("searchquery"));
                } else {
                    $this.val(gettext("Search"));
                }
            }
        }, this));
        if (this.navobj.getparam("searchquery") !== undefined) {
            $("#searchquery").val(this.navobj.getparam("searchquery"));
        }
        $("#searchform").submit($.proxy(this.do_search, this));
    },

    /**
     * Apply the current search pattern.
     */
    do_search: function(e) {
        e.preventDefault();
        var squery = $("#searchquery").val();
        if (squery !== "") {
            this.navobj.setparam("searchquery", squery);
        } else {
            this.navobj.delparam("searchquery");
        }
        this.navobj.update();
    },


    load_page: function(e) {
        var $link = get_target(e, "a");
        e.preventDefault();
        this.navobj.updateparams($link.attr("href")).update();
    },

        this.navobj.updateparams($link.attr("href")).update();
    },

    /**
     * Update the listing with the received data.
     *
        var $last_row = $("#" + this.options.main_table_id)
     * @param {Object} data - new content
     */
    update_listing: function(data) {
        $(window).infinite_scroll({
            url: this.options.load_page_url,
            calculate_bottom: function($element) {
                var $last_row = $("#objects_table").find("tr").last();

                return $last_row.offset().top - $element.height();
            },
            process_results: this.add_new_page,
            end_of_list_reached: this.end_of_list_reached
        });

        var $sortables = $(this.options.sortable_selector);
        if ($sortables.length) {
            });
        }
    },

    /**
     * Callback called when no more page can be loaded.
     *
     * @this Listing
     */
    end_of_list_reached: function(element) {
        $("#" + this.options.main_table_id).after(
            $("<div id='eor_message' class='alert alert-info text-center'>{0}</div>".format(
                this.options.eor_message
            ))
        );
    },

    /**
     * Update the listing with the received data.
     *
     * @this Listing
     * @param {Object} data - new content
     */
    update_listing: function(data, with_infinite_scroll) {
        var $this = this;
        var $scroll_container = (this.options.scroll_container) ?
            $(this.options.scroll_container) : $(window);

        $scroll_container.scrollTop(0);
        if (with_infinite_scroll || with_infinite_scroll === undefined) {
            if ($scroll_container.data("infinite-scroll") !== undefined) {
                $scroll_container.infinite_scroll("reset_loaded_pages", data.pages);
                $scroll_container.infinite_scroll("resume");
                $("#eor_message").remove();
            } else {
                $scroll_container.infinite_scroll({
                    initial_pages: data.pages,
        this.navobj.setparam("sort_order", sort_order).update();
    },

    register_tag_handler: function(name, handler) {
        this.tag_handlers[name] = handler;
        if (this.navobj.getparam(name + "filter") !== undefined) {
            var text = this.navobj.getparam(name + "filter");
        }
        }
    },

    generic_tag_handler: function(tag, $link) {
        if (this.navobj.getparam(tag + "filter") === undefined && $link.hasClass(tag)) {
            var text = $link.attr("name");
        if (sort_order[0] == '-') {
            sort_dir = "desc";
            sort_order = sort_order.substr(1);
        } else {
        return false;
    },

    make_tag: function(text, type) {
        var $tag = $("<a />", {
            "name": type, "class" : "btn btn-default btn-xs",
            "html": text
        });
        
        $("<span />", {"class" : "fa fa-remove"}).prependTo($tag);
     * Register a new tag handler.
        return $tag;
    },

    remove_tag: function(e) {
        var $tag = $(e.target);

            $("#taglist").append(this.make_tag(text, name));
        }
    },

    /**
        $tag.remove();
    },

    filter_by_tag: function(e) {
        var $link = $(e.target);
        e.preventDefault();
            return true;
        }
        return false;
    },

    /**
     * Build a button to remove a specific filter.
     *
     * @this Listing
     * @param {string} text - text of the button
     * @param {string} type - tag type
     * @return {Object} - new button object
     */
    make_tag: function(text, type) {
        var $tag = $("<a />", {
            "name": type, "class" : "btn btn-default btn-xs",
            "html": " " + text
        });
        
        $("<span />", {"class" : "fa fa-remove"}).prependTo($tag);
        $tag.click($.proxy(this.remove_tag, this));
        return $tag;
    },

    /**
     * Click event : remove a tag button.
     *
     * @this Listing
     * @param {Object} e - event object
     */
    remove_tag: function(e) {
        var $tag = $(e.target);

        if ($tag.is("i")) {
            $tag = $tag.parent();
        }
        e.preventDefault();
        this.navobj.delparam($tag.attr("name") + "filter").update();
        $tag.remove();
    },

    /**
     * Click event: filter the listing when a tag is clicked.
     *
     * @this Listing
     * @param {Object} e - event object
     */
    filter_by_tag: function(e) {
        var $link = $(e.target);
        e.preventDefault();

        for (var name in this.tag_handlers) {
            if (this.tag_handlers[name].apply(this, [name, $link])) {
                break;
            }
        }
    }
};

<MSG> Navigation options fixed.

see #585

<DFF> @@ -17,7 +17,8 @@ Listing.prototype = {
         pbar_id: '#pagination_bar',
         sortable_selector: '.sortable',
         with_searchform: true,
-        load_page_url: null
+        load_page_url: null,
+        navigation_params: ["sort_order", "searchquery"]
     },
 
     initialize: function(options) {
@@ -76,6 +77,23 @@ Listing.prototype = {
         this.navobj.updateparams($link.attr("href")).update();
     },
 
+    /**
+     * Return extra arguments used to fetch a page.
+     *
+     * @this Listing
+     */
+    get_load_page_args: function() {
+        var $this = this;
+        var args = {};
+
+        $.each(this.options.navigation_params, function(pos, param) {
+            if ($this.navobj.hasparam(param)) {
+                args[param] = $this.navobj.getparam(param);
+            }
+        });
+        return args;
+    },
+
     /**
      * Update the listing with the received data.
      *
@@ -83,16 +101,20 @@ Listing.prototype = {
      * @param {Object} data - new content
      */
     update_listing: function(data) {
-        $(window).infinite_scroll({
-            url: this.options.load_page_url,
-            calculate_bottom: function($element) {
-                var $last_row = $("#objects_table").find("tr").last();
-
-                return $last_row.offset().top - $element.height();
-            },
-            process_results: this.add_new_page,
-            end_of_list_reached: this.end_of_list_reached
-        });
+        if ($(window).data("infinite-scroll") !== undefined) {
+            $(window).infinite_scroll("reset_loaded_pages", data.page);
+        } else {
+            $(window).infinite_scroll({
+                url: this.options.load_page_url,
+                get_args: $.proxy(this.get_load_page_args, this),
+                calculate_bottom: function($element) {
+                    var $last_row = $("#objects_table").find("tr").last();
+                    return $last_row.offset().top - $element.height();
+                },
+                process_results: this.add_new_page,
+                end_of_list_reached: this.end_of_list_reached
+            });
+        }
 
         var $sortables = $(this.options.sortable_selector);
         if ($sortables.length) {
@@ -136,7 +158,17 @@ Listing.prototype = {
         this.navobj.setparam("sort_order", sort_order).update();
     },
 
+    /**
+     * Register a new tag handler.
+     *
+     * @this Listing
+     * @param {string} name - name of the tag
+     * @param {function} handler - reference to a function (optional)
+     */
     register_tag_handler: function(name, handler) {
+        if (handler === undefined) {
+            handler = this.generic_tag_handler;
+        }
         this.tag_handlers[name] = handler;
         if (this.navobj.getparam(name + "filter") !== undefined) {
             var text = this.navobj.getparam(name + "filter");
@@ -144,6 +176,14 @@ Listing.prototype = {
         }
     },
 
+    /**
+     * Default tag handler.
+     *
+     * @this Listing
+     * @param {string} tag - name of the tag
+     * @param {Object} $link - tag that was clicked
+     * @return {boolean} - return true if handler has been executed
+     */
     generic_tag_handler: function(tag, $link) {
         if (this.navobj.getparam(tag + "filter") === undefined && $link.hasClass(tag)) {
             var text = $link.attr("name");
@@ -154,10 +194,18 @@ Listing.prototype = {
         return false;
     },
 
+    /**
+     * Build a button to remove a specific filter.
+     *
+     * @this Listing
+     * @param {string} text - text of the button
+     * @param {string} type - tag type
+     * @return {Object} - new button object
+     */
     make_tag: function(text, type) {
         var $tag = $("<a />", {
             "name": type, "class" : "btn btn-default btn-xs",
-            "html": text
+            "html": " " + text
         });
         
         $("<span />", {"class" : "fa fa-remove"}).prependTo($tag);
@@ -165,6 +213,12 @@ Listing.prototype = {
         return $tag;
     },
 
+    /**
+     * Click event : remove a tag button.
+     *
+     * @this Listing
+     * @param {Object} e - event object
+     */
     remove_tag: function(e) {
         var $tag = $(e.target);
 
@@ -176,6 +230,12 @@ Listing.prototype = {
         $tag.remove();
     },
 
+    /**
+     * Click event: filter the listing when a tag is clicked.
+     *
+     * @this Listing
+     * @param {Object} e - event object
+     */
     filter_by_tag: function(e) {
         var $link = $(e.target);
         e.preventDefault();

Navigation options fixed.