Framework  3.9
related_item_select_field_renderer.inc
Go to the documentation of this file.
1 <?php
6 /**************************************************************
7 
8  Copyright (c) 2007-2010 Sonjara, Inc
9 
10  Permission is hereby granted, free of charge, to any person
11  obtaining a copy of this software and associated documentation
12  files (the "Software"), to deal in the Software without
13  restriction, including without limitation the rights to use,
14  copy, modify, merge, publish, distribute, sublicense, and/or sell
15  copies of the Software, and to permit persons to whom the
16  Software is furnished to do so, subject to the following
17  conditions:
18 
19  The above copyright notice and this permission notice shall be
20  included in all copies or substantial portions of the Software.
21 
22  Except as contained in this notice, the name(s) of the above
23  copyright holders shall not be used in advertising or otherwise
24  to promote the sale, use or other dealings in this Software
25  without prior written authorization.
26 
27  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
29  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
31  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
32  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
33  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
34  OTHER DEALINGS IN THE SOFTWARE.
35 
36 *****************************************************************/
37 
38 require_once realpath(dirname(__FILE__))."/../field_renderers.inc";
39 
45 class RelatedItemSelectFieldRenderer extends FieldRenderer
46 {
47  var $items;
48  var $default;
49  var $field;
50  var $nameField;
51  var $valueField;
52  var $allowAdd;
53  var $relatedClass;
54  var $constraint;
55  var $max_chars;
56  var $allowNone;
57  var $templateItem;
58  var $onItemAdded;
59  var $sorted;
60  var $otherField;
61  var $onChange;
62  var $width;
63  var $defaultText = "";
64  var $defaultReadOnlyText;
65  var $toggleAddNew;
66  var $addNewButtonText = "Add New";
67  var $addNewCancelButtonText = "Cancel";
68  var $onPostProcess;
69 
70  static $optionsCache = array();
71 
114  function RelatedItemSelectFieldRenderer(&$form, $field, $label, $relatedClass, $constraint, $nameField, $valueField = "", $allowAdd = false, $allowNone = false, $maxChars = 80)
115  {
116  $this->relatedClass = $relatedClass;
117  $this->constraint = $constraint;
118  $this->field = $field;
119  $this->nameField = $nameField;
120  $this->valueField = $valueField;
121  $this->allowAdd = $allowAdd;
122  $this->allowNone = $allowNone;
123  $this->max_chars = $maxChars;
124  $this->templateItem = null;
125  $this->sorted = false;
126  $this->toggleAddNew = false;
127 
128  $this->FieldRenderer($form);
129  if ($form->data->hasField($field))
130  {
131  $form->override($field, $label, $this);
132  }
133  else
134  {
135  $form->add($this, $field);
136  // JDG 8/14/2011 - allow "additional" fields to override label
137  $form->overrides[$field]['label'] = $label;
138  }
139  }
140 
141  function allowOther($field)
142  {
143  $this->allowAdd = false; // Cannot have 'add' and 'other' at the same time
144  $this->otherField = $field;
145  }
146 
152  function showAddNewToggle($addNew = "Add New", $cancel = "Cancel")
153  {
154  $this->toggleAddNew = true;
155  $this->addNewButtonText = $addNew;
156  $this->addNewCancelButtonText = $cancel;
157  }
158 
159  function renderScript($field)
160  {
161  $id = "{$this->parent->id}_{$field}";
162 ?>
163 <script type='text/javascript'>
164 function <?php echo $id?>_onChange(elt)
165 {
166  var addNewButton = document.id('<?echo $id?>_addnew_button');
167  if (addNewButton)
168  {
169  if (elt.value == '')
170  {
171  addNewButton.setStyle('display', 'inline-block');
172  }
173  else
174  {
175  addNewButton.setStyle('display', 'none');
176  }
177  }
178 <?
179  if ($this->onChange)
180  {
181 ?>
182  return <?php echo $this->onChange?>(elt);
183 <?php
184  }
185 ?>
186 }
187 <?php
188  if ($this->toggleAddNew)
189  {
190 ?>
191 function <?php echo $id?>_showAddNew()
192 {
193  var addNew = document.id('<?php echo $id?>_addnew');
194  var select = document.id('<?php echo $id?>_select');
195  var text = document.id('<?php echo $id?>_addnew_text');
196  addNew.setStyle('display', 'inline-block');
197  select.setStyle('display', 'none');
198  text.focus();
199 }
200 
201 function <?php echo $id?>_hideAddNew()
202 {
203  var addNew = document.id('<?php echo $id?>_addnew');
204  var select = document.id('<?php echo $id?>_select');
205  var elt = document.id('<?php echo $id?>');
206  addNew.setStyle('display', 'none');
207  select.setStyle('display', 'inline-block');
208  elt.focus();
209 }
210 <?php
211  }
212 ?>
213 </script>
214 <?php
215  }
216 
217  function renderField($field)
218  {
219  $this->getRelatedItems();
220 
221  if($this->width)
222  $style = "style='width: {$this->width}'";
223  $this->_startField($field);
224 
225  $id = "{$this->parent->id}_{$field}";
226 
227  if ($this->allowAdd)
228  {
229  echo "<span id='{$id}_select'>";
230  }
231  echo "<select $style id='{$id}' name='$field'";
232 
233  echo " onchange='{$id}_onChange(this);'";
234 
235  echo ">\n";
236 
237  $vals = array();
238  foreach($this->items as $item)
239  {
240  $name = $this->formatName($item, $this->nameField);
241  $valueField = ($this->valueField != "") ? $this->valueField : $item->getPrimaryKey();
242  $value = $item->get($valueField);
243  if (!$value) $name = $this->defaultText;
244 
245  $vals[$name] = $value;
246  }
247 
248  if ($this->sorted)
249  {
250  ksort($vals, SORT_LOCALE_STRING);
251  }
252 
253  $current = $this->parent->data->get($field);
254 
255  foreach($vals as $name => $value)
256  {
257 
258  $name = htmlSafe($name);
259  $trunced = $this->max_chars ? ellipsis($name, $this->max_chars, true) : $name;
260  //trace("valueField: $valueField", 3);
261 
262  echo "<option value='{$value}'";
263 
264  if ($trunced != $name) echo " title='$name'";
265 
266  /*
267  * If this form is new and there is a default, then
268  * set the default as selected. If this form is
269  * retrieved or loaded from a saved record, then
270  * set selected to the field value of that record
271  */
272  if (($current != NULL) AND ($value == $current))
273  echo " selected";
274  elseif (($current == NULL) AND ($value != NULL) AND ($value == $default))
275  echo " selected";
276  echo ">$trunced</option>\n";
277  }
278  echo "</select>";
279 
280  if ($this->allowAdd)
281  {
282  if ($this->toggleAddNew)
283  {
284  echo " <a id='{$this->parent->id}_{$field}_addnew_button' href='#' class='button' onclick='{$id}_showAddNew(); return false'>{$this->addNewButtonText}</a></span>";
285  echo "<span id='{$this->parent->id}_{$field}_addnew' style='display: none; white-space: nowrap'><input type='text' id='{$id}_addnew_text' name='autoform_add_$field' value='' size='30'>";
286  echo " <a id='{$this->parent->id}_{$field}_cancel_button' href='#' class='button' onclick='{$id}_hideAddNew(); return false'>{$this->addNewCancelButtonText}</a></span>";
287  }
288  else
289  {
290  echo "</span>";
291  echo "<span id={$this->parent->id}_{$field}_addnew' style='white-space: nowrap'> or add new: <input type='text' name='autoform_add_$field' value='' size='30'></span>";
292  }
293  }
294  else if ($this->otherField)
295  {
296  $otherField = $this->otherField;
297  $filter = $this->parent->data->getFilter();
298 
299  $value = $this->parent->data->get($otherField);
300 
301  $pkField = $this->parent->data->getPrimaryKey();
302  if ($this->parent->data->get($pkField) &&
303  $filter &&
304  $filter->isExcluded($this->otherField))
305  {
306  $obj = clone($this->parent->data);
307  $obj->setFilter(new InclusionFilter($obj->getPrimaryKey(), $this->otherField));
308  $obj->select();
309  $value = $obj->get($otherField);
310  }
311  else
312  {
313  $value = $this->parent->data->get($otherField);
314  }
315 
316  echo "<span style='white-space: nowrap'> if other, please specify: <input type='text' name='{$otherField}' value='{$value}' size='30'/></span>";
317  }
318  $this->_endField($field);
319  }
320 
321  function renderSearchField($field, $mode)
322  {
323  if ($mode != "equal") return;
324 
325  $this->getRelatedItems();
326 
327  $this->_startField($field);
328 
329  if($this->width)
330  $style = "style='width: {$this->width}'";
331 
332  echo "<select name='{$field}:$mode' $style>\n";
333 
334  //AJG: Don't add double empty slots when allowNone is true
335  if (!$this->allowNone)
336  {
337  echo "<option value=''></option>\n";
338  }
339 
340  $value = $this->parent->params->get($field, $mode);
341 
342  foreach($this->items as $item)
343  {
344  $valueField = ($this->valueField != "") ? $this->valueField : $item->getPrimaryKey();
345  $name = $this->formatName($item, $this->nameField);
346 
347  echo "<option value='{$item->get($valueField)}'";
348  if ($item->get($valueField) == $value) echo " selected";
349  echo ">".ellipsis($name, $this->max_chars)."</option>\n";
350  }
351  echo "</select>";
352 
353  $this->_endField($field);
354  }
355 
356  function renderReadOnly($field)
357  {
358  $item = $this->getSelectedItem($field);
359 
360  $this->_startField($field);
361 
362  trace("RelatedItemSelectFieldRenderer renderReadOnly name field is {$this->nameField} field is $field and item is {$item->role}", 3);
363  if (!$item->getPrimaryKeyValue())
364  {
365  trace("Using default readonly text '{$this->defaultReadOnlyText}'", 3);
366  $name = $this->defaultReadOnlyText;
367  }
368  else
369  {
370  $name = $this->formatName($item, $this->nameField);
371  }
372 
373  if (!$this->parent->readOnlyForm)
374  {
375  $valueField = ($this->valueField != "") ? $this->valueField : $item->getPrimaryKey();
376  $value = $item->get($valueField);
377  echo "<input type='hidden' id='{$this->parent->id}_{$field}' name='$field' value='$value'/>";
378  }
379 
380  echo $name;
381 
382  $this->_endField($field);
383 
384  }
385 
386  function getRelatedItems($readonly = false)
387  {
388  $cacheKey = "$this->relatedClass, $this->constraint";
389 
390  if (array_key_exists($cacheKey, RelatedItemSelectFieldRenderer::$optionsCache))
391  {
392  $this->items = RelatedItemSelectFieldRenderer::$optionsCache[$cacheKey];
393  }
394  else
395  {
396  $this->items = query($this->relatedClass, $this->constraint);
397  RelatedItemSelectFieldRenderer::$optionsCache[$cacheKey] = $this->items;
398  }
399 
400  if ($this->allowNone)
401  {
402  $none = new $this->relatedClass;
403  if (is_string($this->allowNone))
404  {
405  $nameField = $this->nameField;
406  if ($readonly)
407  {
408  $none->set($nameField, $this->defaultReadOnlyText);
409  }
410  else if ($this->defaultText)
411  {
412  $none->set($nameField, $this->defaultText);
413  }
414  else
415  {
416  $none->set($nameField, $this->allowNone);
417  }
418  }
419  $this->items = array_merge(array($none), $this->items);
420  }
421  return $this->items;
422  }
423 
424  function getSelectedItem($field)
425  {
426  $item = new $this->relatedClass;
427  $valueField = $this->valueField ? $this->valueField : $item->getPrimaryKey();
428  $value = $this->parent->data->get($field);
429 
430  try
431  {
432  $item = Query::create($this->relatedClass, "WHERE {$valueField}=:v")
433  ->bind(":v", $value)
434  ->executeSingle();
435  }
436  catch(DataNotFoundException $e)
437  {
438  $item = new $this->relatedClass;
439  }
440 
441  return $item;
442  }
443 
444  function preProcess($field = "")
445  {
446  trace("RelatedItemSelectFieldRenderer::process()", 3);
447 
448  $item = new $this->relatedClass;
449 
450  if ($this->otherField)
451  {
452  $filter = $this->parent->data->getFilter();
453 
454  // Ensure that the 'other' field value gets written
455  if ($filter)
456  {
457  $filter->includeField($this->otherField);
458  $otherField = $this->otherField;
459  $this->parent->data->set($otherField, isset($_POST[$otherField]) ? $_POST[$otherField] : "");
460  }
461  }
462 
463  if (!$this->allowAdd) return;
464  $valueField = $valueField ? $this->valueField : $item->getPrimaryKey();
465  $field = "autoform_add_{$this->field}";
466  $val = $_POST[$field];
467 
468  trace("Checking for value '$val' in field $field", 3);
469 
470  if ($val)
471  {
472  foreach($this->items as $item)
473  {
474  if ($val == $this->formatName($item, $this->nameField))
475  {
476  $this->data->set($valueField, $item->get($valueField));
477  return;
478  }
479  }
480 
481  $obj = ($this->templateItem) ? $this->templateItem : new $this->relatedClass;
482  $pk = $obj->getPrimaryKey();
483 
484  $nameField = $this->nameField;
485 
486  $obj->set($nameField, $val);
487  $obj->save();
488 
489  if ($this->onItemAdded)
490  {
491  call_user_func($this->onItemAdded, $this->parent, $obj);
492  }
493 
494  $this->parent->data->set($this->field, $obj->get($pk));
495  }
496  }
497 
498 
499  function postProcess($field = "")
500  {
501  if (is_callable($this->onPostProcess))
502  {
503  trace("*** Calling RelatedItemSelectFieldRenderer::\$onPostProcess", 3);
504  call_user_func($this->onPostProcess, $this->parent);
505  }
506  }
507 }
508 ?>
FieldRenderer is the abstract base class for all FieldRenderers.
_startField($field, $styles="")
Internal method to generate the starting HTML for the field (including the label)
preProcess($field="")
FieldRenderers can override this method to provide behavior that occurs prior to the saving of the pa...
renderField($field)
FieldRenderers must override this method to provide the HTML implementation of the control used to ed...
formatName($item, $name)
Formats the given DataItem based on the supplied format string.
_endField($field)
Internal method to generate the closing HTML for the field.
renderScript($field)
FieldRenderers can override this method to provide any Javascript that their control requires for an ...
renderSearchField($field, $mode)
FieldRenderers must override this method to provide the HTML implementation of the control displayed ...
$onPostProcess
callback hook for processing after saving the form's data object - individual renderers may override ...
FieldRenderer($parent)
Constructor.
postProcess($field="")
FieldRenderers can override this method to provide behavior that occurs after the parent form's targe...
Used to place a filter on the contents of a DataItem-derived object.
static create($class, $constraints="")
Static factory method to create a new Query.
Definition: query.inc:358
trace($msg, $lvl=3, $callStack=null)
Send output to the trace log.
Definition: functions.inc:1010
ellipsis($txt, $max, $wholeWord=false)
Truncate the supplied text at the given maximum length.
Definition: functions.inc:779
query($class)
Performs a query against the database, returning an array of DataItem objects of the specified class.
Definition: query.inc:373