/*!
 * Bootstrap Data Table Plugin v1.5.5
 *
 * Author: Jeff Dupont
 * ==========================================================
 * Copyright 2012
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ==========================================================
 */

;(function( $ ){

 /* DATATABLE CLASS DEFINITION
  * ========================== */
  var DataTable = function ( element, options ) {
    this.$element = $(element);
    this.options = options;
    this.pendingRequest = null;
    this.enabled = true;
    this.columns = [];
    this.rows = [];
    this.buttons = [];

    // this needs to be handled better
    this.localStorageId = "datatable_" + (options.id || options.url.replace(/\W/ig, '_'));

    // set the defaults for the column options array
    for(var column in this.options.columns) {
      // check sortable
      if(typeof this.options.columns[column].sortable === undefined)
        this.options.columns[column].sortable = true;
    }

    this.$default = this.$element.children().length ?
      this.$element.children() :
      $("<div></div>")
        .addClass("alert alert-danger")
        .html(i18n.no_results_found);

    this.$element.addClass("clearfix");

    // clear out the localStorage for this entry
    if(localStorage) {
      localStorage[this.localStorageId] = 'false';
    }

    if(this.options.tablePreRender && typeof this.options.tablePreRender === 'function')
      this.options.tablePreRender.call(this)

    // initialize the toolbar
    _initToolbar.call(this)

    if(this.options.autoLoad === true) this.render();
  };

  DataTable.prototype = {

      constructor: DataTable

    , render: function () {
        var o = this.options
          , $e = this.$element;

        // show loading
        this.loading( true );

        // reset the columns and rows
        this.columns = [];
        this.rows    = [];
        this.buttons = [];
        this.$wrapper    = undefined;
        this.$table      = undefined;
        this.$header     = undefined;
        this.$body       = undefined;
        this.$footer     = undefined;
        this.$pagination = undefined;

        if(this.$toolbar) this.$toolbar.remove();

        // top
        this.$top_details = $("<div></div>")
          .attr("id", "dt-top-details");
        // bottom
        this.$bottom_details = $("<div></div>")
          .attr("id", "dt-bottom-details");

        // localize the object
        var that = this;

        // pull in the data from the ajax call
        if (o.debug) console.log($.extend({}, o.post, {
                  currentPage: o.currentPage
                , perPage: o.perPage
                , sort: o.sort
                , filter: o.filter
              }) );
        if(o.url !== "") {
          var req_data = (typeof o.post === "function") ? o.post() : o.post;

          if(this.pendingRequest)
            this.pendingRequest.abort();

          this.pendingRequest = $.ajax({
              url: o.url
            , type: "GET"
            , dataType: "json"
            , data: $.extend({}, req_data, {
                  currentPage: o.currentPage
                , perPage: o.perPage
                , sortColumn: (o.sort.length > 0) ? o.sort[0][0] : null
                , sortOrder: (o.sort.length > 0) ? o.sort[0][1] : null
                , filter: o.filter
              })
            , success: function(res, status, xhr) {
                that.pendingRequest = null;

                if(o.dataAdapter)
                  res = o.dataAdapter(res, o);

                that.resultset = res;                  
                if(!res || res === undefined || !res.data || (res.data.length == 0 && !o.forceTable) ) {
                  showError.call(that, res);
                  return;
                }

                // clear out the current elements in the container
                $e.empty();

                // set the sort and filter configuration
                o.sort = res.sort;
                o.filter = res.filter;
                o.totalRows = res.totalRows;

                // set the current page if we're forcing it from the server
                if(res.currentPage) o.currentPage = parseInt(res.currentPage);

                // retrieve the saved columns
                _retrieveColumns.call(that, localStorage[that.localStorageId])

                // append the table
                $e.append(that.table());

                // append the detail boxes
                $e.prepend(that.$top_details);
                $e.append(that.$bottom_details);

                // render the rest of the table
                if(o.showHeader)        that.header();
                if(o.showFooter)        that.footer();

                // fill in the table body
                that.body();

                // render the pagination
                if(o.showTopPagination && that.pagination())
                  that.$top_details.append(that.pagination().clone(true));
                if(o.showPagination && that.pagination())
                  that.$bottom_details.append(that.pagination().clone(true));

                // update the details for the results
                if(! o.hideDetails)     that.details();

                // initialize the toolbar
                _initToolbar.call(that);

                // nearly complete... let the user apply any final adjustments
                if(o.tableCallback && typeof o.tableCallback === 'function')
                  o.tableCallback.call(that);

                that.loading( false );
              }
            , error: function(xhr, status, e) {
                that.pendingRequest = null;
                if(status !== "abort")
                  console.error("Datatable: error while loading data: " + e);
                showError.call(that, null, status, e);

                that.loading( false );
              }
          });
        }
      }

    , loading: function ( show ) {
        var $e = this.$element;
        var o = this.options;

        if(o.customLoading) {
          o.customLoading(this, show);
          return;
        }

        if(!this.$loading) {
          this.$loading = $("<div></div>")
            .css({
                position: 'absolute'
              , top: parseInt($e.position().top) + 5 + (o.loadingYOffset || 0)
              , left: parseInt($e.position().left) + parseInt($e.css("marginLeft")) + Math.floor($e.width() / 4)
              , width: Math.floor($e.width() / 2) + "px"
            })
            .append(
              $("<div></div>")
                .addClass("progress progress-striped")
                .append(
                  $("<div></div>")
                    .addClass("progress-bar progress-bar-info")
                    .attr('style', "width: 40%")
                )
            )
            .appendTo(document.body)
        }

        if(show) {
          $e.css({ opacity: 0.2 });
        }
        else {
          $e.css({ opacity: 1 });

          this.$loading.remove();
          this.$loading = undefined;
        }
      }

    , toolbar: function () {
        var o = this.options
          , $e = this.$element
          , that = this;

        this.$toolbar = $("<div></div>")
          .addClass("dt-toolbar btn-toolbar pull-right");

        this.$button_group = $("<div></div>")
          .addClass("btn-group")
          .appendTo(this.$toolbar);

        // add all the custom buttons
        for(var i = 0; i < o.buttons.length; i++) {
          this.buttons.push(o.buttons[i]);
        }

        // attach all buttons to the toolbar
        $.each(this.buttons, function() {
          that.$button_group.append(this);
        });

        // attach the toolbar to the section header
        if(o.sectionHeader) {
          this.$section_header = $(o.sectionHeader);
          this.$section_header.append(this.$toolbar);
        }
        else if(o.title !== '' && !this.$section_header) {
          this.$section_header = $("<h2></h2>")
            .html(o.title)
            .append(this.$toolbar);
          $e.before(this.$section_header);
        }
        else {
            if(!this.$toolbar_container) {
              this.$toolbar_container = $("<div></div>")
                .addClass('dt-toolbar-container clearfix')
            }
            $e.prepend(
              this.$toolbar_container
                .append(this.$toolbar)
            );
        }

        return this.$toolbar;
      }

    , details: function () {
        var o = this.options
          , res = this.resultset
          , start = 0
          , end = 0
          , that = this;

        start = (o.currentPage * o.perPage) - o.perPage + 1
        if(start < 1) start = 1;

        end = (o.currentPage * o.perPage);
        if(end > o.totalRows) end = o.totalRows;

        $('<div class="pull-left"><p>' + i18n.showing_x_to_y_rows.sformat(start, end, o.totalRows) + '</p></div>')
          .prependTo(this.$bottom_details);
      }

    , table: function () {
        var $e = this.$element
          , o = this.options;

        if(!this.$table_wrapper) {
          this.$wrapper = $("<div></div>")
            .addClass("dt-table-wrapper");
        }

        if (!this.$table) {
          this.$table = $('<table></table>')
            .addClass(o.class);
        }

        this.$wrapper.append(this.$table);
        return this.$wrapper;
      }

    , header: function () {
        var o = this.options
          , res = this.resultset;

        if(!this.$header) {
          this.$header = $('<thead></thead>');
          var row = $('<tr></tr>');

          // loop through the columns
          for(var column in o.columns) {
            var $cell = this.column(column)
              , colprop = $cell.data("column_properties");

            // attach the sort click event
            if(colprop.sortable && !colprop.custom)
              $cell.click(this, this.sort)
                .css({'cursor':'pointer'});

            for(var i = 0; i < o.sort.length; i++) {
              if(o.sort[i][0] == colprop.field) {
                if(o.sort[i][1] == "asc") {
                  $cell.append($(o.ascending));
                  colprop.sortOrder = "asc";
                }
                else if(o.sort[i][1] == "desc") {
                  $cell.append($(o.descending));
                  colprop.sortOrder = "desc";
                }
              }
            }

            row.append($cell);
            this.$header.append(row);
            this.columns.push($cell);
          }

          // any final user adjustments to the header
          if(o.headerCallback && typeof o.headerCallback === 'function')
            o.headerCallback.call(this);

          this.$table
            .append(this.$header);
        }
        return this.$header;
      }

    , footer: function () {
        var o = this.options
          , res = this.resultset

        if(!this.$footer) {
          this.$footer = $('<tfoot></tfoot>');

          // loop through the columns
          for(column in o.columns) {
            var $cell = $('<td></td>')

            $cell
              .data("cell_properties", o.columns[column])
              .addClass(o.columns[column].classname)

            this.$footer.append($cell);
          }

          // any final user adjustments to the footer
          if(o.footerCallback && typeof o.footerCallback === 'function')
            o.footerCallback.call(this, this.resultset.footer)

          this.$table
            .append(this.$footer);
        }
        return this.$footer;
      }

    , body: function () {
        var res = this.resultset
          , o = this.options;

        if(!this.$body) {
          this.$body = $('<tbody></tbody>');

          // loop through the results
          for(var i = 0; i < res.data.length; i++) {
            var row = this.row(res.data[i]);
            this.$body.append(row);
            this.rows.push(row);
          }

          if(o.showFilterRow) this.$body.prepend(this.filter());

          this.$table
            .append(this.$body);
        }
        return this.$body;
      }

    , filter: function () {
        var $row = $("<tr></tr>")
          , o = this.options
          , that = this;

        $row.addClass("dt-filter-row");

        // loop through the columns
        for(var column in o.columns) {
          var $cell = $("<td></td>")
            .addClass(o.columns[column].classname);

          if(o.columns[column].hidden) $cell.hide();

          if(o.columns[column].filter && o.columns[column].field) {
            $cell
              .append(
                $("<input/>")
                  .attr("name", "filter_" + o.columns[column].field)
                  .data("filter", o.columns[column].field)
                  .val(o.filter[o.columns[column].field] || "")
                  // .change(this, this.runFilter)
                  .change(function(e){
                    runFilter.call(this, that);
                  })
              );
          }

          $row.append($cell);
        }
        return $row;
      }

    , row: function ( rowdata ) {
        var $row = $("<tr></tr>")
          , o = this.options;

        // loop through the columns
        for(var column in o.columns) {
          var cell = this.cell( rowdata, column );
          $row.append(cell);
        }

        // callback for postprocessing on the row
        if(o.rowCallback && typeof o.rowCallback === "function")
          $row = o.rowCallback( $row, rowdata );

        return $row;
      }

    , cell: function ( data, column ) {
        var celldata = data[this.options.columns[column].field] || this.options.columns[column].custom
          , $cell = $('<td></td>')
          , o = this.options;

        // preprocess on the cell data for a column
        if(o.columns[column].callback && typeof o.columns[column].callback === "function")
          celldata = o.columns[column].callback.call( $cell, data, o.columns[column] )

        $cell
          .data("cell_properties", o.columns[column])
          .addClass(o.columns[column].classname)
          .append(celldata || "&nbsp;")

        if(o.columns[column].css) $cell.css(o.columns[column].css);

        if(o.columns[column].hidden) $cell.hide();

        return $cell;
      }

    , column: function ( column ) {
        var $cell = $('<th></th>')
          , o = this.options
          , classname = "dt-column_" + column + Math.floor((Math.random()*1000)+1);

        o.columns[column].classname = classname;

        $cell
          .data("column_properties", o.columns[column])
          .addClass(classname)
          .text(o.columns[column].title);

        if(o.columns[column].css) $cell.css(o.columns[column].css);

        if(o.columns[column].hidden) $cell.hide();

        return $cell;
      }

    , sort: function ( e ) {
        var colprop = $(this).data("column_properties")
          , that = e.data
          , o = e.data.options
          , found = false;

        colprop.sortOrder = colprop.sortOrder ? (colprop.sortOrder == "asc" ? "desc" : "") : "asc";

        if(o.allowMultipleSort) {
          // does the sort already exist?
          for(var i = 0; i < o.sort.length; i++) {
            if(o.sort[i][0] == colprop.field) {
              o.sort[i][1] = colprop.sortOrder;
              if(colprop.sortOrder === "") o.sort.splice(i,1);
              found = true;
            }
          }
          if(!found) o.sort.push([colprop.field, colprop.sortOrder]);
        }
        else {
          // clear out any current sorts
          o.sort = [];
          o.sort.push([colprop.field, colprop.sortOrder]);
        }
        if(o.debug) console.log(o.sort);

        that.render();
      }

    , pagination: function () {
        var $e = this.$element
          , that = this
          , o = this.options
          , res = this.resultset;

        // no paging needed
        if(o.perPage >= res.totalRows) return;

        if(!this.$pagination) {
          this.$pagination = $("<div></div>").addClass("pull-right");

          // how many pages?
          o.pageCount = Math.ceil(res.totalRows / o.perPage);

          // setup the pager container and the quick page buttons
          var $pager = $("<ul></ul>").addClass("pagination")
            , $first = $("<li></li>").append(
                $("<a></a>")
                  .attr("href", "#")
                  .data("page", 1)
                  .html("&laquo;")
                  .click(function() {
                    o.currentPage = 1
                    that.render();
                    return false;
                  })
              )
            , $previous = $("<li></li>").append(
                $("<a></a>")
                  .attr("href", "#")
                  .data("page", o.currentPage - 1)
                  .html("&lt;")
                  .click(function() {
                    o.currentPage -= 1
                    o.currentPage = o.currentPage >= 1 ? o.currentPage : 1
                    that.render();
                    return false;
                  })
              )
            , $next = $("<li></li>").append(
                $("<a></a>")
                  .attr("href", "#")
                  .data("page", o.currentPage + 1)
                  .html("&gt;")
                  .click(function() {
                    o.currentPage += 1
                    o.currentPage = o.currentPage <= o.pageCount? o.currentPage : o.pageCount
                    that.render();
                    return false;
                  })
              )
            , $last = $("<li></li>").append(
                $("<a></a>")
                  .attr("href", "#")
                  .data("page", o.pageCount)
                  .html("&raquo;")
                  .click(function() {
                    o.currentPage = o.pageCount
                    that.render();
                    return false;
                  })
              );


          var totalPages = o.pagePadding * 2
            , start
            , end;

          if(totalPages >= o.pageCount) {
            start = 1;
            end = o.pageCount;
          }
          else {
            start = o.currentPage - o.pagePadding;
            if(start <= 0) start = 1;

            end = start + totalPages;
            if(end > o.pageCount) {
              end = o.pageCount;
              start = end - totalPages;
            }
          }

          // append the pagination links
          for(var i = start; i <= end; i++) {
            var $link = $("<li></li>")
              .append(
                $("<a></a>")
                  .attr("href", "#")
                  .data("page", i)
                  .text(i)
                  .click(function() {
                    o.currentPage = $(this).data('page')
                    that.render();
                    return false;
                  })
              );

              if(i == o.currentPage) $link.addClass("active");

              $pager.append($link);
          }

          // append quick jump buttons
          if(o.currentPage == 1) {
            $first.addClass("disabled");
            $previous.addClass("disabled");
          }
          if(o.currentPage == o.pageCount) {
            $next.addClass("disabled");
            $last.addClass("disabled");
          }
          $pager.prepend($first, $previous);
          $pager.append($next, $last);

          this.$pagination.append($pager);
        }
        return this.$pagination;
      }

    , remove: function() {
        var $e = this.$element

        if(this.$section_header) this.$section_header.remove();

        $e.data("datatable", null);
        $e.empty();
      }

  };


 /* DATATABLE PRIVATE METHODS
  * ========================= */

  function _initToolbar() {
    var o = this.options;

    // create the perpage dropdown
    if(! o.hidePerPage)   _initPerPage.call(this);

    // handle filter options
    if(o.filterModal)     _initFilterModal.call(this);

    // handle the column management
    // ntop - disabled
    //if(o.toggleColumns)   _initColumnModal.call(this);

    // handle the overflow option
    // ntop - disabled
    // if(o.allowOverflow)   _initOverflowToggle.call(this);

    // initialize the table info
    if(o.allowTableinfo)   _initTableInfo.call(this);

    // create the buttons and section functions
    this.toolbar();
  }

  function _initColumnModal() {
    var o = this.options
      , $e = this.$element
      , $top_details = this.$top_details
      , $toggle = $("<a></a>");

    // localize the object
    var that = this;

    if(!this.$column_modal) {
      var randId = Math.floor((Math.random()*100)+1);
      this.$column_modal = $('<div></div>')
        .attr("id", "dt-column-modal_" + randId)
        .attr("tabindex","-1")
        .attr("role","dialog")
        .attr("aria-labelledby", "dt-column-modal-label_" + randId)
        .attr("aria-hidden","true")
        .addClass("modal fade")
        .hide();

      // render the modal header
      this.$column_modalheader = $("<div></div>")
        .addClass("modal-header")
        .append(
          $("<button></button>")
            .addClass("close")
            .data("dismiss", "modal")
            .attr("aria-hidden","true")
            .html('&times;')
            .click(function(){
              that.$column_modal.modal('hide');
            })
        )
        .append(
          $("<h3></h3>")
            .addClass("modal-title")
            .attr("id","dt-column-modal-label_" + randId)
            .text("Toggle Columns")
        );

      // render the modal footer
      this.$column_modalfooter = $("<div></div>")
        .addClass("modal-footer")
        .append(
            // show the check 'all / none' columns
            $('<div class="pull-left"></div>')
              .append(
                $('<div class="btn-group"></div>')
                  .append(
                    $('<button></button>')
                      .addClass("btn btn-info")
                      .append(
                        $("<span></span>")
                          .addClass("glyphicon glyphicon-check")
                          .text("All")
                      )
                      .click(function () {
                        $(this).closest(".modal").find('button.on-off').each(function () {
                          if($(this).data('column-hidden')){
                              $(this).click();
                          }
                        })
                        return false;
                      }),
                    $('<button></button>')
                      .addClass("btn btn-warning")
                      .append(
                        $("<span></span>")
                          .addClass("glyphicon glyphicon-unchecked")
                          .text("None")
                      )
                      .click(function () {
                        $(this).closest(".modal").find('button.on-off').each(function () {
                          if(!$(this).data('column-hidden')){
                            $(this).click();
                          }
                        })
                        return false;
                      })
                  )
              )

          , o.allowSaveColumns ? $("<button></button>")
              .addClass("btn btn-primary")
              .text("Save")
              .click(function() {
                _saveColumns.call(that)
                return false;
              }) : ""

          , $("<button></button>")
            .addClass("btn btn-default")
            .data('dismiss', 'modal')
            .append(
              $("<span></span>")
            )
            .text("Close")
            .click(function() {
              that.$column_modal.modal('hide')
              return false;
            })
        );

      // render the modal body
      this.$column_modalbody = $("<div></div>")
        .addClass("modal-body");

      this.$column_modaldialog = $("<div></div>")
        .addClass("modal-dialog")
        .append(
          $("<div></div>")
            .addClass("modal-content")
            .append(
              this.$column_modalheader
              ,this.$column_modalbody
              ,this.$column_modalfooter
            )
        );

      // render and add the modal to the container
      this.$column_modal
        .append(
          this.$column_modaldialog
        ).appendTo(document.body);
    }
    // render the display modal button
    $toggle
      .addClass("btn")
      .data("toggle", "modal")
      .data("content", "Choose which columns you would like to display.")
      .data("target","#" + this.$column_modal.attr("id"))
      .attr("href", "#" + this.$column_modal.attr("id"))
      .append(
        $("<span></span>")
          .addClass("glyphicon glyphicon-cog")
      )
      .click(function(e) {
        that.$column_modal
          .on('show.bs.modal', function () {
            if(o.debug) console.log(that);
            _updateColumnModalBody.call(that, that.$column_modalbody);
          })
          .modal();
        return false;
      })
      .popover({
        "trigger": 'hover',
        "placement":'top'
      });
    this.buttons.unshift($toggle);

    if(o.debug) console.log($toggle);

    return this.$column_modal;
  }

  function _initFilterModal() {
    var o = this.options
      , $e = this.$element
      , $toggle = $("<a></a>");

    // render the display modal button
    $toggle
      .addClass("btn")
      .data("toggle", "modal")
      .attr("href", "#")
      .data("content", "Open the filter dialog.")
      .extend(
        $("<span></span>")
          .addClass("glyphicon glyphicon-filter")
      )
      .click(function() {
        if($(o.filterModal).hasClass("modal"))
          $(o.filterModal)
            .modal();
        else if($(o.filterModal).is(":visible"))
          $(o.filterModal)
            .hide();
        else
          $(o.filterModal)
            .show();

        return false;
      })
      .popover({
        "trigger": 'hover',
        "placement":'top'
      });
    this.buttons.unshift($toggle);
  }

  function _initPerPage() {
    var o = this.options
      , $e = this.$element
      , that = this;

    // per page options and current filter/sorting
    var $perpage_select = $("<a></a>")
      .addClass("btn dropdown-toggle")
      .data("content", i18n.change_number_of_rows + ".")
      .attr("data-toggle", "dropdown")
      .html(o.perPage + "&nbsp;")
      .css({ fontWeight: 'normal' })
      .append(
        $("<span></span>")
          .addClass("caret")
      )
      .popover({
        "trigger": 'hover',
        "placement":'top'
      });
    this.buttons.push($perpage_select);

    var $perpage_values = $("<ul></ul>")
      .addClass("dropdown-menu")
      .css({ fontSize: 'initial', fontWeight: 'normal' })
      .append(
          $('<li data-value="10"><a href="#">10</a></li>')
            .click(function() { _updatePerPage.call(this, that); return false; })
        , $('<li data-value="20"><a href="#">20</a></li>')
            .click(function() { _updatePerPage.call(this, that); return false; })
        , $('<li data-value="50"><a href="#">50</a></li>')
            .click(function() { _updatePerPage.call(this, that); return false; })
        , $('<li data-value="100"><a href="#">100</a></li>')
            .click(function() { _updatePerPage.call(this, that); return false; })
        , $('<li data-value="200"><a href="#">200</a></li>')
            .click(function() { _updatePerPage.call(this, that); return false; })
      );
    this.buttons.push($perpage_values);
  }

  function _initTableInfo() {
    var o = this.options
      , $e = this.$element
      , $info = $("<a></a>");

    // render the display modal button
    $info
      .addClass("btn")
      .attr("href", "#")
      .append(
        $("<span></span>")
          .addClass("glyphicon glyphicon-info-sign")
      )
      .click(function() {
        return false;
      });

    var $page_sort = []
      , $page_filter = [];

    // sort
    $.each(o.sort, function(i, v){
      if(!v.length) return;
      var heading;
      for(var column in o.columns) {
        if(o.columns[column].field == v[0]) heading = o.columns[column].title;
      }
      $page_sort.push( heading + " " + v[1].toUpperCase() );
    });

    // filter
    $.each(o.filter, function(k, v) {
      var heading;
      for(var column in o.columns) {
        if(o.columns[column].field == k) heading = o.columns[column].title;
      }
      $page_filter.push( (heading || k) + " = '" + v + "'" );
    });
    $($info)
      .data("content",
        $('<dl></dl>').append(
          $page_sort.length > 0 ? '<dt><i class="icon-th-list"></i> Sort:</dt><dd>' + $page_sort.join(", ") + '</dd>' : ''
          ,
          $page_filter.length > 0 ? '<dt><i class="icon-filter"></i> Filter:</dt><dd>' + $page_filter.join(", ") + '</dd>' : ''
        ))
      .popover({
        placement: "bottom"
      });

    this.buttons.unshift($info);
  }

  function _initOverflowToggle() {
    var o = this.options
      , $wrapper = this.$wrapper
      , $overflow = $("<a></a>");

    // create the button
    $overflow
      .addClass("btn")
      .attr("href", "#")
      .attr("title", "Toggle the size of the table to fit the data or to fit the screen.")
      .append(
        $("<span></span>")
          .addClass("glyphicon glyphicon-resize-full")
      )
      .click(function() {
        if($wrapper) _toggleOverflow.call(this, $wrapper);
        return false;
      });

    if(!this.resultset || !this.resultset.data || this.resultset.data.length == 0)
      $overflow
        .addClass("disabled")

    this.buttons.push($overflow);
  }

  function _toggleOverflow(el) {
    if(el.css('overflow') == 'scroll') {
      $(this).children("span.glyphicon")
        .attr("class", "glyphicon glyphicon-resize-full");

      el.css({
          overflow: 'visible'
        , width: 'auto'
      });
    }
    else {
      $(this).children("span.glyphicon")
        .attr("class", "glyphicon glyphicon-resize-small");

      el.css({
          overflow: 'scroll'
        , width: el.width()
      });
    }
  }

  function _updatePerPage(that) {
    var o = that.options;

    // update the perpage value
    o.perPage = $(this).data("value");

    // the offset
    var offset = o.currentPage * o.perPage;
    while(offset > o.totalRows) {
      o.currentPage--;
      offset = o.currentPage * o.perPage;
    }
    if(o.currentPage < 1) o.currentPage = 1;

    if($(this).popover) $(this).popover('hide');

    // update the table
    that.render();

    return false;
  }

  function showError(data, status, err_msg) {
    var o = this.options
      , $e = this.$element;

    $e.empty();

    // initialize the toolbar
    _initToolbar.call(this);

    // nearly complete... let the user apply any final adjustments
    if(o.tableCallback && typeof o.tableCallback === 'function')
      o.tableCallback.call(this);

    this.loading( false );

    var custom_no_res = null;

    if(o.noResultsCallback)
      custom_no_res = o.noResultsCallback(this, data);

    if(custom_no_res) {
      $e.append(custom_no_res);
    } else if(err_msg && (status != "abort")) {
      $e.append($("<div></div>")
        .addClass("alert alert-danger")
        .html(err_msg));
    } else if(data && typeof data.error === "string") {
      $e.append($("<div></div>")
        .addClass("alert alert-danger")
        .html(data.error));
    } else if(o.noResultsMessage) {
      var msg = o.noResultsMessage(this, data);

      $e.append($("<div></div>")
        .addClass("alert alert-danger")
        .html(msg));
    } else if(this.$default)
      $e.append(this.$default);
  }

  function runFilter(that) {
    var o = that.options;

    o.filter[$(this).data("filter")] = $(this).val();
    if(o.debug) console.log(o.filter);

    that.render();
  }

  function _updateColumnModalBody(body) {
    var o = this.options
      , $container = $("<form></form>").addClass("form-inline")
      , that = this;

    // loop through the columns
    for(var column in o.columns) {
      if(o.columns[column].title === "") continue;
      var $item = $('<div></div></br>')
        .addClass('form-group')
        .append(
          $("<label></label>")
            .addClass("control-label")
            .append(
              o.columns[column].title,

              $("<button></button>")
                .addClass("on-off btn " + (o.columns[column].hidden ? 'btn-info' : 'btn-warning'))
                .data("column", column)
                .data("column-hidden",o.columns[column].hidden)
                .text(o.columns[column].hidden ? "ON" : "OFF")
                .click(function () {
                  _toggleColumn.call(this, that);
                  return false;
                })
            )
        );
      $container.append($item);
    }

    body.empty();
    body.append(
      $container
    );
  }

  function _toggleColumn(that) {
    var o = that.options
      , column = $(this).data("column")
      , $column = $("." + o.columns[column].classname);

    if($column.is(":visible")) {
      $column.hide();
      o.columns[column].hidden = true;
      $(this).removeClass("btn-warning").addClass("btn-info").text("ON").data("column-hidden",true);
    }
    else {
      $column.show();
      o.columns[column].hidden = false;
      $(this).removeClass("btn-info").addClass("btn-warning").text("OFF").data("column-hidden",false);
    }
    return false;
  }

  function _saveColumns() {
    var o = this.options
      , columns = [];

    // save the columns to the localstorage
    if(localStorage) {
      localStorage[this.localStorageId] = JSON.stringify(o.columns);
    }

    $.ajax({
        url: o.url
      , type: "POST"
      , dataType: "json"
      , data:  $.extend({}, o.post, {
            action: "save-columns"
          , columns: JSON.stringify(o.columns)
          , sort: JSON.stringify(o.sort)
          , filter: JSON.stringify(o.filter)
        })
      , success: function( res ) {
          if(o.debug) console.log("columns saved");
        }
    });

    this.$column_modal.modal("hide");
  }

  function _retrieveColumns(data) {
    var o = this.options
      , columns = data ? JSON.parse(data) : []
      , res = this.resultset;

    // if the server doesn't pass the column property back
    if(!res.columns) res.columns = [];

    for(column in o.columns) {
      o.columns[column] = $.extend({}, o.columns[column], res.columns[column], columns[column]);
    }
  }


 /* DATATABLE PLUGIN DEFINITION
  * =========================== */

  $.fn.datatable = function ( options ) {
    $.fn.datatable.init.call(this, options, DataTable, 'datatable');
    return this;
  };

  $.fn.datatable.init = function ( options, Constructor, name ) {
    var datatable;

    if (options === true) {
      return this.data(name);
    } else if (typeof options == 'string') {
      datatable = this.data(name);
      if (datatable) {
        datatable[options]();
      }
      return this;
    }

    options = $.extend({}, $.fn[name].defaults, options);

    function get ( el ) {
      var datatable = $.data(el, name);

      if (!datatable) {
        datatable = new Constructor(el, $.fn.datatable.elementOptions(el, options));
        $.data(el, name, datatable);
      }

      return datatable;
    }

    this.each(function() {
      get(this);
    });

    return this;
  };

  $.fn.datatable.DataTable = DataTable;

  $.fn.datatable.elementOptions = function ( el, options ) {
    return $.metadata ? $.extend({}, options, $(el).metadata()) : options;
  };

  $.fn.datatable.defaults = {
    debug: false,
    id: undefined,
    title: 'Data Table Results',
    class: 'table table-striped table-bordered',
    perPage: 10,
    pagePadding: 2,
    sort: [],
    filter: {},
    post: {},
    buttons: [],
    sectionHeader: undefined,
    totalRows: 0,
    currentPage: 1,
    showPagination: true,
    showTopPagination: false,
    showHeader: true,
    showFooter: false,
    showFilterRow: false,
    filterModal: undefined,
    allowExport: false,
    allowOverflow: true,
    allowMultipleSort: false,
    allowSaveColumns: true,
    toggleColumns: true,
    url: '',
    columns: [],
    ascending: $("<span></span>").addClass("glyphicon glyphicon-chevron-up"),
    descending: $("<span></span>").addClass("glyphicon glyphicon-chevron-down"),
    rowCallback: undefined,
    tableCallback: undefined,
    headerCallback: undefined,
    footerCallback: undefined,
    tablePreRender: undefined,
    autoLoad: true
  };
})(window.jQuery);
