FormExtHelper.php 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  1. <?php
  2. //App::import('Helper', 'Tools.Form');
  3. App::uses('FormHelper', 'View/Helper');
  4. /**
  5. * Enhance Forms with JS widget stuff
  6. * TODO: namespace change: make it HtmlHelper
  7. *
  8. * FormExtHelper
  9. * 2011-03-07 ms
  10. */
  11. class FormExtHelper extends FormHelper { // Maybe FormHelper itself some day?
  12. public $helpers = array('Html', 'Js', 'Tools.Common');
  13. public $settings = array(
  14. 'webroot' => true # false => tools plugin
  15. );
  16. public $scriptsAdded = array(
  17. 'date' => false,
  18. 'time' => false,
  19. 'maxLength' => false,
  20. 'autoComplete' => false
  21. );
  22. public function __construct($View = null, $settings = array()) {
  23. if (($webroot = Configure::read('Asset.webroot')) !== null) {
  24. $this->settings['webroot'] = $webroot;
  25. }
  26. parent::__construct($View, $settings);
  27. }
  28. /**
  29. * fix for required adding (only manually)
  30. * 2011-11-01 ms
  31. */
  32. protected function _introspectModel($model, $key, $field = null) {
  33. if ($key === 'validates' && Configure::read('Validation.autoRequire') === false) {
  34. return false;
  35. }
  36. return parent::_introspectModel($model, $key, $field);
  37. }
  38. public function postLink($title, $url = null, $options = array(), $confirmMessage = false) {
  39. if (!isset($options['class'])) {
  40. $options['class'] = 'postLink';
  41. }
  42. return parent::postLink($title, $url ,$options, $confirmMessage);
  43. }
  44. /**
  45. * Generates a form input element complete with label and wrapper div
  46. * HTML 5 ready!
  47. *
  48. * ### Options
  49. *
  50. * See each field type method for more information. Any options that are part of
  51. * $attributes or $options for the different **type** methods can be included in `$options` for input().
  52. *
  53. * - `type` - Force the type of widget you want. e.g. `type => 'select'`
  54. * - `label` - Either a string label, or an array of options for the label. See FormHelper::label()
  55. * - `div` - Either `false` to disable the div, or an array of options for the div.
  56. * See HtmlHelper::div() for more options.
  57. * - `options` - for widgets that take options e.g. radio, select
  58. * - `error` - control the error message that is produced
  59. * - `empty` - String or boolean to enable empty select box options.
  60. * - `before` - Content to place before the label + input.
  61. * - `after` - Content to place after the label + input.
  62. * - `between` - Content to place between the label + input.
  63. * - `format` - format template for element order. Any element that is not in the array, will not be in the output.
  64. * - Default input format order: array('before', 'label', 'between', 'input', 'after', 'error')
  65. * - Default checkbox format order: array('before', 'input', 'between', 'label', 'after', 'error')
  66. * - Hidden input will not be formatted
  67. * - Radio buttons cannot have the order of input and label elements controlled with these settings.
  68. *
  69. * @param string $fieldName This should be "Modelname.fieldname"
  70. * @param array $options Each type of input takes different options.
  71. * @return string Completed form widget.
  72. * @access public
  73. * @link http://book.cakephp.org/view/1390/Automagic-Form-Elements
  74. */
  75. public function inputExt($fieldName, $options = array()) {
  76. //$this->setEntity($fieldName);
  77. $options = array_merge(
  78. array('before' => null, 'between' => null, 'after' => null, 'format' => null),
  79. $this->_inputDefaults,
  80. $options
  81. );
  82. $modelKey = $this->model();
  83. $fieldKey = $this->field();
  84. if (!isset($this->fieldset[$modelKey])) {
  85. $this->_introspectModel($modelKey);
  86. }
  87. if (!isset($options['type'])) {
  88. $magicType = true;
  89. $options['type'] = 'text';
  90. if (isset($options['options'])) {
  91. $options['type'] = 'select';
  92. } elseif (in_array($fieldKey, array('color', 'email', 'number', 'range', 'url'))) {
  93. $options['type'] = $fieldKey;
  94. } elseif (in_array($fieldKey, array('psword', 'passwd', 'password'))) {
  95. $options['type'] = 'password';
  96. } elseif (isset($this->fieldset[$modelKey]['fields'][$fieldKey])) {
  97. $fieldDef = $this->fieldset[$modelKey]['fields'][$fieldKey];
  98. $type = $fieldDef['type'];
  99. $primaryKey = $this->fieldset[$modelKey]['key'];
  100. }
  101. if (isset($type)) {
  102. $map = array(
  103. 'string' => 'text', 'datetime' => 'datetime', 'boolean' => 'checkbox',
  104. 'timestamp' => 'datetime', 'text' => 'textarea', 'time' => 'time',
  105. 'date' => 'date', 'float' => 'text', 'integer' => 'number',
  106. );
  107. if (isset($this->map[$type])) {
  108. $options['type'] = $this->map[$type];
  109. } elseif (isset($map[$type])) {
  110. $options['type'] = $map[$type];
  111. }
  112. if ($fieldKey == $primaryKey) {
  113. $options['type'] = 'hidden';
  114. }
  115. }
  116. if (preg_match('/_id$/', $fieldKey) && $options['type'] !== 'hidden') {
  117. $options['type'] = 'select';
  118. }
  119. if ($modelKey === $fieldKey) {
  120. $options['type'] = 'select';
  121. if (!isset($options['multiple'])) {
  122. $options['multiple'] = 'multiple';
  123. }
  124. }
  125. }
  126. $types = array('checkbox', 'radio', 'select');
  127. if (
  128. (!isset($options['options']) && in_array($options['type'], $types)) ||
  129. (isset($magicType) && $options['type'] == 'text')
  130. ) {
  131. $varName = Inflector::variable(
  132. Inflector::pluralize(preg_replace('/_id$/', '', $fieldKey))
  133. );
  134. $varOptions = $this->_View->getVar($varName);
  135. if (is_array($varOptions)) {
  136. if ($options['type'] !== 'radio') {
  137. $options['type'] = 'select';
  138. }
  139. $options['options'] = $varOptions;
  140. }
  141. }
  142. $autoLength = (!array_key_exists('maxlength', $options) && isset($fieldDef['length']));
  143. if ($autoLength && $options['type'] == 'text') {
  144. $options['maxlength'] = $fieldDef['length'];
  145. }
  146. if ($autoLength && $fieldDef['type'] == 'float') {
  147. $options['maxlength'] = array_sum(explode(',', $fieldDef['length']))+1;
  148. }
  149. $divOptions = array();
  150. $div = $this->_extractOption('div', $options, true);
  151. unset($options['div']);
  152. if (!empty($div)) {
  153. $divOptions['class'] = 'input';
  154. $divOptions = $this->addClass($divOptions, $options['type']);
  155. if (is_string($div)) {
  156. $divOptions['class'] = $div;
  157. } elseif (is_array($div)) {
  158. $divOptions = array_merge($divOptions, $div);
  159. }
  160. if (
  161. isset($this->fieldset[$modelKey]) &&
  162. in_array($fieldKey, $this->fieldset[$modelKey]['validates'])
  163. ) {
  164. $divOptions = $this->addClass($divOptions, 'required');
  165. }
  166. if (!isset($divOptions['tag'])) {
  167. $divOptions['tag'] = 'div';
  168. }
  169. }
  170. $label = null;
  171. if (isset($options['label']) && $options['type'] !== 'radio') {
  172. $label = $options['label'];
  173. unset($options['label']);
  174. }
  175. if ($options['type'] === 'radio') {
  176. $label = false;
  177. if (isset($options['options'])) {
  178. $radioOptions = (array)$options['options'];
  179. unset($options['options']);
  180. }
  181. }
  182. if ($label !== false) {
  183. $label = $this->_inputLabel($fieldName, $label, $options);
  184. }
  185. $error = $this->_extractOption('error', $options, null);
  186. unset($options['error']);
  187. $selected = $this->_extractOption('selected', $options, null);
  188. unset($options['selected']);
  189. if (isset($options['rows']) || isset($options['cols'])) {
  190. $options['type'] = 'textarea';
  191. }
  192. if ($options['type'] === 'datetime' || $options['type'] === 'date' || $options['type'] === 'time' || $options['type'] === 'select') {
  193. $options += array('empty' => false);
  194. }
  195. if ($options['type'] === 'datetime' || $options['type'] === 'date' || $options['type'] === 'time') {
  196. $dateFormat = $this->_extractOption('dateFormat', $options, 'MDY');
  197. $timeFormat = $this->_extractOption('timeFormat', $options, 12);
  198. unset($options['dateFormat'], $options['timeFormat']);
  199. }
  200. if ($options['type'] === 'email') {
  201. }
  202. $type = $options['type'];
  203. $out = array_merge(
  204. array('before' => null, 'label' => null, 'between' => null, 'input' => null, 'after' => null, 'error' => null),
  205. array('before' => $options['before'], 'label' => $label, 'between' => $options['between'], 'after' => $options['after'])
  206. );
  207. $format = null;
  208. if (is_array($options['format']) && in_array('input', $options['format'])) {
  209. $format = $options['format'];
  210. }
  211. unset($options['type'], $options['before'], $options['between'], $options['after'], $options['format']);
  212. switch ($type) {
  213. case 'hidden':
  214. $input = $this->hidden($fieldName, $options);
  215. $format = array('input');
  216. unset($divOptions);
  217. break;
  218. case 'checkbox':
  219. $input = $this->checkbox($fieldName, $options);
  220. $format = $format ? $format : array('before', 'input', 'between', 'label', 'after', 'error');
  221. break;
  222. case 'radio':
  223. $input = $this->radio($fieldName, $radioOptions, $options);
  224. break;
  225. case 'select':
  226. $options += array('options' => array());
  227. $list = $options['options'];
  228. unset($options['options']);
  229. $input = $this->select($fieldName, $list, $selected, $options);
  230. break;
  231. case 'time':
  232. $input = $this->dateTime($fieldName, null, $timeFormat, $selected, $options);
  233. break;
  234. case 'date':
  235. $input = $this->dateTime($fieldName, $dateFormat, null, $selected, $options);
  236. break;
  237. case 'datetime':
  238. $input = $this->dateTime($fieldName, $dateFormat, $timeFormat, $selected, $options);
  239. break;
  240. case 'textarea':
  241. $input = $this->textarea($fieldName, $options + array('cols' => '30', 'rows' => '6'));
  242. break;
  243. case 'password':
  244. case 'file':
  245. $input = $this->{$type}($fieldName, $options);
  246. break;
  247. default:
  248. $options['type'] = $type;
  249. $input = $this->text($fieldName, $options);
  250. }
  251. if ($type != 'hidden' && $error !== false) {
  252. $errMsg = $this->error($fieldName, $error);
  253. if ($errMsg) {
  254. $divOptions = $this->addClass($divOptions, 'error');
  255. $out['error'] = $errMsg;
  256. }
  257. }
  258. $out['input'] = $input;
  259. $format = $format ? $format : array('before', 'label', 'between', 'input', 'after', 'error');
  260. $output = '';
  261. foreach ($format as $element) {
  262. $output .= $out[$element];
  263. unset($out[$element]);
  264. }
  265. if (!empty($divOptions['tag'])) {
  266. $tag = $divOptions['tag'];
  267. unset($divOptions['tag']);
  268. $output = $this->Html->tag($tag, $output, $divOptions);
  269. }
  270. return $output;
  271. }
  272. /**
  273. * override with some custom functionality
  274. * - html5 list/datalist (fallback = invisible)
  275. * 2011-07-16 ms
  276. */
  277. public function input($fieldName, $options = array()) {
  278. $this->setEntity($fieldName);
  279. $modelKey = $this->model();
  280. $fieldKey = $this->field();
  281. if (isset($options['datalist'])) {
  282. $options['autocomplete'] = 'off';
  283. if (!isset($options['list'])) {
  284. $options['list'] = ucfirst($fieldKey).'List';
  285. }
  286. $datalist = $options['datalist'];
  287. $list = '<datalist id="'.$options['list'].'">';
  288. //$list .= '<!--[if IE]><div style="display: none"><![endif]-->';
  289. foreach ($datalist as $key => $val) {
  290. if (!isset($options['escape']) || $options['escape'] !== false) {
  291. $key = h($key);
  292. $val = h($val);
  293. }
  294. $list .= '<option label="'.$val.'" value="'.$key.'"></option>';
  295. }
  296. //$list .= '<!--[if IE]></div><![endif]-->';
  297. $list .= '</datalist>';
  298. unset($options['datalist']);
  299. $options['after'] = !empty($options['after']) ? $options['after'].$list : $list;
  300. }
  301. if (isset($options['required'])) {
  302. $this->_introspectModel($modelKey, 'validates', $fieldKey);
  303. $this->fieldset[$modelKey]['validates'][$fieldKey] = $options['required'];
  304. if ($options['required'] === false) {
  305. $autoRequire = Configure::read('Validation.autoRequire');
  306. Configure::write('Validation.autoRequire', false);
  307. }
  308. //unset($options['require']);
  309. }
  310. if (Configure::read('Validation.browserAutoRequire') !== true) {
  311. if (!empty($options['required'])) {
  312. //$options['div']['class'] = !empty($options['div']['class']) ? $options['div']['class'].' required' : 'required';
  313. //$options['class'] = $this->addClass(isset($options['class'])?$options['class']:array(), 'required');
  314. /*
  315. $this->setEntity($fieldName);
  316. $modelKey = $this->model();
  317. $fieldKey = $this->field();
  318. $this->fieldset[$modelKey]['validates'][$fieldKey] = true;
  319. */
  320. }
  321. if (isset($options['required'])) {
  322. unset($options['required']);
  323. }
  324. }
  325. $res = parent::input($fieldName, $options);
  326. if (isset($autoRequire)) {
  327. Configure::write('Validation.autoRequire', $autoRequire);
  328. }
  329. return $res;
  330. }
  331. /** redirect **/
  332. /**
  333. * @param selectOptions:
  334. * - e.g: array('index'=>true/false, 'view'=>array('url'=>x, 'label'=>y), 'edit'=>'xyz', '/some/url'=>'some label')
  335. * 2010-05-02 ms
  336. */
  337. public function redirect($selectOptions = array(), $tagOptions = array()) {
  338. $options = array('index'=>'Zurück zur Übersicht', 'view'=>__('View %s', __('Record')), '-1'=>'Auf dieser Seite bleiben');
  339. foreach ($selectOptions as $key => $text) {
  340. if ($text === false) {
  341. # deactivate this one
  342. if (isset($options[$key])) {
  343. unset($options[$key]);
  344. }
  345. continue;
  346. } elseif ($text === true) {
  347. # leave it as it is
  348. } elseif (is_array($text)) {
  349. # own id and label?
  350. if (isset($text['url']) && isset($text['label'])) {
  351. if (isset($options[$key])) {
  352. unset($options[$key]);
  353. }
  354. $options[$text['url']] = $text['label'];
  355. }
  356. } else {
  357. # url => label
  358. $options[$key] = $text;
  359. }
  360. }
  361. //$options = array('4'=>'Zum Profil dieses Mitglieds');
  362. //$options = array('edit'=>__('Edit %s', __('Record')));
  363. //$options = array('add'=>'Einen weiteren Eintrag anlegen');
  364. //$options[-1] = 'Auf dieser Seite bleiben';
  365. return $this->input('Form.redirect', array('label'=>'Im Anschluss', 'options'=>$options), $tagOptions);
  366. }
  367. /** date(time) **/
  368. //TODO: use http://trentrichardson.com/examples/timepicker/
  369. // or maybe: http://pttimeselect.sourceforge.net/example/index.html (if 24 hour + select dropdowns are supported)
  370. /**
  371. * quicklinks: clear, today, ...
  372. * 2011-04-29 ms
  373. */
  374. public function dateScripts($scripts = array(), $quicklinks = false) {
  375. foreach ($scripts as $script) {
  376. if (!$this->scriptsAdded[$script]) {
  377. switch ($script) {
  378. case 'date':
  379. if ($this->settings['webroot']) {
  380. $this->Html->script('datepicker/lang/de', false);
  381. $this->Html->script('datepicker/datepicker', false);
  382. $this->Html->css('common/datepicker', null, array('inline'=>false));
  383. } else {
  384. $this->Common->script(array('Tools.Js|datepicker/lang/de', 'Tools.Js|datepicker/datepicker'), false);
  385. $this->Common->css(array('Tools.Js|datepicker/datepicker'), null, array('inline'=>false));
  386. }
  387. $this->scriptsAdded['date'] = true;
  388. break;
  389. case 'time':
  390. continue;
  391. if ($this->settings['webroot']) {
  392. } else {
  393. //'Tools.Jquery|ui/core/jquery.ui.core', 'Tools.Jquery|ui/core/jquery.ui.widget', 'Tools.Jquery|ui/widgets/jquery.ui.slider',
  394. $this->Common->script(array('Tools.Jquery|plugins/jquery.timepicker.core', 'Tools.Jquery|plugins/jquery.timepicker'), false);
  395. $this->Common->css(array('Tools.Jquery|ui/core/jquery.ui', 'Tools.Jquery|plugins/jquery.timepicker'), null, array('inline'=>false));
  396. }
  397. break;
  398. default:
  399. break;
  400. }
  401. if ($quicklinks) {
  402. }
  403. }
  404. }
  405. }
  406. public function dateTimeExt($field, $options = array()) {
  407. $res = array();
  408. if (!isset($options['separator'])) {
  409. $options['separator'] = null;
  410. }
  411. if (!isset($options['label'])) {
  412. $options['label'] = null;
  413. }
  414. if (strpos($field, '.') !== false) {
  415. list($model, $field) = explode('.', $field, 2);
  416. } else {
  417. $entity = $this->entity();
  418. $model = $this->model();
  419. }
  420. $defaultOptions = array(
  421. 'empty' => false,
  422. 'return' => true,
  423. );
  424. $customOptions = array_merge($defaultOptions, $options);
  425. $res[] = $this->date($field, $customOptions);
  426. $res[] = $this->time($field, $customOptions);
  427. $select = implode(' &nbsp; ', $res);
  428. //return $this->date($field, $options).$select;
  429. if ($this->isFieldError($field)) {
  430. $error = $this->error($field);
  431. } else {
  432. $error = '';
  433. }
  434. $fieldname = Inflector::camelize($field);
  435. $script = '
  436. <script type="text/javascript">
  437. // <![CDATA[
  438. var opts = {
  439. formElements:{"'.$model.$fieldname.'":"Y","'.$model.$fieldname.'-mm":"m","'.$model.$fieldname.'-dd":"d"},
  440. showWeeks:true,
  441. statusFormat:"l-cc-sp-d-sp-F-sp-Y",
  442. // Position the button within a wrapper span with an id of "button-wrapper"
  443. positioned:"button-wrapper"
  444. };
  445. datePickerController.createDatePicker(opts);
  446. // ]]>
  447. </script>
  448. ';
  449. return '<div class="input date'.(!empty($error)?' error':'').'">'.$this->label($model.'.'.$field, $options['label']).''.$select.''.$error.'</div>'.$script;
  450. }
  451. /**
  452. * @deprecated
  453. * use Form::dateExt
  454. */
  455. public function date($field, $options = array()) {
  456. return $this->dateExt($field, $options);
  457. }
  458. /**
  459. * date input (day, month, year) + js
  460. * @see http://www.frequency-decoder.com/2006/10/02/unobtrusive-date-picker-widgit-update/
  461. * @param field (field or Model.field)
  462. * @param options
  463. * - separator (between day, month, year)
  464. * - label
  465. * - empty
  466. * - disableDays (TODO!)
  467. * - minYear/maxYear (TODO!) / rangeLow/rangeHigh (xxxx-xx-xx or today)
  468. * 2010-01-20 ms
  469. */
  470. public function dateExt($field, $options = array()) {
  471. $return = false;
  472. if (isset($options['return'])) {
  473. $return = $options['return'];
  474. unset($options['return']);
  475. }
  476. $quicklinks = false;
  477. if (isset($options['quicklinks'])) {
  478. $quicklinks = $options['quicklinks'];
  479. unset($options['quicklinks']);
  480. }
  481. if (isset($options['callbacks'])) {
  482. $callbacks = $options['callbacks'];
  483. unset($options['callbacks']);
  484. }
  485. $this->dateScripts(array('date'), $quicklinks);
  486. $res = array();
  487. if (!isset($options['separator'])) {
  488. $options['separator'] = '-';
  489. }
  490. if (!isset($options['label'])) {
  491. $options['label'] = null;
  492. }
  493. if (isset($options['disableDays'])) {
  494. $disableDays = $options['disableDays'];
  495. }
  496. if (isset($options['highligtDays'])) {
  497. $highligtDays = $options['highligtDays'];
  498. } else {
  499. $highligtDays = '67';
  500. }
  501. if (strpos($field, '.') !== false) {
  502. list($modelName, $fieldName) = explode('.', $field, 2);
  503. } else {
  504. $entity = $this->entity();
  505. $modelName = $this->model();
  506. $fieldName = $field;
  507. }
  508. $defaultOptions = array(
  509. 'empty' => false,
  510. 'minYear' => date('Y')-10,
  511. 'maxYear' => date('Y')+2
  512. );
  513. $fieldName = Inflector::camelize($fieldName);
  514. $customOptions = array(
  515. 'id' => $modelName.$fieldName.'-dd',
  516. 'class' => 'day'
  517. );
  518. $customOptions = array_merge($defaultOptions, $customOptions, $options);
  519. $res['d'] = $this->day($field, $customOptions);
  520. $customOptions = array(
  521. 'id' => $modelName.$fieldName.'-mm',
  522. 'class' => 'month'
  523. );
  524. $customOptions = array_merge($defaultOptions, $customOptions, $options);
  525. $res['m'] = $this->month($field, $customOptions);
  526. $customOptions = array(
  527. 'id' => $modelName.$fieldName,
  528. 'class' => 'year'
  529. );
  530. $customOptions = array_merge($defaultOptions, $customOptions, $options);
  531. $minYear = $customOptions['minYear'];
  532. $maxYear = $customOptions['maxYear'];
  533. $res['y'] = $this->year($field, $minYear, $maxYear, $customOptions);
  534. if (isset($options['class'])) {
  535. $class = $options['class'];
  536. unset($options['class']);
  537. }
  538. $select = implode($options['separator'], $res);
  539. if ($this->isFieldError($field)) {
  540. $error = $this->error($field);
  541. } else {
  542. $error = '';
  543. }
  544. if (!empty($callbacks)) {
  545. //callbackFunctions:{"create":...,"dateset":[updateBox]},
  546. $c = $callbacks['update'];
  547. $callbacks = 'callbackFunctions:{"dateset":['.$c.']},';
  548. }
  549. if (!empty($customOptions['type']) && $customOptions['type'] == 'text') {
  550. $script = '
  551. <script type="text/javascript">
  552. // <![CDATA[
  553. var opts = {
  554. formElements:{"'.$modelName.$fieldName.'":"d-dt-m-dt-Y"},
  555. showWeeks:true,
  556. statusFormat:"l-cc-sp-d-sp-F-sp-Y",
  557. '.(!empty($callbacks)?$callbacks:'').'
  558. // Position the button within a wrapper span with an id of "button-wrapper"
  559. positioned:"button-wrapper"
  560. };
  561. datePickerController.createDatePicker(opts);
  562. // ]]>
  563. </script>
  564. ';
  565. $options = am(array('id' => $modelName.$fieldName), $options);
  566. $select = $this->text($field, $options);
  567. return '<div class="input date'.(!empty($error)?' error':'').'">'.$this->label($modelName.'.'.$field, $options['label']).''.$select.''.$error.'</div>'.$script;
  568. }
  569. if ($return) {
  570. return $select;
  571. }
  572. $script = '
  573. <script type="text/javascript">
  574. // <![CDATA[
  575. var opts = {
  576. formElements:{"'.$modelName.$fieldName.'":"Y","'.$modelName.$fieldName.'-mm":"m","'.$modelName.$fieldName.'-dd":"d"},
  577. showWeeks:true,
  578. statusFormat:"l-cc-sp-d-sp-F-sp-Y",
  579. '.(!empty($callbacks)?$callbacks:'').'
  580. // Position the button within a wrapper span with an id of "button-wrapper"
  581. positioned:"button-wrapper"
  582. };
  583. datePickerController.createDatePicker(opts);
  584. // ]]>
  585. </script>
  586. ';
  587. return '<div class="input date'.(!empty($error)?' error':'').'">'.$this->label($modelName.'.'.$field, $options['label']).''.$select.''.$error.'</div>'.$script;
  588. }
  589. /**
  590. * @deprecated
  591. * use Form::dateTimeExt
  592. */
  593. public function dateTime($field, $options = array(), $tf = 24, $s = null, $a = array()) {
  594. # temp fix
  595. if (!is_array($options)) {
  596. if ($options === null) {
  597. $options = 'DMY';
  598. }
  599. return parent::dateTime($field, $options, $tf, $s, $a);
  600. }
  601. return $this->dateTimeExt($field, $options);
  602. }
  603. /**
  604. * @deprecated
  605. * use Form::timeExt
  606. */
  607. public function time($field, $options = array()) {
  608. return $this->timeExt($field, $options);
  609. }
  610. public function timeExt($field, $options = array()) {
  611. $return = false;
  612. if (isset($options['return'])) {
  613. $return = $options['return'];
  614. unset($options['return']);
  615. }
  616. $this->dateScripts(array('time'));
  617. $res = array();
  618. if (!isset($options['separator'])) {
  619. $options['separator'] = ':';
  620. }
  621. if (!isset($options['label'])) {
  622. $options['label'] = null;
  623. }
  624. $defaultOptions = array(
  625. 'empty' => false,
  626. 'timeFormat' => 24,
  627. );
  628. if (strpos($field, '.') !== false) {
  629. list($model, $field) = explode('.', $field, 2);
  630. } else {
  631. $entity = $this->entity();
  632. $model = $this->model();
  633. }
  634. $fieldname = Inflector::camelize($field);
  635. $customOptions = array_merge($defaultOptions, $options);
  636. $format24Hours = $customOptions['timeFormat'] != '24' ? false : true;
  637. if (strpos($field, '.') !== false) {
  638. list($model, $field) = explode('.', $field, 2);
  639. } else {
  640. $entity = $this->entity();
  641. $model = $this->model();
  642. }
  643. $hourOptions = array_merge($customOptions, array('class'=>'hour'));
  644. $res['h'] = $this->hour($field, $format24Hours, $hourOptions);
  645. $minuteOptions = array_merge($customOptions, array('class'=>'minute'));
  646. $res['m'] = $this->minute($field, $minuteOptions);
  647. $select = implode($options['separator'], $res);
  648. if ($this->isFieldError($field)) {
  649. $error = $this->error($field);
  650. } else {
  651. $error = '';
  652. }
  653. if ($return) {
  654. return $select;
  655. }
  656. /*
  657. $script = '
  658. <script type="text/javascript">
  659. // <![CDATA[
  660. $(document).ready(function() {
  661. $(\'#'.$model.$fieldname.'-timepicker\').jtimepicker({
  662. // Configuration goes here
  663. \'secView\': false
  664. });
  665. });
  666. // ]]>
  667. </script>
  668. ';
  669. */
  670. $script = '';
  671. //<div id="'.$model.$fieldname.'-timepicker"></div>
  672. return '<div class="input date'.(!empty($error)?' error':'').'">'.$this->label($model.'.'.$field, $options['label']).''.$select.''.$error.'</div>'.$script;
  673. }
  674. /** maxLength **/
  675. public $maxLengthOptions = array(
  676. 'maxCharacters' => 255,
  677. //'events' => array(),
  678. 'status' => true,
  679. 'statusClass' => 'status',
  680. 'statusText' => 'characters left',
  681. 'slider' => true
  682. );
  683. public function maxLengthScripts() {
  684. if (!$this->scriptsAdded['maxLength']) {
  685. $this->Html->script('jquery/maxlength/jquery.maxlength', array('inline'=>false));
  686. $this->scriptsAdded['maxLength'] = true;
  687. }
  688. }
  689. /**
  690. * maxLength js for textarea input
  691. * final output
  692. * @param array $selectors with specific settings
  693. * @param array $globalOptions
  694. * @return string with JS code
  695. * 2009-07-30 ms
  696. */
  697. public function maxLength($selectors = array(), $options = array()) {
  698. $this->maxLengthScripts();
  699. $js = '';
  700. $this->maxLengthOptions['statusText'] = __($this->maxLengthOptions['statusText']);
  701. $selectors = (array)$selectors;
  702. foreach ($selectors as $selector => $settings) {
  703. if (is_int($selector)) {
  704. $selector = $settings;
  705. $settings = array();
  706. }
  707. $js .= $this->_maxLength($selector, array_merge($this->maxLengthOptions, $settings));
  708. }
  709. if (!empty($options['plain'])) {
  710. return $js;
  711. }
  712. $js = $this->documentReady($js);
  713. return $this->Html->scriptBlock($js);
  714. }
  715. protected function _maxLength($selector, $settings = array()) {
  716. return '
  717. jQuery(\''.$selector.'\').maxlength('.$this->Js->object($settings, array('quoteKeys'=>false)).');
  718. ';
  719. }
  720. public function scripts($type) {
  721. switch ($type) {
  722. case 'charCount':
  723. $this->Html->script('jquery/plugins/charCount', array('inline'=>false));
  724. $this->Html->css('/js/jquery/plugins/charCount', null, array('inline'=>false));
  725. break;
  726. default:
  727. return false;
  728. }
  729. $this->scriptsAdded[$type] = true;
  730. return true;
  731. }
  732. public $charCountOptions = array(
  733. 'allowed' => 255,
  734. );
  735. public function charCount($selectors = array(), $options = array()) {
  736. $this->scripts('charCount');
  737. $js = '';
  738. $selectors = (array)$selectors;
  739. foreach ($selectors as $selector => $settings) {
  740. if (is_int($selector)) {
  741. $selector = $settings;
  742. $settings = array();
  743. }
  744. $settings = am($this->charCountOptions, $options, $settings);
  745. $js .= 'jQuery(\''.$selector.'\').charCount('.$this->Js->object($settings, array('quoteKeys'=>false)).');';
  746. }
  747. $js = $this->documentReady($js);
  748. return $this->Html->scriptBlock($js);
  749. }
  750. public function documentReady($string) {
  751. return 'jQuery(document).ready(function() {
  752. '.$string.'
  753. });';
  754. }
  755. public function autoCompleteScripts() {
  756. if (!$this->scriptsAdded['autoComplete']) {
  757. $this->Html->script('jquery/autocomplete/jquery.autocomplete', false);
  758. $this->Html->css('/js/jquery/autocomplete/jquery.autocomplete', null, array('inline'=>false));
  759. $this->scriptsAdded['autoComplete'] = true;
  760. }
  761. }
  762. /**
  763. * //TODO
  764. * @param jquery: defaults to null = no jquery markup
  765. * - url, data, object (one is neccessary), options
  766. * 2010-01-27 ms
  767. */
  768. public function autoComplete($field = null, $options = array(), $jquery = null) {
  769. $this->autoCompleteScripts();
  770. $defaultOptions = array(
  771. 'autocomplete' => 'off'
  772. );
  773. $options = array_merge($defaultOptions, $options);
  774. if (empty($options['id']) && is_array($jquery)) {
  775. $options['id'] = Inflector::camelize(str_replace(".", "_", $field));
  776. }
  777. $res = $this->input($field, $options);
  778. if (is_array($jquery)) {
  779. # custom one
  780. $res .= $this->_autoComplete($options['id'], $jquery);
  781. }
  782. return $res;
  783. }
  784. protected function _autoComplete($id, $jquery = array()) {
  785. if (!empty($jquery['url'])) {
  786. $var = '"'.$this->Html->url($jquery['url']).'"';
  787. } elseif (!empty($jquery['var'])) {
  788. $var = $jquery['object'];
  789. } else {
  790. $var = '['.$jquery['data'].']';
  791. }
  792. $options = '';
  793. if (!empty($jquery['options'])) {
  794. }
  795. $js = 'jQuery("#'.$id.'").autocomplete('.$var.', {
  796. '.$options.'
  797. });
  798. ';
  799. $js = $this->documentReady($js);
  800. return $this->Html->scriptBlock($js);
  801. }
  802. /** checkboxes **/
  803. public function checkboxScripts() {
  804. if (!$this->scriptsAdded['checkbox']) {
  805. $this->Html->script('jquery/checkboxes/jquery.checkboxes', false);
  806. $this->scriptsAdded['checkbox'] = true;
  807. }
  808. }
  809. /**
  810. * returns script + elements "all", "none" etc
  811. * 2010-02-15 ms
  812. */
  813. public function checkboxScript($id) {
  814. $this->checkboxScripts();
  815. $js = 'jQuery("#'.$id.'").autocomplete('.$var.', {
  816. '.$options.'
  817. });
  818. ';
  819. $js = $this->documentReady($js);
  820. return $this->Html->scriptBlock($js);
  821. }
  822. public function checkboxButtons($buttonsOnly = false) {
  823. $res = '<div>';
  824. $res .= __('Selection').': ';
  825. $res .= $this->Html->link(__('All'), 'javascript:void(0)');
  826. $res .= $this->Html->link(__('None'), 'javascript:void(0)');
  827. $res .= $this->Html->link(__('Revert'), 'javascript:void(0)');
  828. $res .= '</div>';
  829. if ($buttonsOnly !== true) {
  830. $res .= $this->checkboxScript();
  831. }
  832. return $res;
  833. }
  834. /**
  835. * displays a single checkbox - called for each
  836. */
  837. public function _checkbox($id, $group = null, $options = array()) {
  838. $defaults = array(
  839. 'class' => 'checkboxToggle'
  840. );
  841. $options = am($defaults, $options);
  842. return $script . parent::checkbox($fieldName, $options);
  843. }
  844. /** other stuff **/
  845. /**
  846. * echo $this->FormExt->buttons($buttons);
  847. * with
  848. * $buttons = array(
  849. * array(
  850. * 'title' => 'Login',
  851. * 'options' => array('type' => 'submit')
  852. * ),
  853. * array(...)
  854. * );
  855. * @param array $buttons
  856. * @return string $buttonSubmitDiv
  857. * 2009-07-26 ms
  858. */
  859. public function buttons($buttons = null) {
  860. $return = '';
  861. if (!empty($buttons) && is_array($buttons)) {
  862. $buttons_content = '';
  863. foreach ($buttons as $button) {
  864. if (empty($button['options'])) { $button['options'] = array(); }
  865. $buttons_content .= $this->button($button['name'], $button['options']);
  866. }
  867. $return = $this->Html->div('submit', $buttons_content);
  868. }
  869. return $return;
  870. }
  871. /** nice buttons **/
  872. protected $buttons = array();
  873. protected $buttonAlign = 'left';
  874. /**
  875. * @param title
  876. * @param options:
  877. * - color (green, blue, red, orange)
  878. * - url
  879. * - align (left/right)
  880. * @param attributes (html)
  881. * 2010-03-15 ms
  882. */
  883. public function addButton($title, $options = array(), $attr = array()) {
  884. $url = !empty($options['url']) ? $options['url'] : 'javascript:void(0)';
  885. $color = !empty($options['color']) ? ' ovalbutton'.ucfirst($options['color']) : '';
  886. if (isset($options['align'])) {
  887. $this->buttonAlign = $options['align'];
  888. }
  889. if ($this->buttonAlign == 'left') {
  890. $align = 'margin-right:5px';
  891. } elseif ($this->buttonAlign == 'right') {
  892. $align = 'margin-left:5px';
  893. }
  894. $class = 'ovalbutton'.$color;
  895. if (!empty($attr['class'])) {
  896. $class .= ' '.$attr['class'];
  897. }
  898. $style = array();
  899. if (!empty($align)) {
  900. $style[] = $align;
  901. }
  902. if (!empty($attr['class'])) {
  903. $style[] = $attr['style'];
  904. }
  905. $style = implode(';', $style);
  906. $attr = array_merge($attr, array('escape'=>false,'class'=>$class, 'style'=>$style));
  907. //$this->buttons[] = '<a class="ovalbutton'.$color.'"'.$href.''.$align.'><span>'.$title.'</span></a>';
  908. if (!isset($attr['escape']) || $attr['escape'] !== true) {
  909. $title = h($title);
  910. }
  911. $this->buttons[] = $this->Html->link('<span>'.$title.'</span>', $url, $attr);
  912. }
  913. /**
  914. * 2010-03-15 ms
  915. */
  916. public function displayButtons($options = array()) {
  917. $res = '<div class="buttonwrapper" style="text-align: '.$this->buttonAlign.'">'.implode('', $this->buttons).'</div>';
  918. $this->buttons = array();
  919. $this->buttonAlign = 'left';
  920. return $res;
  921. }
  922. /*
  923. public $datetimeQuicklinks = '
  924. public function str_pad(input, pad_length, pad_string, pad_type) {
  925. // http://kevin.vanzonneveld.net
  926. // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  927. // + namespaced by: Michael White (http://getsprink.com)
  928. // + input by: Marco van Oort
  929. // + bugfixed by: Brett Zamir (http://brett-zamir.me)
  930. // * example 1: str_pad('Kevin van Zonneveld', 30, '-=', 'STR_PAD_LEFT');
  931. // * returns 1: '-=-=-=-=-=-Kevin van Zonneveld'
  932. // * example 2: str_pad('Kevin van Zonneveld', 30, '-', 'STR_PAD_BOTH');
  933. // * returns 2: '------Kevin van Zonneveld-----'
  934. var half = '',
  935. pad_to_go;
  936. var str_pad_repeater = function (s, len) {
  937. var collect = '',
  938. i;
  939. while (collect.length < len) {
  940. collect += s;
  941. }
  942. collect = collect.substr(0, len);
  943. return collect;
  944. };
  945. input += '';
  946. pad_string = pad_string !== undefined ? pad_string : ' ';
  947. if (pad_type != 'STR_PAD_LEFT' && pad_type != 'STR_PAD_RIGHT' && pad_type != 'STR_PAD_BOTH') {
  948. pad_type = 'STR_PAD_RIGHT';
  949. }
  950. if ((pad_to_go = pad_length - input.length) > 0) {
  951. if (pad_type == 'STR_PAD_LEFT') {
  952. input = str_pad_repeater(pad_string, pad_to_go) + input;
  953. } elseif (pad_type == 'STR_PAD_RIGHT') {
  954. input = input + str_pad_repeater(pad_string, pad_to_go);
  955. } elseif (pad_type == 'STR_PAD_BOTH') {
  956. half = str_pad_repeater(pad_string, Math.ceil(pad_to_go / 2));
  957. input = half + input + half;
  958. input = input.substr(0, pad_length);
  959. }
  960. }
  961. return input;
  962. }
  963. $(document).ready(function() {
  964. $('.date').append(' <span class="setRemove hand">ENTFERNEN</span> <span class="setToday hand">HEUTE</span> <span class="setNow hand">JETZT</span>');
  965. $('.setRemove').click(function() {
  966. var container = $(this).parent('div');
  967. container.children('.day').val("");
  968. container.children('.month').val("");
  969. container.children('.year').val("");
  970. container.children('.hour').val("");
  971. container.children('.minute').val("");
  972. });
  973. $('.setNow').click(function() {
  974. var d = new Date();
  975. var curr_hour = str_pad(d.getHours(), 2, "0", 'STR_PAD_LEFT');
  976. var curr_minute = str_pad(d.getMinutes(), 2, "0", 'STR_PAD_LEFT');
  977. var container = $(this).parent('div');
  978. container.children('.hour').val(curr_hour);
  979. container.children('.minute').val(curr_minute);
  980. });
  981. $('.setToday').click(function() {
  982. var d = new Date();
  983. var curr_date = str_pad(d.getDate(), 2, "0", 'STR_PAD_LEFT');
  984. var curr_month = str_pad(d.getMonth()+1, 2, "0", 'STR_PAD_LEFT');
  985. var curr_year = str_pad(d.getFullYear(), 2, "4", 'STR_PAD_LEFT');
  986. var container = $(this).parent('div');
  987. container.children('.day').val(curr_date);
  988. container.children('.month').val(curr_month);
  989. container.children('.year').val(curr_year);
  990. });
  991. });
  992. ';
  993. */
  994. }