Audienti DataTables

Audienti's DataTables are built on top of the DataTables.js the Table library for Javascript, with three primary extensions installed: select (so that a selected is added to the row), buttons (to enable buttons on the tables), and checkboxes (enables the multi-select, storing across pages, and remembering on a page refresh).

The base class for an AudientiDataTable is in the javascripts/app/datatables.coffee class. This sets the default actions. Then, on your JS file for your view, you instantiate this with options passed to it to setup the specific dataTable.

Backing a datatable is a class that handles creating the data in the requested format. This should be placed in the app/datatables folder. Note that this is a cyild class of ApplicationDatatable that does the majority of the work. Please review the KeywordsDatatable for an example of this code.

class KeywordDatatable < ApplicationDatatable
  # The base on which all queries (for counts and records) are made. Scoping
  # should occur here to limit things to just the data you actually want. Most
  # of the time, this will be to your current_project.
  def root_query
    current_project.keywords
  end

  # This is expected to return an array with hashes that have the fields
  # we are tracking in them. You can do your queries dynamically, and add
  # additional other attributes here, and as long as the query isn't too
  # bad it should be OK.. as we'll be at most dealing with only 100 records.
  #
  # The DT_RowId will be passed as the id of the tr.
  # The DT_RowData, in theory, should be adding a data attribute on the
  # tr generated from this data.
  def data
    records.map do |record|
      {
        'checkbox'   => record.id,
        'name'       => record.name,
        'created_at' => record.created_at,
        'DT_RowId'   => "keyword_#{record.id}",
        'DT_RowData' => { 'id' => record.id }
      }
    end
  end

  # Columns listed here will not be searched. Any DateTime should not
  # be included in this, as it will cause the saerch function to error with
  # a request for its timezone.
  def skip_search_columns
    %w(checkbox created_at)
  end

  # THe columns that are used in the app. These will be iterated over
  # for the search functions to generate the search string.
  def columns
    %w(checkbox name created_at)
  end
end

The controller takes this information, and if it's from a JS it calls this. The format.json will call to_json on the class, and this is how it gets rendered.

class KeywordsController < SecuredApplicationController
  def index
    datatable = KeywordDatatable.new(view_context, current_user: current_user,
                                                   current_project: current_project)
    @total = datatable.count
    respond_to do |format|
      format.html
      format.json { render json: datatable }
    end
  end

  def create
    factory = KeywordFactory.new(params: params, project: current_project)
    factory.call
    flash[:notice] = 'Keywords were updated.'
    redirect_to request.referrer || project_keywords_path(current_project)
  end
end

Configuring the DataTable in Javascript is very straightforward.

In your assets/controllers/keywords/index.coffee

class @KeywordsIndex
  constructor: ->
    @table = new AudientiTable(@selector, { columns: @columns, buttons: @buttons })
    return { table: @table }

  selector: '#keywords-table'

  buttons:
    [ {
        text: 'Add Tags'
        className: 'btn btn-secondary btn-table'
        href: '#modifyKeywordTags'
        data: {toggle: 'modal'}
        action: (e, dt, node, config) ->
          console.log('perfoming action to pop modal')
          selectedRows = dt.column(0).checkboxes.selected()
          selectedIds = selectedRows.toArray()
          $("modifyKeywordTags").modal('show')
      },
      {
          text: 'Second button'
          className: 'btn btn-secondary btn-table'
          action: (e, dt, node, config) ->
            selectedRows = dt.column(0).checkboxes.selected()
            selectedIds = selectedRows.toArray()
            alert "second button " + selectedIds
            return
      },
      {
          text: 'Clear Selection'
          className: 'btn btn-secondary btn-table'
          action: (e, dt, node, config) ->
            dt.column(0).checkboxes.deselectAll()
            return
      },
    ]

  columns:
    [
      { data: 'checkbox'  },
      { data: 'name'      },
      { data: 'created_at'}
    ]

Note that this uses the page-specific JS model that the application uses. Please review this if you have questions about the base @KeywordsIndex class.

In the constructor which is called on the page, load, we instantiate the datatable, using teh selector defined for the page (#keywords-table). in addition, the AudientiTable expects a hash of options. At a minimum, this must contain the columns definition. It should match the names in your KeywordsDatatable. In addition, it can include things such as the buttons that will be on the top of the table and perform actions. This is passed to DataTables natively, so anything that you want that is a datatabels capability can be passed in through this hash.

Once you have what you want, you return it as a hash/object. The table. makes it so that the table is available in Javascript at App.page.items.table.

Special Circumstances

Handling HTML content. Sometimes, you'll need to have complex data in a table. Not a problem. The DataTable class can handle this and will render the content for you.

Modify the column defintion to HTML, then in the data dlement that you return, reutrn the HTML.

Last updated