FormExtHelper.php 31 KB

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