[
  {
    "path": ".gitignore",
    "content": "# JetBrains phpStorm IDE files -------------------------------------------------\n/.idea/*\n# ------------------------------------------------------------------------------"
  },
  {
    "path": "CHANGELOG.md",
    "content": "bs_grid\n=======\n\nbs_grid is a jQuery Datagrid plugin, based on Twitter Bootstrap.\n\nProject page: [https://www.pontikis.net/labs/bs_grid][HOME]\n[HOME]: http://www.pontikis.net/labs/bs_grid\n\nCopyright Christos Pontikis [http://www.pontikis.net][copyright]\n[copyright]: http://www.pontikis.net\n\nLicense [MIT][mit]\n[mit]: https://raw.github.com/pontikis/bs_grid/master/MIT_LICENSE\n\nRelease 0.9.2 (28 May 2014)\n---------------------------\n* Set rules (predefined filters)\n* Take and Restore snapshots (`takeSnapshot` and `restoreSnapshot` methods)\n\nRelease 0.9.1 (09 May 2014)\n---------------------------\n* Created for Bootstrap 3 (Bootstrap 2 supported)\n* Responsive web design\n* Fully configurable\n* Get data in JSON format using AJAX (any server-side technology)\n* A php class is provided for server side operations\n* Change columns order\n* Show/hide columns\n* Style columns\n* Simple column sorting with a click\n* Flexible data sorting (multi-column)\n* Single or multiple row selection\n* Powerful pagination\n* Powerful filters (Query builder)\n* Multiple instances in same page\n* Localization"
  },
  {
    "path": "MIT_LICENSE",
    "content": "Copyright (c) 2014 Christos Pontikis, http://www.pontikis.net\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "bs_grid\n=======\n\nbs_grid is a jQuery Datagrid plugin, based on Twitter Bootstrap.\n\nCopyright Christos Pontikis [http://www.pontikis.net][copyright]\n[copyright]: http://www.pontikis.net\n\nLicense [MIT][mit]\n[mit]: https://raw.github.com/pontikis/bs_grid/master/MIT_LICENSE\n\nRelease: 0.9.2 (28 May 2014) - View CHANGELOG [here][CHANGELOG]\n[CHANGELOG]: https://raw.github.com/pontikis/bs_grid/master/CHANGELOG.md\n\n\nProject page\n-----------\n[http://www.pontikis.net/labs/bs_grid][HOME]\n[HOME]: http://www.pontikis.net/labs/bs_grid\n\nDemo\n----\n[http://www.pontikis.net/labs/bs_grid/demo][DEMO]\n[DEMO]: http://www.pontikis.net/labs/bs_grid/demo\n\nDocumentation\n-------------\n[http://www.pontikis.net/labs/bs_grid/docs][DOCS]\n[DOCS]: http://www.pontikis.net/labs/bs_grid/docs\n\nSupport Forum\n-------------\n[http://www.pontikis.net/bbs][FORUM]\n[FORUM]: http://www.pontikis.net/bbs\n\nDownload\n--------\nDownload [here][DOWNLOAD]\n[DOWNLOAD]: https://github.com/pontikis/bs_grid/archive/master.zip\n\n\nScreenshots\n-----------\n![bs_grid sample1][sample1]\n[sample1]: https://raw.github.com/pontikis/bs_grid/master/screenshots/sample1.png"
  },
  {
    "path": "bs_grid.jquery.json",
    "content": "{\n    \"name\": \"bs_grid\",\n    \"version\": \"0.9.2\",\n    \"title\": \"Bootstrap Datagrid\",\n    \"author\": {\n        \"name\": \"Christos Pontikis\",\n        \"email\": \"christos@pontikis.net\",\n        \"url\": \"http://www.pontikis.net\"\n    },\n    \"licenses\": [\n        {\n            \"type\": \"MIT\",\n            \"url\": \"https://raw.github.com/pontikis/bs_grid/master/MIT_LICENSE\"\n        }\n    ],\n    \"dependencies\": {\n        \"jquery\": \">=1.8\",\n        \"bs_pagination\": \">=1.0.2\",\n        \"jui_filter_rules\": \">=1.0.5\",\n        \"momentjs\": \">=2.2.1\"\n    },\n    \"description\": \"bs_grid is a jQuery Datagrid plugin, based on Twitter Bootstrap. Advanced row selection, sorting, pagination and filtering. Fully customizable, responsive web design, localization.\",\n    \"homepage\": \"http://www.pontikis.net/labs/bs_grid\",\n    \"docs\": \"http://www.pontikis.net/labs/bs_grid/docs\",\n    \"demo\": \"http://www.pontikis.net/labs/bs_grid/demo\",\n    \"download\": \"https://github.com/pontikis/bs_grid/archive/master.zip\",\n    \"keywords\": [\n        \"grid\",\n        \"datagrid\",\n        \"table\",\n        \"database\",\n        \"paging\",\n        \"sorting\",\n        \"searching\",\n        \"responsive\",\n        \"bootstrap\",\n        \"ui\"\n    ],\n    \"bugs\": \"https://github.com/pontikis/bs_grid/issues\"\n}"
  },
  {
    "path": "jquery.bs_grid.bs2.css",
    "content": "/*\n * bs_grid v0.9.2 CSS (for Bootstrap 2) ****************************************\n*/\n\n/*\nDO NOT CHANGE this file, as it will be overwritten in next update.\nWrite your own classes in other css file.\n*/\n\n.tools {\n    height: 32px;\n}\n\n.col-checkbox {\n    margin-top: 0 !important;\n}\n\n.columns-label {\n    margin-top: 3px;\n    margin-bottom: 3px;\n    font-weight: normal;\n}\n\n.columns-li-padding {\n    padding: 3px 20px;\n}\n\n.sorting-name {\n    display: inline-block;\n    padding-left: 20px;\n    margin-top: 3px;\n    margin-bottom: 3px;\n    vertical-align: middle;\n    padding-top: 5px;\n}\n\n.selected-rows {\n    margin-left: 5px;\n    margin-right: 2px;\n    text-align: right;\n}\n\n.th-common {\n    cursor: pointer;\n}\n\n.no-records-found {\n    text-align: center;\n}\n\n.pagination-container {\n    padding: 7px 7px 5px 7px !important;\n}\n\n.filters-container {\n    padding: 0 7px 10px 7px !important;\n}\n\n.filters-button {\n    margin: 0 3px;\n}\n\n/* Large desktop */\n@media (min-width: 1200px) {\n}\n\n/* Portrait tablet to landscape and desktop */\n@media (min-width: 768px) and (max-width: 979px) {\n}\n\n/* Landscape phone to portrait tablet */\n@media (max-width: 767px) {\n    .table-responsive {\n        width: 100%;\n        margin-bottom: 15px;\n        overflow-x: scroll;\n        overflow-y: hidden;\n        -webkit-overflow-scrolling: touch;\n        -ms-overflow-style: -ms-autohiding-scrollbar;\n        border: 1px solid #ddd;\n    }\n    .table-responsive > .table {\n        margin-bottom: 0;\n    }\n    .table-responsive > .table > thead > tr > th,\n    .table-responsive > .table > tbody > tr > th,\n    .table-responsive > .table > tfoot > tr > th,\n    .table-responsive > .table > thead > tr > td,\n    .table-responsive > .table > tbody > tr > td,\n    .table-responsive > .table > tfoot > tr > td {\n        white-space: nowrap;\n    }\n    .table-responsive > .table-bordered {\n        border: 0;\n    }\n    .table-responsive > .table-bordered > thead > tr > th:first-child,\n    .table-responsive > .table-bordered > tbody > tr > th:first-child,\n    .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n    .table-responsive > .table-bordered > thead > tr > td:first-child,\n    .table-responsive > .table-bordered > tbody > tr > td:first-child,\n    .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n        border-left: 0;\n    }\n    .table-responsive > .table-bordered > thead > tr > th:last-child,\n    .table-responsive > .table-bordered > tbody > tr > th:last-child,\n    .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n    .table-responsive > .table-bordered > thead > tr > td:last-child,\n    .table-responsive > .table-bordered > tbody > tr > td:last-child,\n    .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n        border-right: 0;\n    }\n    .table-responsive > .table-bordered > tbody > tr:last-child > th,\n    .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n    .table-responsive > .table-bordered > tbody > tr:last-child > td,\n    .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n        border-bottom: 0;\n    }\n}\n\n/* Landscape phones and down */\n@media (max-width: 480px) {\n}"
  },
  {
    "path": "jquery.bs_grid.css",
    "content": "/*\n * bs_grid v0.9.2 CSS **********************************************************\n*/\n\n/*\nDO NOT CHANGE this file, as it will be overwritten in next update.\nWrite your own classes in other css file.\n*/\n\n/* bootstrap \"xs\": Extra small devices (phones, less than 768px) */\n/* No media query since this is the default in Bootstrap */\n\n.tools {\n    height: 38px;\n}\n\n.col-checkbox {\n    margin-top: 0 !important;\n}\n\n.columns-label {\n    margin-top: 3px;\n    margin-bottom: 3px;\n    font-weight: normal;\n}\n\n.columns-li-padding {\n    padding: 3px 20px;\n}\n\n.sorting-name {\n    display: inline-block;\n    padding-left: 20px;\n    margin-top: 3px;\n    margin-bottom: 3px;\n    vertical-align: middle;\n}\n\n.selected-rows {\n    margin-left: 5px;\n    margin-right: 2px;\n    text-align: right;\n}\n\n.th-common {\n    cursor: pointer;\n}\n\n.no-records-found {\n    text-align: center;\n}\n\n.pagination-container {\n    padding: 7px 7px 5px 7px !important;\n}\n\n.filters-container {\n    padding: 0 7px 10px 7px !important;\n}\n\n.filters-button {\n    margin: 0 3px;\n}\n\n/* bootstrap \"sm\": Small devices (tablets, 768px and up) */\n@media (min-width: 768px) {\n\n}\n\n/* bootstrap \"md\": Medium devices (desktops, 992px and up) */\n@media (min-width: 992px) {\n\n}\n\n/* bootstrap \"lg\": Large devices (large desktops, 1200px and up) */\n@media (min-width: 1200px) {\n\n}"
  },
  {
    "path": "jquery.bs_grid.js",
    "content": "/**\n * @fileOverview bs_grid is a jQuery datagrid plugin based on Twitter Bootstrap.\n *               <p>License MIT\n *               <br />Copyright Christos Pontikis <a href=\"http://www.pontikis.net\">http://www.pontikis.net</a>\n *               <br />Project page <a href=\"http://www.pontikis.net/labs/bs_grid/\">http://www.pontikis.net/labs/bs_grid/</a>\n * @version 0.9.2 (28 May 2014)\n * @author Christos Pontikis http://www.pontikis.net\n * @requires jquery >= 1.8, twitter bootstrap >= 2, bs_pagination plugin, jQuery UI sortable (optional), jui_filter_rules plugin >= 1.0.4 (optional)\n */\n\n/**\n * See <a href=\"http://jquery.com\">http://jquery.com</a>.\n * @name $\n * @class\n * See the jQuery Library  (<a href=\"http://jquery.com\">http://jquery.com</a>) for full details.  This just\n * documents the function and classes that are added to jQuery by this plug-in.\n */\n\n/**\n * See <a href=\"http://jquery.com\">http://jquery.com</a>\n * @name fn\n * @class\n * See the jQuery Library  (<a href=\"http://jquery.com\">http://jquery.com</a>) for full details.  This just\n * documents the function and classes that are added to jQuery by this plug-in.\n * @memberOf $\n */\n\n/**\n * Pseudo-Namespace containing bs_grid private methods (for documentation purposes)\n * @name _private_methods\n * @namespace\n */\n\n\"use strict\";\n(function($) {\n\n    var pluginName = \"bs_grid\",\n        pluginGivenOptions = \"bs_grid_given_options\",\n        pluginStatus = \"bs_grid_status\";\n\n    // public methods\n    var methods = {\n\n        /**\n         * @lends $.fn.bs_grid\n         */\n        init: function(options) {\n\n            var elem = this;\n\n            return this.each(function() {\n\n                /**\n                 * store given options on first launch (in new object - no reference)\n                 */\n                if(typeof  elem.data(pluginGivenOptions) === \"undefined\") {\n                    elem.data(pluginGivenOptions, $.extend(true, {}, options));\n                }\n\n                /**\n                 * settings and defaults\n                 * settings modification will affect elem.data(pluginName) and vice versa\n                 */\n                var settings = elem.data(pluginName);\n                if(typeof settings === \"undefined\") {\n                    var bootstrap_version = \"3\";\n                    if(options.hasOwnProperty(\"bootstrap_version\") && options[\"bootstrap_version\"] == \"2\") {\n                        bootstrap_version = \"2\";\n                    }\n                    var defaults = methods.getDefaults.call(elem, bootstrap_version);\n                    // deep merge ('true' arg) is required, as there are object attibutes (paginationOptions, filterOptions)\n                    settings = $.extend(true, {}, defaults, options);\n                } else {\n                    settings = $.extend(true, {}, settings, options);\n                }\n                elem.data(pluginName, settings);\n\n                // initialize plugin status\n                if(typeof  elem.data(pluginStatus) === \"undefined\") {\n                    elem.data(pluginStatus, {});\n                }\n\n                if(!settings.row_primary_key) {\n                    settings.selected_ids = [];\n                } else {\n                    switch(settings.rowSelectionMode) {\n                        case \"single\":\n                            if(settings.selected_ids.length > 1) {\n                                settings.selected_ids = [];\n                            }\n                            break;\n                        case false:\n                            settings.selected_ids = [];\n                            break;\n                    }\n                }\n                var container_id = elem.attr(\"id\");\n\n                // apply container style\n                elem.removeClass().addClass(settings.containerClass);\n\n                // bind events\n                elem.unbind(\"onCellClick\").bind(\"onCellClick\", settings.onCellClick);\n                elem.unbind(\"onRowClick\").bind(\"onRowClick\", settings.onRowClick);\n                elem.unbind(\"onDatagridError\").bind(\"onDatagridError\", settings.onDatagridError);\n                elem.unbind(\"onDebug\").bind(\"onDebug\", settings.onDebug);\n                elem.unbind(\"onDisplay\").bind(\"onDisplay\", settings.onDisplay);\n\n                // initialize plugin html\n                var tools_id = create_id(settings.tools_id_prefix, container_id),\n                    columns_list_id = create_id(settings.columns_list_id_prefix, container_id),\n                    default_columns_list = \"\",\n                    sorting_list_id = create_id(settings.sorting_list_id_prefix, container_id),\n                    default_sorting_list = \"\",\n                    sorting_radio_name = create_id(settings.sorting_radio_name_prefix, container_id) + \"_\",\n                    startPos, newPos,\n                    selected_rows_id = create_id(settings.selected_rows_id_prefix, container_id),\n                    selection_list_id = create_id(settings.selection_list_id_prefix, container_id),\n                    table_container_id = create_id(settings.table_container_id_prefix, container_id),\n                    table_id = create_id(settings.table_id_prefix, container_id),\n                    no_results_id = create_id(settings.no_results_id_prefix, container_id),\n                    filter_toggle_id = create_id(settings.filter_toggle_id_prefix, container_id),\n                    custom_html1_id = create_id(settings.custom_html1_id_prefix, container_id),\n                    custom_html2_id = create_id(settings.custom_html2_id_prefix, container_id),\n                    pagination_id = create_id(settings.pagination_id_prefix, container_id),\n                    filter_container_id = create_id(settings.filter_container_id_prefix, container_id),\n                    filter_rules_id = create_id(settings.filter_rules_id_prefix, container_id),\n                    filter_tools_id = create_id(settings.filter_tools_id_prefix, container_id),\n                    elem_html = \"\", tools_html = \"\";\n\n                // create basic html structure ---------------------------------\n                elem_html += '<div id=\"' + tools_id + '\" class=\"' + settings.toolsClass + '\"></div>';\n\n                elem_html += '<div id=\"' + table_container_id + '\" class=\"' + settings.dataTableContainerClass + '\">';\n                elem_html += '<table id=\"' + table_id + '\" class=\"' + settings.dataTableClass + '\"></table>';\n                elem_html += '</div>';\n\n                elem_html += '<div id=\"' + no_results_id + '\" class=\"' + settings.noResultsClass + '\">' + rsc_bs_dg.no_records_found + '</div>';\n\n                if(settings.customHTMLelementID1) {\n                    elem_html += '<div id=\"' + custom_html1_id + '\"></div>';\n                }\n\n                elem_html += '<div id=\"' + pagination_id + '\"></div>';\n\n                if(settings.customHTMLelementID2) {\n                    elem_html += '<div id=\"' + custom_html2_id + '\"></div>';\n                }\n\n                if(settings.useFilters) {\n                    elem_html += '<div id=\"' + filter_container_id + '\" class=\"' + settings.filterContainerClass + '\">';\n\n                    elem_html += '<div id=\"' + filter_rules_id + '\"></div>';\n\n                    elem_html += '<div id=\"' + filter_tools_id + '\" class=\"' + settings.filterToolsClass + '\">';\n                    elem_html += '<button class=\"' + settings.filterApplyBtnClass + '\">' + rsc_bs_dg.filters_apply + '</button>';\n                    elem_html += '<button class=\"' + settings.filterResetBtnClass + '\">' + rsc_bs_dg.filters_reset + '</button>';\n\n                    elem_html += '</div>';\n                }\n\n                elem_html += '</div>';\n\n                elem.html(elem_html);\n                $(\"#\" + no_results_id).hide();\n\n                var elem_tools = $(\"#\" + tools_id),\n                    elem_table = $(\"#\" + table_id),\n                    elem_pagination = $(\"#\" + pagination_id);\n\n                // create toolbar ----------------------------------------------\n                // columns list\n                tools_html += '<div class=\"btn-group pull-right\">';\n\n                tools_html += '<button type=\"button\" class=\"' + settings.columnsListLaunchButtonClass + '\" data-toggle=\"dropdown\" title=\"' + rsc_bs_dg.columns + '\">';\n                tools_html += '<span class=\"' + settings.columnsListLaunchButtonIconClass + '\"></span>';\n                tools_html += '<span class=\"caret\"></span>';\n                tools_html += '</button>';\n\n                tools_html += '<ul id=\"' + columns_list_id + '\" class=\"' + settings.columnsListClass + '\">';\n\n                var col_name, col_checked,\n                    col_list_len = 0;\n                for(var i in settings.columns) {\n                    if(column_is_function(settings.columns[i])) {\n                        continue;\n                    }\n                    col_list_len++;\n                    col_name = get_column_header(settings.columns[i]);\n                    col_checked = column_is_visible(settings.columns[i]) ? \" checked\" : \"\";\n                    default_columns_list += '<li><a href=\"javascript:void(0);\">' +\n                        '<label class=\"' + settings.columnsListLabelClass + '\">' +\n                        '<input type=\"checkbox\" class=\"' + settings.columnsListCheckClass + '\"' + col_checked + '> ' + col_name +\n                        '</label>' +\n                        '</a></li>';\n                }\n                default_columns_list += '<li class=\"not-sortable ' + settings.columnsListDividerClass + '\"></li>';\n\n                var row_index_checked = settings.showRowNumbers ? \" checked\" : \"\";\n                default_columns_list += '<li class=\"not-sortable columns-li-padding\"><label class=\"' + settings.columnsListLabelClass + '\"><input type=\"checkbox\" class=\"' + settings.columnsListCheckClass + '\"' + row_index_checked + '>&nbsp;' + rsc_bs_dg.columns_show_row_numbers + '</label></li>';\n                default_columns_list += '<li class=\"not-sortable ' + settings.columnsListDividerClass + '\"></li>';\n                default_columns_list += '<li class=\"not-sortable columns-li-padding\"><button class=\"' + settings.columnsListDefaultButtonClass + '\">' + rsc_bs_dg.columns_default + '</button></li>';\n\n                //save default columns list\n                if(typeof elem.data(pluginStatus)[\"default_columns_list\"] === \"undefined\") {\n                    elem.data(pluginStatus)[\"default_columns_list\"] = default_columns_list;\n                }\n\n                tools_html += default_columns_list;\n\n                tools_html += '</ul>';\n\n                tools_html += '</div>';\n\n                // sorting list ------------------------------------------------\n                tools_html += '<div class=\"btn-group pull-right\">';\n\n                tools_html += '<button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\" title=\"' + rsc_bs_dg.sorting + '\">';\n                tools_html += '<span class=\"' + settings.sortingListLaunchButtonIconClass + '\"></span>';\n                tools_html += '<span class=\"caret\"></span>';\n                tools_html += '</button>';\n\n                tools_html += '<ul id=\"' + sorting_list_id + '\" class=\"dropdown-menu dropdown-menu-right\">';\n\n                for(var i in settings.sorting) {\n                    var sort_name = get_sorting_name(settings.sorting[i]),\n                        checked_asc = settings.sorting[i][\"order\"] == \"ascending\" ? \" checked\" : \"\",\n                        checked_desc = settings.sorting[i][\"order\"] == \"descending\" ? \" checked\" : \"\",\n                        checked_none = settings.sorting[i][\"order\"] == \"none\" ? \" checked\" : \"\";\n                    default_sorting_list += '<li><a href=\"javascript:void(0);\">' +\n                        '<label class=\"' + settings.sortingLabelCheckboxClass + '\"><input type=\"radio\" name=\"' + sorting_radio_name + i + '\"' + checked_asc + '>' + rsc_bs_dg.sort_ascending + '</label>' +\n                        '<label class=\"' + settings.sortingLabelCheckboxClass + '\"><input type=\"radio\" name=\"' + sorting_radio_name + i + '\"' + checked_desc + '>' + rsc_bs_dg.sort_descending + '</label>' +\n                        '<label class=\"' + settings.sortingLabelCheckboxClass + '\"><input type=\"radio\" name=\"' + sorting_radio_name + i + '\"' + checked_none + '>' + rsc_bs_dg.sort_none + '</label>' +\n                        '<span class=\"' + settings.sortingNameClass + '\">' + sort_name + '</span>' +\n                        '</a></li>';\n                }\n                default_sorting_list += '<li class=\"not-sortable ' + settings.columnsListDividerClass + '\"></li>';\n                default_sorting_list += '<li class=\"not-sortable columns-li-padding\"><button class=\"' + settings.columnsListDefaultButtonClass + '\">' + rsc_bs_dg.sorting_default + '</button></li>';\n\n                //save default columns list\n                if(typeof elem.data(pluginStatus)[\"default_sorting_list\"] === \"undefined\") {\n                    elem.data(pluginStatus)[\"default_sorting_list\"] = default_sorting_list;\n                }\n\n                tools_html += default_sorting_list;\n\n                tools_html += '</ul>';\n\n                tools_html += '</div>';\n\n                // selection list ----------------------------------------------\n                if(settings.row_primary_key &&\n                    (settings.rowSelectionMode == \"single\" || settings.rowSelectionMode == \"multiple\")) {\n                    tools_html += '<div class=\"btn-group pull-right\">';\n\n                    tools_html += '<button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\" title=\"' + rsc_bs_dg.select + '\">';\n                    tools_html += '<span class=\"' + settings.selectButtonIconClass + '\"></span>';\n                    tools_html += '<span id=\"' + selected_rows_id + '\" class=\"' + settings.selectedRowsClass + '\">' + settings.selected_ids.length + '</span>';\n                    tools_html += '<span class=\"caret\"></span>';\n                    tools_html += '</button>';\n\n                    tools_html += '<ul id=\"' + selection_list_id + '\" class=\"dropdown-menu dropdown-menu-right\">';\n\n                    if(settings.rowSelectionMode == \"multiple\") {\n                        tools_html += '<li><a href=\"javascript:void(0);\">' + rsc_bs_dg.select_all_in_page + '</a></li>';\n                        tools_html += '<li><a href=\"javascript:void(0);\">' + rsc_bs_dg.deselect_all_in_page + '</a></li>';\n                        tools_html += '<li><a href=\"javascript:void(0);\">' + rsc_bs_dg.select_inverse_in_page + '</a></li>';\n                        tools_html += '<li class=\"not-sortable ' + settings.columnsListDividerClass + '\"></li>';\n                    }\n\n                    tools_html += '<li><a href=\"javascript:void(0);\">' + rsc_bs_dg.deselect_all + '</a></li>';\n\n                    tools_html += '</ul>';\n\n                    tools_html += '</div>';\n                }\n\n                // filter toggle button ----------------------------------------\n                if(settings.useFilters) {\n                    tools_html += '<button id=\"' + filter_toggle_id + '\" class=\"btn btn-default pull-right\" title=\"' + rsc_bs_dg.filters + '\"><span class=\"' + settings.filterToggleButtonIconClass + '\"></span></button>';\n                }\n\n                elem_tools.html(tools_html);\n\n                // initialize grid ---------------------------------------------\n                var grid_init = methods.displayGrid.call(elem, false);\n\n                /**\n                 * EVENTS ******************************************************\n                 */\n\n                //TOOLS - columns list -----------------------------------------\n                var elem_columns_list = $(\"#\" + columns_list_id);\n\n                // Prevent closing on click\n                elem_columns_list.click(function(e) {\n                    e.stopPropagation();\n                });\n\n                // show - hide column\n                elem_columns_list.off(\"click\", \"input[type=checkbox]\").on(\"click\", \"input[type=checkbox]\", function() {\n\n                    var col_index = $($(\"#\" + columns_list_id + \" li input[type=checkbox]\")).index(this);\n                    if(col_index < col_list_len) {\n\n                        var checked_columns = $(\"#\" + columns_list_id + \" li\").not(\".not-sortable\").find(\"input[type=checkbox]:checked\").length;\n                        if(checked_columns == 1) {\n                            $(\"#\" + columns_list_id + \" li input[type=checkbox]:checked:first\").slice(0, col_list_len).attr(\"disabled\", true);\n                        } else {\n                            $(\"#\" + columns_list_id + \" li input[type=checkbox]\").slice(0, col_list_len).removeAttr(\"disabled\");\n                        }\n                        set_column_visible(settings.columns[col_index], $(this).is(\":checked\"));\n\n                    } else {\n                        // show - hide row index\n                        settings.showRowNumbers = $(this).is(\":checked\");\n                    }\n                    methods.displayGrid.call(elem, false);\n                });\n\n                // change column index\n                if(settings.useSortableLists) {\n                    if(col_list_len > 2) {\n                        elem_columns_list.sortable({\n                            items: \"li:not(.not-sortable)\",\n                            start: function(event, ui) {\n                                startPos = ui.item.index();\n                            },\n                            stop: function(event, ui) {\n                                newPos = ui.item.index();\n                                if(newPos !== startPos) {\n                                    array_move(settings.columns, startPos, newPos);\n                                    methods.displayGrid.call(elem, false);\n                                }\n                            }\n                        });\n                    }\n                }\n\n                // display default columns\n                elem_columns_list.off(\"click\", \"li:last button\").on(\"click\", \"li:last button\", function() {\n\n                    for(var i in elem.data(pluginGivenOptions)[\"columns\"]) {\n                        settings.columns[i] = $.extend(true, {}, elem.data(pluginGivenOptions)[\"columns\"][i]);\n                    }\n\n                    var checked = elem.data(pluginGivenOptions)[\"showRowNumbers\"];\n                    if(typeof checked === \"undefined\") {\n                        checked = methods.getDefaults.call(elem, settings.bootstrap_version)[\"showRowNumbers\"];\n                    }\n                    settings.showRowNumbers = checked;\n\n                    elem_columns_list.html(elem.data(pluginStatus)[\"default_columns_list\"]);\n\n                    methods.displayGrid.call(elem, false);\n                });\n\n                // TOOLS - sorting list ----------------------------------------\n                var elem_sorting_list = $(\"#\" + sorting_list_id),\n                    sort_len = settings.sorting.length;\n\n                // Prevent closing on click\n                elem_sorting_list.click(function(e) {\n                    e.stopPropagation();\n                });\n\n                // change sorting type\n                elem_sorting_list.off(\"click\", \"input[type=radio]\").on(\"click\", \"input[type=radio]\", function() {\n                    var radio_index = $($(\"#\" + sorting_list_id + \" li input[type=radio]\")).index(this),\n                        sorting_index = Math.floor(radio_index / 3),\n                        sorting_type = \"\";\n                    switch(radio_index % 3) {\n                        case 0:\n                            sorting_type = \"ascending\";\n                            break;\n                        case 1:\n                            sorting_type = \"descending\";\n                            break;\n                        case 2:\n                            sorting_type = \"none\";\n                            break;\n                    }\n                    if(settings.sorting[sorting_index][\"order\"] != sorting_type) {\n                        settings.sorting[sorting_index][\"order\"] = sorting_type;\n                        methods.displayGrid.call(elem, false);\n                    }\n                });\n\n                // change sorting index\n                if(settings.useSortableLists) {\n                    if(sort_len > 2) {\n                        elem_sorting_list.sortable({\n                            items: \"li:not(.not-sortable)\",\n                            start: function(event, ui) {\n                                startPos = ui.item.index();\n                            },\n                            stop: function(event, ui) {\n                                newPos = ui.item.index();\n                                if(newPos !== startPos) {\n                                    array_move(settings.sorting, startPos, newPos);\n                                    methods.displayGrid.call(elem, false);\n                                }\n                            }\n                        });\n                    }\n                }\n\n                // display default sorting\n                elem_sorting_list.off(\"click\", \"li:last button\").on(\"click\", \"li:last button\", function() {\n\n                    for(var i in elem.data(pluginGivenOptions)[\"sorting\"]) {\n                        settings.sorting[i] = $.extend(true, {}, elem.data(pluginGivenOptions)[\"sorting\"][i]);\n                    }\n                    elem_sorting_list.html(elem.data(pluginStatus)[\"default_sorting_list\"]);\n\n                    methods.displayGrid.call(elem, false);\n                });\n\n                // row selection -----------------------------------------------\n                if(settings.row_primary_key &&\n                    (settings.rowSelectionMode == \"single\" || settings.rowSelectionMode == \"multiple\")) {\n\n                    var row_prefix_len = (table_id + \"_tr_\").length;\n\n                    // click on row\n                    elem_table.off(\"click\", \"tbody tr\").on(\"click\", \"tbody tr\", function() {\n\n                        var row_id = parseInt($(this).attr(\"id\").substr(row_prefix_len)),\n                            row_status,\n                            idx = methods.selectedRows.call(elem, \"selected_index\", row_id);\n\n                        if(idx > -1) {\n                            methods.selectedRows.call(elem, \"remove_id\", idx);\n                            methods.selectedRows.call(elem, \"mark_deselected\", row_id);\n                            row_status = \"deselected\";\n                        } else {\n                            if(settings.rowSelectionMode == \"single\") {\n                                methods.selectedRows.call(elem, \"clear_all_ids\");\n                                methods.selectedRows.call(elem, \"mark_page_deselected\");\n                            }\n                            methods.selectedRows.call(elem, \"add_id\", row_id);\n                            methods.selectedRows.call(elem, \"mark_selected\", row_id);\n                            row_status = \"selected\";\n                        }\n\n                        // update selected rows counter\n                        methods.selectedRows.call(elem, \"update_counter\");\n\n                        elem.triggerHandler(\"onRowClick\", {row_id: row_id, row_status: row_status});\n                    });\n\n                    // selection list\n                    var elem_selection_list = $(\"#\" + selection_list_id);\n\n                    elem_selection_list.off(\"click\", \"li\").on(\"click\", \"li\", function() {\n                        var sel_index = $(this).index();\n\n                        if(settings.rowSelectionMode == \"single\") {\n                            methods.selectedRows.call(elem, \"clear_all_ids\");\n                            methods.selectedRows.call(elem, \"mark_page_deselected\");\n                        } else if(settings.rowSelectionMode == \"multiple\") {\n\n                            var selector_table_tr = \"#\" + table_id + \" tbody tr\",\n                                row_prefix_len = (table_id + \"_tr_\").length,\n                                row_id, idx;\n                            switch(sel_index) {\n                                case 0:\n                                    $(selector_table_tr).each(function() {\n                                        row_id = parseInt($(this).attr(\"id\").substr(row_prefix_len));\n                                        idx = methods.selectedRows.call(elem, \"selected_index\", row_id);\n                                        if(idx == -1) {\n                                            methods.selectedRows.call(elem, \"add_id\", row_id);\n                                        }\n                                    });\n                                    methods.selectedRows.call(elem, \"mark_page_selected\");\n                                    break;\n                                case 1:\n                                    $(selector_table_tr).each(function() {\n                                        row_id = parseInt($(this).attr(\"id\").substr(row_prefix_len));\n                                        idx = methods.selectedRows.call(elem, \"selected_index\", row_id);\n                                        if(idx > -1) {\n                                            methods.selectedRows.call(elem, \"remove_id\", idx);\n                                        }\n                                    });\n                                    methods.selectedRows.call(elem, \"mark_page_deselected\");\n                                    break;\n                                case 2:\n                                    $(selector_table_tr).each(function() {\n                                        row_id = parseInt($(this).attr(\"id\").substr(row_prefix_len));\n                                        idx = methods.selectedRows.call(elem, \"selected_index\", row_id);\n                                        if(idx > -1) {\n                                            methods.selectedRows.call(elem, \"remove_id\", idx);\n                                        } else {\n                                            methods.selectedRows.call(elem, \"add_id\", row_id);\n                                        }\n                                    });\n                                    methods.selectedRows.call(elem, \"mark_page_inversed\");\n                                    break;\n                                case 4:\n                                    methods.selectedRows.call(elem, \"clear_all_ids\");\n                                    methods.selectedRows.call(elem, \"mark_page_deselected\");\n                                    break;\n                            }\n                        }\n\n                        // update selected rows counter\n                        methods.selectedRows.call(elem, \"update_counter\");\n\n                    });\n\n                }\n\n                // click on cell -----------------------------------------------\n                elem_table.off(\"click\", \"tbody tr td\").on(\"click\", \"tbody tr td\", function() {\n                    var col_index = $(this).index();\n                    var row_index = $(this).parent(\"tr\").index();\n                    elem.triggerHandler(\"onCellClick\", {col: col_index, row: row_index});\n                });\n\n                // simple columns sorting --------------------------------------\n                elem_table.off(\"click\", \"thead th\").on(\"click\", \"thead th\", function() {\n\n                    var th_index = $(this).index(),\n                        th_text = $(this).text();\n\n                    if(settings.showRowNumbers) {\n                        if(th_index == 0) {\n                            return false;\n                        } else {\n                            th_index--;\n                        }\n                    }\n\n                    // get column field\n                    var visible_index = -1, th_field = \"\", th_sortable = false;\n                    for(var i in settings.columns) {\n                        if(column_is_visible(settings.columns[i])) {\n                            visible_index++;\n                            if(visible_index == th_index) {\n                                th_field = settings.columns[i].field;\n                                th_sortable = column_is_sortable(settings.columns[i]);\n                                break;\n                            }\n                        }\n                    }\n\n                    if(!th_sortable) {\n                        return false;\n                    }\n\n                    if(th_field) {\n\n                        // get sorting order for this field (if any)\n                        var current_order = \"\", new_order;\n                        for(var i in settings.sorting) {\n                            if(settings.sorting[i].field == th_field) {\n                                current_order = settings.sorting[i].order;\n                                break;\n                            }\n                        }\n\n                        switch(current_order) {\n                            case \"ascending\":\n                                new_order = \"descending\";\n                                break;\n                            case \"descending\":\n                                new_order = \"none\";\n                                break;\n                            case \"none\":\n                                new_order = \"ascending\";\n                                break;\n                            default:\n                                new_order = \"ascending\";\n                        }\n\n                        settings.sorting = [\n                            {\n                                sortingName: th_text,\n                                field: th_field,\n                                order: new_order\n                            }\n                        ];\n\n                        // update sorting list\n                        var checked_asc = new_order == \"ascending\" ? \" checked\" : \"\",\n                            checked_desc = new_order == \"descending\" ? \" checked\" : \"\",\n                            checked_none = new_order == \"none\" ? \" checked\" : \"\";\n                        var sorting_list_html = '<li><a href=\"javascript:void(0);\">' +\n                            '<label class=\"' + settings.sortingLabelCheckboxClass + '\"><input type=\"radio\" name=\"' + sorting_radio_name + '0' + '\"' + checked_asc + '>' + rsc_bs_dg.sort_ascending + '</label>' +\n                            '<label class=\"' + settings.sortingLabelCheckboxClass + '\"><input type=\"radio\" name=\"' + sorting_radio_name + '0' + '\"' + checked_desc + '>' + rsc_bs_dg.sort_descending + '</label>' +\n                            '<label class=\"' + settings.sortingLabelCheckboxClass + '\"><input type=\"radio\" name=\"' + sorting_radio_name + '0' + '\"' + checked_none + '>' + rsc_bs_dg.sort_none + '</label>' +\n                            '<span class=\"' + settings.sortingNameClass + '\">' + th_text + '</span>' +\n                            '</a></li>';\n\n                        sorting_list_html += '<li class=\"not-sortable ' + settings.columnsListDividerClass + '\"></li>';\n                        sorting_list_html += '<li class=\"not-sortable columns-li-padding\"><button class=\"' + settings.columnsListDefaultButtonClass + '\">' + rsc_bs_dg.sorting_default + '</button></li>';\n                        elem_sorting_list.html(sorting_list_html);\n\n                        // display grid\n                        methods.displayGrid.call(elem, false);\n\n                    }\n\n                });\n\n                // PAGINATION --------------------------------------------------\n                $.when(grid_init).then(function(data, textStatus, jqXHR) {\n\n                    var total_rows = data[\"total_rows\"];\n\n                    var pagination_options = settings.paginationOptions,\n                        bs_grid_pagination_options = {\n                            // defined by bs_grid\n                            currentPage: settings.pageNum,\n                            rowsPerPage: settings.rowsPerPage,\n                            maxRowsPerPage: settings.maxRowsPerPage,\n                            totalPages: Math.ceil(total_rows / settings.rowsPerPage),\n                            totalRows: total_rows,\n                            bootstrap_version: settings.bootstrap_version,\n\n                            onChangePage: function(event, params) {\n                                settings.pageNum = params.currentPage;\n                                settings.rowsPerPage = params.rowsPerPage;\n                                methods.displayGrid.call(elem, false);\n                            }\n                        };\n                    $.extend(pagination_options, bs_grid_pagination_options);\n                    elem_pagination.bs_pagination(pagination_options);\n\n                    // custom html ---------------------------------------------\n                    // (not an event, but page renders better displaying custom html after grid rendering)\n                    if(settings.customHTMLelementID1) {\n                        $(\"#\" + custom_html1_id).html($(\"#\" + settings.customHTMLelementID1).html());\n                    }\n\n                    if(settings.customHTMLelementID2) {\n                        $(\"#\" + custom_html2_id).html($(\"#\" + settings.customHTMLelementID2).html());\n                    }\n\n                });\n\n                // FILTERS -----------------------------------------------------\n                if(settings.useFilters) {\n\n                    var elem_filter_toggle = $(\"#\" + filter_toggle_id),\n                        elem_filter_container = $(\"#\" + filter_container_id),\n                        elem_filter_rules = $(\"#\" + filter_rules_id),\n                        elem_filter_tools = $(\"#\" + filter_tools_id);\n\n                    // initialize jui_filter_rules plugin ----------------------\n                    var filter_options = settings.filterOptions,\n                        bs_grid_internal_filter_options = {\n                            bootstrap_version: settings.bootstrap_version,\n                            onValidationError: function(event, data) {\n                                elem.triggerHandler(\"onDatagridError\", data);\n                            },\n                            onSetRules: function() {\n                            }\n                        };\n                    filter_options = $.extend({}, filter_options, bs_grid_internal_filter_options);\n                    elem_filter_rules.jui_filter_rules(filter_options);\n\n                    if(filter_options.filter_rules.length > 0) {\n                        elem_filter_container.show();\n                    } else {\n                        elem_filter_container.hide();\n                    }\n\n                    /* filter toogle */\n                    elem_tools.off(\"click\", \"#\" + filter_toggle_id).on(\"click\", \"#\" + filter_toggle_id, function() {\n\n                        if(elem_filter_container.is(\":visible\")) {\n                            elem_filter_container.slideUp();\n                            if(settings.filterOptions.filter_rules.length > 0) {\n                                // mark filter toggle as active\n                                elem_filter_toggle.addClass(settings.filterToggleActiveClass);\n                            }\n                        } else {\n                            elem_filter_container.slideDown();\n                            // mark filter toggle as inactive\n                            elem_filter_toggle.removeClass(settings.filterToggleActiveClass);\n                        }\n\n                    });\n\n                    /* filter tools */\n                    elem_filter_tools.off(\"click\", \"button\").on(\"click\", \"button\", function() {\n\n                        var btn_index = $(this).index(),\n                            a_rules = elem_filter_rules.jui_filter_rules(\"getRules\", 0, []);\n\n                        if(a_rules == false) {\n                            return false;\n                        }\n\n                        switch(btn_index) {\n                            case 0:\n                                //elem_filter_rules.jui_filter_rules(\"markAllRulesAsApplied\");\n                                settings.filterOptions.filter_rules = a_rules;\n                                // Reset selected rows\n                                settings.selected_ids = [];\n                                break;\n                            case 1:\n                                elem_filter_rules.jui_filter_rules(\"clearAllRules\");\n                                settings.filterOptions.filter_rules = [];\n                                break;\n                        }\n                        settings.pageNum = 1;\n                        methods.displayGrid.call(elem, true);\n\n                    });\n\n                }\n\n            });\n        },\n\n        /**\n         * Get plugin version\n         * @returns {string}\n         */\n        getVersion: function() {\n            return \"0.9.2\";\n        },\n\n        /**\n         * Get default values\n         * @example $(element).bs_grid(\"getDefaults\", \"3\");\n         * @return {Object}\n         */\n        getDefaults: function(bootstrap_version) {\n            var default_settings = {\n                pageNum: 1,\n                rowsPerPage: 10,\n                maxRowsPerPage: 100,\n                row_primary_key: \"\",\n                rowSelectionMode: \"single\", // \"multiple\", \"single\", false\n                selected_ids: [],\n\n                /**\n                 * MANDATORY PROPERTIES: field\n                 * UNIQUE PROPERTIES: field\n                 * {field: \"customer_id\", header: \"Code\", visible: \"no\", is_function: \"no\", \"headerClass\": \"th_code hidden-xs\", \"dataClass\": \"td_code hidden-xs\"},\n                 */\n                columns: [],\n\n                /**\n                 * MANDATORY PROPERTIES: field, order\n                 * UNIQUE PROPERTIES: field\n                 * order is one of \"ascending\", \"descending\", \"none\"\n                 * {sortingName: \"Code\", field: \"customer_id\", order: \"none\"},\n                 */\n                sorting: [],\n\n                /**\n                 * See bs_pagination plugin documentation\n                 */\n                paginationOptions: {\n                    containerClass: \"well pagination-container\",\n                    visiblePageLinks: 5,\n                    showGoToPage: true,\n                    showRowsPerPage: true,\n                    showRowsInfo: true,\n                    showRowsDefaultInfo: true,\n                    disableTextSelectionInNavPane: true\n                }, // \"currentPage\", \"rowsPerPage\", \"maxRowsPerPage\", \"totalPages\", \"totalRows\", \"bootstrap_version\", \"onChangePage\" will be ignored\n\n                /**\n                 * See jui_filter_rules plugin documentation\n                 */\n                filterOptions: {\n                    filters: [],\n                    filter_rules: []\n                }, // \"bootstrap_version\", \"onSetRules\", \"onValidationError\" will be ignored\n\n                useFilters: true,\n                showRowNumbers: false,\n                showSortingIndicator: true,\n                useSortableLists: true,\n                customHTMLelementID1: \"\",\n                customHTMLelementID2: \"\",\n\n                /* STYLES ----------------------------------------------------*/\n                bootstrap_version: \"3\",\n\n                // bs 3\n                containerClass: \"grid_container\",\n                noResultsClass: \"alert alert-warning no-records-found\",\n\n                toolsClass: \"tools\",\n\n                columnsListLaunchButtonClass: \"btn btn-default dropdown-toggle\",\n                columnsListLaunchButtonIconClass: \"glyphicon glyphicon-th\",\n                columnsListClass: \"dropdown-menu dropdown-menu-right\",\n                columnsListLabelClass: \"columns-label\",\n                columnsListCheckClass: \"col-checkbox\",\n                columnsListDividerClass: \"divider\",\n                columnsListDefaultButtonClass: \"btn btn-primary btn-xs btn-block\",\n\n                sortingListLaunchButtonIconClass: \"glyphicon glyphicon-sort\",\n                sortingLabelCheckboxClass: \"radio-inline\",\n                sortingNameClass: \"sorting-name\",\n\n                selectButtonIconClass: \"glyphicon  glyphicon-check\",\n                selectedRowsClass: \"selected-rows\",\n\n                filterToggleButtonIconClass: \"glyphicon glyphicon-filter\",\n                filterToggleActiveClass: \"btn-info\",\n\n                sortingIndicatorAscClass: \"glyphicon glyphicon-chevron-up text-muted\",\n                sortingIndicatorDescClass: \"glyphicon glyphicon-chevron-down text-muted\",\n\n                dataTableContainerClass: \"table-responsive\",\n                dataTableClass: \"table table-bordered table-hover\",\n                commonThClass: \"th-common\",\n                selectedTrClass: \"warning\",\n\n                filterContainerClass: \"well filters-container\",\n                filterToolsClass: \"\",\n                filterApplyBtnClass: \"btn btn-primary btn-sm filters-button\",\n                filterResetBtnClass: \"btn btn-default btn-sm filters-button\",\n\n                // prefixes\n                tools_id_prefix: \"tools_\",\n                columns_list_id_prefix: \"columns_list_\",\n                sorting_list_id_prefix: \"sorting_list_\",\n                sorting_radio_name_prefix: \"sort_radio_\",\n                selected_rows_id_prefix: \"selected_rows_\",\n                selection_list_id_prefix: \"selection_list_\",\n                filter_toggle_id_prefix: \"filter_toggle_\",\n\n                table_container_id_prefix: \"tbl_container_\",\n                table_id_prefix: \"tbl_\",\n\n                no_results_id_prefix: \"no_res_\",\n\n                custom_html1_id_prefix: \"custom1_\",\n                custom_html2_id_prefix: \"custom2_\",\n\n                pagination_id_prefix: \"pag_\",\n                filter_container_id_prefix: \"flt_container_\",\n                filter_rules_id_prefix: \"flt_rules_\",\n                filter_tools_id_prefix: \"flt_tools_\",\n\n                // misc\n                debug_mode: \"no\",\n\n                // events\n                onCellClick: function() {\n                },\n                onRowClick: function() {\n                },\n                onDatagridError: function() {\n                },\n                onDebug: function() {\n                },\n                onDisplay: function() {\n                }\n            };\n\n            if(bootstrap_version == \"2\") {\n                default_settings.bootstrap_version = \"2\";\n                // bs 2\n                default_settings.columnsListLaunchButtonIconClass = \"icon-th\";\n                default_settings.sortingListLaunchButtonIconClass = \"icon-resize-vertical\";\n                default_settings.sortingLabelCheckboxClass = \"radio inline\";\n                default_settings.selectButtonIconClass = \"icon-check\";\n                default_settings.filterToggleButtonIconClass = \"icon-filter\";\n                default_settings.filterToggleActiveClass = \"btn-info\";\n\n                default_settings.sortingIndicatorAscClass = \"icon-chevron-up muted\";\n                default_settings.sortingIndicatorDescClass = \"icon-chevron-down muted\";\n\n                default_settings.dataTableContainerClass = \"table-responsive\";\n                default_settings.dataTableClass = \"table table-bordered table-hover\";\n\n                default_settings.filterApplyBtnClass = \"btn btn-primary filters-button \";\n                default_settings.filterResetBtnClass = \"btn btn-default filters-button\";\n            }\n\n            return default_settings;\n        },\n\n        /**\n         * Get any option set to plugin using its name (as string)\n         *\n         * @example $(element).bs_grid(\"getOption\", some_option);\n         * @param {String} opt\n         * @return {*}\n         */\n        getOption: function(opt) {\n            var elem = this;\n            return elem.data(pluginName)[opt];\n        },\n\n        /**\n         * Get all options\n         * @example $(element).bs_grid(\"getAllOptions\");\n         * @return {*}\n         */\n        getAllOptions: function() {\n            var elem = this;\n            return elem.data(pluginName);\n        },\n\n        /**\n         * Destroy plugin\n         * @example $(element).bs_grid(\"destroy\");\n         */\n        destroy: function() {\n            var elem = this,\n                container_id = elem.attr(\"id\"),\n                pagination_container_id = create_id(methods.getOption.call(elem, \"pagination_id_prefix\"), container_id),\n                filter_rules_id = create_id(methods.getOption.call(elem, \"filter_rules_id_prefix\"), container_id);\n\n            $(\"#\" + pagination_container_id).removeData();\n            $(\"#\" + filter_rules_id).removeData();\n            elem.removeData();\n        },\n\n        /**\n         *\n         * @param {string} action\n         * @param {int} id the id or its (zero based) index in selected IDs\n         * @returns {*}\n         */\n        selectedRows: function(action, id) {\n            var elem = this,\n                container_id = elem.attr(\"id\"),\n                s = methods.getAllOptions.call(elem),\n                table_id = create_id(methods.getOption.call(elem, \"table_id_prefix\"), container_id),\n                selectedTrClass = methods.getOption.call(elem, \"selectedTrClass\"),\n                selector_table_tr = \"#\" + table_id + \" tbody tr\",\n                table_tr_prefix = \"#\" + table_id + \"_tr_\";\n\n            switch(action) {\n                case \"get_ids\":\n                    return s.selected_ids;\n                    break;\n                case \"clear_all_ids\":\n                    s.selected_ids = [];\n                    break;\n                case \"update_counter\":\n                    var selected_rows_id = create_id(methods.getOption.call(elem, \"selected_rows_id_prefix\"), container_id);\n                    $(\"#\" + selected_rows_id).text(s.selected_ids.length);\n                    break;\n                case \"selected_index\":\n                    return $.inArray(id, s.selected_ids);\n                    break;\n                case \"add_id\":\n                    s.selected_ids.push(id);\n                    break;\n                case \"remove_id\":\n                    s.selected_ids.splice(id, 1);\n                    break;\n                case \"mark_selected\":\n                    $(table_tr_prefix + id).addClass(selectedTrClass);\n                    break;\n                case \"mark_deselected\":\n                    $(table_tr_prefix + id).removeClass(selectedTrClass);\n                    break;\n                case \"mark_page_selected\":\n                    $(selector_table_tr).addClass(selectedTrClass);\n                    break;\n                case \"mark_page_deselected\":\n                    $(selector_table_tr).removeClass(selectedTrClass);\n                    break;\n                case \"mark_page_inversed\":\n                    $(selector_table_tr).toggleClass(selectedTrClass);\n                    break;\n            }\n\n        },\n\n        /**\n         * Set a class to page column\n         * @example $(element).bs_grid(\"setPageColClass\", 1, \"headerClass\", \"dataClass\");\n         * @param {Number} col_index\n         * @param {String} headerClass\n         * @param {String} dataClass\n         */\n        setPageColClass: function(col_index, headerClass, dataClass) {\n            var elem = this,\n                container_id = elem.attr(\"id\"),\n                data_table_selector = \"#\" + create_id(methods.getOption.call(elem, \"table_id_prefix\"), container_id);\n\n            if(headerClass !== \"\") {\n                $(data_table_selector + \" th\").eq(col_index).addClass(headerClass);\n            }\n            if(dataClass !== \"\") {\n                $(data_table_selector + \" tr\").each(function() {\n                    $(this).find(\"td\").eq(col_index).addClass(dataClass);\n                });\n            }\n\n        },\n\n        /**\n         * Set a class to page column\n         * @example $(element).bs_grid(\"removePageColClass\", 1, \"headerClass\", \"dataClass\");\n         * @param {Number} col_index\n         * @param {String} headerClass\n         * @param {String} dataClass\n         */\n        removePageColClass: function(col_index, headerClass, dataClass) {\n            var elem = this,\n                container_id = elem.attr(\"id\"),\n                data_table_selector = \"#\" + create_id(methods.getOption.call(elem, \"table_id_prefix\"), container_id);\n\n            $(data_table_selector + \" th\").eq(col_index).removeClass(headerClass);\n            $(data_table_selector + \" tr\").each(function() {\n                $(this).find(\"td\").eq(col_index).removeClass(dataClass);\n            });\n\n        },\n\n        displayGrid: function(refresh_pag) {\n\n            var elem = this,\n                container_id = elem.attr(\"id\"),\n                s = methods.getAllOptions.call(elem),\n\n                table_id = create_id(s.table_id_prefix, container_id),\n                elem_table = $(\"#\" + table_id),\n                no_results_id = create_id(s.no_results_id_prefix, container_id),\n                elem_no_results = $(\"#\" + no_results_id),\n                filter_rules_id = create_id(s.filter_rules_id_prefix, container_id),\n                pagination_id = create_id(s.pagination_id_prefix, container_id),\n                elem_pagination = $(\"#\" + pagination_id),\n                err_msg;\n\n            // fetch page data and display datagrid\n            var res = $.ajax({\n                type: \"POST\",\n                url: s.ajaxFetchDataURL,\n                data: {\n                    page_num: s.pageNum,\n                    rows_per_page: s.rowsPerPage,\n                    columns: s.columns,\n                    sorting: s.sorting,\n                    filter_rules: s.filterOptions.filter_rules,\n                    debug_mode: s.debug_mode\n                },\n                dataType: \"json\",\n                success: function(data) {\n                    var server_error, filter_error, row_primary_key, total_rows, page_data, page_data_len, v,\n                        columns = s.columns,\n                        col_len = columns.length,\n                        column, c;\n\n                    server_error = data[\"error\"];\n                    if(server_error != null) {\n                        err_msg = \"ERROR: \" + server_error;\n                        elem.html('<span style=\"color: red;\">' + err_msg + '</span>');\n                        elem.triggerHandler(\"onDatagridError\", {err_code: \"server_error\", err_description: server_error});\n                        $.error(err_msg);\n                    }\n\n                    if(s.useFilters) {\n                        var elem_filter_rules = $(\"#\" + filter_rules_id);\n                        filter_error = data[\"filter_error\"];\n                        if(filter_error[\"error_message\"] != null) {\n                            elem_filter_rules.jui_filter_rules(\"markRuleAsError\", filter_error[\"element_rule_id\"], true);\n                            elem_filter_rules.triggerHandler(\"onValidationError\", {err_code: \"filter_validation_server_error\", err_description: filter_error[\"error_message\"]});\n                            $.error(filter_error[\"error_message\"]);\n                        }\n                    }\n\n                    total_rows = data[\"total_rows\"];\n                    page_data = data[\"page_data\"];\n                    page_data_len = page_data.length;\n\n                    elem.data(pluginStatus)[\"total_rows\"] = total_rows;\n\n                    row_primary_key = s.row_primary_key;\n\n                    if(s.debug_mode == \"yes\") {\n                        elem.triggerHandler(\"onDebug\", {debug_message: data[\"debug_message\"]});\n                    }\n\n                    // replace null with empty string\n                    if(page_data_len > 0) {\n                        for(v = 0; v < page_data_len; v++) {\n                            for(c = 0; c < col_len; c++) {\n                                column = columns[c];\n                                if(column_is_visible(column)) {\n                                    if(page_data[v][column[\"field\"]] == null) {\n                                        page_data[v][column[\"field\"]] = '';\n                                    }\n                                }\n                            }\n                        }\n                    }\n\n                    // create data table\n                    var pageNum = parseInt(s.pageNum),\n                        rowsPerPage = parseInt(s.rowsPerPage),\n                        sortingIndicator,\n                        row_id_html, i, row, tbl_html, row_index,\n                        offset = ((pageNum - 1) * rowsPerPage);\n\n                    tbl_html = '<thead>';\n                    row_id_html = (row_primary_key ? ' id=\"' + table_id + '_tr_0\"' : '');\n                    tbl_html += '<tr' + row_id_html + '>';\n\n                    if(s.showRowNumbers) {\n                        tbl_html += '<th class=\"' + s.commonThClass + '\">' + rsc_bs_dg.row_index_header + '</th>';\n                    }\n\n                    for(i in s.columns) {\n                        if(column_is_visible(s.columns[i])) {\n                            sortingIndicator = \"\";\n                            if(s.showSortingIndicator) {\n                                var sorting_type = \"none\";\n                                for(var e in s.sorting) {\n                                    if(s.sorting[e].field == s.columns[i].field) {\n                                        sorting_type = s.sorting[e].order;\n                                        break;\n                                    }\n                                }\n                                switch(sorting_type) {\n                                    case \"ascending\":\n                                        sortingIndicator = '&nbsp;<span class=\"' + s.sortingIndicatorAscClass + '\"></span>';\n                                        break;\n                                    case \"descending\":\n                                        sortingIndicator = '&nbsp;<span class=\"' + s.sortingIndicatorDescClass + '\"></span>';\n                                        break;\n                                    default:\n                                        sortingIndicator = '';\n                                }\n                            }\n                            tbl_html += '<th class=\"' + s.commonThClass + '\">' + s.columns[i].header + sortingIndicator + '</th>';\n                        }\n                    }\n                    tbl_html += '</tr>';\n                    tbl_html += '</thead>';\n\n                    tbl_html += '<tbody>';\n                    for(row in page_data) {\n\n                        row_id_html = (row_primary_key ? ' id=\"' + table_id + '_tr_' + page_data[row][row_primary_key] + '\"' : '');\n                        tbl_html += '<tr' + row_id_html + '>';\n\n                        if(s.showRowNumbers) {\n                            row_index = offset + parseInt(row) + 1;\n                            tbl_html += '<td>' + row_index + '</td>';\n                        }\n\n                        for(i in s.columns) {\n                            if(column_is_visible(s.columns[i])) {\n                                tbl_html += '<td>' + page_data[row][s.columns[i].field] + '</td>';\n                            }\n                        }\n\n                        tbl_html += '</tr>';\n                    }\n                    tbl_html += '<tbody>';\n\n                    elem_table.html(tbl_html);\n\n                    // refresh pagination (if needed)\n                    if(refresh_pag) {\n                        elem_pagination.bs_pagination({\n                            currentPage: s.pageNum,\n                            totalPages: Math.ceil(total_rows / s.rowsPerPage),\n                            totalRows: total_rows\n                        });\n                    }\n\n                    // no results\n                    if(total_rows == 0) {\n                        elem_pagination.hide();\n                        elem_no_results.show();\n                    } else {\n                        elem_pagination.show();\n                        elem_no_results.hide();\n                    }\n\n                    // apply given styles ------------------------------------------\n                    var col_index = s.showRowNumbers ? 1 : 0,\n                        headerClass = \"\", dataClass = \"\";\n                    for(i in s.columns) {\n                        if(column_is_visible(s.columns[i])) {\n                            headerClass = \"\", dataClass = \"\";\n                            if(columns[i].hasOwnProperty(\"headerClass\")) {\n                                headerClass = columns[i][\"headerClass\"];\n                            }\n                            if(columns[i].hasOwnProperty(\"dataClass\")) {\n                                dataClass = columns[i][\"dataClass\"];\n                            }\n                            methods.setPageColClass.call(elem, col_index, headerClass, dataClass);\n                            col_index++;\n                        }\n                    }\n\n                    // apply row selections ----------------------------------------\n                    if(s.row_primary_key && s.selected_ids.length > 0) {\n\n                        if(s.rowSelectionMode == \"single\" || s.rowSelectionMode == \"multiple\") {\n                            var row_prefix_len = (table_id + \"_tr_\").length,\n                                row_id, idx;\n                            $(\"#\" + table_id + \" tbody tr\").each(function() {\n                                row_id = parseInt($(this).attr(\"id\").substr(row_prefix_len));\n                                idx = methods.selectedRows.call(elem, \"selected_index\", row_id);\n                                if(idx > -1) {\n                                    methods.selectedRows.call(elem, \"mark_selected\", row_id);\n                                }\n                            });\n                        }\n                    }\n\n                    // update selected rows counter\n                    methods.selectedRows.call(elem, \"update_counter\");\n\n                    // trigger event onDisplay\n                    elem.triggerHandler(\"onDisplay\");\n\n                }\n            });\n\n            return res;\n\n        },\n\n        /**\n         * Get datagrid snapshot (current status: row selections, pagination, filters)\n         * @returns {*}\n         */\n        takeSnapshot: function() {\n            var elem = this;\n            return $.extend(true, {}, methods.getAllOptions.call(elem));\n        },\n\n        /**\n         * Restore taken snaphot\n         * @param {obj} params\n         */\n        restoreSnapshot: function(params) {\n            var elem = this;\n            elem.removeData(pluginName);\n            methods.init.call(elem, params);\n        }\n\n    };\n\n    /* private methods ------------------------------------------------------ */\n\n    /**\n     * @lends _private_methods\n     */\n\n    /**\n     * Create element id\n     * @function\n     * @param prefix\n     * @param plugin_container_id\n     * @return {*}\n     */\n    var create_id = function(prefix, plugin_container_id) {\n        return prefix + plugin_container_id;\n    };\n\n    /**\n     * Check if column is visible (utility function)\n     *\n     * @param {object} column\n     * @returns {boolean}\n     */\n    var column_is_visible = function(column) {\n        var visible = \"visible\";\n        return !column.hasOwnProperty(visible) || (column.hasOwnProperty(visible) && column[visible] == \"yes\");\n    };\n\n    /**\n     * Set column visible property (utility function)\n     *\n     * @param {object} column\n     * @param {boolean} status\n     */\n    var set_column_visible = function(column, status) {\n        var visible = \"visible\";\n        if(status) {\n            if(column.hasOwnProperty(visible)) {\n                delete column[visible];\n            }\n        } else {\n            column[visible] = \"no\";\n        }\n    };\n\n    /**\n     * Get column header (utility function)\n     *\n     * @param {object} column\n     * @returns {string}\n     */\n    var get_column_header = function(column) {\n        return column.hasOwnProperty(\"header\") ? column[\"header\"] : column[\"field\"];\n    };\n\n    /**\n     * Get sorting name (utility function)\n     *\n     * @param {object} sorting\n     * @returns {string}\n     */\n    var get_sorting_name = function(sorting) {\n        return sorting.hasOwnProperty(\"sortingName\") ? sorting[\"sortingName\"] : sorting[\"field\"];\n    };\n\n    /**\n     * Check if column is sortable (utility function)\n     *\n     * @param {object} column\n     * @returns {boolean}\n     */\n    var column_is_sortable = function(column) {\n        return !column.hasOwnProperty(\"sortable\") || (column.hasOwnProperty(\"sortable\") && column[\"sortable\"] == \"yes\");\n    };\n\n    /**\n     * Check if column is function (utility function)\n     *\n     * @param {object} column\n     * @returns {boolean}\n     */\n    var column_is_function = function(column) {\n        var is_function = \"is_function\";\n        return column.hasOwnProperty(is_function) && column[is_function] == \"yes\";\n    };\n\n    /**\n     * Move array element from fromIndex to toIndex\n     * @param arr\n     * @param fromIndex\n     * @param toIndex\n     */\n    var array_move = function(arr, fromIndex, toIndex) {\n        var element = arr[fromIndex];\n        arr.splice(fromIndex, 1);\n        arr.splice(toIndex, 0, element);\n    };\n\n    /**\n     * bs_grid - datagrid jQuery plugin.\n     *\n     * @class bs_grid\n     * @memberOf $.fn\n     */\n    $.fn.bs_grid = function(method) {\n\n        if(this.size() != 1) {\n            var err_msg = \"You must use this plugin (\" + pluginName + \") with a unique element (at once)\";\n            this.html('<span style=\"color: red;\">' + 'ERROR: ' + err_msg + '</span>');\n            $.error(err_msg);\n        }\n\n        // Method calling logic\n        if(methods[method]) {\n            return methods[ method ].apply(this, Array.prototype.slice.call(arguments, 1));\n        } else if(typeof method === \"object\" || !method) {\n            return methods.init.apply(this, arguments);\n        } else {\n            $.error(\"Method \" + method + \" does not exist on jQuery.\" + pluginName);\n        }\n\n    };\n\n})(jQuery);"
  },
  {
    "path": "localization/el.bs2.js",
    "content": "/**\n * bs_grid v0.9.2 localization - GREEK (bootstrap 2 version) *******************\n *\n * DO NOT CHANGE this file, as it will be overwritten in next update.\n * To use different values, write and use a similar structure js file.\n *\n */\nvar rsc_bs_dg = {\n\n    columns: \"Πεδία\",\n    columns_show_row_numbers: \"Αρίθμηση σειρών\",\n    columns_default: \"Προεπιλεγμένα\",\n\n    sorting: \"Ταξινόμηση\",\n    sort_ascending: \"<i class='icon-chevron-up'></i>&nbsp;&nbsp;\",\n    sort_descending: \"<i class='icon-chevron-down'></i>&nbsp;&nbsp;\",\n    sort_none: \"<i class='icon-minus'></i>&nbsp;&nbsp;\",\n    sorting_default: \"Προεπιλεγμένη\",\n\n    filters: \"Φίλτρα\",\n    filters_apply: \"Εφαρμογή\",\n    filters_reset: \"Απαλοιφή\",\n\n    select: \"Επιλογή\",\n    select_all_in_page: \"Όλα στη σελίδα\",\n    deselect_all_in_page: \"Κανένα στη σελίδα\",\n    select_inverse_in_page: \"Αναστροφή\",\n    deselect_all: \"Κανένα επιλεγμένο\",\n\n    row_index_header: \"#\",\n    no_records_found: \"Δεν βρέθηκαν εγγραφές\"\n};"
  },
  {
    "path": "localization/el.js",
    "content": "/**\n * bs_grid v0.9.2 localization - GREEK *****************************************\n *\n * DO NOT CHANGE this file, as it will be overwritten in next update.\n * To use different values, write and use a similar structure js file.\n *\n */\nvar rsc_bs_dg = {\n\n    columns: \"Πεδία\",\n    columns_show_row_numbers: \"Αρίθμηση σειρών\",\n    columns_default: \"Προεπιλεγμένα\",\n\n    sorting: \"Ταξινόμηση\",\n    sort_ascending: \"<i class='glyphicon glyphicon-chevron-up'></i>&nbsp;&nbsp;\",\n    sort_descending: \"<i class='glyphicon glyphicon-chevron-down'></i>&nbsp;&nbsp;\",\n    sort_none: \"<i class='glyphicon glyphicon-minus'></i>&nbsp;&nbsp;\",\n    sorting_default: \"Προεπιλεγμένη\",\n\n    filters: \"Φίλτρα\",\n    filters_apply: \"Εφαρμογή\",\n    filters_reset: \"Απαλοιφή\",\n\n    select: \"Επιλογή\",\n    select_all_in_page: \"Όλα στη σελίδα\",\n    deselect_all_in_page: \"Κανένα στη σελίδα\",\n    select_inverse_in_page: \"Αναστροφή\",\n    deselect_all: \"Κανένα επιλεγμένο\",\n\n    row_index_header: \"#\",\n    no_records_found: \"Δεν βρέθηκαν εγγραφές\"\n\n\n};"
  },
  {
    "path": "localization/en.bs2.js",
    "content": "/**\n * bs_grid v0.9.2 localization - ENGLISH (bootstrap 2 version) *****************\n *\n * DO NOT CHANGE this file, as it will be overwritten in next update.\n * To use different values, write and use a similar structure js file.\n *\n */\nvar rsc_bs_dg = {\n\n    columns: \"Columns\",\n    columns_show_row_numbers: \"Row numbers\",\n    columns_default: \"Default\",\n\n    sorting: \"Sorting\",\n    sort_ascending: \"<i class='icon-chevron-up'></i>&nbsp;&nbsp;\",\n    sort_descending: \"<i class='icon-chevron-down'></i>&nbsp;&nbsp;\",\n    sort_none: \"<i class='icon-minus'></i>&nbsp;&nbsp;\",\n    sorting_default: \"Default\",\n\n    filters: \"Filters\",\n    filters_apply: \"Apply\",\n    filters_reset: \"Reset\",\n\n    select: \"Select\",\n    select_all_in_page: \"All in page\",\n    deselect_all_in_page: \"None in page\",\n    select_inverse_in_page: \"Inverse\",\n    deselect_all: \"Deselect all\",\n\n    row_index_header: \"#\",\n    no_records_found: \"No records found\"\n};"
  },
  {
    "path": "localization/en.js",
    "content": "/**\n * bs_grid v0.9.2 localization - ENGLISH ***************************************\n *\n * DO NOT CHANGE this file, as it will be overwritten in next update.\n * To use different values, write and use a similar structure js file.\n *\n */\nvar rsc_bs_dg = {\n\n    columns: \"Columns\",\n    columns_show_row_numbers: \"Row numbers\",\n    columns_default: \"Default\",\n\n    sorting: \"Sorting\",\n    sort_ascending: \"<i class='glyphicon glyphicon-chevron-up'></i>&nbsp;&nbsp;\",\n    sort_descending: \"<i class='glyphicon glyphicon-chevron-down'></i>&nbsp;&nbsp;\",\n    sort_none: \"<i class='glyphicon glyphicon-minus'></i>&nbsp;&nbsp;\",\n    sorting_default: \"Default\",\n\n    filters: \"Filters\",\n    filters_apply: \"Apply\",\n    filters_reset: \"Reset\",\n\n    select: \"Select\",\n    select_all_in_page: \"All in page\",\n    deselect_all_in_page: \"None in page\",\n    select_inverse_in_page: \"Inverse\",\n    deselect_all: \"Deselect all\",\n\n    row_index_header: \"#\",\n    no_records_found: \"No records found\"\n};"
  },
  {
    "path": "localization/ru.bs2.js",
    "content": "/**\n * bs_grid v0.9.2 localization - ENGLISH (bootstrap 2 version) *****************\n *\n * DO NOT CHANGE this file, as it will be overwritten in next update.\n * To use different values, write and use a similar structure js file.\n *\n */\nvar rsc_bs_dg = {\n\n    columns: \"Столбцы\",\n    columns_show_row_numbers: \"Номера строк\",\n    columns_default: \"По умолчанию\",\n\n    sorting: \"Сортировка\",\n    sort_ascending: \"<i class='icon-chevron-up'></i>&nbsp;&nbsp;\",\n    sort_descending: \"<i class='icon-chevron-down'></i>&nbsp;&nbsp;\",\n    sort_none: \"<i class='icon-minus'></i>&nbsp;&nbsp;\",\n    sorting_default: \"По умолчанию\",\n\n    filters: \"Фильтры\",\n    filters_apply: \"Применить\",\n    filters_reset: \"Сброс\",\n\n    select: \"Выбор\",\n    select_all_in_page: \"Всё на странице\",\n    deselect_all_in_page: \"Ничего на странице\",\n    select_inverse_in_page: \"Обратить\",\n    deselect_all: \"Снять выделение\",\n\n    row_index_header: \"№\",\n    no_records_found: \"Ничего не найдено\"\n};"
  },
  {
    "path": "localization/ru.js",
    "content": "/**\n * bs_grid v0.9.2 localization - ENGLISH ***************************************\n *\n * DO NOT CHANGE this file, as it will be overwritten in next update.\n * To use different values, write and use a similar structure js file.\n *\n */\nvar rsc_bs_dg = {\n\n    columns: \"Столбцы\",\n    columns_show_row_numbers: \"Номера строк\",\n    columns_default: \"По умолчанию\",\n\n    sorting: \"Сортировка\",\n    sort_ascending: \"<i class='glyphicon glyphicon-chevron-up'></i>&nbsp;&nbsp;\",\n    sort_descending: \"<i class='glyphicon glyphicon-chevron-down'></i>&nbsp;&nbsp;\",\n    sort_none: \"<i class='glyphicon glyphicon-minus'></i>&nbsp;&nbsp;\",\n    sorting_default: \"По умолчанию\",\n\n    filters: \"Фильтры\",\n    filters_apply: \"Применить\",\n    filters_reset: \"Сброс\",\n\n    select: \"Выбор\",\n    select_all_in_page: \"Всё на странице\",\n    deselect_all_in_page: \"Ничего на странице\",\n    select_inverse_in_page: \"Обратить\",\n    deselect_all: \"Снять выделение\",\n\n    row_index_header: \"№\",\n    no_records_found: \"Ничего не найдено\"\n};"
  },
  {
    "path": "server_side/php/ajax_page_data.dist.php",
    "content": "<?php\n/**\n * ajax_page_data.dist.php, bs_grid ajax fetch page data template script\n *\n * Sample php file getting totalrows and page data\n *\n * Da Capo database wrapper is required https://github.com/pontikis/dacapo\n *\n * @version 0.9.2 (28 May 2014)\n * @author Christos Pontikis http://pontikis.net\n * @license  http://opensource.org/licenses/MIT MIT license\n **/\n\n// PREVENT DIRECT ACCESS (OPTIONAL) --------------------------------------------\n$isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) AND\nstrtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';\nif(!$isAjax) {\n\tprint 'Access denied - not an AJAX request...' . ' (' . __FILE__ . ')';\n\texit;\n}\n\n// REQUIRED --------------------------------------------------------------------\nrequire_once '/path/to/dacapo.php';                                 // CONFIGURE\nrequire_once '/path/to/jui_filter_rules.php';                       // CONFIGURE\nrequire_once '/path/to/bs_grid.php';                                // CONFIGURE\n\n// create new datasource                                            // CONFIGURE\n$db_settings = array(\n\t'rdbms' => 'MYSQLi',\n\t'db_server' => 'localhost',\n\t'db_user' => 'DB_USER_HERE',\n\t'db_passwd' => 'DB_PASS_HERE',\n\t'db_name' => 'DB_NAME',\n\t'db_port' => '3306',\n\t'charset' => 'utf8',\n\t'use_pst' => true,\n\t'pst_placeholder' => 'question_mark'\n);\n$ds = new dacapo($db_settings, null);\n\n$page_settings = array(\n\t\"selectCountSQL\" => \"SQL_HERE\",                                 // CONFIGURE\n\t\"selectSQL\" => \"SQL_HERE\",                                      // CONFIGURE\n\t\"page_num\" => $_POST['page_num'],\n\t\"rows_per_page\" => $_POST['rows_per_page'],\n\t\"columns\" => $_POST['columns'],\n\t\"sorting\" =>  isset($_POST['sorting']) ? $_POST['sorting'] : array(),\n\t\"filter_rules\" => isset($_POST['filter_rules']) ? $_POST['filter_rules'] : array()\n);\n\n$jfr = new jui_filter_rules($ds);\n$jdg = new bs_grid($ds, $jfr, $page_settings, $_POST['debug_mode'] == \"yes\" ? true : false);\n\n$data = $jdg->get_page_data();\n\n// data conversions (if necessary)\nforeach($data['page_data'] as $key => $row) {\n\t// your code here\n}\n\necho json_encode($data);"
  },
  {
    "path": "server_side/php/bs_grid.php",
    "content": "<?php\n\n/**\n * bs_grid, helper class for jquery.bs_grid plugin, handles server operations (mainly through AJAX requests).\n *\n * see ajax_page_data.dist.php for usage instructions\n *\n * Da Capo database wrapper is required https://github.com/pontikis/dacapo\n * jui_filter_rules is required  https://github.com/pontikis/jui_filter_rules\n *\n * @version 0.9.2 (28 May 2014)\n * @author Christos Pontikis http://www.pontikis.net\n * @license  http://opensource.org/licenses/MIT MIT license\n **/\nclass bs_grid {\n\n\t/** @var string Last error occured */\n\tprivate $last_error;\n\t/** @var string Last filter error occured */\n\tprivate $last_filter_error;\n\t/** @var string Debug message */\n\tprivate $debug_message;\n\n\t/**\n\t * Constructor\n\t *\n\t * @param dacapo $ds\n\t * @param jui_filter_rules $jfr\n\t * @param $page_settings\n\t * @param bool $debug_mode\n\t */\n\tpublic function __construct(dacapo $ds, jui_filter_rules $jfr, $page_settings, $debug_mode = false) {\n\t\t// initialize\n\t\t$this->ds = $ds;\n\t\t$this->jfr = $jfr;\n\t\t$this->page_settings = $page_settings;\n\t\t$this->debug_mode = $debug_mode;\n\n\t\t$this->last_error = null;\n\t\t$this->last_filter_error = array();\n\t\t$this->debug_message = array();\n\t}\n\n\t/**\n\t * Get last error\n\t *\n\t * @return null|string\n\t */\n\tpublic function get_last_error() {\n\t\treturn $this->last_error;\n\t}\n\n\t/**\n\t * Get last filter error\n\t *\n\t * @return array|string\n\t */\n\tpublic function get_last_filter_error() {\n\t\treturn $this->last_filter_error;\n\t}\n\n\t/**\n\t * Get debug message\n\t *\n\t * @return array|string\n\t */\n\tpublic function get_debug_message() {\n\t\treturn $this->debug_message;\n\t}\n\n\t/**\n\t * Gets whereSQL and bind_params array using jui_filter_rules class\n\t *\n\t * @param $filter_rules\n\t * @return array\n\t */\n\tpublic function get_whereSQL($filter_rules) {\n\n\t\t$ds = $this->ds;\n\t\t$jfr = $this->jfr;\n\t\t$last_jfr_error = array(\n\t\t\t'element_rule_id' => null,\n\t\t\t'error_message' => null\n\t\t);\n\n\t\tif(count($filter_rules) == 0) {\n\t\t\t$result = array('sql' => '', 'bind_params' => array());\n\t\t} else {\n\t\t\t$result = $jfr->parse_rules($filter_rules);\n\n\t\t\t$last_jfr_error = $jfr->get_last_error();\n\t\t\tif(!is_null($last_jfr_error['error_message'])) {\n\t\t\t\t$result = $last_jfr_error;\n\t\t\t}\n\t\t}\n\n\t\tif($this->debug_mode) {\n\t\t\tarray_push($this->debug_message, 'WHERE  SQL: ' . $result['sql']);\n\t\t\tarray_push($this->debug_message, 'BIND PARAMS: ' . print_r($result['bind_params'], true));\n\t\t\tif($ds->use_pst) {\n\t\t\t\t$bind_params_type = '';\n\t\t\t\tforeach($result[\"bind_params\"] as $bind_param) {\n\t\t\t\t\t$bind_params_type .= gettype($bind_param) . ' ';\n\t\t\t\t}\n\t\t\t\tarray_push($this->debug_message, 'BIND PARAMS TYPE: ' . $bind_params_type);\n\t\t\t}\n\t\t\tarray_push($this->debug_message, 'PREPARED STATEMENTS: ' . ($ds->use_pst ? \"yes\" : \"no\"));\n\t\t\tif(count($filter_rules) > 0) {\n\t\t\t\tif(!is_null($last_jfr_error['error_message'])) {\n\t\t\t\t\tarray_push($this->debug_message, 'FILTER ERROR: ' . print_r($last_jfr_error['error_message'], true));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * Get sorting SQL (ORDER BY clause)\n\t *\n\t * @param array $sorting\n\t * @return string\n\t */\n\tprivate function get_sortingSQL($sorting) {\n\t\t$sortingSQL = '';\n\t\tforeach($sorting as $sort) {\n\t\t\tif($sort['order'] == 'ascending') {\n\t\t\t\t$sortingSQL .= $sort['field'] . ' ASC, ';\n\t\t\t} else if($sort['order'] == 'descending') {\n\t\t\t\t$sortingSQL .= ' ' . $sort['field'] . ' DESC, ';\n\t\t\t}\n\t\t}\n\t\t$len = mb_strlen($sortingSQL);\n\t\tif($len > 0) {\n\t\t\t$sortingSQL = ' ORDER BY ' . substr($sortingSQL, 0, $len - 2) . ' ';\n\t\t}\n\n\t\tif($this->debug_mode) {\n\t\t\tarray_push($this->debug_message, 'sortingSQL: ' . $sortingSQL);\n\t\t}\n\t\treturn $sortingSQL;\n\t}\n\n\t/**\n\t * Gets total rows count\n\t *\n\t * @param string $selectCountSQL\n\t * @param string $whereSQL\n\t * @param array $a_bind_params\n\t * @return int|bool Total rows or false\n\t */\n\tpublic function get_total_rows($selectCountSQL, $whereSQL, $a_bind_params) {\n\n\t\t$ds = $this->ds;\n\n\t\t$sql = $selectCountSQL . ' ' . $whereSQL;\n\t\t$query_options = array(\n\t\t\t'get_row' => true\n\t\t);\n\t\t$res = $ds->select($sql, $a_bind_params, $query_options);\n\t\tif(!$res) {\n\t\t\t$this->last_error = $ds->last_error;\n\t\t\t$total_rows = false;\n\t\t} else {\n\t\t\t$rs = $ds->data;\n\t\t\t$total_rows = $rs['totalrows'];\n\t\t}\n\n\t\tif($this->debug_mode) {\n\t\t\tarray_push($this->debug_message, 'RDBMS: ' . $ds->rdbms);\n\t\t\tarray_push($this->debug_message, 'selectCountSQL: ' . $selectCountSQL);\n\t\t\tarray_push($this->debug_message, 'total_rows: ' . $total_rows);\n\t\t}\n\n\t\treturn $total_rows;\n\t}\n\n\n\t/**\n\t * @param $selectSQL\n\t * @param $whereSQL\n\t * @param $a_bind_params\n\t * @param $sortingSQL\n\t * @param $page_num\n\t * @param $rows_per_page\n\t * @return bool|null\n\t */\n\tpublic function fetch_page_data($selectSQL, $whereSQL, $a_bind_params, $sortingSQL, $page_num, $rows_per_page) {\n\n\t\t$ds = $this->ds;\n\n\t\t$limitSQL = $ds->limit($rows_per_page, ($page_num - 1) * $rows_per_page);\n\n\t\t$sql = $selectSQL . ' ' . $whereSQL . ' ' . $sortingSQL . ' ' . $limitSQL;\n\t\t$res = $ds->select($sql, $a_bind_params);\n\t\tif(!$res) {\n\t\t\t$this->last_error = $ds->last_error;\n\t\t\t$a_data = false;\n\t\t} else {\n\t\t\t$a_data = $ds->data;\n\t\t}\n\n\t\tif($this->debug_mode) {\n\t\t\tarray_push($this->debug_message, 'selectSQL: ' . $selectSQL);\n\t\t}\n\t\treturn $a_data;\n\n\t}\n\n\n\t/**\n\t * Get page data\n\t *\n\t * @return array\n\t */\n\tpublic function get_page_data() {\n\n\t\t$fixed_where = null;\n\t\tif(array_key_exists('fixed_where', $this->page_settings)) {\n\t\t\t$fixed_where = $this->page_settings['fixed_where'];\n\t\t}\n\n\t\t$fixed_bind_params = null;\n\t\tif(array_key_exists('fixed_where', $this->page_settings)) {\n\t\t\t$fixed_bind_params = $this->page_settings['fixed_bind_params'];\n\t\t}\n\n\n\t\t$total_rows = null;\n\t\t$a_data = null;\n\n\t\t// initialize\n\t\t$result = array(\n\t\t\t'total_rows' => null,\n\t\t\t'page_data' => null,\n\t\t\t'error' => null,\n\t\t\t'filter_error' => array(),\n\t\t\t'debug_message' => array()\n\t\t);\n\n\t\t$where = $this->get_whereSQL($this->page_settings['filter_rules']);\n\t\tif(array_key_exists('error_message', $where)) {\n\t\t\t$this->last_filter_error = $where;\n\t\t} else {\n\n\t\t\tif($fixed_where) {\n\t\t\t\t$tmp_where = $fixed_where;\n\t\t\t\tif($where['sql']) {\n\t\t\t\t\t$tmp_where .= ' AND (' . mb_substr($where['sql'], 6) . ')';\n\t\t\t\t}\n\t\t\t\t$where['sql'] = $tmp_where;\n\t\t\t}\n\n\t\t\tif($fixed_bind_params) {\n\t\t\t\t$reversed = array_reverse($fixed_bind_params);\n\t\t\t\tforeach($reversed as $param) {\n\t\t\t\tarray_unshift($where['bind_params'], $param);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$total_rows = $this->get_total_rows($this->page_settings['selectCountSQL'],\n\t\t\t\t$where['sql'], $where['bind_params']);\n\n\t\t\tif($total_rows !== false) {\n\n\t\t\t\t// calculate sortingSQL\n\t\t\t\t$sortingSQL = $this->get_sortingSQL($this->page_settings['sorting']);\n\n\t\t\t\t$a_data = $this->fetch_page_data(\n\t\t\t\t\t$this->page_settings['selectSQL'],\n\t\t\t\t\t$where['sql'],\n\t\t\t\t\t$where['bind_params'],\n\t\t\t\t\t$sortingSQL,\n\t\t\t\t\t$this->page_settings['page_num'],\n\t\t\t\t\t$this->page_settings['rows_per_page']);\n\t\t\t}\n\t\t}\n\n\t\t$result['total_rows'] = $total_rows;\n\t\t$result['page_data'] = $a_data;\n\t\t$result['error'] = $this->get_last_error();\n\t\t$result['filter_error'] = $this->get_last_filter_error();\n\t\t$result['debug_message'] = $this->get_debug_message();\n\n\t\treturn $result;\n\t}\n\n\n\t/**\n\t * Get all data\n\t *\n\t * @return array\n\t */\n\tpublic function get_all_data() {\n\n\t\t$fixed_where = null;\n\t\tif(array_key_exists('fixed_where', $this->page_settings)) {\n\t\t\t$fixed_where = $this->page_settings['fixed_where'];\n\t\t}\n\n\t\t$fixed_bind_params = null;\n\t\tif(array_key_exists('fixed_where', $this->page_settings)) {\n\t\t\t$fixed_bind_params = $this->page_settings['fixed_bind_params'];\n\t\t}\n\n\t\t$total_rows = null;\n\t\t$a_data = null;\n\n\t\t// initialize\n\t\t$result = array(\n\t\t\t'total_rows' => null,\n\t\t\t'all_data' => null,\n\t\t\t'error' => null,\n\t\t\t'filter_error' => array(),\n\t\t\t'debug_message' => array()\n\t\t);\n\n\t\t$where = $this->get_whereSQL($this->page_settings['filter_rules']);\n\t\tif(array_key_exists('error_message', $where)) {\n\t\t\t$this->last_filter_error = $where;\n\t\t} else {\n\n\t\t\tif($fixed_where) {\n\t\t\t\t$tmp_where = $fixed_where;\n\t\t\t\tif($where['sql']) {\n\t\t\t\t\t$tmp_where .= ' AND (' . mb_substr($where['sql'], 6) . ')';\n\t\t\t\t}\n\t\t\t\t$where['sql'] = $tmp_where;\n\t\t\t}\n\n\t\t\tif($fixed_bind_params) {\n\t\t\t\t$reversed = array_reverse($fixed_bind_params);\n\t\t\t\tforeach($reversed as $param) {\n\t\t\t\t\tarray_unshift($where['bind_params'], $param);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$total_rows = $this->get_total_rows($this->page_settings['selectCountSQL'],\n\t\t\t\t$where['sql'], $where['bind_params']);\n\n\t\t\tif($total_rows !== false) {\n\n\t\t\t\t// calculate sortingSQL\n\t\t\t\t$sortingSQL = $this->get_sortingSQL($this->page_settings['sorting']);\n\n\t\t\t\t$ds = $this->ds;\n\n\t\t\t\t$sql = $this->page_settings['selectSQL'] . ' ' . $where['sql']. ' ' . $sortingSQL;\n\t\t\t\t$res = $ds->select($sql, $where['bind_params']);\n\t\t\t\tif(!$res) {\n\t\t\t\t\t$this->last_error = $ds->last_error;\n\t\t\t\t\t$a_data = false;\n\t\t\t\t} else {\n\t\t\t\t\t$a_data = $ds->data;\n\t\t\t\t}\n\n\t\t\t\tif($this->debug_mode) {\n\t\t\t\t\tarray_push($this->debug_message, 'selectSQL: ' . $this->page_settings['selectSQL']);\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\t$result['total_rows'] = $total_rows;\n\t\t$result['all_data'] = $a_data;\n\t\t$result['error'] = $this->get_last_error();\n\t\t$result['filter_error'] = $this->get_last_filter_error();\n\t\t$result['debug_message'] = $this->get_debug_message();\n\n\t\treturn $result;\n\t}\n\n\n}"
  }
]