表单
基础表单
在 zin 中使用基础表单可以通过 formBase
和 form
部件实现,他们用于构建 HTML 表单 <form>
元素的基本结构。 form
相比较 formBase
提供了 items
、fields
等属性来通过配置定义表单,而且支持通过 layout
来自定义布局。
表单控件
在 zin 中提供了大量表单控件,这些控件可以直接通过 zin 部件的方式进行声明调用,下面列举常用表单控件。
- 内容输入
input
- 复选框
checkbox
- 单选框
radio
- 开关
switcher
- 复选框列表
checkList
- 单选框列表
radioList
- 输入组
inputGroup
- 输入框
inputControl
- 选择框
select
- 下拉选择框
picker
- 文本编辑器
editor
- 日期选择框
datePicker
- 时间选择框
timePicker
- 日期时间选择框
datetimePicker
- 优先级选择框
priPicker
- 严重程度选择框
severityPicker
- 颜色选择器
colorPicker
- 文件上传
upload
- 带颜色文本输入
colorInput
在 zin 中还提供了一个通用表单控件部件 control
用于动态定义表单控件。
表单布局
布局设置
zin 提供了多种布局方式,可以在 form()
内通过 layout
属性来设置表单的布局方式,包括:
horz
:水平布局,表单经典布局,表单项标签在左侧,右侧表单控件。grid
:网格布局,表单新的布局,表单项标签在上方,下方为表单控件,表单项可以水平排列。normal
:普通布局,表单项从上往下排列布局,每个表单项标签在上方,下方为表单控件。
表单布局组件
要实现表单布局,还需要搭配如下部件实现:
- 表单组
formGroup
:用于实现表单布局的常用单元,包括表单项标签和控件,适用于所有表单布局。 - 表单行
formRow
:用于构建水平布局,使子部件以內联的形式排列于一行之中,它一般与表单组部件formGroup
一起使用。
表单面板
表单面板是对表单的进一步封装,提供了额外的头部标题和自定义按钮等设置,在 zin 中提供了如下部件用于使用表单面板:
- 表单面板
formPanel
:用于构建经典的水平布局。 - 网格布局表单面板
formGridPanel
:用于构建网格表单布局。
formPanel
和 formGridPanel
继承自 面板 panel
,可以同时使用 panel
和 form
的属性进行功能定制。
表单字段配置
字段定义
字段和字段列表的定义参考文档 字段。
禅道字段列表定义约定
在禅道中,各个模块的字段列表定义位置约定如下:
- 模块的基础字段列表定义在
module/<MODULE_NAME>/ui/common.field.php
文件中; - 模块具体页面的字段列表定义在
module/<MODULE_NAME>/ui/<METHOD_NAME>.field.php
文件中; - 无需在视图中手动导入上述字段定义文件,禅道会自动导入;
下面为一个实例:
<?php
namespace zin;
global $lang;
$fields = defineFieldList('bug');
$fields->field('product')
->required()
->items(data('products'))
->value(data('bug.productID'));
$fields->field('branch')
->items(data('branchs'))
->value(data('bug.branch'));
$fields->field('deadline')
->control('datePicker');
$fields->field('title')
->width('full');
$fields->field('color')
->control('color');
$fields->field('keywords');
$fields->field('case')->control('hidden')->value(data('bug.caseID'));
$fields->field('caseVersion')->control('hidden')->value(data('bug.version'));
$fields->field('result')->control('hidden')->value(data('bug.runID'));
$fields->field('testtask')->control('hidden')->value(data('bug.testtask'));
$fields = defineFieldList('bug.kanban');
$fields->field('region')
->label($lang->kanbancard->region)
->items(data('regionPairs'))
->value(data('regionID'));
$fields->field('lane')
->label($lang->kanbancard->lane)
->items(data('lanePairs'))
->value(data('laneID'));
<?php
namespace zin;
global $lang;
$fields = defineFieldList('bug.create', 'bug', '!branch,color');
$fields->field('product')
->hidden(data('product.shadow'))
->control('inputGroup')
->items(false)
->itemBegin('product')->type('picker')->items(data('products'))->value(data('bug.productID'))->itemEnd()
->item((data('product.type') !== 'normal' && isset(data('products')[data('bug.productID')])) ? field('branch')->type('picker')->boxClass('flex-none')->width('100px')->name('branch')->items(data('branches'))->value(data('bug.branch')) : null);
$fields->field('title')
->width('full')
->control('colorInput', array('colorValue' => data('bug.color')));
<?php
declare(strict_types=1);
namespace zin;
$fields = useFields('bug.create');
if(!empty($executionType) && $executionType == 'kanban') $fields->merge('bug.kanban');
jsVar('bug', $bug);
jsVar('moduleID', $bug->moduleID);
jsVar('tab', $this->app->tab);
jsVar('createRelease', $lang->release->create);
jsVar('refresh', $lang->refreshIcon);
jsVar('projectExecutionPairs', $projectExecutionPairs);
$autoLoad = array();
$autoLoad['product'] = 'product,module,openedBuild,execution,project,story,task,assignedTo';
$autoLoad['branch'] = 'module,openedBuild,execution,project,story,task,assignedTo';
$autoLoad['module'] = 'assignedTo,story';
$autoLoad['project'] = 'openedBuild,execution,story,task,assignedTo';
$autoLoad['execution'] = 'openedBuild,story,task,assignedTo';
$autoLoad['region'] = 'lane';
formGridPanel
(
set::title($lang->bug->create),
set::fields($fields),
set::loadUrl($loadUrl),
set::autoLoad($autoLoad),
on::click('#allBuilds', 'loadAllBuilds'),
on::click('#allUsers', 'loadAllUsers'),
on::click('#refreshExecutionBuild', 'refreshExecutionBuild'),
on::click('#refreshProductBuild', 'refreshProductBuild'),
);
使用字段
通过 useFields()
方法来引用名称的方式来使用字段列表中的字段,例如:
/** 使用字段(省略定义字段的代码)。 */
$fields = useFields('bug', $isKanban ? 'bug.kanban' : null, '!color,region');
/** 获取字段列表中的所有字段。 */
$names = $fields->names(); // array('title', 'product', 'branch', 'lane')
修改字段
字段列表实例 fieldList
上提供了一些方法可以修改字段列表,例如:
/** 修改 title 字段。 */
$fields->field('title')->label('标题')->required();
/** 根据条件移除 branch 字段。 */
if($noBranch)
{
$fields->remove('branch');
}
/** 增加新的字段。 */
$fields->field('newField')->label('新字段');
批量定义字段标签数据
通过字段列表实例方法 setLabelData()
可以批量定义字段标签数据,例如:
$labelData = array(
'title' => '标题',
'product' => '产品',
'branch' => '分支',
'lane' => '泳道',
'newField' => '新字段',
);
$fields->setLabelData($labelData);
批量定义字段默认值数据
通过字段列表实例方法 setValueData()
可以批量定义字段默认值数据,例如:
$valueData = array(
'title' => '测试',
'product' => '1',
'branch' => '2',
'lane' => '3',
'newField' => '4',
);
$fields->setValueData($valueData);
在表单中使用字段列表
通过 fields
属性可以将字段列表应用到表单相关部件中,所有支持 fields
属性的部件包括:
- 表单
form
基础表单。 - 表单面板
formPanel
:用于构建经典的水平布局。 - 网格布局表单面板
formGridPanel
:用于构建网格表单布局。
示例:
/** 使用字段(省略定义字段的代码)。 */
$fields = useFields('bug', $isKanban ? 'bug.kanban' : null, '!color,region');
/** 修改 title 字段。 */
$fields->field('title')->label('标题')->required();
/** 根据条件移除 branch 字段。 */
if($noBranch)
{
$fields->remove('branch');
}
/** 增加新的字段。 */
$fields->field('newField')->label('新字段');
/** 批量定义字段标签数据。 */
$labelData = array(
'title' => '标题',
'product' => '产品',
'branch' => '分支',
'lane' => '泳道',
'newField' => '新字段',
);
$fields->setLabelData($labelData);
/** 批量定义字段默认值数据。 */
$valueData = array(
'title' => '测试',
'product' => '1',
'branch' => '2',
'lane' => '3',
'newField' => '4',
);
$fields->setValueData($valueData);
/** 定义网格表单面板。 */
formGirdPanel
(
/* 通过 fields 定义表单项。 */
set::fields($fields) // 设置字段列表
);
表单字段顺序
表单字段顺序默认取决于字段列表中的顺序,但也可以通过 fieldList
实例方法 orders()
来调整字段顺序,下面分别进行介绍。
修改字段列表定义顺序
在表单字段列表 fieldList
实例上提供了如下方法来调整字段顺序:
/** 将字段移动到字段列表的开头。 */
moveToBegin(string $names): fieldList
/** 将字段移动到字段列表的末尾。 */
moveToEnd(string $names): fieldList
/** 将字段移动到指定字段的前方位置。 */
moveBefore(string $names, string $beforeName): fieldList
/** 将字段移动到指定字段的后方位置。 */
moveAfter(string $names, string $afterName): fieldList
/** 对字段列表中的字段进行排序。 */
sort(string|array ...$sortNames): fieldList
提示
- 可以通过
,
分隔多个字段名称,也可以通过多个参数来指定多个字段名称。 - 其中排序字段可以以
'$BEGIN'
和'$END'
分别表示移动到字段列表的开始或结尾位置。
示例:
/** 定义名称为 `bug` 的字段列表。 */
$fields = defineFieldList('bug');
/** 在字段列表上定义字段。 */
$fields->field('title');
$fields->field('product')->label('产品')->required();
$fields->field('branch');
$fields->field('bug');
$fields->field('color');
$fields->field('type');
$fields->field('keywords');
/* 将 color 字段移动到字段列表的开头: */
$fields->moveToBegin('color');
// 或 $fields->field('color')->moveToBegin();
// 调整后的顺序为:color, title, product, branch, bug, type, keywords
/* 将 title 字段移动到字段列表的末尾: */
$fields->moveToEnd('title,branch');
// 调整后的顺序为:color, product, bug, type, keywords, title, branch
/* 将字段移到指定字段后面: */
$fields->moveAfter('title,branch', 'color');
// 调整后的顺序为:color, title, branch, product, bug, type, keywords
/* 将字段移到指定字段前面: */
$fields->moveBefore('title,branch', 'color');
// 调整后的顺序为:title, branch, color, product, bug, type, keywords
/* 按规则对字段进行排序: */
$fields->sort('color,title,branch');
// 将 title 移到 color 后面,然后将 branch 移到 title 后面
// 调整后的顺序为:color, title, branch, product, bug, type, keywords
/* 指定多个规则对字段进行排序: */
$fields->sort('color,title,branch', '$BEGIN,type,product');
// 将 title 移到 color 后面,然后将 branch 移到 title 后面
// 将 type 移到字段列表的开始,然后将 product 移到 type 后面
// 调整后的顺序为:type, product, color, title, branch, bug, keywords
定义字段导出顺序
默认情况下表单中的字段会安装字段列表中显示的顺序进行显示,但有时需要调整表单中字段的显示顺序,这时可以通过 fieldList
实例方法 orders()
来定义字段导出顺序,如果需要为完整模式定义字段导出顺序,则可以通过 fullModeOrders()
方法来定义。相关方法定义如下:
/** 定义字段导出顺序。 */
orders(string|array ...$names): fieldList
/** 定义完整模式字段导出顺序。 */
fullModeOrders(string|array ...$names): fieldList
提示
- 可以通过
,
分隔多个字段名称,也可以通过多个参数来指定多个字段名称。 - 其中排序字段可以以
'$BEGIN'
和'$END'
分别表示移动到字段列表的开始或结尾位置。
示例:
/** 定义名称为 `bug` 的字段列表。 */
$fields = defineFieldList('bug');
/** 在字段列表上定义字段。 */
$fields->field('title');
$fields->field('product')->label('产品')->required();
$fields->field('branch');
$fields->field('bug');
$fields->field('color');
/** 定义字段导出顺序。 */
$fields->orders('branch,title', '$BEGIN,color,bug');
// 将 title 移到 branch 字段后面,然后将 color 移到列表开始,接着将 bug 移到 color 后面
// 默认模式下的顺序为:color, bug, product, branch, title,
/** 定义完整模式字段导出顺序。 */
$fields->fullModeOrders('branch,title,bug,color,product');
// 依次按照 branch, title, bug, color, product 的顺序导出字段
// 完整模式下的顺序为:branch, title, bug, color, product
表单字段渲染
表单字段渲染一般通过 control
属性来指定,详细用法参考 字段 ➡️ 设置字段表单控件。下面介绍主要用法。
渲染指定类型的表单控件
将 control
属性设置为 zin 部件名称即可,如果要设置渲染的部件其他属性,应该将 control
属性设置为一个数组,数组内通过 control
属性指定部件名称,其他属性作为部件实际渲染的属性。
/** 使用字段(省略定义字段的代码)。 */
$fields = useFields('bug', $isKanban ? 'bug.kanban' : null, '!color,region');
/* 字段控件渲染为普通输入框。 */
$fields->field('title')->control('input');
/* 等同于: */
$fields->field('title')->control(array('control' => 'input'));
/* 省略 control 设置。 */
$fields->field('title');
/* 字段控件渲染为下拉选择器。 */
$fields->field('type')->control('picker', array('multiple' => true, 'items' => $items));
/* 字段控件渲染为下拉选择器。 */
$fields->field('type')->control('picker', array('multiple' => true, 'items' => $items));
/* 通过 `controlBegin` 和 `controlEnd` 链式设置控件属性。 */
$fields->field('title')
->controlBegin('picker')
->multiple()
->items($items)
->controlEnd();
直接指定渲染的内容
有时一些特殊内容没有合适的控件,可以通过 control
属性直接指定渲染的内容,或者指定为回调函数,回调函数的返回值作为渲染的内容,例如:
直接设置渲染的内容:
$fields->field('title')->control(div('hello world')); // 渲染为 div。
通过回调函数来设置渲染的内容,回调函数第一个参数为当前字段上的所有属性:
$buildTitleField = function($props)
{
return div
(
'hello world',
data('lang.zentaoPMS'),
json_encode($props)
);
};
$fields->field('title')->control($buildTitleField);
表单联动
通过 autoLoad
属性设置联动规则
表单联动为当表单某个字段变更时,需要更新其他字段的选项,在 zin 中可以通过中相关表单布局中的 autoLoad
属性来设置联动规则。所有支持 autoLoad
属性的部件包括:
- 表单
form
基础表单。 - 表单面板
formPanel
:用于构建经典的水平布局。 - 网格布局表单面板
formGridPanel
:用于构建网格表单布局。
联动规则通过一个关联数组定义,数组键名为触发联动的字段名或选择器,键值为联动触发时要更新的表单字段,通过 ,
分隔多个需要更新的字段,下面为一个实例:
/* 定义联动规则数组。 */
$autoLoad = array();
/* 当 [name="product"] 表单控件变更时,更新 product,module,openedBuild,execution,project 表单控件。 */
$autoLoad['product'] = 'product,module,openedBuild,execution,project';
/* 当 [name="module"] 表单控件变更时,更新 assignedTo,story 表单控件。 */
$autoLoad['module'] = 'assignedTo,story';
/* 当 [name="region"] 表单控件变更时,更新 lane 表单控件。 */
$autoLoad['[name="region"]'] = 'lane';
/* 声明网格表单面板部件。 */
formGridPanel
(
/* 通过 fields 定义表单项。 */
set::fields(useFields('bug')), // 设置字段列表
/* 定义表单字段默认值获取对象。 */
set::data($bug),
/* 定义表单字段标签获取对象。 */
set::labelData($lang->bug),
/* 定义联动规则。 */
set::autoLoad($autoLoad)
);
通过 fieldList
的 autoLoad()
方法设置联动规则
通过字段列表实例方法 autoLoad()
可以设置联动规则,例如:
/** 使用字段(省略定义字段的代码)。 */
$fields = useFields('bug', $isKanban ? 'bug.kanban' : null, '!color,region');
/** 通过字段列表实例方法 `autoLoad()` 可以设置联动规则。 */
$fields->autoLoad('product', 'product,module,openedBuild,execution,project')
->autoLoad(array('module' => 'assignedTo,story', '[name="region"]' => 'lane'));
表单提交
表单默认启用了异步提交的功能,即表单提交后,不会刷新页面,而是通过 ajax 请求后台处理表单数据,然后根据后台返回的结果进行失败或成功的相应处理。
如 示例 中的 form
部件声明:
form
(
...
);
<form class="form load-indicator form-grid form-ajax" id="zin10" action="/project-create.html" method="post">
...
<div class="form-row">
<div class="toolbar form-actions form-group gap-4 no-label">
<button class="toolbar-item btn primary" type="submit"><span class="text">保存</span></button>
<a class="toolbar-item btn btn-default" type="button" href=""><span class="text">返回</span></a>
</div>
</div>
<script>(function(){$(() => zui.create("ajaxForm","#zin10",[]));}())</script>
</form>
通过上面的示例代码可以看出,在表单的最后输出了 ZUI JS 组件
的绑定脚本 (function(){$(() => zui.create("ajaxForm","#zin10",[]));}())
,将表单绑定为 ajaxForm
组件,由该组件实现了表单的异步交互。
提示
- 如需要停用表单的异步提交功能,可将
target
属性设置为非ajax
值。 - 设置表单
action
属性,指定表单提交的目标地址。
Ajax 表单用法
在禅道中所有表单通过 ajax 的形式向后端提交,后端根据情况通过 json 的形式向前端返回结果。下面以示例的方式分别说明提交失败和成功时的结果定义。
提交失败的处理结果
通过 message
字段指定失败信息:
{
"result": "fail",
"message": "项目名称不能为空"
}
通常我们需要告诉用户页面哪个字段验证失败,这时可以为 message
指定一个对象表明所有存在错误的字段:
{
"result": "fail",
"message":
{
"name": "项目名称不能为空",
"begin": "开始日期不能为空",
"end": ["结束日期不能为空", "结束日期不能小于开始日期"]
}
}
失败结果的 message
数据的键为表单控件的 name
属性值,值为错误提示信息。
提交成功的处理结果
当表单提交成功时,可以通过各种字段来指定后续操作,下面为一个完整的例子,实际情况中按需定义:
{
"result": "success",
/* 此消息会在界面进行短暂提示 */
"message": "创建成功",
/* 提交成功后要在当前应用跳转的页面,该属性可以为其他类型的值,参考下文 */
"load": "project-browse-1.html",
/* 提交成功后关闭对话框,该属性可以为其他类型的值,参考下文 */
"closeModal": true,
/* 要执行的回调函数,该属性可以为其他类型的值,参考下文 */
"callback": "loadCurrentPage();"
}
load
属性的所有形式
类型 | 说明 |
---|---|
string | 当前页面通过一个具体的 url 更新,通常用于表单界面跳回列表页。 |
true | 则直接更新当前应用页面,通常适用于对话框表单。 |
'table' | 直接更新当前应用页面中的列表,确保界面用到了 dtable。 |
'modal' | 更新当前显示的对话框 |
'login' | 跳转到登录页面 |
{url: string, selector: string | string[]} | 更新当前应用页面中的匹配指定选择器的内容,例如 {url: 'project-browse-1.html', selector: '#featureBar>*'} 。 |
{confirm: string, confirmed: string | {url: string, selector: string | string[]}, canceled: string | {url: string, selector: string | string[]}} | 先弹出一个确认对话框,确认后再执行更新操作,没有确认执行另一个更新操作,例如 {confirm: '要返回列表页面吗?', confirmed: {url: 'project-browse-1.html', selector: '#featureBar>*'}, canceled: 'task-view-1.html'} 。 |
OpenUrlOptions | 通过 load 参数指定一个 openUrl 方法所需的选项,详情参考文档 禅道页面导航 → 万能的 openUrl。 |
closeModal
属性的所有形式
类型 | 说明 |
---|---|
true | 关闭最后一个打开的对话框,通常用于通过对话框打开的表单,指定次属性可以在表单提交成功后关闭当前对话框。 |
string | 通过一个字符串指定要关闭的对话框的 ID,例如 closeModal: "myModal" 。 |
callback
属性的所有形式
类型 | 说明 |
---|---|
string | 指定一个函数名,例如 callback: "loadCurrentPage" ,或者指定调用代码,例如 callback: 'loadCurrentPage("#featureBar>*");' 。 |
{name: string, params: unknown[]} | 通过一个对象分别指定函数名和调用参数,例如 {name: 'loadCurrentPage', params: ['#featureBar>*']} 。 |
手动提交
有时需要手动提交数据到服务器,例如用户点击删除按钮,这时需要将所删除的对象 ID 提交到服务器,服务器返回提交结构定义对象,然后根据提交结果执行相应的操作。这时可以使用 $.ajaxSubmit
方法来实现,该方法定义如下:
function $.ajaxSubmit(options: AjaxSubmitOptions): Promise<[result: AjaxFormResult | undefined, error: Error | undefined]>;
该方法需要传入一个参数对象,定义如下:
type AjaxSubmitOptions = {
/* 提交的目标地址,例如 'project-delete-123.html'。 */
url: string;
/* 要提交的数据,例如 {id: 123}。 */
data?: Record<string, unknown> | FormData;
/* 提交的方式,默认 'POST'。 */
method?: string;
/* 提交的数据类型,默认 'json'。 */
headers?: HeadersInit;
/* 目标元素。 */
element?: HTMLElement;
/* 提交时要在目标元素上添加的类名。 */
loadingClass?: string;
/* 提交前的回调函数,返回 false 取消提交。 */
beforeSubmit?: (options: AjaxSubmitOptions) => false | void;
/* 发起请求前的回调函数,可以修改 formData 返回新的 FormData。 */
beforeSend?: (formData: FormData) => void | FormData;
/* 提交成功后的回调函数,返回 false 取消后续操作。 */
onSuccess?: (result: AjaxFormResult) => void | false;
/* 提交失败后的回调函数,返回 false 取消后续操作。 */
onFail?: (result: AjaxFormResult) => void | false;
/* 请求发生错误的回调函数。 */
onError?: (error: Error, responseText?: string) => void;
/* 请求完成的回调函数。 */
onComplete?: (result?: AjaxFormResult, error?: Error) => void;
/* 当需要显示提示消息的回调函数。 */
onMessage?: (message: string | Record<string, string | string[]>, result: AjaxFormResult) => void;
/* 提交成功后要在当前应用跳转的页面或加载选项,此选项会覆盖后端返回的 load 参数。 */
load?: string | {url?: string, selector?: string | string[]};
/* 提交成功后关闭对话框,此选项会覆盖后端返回的 closeModal 参数。 */
closeModal?: boolean | string;
/* 要执行的回调函数,此选项会覆盖后端返回的 callback 参数。 */
callback?: string | {name: string, target?: string, params?: unknown[]};
};
该方法会通过 Promise
使用数组返回两个值,第一个值为 AjaxFormResult
,第二个为 Error
对象,根据实际情况,这两个值都有可能为 undefined
。
下面为一个简单例子:
<button type="button" class="btn" onclick="deleteProject(123)">删除项目</button>
<script>
function deleteProject(id)
{
$.ajaxSubmit({
/* 请求地址。 */
url: 'project-delete.html',
/* 请求参数。 */
data: {id: id},
/* 请求成功后关闭对话框。 */
closeModal: true,
/* 请求成功后自动跳转到项目列表页面。 */
load: 'project-browse-1.html',
});
}
</script>
$.ajaxSubmit
还可以通过添加类名 .ajax-submit
在元素上触发,当元素被点击时通过获取 data-*
作为初始化选项调用 $.ajaxSubmit
方法,例如上例可以改写为:
<button type="button" class="btn ajax-submit" data-url="project-delete.html" data-data='{"id": 123}' data-close-modal="true" data-load="project-browse-1.html">删除项目</button>
综合示例
下面为禅道创建项目集页面(program-create
)的 zin 实现代码(部分处理逻辑已经被省略):
<?php
namespace zin;
$parentID = $parentProgram->id ?? 0;
$currency = $parentID ? $parentProgram->budgetUnit : $config->project->defaultCurrency;
$aclList = $parentProgram ? $lang->program->subAclList : $lang->program->aclList;
$budgetPlaceholder = $parentProgram ? $lang->program->parentBudget . zget($lang->project->currencySymbol, $parentProgram->budgetUnit) . $budgetLeft : '';
$budgetAvaliable = !$parentID || $budgetLeft;
jsVar('LONG_TIME', LONG_TIME);
jsVar('lang', ['budgetOverrun' => $lang->project->budgetOverrun, 'currencySymbol' => $lang->project->currencySymbol, 'ignore' => $lang->program->ignore]);
jsVar('weekend', $config->execution->weekend);
set::title($parentID ? $lang->program->children : $lang->program->create);
formPanel
(
on::change('#parent', 'onParentChange'),
on::change('#budget', 'onBudgetChange'),
on::change('#future', 'onFutureChange'),
on::change('#acl', 'onAclChange'),
formGroup
(
set::width('1/2'),
set::name('parent'),
set::label($lang->program->parent),
set::disabled($parentID),
set::value($parentID),
set::items($parents),
),
formGroup
(
set::width('1/2'),
set::name('name'),
set::strong(true),
set::label($lang->program->name)
),
formGroup
(
set::width('1/4'),
set::name('PM'),
set::label($lang->program->PM),
set::items($pmUsers)
),
formRow
(
set::id('budgetRow'),
formGroup
(
set::width('1/2'),
set::label($lang->program->budget),
inputGroup
(
set::seg(true),
input
(
set::name('budget'),
set::placeholder($budgetPlaceholder),
set::disabled(!$budgetAvaliable),
set('data-budget-left', $budgetLeft),
set('data-currency-symbol', $parentProgram ? zget($lang->project->currencySymbol, $parentProgram->budgetUnit) : NULL),
),
select
(
zui::width('1/3'),
set::name('budgetUnit'),
set::disabled($parentID || !$budgetAvaliable),
set::items($budgetUnitList),
set::value($currency)
)
)
),
formHidden('budgetUnit', $currency),
formGroup
(
set::name('future'),
set::value('1'),
set::disabled(!$budgetAvaliable),
set::control(['type' => 'checkbox', 'rootClass' => 'ml-4', 'text' => $lang->project->future, 'checked' => !$budgetAvaliable])
),
),
formRow
(
formGroup
(
set::width('1/2'),
set::label($lang->project->dateRange),
set::required(true),
inputGroup
(
set::seg(true),
input
(
set::type('date'),
set::name('begin'),
set::value(date('Y-m-d')),
set::placeholder($lang->project->begin),
set::required(true),
on::change('computeWorkDays')
),
$lang->project->to,
input
(
set::type('date'),
set::name('end'),
set::placeholder($lang->project->end),
set::required(true),
on::change('outOfDateTip')
),
)
),
formGroup
(
set::name('delta'),
set::class('pl-4'),
set::control(['type' => 'radioList', 'inline' => true, 'rootClass' => 'ml-4', 'items' => $lang->program->endList]),
on::change('computeEndDate')
),
),
formGroup
(
set::name('desc'),
set::label($lang->program->desc),
set::control('editor')
),
formHidden('status', 'wait'),
formGroup
(
set::name('acl'),
set::label($lang->program->acl),
set::value('private'),
set::items($aclList),
set::control('radioList'),
),
formRow
(
set::id('whitelistRow'),
formGroup
(
set::width('3/4'),
set::name('whitelist'),
set::label($lang->whitelist),
set::control('select')
)
)
);
render();