Saturday, June 1, 2013

jQuery custom pagination class

Html data load as follows:

<input type="hidden" class="contact_admin_total_row_count" value="5"/>
<input type="hidden" class="contact_admin_pagination_limit" value="2"/>
<input type="hidden" class="contact_admin_total_row_found" value="1"/>

<table class="table_design_1">
/* data row here */ 
</table> 

Start pagination as follows:


var options = {
    currentPage: 1,
    loadUrl: BASE_URL + "contact/admin",
    autoLoad: true,
    total: parseInt(jQuery(".contact_admin_total_row_count").attr("value")),
    limit: parseInt(jQuery(".contact_admin_pagination_limit").attr("value")),
    totalFinderId: ".contact_admin_total_row_count",
    resultCountElm: ".contact_admin_total_row_found",
    counter: 100
};
jQuery(".table_design_1").startPagination(options, 
function(paginationCounterId,options,elm,afterCall,targetUrl,currentPage) {
    /* after some work done, refresh pagination */
    elm.refreshPagination(paginationCounterId,options,elm,afterCall,targetUrl,currentPage); 
});

And the pagination class

var paginationCounter = 1;
jQuery.fn.startPagination = function(opts, afterCall) {
    var elm = jQuery(this);
    jQuery.fn.startPagination.defaults = {
        loadUrl: "", /* Define data load url, suppose: http://pritom.com/blog_list */
        total: 5, /* Define total number of items, such 200 */
        limit: 20, /* Define pagination limit such 20 items per page */
        currentPage: 1, /* Current selected page, if 2 then items from 20-39 would load */
        place: "both", /* Pagination link for both upper and lower portion, options: ['top', 'bottom', 'both'] */
        directHtml: false, /* Would replace direct html to element or search in body */
        counter: 0, /* If one page contains more than one pagination list use integer number to defferentiate them */
        offset: 0, /* Start loading index */
        configs: {},
        sortClass: 0, /* make a header class 'sortable' to make data table sortable and define property 'sort-data'
         * to set sort data. */
        sortType: "",
        formParams: {}, /* If any params value need to submit as POST */
        showPagination: true,
        changeBrowserHistory: false, /* Change browser url if you refresh the page */
        browserHistoryURL: null, /* Then define browser history url: http://pritom.com/blog_list */
        autoLoad: false, /* Load first time if needed */
        totalFinderId: null, /* jQuery element to find total items found for re-organize pagination */
        resultCountElm: null, /* jQuery element to find current items loaded for re-load pagination if zero result found */
        parentDiv: null, /* Define if possible a parent div */
        limitFinderElm: null  /* jQuery element to find limit to paginate */
    };
    var options = $.extend(jQuery.fn.startPagination.defaults, opts);
    var paginationCounterId = null;
    if(options.counter == null || isNaN(options.counter) || options.counter == 0) {
        options.counter = paginationCounter++;
    }

    options.offset = parseInt(options.offset);
    if(isNaN(options.offset)) {
        return;
    }
    options.limit = parseInt(options.limit);
    if(isNaN(options.limit)) {
        return;
    }

    if(options.counter != 0) {
        paginationCounterId = "paginationCounter"+options.counter;
    }
    jQuery("."+paginationCounterId).remove();

    var total = parseInt(options.total);
    if(isNaN(total)) {
        return;
    }
    var limit = parseInt(options.limit);
    if(isNaN(limit)) {
        return;
    }
    var start = options.currentPage * limit;
    var pageCount = Math.ceil(total / limit);
    if(options.offset != 0) {
        start = options.offset;
        options.currentPage = Math.ceil(start/ limit) + 1;
    }
    if(isNaN(options.currentPage)) {
        options.currentPage = 1;
    }
    var propertyElm = "<input type='hidden' class='url' value='"+options.loadUrl+"'/>";
    propertyElm += "<input type='hidden' class='limit' value='"+limit+"'/>";
    var pages = [];
    var hasPrevious = 1;
    var hasNext = pageCount;
    if(options.currentPage == 1) {
        pages.push(1);
        if(pageCount >= 2) {
            pages.push(2);
        }
        if(pageCount > 2) {
            pages.push(3);
        }
    } else if(options.currentPage == pageCount) {
        if(pageCount - 2 > 0) {
            pages.push(pageCount - 2);
        }
        if(pageCount - 1 > 0) {
            pages.push(pageCount - 1);
        }
        pages.push(pageCount);
    } else {
        if(pageCount - 1 > 0) {
            pages.push(options.currentPage - 1);
        }
        pages.push(options.currentPage);
        if(options.currentPage + 1 <= pageCount) {
            pages.push(options.currentPage + 1);
        }
    }
    var classElm = "<style type=\"text/css\">" +
        ".pagination-disable {" +
        "    pointer-events: none;" +
        "}" +
        "";
    var hasPreviousClass = " pagination-disable ";
    var hasNextClass = " pagination-disable ";
    if(options.currentPage > 1) {
        hasPrevious = options.currentPage - 1;
        hasPreviousClass = "";
    }
    if(options.currentPage < pageCount) {
        hasNext = options.currentPage + 1;
        hasNextClass = "";
    }
    classElm += "</style>";
    var firstPage = "<a href='javascript:void(0)' pc='1' class='pagination-link pagination-first "+hasPreviousClass+"'><span>First&nbsp;</span></a>";
    var lastPage = "<a href='javascript:void(0)' pc='"+pageCount+"' class='pagination-link pagination-last "+hasNextClass+"'><span>&nbsp;Last</span></a>";
    var prevPage = "<a href='javascript:void(0)' pc='"+hasPrevious+"' class='pagination-link pagination-prev "+hasPreviousClass+"'><span>Prev</span></a>";
    var nextPage = "<a href='javascript:void(0)' pc='"+hasNext+"' class='pagination-link pagination-next "+hasNextClass+"'><span>Next</span></a>";
    var middlePage = "";
    jQuery.each(pages, function() {
        var _n = this;
        if(_n == options.currentPage) {
            middlePage += "<a href='javascript:void(0)' pc='"+_n+"' class='pagination-link pagination-next pagination-disable pagination-active'><span>&nbsp;"+_n+"&nbsp;</span></a>";
        } else {
            middlePage += "<a href='javascript:void(0)' pc='"+_n+"' class='pagination-link pagination-next'><span>&nbsp;"+_n+"&nbsp;</span></a>";
        }
    });
    var showingPage = "<span class='showing'>Showing "+((options.currentPage*options.limit)-options.limit+1)
        +" - "+((options.currentPage*options.limit)>options.total ? options.total : (options.currentPage*options.limit))+" of "+options.total+"</span>";
    var paginationElm = "<div class='PAGINATION_POSITION pagination "+paginationCounterId+"'>"+propertyElm;
    paginationElm += "<div class='sub-pagination'>";
    paginationElm += showingPage+firstPage+prevPage+middlePage+nextPage+lastPage;
    paginationElm += classElm+"</div></div>";
    if(options.showPagination == true && total > 0 && pageCount > 1) {
        if(options.place == "both") {
            elm.after(paginationElm.replace("PAGINATION_POSITION", "pagination_bottom "+paginationCounterId+"_bottom"));
            elm.before(paginationElm.replace("PAGINATION_POSITION", "pagination_top "+paginationCounterId+"_top"));
        } else if(options.place == "top") {
            elm.before(paginationElm.replace("PAGINATION_POSITION", "pagination_top "+paginationCounterId+"_top"));
        } else {
            elm.after(paginationElm.replace("PAGINATION_POSITION", "pagination_bottom "+paginationCounterId+"_bottom"));
        }
    }
    /**
     * START SORTING AND SEARCHING PROCESS
     */
    startSorting(paginationCounterId,options,elm,afterCall);
    startSearching(paginationCounterId,options,elm,afterCall);

    jQuery("body").find("."+paginationCounterId).delegate(".pagination-link", "click", function() {
        var elmTargetClick = jQuery(this);
        paginationLinkClickEvent(options, paginationCounterId, elm, afterCall, elmTargetClick);
    });
    if(options.autoLoad == true) {
        options.autoLoad = false;
        paginationLinkClickEvent(options, paginationCounterId, elm, afterCall, jQuery("body").find("."+paginationCounterId).find(".pagination-active"));
    }
};
function paginationLinkClickEvent(options, paginationCounterId, elm, afterCall, elmTargetClick) {
    var linkElm = elmTargetClick;
    var pc = parseInt(jQuery.trim(linkElm.attr("pc")));
    if(isNaN(pc)){
        pc = 1;
    }
    options.currentPage = pc;
    var limit = options.limit;
    var offset = (parseInt(pc) * parseInt(limit)) - parseInt(limit);
    setBrowserHistoryIfNeed(options, offset, limit);
    elm.refreshPagination(paginationCounterId,options,elm,afterCall);
}
function setBrowserHistoryIfNeed(options, offset, limit) {
    if(options.changeBrowserHistory == true) {
        if(options.browserHistoryURL != null) {
            var theURL = options.browserHistoryURL;
            if(theURL.indexOf("?") >= 0) {
                theURL = theURL + "&offset="+offset+"&limit="+limit;
            } else {
                theURL = theURL + "?offset="+offset+"&limit="+limit;
            }
            jQuery.setBrowserHistoryToCurrentState(theURL);
        }
    }
}
function startSorting(paginationCounterId,options,elm,afterCall) {
    paginationCounterId += "Sortable";
    var clickImage = "<span sortType='asc' class='img img-asc'><img style='cursor: pointer;' border='0' src='"+BASE_URL+"images/sort.png'/></span>";
    var clickImageReverse = "<span sortType='desc' class='img img-desc'><img style='cursor: pointer;' border='0' src='"+BASE_URL+"images/sort_r.png'/></span>";
    var clickMe = "<span class='sort-click "+paginationCounterId+"'><span class='sort-text'>"+clickImage+clickImageReverse+"</span></span>";
    var sortNumber = 0;
    elm.find(".sortable").each(function() {
        sortNumber++;
        var mainElm = jQuery(this);

        var sortBy = mainElm.attr("sort-data");
        if(sortBy === undefined) {
            console.log("sort by undefined");
            return;
        }
        mainElm.append(clickMe);
        mainElm.find(".sort-click").attr("sort-number", sortNumber);
        if(sortNumber == options.sortClass) {
            mainElm.find(".sort-click").addClass("active-"+options.sortType);
            mainElm.find(".sort-click").find(".img-"+options.sortType).remove();
        }
        mainElm.find(".img").bind("click", function() {
            var sortElm = jQuery(this);
            elm.find(".img").removeClass("active");
            sortElm.addClass("active");
            var sortType = jQuery(this).attr("sortType");
            if(sortType === undefined) {
                sortType = "asc";
            }
            if(sortType != "asc" && sortType != "desc") {
                sortType = "asc";
            }
            sortElm.closest(".sort-click").removeClass("asc").removeClass("desc");
            sortElm.closest(".sort-click").addClass(sortType);
            options.sortClass = sortElm.closest(".sort-click").attr("sort-number");
            options.sortType = sortType;
            options.configs['sortBy'] = sortBy;
            options.configs['sortType'] = sortType;
            elm.refreshPagination(paginationCounterId,options,elm,afterCall);
            return false;
        });
        mainElm.bind("click", function() {
            var sortElm = null;
            if(jQuery(this).find("span.sort-click").find("span.img-asc").length) {
                sortElm = jQuery(this).find("span.sort-click").find("span.img-asc");
            } else if(jQuery(this).find("span.sort-click").find("span.img-desc").length) {
                sortElm = jQuery(this).find("span.sort-click").find("span.img-desc");
            } else {
                return false;
            }
            sortElm.trigger("click");
            return false;
        });
    });
}
function startSearching(paginationCounterId,options,elm,afterCall) {
    paginationCounterId += "Searchable";
    var sortNumber = 0;
    console.log(".searchable class to header make it searchabge by rendering a text box");
    elm.find(".searchable").each(function() {
        sortNumber++;
        var mainElm = jQuery(this);
        var search_name = mainElm.attr("search_name");
        if(search_name === undefined) {
            console.log("need attribute 'search_name'");
            return;
        }
        var clickMe = "<div style='clear: both;'></div>" +
            "<input type='text' class='search_txt' placeholder='Enter search value' name='"+search_name+"' />";
        mainElm.append(clickMe);
        mainElm.find(".search_txt").change(function() {
            var searchValue = jQuery.trim(jQuery(this).attr("value"));
            options.formParams["__s[" + search_name + "]"] = searchValue;
            loadPaginationData(paginationCounterId,options,elm,afterCall);
        });
        mainElm.find(".search_txt").keypress(function(event) {
            if(event.which == 13) {
                jQuery(this).trigger("change");
            }
        });
    });
}
jQuery.fn.refreshPagination = function(paginationCounterId,options,elm,afterCall) {
    var elm = jQuery(this);
    var appendTo = jQuery("body");
    var pageHeight = parseInt(appendTo.css("height").replace(/px/g, "")) + jQuery("body").scrollTop();
    var pageWidth = appendTo.css("width");
    if(options.parentDiv != null && jQuery(options.parentDiv).length) {
        appendTo = jQuery(options.parentDiv);
        jQuery(options.parentDiv).css({
            position: "relative"
        });
        pageHeight = parseInt(jQuery(options.parentDiv).css("height").replace(/px/g, "")) + 50;
        pageWidth = appendTo.css("width");
    }
    var newDiv = jQuery('<div class="body-over" style="filter:alpha(opacity=0.3); -moz-opacity:0.3; opacity:0.3;">').appendTo(appendTo);
    newDiv.css({
        width: pageWidth,
        height: pageHeight + "px",
        position: "absolute",
        top: "0px",
        left: "0px",
        backgroundColor: "black",
        zIndex: 1000000000
    });

    var moreConfigLink = "";
    jQuery.each(options.configs, function(key, value) {
        if(moreConfigLink != "") {
            moreConfigLink += "&";
        }
        moreConfigLink += key + "=" + value;
    });

    var targetUrl = "";
    var _a = options.limit;
    var _b = (_a * options.currentPage) - _a;
    if(options.loadUrl.indexOf("?") >= 0) {
        targetUrl = options.loadUrl + "&offset="+_b+"&limit="+_a;
    } else {
        targetUrl = options.loadUrl + "?offset="+_b+"&limit="+_a;
    }
    if(targetUrl.indexOf("?") >= 0) {
        targetUrl += "&" + moreConfigLink;
    } else {
        targetUrl += "?" + moreConfigLink;
    }
    jQuery.ajax({
        type: "POST",
        dataType: "HTML",
        data: options.formParams,
        url: targetUrl,
        success: function(htmlData) {
            if(options.directHtml == true) {
                elm.html(htmlData);
            } else {
                var newElm = $("<div>"+htmlData+"</div>");
                if(newElm.find("#"+elm.attr("id")).length) {
                    elm.html(newElm.find("#"+elm.attr("id")).html());
                } else if(newElm.find("."+elm.attr("class")).length) {
                    elm.html(newElm.find("."+elm.attr("class")).html());
                }
            }
            var newElement = jQuery("<div>"+htmlData+"</div>");
            if(options.totalFinderId != null) {
                if(newElement.find(options.totalFinderId).length) {
                    var tempTotal = newElement.find(options.totalFinderId).attr("value");
                    if( tempTotal !== undefined ) {
                        tempTotal = parseInt(tempTotal);
                        if( !isNaN(tempTotal) ) {
                            options.total = tempTotal;
                        }
                    }
                }
            }
            var resultCount = null;
            if(options.resultCountElm != null) {
                if(newElement.find(options.resultCountElm).length) {
                    var tempTotal = newElement.find(options.resultCountElm).attr("value");
                    if( tempTotal !== undefined ) {
                        tempTotal = parseInt(tempTotal);
                        if( !isNaN(tempTotal) ) {
                            resultCount = tempTotal;
                        }
                    }
                }
            }
            if(options.limitFinderElm != null) {
                if(newElement.find(options.limitFinderElm).length) {
                    var tempTotal = newElement.find(options.limitFinderElm).attr("value");
                    if( tempTotal !== undefined ) {
                        tempTotal = parseInt(tempTotal);
                        if( !isNaN(tempTotal) ) {
                            options.limit = tempTotal;
                        }
                    }
                }
            }
            if(resultCount != null && resultCount == 0 && options.currentPage > 1) {
                options.currentPage--;
                while(true) {
                    var _a = options.limit;
                    var _b = (_a * options.currentPage) - _a;
                    if(_b > options.total && options.currentPage > 1) {
                        options.currentPage--;
                    } else {
                        break;
                    }
                }
                elm.refreshPagination(paginationCounterId,options,elm,afterCall);
                return false;
            }
            jQuery("."+paginationCounterId).remove();
            elm.startPagination(options, afterCall);

            if(afterCall) {
                afterCall(paginationCounterId,options,elm,afterCall);
            }
            jQuery(".body-over").remove();
        }
    });
};

YII CGridView with custom action button


<?php 
$this->widget('zii.widgets.grid.CGridView', array(
    'dataProvider'=>$model->search(),
    'filter'=>$model,
    "itemsCssClass" => "table_design_1",
    "htmlOptions" => array(
        "class" => "div_contact_admin_grid_view"
    ),
    "ajaxUpdate" => false,
    'columns'=>array(
        array(
            'name'=>'family_name', 
            'header'=>'First name',
            'type' => 'raw',
            'value' => 'CHtml::link($data->family_name,$data->id)'
        ),
        array(
            'name'=>'given_name', 
            'header'=>'Last name',
            'type' => 'raw',
            'value' => 'CHtml::link($data->given_name,$data->id)'
        ),
        array(
            'class'=>'CButtonColumn',
            'template'=>'{delete_contact}{view}{update}',
            'buttons'=>array (
                'delete_contact' => array (
                    'label'=>'Delete this contact',
                    'imageUrl'=>Yii::app()->request->baseUrl.'/images/delete.png',
                    'url'=>'Yii::app()->createUrl("contact/delete", array("id"=>$data->id))',
                    'visible' => '1',
                    "options" => array(
                        "class" => "delete_contact"
                    )
                )
            ),
            'viewButtonUrl'=>'Yii::app()->request->getBaseUrl(true)."/contact/view/".$data["id"]',
            'updateButtonUrl'=>'Yii::app()->request->getBaseUrl(true)."/contact/update/".$data["id"]',
            "htmlOptions" => array(
                'style'=>'width: 60px;',
                'class' => 'action_class'
            )
        )
    )
)); ?>

YII CGridView with CActiveDataProvider customer limit and offset params

It is a CGridView example with CActiveDataProvider class:
<?php 
$this->widget('zii.widgets.grid.CGridView', array(
    'dataProvider'=>$model->search(),
    'filter'=>$model,
    "itemsCssClass" => "table_design_1",
    "htmlOptions" => array(
        "class" => "div_design_1"
    ),
    'columns'=>array(
        array(
            'name'=>'family_name', 
            'header'=>'First name',
            'type' => 'raw',
            'value' => 'CHtml::link($data->family_name,$data->id)'
        ),
        array(
            'name'=>'given_name', 
            'header'=>'Last name',
            'type' => 'raw',
            'value' => 'CHtml::link($data->given_name,$data->id)'
        ),
        array(
            'class'=>'CButtonColumn',
            'viewButtonUrl'=>'Yii::app()->request->getBaseUrl(true)."/contact/view/".$data["id"]',
            'updateButtonUrl'=>'Yii::app()->controller->createUrl("update",$data->primaryKey)',
            'deleteButtonUrl'=>'Yii::app()->controller->createUrl("delete",$data->primaryKey)',
            "htmlOptions" => array(
                'style'=>'width: 60px;'
            )
        )
    )
)); ?> 
If you need to get total items count find by CDbCriteria you need to write:
$model->search()->getTotalItemCount(); 
 
But if you want to customize your data provider with custom limit and offset params then you need
to set 'offset' and 'limit' option to 'CDbCriteria' class as following and need to set pagination to false:

 
$criteria=new CDbCriteria;
$criteria->order = $this->sortString;
$criteria->offset = 5;
$criteria->limit = 10;

return new CActiveDataProvider($this, array(
    'criteria'=>$criteria,
    "pagination" => false
));