/** * Register JavaScript code for the grid. */ protected function registerScript() { if ($this->getGrid()->isUpdate()) { return; } $name = Json::encode($this->getGrid()->getName()); $baseUrl = Json::encode($this->getGrid()->getBaseUrl()); $embedded = $this->getGrid()->isEmbedded() ? 'true' : 'false'; $this->getPage()->register_script(Config::getGlobal('assets_url') . 'javascript/class.atkdatagrid.js'); $this->getPage()->register_loadscript("\n ATK.DataGrid.register({$name}, {$baseUrl}, {$embedded});\n "); }
/** * If a search action has been defined and a search only returns one result * the user will be automatically redirected to the search action. * * @param DataGrid $grid data grid * * @return bool redirect active? */ protected function redirectToSearchAction($grid) { $node = $this->getNode(); $search = $grid->getPostvar('atksearch'); // check if we are searching and a search action has been defined if (!is_array($search) || count($search) == 0 || !is_array($node->m_search_action) || count($node->m_search_action) == 0) { return false; } // check if there is only a single record in the result $grid->loadRecords(); if ($grid->getCount() != 1) { return false; } $records = $grid->getRecords(); foreach ($node->m_search_action as $action) { if (!$node->allowed($action, $records[0])) { continue; } // reset search so we can back to the normal admin screen if we want $grid->setPostvar('atksearch', array()); $sm = SessionManager::getInstance(); $url = $sm->sessionUrl(Tools::dispatch_url($node->atkNodeUri(), $action, array('atkselector' => $node->primaryKey($records[0]))), SessionManager::SESSION_NESTED); if ($grid->isUpdate()) { $script = 'document.location.href = ' . Json::encode($url) . ';'; $node->getPage()->register_loadscript($script); } else { $node->redirect($url); } return true; } return false; }
/** * Renders the debug and error messages to a nice HTML string. * * @return string html string */ public function renderDebugAndErrorMessages() { global $ATK_VARS, $g_debug_msg, $g_error_msg; // check if this is an Ajax request $isPartial = isset($ATK_VARS['atkpartial']); // only display error messages if (count($g_error_msg) > 0 && Config::getGlobal('display_errors') && Config::getGlobal('debug') <= 0 && !$isPartial) { return $this->renderPlainErrorMessages(); } else { if (Config::getGlobal('debug') <= 0 || count($g_debug_msg) == 0 && count($g_error_msg) == 0) { return ''; } } $expanded = !$isPartial; if ($expanded && array_key_exists('atkdebugstate', $_COOKIE) && @$_COOKIE['atkdebugstate'] == 'collapsed') { $expanded = false; } // render debug block $block = $this->renderDebugBlock($expanded); if ($isPartial) { $output = '<script type="text/javascript"> ATK.Debug.addContent(' . Json::encode($block) . '); </script>'; } else { $ui = Ui::getInstance(); $script = Config::getGlobal('assets_url') . 'javascript/class.atkdebug.js'; $redirect = $this->renderRedirectLink(); $output = ' <script type="text/javascript" src="' . $script . '"></script> <div id="atk_debugging_div"> ' . $redirect . ' ' . $block . ' </div>'; } return $output; }
/** * Function returns a generic html form for editing a record. * * @param string $mode The edit mode ("add" or "edit"). * @param array $record The record to edit. * @param array $forceList A key-value array used to preset certain * fields to a certain value. * @param array $suppressList An array of fields that will be hidden. * @param string $fieldprefix If set, each form element is prefixed with * the specified prefix (used in embedded * forms) * @param string $template The template to use for the edit form * @param bool $ignoreTab Ignore the tabs an attribute should be shown on. * * @return string the edit form as a string */ public function editForm($mode = 'add', $record = null, $forceList = '', $suppressList = '', $fieldprefix = '', $template = '', $ignoreTab = false) { $node = $this->m_node; /* get data, transform into form, return */ $data = $node->editArray($mode, $record, $forceList, $suppressList, $fieldprefix, $ignoreTab); // Format some things for use in tpl. /* check for errors and display them */ $tab = $node->getActiveTab(); $error_title = ''; $pk_err_attrib = []; $tabs = $node->getTabs($node->m_action); // Handle errors $errors = []; if (count($data['error']) > 0) { $error_title = '<b>' . Tools::atktext('error_formdataerror') . '</b>'; foreach ($data['error'] as $error) { if ($error['err'] == 'error_primarykey_exists') { $pk_err_attrib[] = $error['attrib_name']; } else { if (count($tabs) > 1 && $error['tab']) { $tabLink = $this->getTabLink($node, $error); $error_tab = ' (' . Tools::atktext('error_tab') . ' ' . $tabLink . ' )'; } else { $tabLink = null; $error_tab = ''; } if (is_array($error['label'])) { $label = implode(', ', $error['label']); } else { if (!empty($error['label'])) { $label = $error['label']; } else { if (!is_array($error['attrib_name'])) { $label = $node->text($error['attrib_name']); } else { $label = []; foreach ($error['attrib_name'] as $attrib) { $label[] = $node->text($attrib); } $label = implode(', ', $label); } } } /* Error messages should be rendered in templates using message, label and the link to the tab. */ $err = array('message' => $error['msg'], 'tablink' => $tabLink, 'label' => $label); /* * @deprecated: For backwards compatibility, we still support the msg variable as well. * Although the message, tablink variables should be used instead of msg and tab. */ $err = array_merge($err, array('msg' => $error['msg'] . $error_tab)); $errors[] = $err; } } if (count($pk_err_attrib) > 0) { // Make primary key error message $pk_err_msg = ''; for ($i = 0; $i < count($pk_err_attrib); ++$i) { $pk_err_msg .= Tools::atktext($pk_err_attrib[$i], $node->m_module, $node->m_type); if ($i + 1 < count($pk_err_attrib)) { $pk_err_msg .= ', '; } } $errors[] = array('label' => Tools::atktext('error_primarykey_exists'), 'message' => $pk_err_msg); } } /* display the edit fields */ $fields = []; $errorFields = []; $attributes = []; for ($i = 0, $_i = count($data['fields']); $i < $_i; ++$i) { $field =& $data['fields'][$i]; $tplfield = $this->createTplField($data['fields'], $i, $mode, $tab); $fields[] = $tplfield; // make field available in numeric array $params[isset($field['name']) ? $field['name'] : null] = $tplfield; // make field available in associative array $attributes[isset($field['name']) ? $field['name'] : null] = $tplfield; // make field available in associative array if (!empty($field['error'])) { $errorFields[] = $field['id']; } } $ui = $this->getUi(); $page = $this->getPage(); $page->register_script(Config::getGlobal('assets_url') . 'javascript/formsubmit.js'); // register fields that contain errornous values $page->register_scriptcode('var atkErrorFields = ' . Json::encode($errorFields) . ';'); if (Config::getGlobal('lose_changes_warning', true)) { // If we are in the save or update action the user has added a nested record, has done // a selection using the select handler or generated an error, in either way we assume // the form has been changed, so we always warn the user when leaving the page. $isChanged = 'false'; if (isset($record['atkerror']) && count($record['atkerror']) > 0 || isset($this->m_node->m_postvars['__atkunloadhelper']) && $this->m_node->m_postvars['__atkunloadhelper']) { $isChanged = 'true'; } $unloadText = addslashes($this->m_node->text('lose_changes_warning')); $page->register_script(Config::getGlobal('assets_url') . 'javascript/class.atkunloadhelper.js'); $page->register_loadscript("new ATK.UnloadHelper('entryform', '{$unloadText}', {$isChanged});"); } $result = ''; foreach ($data['hide'] as $hidden) { $result .= $hidden; } $params['activeTab'] = $tab; $params['fields'] = $fields; // add all fields as a numeric array. $params['attributes'] = $attributes; // add all fields as an associative array $params['errortitle'] = $error_title; $params['errors'] = $errors; // Add the list of errors. Tools::atkdebug("Render editform - {$template}"); if ($template) { $result .= $ui->render($template, $params); } else { $tabTpl = $this->_getTabTpl($node, $tabs, $mode, $record); $params['fieldspart'] = $this->_renderTabs($fields, $tabTpl); $result .= $ui->render('editform_common.tpl', $params); } return $result; }
/** * Fetch value for this relation based on the postvars. * * @param array $postvars * * @return mixed The value of this relation */ public function fetchValue($postvars) { $ret = []; $vals = Json::decode($postvars[$this->fieldName()]['selected'][0][$this->getRemoteKey()], true); if (is_array($vals)) { foreach ($vals as $val) { $ret[][$this->getRemoteKey()] = $val; } } $postvars[$this->fieldName()]['selected'] = $ret; return $postvars[$this->fieldName()]; }
/** * Re-render / refresh the attribute with the given name. * * @param string $name attribute name */ public function refreshAttribute($name) { if ($this->isInitial()) { return; } $offset = count($this->getNode()->getPage()->getLoadScripts()); $error = []; $editArray = array('fields' => array()); $this->m_node->getAttribute($name)->addToEditArray($this->getMode(), $editArray, $this->getRecord(), $error, $this->getFieldPrefix()); $scriptCode = ''; foreach ($editArray['fields'] as $field) { $element = str_replace('.', '_', $this->getNode()->atkNodeUri() . '_' . $field['id']); $value = Json::encode(Tools::atk_iconv(Tools::atkGetCharset(), 'UTF-8', $field['html'])); // Json::encode excepts string in UTF-8 $scriptCode .= "if (\$('{$element}')) { \$('{$element}').update({$value}); } "; } $this->getNode()->getPage()->register_loadscript($scriptCode, $offset); }
/** * Returns a JavaScript call to save the current grid's contents when in edit mode. * * @return string JavaScript call (might need escaping when used in HTML code) */ public function getSaveCall() { $sm = SessionManager::getInstance(); $url = $sm->sessionUrl(Tools::dispatch_url($this->getNode()->atkNodeUri(), 'multiupdate', array('output' => 'json')), SessionManager::SESSION_PARTIAL); return 'ATK.DataGrid.save(' . Json::encode($this->getName()) . ', ' . Json::encode($url) . ');'; }
/** * Initialize and calls the dependencies. * * @param array $record record * @param string $fieldPrefix the prefix for this attribute in an HTML form * @param string $mode add/edit mode * @param bool $noCall only initialize dependencies, without calling them */ public function initDependencies(&$record, $fieldPrefix, $mode, $noCall = false) { if (count($this->getDependencies()) == 0) { return; } if (!$noCall) { $this->_callDependencies($record, $fieldPrefix, $mode, true); } $action = $this->getOwnerInstance()->m_action; if ($action == null) { $action = $mode == 'add' ? 'add' : 'edit'; } $url = Tools::partial_url($this->getOwnerInstance()->atkNodeUri(), $action, 'attribute.' . $this->fieldName() . '.dependencies', array('atkdata' => array('fieldPrefix' => $fieldPrefix, 'mode' => $mode))); $url = Json::encode($url); $this->getOwnerInstance()->getPage()->register_script(Config::getGlobal('assets_url') . 'javascript/class.atkattribute.js'); $code = "ATK.Attribute.callDependencies({$url}, el);"; $this->addOnChangeHandler($code); }
/** * Returns a piece of html code that can be used in a form to edit this * attribute's value. (Month will be a dropdownbox, year and day text fields). * * @todo We can't show a calendar when we have a year dropdown? * @todo The calendar doesn't use the min/max values? * * @param array $record Array with 3 fields (year, month, day) * @param string $id html id * @param string $name html name * @param string $fieldprefix The fieldprefix * @param string $postfix * @param string $mode The mode ('add' or 'edit') * @param bool $obligatory Is this field obligatory or not * * @return string Piece a of HTML Code */ public function draw($record = [], $id, $name, $fieldprefix = '', $postfix = '', $mode = '', $obligatory = false) { $result = ''; // go in simplemode when a pda is detected if (BrowserInfo::detectPDA()) { $this->setSimpleMode(true); } $this->m_yeardropdown = false; if (!$this->m_simplemode) { self::registerScriptsAndStyles(!$this->hasFlag(self::AF_DATE_NO_CALENDAR)); } $fieldname = $name . $postfix; /* text mode? */ if ($this->hasFlag(self::AF_DATE_STRING) || $mode == 'list') { $value =& $record[$this->fieldName()]; if (is_array($value)) { $value = adodb_date($this->m_date_format_edit, adodb_mktime(0, 0, 0, $value['month'], $value['day'], $value['year'])); } elseif ($obligatory) { $value = adodb_date($this->m_date_format_edit); } else { $value = ''; } $fieldname = $fieldname . '[date]'; $result = '<input type="text" id="' . $id . '" class="atkdateattribute form-control" name="' . $fieldname . '" value="' . $value . '">'; if (!$this->hasFlag(self::AF_DATE_NO_CALENDAR) && $mode != 'list') { $format = str_replace(array('y', 'Y', 'm', 'n', 'j', 'd'), array('yy', 'y', 'mm', 'm', 'd', 'dd'), $this->m_date_format_edit); $mondayFirst = 'false'; if (is_bool(Tools::atktext('date_monday_first'))) { $mondayFirst = Tools::atktext('date_monday_first') === true ? 'true' : $mondayFirst; } $result .= ' <input ' . $this->getCSSClassAttribute(['btn', 'btn-default', 'button', 'atkbutton', 'form-control']) . ' type="button" value="..." onclick="return showCalendar(\'' . $id . '\', \'' . $id . '\', \'' . $format . '\', false, ' . $mondayFirst . ');">'; } return $result; } /* this field */ $field = Tools::atkArrayNvl($record, $this->fieldName()); $str_format = $this->m_date_format_edit; /* currently selected date */ if (is_array($field) && $field['year'] == 0 && $field['month'] == 0 && $field['day'] == 0) { $current = null; } elseif (!is_array($field) && empty($field)) { $current = null; } elseif (is_array($field)) { if ($this->checkDateArray($field)) { $current = adodb_mktime(0, 0, 0, $field['month'], $field['day'], $field['year']); } else { $current = null; Tools::triggerError($record, $this->fieldName(), 'error_date_invalid'); } } else { $date = self::dateArray($field); if ($this->checkDateArray($date)) { $current = adodb_mktime(0, 0, 0, $date['month'], $date['day'], $date['year']); } else { $current = null; } } /* minimum date */ $minimum = $mode != 'search' ? $this->m_date_min : 0; if ($minimum != 0) { $str_min = adodb_date('Ymd', $minimum); } else { $str_min = 0; } /* maximum date */ $maximum = $mode != 'search' ? $this->m_date_max : 0; if ($maximum != 0) { $str_max = adodb_date('Ymd', $maximum); } else { $str_max = 0; } $current = $this->getValidCurrentDate($current, $minimum, $maximum, $mode); /* get dates in array format */ if ($current !== null) { $current = adodb_getdate($current); } if (!empty($minimum)) { $minimum = adodb_getdate($minimum); } if (!empty($maximum)) { $maximum = adodb_getdate($maximum); } /* minimum and maximum */ $current['d_min'] = !empty($minimum) && $current['year'] == $minimum['year'] && $current['mon'] == $minimum['mon'] ? $minimum['mday'] : 1; $current['d_max'] = !empty($maximum) && $current['year'] == $maximum['year'] && $current['mon'] == $maximum['mon'] ? $maximum['mday'] : $this->getDays($current); $current['m_min'] = !empty($minimum) && $current['year'] == $minimum['year'] ? $minimum['mon'] : 1; $current['m_max'] = !empty($maximum) && $current['year'] == $maximum['year'] ? $maximum['mon'] : 12; $current['y_min'] = !empty($minimum) ? $minimum['year'] : 0; $current['y_max'] = !empty($maximum) ? $maximum['year'] : 0; /* small date selections, never possible is field isn't obligatory (no min/max date) */ if (!empty($maximum) && !empty($minimum) && $str_max - $str_min < 25) { $str_script = ''; if (count($this->m_onchangecode)) { $this->_renderChangeHandler($fieldprefix); $str_script = $this->getHtmlId($fieldprefix) . '_onChange(this);'; } $result = '<select id="' . $id . '" name="' . $fieldname . '" onChange="' . $str_script . '" class="form-control select-standard">'; for ($i = $str_min; $i <= $str_max; ++$i) { $tmp_date = adodb_getdate(adodb_mktime(0, 0, 0, substr($i, 4, 2), substr($i, 6, 2), substr($i, 0, 4))); $result .= '<option value="' . $i . '"' . ($current !== null && $tmp_date[0] == $current[0] ? ' selected' : '') . '>' . $this->formatDate($tmp_date, $str_format, !$this->hasFlag(self::AF_DATE_EDIT_NO_DAY)) . '</option>'; } $result .= '</select>'; return $result; } if ($this->hasFlag(self::AF_DATE_EMPTYFIELD)) { $emptyfield = true; } else { if (!$obligatory) { $emptyfield = true; } else { $emptyfield = false; } } $info = array('format' => $str_format, 'min' => $str_min, 'max' => $str_max, 'emptyfield' => $emptyfield, 'weekday' => !$this->hasFlag(self::AF_DATE_EDIT_NO_DAY)); if (!$this->m_simplemode) { $result .= '<div class="' . $this->get_class_name() . ' form-inline"><script language="javascript">var atkdateattribute_' . $id . ' = ' . Json::encode($info) . ';</script>'; } /* other date selections */ $weekdayFormat = null; $str_script = ''; for ($i = 0; $i < strlen($str_format); ++$i) { /* javascript method */ if (!$this->m_simplemode) { $str_script = "AdjustDate(this, '" . $id . "');"; } if (count($this->m_onchangecode)) { $this->_renderChangeHandler($fieldprefix); $str_script .= $this->getHtmlId($fieldprefix) . '_onChange(this);'; } /* year input box */ if ($str_format[$i] == 'y' || $str_format[$i] == 'Y') { $result .= $this->renderYear($id, $fieldname, $str_script, $current, $str_format[$i], $obligatory); } elseif ($str_format[$i] == 'D' || $str_format[$i] == 'l') { $weekdayFormat = $str_format[$i]; } elseif ($str_format[$i] == 'j' || $str_format[$i] == 'd') { $result .= $this->renderDay($id, $fieldname, $str_script, $current, $str_format[$i], $obligatory, $weekdayFormat); } elseif ($str_format[$i] == 'm' || $str_format[$i] == 'n' || $str_format[$i] == 'M' || $str_format[$i] == 'F') { $result .= $this->renderMonth($id, $fieldname, $str_script, $current, $str_format[$i], $obligatory); } else { $result .= $str_format[$i]; } } if (!$this->hasFlag(self::AF_DATE_NO_CALENDAR) && !$this->m_yeardropdown && !$this->m_simplemode && $mode != 'list') { $mondayFirst = 'false'; if (is_bool(Tools::atktext('date_monday_first'))) { $mondayFirst = Tools::atktext('date_monday_first') === true ? 'true' : $mondayFirst; } $result .= ' <input ' . $this->getCSSClassAttribute(array('button', 'atkbutton', 'btn', 'btn-default', 'form-control')) . ' type="reset" value="..." onclick="return showCalendar(\'' . $id . '\', \'' . $id . '[year]\', \'y-mm-dd\', true, ' . $mondayFirst . ');">'; } if (!$this->m_simplemode) { $result .= '</div>'; // form-inline } return $result; }