/** * @see html::table() */ public static function table($options = []) { $rows = isset($options['options']) ? $options['options'] : []; if (!empty($options['header']) && is_array($options['header'])) { $header = $options['header']; } else { // we need to grab header from first row $header = current($rows); $options['skip_header'] = true; } $result = []; $first_column = null; // header first if (!empty($header) && empty($options['skip_header'])) { $temp2 = '<thead>'; $temp2 .= '<tr>'; foreach ($header as $k => $v) { // determine first column if ($first_column === null) { $first_column = $k; } if (is_array($v)) { $tag = !empty($v['header_use_td_tag']) ? 'td' : 'th'; $temp_value = isset($v['value']) ? $v['value'] : ''; unset($v['value']); $temp2 .= '<' . $tag . ' ' . self::generate_attributes($v, $tag) . '>' . $temp_value . '</' . $tag . '>'; } else { $temp2 .= '<th nowrap>' . $v . '</th>'; } } $temp2 .= '</tr>'; $temp2 .= '</thead>'; $result[] = $temp2; } // unsetting some values unset($options['options'], $options['header'], $options['skip_header']); // rows second foreach ($rows as $k => $v) { // we need to extract row attributes from first column $row_options = []; foreach ($header as $k2 => $v2) { if (isset($v[$k2]) && is_array($v[$k2])) { $row_options = array_merge_hard($row_options, array_key_extract_by_prefix($v[$k2], 'row_')); } } $temp2 = '<tr ' . self::generate_attributes($row_options, 'tr') . '>'; // important we render based on header array and not on what is in rows $flag_colspan = 0; foreach ($header as $k2 => $v2) { if ($flag_colspan > 0) { $flag_colspan--; continue; } if (!isset($v[$k2])) { $v[$k2] = null; } if (is_array($v[$k2])) { $temp3 = isset($v[$k2]['value']) ? $v[$k2]['value'] : ''; unset($v[$k2]['value']); if (!empty($v[$k2]['nowrap'])) { $v[$k2]['nowrap'] = 'nowrap'; } if (!empty($v[$k2]['colspan'])) { $flag_colspan = $v[$k2]['colspan']; $flag_colspan--; } $temp2 .= '<td ' . self::generate_attributes($v[$k2], 'td') . '>' . $temp3 . '</td>'; } else { $temp2 .= '<td nowrap>' . $v[$k2] . '</td>'; } } // reset colspan $flag_colspan = 0; $temp2 .= '</tr>'; $result[] = $temp2; } // todo: add footer // todo: maybe use <thead>, <tfoot>, and a <tbody> tags return '<table ' . self::generate_attributes($options, 'table') . '>' . implode('', $result) . '</table>'; }
/** * Render elements value * * @param array $options * @param mixed $value * @param array $neighbouring_values * @return string * @throws Exception */ public function render_element_value(&$options, $value = null, &$neighbouring_values = []) { // field name and values_key $options['options']['field_name'] = $options['options']['details_field_name'] ?? $options['options']['name']; $options['options']['field_values_key'] = implode('[::]', $options['options']['field_values_key'] ?? [$options['options']['field_name']]); // custom renderer if (!empty($options['options']['custom_renderer'])) { $method = factory::method($options['options']['custom_renderer'], null, true); $options_custom_renderer = $options; call_user_func_array($method, [&$this, &$options, &$value, &$neighbouring_values]); } // handling override_field_value method if (!empty($this->wrapper_methods['override_field_value']['main'])) { call_user_func_array($this->wrapper_methods['override_field_value']['main'], [&$this, &$options, &$value, &$neighbouring_values]); } $result_options = $options['options']; $options['options']['value'] = $value; array_key_extract_by_prefix($result_options, 'label_'); $element_expand = !empty($result_options['expand']); $html_suffix = $result_options['html_suffix'] ?? ''; // unset certain keys unset($result_options['order'], $result_options['required'], $result_options['html_suffix']); // processing options $flag_select_or_autocomplete = !empty($result_options['options_model']) || !empty($result_options['options']); if (!empty($result_options['options_model'])) { if (empty($result_options['options_params'])) { $result_options['options_params'] = []; } if (empty($result_options['options_options'])) { $result_options['options_options'] = []; } $result_options['options_options']['i18n'] = $result_options['options_options']['i18n'] ?? true; $result_options['options_options']['acl'] = $result_options['options_options']['acl'] ?? $this->acl; if (empty($result_options['options_depends'])) { $result_options['options_depends'] = []; } // options depends & params $this->process_params_and_depends($result_options['options_depends'], $neighbouring_values, $options, true); $this->process_params_and_depends($result_options['options_params'], $neighbouring_values, $options, false); $result_options['options_params'] = array_merge_hard($result_options['options_params'], $result_options['options_depends']); // we do not need options for autocomplete if (strpos($result_options['method'], 'autocomplete') === false) { $skip_values = []; if (!empty($options['options']['details_key'])) { if (!empty($options['options']['details_parent_key'])) { $temp_key = $options['options']['details_parent_key'] . '::' . $options['options']['details_key']; if (!empty($this->misc_settings['details_unique_select'][$temp_key][$options['options']['details_field_name']][$options['options']['__parent_row_number']])) { $skip_values = array_keys($this->misc_settings['details_unique_select'][$temp_key][$options['options']['details_field_name']][$options['options']['__parent_row_number']]); } } else { if (!empty($this->misc_settings['details_unique_select'][$options['options']['details_key']][$options['options']['details_field_name']])) { $skip_values = array_keys($this->misc_settings['details_unique_select'][$options['options']['details_key']][$options['options']['details_field_name']]); } } } $result_options['options'] = object_data_common::process_options($result_options['options_model'], $this, $result_options['options_params'], $value, $skip_values, $result_options['options_options']); } else { // we need to inject form id into autocomplete $result_options['form_id'] = "form_{$this->form_link}_form"; } } // by default all selects are searchable if not specified otherwise if ($flag_select_or_autocomplete) { $result_options['searchable'] = $result_options['searchable'] ?? false; } // different handling for different type switch ($options['type']) { case 'container': $options_container = $options; //$options_container['previous_data'] = $v; // todo: pass $form_data_key from parent $options_container['previous_key'] = $options['previous_key']; // render container $temp_container_value = $this->render_container($data['fm_part_child_container_name'], $parents, $options_container); if (!empty($html_expand)) { // get part id $temp_id = $this->id('part_details', ['part_name' => $data['fm_part_name'], 'part_id' => $options_container['previous_id']]); $temp_id_div_inner = $temp_id . '_html_expand_div_inner'; $temp_expand_div_inner = ['id' => $temp_id_div_inner, 'style' => 'display: none;', 'value' => $temp_container_value]; $temp_expand_div_a = ['href' => 'javascript:void(0);', 'onclick' => "numbers.element.toggle('{$temp_id_div_inner}');", 'value' => '+ / -']; $temp_expand_div_outer = ['align' => 'left', 'value' => html::a($temp_expand_div_a) . '<br />' . html::div($temp_expand_div_inner)]; $value = html::div($temp_expand_div_outer); } else { $value = $temp_container_value; } $result_options['value'] = $value; break; case 'field': $element_method = $result_options['method'] ?? 'html::input'; if (strpos($element_method, '::') === false) { $element_method = 'html::' . $element_method; } // value in special order $flag_translated = false; if (in_array($element_method, ['html::a', 'html::submit', 'html::button', 'html::button2'])) { // translate value $result_options['value'] = i18n($result_options['i18n'] ?? null, $result_options['value'] ?? null); // process confirm_message $result_options['onclick'] = $result_options['onclick'] ?? ''; if (!empty($result_options['confirm_message'])) { $result_options['onclick'] .= 'return confirm(\'' . strip_tags(i18n(null, $result_options['confirm_message'])) . '\');'; } // processing onclick for buttons if (in_array($element_method, ['html::submit', 'html::button', 'html::button2'])) { if (!empty($result_options['onclick']) && strpos($result_options['onclick'], 'this.form.submit();') !== false) { $result_options['onclick'] = str_replace('this.form.submit();', "numbers.form.trigger_submit(this.form);", $result_options['onclick']) . ' return true;'; } else { if (empty($result_options['onclick'])) { $result_options['onclick'] .= 'numbers.form.trigger_submit_on_button(this); return true;'; } else { $result_options['onclick'] = 'numbers.form.trigger_submit_on_button(this); ' . $result_options['onclick']; } } } $flag_translated = true; // icon if (!empty($result_options['icon'])) { $result_options['value'] = html::icon(['type' => $result_options['icon']]) . ' ' . $result_options['value']; } // accesskey if (isset($result_options['accesskey'])) { $accesskey = explode('::', i18n(null, 'accesskey::' . $result_options['name'] . '::' . $result_options['accesskey'], ['skip_translation_symbol' => true])); $result_options['accesskey'] = $accesskey[2]; $result_options['title'] = ($result_options['title'] ?? '') . ' ' . i18n(null, 'Shortcut Key: ') . $accesskey[2]; } } else { if (in_array($element_method, ['html::div', 'html::span'])) { if (!empty($result_options['i18n'])) { $result_options['value'] = i18n($result_options['i18n'] ?? null, $result_options['value'] ?? null); $flag_translated = true; } } else { // editable fields $result_options['value'] = $value; // if we need to empty value, mostly for password fields if (!empty($result_options['empty_value'])) { $result_options['value'] = ''; } // we need to empty zero integers and sequences, before format if (($result_options['php_type'] ?? '') == 'integer' && ($result_options['type'] ?? '') != 'boolean' && ($result_options['domain'] ?? '') != 'counter' && 'counter' && empty($result_options['value'])) { $result_options['value'] = ''; } // format, not for selects/autocompletes/presets if (!$flag_select_or_autocomplete) { if (!empty($result_options['format'])) { if (!empty($this->errors['fields'][$result_options['error_name']]) && empty($this->errors['formats'][$result_options['error_name']])) { // nothing } else { $result_options['format_options'] = $result_options['format_options'] ?? []; if (!empty($result_options['format_depends'])) { $this->process_params_and_depends($result_options['format_depends'], $neighbouring_values, $options, true); $result_options['format_options'] = array_merge_hard($result_options['format_options'], $result_options['format_depends']); } $method = factory::method($result_options['format'], 'format'); $result_options['value'] = call_user_func_array([$method[0], $method[1]], [$result_options['value'], $result_options['format_options']]); } } } // align if (!empty($result_options['align'])) { $result_options['style'] = ($result_options['style'] ?? '') . 'text-align:' . $result_options['align'] . ';'; } // processing persistent if (!empty($result_options['persistent']) && $this->values_loaded) { if ($result_options['persistent'] === 'if_set') { $original_value = $detail = array_key_get($this->original_values, $result_options['values_key']); if (!empty($original_value)) { $result_options['readonly'] = true; } } else { if (count($result_options['values_key']) == 1) { // parent record $result_options['readonly'] = true; } else { if (empty($result_options['__new_row'])) { // details $temp = $result_options['values_key']; array_pop($temp); $detail = array_key_get($this->original_values, $temp); if (!empty($detail)) { $result_options['readonly'] = true; } } } } } // maxlength if (in_array($result_options['type'] ?? '', ['char', 'varchar']) && !empty($result_options['length'])) { $result_options['maxlength'] = $result_options['length']; } // global readonly if (!empty($this->misc_settings['global']['readonly']) && empty($result_options['navigation'])) { $result_options['readonly'] = true; } // title if (isset($options['options']['label_name'])) { $result_options['title'] = ($result_options['title'] ?? '') . ' ' . strip_tags(i18n(null, $options['options']['label_name'])); } } } // translate place holder if (array_key_exists('placeholder', $result_options)) { if (!empty($result_options['placeholder'])) { $result_options['placeholder'] = strip_tags(i18n(null, $result_options['placeholder'])); } } else { if (!empty($result_options['validator_method']) && empty($result_options['value'])) { $temp = object_validator_base::method($result_options['validator_method'], $result_options['value'], $result_options['validator_params'] ?? [], $options['options'], $neighbouring_values); if ($flag_select_or_autocomplete) { $placeholder = $temp['placeholder_select']; } else { $placeholder = $temp['placeholder']; } if (!empty($placeholder)) { $result_options['placeholder'] = strip_tags(i18n(null, $placeholder)); } } } // events foreach (numbers_frontend_html_class_html5::$events as $e) { if (!empty($result_options['readonly'])) { // important - readonly emenets cannot have events unset($result_options[$e]); } else { if (!empty($result_options[$e])) { $result_options[$e] = str_replace('this.form.submit();', 'numbers.form.trigger_submit(this);', $result_options[$e]); $result_options[$e] = str_replace('this.form.extended.', $this->misc_settings['extended_js_class'] . '.', $result_options[$e]); } } } break; case 'html': $element_method = null; break; default: throw new Exception('Render detail type: ' . $data['fm_part_type']); } // handling html_method if (isset($element_method)) { $method = factory::method($element_method, 'html'); $field_method_object = factory::model($method[0], true); // todo: unset non html attributes $value = $field_method_object->{$method[1]}($result_options); // building navigation if (!empty($result_options['navigation'])) { $name = 'navigation[' . $result_options['name'] . ']'; $temp = '<table width="100%" dir="ltr">'; // always left to right $temp .= '<tr>'; $temp .= '<td width="1%">' . html::button2(['name' => $name . '[first]', 'value' => html::icon(['type' => 'step-backward']), 'onclick' => 'numbers.form.trigger_submit_on_button(this);', 'title' => i18n(null, 'First')]) . '</td>'; $temp .= '<td width="1%"> </td>'; $temp .= '<td width="1%">' . html::button2(['name' => $name . '[previous]', 'value' => html::icon(['type' => 'caret-left']), 'onclick' => 'numbers.form.trigger_submit_on_button(this);', 'title' => i18n(null, 'Previous')]) . '</td>'; $temp .= '<td width="1%"> </td>'; $temp .= '<td width="90%">' . $value . '</td>'; $temp .= '<td width="1%"> </td>'; $temp .= '<td width="1%">' . html::button2(['name' => $name . '[refresh]', 'value' => html::icon(['type' => 'refresh']), 'onclick' => 'numbers.form.trigger_submit_on_button(this);', 'title' => i18n(null, 'Refresh')]) . '</td>'; $temp .= '<td width="1%"> </td>'; $temp .= '<td width="1%">' . html::button2(['name' => $name . '[next]', 'value' => html::icon(['type' => 'caret-right']), 'onclick' => 'numbers.form.trigger_submit_on_button(this);', 'title' => i18n(null, 'Next')]) . '</td>'; $temp .= '<td width="1%"> </td>'; $temp .= '<td width="1%">' . html::button2(['name' => $name . '[last]', 'value' => html::icon(['type' => 'step-forward']), 'onclick' => 'numbers.form.trigger_submit_on_button(this);', 'title' => i18n(null, 'Last')]) . '</td>'; $temp .= '</tr>'; $temp .= '</table>'; $value = $temp; } } // html suffix and prefix if (!empty($html_suffix)) { $value .= $html_suffix; } // if we need to display settings if (application::get('flag.numbers.frontend.html.form.show_field_settings')) { $id_original = $result_options['id'] . '__settings_original'; $id_modified = $result_options['id'] . '__settings_modified'; $value .= html::a(['href' => 'javascript:void(0);', 'onclick' => "\$('#{$id_original}').toggle();", 'value' => html::label2(['type' => 'primary', 'value' => count($options['options'])])]); $value .= html::a(['href' => 'javascript:void(0);', 'onclick' => "\$('#{$id_modified}').toggle();", 'value' => html::label2(['type' => 'warning', 'value' => count($result_options)])]); $value .= '<div id="' . $id_original . '" style="display:none; position: absolute; text-align: left; width: 500px; z-index: 32000;">' . print_r2($options['options'], true) . '</div>'; $value .= '<div id="' . $id_modified . '" style="display:none; position: absolute; text-align: left; width: 500px; z-index: 32000;">' . print_r2($result_options, true) . '</div>'; } // we need to put original options back if (!empty($options['options']['custom_renderer'])) { $options = $options_custom_renderer; } return $value; }