Framework  3.9
spreadsheet_form.inc
Go to the documentation of this file.
1 <?php
5 /**************************************************************
6 
7  Copyright (c) 2007-2010 Sonjara, Inc
8 
9  Permission is hereby granted, free of charge, to any person
10  obtaining a copy of this software and associated documentation
11  files (the "Software"), to deal in the Software without
12  restriction, including without limitation the rights to use,
13  copy, modify, merge, publish, distribute, sublicense, and/or sell
14  copies of the Software, and to permit persons to whom the
15  Software is furnished to do so, subject to the following
16  conditions:
17 
18  The above copyright notice and this permission notice shall be
19  included in all copies or substantial portions of the Software.
20 
21  Except as contained in this notice, the name(s) of the above
22  copyright holders shall not be used in advertising or otherwise
23  to promote the sale, use or other dealings in this Software
24  without prior written authorization.
25 
26  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
28  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
30  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
31  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
32  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
33  OTHER DEALINGS IN THE SOFTWARE.
34 
35 *****************************************************************/
36 
37 require_once realpath(dirname(__FILE__)."/composite_auto_form.inc");
38 
49 {
50  var $colCount = 0;
51  var $subform;
52  var $columnWidths = array();
53  var $msg;
54  var $onStartRow = null;
55  var $isRowSelected = null;
56  var $isRowEmpty = null;
57  var $rowLabelFormat = null;
58  var $rowLabelTitle = null;
60  var $emptyMessage = "The spreadsheet is empty";
61  var $onFormChanged = null;
62  var $ajaxSubmitHandler = false;
63  var $ajaxFailureHandler = false;
64 
90  function SpreadsheetForm($subform, $items = array(), $blanks = 5, $method="POST", $action="", $id="", $onStartRow = null, $isRowSelected = null)
91  {
92  $this->subform = $subform;
93  $this->onStartRow = $onStartRow;
94  $this->isRowSelected = $isRowSelected;
95 
96  // Create one instance of AutoForm to apply certain setting
97  // for all forms, e.g., aliases and hiddens
98  parent::CompositeAutoForm($method, $action);
99  $this->formCSS = "list spreadsheet";
100  $this->layout = AutoFormLayout::create("spreadsheet", $this);
101 
102  $target = $subform->data;
103 
104  $this->id = get_class($target)."_form";
105 
106  for($i=0; $i < $blanks; $i++)
107  {
108  $items[] = clone($target);
109  }
110 
111  $this->createSpreadsheetRowForms($items, $subform);
112  }
113 
114  function showRowLabels($columnTitle, $format, $defaultText)
115  {
116  $this->rowLabelTitle = $columnTitle;
117  $this->rowLabelFormat = $format;
118  $this->rowLabelDefaultText = $defaultText;
119  }
120 
122  {
123  $subform->subordinate = true;
124  $subform->layout = AutoFormLayout::create("table_row", $subform);
125 
126  // Don't allow HTMLEditor in spreadsheet form,
127  // override with Text Editor
128  $fields = $subform->data->getFields();
129  foreach($fields as $field => $type)
130  {
131  $renderer = $subform->getRenderer($field);
132  if(get_class($renderer) == "HTMLFieldRenderer")
133  {
134  $subform->override($field, "", new TextFieldRenderer($subform));
135  }
136  }
137 
138  $idx = 1;
139  foreach($items as $item)
140  {
141  $this->createOneSpreadsheetRow($item, clone($subform), clone($subform->validator), $idx);
142  $idx++;
143  }
144  }
145 
146  function createOneSpreadsheetRow($item, $row, $validator, $idx)
147  {
148  $layout = "table_row";
149 
150  if ($this->onStartRow)
151  {
152  $ret = call_user_func($this->onStartRow, $item);
153  }
154 
155  // If the row callback explicitly returns false, omit this row
156  if ($ret === false) return;
157 
158  $row->data =& $item;
159  $row->id = $this->id . "_$idx";
160  $row->validator = $validator;
161  $row->validator->id = $row->id;
162  if ($ret) $row->formCSS = $ret;
163 
164 
165  $fields = $row->data->getFields();
166  // clone a new instance of each field renderer
167  // created for the template subform.
168  foreach($fields as $field => $type)
169  {
170  if ($item->filter && $item->filter->isExcluded($field)) continue;
171 
172  $r = $row->getRenderer($field);
173  if($r)
174  {
175  $renderer = clone($r);
176  $renderer->parent = $row;
177  $row->override($field, "", $renderer);
178  }
179  }
180 
181  $additional = array();
182  foreach($row->additional as $r)
183  {
184  $renderer = $r['renderer'];
185  // JDG 5/24/11 - fix get field name
186  $r2 = clone($renderer);
187  $r2->parent = &$row;
188  $r['renderer'] = $r2;
189 
190  $additional[] = $r;
191 
192  trace("#@#@#@ ID: {$r2->parent->id}", 3);
193  $row->override($r['field'], $r['label'], $r2);
194  }
195 
196  $row->additional = $additional;
197 
198  trace(print_r($row->additional, true), 3);
199 
200  //$form = SubordinateAutoForm::create($this, $row, true);
201  $subform = new SubordinateAutoForm($this, $row, true);
202 
203  if ($this->isRowSelected)
204  {
205  $layout = "selectable_table_row";
206  $subform->isSelected = call_user_func($this->isRowSelected, $item);
207  }
208 
209  $subform->form->layout = AutoFormLayout::create($layout, $subform);
210 
211  if ($ret)
212  {
213  $subform->containerClass = trim($this->containerClass. " " . $ret);
214  }
215  }
216 
223  function setColumnWidth($field, $width)
224  {
225  $this->columnWidths[$field] = $width;
226  }
227 
234  function ajaxSubmit($success, $failure = null)
235  {
236  $id = $this->id ? $this->id : "composite_auto_form";
237  if ($failure == null)
238  {
239  $failure = "function() {document.id('{$id}_error').set('text','Failed to communicate with server'); }";
240  }
241 
242  $this->ajaxSubmitHandler = $success;
243  $this->ajaxFailureHandler = $failure;
244  }
245 
249  function writeScript()
250  {
251  if (!count($this->forms)) return;
252 
253  $script = parent::writeScript();
254 
255  $id = $this->id ? $this->id : "composite_auto_form";
256 
257  if ($this->onFormChanged) $options = "onFormChanged: {$this->onFormChanged}";
258 
259  $script .= <<<ENDSCRIPT
260  <script type='text/javascript'>
261  window.addEvent('domready', function()
262  {
263  document.id('{$id}').manager = new SpreadsheetFormManager('{$id}', {{$options}});
264  });
265  </script>
266 ENDSCRIPT;
267 
268  if ($this->ajaxSubmitHandler && !$this->readOnlyForm)
269  {
270  $script .= <<<ENDSCRIPT
271  <script>
272  document.id('{$id}').iFrameFormRequest(
273  {
274  'onRequest': function() { return onCompositeFormSubmit(document.id('{$id}')); },
275  'onComplete': {$this->ajaxSubmitHandler},
276  'onFailure': {$this->ajaxFailureHandler}
277  });
278  </script>
279 ENDSCRIPT;
280  }
281 
282  return $script;
283  }
284 
289  function drawForm()
290  {
291  if (!count($this->forms))
292  {
293  echo $this->emptyMessage;
294  return;
295  }
296 
297  $id = $this->id ? $this->id : "composite_auto_form";
298 
299  echo "<form id='$id' method='{$this->method}' action='{$this->action}' enctype='multipart/form-data'";
300  echo " onsubmit='return onCompositeFormSubmit(this);'";
301  echo ">\n";
302 
303  $layout = ($this->isRowSelected) ? "selectable_table_header" : "table_header";
304  $form = $this->subform;
305  $form->layout = AutoFormLayout::create($layout, $form);
306  $obj =& $form->getData();
307  $pk = $obj->getPrimaryKey();
308 
309  $filter = $obj->getFilter();
310  $fields = $obj->getFields();
311 
312 
313  if ($this->layout->externalErrorBox) $this->layout->errorBox();
314 
315  if ($this->buttons_at_top)
316  {
317  echo "<p>";
318  $this->drawButtons();
319  echo "</p>";
320  }
321 
322  if (!$this->layout->externalErrorBox) $this->layout->errorBox();
323 
324  if ($form->markRequiredFields && $form->validator->hasRequiredFields())
325  {
326  $this->layout->requiredFields($form->requiredFieldsText);
327  }
328 
329  $this->layout->startUngrouped();
330 
331  // Draw headings
332  echo "<thead><tr>\n";
333 
334  if ($this->isRowSelected)
335  {
336  echo "<th>&nbsp;</th>";
337  }
338 
339  if ($this->rowLabelTitle)
340  {
341  echo "<th>{$this->rowLabelTitle}</th>";
342  }
343 
344  foreach($fields as $field => $type)
345  {
346  if ($field != $pk && !array_key_exists($field, $form->hidden) && !($filter && $filter->isExcluded($field)))
347  {
348  $renderer = $form->getRenderer($field);
349  if($renderer)
350  {
351  $styles = "text-align: center;";
352  if(array_key_exists($field, $this->columnWidths))
353  {
354  $width = $this->columnWidths[$field];
355  if ($width)
356  {
357  if (is_numeric($width))
358  {
359  $styles .= " width: {$width}px";
360  }
361  else
362  {
363  $styles .= "width: {$width}";
364  }
365  }
366  }
367  $renderer->_printLabel($field, 1, $styles);
368  $this->colCount++;
369  }
370  }
371  }
372 
373  foreach($form->additional as $r)
374  {
375  $renderer = $r['renderer'];
376  // JDG 5/24/11 - fix get field name
377  $field = $r['field'];
378  if(!$field)
379  $field = $renderer->field;
380 
381  if(array_key_exists($field, $form->hidden))
382  continue;
383 
384  $styles = "text-align: center;";
385  if(array_key_exists($field, $this->columnWidths))
386  {
387  $width = $this->columnWidths[$field];
388  if ($width)
389  {
390  if (is_numeric($width))
391  {
392  $styles .= " width: {$width}px";
393  }
394  else
395  {
396  $styles .= "width: {$width}";
397  }
398  }
399  }
400  $renderer->_printLabel($field, 1, $styles);
401  $this->colCount++;
402  }
403  echo "</tr></thead>\n";
404 
405  foreach($this->forms as $form)
406  {
407  ob_start();
408  $form->drawForm();
409  $output = ob_get_contents();
410  ob_end_clean();
411 
412  $output = preg_replace("/\\bname=(['\"])([^'\"]*?)['\"]/", "name=$1{$form->id}__$2$1", $output);
413 
414  if ($this->rowLabelTitle)
415  {
416  $output = preg_replace("/<tr id=(.*?)>/", "<tr id=$1><td>".($form->getData()->format($this->rowLabelFormat))."</td>", $output);
417  }
418 
419  //$form->form->layout->startUngrouped();
420  echo $output;
421  //$form->form->layout->endUngrouped();
422  }
423 
424  $this->layout->endUngrouped();
425  $this->drawButtons();
426  echo "</form>\n";
427  }
428 
433  function save()
434  {
435  global $method;
436 
437  // Do magic
438 
439  $valid = true;
440 
441 
442  foreach($this->forms as $form)
443  {
444  if ($this->isRowSelected != null)
445  {
446  // Selectable mode - skip form save if not selected
447 
448  if (!$_POST[$form->id."__#selected"])
449  {
450  trace("SKIPPING {$form->id}", 4);
451  continue;
452  }
453  }
454 
455  foreach($_POST as $name => $value)
456  {
457  if (strpos($name, "__") === false)
458  {
459  unset($_POST[$name]);
460  }
461  }
462 
463  $prefix = $form->id."__";
464  $len = strlen($prefix);
465 
466  foreach($_POST as $name => $value)
467  {
468  if (!strncmp($name, $prefix, $len))
469  {
470  $_POST[substr($name, $len)] = $value;
471  }
472  }
473 
474  if (is_callable($this->isRowEmpty))
475  {
476  if (call_user_func($this->isRowEmpty)) continue;
477  }
478 
479  if (!$form->save()) $valid = false;
480  }
481 
482  return $valid;
483  }
484  /*
485  * Create a queryString containing all the
486  * keys of the objects that saved successfully
487  * to use to reload the page after save.
488  */
489  function getQueryString($qs = "")
490  {
491  $values = array();
492 
493  foreach($this->forms as $form)
494  {
495  $obj = $form->getData();
496  $pk = $obj->getPrimaryKey();
497  $val = $obj->$pk;
498  if($val)
499  {
500  $name = "{$form->id}_$pk}";
501  $params .= $sep.urlencode($pk)."[".urlencode($name)."]=".urlencode($val);
502  $sep = "&";
503  }
504  }
505 
506  if($params && $qs)
507  $params = appendToQueryString($qs, $params);
508 
509  return $params;
510  }
511 }
512 ?>
static create($type, $form)
CompositeAutoForm is a container for situations where multiple AutoForms need to be managed on the sa...
drawButtons()
Draws any additional buttons specified in the calling script.
SpreadsheetForm.
$onStartRow
Callback hook to determine row style or suppression of row.
showRowLabels($columnTitle, $format, $defaultText)
SpreadsheetForm($subform, $items=array(), $blanks=5, $method="POST", $action="", $id="", $onStartRow=null, $isRowSelected=null)
Creates a new SpreadsheetForm.
$isRowSelected
Callback hook to determine whether a given row is selected. Assigning a callback to this hook turns o...
$onFormChanged
Javascript function to be called when any value is changed within the spreadsheet.
createOneSpreadsheetRow($item, $row, $validator, $idx)
$emptyMessage
The message to display if the item list is empty and the number of blank rows is set to zero.
$ajaxSubmitHandler
JavaScript Callback for AJAX Submit mode - called on success.
$columnWidths
Array of column widths, used to control relative cell sizes in the table output.
save()
Overrides CompositeAutoForm::save() to provide support for selectable spreadsheet forms.
$isRowEmpty
Callback hook to determine if a given row should be saved, or skipped because it is an empty row.
drawForm()
Draws the spreadsheet.
setColumnWidth($field, $width)
Set the td width for the table.
createSpreadsheetRowForms($items, $subform)
$subform
The prototype subform used to model each row in the spreadsheet.
$msg
Text to appear below buttons at top and above table.
$ajaxFailureHandler
JavaScript Callback for AJAX Submit mode - called on failure.
writeScript()
Generates the Javascript for all the subforms, and creates a SpreadsheetFormManager object.
ajaxSubmit($success, $failure=null)
Put the form into AJAX submission mode.
Field renderer for text data fields.
trace($msg, $lvl=3, $callStack=null)
Send output to the trace log.
Definition: functions.inc:1010
appendToQueryString($qs, $params)
Appends the specified parameters to the supplied query string.
Definition: functions.inc:1594