FieldtypeRockGrid by bernhard

ProcessWire is awesome for creating all kinds of custom backend applications, but where it is not so awesome in my opinion is when it comes to listing this data. Of course we have the built in page lister and we have ListerPro, but none of that solutions is capable of properly displaying large amounts of data, for example lists of revenues, aggregations, quick and easy sorts by the user, instant filter and those kind of features. RockGrid to the rescue.

Docs

Add global RockGrid settings (colors, styles, values)


// /site/assets/RockGrid/fields/global.js
document.addEventListener('RockGridReady', function(e) {
  RockGrid.settings = {
    foo: 'bar',
  };
});

Disable floating filter


document.addEventListener('RockGridItemBeforeInit', function(e) {
  if(e.target.id != 'RockGridItem_yourgridid') return;
  var grid = RockGrid.getGrid(e.target.id);
  grid.gridOptions.floatingFilter = false;
});

Example how to trigger refresh after panel was closed


// /site/assets/RockGrid/fields/global.js
$(document).on('pw-panel-closed', function(event, $panel) {
  console.log(event);
  console.log($panel);
  $.each($('.RockGridItem:visible'), function(i, $el) {
    $wrapper = $(RockGrid.getGrid($el).getWrapperDOM());
    $wrapper.find('.rockgridbutton.refresh').click();
  });
});

Example of a RockGrid in a ProcessModule


$form = $this->modules->get('InputfieldForm');

$f = $this->modules->get('InputfieldRockGrid');
$f->name = 'myRockGridField';
$f->themeBorder = 'none';
$f->height = 0;
$f->pageSize = 1; // set initial pagination to 25 rows
$f->setData(new RockFinder("id>0, limit=10", [
  'title',
  'templates_id',
  'created',
  'status',
]));
$form->add($f);

return $form->render();

Then you can modify your grid via a javascript file: /site/assets/RockGrid/fields/myRockGridField.js


Example of a multilanguage setup:


You can define language strings either locally for a grid in /site/assets/RockGrid/fields/yourfield.php:

$this->x('pl_name', __('Projektleiter'));
$this->x('title', __('Bezeichnung'));

Access these strings in the grid's js file:

var grid = RockGrid.getGrid('yourgridname');
console.log(grid.str.pl_name);
console.log(grid.str.title);

Or you can define global strings in any globally loaded file, but recommended in site/assets/RockGrid/lang.php (note I'm using ->rg->x instead of ->x):

$this->rg->x('pl_name', __('Projektleiter'));
$this->rg->x('title', __('Bezeichnung'));

Access these strings in the grid's js file:

console.log(RockGrid.str.pl_name);
console.log(RockGrid.str.title);

Transmit data to the client-side


Select-Option fields by default show the ID of the field and not the label. In this case it might be the easiest solution to send an array to JavaScript with a reference list of which ID has which label:

// site/assets/RockGrid/fields/yourfield.php
$this->js([
  'type' => [
    1 => 'type1',
    2 => 'type2',
    3 => 'type3'
  ]
]);

There is also a shortcut for options fields:

// site/assets/RockGrid/fields/yourfield.php
$this->js('RockGrid', [
  'efforttype' => $this->getOptionsFromField('efforttype'),
  'projectstatus' => $this->getOptionsFromField('projectstatus'),
]);

Then the data is available in your griditem object:

js-data

You can then create a cellRenderer or a valueGetter to replace the field IDs by labels:

col = grid.getColDef('rockprojecteffort_type');
col.headerName = 'Typ';
col.cellRenderer = function(params) {
  // get the current cell value
  var val = params.data.rockprojecteffort_type;
  // return the corresponding label
  return grid.js.type[val];
};

Notice that you have a helper method to get all options of an options field:

$this->js([
  'type' => $this->getOptionsFromField('rockprojecteffort_type'),
]);

Add payload to AJAX-Requests


When having an AJAX powered grid it is sometimes necessary to pass variables to the server. For example you could have a process module showing a grid that should only list projects of one specific year.

document.addEventListener('RockGridItemBeforeInit', function(e) {
  if(e.target.id != 'RockGridItem_yourgridid') return;
  var grid = RockGrid.getGrid(e.target.id);

  // get payload for ajax requests
  grid.getPayload = function() {
    return {
      year: grid.js.year,
      randomDemo: function() {
        return Math.random();
      }
    }
  }
});

The resulting request url could look something like that:

?field=auftragsbuch&RockGrid=1&year=2019&randomDemo=0.9468739776294344

This way you can pass any variables to the data-requesting AJAX call, for example a filter value, a selectbox value etc.

In your php file you can access the variable like this:

$year = wire()->input->get->year ?: date('Y');
$selector = "template=project,year=$year";
$finder = new RockFinder($selector, [...]);

Set visible columns


document.addEventListener('RockGridItemAfterInit', function(e) {
  if(e.target.id != 'RockGridItem_architecture') return;
  var grid = RockGrid.getGrid(e.target.id);

  grid.setColumns(['id', 'fbdone', 'answers', 'fgcount', 'fg:title', 'fncount', 'fn:title', 'fbrole:title']);
});
document.addEventListener('RockGridItemAfterInit', function(e) {
  if(e.target.id != 'RockGridItem_mygridid') return;
  var grid = RockGrid.getGrid(e.target.id);

  // hide one column
  grid.columnApi().setColumnVisible('myhiddencolumn', false);
});

Manual initialisation / JavaScript-only Grids


Sometimes you might need JavaScript-only grids. For example you could create a grid that shows aggregated data of another grid. The simplest example of such a grid is taken from the ag-grid getting started guide.

document.addEventListener('RockGridItemBeforeInit', function(e) {
  if(e.target.id != 'RockGridItem_yourgridid') return;
  var grid = RockGrid.getGrid(e.target.id);
  var gridOptions = grid.gridOptions;

  gridOptions.columnDefs = [
    {headerName: "Make", field: "make"},
    {headerName: "Model", field: "model"},
    {headerName: "Price", field: "price"}
  ];

  gridOptions.rowData = [
    {make: "Toyota", model: "Celica", price: 35000},
    {make: "Ford", model: "Mondeo", price: 32000},
    {make: "Porsche", model: "Boxter", price: 72000}
  ];
});

To update this grid whenever the master grid is changed use something like this:

document.addEventListener('RockGridItemBeforeInit', function(e) {
  if(e.target.id != 'RockGridItem_slave') return;
  var grid = RockGrid.getGrid(e.target.id);
  var gridOptions = grid.gridOptions;

  gridOptions.columnDefs = [
    {headerName: "Make", field: "make"},
    {headerName: "Model", field: "model"},
    {headerName: "Price", field: "price"}
  ];

  /**
   * create custom redraw function for this grid
   */
  grid.redraw = function() {
    var master = RockGrid.getGrid('master');

    // get filtered id's of master grid
    // see pluck() function in RockGridItem.js
    var ids = master.pluck('id', {filter:true, sort:false});
    console.log(ids);

    // setup data array
    var data = [];
    for(var i = 0; i<ids.length; i++) {
      data.push({
        make: 'Row ' + i,
        model: ids[i],
        price: Math.random(),
      });
    }

    // set new data
    grid.api().setRowData(data);
  }
});

/**
 * attach event listeners to master table
 */
document.addEventListener('RockGridItemAfterInit', function(e) {
  if(e.target.id != 'RockGridItem_master') return;
  var grid = RockGrid.getGrid(e.target.id);

  var redraw = function(e) { RockGrid.getGrid('slave').redraw(); }
  grid.gridOptions.api.addEventListener('rowDataChanged', redraw);
  grid.gridOptions.api.addEventListener('filterChanged', redraw);
});

Add dynamic columns


document.addEventListener('RockGridItemBeforeInit', function(e) {
  if(e.target.id != 'RockGridItem_mygridid') return;
  var grid = RockGrid.getGrid(e.target.id);

  // add reports column
  grid.gridOptions.columnDefs.push({
    valueGetter: function() { return 'x'; },
  });
});

Move columns


document.addEventListener('RockGridItemAfterInit', function(e) {
  if(e.target.id != 'RockGridItem_auftragsbuch') return;
  var grid = RockGrid.getGrid(e.target.id);

  grid.columnApi().setColumnVisible('demo1', false);
  grid.columnApi().moveColumn('demo2', 1);
});

Listening for events


See https://www.ag-grid.com/javascript-grid-reference-overview/#listening-to-events for more information and https://www.ag-grid.com/javascript-grid-events/ for all available events.

Example:

document.addEventListener('RockGridItemAfterInit', function(e) {
  if(e.target.id != 'RockGridItem_projects') return;
  var grid = RockGrid.getGrid(e.target.id);

  grid.api().addEventListener('rowSelected', function(e) {
    console.log('row was (un)selected!');

    // show array of selected ids
    console.log(grid.pluck('id', {selected:true}));
  })
});

AJAX callbacks, Batcher Plugin


In your php data-file:

$this->ajax('send', function($data) {
  foreach($data as $item) {
    // do something
  }
});

You can also set options on the server-side so you do not need to put sensitive data in the AJAX request:

$this->ajax('send', function($data, $options) {
  $project = $options['projectid'];
  foreach($data as $person) {
    // do something
  }
}, ['projectid' => $project]);

Setup the GUI on the client-side:

$(document).on('click', 'button[name=send]', function() {
  var grid = RockGrid.getGrid('recipients');

  // get recipients type
  var mails = $('input[name=mails]:checked', '#sendmailform').val();
  var recipient = $('input[name=recipient]:checked', '#sendmailform').val();

  // show confirm dialog
  var Batcher = RockGrid.batcher;
  Batcher.items = grid.pluck('id', {selected: (mails=='selected')});
  Batcher.batchSize = 10;
  Batcher.action = function(items) {
    // send ajax request to this grid
    var ajax = grid.ajax({
      action: 'send',
      data: items,
    }).done(function(params) {
      Batcher.nextBatch();
    });
  };
  Batcher.confirmStart({
    msg: grid.js.confirmsend,
    onYes: function() {
      Batcher.nextBatch();
    },
    onLoad: function() {
      if(!Batcher.items.length) {
        ProcessWire.alert(grid.js.noItems);
        return false;
      }
    },
  });
});

Extending RockGrid and RockGridItem


You can extend RockGrid easily by placing code like this into /site/assets/RockGrid/fields/global.js

document.addEventListener('RockGridItemReady', function(e) {
  /**
   * be careful not to get in conflict with RockGridItem's original methods
   * call it like this: grid.myCustomDemoMethod('hello world');
   */
  RockGridItem.prototype.myCustomDemoMethod = function(str) {
    console.log(this); // current RockGridItem instance
    console.log(str);
  }
});

Create custom filters


See a simple example filter with lots of comments here: /site/modules/FieldtypeRockGrid/plugins/filters/example.js

Apply the filter like this:

document.addEventListener('RockGridItemBeforeInit', function(e) {
  if(e.target.id != 'RockGridItem_yourgrid') return;
  var grid = RockGrid.getGrid(e.target.id);
  col = grid.getColDef('title');
  col.filter = RockGrid.filters.example;
  col.floatingFilterComponent = RockGrid.filters.exampleFloating;
});

Extending Grids

Sometimes several RockGrids are very similar and you don't want to duplicate your code. You can create one base file and include this file from others:

// trainings.php
$finder = new RockFinder([
  'template' => 'training',
  'sort' => 'from',
], [
  'first',
  'from',
  'to',
]);
$finder->addField('coach', ['title']);
$finder->addField('client', ['fullname']);

// trainings_booked.php
include(__DIR__.'/trainings.php');

$finder->setSelectorValue('client', $this->user); // only show trainings of current user
$finder->addField('done'); // add column to show if the training was completed

$this->setData($finder);

You can then create another file, for example trainings_available.php to show all trainings that are in the future and available for booking:

include(__DIR__.'/trainings.php');

$finder->setSelectorValue('from>', time());
$finder->setSelectorValue('client', null); // client must not be set -> means available for booking

$this->setData($finder);

Locale support


Locale support is ON by default since v0.0.19; You just need to set the correct locale when the grid is ready:

document.addEventListener('RockGridReady', function(e) {
  moment.locale('de');
});

Using tippy.js


img

col = grid.getColDef('ids');
col.headerName = 'Rechnung';
col.cellRenderer = function(params) {
  if(!params.value) return '';
  var ids =  params.data.ids.split(',');
  var out = 'IDs: ' + ids.join(', ');
  var tippy = 'One per line:<br>' + ids.join('<br>');
  return RockGrid.tippy(out, tippy);
}


Install and use modules at your own risk. Always have a site and database backup before installing new modules.

Twitter updates

  • ProcessWire 3.0.180 core updates– More
    18 June 2021
  • Today a new version of FormBuilder has been released in the FormBuilder support board (our 50th version) and it has a lot of interesting new features, which we’ll take a closer look at in this post— More
    11 June 2021
  • ProcessWire 3.0.179 adds great new admin theme customization tools that put you in full control over the Uikit admin styles— More
    28 May 2021

Latest news

  • ProcessWire Weekly #371
    In the 371st issue of ProcessWire Weekly we'll check out ProcessWire 3.0.180, introduce a new module called Flowti Page Serializer, highlight some recent forum posts and tutorials, and more. Read on!
    Weekly.pw / 19 June 2021
  • ProcessWire FormBuilder v50 updates
    Today a new version of FormBuilder has been released in the FormBuilder support board (our 50th version) and it has a lot of interesting new features, which we'll take a closer look at in this post.
    Blog / 11 June 2021
  • Subscribe to weekly ProcessWire news

“The end client and designer love the ease at which they can update the website. Training beyond how to log in wasn’t even necessary since ProcessWire’s default interface is straightforward.” —Jonathan Lahijani