FormExtHelper.php 36 KB

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