Open Monograph Press  1.1
 All Classes Namespaces Functions Variables Groups Pages
GridHandler.inc.php
1 <?php
2 
16 // Import the base Handler.
17 import('lib.pkp.classes.handler.PKPHandler');
18 
19 // Import action class.
20 import('lib.pkp.classes.linkAction.LinkAction');
21 
22 // Import grid classes.
23 import('lib.pkp.classes.controllers.grid.GridColumn');
24 import('lib.pkp.classes.controllers.grid.GridRow');
25 
26 // Import JSON class for use with all AJAX requests.
27 import('lib.pkp.classes.core.JSONMessage');
28 
29 // Grid specific action positions.
30 define('GRID_ACTION_POSITION_DEFAULT', 'default');
31 define('GRID_ACTION_POSITION_ABOVE', 'above');
32 define('GRID_ACTION_POSITION_LASTCOL', 'lastcol');
33 define('GRID_ACTION_POSITION_BELOW', 'below');
34 
35 class GridHandler extends PKPHandler {
36 
38  var $_title = '';
39 
41  var $_emptyRowText = 'grid.noItems';
42 
44  var $_instructions = '';
45 
47  var $_footNote = '';
48 
50  var $_dataProvider;
51 
57  var $_actions = array(GRID_ACTION_POSITION_DEFAULT => array());
58 
60  var $_columns = array();
61 
63  var $_data;
64 
66  var $_itemIterator;
67 
69  var $_template;
70 
72  var $_urls;
73 
76 
77 
85  function GridHandler($dataProvider = null) {
86  $this->_dataProvider = $dataProvider;
87  parent::PKPHandler();
88  }
89 
90 
91  //
92  // Getters and Setters
93  //
98  function getDataProvider() {
99  return $this->_dataProvider;
100  }
101 
119  function getRequestArgs() {
120  $dataProvider = $this->getDataProvider();
121  $requestArgs = array();
122  if (is_a($dataProvider, 'GridDataProvider')) {
123  $requestArgs = $dataProvider->getRequestArgs();
124  }
125 
126  $this->callFeaturesHook('getRequestArgs', array('grid' => &$this, 'requestArgs' => &$requestArgs));
127 
128  return $requestArgs;
129  }
130 
138  function getRequestArg($key) {
139  $requestArgs = $this->getRequestArgs();
140  assert(isset($requestArgs[$key]));
141  return $requestArgs[$key];
142  }
143 
148  function getTitle() {
149  return $this->_title;
150  }
151 
156  function setTitle($title) {
157  $this->_title = $title;
158  }
159 
163  function getEmptyRowText() {
164  return $this->_emptyRowText;
165  }
166 
170  function setEmptyRowText($emptyRowText) {
171  $this->_emptyRowText = $emptyRowText;
172  }
173 
178  function getInstructions() {
179  return $this->_instructions;
180  }
181 
186  function setInstructions($instructions) {
187  $this->_instructions = $instructions;
188  }
189 
194  function getFootNote() {
195  return $this->_footNote;
196  }
197 
202  function setFootNote($footNote) {
203  $this->_footNote = $footNote;
204  }
205 
211  function getActions($position = GRID_ACTION_POSITION_ABOVE) {
212  if(!isset($this->_actions[$position])) return array();
213  return $this->_actions[$position];
214  }
215 
221  function addAction($action, $position = GRID_ACTION_POSITION_ABOVE) {
222  if (!isset($this->_actions[$position])) $this->_actions[$position] = array();
223  $this->_actions[$position][$action->getId()] = $action;
224  }
225 
230  function &getColumns() {
231  return $this->_columns;
232  }
233 
239  function getColumn($columnId) {
240  assert(isset($this->_columns[$columnId]));
241  return $this->_columns[$columnId];
242  }
243 
249  function &getColumnsByFlag($flag) {
250  $columns = array();
251  foreach ($this->getColumns() as $column) {
252  if ($column->hasFlag($flag)) {
253  $columns[$column->getId()] = $column;
254  }
255  }
256 
257  return $columns;
258  }
259 
266  function getColumnsCount($flag) {
267  $count = 0;
268  foreach ($this->getColumns() as $column) {
269  if (!$column->hasFlag($flag)) {
270  $count++;
271  }
272  }
273 
274  return $count;
275  }
276 
282  function hasColumn($columnId) {
283  return isset($this->_columns[$columnId]);
284  }
290  function addColumn($column) {
291  assert(is_a($column, 'GridColumn'));
292  $this->_columns[$column->getId()] = $column;
293  }
294 
300  function &getGridDataElements($request) {
301  $filter = $this->getFilterSelectionData($request);
303  // Try to load data if it has not yet been loaded.
304  if (is_null($this->_data)) {
305  $data = $this->loadData($request, $filter);
306 
307  if (is_null($data)) {
308  // Initialize data to an empty array.
309  $data = array();
310  }
311 
312  $this->setGridDataElements($data);
313  }
314 
315  $this->callFeaturesHook('getGridDataElements', array('request' => &$request, 'grid' => &$this, 'gridData' => &$gridData, 'filter' => &$filter));
316 
317  return $this->_data;
318  }
319 
324  function hasGridDataElements($request) {
325  $data =& $this->getGridDataElements($request);
326  assert (is_array($data));
327  return (boolean) count($data);
328  }
329 
334  function setGridDataElements($data) {
335  $this->callFeaturesHook('setGridDataElements', array('grid' => &$this, 'data' => &$data));
337  // FIXME: We go to arrays for all types of iterators because
338  // iterators cannot be re-used, see #6498.
339  if (is_array($data)) {
340  $this->_data = $data;
341  } elseif(is_a($data, 'DAOResultFactory')) {
342  $this->_data = $data->toAssociativeArray();
343  } elseif(is_a($data, 'ItemIterator')) {
344  $this->_data = $data->toArray();
345  } else {
346  assert(false);
347  }
348  }
349 
354  function getTemplate() {
355  if (is_null($this->_template)) {
356  $this->setTemplate('controllers/grid/grid.tpl');
357  }
358 
359  return $this->_template;
360  }
361 
366  function setTemplate($template) {
367  $this->_template = $template;
368  }
369 
375  function getUrls() {
376  return $this->_urls;
377  }
378 
385  function setUrls($request, $extraUrls = array()) {
386  $router = $request->getRouter();
387  $urls = array(
388  'fetchGridUrl' => $router->url($request, null, null, 'fetchGrid', null, $this->getRequestArgs()),
389  'fetchRowUrl' => $router->url($request, null, null, 'fetchRow', null, $this->getRequestArgs())
390  );
391  $this->_urls = array_merge($urls, $extraUrls);
392  }
393 
401  function getIsSubcomponent() {
402  return false;
403  }
404 
409  function getFeatures() {
410  return $this->_features;
411  }
412 
419  function getItemIterator() {
420  return $this->_itemIterator;
421  }
422 
427  function getPublishChangeEvents() {
428  return array();
429  }
430 
431  // FIXME: Since we've moved to PHP5, maybe those methods
432  // should be moved into interfaces like OrderableItems
433  // and SelectableItems. Then each grid can implement
434  // them in a clear way. It will also simplify this base
435  // class hiding optional interfaces.
436 
437  //
438  // Orderable items.
439  //
445  function getDataElementSequence(&$gridDataElement) {
446  assert(false);
447  }
448 
456  function setDataElementSequence($request, $rowId, &$gridDataElement, $newSequence) {
457  assert(false);
458  }
459 
460  //
461  // Selectable items.
462  //
469  function isDataElementSelected($gridDataElement) {
470  assert(false);
471  }
472 
478  function getSelectName() {
479  assert(false);
480  }
493  function getRequestedRow($request, $args) {
494  $isModified = isset($args['modify']);
495  if (isset($args['rowId']) && !$isModified) {
496  // A row ID was specified. Fetch it
497  $elementId = $args['rowId'];
498 
499  // Retrieve row data for the requested row id
500  $dataElement = $this->getRowDataElement($request, $elementId);
501  if (is_null($dataElement)) {
502  // If the row doesn't exist then
503  // return null. It may be that the
504  // row has been deleted in the meantime
505  // and the client does not yet know about this.
506  $nullVar = null;
507  return $nullVar;
508  }
509  } elseif ( $isModified ) {
510  // The row is modified. The client may be asking
511  // for a formatted new entry, to be saved later, or
512  // for a representation of a modified row.
513  $dataElement = $this->getRowDataElement($request, null);
514  if ( isset($args['rowId']) ) {
515  // the rowId holds the elementId being modified
516  $elementId = $args['rowId'];
517  } else {
518  // no rowId means that there is no element being modified.
519  $elementId = null;
520  }
521  }
522 
523  // Instantiate a new row
524  return $this->_getInitializedRowInstance($request, $elementId, $dataElement, $isModified);
525  }
526 
533  function renderRow($request, $row) {
534  $this->setFirstDataColumn();
535  return $this->renderRowInternally($request, $row);
536  }
537 
545  function getGridRangeInfo($request, $rangeName, $contextData = null) {
546  $rangeInfo = parent::getRangeInfo($request, $rangeName, $contextData);
547 
548  $this->callFeaturesHook('getGridRangeInfo', array('request' => &$request, 'grid' => &$this, 'rangeInfo' => $rangeInfo));
549 
550  return $rangeInfo;
551  }
552 
553 
554  //
555  // Overridden methods from PKPHandler
556  //
560  function authorize($request, &$args, $roleAssignments, $enforceRestrictedSite = true) {
561  $dataProvider = $this->getDataProvider();
562  $hasDataProvider = is_a($dataProvider, 'GridDataProvider');
563  if ($hasDataProvider) {
564  $this->addPolicy($dataProvider->getAuthorizationPolicy($request, $args, $roleAssignments));
565  }
566 
567  $success = parent::authorize($request, $args, $roleAssignments);
568 
569  if ($hasDataProvider && $success === true) {
570  $dataProvider->setAuthorizedContext($this->getAuthorizedContext());
571  }
572 
573  return $success;
574  }
575 
581  function initialize($request, $args = null) {
582  parent::initialize($request, $args);
583 
584  // Load grid-specific translations
585  AppLocale::requireComponents(LOCALE_COMPONENT_PKP_GRID, LOCALE_COMPONENT_APP_COMMON);
586 
587  // Give a chance to grid add features before calling hooks.
588  // Because we must control when features are added to a grid,
589  // this is the only place that should use the _addFeature() method.
590  $this->_addFeatures($this->initFeatures($request, $args));
591  $this->callFeaturesHook('gridInitialize', array('grid' => &$this));
592  }
593 
594 
595  //
596  // Public handler methods
597  //
605  function fetchGrid($args, $request) {
606  $this->setUrls($request);
607 
608  // Prepare the template to render the grid.
609  $templateMgr = TemplateManager::getManager($request);
610  $templateMgr->assign('grid', $this);
611 
612  // Add rendered filter
613  $renderedFilter = $this->renderFilter($request);
614  $templateMgr->assign('gridFilterForm', $renderedFilter);
615 
616  // Add columns.
618  $columns = $this->getColumns();
619  $templateMgr->assign('columns', $columns);
620 
621  // Do specific actions to fetch this grid.
622  $this->doSpecificFetchGridActions($args, $request, $templateMgr);
623 
624  // Assign additional params for the fetchRow and fetchGrid URLs to use.
625  $templateMgr->assign('gridRequestArgs', $this->getRequestArgs());
626 
627  $this->callFeaturesHook('fetchGrid', array('grid' => &$this, 'request' => &$request));
628 
629  // Assign features.
630  $templateMgr->assign('features', $this->getFeatures());
631 
632  // Let the view render the grid.
633  $json = new JSONMessage(true, $templateMgr->fetch($this->getTemplate()));
634  return $json->getString();
635  }
636 
645  function fetchRow(&$args, $request) {
646  // Instantiate the requested row (includes a
647  // validity check on the row id).
648  $row = $this->getRequestedRow($request, $args);
649 
650  $json = new JSONMessage(true);
651  if (is_null($row)) {
652  // Inform the client that the row does no longer exist.
653  $json->setAdditionalAttributes(array('elementNotFound' => $args['rowId']));
654  } else {
655  // Render the requested row
656  $renderedRow = $this->renderRow($request, $row);
657  $json->setContent($renderedRow);
658 
659  // Add the sequence map so grid can place the row at the correct position.
660  $sequenceMap = $this->getRowsSequence($request);
661  $json->setAdditionalAttributes(array('sequenceMap' => $sequenceMap));
662  }
663 
664  $this->callFeaturesHook('fetchRow', array('request' => &$request, 'grid' => &$this, 'row' => &$row, 'jsonMessage' => &$json));
665 
666  // Render and return the JSON message.
667  return $json->getString();
668  }
669 
676  function fetchCell(&$args, $request) {
677  // Check the requested column
678  if(!isset($args['columnId'])) fatalError('Missing column id!');
679  if(!$this->hasColumn($args['columnId'])) fatalError('Invalid column id!');
680  $this->setFirstDataColumn();
681  $column = $this->getColumn($args['columnId']);
682 
683  // Instantiate the requested row
684  $row = $this->getRequestedRow($request, $args);
685  if (is_null($row)) fatalError('Row not found!');
686 
687  // Render the cell
688  $json = new JSONMessage(true, $this->_renderCellInternally($request, $row, $column));
689  return $json->getString();
690  }
691 
700  function saveSequence($args, $request) {
701  $this->callFeaturesHook('saveSequence', array('request' => &$request, 'grid' => &$this));
702 
703  return DAO::getDataChangedEvent();
704  }
705 
710  public function getJSHandler() {
711  return '$.pkp.controllers.grid.GridHandler';
712  }
713 
714  //
715  // Protected methods to be overridden/used by subclasses
716  //
726  protected function getRowsSequence($request) {
727  return array_keys($this->getGridDataElements($request));
728  }
729 
730 
737  protected function getRowInstance() {
738  //provide a sensible default row definition
739  return new GridRow();
740  }
741 
751  protected function &getDataElementFromRequest($request, &$elementId) {
752  fatalError('Grid does not support data element creation!');
753  }
754 
762  protected function getRowDataElement($request, $rowId) {
763  $elements =& $this->getGridDataElements($request);
764 
765  assert(is_array($elements));
766  if (!isset($elements[$rowId])) return null;
767 
768  return $elements[$rowId];
769  }
770 
779  protected function loadData($request, $filter) {
780  $gridData = null;
781  $dataProvider = $this->getDataProvider();
782  if (is_a($dataProvider, 'GridDataProvider')) {
783  // Populate the grid with data from the
784  // data provider.
785  $gridData = $dataProvider->loadData();
786  }
788  $this->callFeaturesHook('loadData', array('request' => &$request, 'grid' => &$this, 'gridData' => &$gridData));
789 
790  return $gridData;
791  }
792 
797  protected function getFilterForm() {
798  return null;
799  }
800 
808  protected function getFilterSelectionData($request) {
809  return null;
810  }
811 
818  protected function renderFilter($request, $filterData = array()) {
819  $form = $this->getFilterForm();
820  assert(is_null($form) || is_a($form, 'Form') || is_string($form));
821 
822  $renderedForm = '';
823  switch(true) {
824  case is_a($form, 'Form'):
825  // Only read form data if the clientSubmit flag has been checked
826  $clientSubmit = (boolean) $request->getUserVar('clientSubmit');
827  if($clientSubmit) {
828  $form->readInputData();
829  $form->validate();
830  }
831 
832  $form->initData($filterData, $request);
833  $renderedForm = $form->fetch($request);
834  break;
835  case is_string($form):
836  $templateMgr = TemplateManager::getManager($request);
837 
838  // Assign data to the filter.
839  $templateMgr->assign('filterData', $filterData);
840 
841  // Assign current selected filter data.
842  $filterSelectionData = $this->getFilterSelectionData($request);
843  $templateMgr->assign('filterSelectionData', $filterSelectionData);
845  $renderedForm = $templateMgr->fetch($form);
846  break;
847  }
848 
849  return $renderedForm;
850  }
851 
857  protected function noAutocompleteResults() {
858  $returner = array();
859  $returner[] = array('label' => __('common.noMatches'), 'value' => '');
860 
861  $json = new JSONMessage(true, $returner);
862  return $json->getString();
863  }
864 
872  protected function doSpecificFetchGridActions($args, $request, $templateMgr) {
873  $this->_fixColumnWidths();
874 
875  // Render the body elements.
876  $gridBodyParts = $this->renderGridBodyPartsInternally($request);
877  $templateMgr->assign('gridBodyParts', $gridBodyParts);
878  }
879 
887  protected function setFirstDataColumn() {
888  $columns =& $this->getColumns();
889  $firstColumn = reset($columns);
890  $firstColumn->addFlag('firstColumn', true);
891  }
892 
902  protected function initFeatures($request, &$args) {
903  return array();
904  }
905 
911  protected function callFeaturesHook($hookName, $args) {
912  $features = $this->getFeatures();
913  if (is_array($features)) {
914  foreach ($features as &$feature) {
915  if (is_callable(array($feature, $hookName))) {
916  $feature->$hookName($args);
917  } else {
918  assert(false);
919  }
920  }
921  }
922  }
930  protected function renderRowsInternally($request, &$elements) {
931  // Iterate through the rows and render them according
932  // to the row definition.
933  $renderedRows = array();
934  foreach ($elements as $elementId => $element) {
935  // Instantiate a new row.
936  $row = $this->_getInitializedRowInstance($request, $elementId, $element);
937 
938  // Render the row
939  $renderedRows[] = $this->renderRowInternally($request, $row);
940  }
941 
942  return $renderedRows;
943  }
944 
955  protected function renderRowInternally($request, $row) {
956  // Iterate through the columns and render the
957  // cells for the given row.
958  $renderedCells = array();
959  $columns = $this->getColumns();
960  foreach ($columns as $column) {
961  assert(is_a($column, 'GridColumn'));
962  $renderedCells[] = $this->_renderCellInternally($request, $row, $column);
963  }
964 
965  // Pass control to the view to render the row
966  $templateMgr = TemplateManager::getManager($request);
967  $templateMgr->assign('grid', $this);
968  $templateMgr->assign('columns', $columns);
969  $templateMgr->assign('cells', $renderedCells);
970  $templateMgr->assign('row', $row);
971  return $templateMgr->fetch($row->getTemplate());
972  }
973 
979  protected function renderGridBodyPartsInternally($request) {
980  // Render the rows.
981  $elements = $this->getGridDataElements($request);
982  $renderedRows = $this->renderRowsInternally($request, $elements);
983 
984  // Render the body part.
985  $templateMgr = TemplateManager::getManager($request);
986  $gridBodyParts = array();
987  if ( count($renderedRows) > 0 ) {
988  $templateMgr->assign('grid', $this);
989  $templateMgr->assign('rows', $renderedRows);
990  $gridBodyParts[] = $templateMgr->fetch('controllers/grid/gridBodyPart.tpl');
991  }
992  return $gridBodyParts;
993  }
994 
995 
996  //
997  // Private helper methods
998  //
1007  private function _getInitializedRowInstance($request, $elementId, &$element, $isModified = false) {
1008  // Instantiate a new row
1009  $row = $this->getRowInstance();
1010  $row->setGridId($this->getId());
1011  $row->setId($elementId);
1012  $row->setData($element);
1013  $row->setRequestArgs($this->getRequestArgs());
1014  $row->setIsModified($isModified);
1016  // Initialize the row before we render it
1017  $row->initialize($request);
1018  $this->callFeaturesHook('getInitializedRowInstance', array('grid' => &$this, 'row' => &$row));
1019  return $row;
1020  }
1021 
1033  private function _renderCellInternally($request, $row, $column) {
1034  // If there is no object, then we want to return an empty row.
1035  // override the assigned GridCellProvider and provide the default.
1036  $element =& $row->getData();
1037  if ( is_null($element) && $row->getIsModified() ) {
1038  import('lib.pkp.classes.controllers.grid.GridCellProvider');
1039  $cellProvider = new GridCellProvider();
1040  return $cellProvider->render($request, $row, $column);
1041  }
1042 
1043  // Otherwise, get the cell content.
1044  // If row defines a cell provider, use it.
1045  $cellProvider = $row->getCellProvider();
1046  if (!is_a($cellProvider, 'GridCellProvider')) {
1047  // Remove reference to the row variable.
1048  unset($cellProvider);
1049  // Get cell provider from column.
1050  $cellProvider = $column->getCellProvider();
1051  }
1052 
1053  return $cellProvider->render($request, $row, $column);
1054  }
1055 
1060  private function _fixColumnWidths() {
1061  $columns =& $this->getColumns();
1062  $width = 0;
1063  $noSpecifiedWidthCount = 0;
1064  // Find the total width and how many columns do not specify their width.
1065  foreach ($columns as $column) {
1066  if ($column->hasFlag('width')) {
1067  $width += $column->getFlag('width');
1068  } else {
1069  $noSpecifiedWidthCount++;
1070  }
1071  }
1072 
1073  // Four cases: we have to add or remove some width, and either we have wiggle room or not.
1074  // We will try just correcting the first case, width less than 100 and some unspecified columns to add it to.
1075  if ($width < 100) {
1076  if ($noSpecifiedWidthCount > 0) {
1077  // We need to add width to columns that did not specify it.
1078  foreach ($columns as $column) {
1079  if (!$column->hasFlag('width')) {
1080  $modifyColumn = $this->getColumn($column->getId());
1081  $modifyColumn->addFlag('width', round((100 - $width)/$noSpecifiedWidthCount));
1082  unset($modifyColumn);
1083  }
1084  }
1085  }
1086  }
1087  }
1088 
1093  private function _addFeatures($features) {
1094  assert(is_array($features));
1095  foreach ($features as &$feature) {
1096  assert(is_a($feature, 'GridFeature'));
1097  $this->_features[$feature->getId()] = $feature;
1098  }
1099  }
1100 }
1101 ?>
GridHandler($dataProvider=null)
addPolicy($authorizationPolicy, $addToTop=false)
hasColumn($columnId)
renderRowsInternally($request, &$elements)
setDataElementSequence($request, $rowId, &$gridDataElement, $newSequence)
saveSequence($args, $request)
renderGridBodyPartsInternally($request)
isDataElementSelected($gridDataElement)
getActions($position=GRID_ACTION_POSITION_ABOVE)
static requireComponents()
Base class for a grid column&#39;s cell provider.
getRowDataElement($request, $rowId)
setEmptyRowText($emptyRowText)
setTemplate($template)
fetchRow(&$args, $request)
addAction($action, $position=GRID_ACTION_POSITION_ABOVE)
addColumn($column)
Class defining basic operations for handling HTML grids.
fetchGrid($args, $request)
authorize($request, &$args, $roleAssignments, $enforceRestrictedSite=true)
getRowsSequence($request)
loadData($request, $filter)
getColumnsCount($flag)
setGridDataElements($data)
renderRowInternally($request, $row)
Class to represent a JSON (Javascript Object Notation) message.
static getDataChangedEvent($elementId=null, $parentElementId=null)
Definition: DAO.inc.php:606
Class defining basic operations for handling HTML gridRows.
Definition: GridRow.inc.php:25
& getDataElementFromRequest($request, &$elementId)
& getGridDataElements($request)
Class for accessing the underlying template engine. Currently integrated with Smarty (from http://sma...
setUrls($request, $extraUrls=array())
renderFilter($request, $filterData=array())
doSpecificFetchGridActions($args, $request, $templateMgr)
getRequestedRow($request, $args)
initialize($request, $args=null)
initFeatures($request, &$args)
getFilterSelectionData($request)
getDataElementSequence(&$gridDataElement)
getColumn($columnId)
& getAuthorizedContext()
hasGridDataElements($request)
setInstructions($instructions)
& getColumnsByFlag($flag)
getGridRangeInfo($request, $rangeName, $contextData=null)
callFeaturesHook($hookName, $args)
fetchCell(&$args, $request)
renderRow($request, $row)
setFootNote($footNote)