// +----------------------------------------------------------------------
// +----------------------------------------------------------------------
// | 表单生成类 https://github.com/LaravelCollective/html
// +----------------------------------------------------------------------
namespace form;
use think\facade\Request;
use think\helper\Arr;
class FormBuilder
{
/**
* 我们创建的标签名称数组
*
* @var array
*/
protected $labels = [];
/**
* 输入类型
*
* @var null
*/
protected $type = null;
/**
* 默认情况下不填充value的输入类型
*
* @var array
*/
protected $skipValueTypes = ['file', 'password', 'checkbox', 'radio'];
/**
* 创建一个CSRF令牌生成隐藏字段
*
* @param string $name
* @param string $type
*
* @return string
*/
public function token($name = '__token__', $type = 'md5')
{
$token = Request::token($name, $type);
return '';
}
/**
* 创建一个表单标签元素。
*
* @param $name
* @param null $value
* @param array $options
* @param bool $escape_html
*
* @return string
*/
public function label($name, $value = null, $options = [], $escape_html = true)
{
$this->labels[] = $name;
$options = $this->attributes($options);
$value = $this->formatLabel($name, $value);
if ($escape_html) {
$value = $this->entities($value);
}
return '';
}
/**
* 创建一个表单输入字段
*
* @param $type
* @param $name
* @param null $value
* @param array $options
*
* @return string
*/
public function input($type, $name, $value = null, $options = [])
{
$this->type = $type;
if (!isset($options['name'])) {
$options['name'] = $name;
}
//我们将得到给定字段的适当值。我们将寻找
//会话中的值查找旧输入数据中的值,然后我们将查找
//在模型实例中(如果已设置)。否则我们只会使用空的
$id = $this->getIdAttribute($name, $options);
if (!in_array($type, $this->skipValueTypes)) {
$value = $this->getValueAttribute($name, $value);
$options['class'] = isset($options['class']) ? $options['class'] . (stripos($options['class'], 'layui-input') !== false ? '' : ' layui-input') : 'layui-input';
}
//一旦我们有了类型、值和ID,我们就可以将它们合并到
//属性数组,以便将它们转换为HTML属性格式
//创建HTML元素时。然后,我们将返回整个输入。
$merge = compact('type', 'value', 'id');
$options = array_merge($options, $merge);
return 'attributes($options) . '>';
}
/**
* 创建一个文本输入字段
*
* @param string $name
* @param null $value
* @param array $options
*
* @return string
*/
public function text($name, $value = null, $options = [])
{
return $this->input('text', $name, $value, $options);
}
/**
* 创建一个密码输入字段
*
* @param string $name
* @param array $options
*
* @return string
*/
public function password(string $name, array $options = [])
{
return $this->input('password', $name, '', $options);
}
/**
* 创建一个范围输入选择器
*
* @param string $name
* @param null $value
* @param array $options
*
* @return string
*/
public function range($name, $value = null, $options = [])
{
return $this->input('range', $name, $value, $options);
}
/**
* 创建一个隐藏的输入字段
*
* @param string $name
* @param null $value
* @param array $options
*
* @return string
*/
public function hidden($name, $value = null, $options = [])
{
return $this->input('hidden', $name, $value, $options);
}
/**
* 创建一个电子邮件输入字段
*
* @param string $name
* @param null $value
* @param array $options
*
* @return string
*/
public function email($name, $value = null, $options = [])
{
return $this->input('email', $name, $value, $options);
}
/**
* 创建一个tel输入字段
*
* @param string $name
* @param null $value
* @param array $options
*
* @return string
*/
public function tel($name, $value = null, $options = [])
{
return $this->input('tel', $name, $value, $options);
}
/**
* 创建一个数字输入字段
*
* @param string $name
* @param null $value
* @param array $options
*
* @return string
*/
public function number($name, $value = null, $options = [])
{
return $this->input('number', $name, $value, $options);
}
/**
* 创建一个url输入字段
*
* @param string $name
* @param null $value
* @param array $options
*
* @return string
*/
public function url($name, $value = null, $options = [])
{
return $this->input('url', $name, $value, $options);
}
/**
* 创建一个textarea输入字段
*
* @param string $name
* @param null $value
* @param array $options
*
* @return string
*/
public function textarea($name, $value = null, $options = [])
{
$this->type = 'textarea';
if (!isset($options['name'])) {
$options['name'] = $name;
}
//接下来,我们将查找rows和cols属性,因为每个属性都是
//在textarea元素定义上。如果他们不在场,我们就
//为开发人员的这些属性假设一些合理的默认值。
$options = $this->setTextAreaSize($options);
$options['id'] = $this->getIdAttribute($name, $options);
$value = (string) $this->getValueAttribute($name, $value);
unset($options['size']);
//接下来,我们将把属性转换成字符串形式。我们还移除了
//“大小”属性,因为它只是一条通往行和列的捷径
//元素。然后我们将为我们创建最终的textarea元素HTML。
$options['class'] = isset($options['class']) ? $options['class'] . (stripos($options['class'], 'layui-textarea') !== false ? '' : ' layui-textarea') : 'layui-textarea';
$options = $this->attributes($options);
return '';
}
/**
* 创建百度富文本编辑器字段
*
* @param string $name
* @param string $value
* @param array $options
*
* @return string
*/
public function ueditor($name, $value = null, $options = [])
{
$domname = str_replace(['[', ']', '.'], '', $name);
if (!isset($options['name'])) {
$options['name'] = $name;
}
if (isset($options['class'])) {
$options['class'][] = 'js-ueditor';
} else {
$options['class'] = 'js-ueditor';
}
$value = (string) $this->getValueAttribute($name, $value);
$options = $this->attributes(array_merge(['id' => "c-{$domname}"], $options));
return '';
}
/**
* 创建一个选择框字段
*
* @param $name
* @param array $list
* @param null $selected
* @param array $selectAttributes
* @param array $optionsAttributes
* @param array $optgroupsAttributes
*
* @return string
*/
public function select(
$name,
$list = [],
$selected = null,
array $selectAttributes = [],
array $optionsAttributes = [],
array $optgroupsAttributes = []
) {
$this->type = 'select';
//在构建选择框时,“值”属性实际上就是所选的
//因此,我们将在检查模型或会话时使用该值
//应该提供一种方便的方法来重新填充post上的表格。
$selected = $this->getValueAttribute($name, $selected);
$selectAttributes['id'] = $this->getIdAttribute($name, $selectAttributes);
if (!isset($selectAttributes['name'])) {
$selectAttributes['name'] = $name;
}
//我们将简单地遍历这些选项,并为每个选项构建一个HTML值
//直到我们有一个HTML声明数组。然后我们会加入他们
//所有这些都整合到一个可以放在表单上的HTML元素中。
$html = [];
if (isset($selectAttributes['placeholder'])) {
$html[] = $this->placeholderOption($selectAttributes['placeholder'], $selected);
unset($selectAttributes['placeholder']);
}
foreach ($list as $value => $display) {
$optionAttributes = $optionsAttributes[$value] ?? [];
$optgroupAttributes = $optgroupsAttributes[$value] ?? [];
$html[] = $this->getSelectOption($display, $value, $selected, $optionAttributes, $optgroupAttributes);
}
//一旦我们拥有了所有这些HTML,我们就可以在之后将其加入到单个元素中
//将属性格式化为HTML“attributes”字符串,然后
//构建一个最终的select语句,它将包含所有的值。
$selectAttributes = $this->attributes($selectAttributes);
$list = implode('', $html);
return "";
}
/**
* 创建一个按钮字段
*
* @param string $value
* @param array $options
*
* @return string
*/
public function button($value = null, $options = [])
{
if (!array_key_exists('type', $options)) {
$options['type'] = 'button';
}
return '';
}
/**
* 创建单选按钮输入字段
*
* @param string $name
* @param mixed $value
* @param bool $checked
* @param array $options
*
* @return string
*/
public function radio($name, $value = null, $checked = null, $options = [])
{
if (is_null($value)) {
$value = $name;
}
if ($checked) {
$options['checked'] = 'checked';
}
return $this->input('radio', $name, $value, $options);
}
/**
* 创建一组单选框字段
*
* @param string $name
* @param array $list
* @param mixed $checked
* @param array $title
* @param array $options
*
* @return string
*/
public function radios($name, $list, $checked = null, $title = [], $options = [])
{
if (is_array($list)) {
$html = [];
$checked = is_null($checked) ? key($list) : $checked;
$checked = is_array($checked) ? $checked : explode(',', $checked);
foreach ($list as $k => $v) {
$options['id'] = "{$name}-{$k}";
$options['title'] = $title[$k] ?? $v;
$html[] = Form::radio($name, $k, in_array($k, $checked), $options);
}
return '
' . implode(' ', $html) . '
';
}
return '';
}
/**
* 创建复选按钮字段
*
* @param string $name
* @param mixed $value
* @param bool $checked
* @param array $options
*
* @return string
*/
public function checkbox($name, $value = 1, $checked = null, $options = [])
{
if ($checked) {
$options['checked'] = 'checked';
}
return $this->input('checkbox', $name, $value, $options);
}
/**
* 创建一组复选按钮框字段
*
* @param string $name
* @param array $list
* @param mixed $checked
* @param array $title
* @param array $options
*
* @return string
*/
public function checkboxs($name, $list, $checked, $title = [], $options = [])
{
if (is_array($list)) {
$html = [];
$checked = is_null($checked) ? [] : $checked;
$checked = is_array($checked) ? $checked : explode(',', $checked);
foreach ($list as $k => $v) {
$options['id'] = "{$name}-{$k}";
$options['title'] = $title[$k] ?? $v;
$html[] = Form::checkbox("{$name}[{$k}]", $k, in_array($k, $checked), $options);
}
return '' . implode(' ', $html) . '
';
}
return '';
}
/**
* 创建一个上传图片组件(单图)字段
*
* @param string $name
* @param string $value
* @param array $inputAttr
* @param array $uploadAttr
* @param array $chooseAttr
* @param array $previewAttr
*
* @return string
*/
public function image($name = null, $value = null, $inputAttr = [], $uploadAttr = [], $chooseAttr = [], $previewAttr = [])
{
$default = [
'data-type' => "image",
'data-mimetype' => 'image/gif,image/jpeg,image/png,image/jpg,image/bmp',
];
$uploadAttr = is_array($uploadAttr) ? array_merge($default, $uploadAttr) : $uploadAttr;
$chooseAttr = is_array($chooseAttr) ? array_merge($default, $chooseAttr) : $chooseAttr;
return $this->uploader($name, $value, $inputAttr, $uploadAttr, $chooseAttr, $previewAttr);
}
/**
* 创建一个上传图片组件(多图)字段
*
* @param string $name
* @param string $value
* @param array $inputAttr
* @param array $uploadAttr
* @param array $chooseAttr
* @param array $previewAttr
*
* @return string
*/
public function images($name = null, $value = null, $inputAttr = [], $uploadAttr = [], $chooseAttr = [], $previewAttr = [])
{
$default = [
'data-type' => "image",
'data-multiple' => 'true',
'data-mimetype' => 'image/gif,image/jpeg,image/png,image/jpg,image/bmp',
];
$uploadAttr = is_array($uploadAttr) ? array_merge($default, $uploadAttr) : $uploadAttr;
$chooseAttr = is_array($chooseAttr) ? array_merge($default, $chooseAttr) : $chooseAttr;
return $this->uploader($name, $value, $inputAttr, $uploadAttr, $chooseAttr, $previewAttr);
}
/**
* 创建上传文件组件(单文件)字段
*
* @param string $name
* @param string $value
* @param array $inputAttr
* @param array $uploadAttr
* @param array $chooseAttr
* @param array $previewAttr
*
* @return string
*/
public function upload($name = null, $value = null, $inputAttr = [], $uploadAttr = [], $chooseAttr = [], $previewAttr = [])
{
$default = [
'data-type' => "file",
];
$uploadAttr = is_array($uploadAttr) ? array_merge($default, $uploadAttr) : $uploadAttr;
$chooseAttr = is_array($chooseAttr) ? array_merge($default, $chooseAttr) : $chooseAttr;
return $this->uploader($name, $value, $inputAttr, $uploadAttr, $chooseAttr, $previewAttr);
}
/**
* 创建上传文件组件(多文件)字段
*
* @param string $name
* @param string $value
* @param array $inputAttr
* @param array $uploadAttr
* @param array $chooseAttr
* @param array $previewAttr
*
* @return string
*/
public function uploads($name = null, $value = null, $inputAttr = [], $uploadAttr = [], $chooseAttr = [], $previewAttr = [])
{
$default = [
'data-type' => "file",
'data-multiple' => 'true',
];
$uploadAttr = is_array($uploadAttr) ? array_merge($default, $uploadAttr) : $uploadAttr;
$chooseAttr = is_array($chooseAttr) ? array_merge($default, $chooseAttr) : $chooseAttr;
return $this->uploader($name, $value, $inputAttr, $uploadAttr, $chooseAttr, $previewAttr);
}
/**
* 创建颜色选择字段
*
* @param string $name
* @param string $value
* @param array $options
*
* @return string
*/
public function color($name = null, $value = null, $options = [])
{
$domname = str_replace(['[', ']', '.'], '', $name);
$input = $this->text($name, $value, array_merge(['id' => "c-{$domname}", 'placeholder' => '请选择颜色'], $options));
$html = <<
{$input}
EOD;
return $html;
}
/**
* 创建日期时间选择器字段
*
* @param string $name
* @param string $value
* @param array $options
*
* @return string
*/
public function datetime($name = null, $value = null, $options = [])
{
$value = is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
return $this->datetimepicker($name, $value, $options);
}
/**
* 日期时间选择器
*
* @param string $name
* @param mixed $value
* @param array $options
* @return string
*/
public function datetimepicker($name, $value, $options = [])
{
$value = is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
$options['class'] = isset($options['class']) ? $options['class'] . ' datetime' : 'datetime';
return $this->text($name, $value, $options);
}
/**
* 创建动态下拉列表字段
*
* @param string $name 名称
* @param mixed $value
* @param string $url 数据源地址
* @param string $field 显示的字段名称,默认为name
* @param string $primaryKey 主键,数据库中保存的值,默认为id
* @param array $options
*
* @return string
*/
public function selectpage($name, $value, $url, $field = null, $primaryKey = null, $options = [])
{
$options = array_merge($options, ['data-source' => $url, 'data-field' => $field ? $field : 'name', 'data-primary-key' => $primaryKey ? $primaryKey : 'id']);
if (isset($options['class'])) {
$options['class'][] = 'selectpage';
} else {
$options['class'] = 'selectpage';
}
return $this->text($name, $value, $options);
}
/**
* 创建动态下拉列表(复选)字段
*
* @param string $name 名称
* @param mixed $value
* @param string $url 数据源地址
* @param string $field 显示的字段名称,默认为name
* @param string $primaryKey 主键,数据库中保存的值,默认为id
* @param array $options
*
* @return string
*/
public function selectpages($name, $value, $url, $field = null, $primaryKey = null, $options = [])
{
$options['data-multiple'] = "true";
return $this->selectpage($name, $value, $url, $field, $primaryKey, $options);
}
protected function uploader($name = null, $value = null, $inputAttr = [], $uploadAttr = [], $chooseAttr = [], $previewAttr = [])
{
$domname = str_replace(['[', ']', '.'], '', $name);
$upload = $uploadAttr === false ? false : true;
$choose = $chooseAttr === false ? false : true;
$preview = $previewAttr === false ? false : true;
$options = [
'id' => "faupload_{$domname}",
'class' => "layui-btn faupload",
'data-input-id' => "c-{$domname}",
];
if ($preview) {
$options['data-preview-id'] = "p-{$domname}";
}
$uploadBtn = $upload ? $this->button(' ' . '上传', array_merge($options, $uploadAttr)) : '';
$options = [
'id' => "fachoose-{$domname}",
'class' => "layui-btn fachoose",
'data-input-id' => "c-{$domname}",
];
if ($preview) {
$options['data-preview-id'] = "p-{$domname}";
}
$chooseBtn = $choose ? $this->button(' ' . '选择', array_merge($options, $chooseAttr)) : '';
$previewAttrHtml = $this->attributes($previewAttr);
$previewArea = $preview ? '' : '';
$input = $this->text($name, $value, array_merge(['id' => "c-{$domname}", 'class' => 'layui-input'], $inputAttr));
$html = <<{$input}
{$uploadBtn}{$chooseBtn}
{$previewArea}
EOD;
return $html;
}
/**
* 获取给定值的选择选项
*
* @param string $display
* @param string $value
* @param string $selected
* @param array $attributes
* @param array $optgroupAttributes
*
* @return mixed
*/
protected function getSelectOption($display, $value, $selected, array $attributes = [], array $optgroupAttributes = [])
{
if (is_array($display)) {
//if (is_iterable($display)) {
return $this->optionGroup($display, $value, $selected, $optgroupAttributes, $attributes);
}
return $this->option($display, $value, $selected, $attributes);
}
/**
* 创建选项组表单元素
*
* @param array $list
* @param string $label
* @param string $selected
* @param array $attributes
* @param array $optionsAttributes
* @param integer $level
*
* @return string
*/
protected function optionGroup($list, $label, $selected, array $attributes = [], array $optionsAttributes = [], $level = 0)
{
$html = [];
$space = str_repeat(" ", $level);
foreach ($list as $value => $display) {
$optionAttributes = $optionsAttributes[$value] ?? [];
if (is_array($display)) {
//if (is_iterable($display)) {
$html[] = $this->optionGroup($display, $value, $selected, $attributes, $optionAttributes, $level + 5);
} else {
$html[] = $this->option($space . $display, $value, $selected, $optionAttributes);
}
}
return '';
}
/**
* 创建一个选择元素选项
*
* @param string $display
* @param string $value
* @param string $selected
* @param array $attributes
*
* @return mixed
*/
protected function option($display, $value, $selected, array $attributes = [])
{
$selected = $this->getSelectedValue($value, $selected);
$options = array_merge(['value' => $value, 'selected' => $selected], $attributes);
$string = '';
}
return $string;
}
/**
* 创建占位符选择元素选项
*
* @param $display
* @param $selected
*
* @return mixed
*/
protected function placeholderOption($display, $selected)
{
$selected = $this->getSelectedValue(null, $selected);
$options = [
'selected' => $selected,
'value' => '',
];
return '';
}
/**
* 确定是否选择了该值
*
* @param string $value
* @param string $selected
*
* @return null|string
*/
protected function getSelectedValue($value, $selected)
{
if (is_array($selected)) {
return in_array($value, $selected, true) || in_array((string) $value, $selected, true) ? 'selected' : null;
}
if (is_int($value) && is_bool($selected)) {
return (bool) $value === $selected;
}
return ((string) $value === (string) $selected) ? 'selected' : null;
}
/**
* 在属性上设置文本区域大小
*
* @param array $options
*
* @return array
*/
protected function setTextAreaSize($options)
{
if (isset($options['size'])) {
return $this->setQuickTextAreaSize($options);
}
//如果没有指定“size”属性,我们将只查找常规
//列和行属性,如果这些属性在
//属性数组。然后我们将返回整个选项数组。
$cols = Arr::get($options, 'cols', 50);
$rows = Arr::get($options, 'rows', 10);
return array_merge($options, compact('cols', 'rows'));
}
/**
* 使用快速“大小”属性设置文本区域大小
*
* @param array $options
*
* @return array
*/
protected function setQuickTextAreaSize($options)
{
$segments = explode('x', $options['size']);
return array_merge($options, ['cols' => $segments[0], 'rows' => $segments[1]]);
}
/**
* 获取应分配给字段的值
*
* @param string $name
* @param string $value
*
* @return mixed
*/
protected function getValueAttribute($name, $value = null)
{
if (is_null($name)) {
return $value;
}
if (!is_null($value)) {
return $value;
}
}
/**
* 将HTML字符串转换为实体
*
* @param string $value
*
* @return string
*/
protected function entities($value)
{
return htmlentities($value, ENT_QUOTES, 'UTF-8', false);
}
/**
* 获取字段名的ID属性
*
* @param string $name
* @param array $attributes
*
* @return string
*/
public function getIdAttribute($name, $attributes)
{
if (array_key_exists('id', $attributes)) {
return $attributes['id'];
}
if (in_array($name, $this->labels)) {
return $name;
}
}
/**
* 设置标签值的格式。
*
* @param string $name
* @param string|null $value
*
* @return string
*/
protected function formatLabel($name, $value)
{
return $value ?: ucwords(str_replace('_', ' ', $name));
}
/**
* 从数组生成HTML属性字符串
*
* @param array $attributes
*
* @return string
*/
public function attributes($attributes)
{
$html = [];
foreach ((array) $attributes as $key => $value) {
$element = $this->attributeElement($key, $value);
if (!is_null($element)) {
$html[] = $element;
}
}
return count($html) > 0 ? ' ' . implode(' ', $html) : '';
}
/**
* 构建单个属性元素
*
* @param string $key
* @param string $value
*
* @return string
*/
protected function attributeElement($key, $value)
{
//[required]之类的HTML属性转换为正确的形式,而不是使用错误的数字。
if (is_numeric($key)) {
return $value;
}
// 将布尔属性视为HTML属性
if (is_bool($value) && $key !== 'value') {
return $value ? $key : '';
}
//多个class属性
if (is_array($value) && $key === 'class') {
return 'class="' . implode(' ', $value) . '"';
}
if (!is_null($value)) {
if (is_array($value) || stripos($value, '"') !== false) {
$value = is_array($value) ? json_encode($value, JSON_UNESCAPED_UNICODE) : $value;
return $key . "='" . $value . "'";
} else {
return $key . '="' . $value . '"';
}
}
}
}
if (!function_exists('e')) {
/**
* 将HTML特殊字符编码为字符串
*
* @param string $value
* @param bool $doubleEncode
*
* @return string
*/
function e($value, $doubleEncode = true)
{
if (is_array($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
}
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8', $doubleEncode);
}
}