Commit a6bb4db3 authored by Rafael Monnerat's avatar Rafael Monnerat

Split JSON Editor from the parameter form implementation

See merge request nexedi/slapos.core!518
parents 4c58c4b9 f253d674
Pipeline #27845 failed with stage
in 0 seconds
......@@ -94,6 +94,9 @@ url_list = [
"gadget_erp5_page_slap_parameter_form.css",
"gadget_erp5_page_slap_parameter_form.html",
"gadget_erp5_page_slap_parameter_form.js",
"gadget_erp5_page_slap_json_form.css",
"gadget_erp5_page_slap_json_form.html",
"gadget_erp5_page_slap_json_form.js",
"gadget_erp5_page_slap_payment_result.html",
"gadget_erp5_page_slap_person_get_token.html",
"gadget_erp5_page_slap_person_get_token.js",
......
.subfield span:not(.slapos-parameter) {
font-weight: normal;
font-style: italic;
padding-left: 7px;
color: rgb(94, 127, 141)
}
.subfield select { margin-bottom: 10px;}
.subfield textarea {width: 250px; height: 60px;}
.subfield .error {
color: #E82525;
font-weight: 700;
}
.input button {margin-left: 10px;}
.bt_close, .subfield .slapos-parameter-dict-key span.bt_close{
padding: 0 6px;
display: block;
float: right;
text-overflow:clip;
white-space:nowrap;
overflow: hidden;
font-size: 1.5em;
border-radius: 2px;
}
.bt_close:hover {
background: #c9c9c9;
color: #fff;
}
.display-none {
display: none;
}
.non-editable > div.input {border: 1px solid rgb(201, 201, 201); padding: 5px; background: white; font-weight: normal;
margin: 5px 0 10px; max-height: 250px; width: 80%;}
textarea.slapos-parameter {
width: 400px;
height: 100px;
}
label.slapos-parameter-dict-key::before {
content: "\25BC Parameter Entry: ";
}
label.slapos-parameter-dict-key-colapse::before {
content: "\25BA Parameter Entry: ";
}
div.slapos-parameter-dict-key {
margin-top: 10px;
margin-left: 6px;
background: rgb(245, 245, 245);
border: 1px solid rgb(230, 230, 230);
padding: 5px;
}
/* cleanup */
fieldset > .subfield > label:not(.slapos-parameter-dict-key) {
color: rgb(112, 125, 136);
}
fieldset > div.subfield {
padding-left: 15px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.field input ~ span,
.subfield input ~ span {
position: relative;
top: -27px;
pointer-events: none;
cursor: text;
}
.field input:not(:placeholder-shown) ~ span:not(.error),
.subfield input:not(:placeholder-shown) ~ span:not(.error),
.field select:not(:placeholder-shown) ~ span:not(.error),
.subfield select:not(:placeholder-shown) ~ span:not(.error) {
visibility: hidden;
}
.field input ~ span:not(.error),
.subfield input ~ span:not(.error),
.field select ~ span:not(.error),
.subfield select ~ span:not(.error) {
opacity: .7;
}
.field input:focus ~ span,
.subfield input:focus ~ span,
.field select:focus ~ span,
.subfield select:focus ~ span {
visibility: hidden;
}
.subfield .input {
margin-bottom: -15px;
}
.subfield .error {
float: right;
margin-right: 30px;
}
.subfield .input textarea {
margin-bottom: 16px;
}
.slapos-parameter-dict-key {
cursor: pointer;
font-style: italic;
color: #000;
}
label.slapos-parameter-dict-key {
text-transform: capitalize;
}
label.slapos-parameter-dict-key ~ div.input label {
color: rgb(112, 125, 136);
}
.slapos-parameter-dict-key label {
color: rgb(112, 125, 136);
}
/* add button */
.slapos-parameter-dict-key ~ .subfield .input {
position: relative;
margin-bottom: 0;
}
.input .add-sub-form {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
text-decoration: none;
cursor: pointer;
color: #000;
margin: 0;
}
.add-sub-form:before {
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='50px' height='50px'><path fill='#000' d='m6,17.16589l12.55862,0l0,-15.274l12.88276,0l0,15.274l12.55862,0l0,15.66822l-12.55862,0l0,15.274l-12.88276,0l0,-15.274l-12.55862,0l0,-15.66822z' fill-opacity='0.5'/></svg>");
background-size: contain;
content: "";
width: 16px;
height: 16px;
position: absolute;
background-repeat: no-repeat;
top: 8px;
left: 4px;
}
.subfield {
padding-top: 3px;
min-height: 5em;
}
.subfield select {
margin-bottom: 0;
}
.subfield .input select + span {
position: relative;
top: -1.9em;
opacity: .7;
}
select.readonly {
background: #EEE;
pointer-events: none;
touch-action: none;
}
div.slap_json_form {
display: block;
margin-inline-start: 2px;
margin-inline-end: 2px;
padding-block-start: 0.35em;
padding-inline-start: 0.75em;
padding-inline-end: 0.75em;
padding-block-end: 0.625em;
min-inline-size: min-content;
border-width: 2px;
border-style: groove;
border-color: threedface;
border-image: initial;
}
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Style" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>creators</string> </key>
<value>
<tuple>
<string>zope</string>
</tuple>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_slap_json_form.css</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>rjs_gadget_erp5_page_slap_json_form_css</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>modification_date</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1523884648.28</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Style</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Gadget Slapos JSON Form Style</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>document_publication_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>edit_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>processing_status_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAU=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1682015684.98</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>published_alive</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1007.63576.46581.3566</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1682455625.16</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="5" aka="AAAAAAAAAAU=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>detect_converted_file</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_processing_state</string> </key>
<value> <string>converted</string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>0.0.0.0</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1682015393.14</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html manifest="gadget_erp5.appcache">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ERP5</title>
<link rel="shortcut icon" href="favicon.ico">
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<script src="domsugar.js" type="text/javascript"></script>
<script src="gadget_erp5_page_slap_json_form.js" type="text/javascript"></script>
<link href="gadget_erp5_page_slap_json_form.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div class="json_form"></div>
<div class="json_form_load_schema"
data-gadget-url="gadget_erp5_page_slap_load_schema.html"
data-gadget-scope="json_form_load_schema" >
</div>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_slap_json_form.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Parameter</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>rjs_gadget_erp5_page_slap_json_form_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Gadget for slap json form</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>document_publication_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>edit_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>processing_status_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAU=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1681412170.2</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>published_alive</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1007.63578.16504.55022</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1682453471.82</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="5" aka="AAAAAAAAAAU=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>detect_converted_file</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_processing_state</string> </key>
<value> <string>converted</string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>0.0.0.0</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1681412127.46</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*jslint nomen: true, maxlen: 200, indent: 2, unparam: true*/
/*global window, rJS, domsugar, Boolean, btoa, atob */
(function (window, rJS, domsugar, Boolean, btoa, atob) {
"use strict";
function render_selection(json_field, default_value, is_required, editable) {
var input,
option_list = [domsugar('option', {
value: "",
selected: (default_value === undefined)
})],
option_index,
selected,
is_selected = (default_value === undefined),
data_format = "string",
property_dict = {
size: 1,
"placeholder": " ",
"class": "slapos-parameter"
};
if (json_field.type === "integer" || json_field.type === "number") {
data_format = "number";
} else if (json_field.type === "boolean") {
data_format = "boolean";
}
if (default_value === undefined) {
default_value = "";
}
for (option_index in json_field['enum']) {
if (json_field['enum'].hasOwnProperty(option_index)) {
selected = (json_field['enum'][option_index].toString() === default_value.toString());
is_selected = (is_selected || selected);
option_list.push(domsugar('option', {
value: json_field['enum'][option_index],
text: json_field['enum'][option_index],
"data-format": data_format,
selected: selected
}));
}
}
if (!is_selected) {
// The default value should be included even if it is
// outside the enum.
option_list.push(domsugar('option', {
value: default_value,
text: default_value,
"data-format": data_format,
selected: true
}));
}
property_dict["data-format"] = data_format;
if (is_required) {
property_dict.required = true;
}
input = domsugar('select', property_dict, option_list);
if (!editable) {
input.classList.add("readonly");
input["aria-disabled"] = "true";
input["tab-index"] = "-1";
}
return input;
}
function render_selection_oneof(json_field, default_value, is_required, editable) {
var input,
option_list = [domsugar('option', {
value: "",
selected: (default_value === undefined)
})],
property_dict = {
size: 1,
"placeholder": " ",
"class": "slapos-parameter"
};
json_field.oneOf.forEach(function (element) {
if ((element['const'] !== undefined) && (element.title !== undefined)) {
var value;
if ((json_field.type === 'array') || (json_field.type === 'object')) {
// Support for unusual types
value = JSON.stringify(element['const']);
} else {
value = element['const'];
}
option_list.push(domsugar('option', {
value: value,
text: element.title,
selected: (value === default_value)
}));
}
});
if (is_required) {
property_dict.required = true;
}
input = domsugar('select', property_dict, option_list);
if (!editable) {
input.classList.add("readonly");
input["aria-disabled"] = "true";
input["tab-index"] = "-1";
}
return input;
}
function render_textarea(json_field, default_value, data_format, is_required, editable) {
var input, property_dict = {
"data-format": data_format,
"placeholder": " ",
"class": "slapos-parameter"
};
if (default_value !== undefined) {
if (default_value instanceof Array) {
property_dict.value = default_value.join("\n");
} else {
property_dict.value = default_value;
}
}
if (is_required) {
property_dict.required = true;
}
input = domsugar('textarea', property_dict);
if (!editable) {
input.setAttribute('readonly', true);
}
return input;
}
function render_field(json_field, default_value, is_required, editable) {
var input,
data_format,
domsugar_input_dict = {"placeholder": " ", "class": "slapos-parameter"};
if (json_field['enum'] !== undefined) {
return render_selection(json_field, default_value, is_required, editable);
}
if (json_field.oneOf !== undefined) {
return render_selection_oneof(json_field, default_value, is_required, editable);
}
if (json_field.type === "boolean") {
json_field['enum'] = [true, false];
if (default_value === "true") {
default_value = true;
}
if (default_value === "false") {
default_value = false;
}
return render_selection(json_field, default_value, is_required, editable);
}
if (json_field.type === "array") {
data_format = json_field.type;
if (json_field.items !== undefined) {
if (json_field.items.type === "number" || json_field.items.type === "integer") {
data_format = "array-number";
}
}
return render_textarea(json_field, default_value, data_format, is_required, editable);
}
if (json_field.type === "string" && json_field.textarea === true) {
return render_textarea(json_field, default_value, "string", is_required, editable);
}
if (default_value !== undefined) {
domsugar_input_dict.value = default_value;
}
if (json_field.type === "integer") {
domsugar_input_dict.type = "number";
} else if (json_field.type === "number") {
domsugar_input_dict.type = "number";
domsugar_input_dict.step = "any";
} else if (json_field.type === "hidden") {
domsugar_input_dict.type = "hidden";
} else {
domsugar_input_dict.type = "text";
}
if (is_required) {
domsugar_input_dict.required = true;
}
input = domsugar('input', domsugar_input_dict);
if (!editable) {
input.setAttribute('readonly', true);
}
return input;
}
function render_subform(json_field, default_dict, root, path, editable) {
var div_input,
key,
div,
label,
input,
default_value,
default_used_list = [],
is_required;
if (default_dict === undefined) {
default_dict = {};
}
if (path === undefined) {
path = "/";
}
if (json_field.patternProperties !== undefined) {
if (json_field.patternProperties['.*'] !== undefined) {
div = domsugar("div", {
"class": "subfield",
title: json_field.description
});
if (editable) {
div_input = domsugar("div", {
"class": "input"
}, [
domsugar('input', {
type: "text",
// Name is only meaningfull to automate tests
name: "ADD" + path
}),
domsugar('button', {
value: btoa(JSON.stringify(json_field.patternProperties['.*'])),
"class": "add-sub-form",
type: "button",
name: path,
text: "+"
})
]);
div.appendChild(div_input);
}
for (default_value in default_dict) {
if (default_dict.hasOwnProperty(default_value)) {
if (editable) {
label = domsugar('label', {
text: default_value,
'class': "slapos-parameter-dict-key slapos-parameter-dict-key-colapse"
}, [
domsugar('span', {
text: "×",
"class": "bt_close CLOSE" + path + "/" + default_value,
title: "Remove this parameter section."
})
]);
} else {
label = domsugar('label', {
text: default_value,
'class': "slapos-parameter-dict-key slapos-parameter-dict-key-colapse"
});
}
div.appendChild(render_subform(
json_field.patternProperties['.*'],
default_dict[default_value],
domsugar("div", {
"class": "slapos-parameter-dict-key"
}, [label]),
path + "/" + default_value,
editable
));
}
}
root.appendChild(div);
return div;
}
}
// Expand by force the allOf recomposing the properties and required.
for (key in json_field.allOf) {
if (json_field.allOf.hasOwnProperty(key)) {
if (json_field.properties === undefined) {
json_field.properties = json_field.allOf[key].properties;
} else if (json_field.allOf[key].properties !== undefined) {
json_field.properties = Object.assign({},
json_field.properties,
json_field.allOf[key].properties
);
}
if (json_field.required === undefined) {
json_field.required = json_field.allOf[key].required;
} else if (json_field.allOf[key].required !== undefined) {
json_field.required.push.apply(
json_field.required,
json_field.allOf[key].required
);
}
}
}
for (key in json_field.properties) {
if (json_field.properties.hasOwnProperty(key)) {
if (editable || default_dict[key] !== undefined) {
label = domsugar("label", {
'text': json_field.properties[key].title
});
is_required = false;
if ((Array.isArray(json_field.required)) && (json_field.required.includes(key))) {
is_required = true;
}
if (json_field.properties[key].type === 'object') {
label.setAttribute("class", "slapos-parameter-dict-key slapos-parameter-dict-key-collapse");
div_input = render_subform(json_field.properties[key],
default_dict[key],
domsugar("div", {"class": "input"}),
path + "/" + key,
editable);
} else {
input = render_field(
json_field.properties[key],
default_dict[key],
is_required,
editable
);
input.name = path + "/" + key;
div_input = domsugar("div", {"class": "input"}, [input]);
}
default_used_list.push(key);
if (json_field.properties[key]['default'] !== undefined) {
div_input.appendChild(
domsugar("span",
{'text': '(default = ' + json_field.properties[key]['default'] + ')'})
);
}
div_input.appendChild(domsugar("span", {'class': 'error'}));
root.appendChild(
domsugar("div", {
"class": "subfield",
title: json_field.properties[key].description
}, [label, div_input])
);
}
}
}
for (key in default_dict) {
if (default_dict.hasOwnProperty(key)) {
if (default_used_list.indexOf(key) < 0) {
if (typeof default_dict[key] === 'object') {
div = domsugar("div", {title: key}, [
domsugar("label", {
text: key,
"class": "slapos-parameter-dict-key slapos-parameter-dict-key-colapse"
}),
render_subform({},
default_dict[key],
domsugar("div", {"class": "input"}),
path + "/" + key,
editable)
]);
} else {
input = render_field({"type": "string", "textarea": true}, default_dict[key], false, editable);
input.name = path + "/" + key;
div = domsugar("div", {
title: key,
"class": "subfield"
}, [
domsugar("label", {text: key}),
domsugar("div", {"class": "input"}, [
input,
domsugar("span", {text: '(Not part of the schema)'}),
domsugar("span", {'class': 'error'})
])
]);
}
default_used_list.push(key);
root.appendChild(div);
}
}
}
return root;
}
function getFormValuesAsJSONDict(element) {
var json_dict = {},
entry,
entry_list,
multi_level_dict = {};
element.querySelectorAll(".slapos-parameter").forEach(function (input, index) {
var index_e, data_format = input.getAttribute("data-format");
if (input.value !== "") {
if (input.type === 'number') {
json_dict[input.name] = parseFloat(input.value);
} else if (input.tagName === "TEXTAREA") {
if (data_format === "string") {
json_dict[input.name] = input.value;
} else if (data_format === "array") {
json_dict[input.name] = input.value.split('\n');
} else if (data_format === "array-number") {
json_dict[input.name] = [];
entry_list = input.value.split("\n");
for (index_e in entry_list) {
if (entry_list.hasOwnProperty(index_e)) {
if (isNaN(parseFloat(entry_list[index_e]))) {
json_dict[input.name].push(entry_list[index_e]);
} else {
json_dict[input.name].push(parseFloat(entry_list[index_e]));
}
}
}
} else {
json_dict[input.name] = input.value.split('\n');
}
} else if (input.tagName === "SELECT") {
if (data_format === "number" || data_format === "integer") {
// Integer must use parseFloat, otherwise the value is rounded
// loosing user's input.
if (isNaN(parseFloat(input.value))) {
json_dict[input.name] = input.value;
} else {
json_dict[input.name] = parseFloat(input.value);
}
} else if (input.getAttribute("data-format") === "boolean") {
if (input.value === "true") {
json_dict[input.name] = Boolean(input.value);
} else if (input.value === "false") {
json_dict[input.name] = false;
} else {
json_dict[input.name] = input.value;
}
} else {
json_dict[input.name] = input.value;
}
} else {
json_dict[input.name] = input.value;
}
}
});
function convertOnMultiLevel(key, value, d) {
var i,
kk,
key_list = key.split("/");
for (i = 2; i < key_list.length; i += 1) {
kk = key_list[i];
if (i === key_list.length - 1) {
d[kk] = value;
} else {
if (!d.hasOwnProperty(kk)) {
d[kk] = {};
}
d = d[kk];
}
}
}
for (entry in json_dict) {
if (json_dict.hasOwnProperty(entry)) {
convertOnMultiLevel(entry, json_dict[entry], multi_level_dict);
}
}
return multi_level_dict;
}
function collapseParameter(element) {
element.parentNode.querySelectorAll("div.subfield").forEach(function (div, i) {
div.classList.toggle("display-none");
});
element.classList.toggle("slapos-parameter-dict-key-colapse");
return element;
}
function removeSubParameter(element) {
element.parentNode.parentNode.remove();
return false;
}
function addSubForm(gadget, element) {
var subform_json = JSON.parse(atob(element.value)),
input_text = element.parentNode.querySelector("input[type='text']"),
div;
if (input_text.value === "") {
return false;
}
div = domsugar('div', {
'class': "slapos-parameter-dict-key"
}, [domsugar('label', {
'class': "slapos-parameter-dict-key",
text: input_text.value
})]);
div = render_subform(subform_json, {}, div, element.name + "/" + input_text.value, gadget.state.editable);
element.parentNode.parentNode.insertBefore(div, element.parentNode.parentNode.children[1]);
return div;
}
/////////////////////////////////////////////////////
// Gadget methods
/////////////////////////////////////////////////////
rJS(window)
.declareMethod("validateJSON", function (schema_url, generated_json) {
return this.getDeclaredGadget('json_form_load_schema')
.push(function (gadget) {
if (schema_url === undefined) {
// Skip validation if no schema is provided.
return {errors: []};
}
return gadget.validateJSON(undefined, schema_url, generated_json);
});
})
.declareMethod('render', function (options) {
return this.changeState({
schema_url: options.schema_url,
json_field: options.json_field,
default_dict: options.default_dict,
editable: options.editable,
// Force refresh in any case
render_timestamp: new Date().getTime()
});
})
.onStateChange(function () {
var gadget = this,
i,
div = render_subform(
gadget.state.json_field,
gadget.state.default_dict,
domsugar('div'),
undefined,
gadget.state.editable
),
div_list = div.querySelectorAll('.slapos-parameter-dict-key > div');
for (i = 0; i < div_list.length; i = i + 1) {
// This should be replaced by a proper class hidden-div
div_list[i].classList.add('display-none');
}
if (div.hasChildNodes()) {
div.classList.add("slap_json_form");
}
return domsugar(gadget.element, {}, [div]);
})
.onEvent("change", function (evt) {
var gadget = this;
// @ts-ignore
if (evt.target.className.indexOf("slapos-parameter") !== -1) {
// getContent is protected by a mutex which prevent
// onchangestate to be called in parallel
return gadget.getContent();
}
}, false, false)
.onEvent("click", function (evt) {
// Only handle click on BUTTON element
var gadget = this,
queue,
// @ts-ignore
tag_name = evt.target.tagName;
if ((tag_name === 'LABEL') &&
// @ts-ignore
(evt.target.className.indexOf("slapos-parameter-dict-key") !== -1)) {
return collapseParameter(evt.target);
}
if ((tag_name === 'SPAN') &&
// @ts-ignore
(evt.target.className.indexOf("bt_close") !== -1)) {
return removeSubParameter(evt.target);
}
if (tag_name === 'BUTTON') {
// Disable any button. It must be managed by this gadget
evt.preventDefault();
}
// Always get content to ensure the possible displayed form
// is checked and content propagated to the gadget state value
queue = gadget.getContent();
if ((tag_name === 'BUTTON') &&
// @ts-ignore
(evt.target.className.indexOf("add-sub-form") !== -1)) {
return queue
.push(function () {
return addSubForm(gadget, evt.target);
});
}
}, false, false)
.declareMethod('getContent', function () {
var gadget = this,
json_dict = getFormValuesAsJSONDict(gadget.element);
return gadget.validateJSON(gadget.state.schema_url, json_dict)
.push(function (validation) {
var error_index,
field_name,
div,
input_field,
error_dict;
gadget.element.querySelectorAll("span.error").forEach(function (span, i) {
span.textContent = "";
});
gadget.element.querySelectorAll("div.error-input").forEach(function (div, i) {
div.setAttribute("class", "");
});
// Update fields if errors exist
for (error_index in validation.errors) {
if (validation.errors.hasOwnProperty(error_index)) {
error_dict = validation.errors[error_index];
// error_dict = { error : "", instanceLocation: "#", keyword: "", keywordLocation: "" }
field_name = error_dict.instanceLocation.slice(1);
if (field_name !== "") {
input_field = gadget.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
if (input_field === null) {
field_name = field_name.split("/").slice(0, -1).join("/");
input_field = gadget.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
}
if (input_field !== null) {
div = input_field.parentNode;
div.setAttribute("class", "slapos-parameter error-input");
div.querySelector("span.error").textContent = validation.errors[error_index].error;
}
} else if (error_dict.keyword === "required") {
// Specific use case for required
field_name = "/" + error_dict.key;
input_field = gadget.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
if (input_field === null) {
field_name = field_name.split("/").slice(0, -1).join("/");
input_field = gadget.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
}
if (input_field !== null) {
div = input_field.parentNode;
div.setAttribute("class", "slapos-parameter error-input");
div.querySelector("span.error").textContent = error_dict.error;
}
}
}
}
return json_dict;
});
}, {mutex: 'statechange'});
}(window, rJS, domsugar, Boolean, btoa, atob));
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Copy_or_Move_Permission</string> </key>
<value>
<list>
<string>Manager</string>
<string>Authenticated</string>
<string>Developer</string>
<string>Owner</string>
</list>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>creators</string> </key>
<value>
<tuple>
<string>zope</string>
</tuple>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_slap_json_form.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>rjs_gadget_erp5_page_slap_json_form_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>modification_date</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1523884648.42</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>gadget_erp5_page_slap_json_form.js</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>document_publication_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>edit_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>processing_status_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAU=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1682025348.84</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>published_alive</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1008.808.61596.8721</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1682619529.51</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="5" aka="AAAAAAAAAAU=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>detect_converted_file</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_processing_state</string> </key>
<value> <string>converted</string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>0.0.0.0</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1681412419.45</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -4,46 +4,8 @@
padding-top: 10px;
}
.subfield span:not(.slapos-parameter) {
font-weight: normal;
font-style: italic;
padding-left: 7px;
color: rgb(94, 127, 141)
}
.subfield select { margin-bottom: 10px;}
.subfield textarea {width: 250px; height: 60px;}
.subfield .error {
color: #E82525;
font-weight: 700;
}
.input button {margin-left: 10px;}
.bt_close, .subfield .slapos-parameter-dict-key span.bt_close{
padding: 0 6px;
display: block;
float: right;
text-overflow:clip;
white-space:nowrap;
overflow: hidden;
font-size: 1.5em;
border-radius: 2px;
}
.bt_close:hover {
background: #c9c9c9;
color: #fff;
}
.hs-short-title{
margin-left:6px;
padding-bottom: 10px;
font-size:12px;
font-weight: normal;
display: inline-block;
}
.display-none {
display: none;
}
......@@ -58,134 +20,48 @@
.non-editable > div.input {border: 1px solid rgb(201, 201, 201); padding: 5px; background: white; font-weight: normal;
margin: 5px 0 10px; max-height: 250px; width: 80%;}
.subfield {
padding-left: 20px;
padding-top: 3px;
}
textarea.slapos-parameter {
width: 400px;
height: 100px;
}
label.slapos-parameter-dict-key::before {
content: "\25BC Parameter Entry: ";
}
label.slapos-parameter-dict-key-colapse::before {
content: "\25BA Parameter Entry: ";
}
div.slapos-parameter-dict-key {
margin-top: 10px;
margin-left: 6px;
background: rgb(245, 245, 245);
border: 1px solid rgb(230, 230, 230);
padding: 5px;
}
/* cleanup */
fieldset > .subfield > label:not(.slapos-parameter-dict-key) {
color: rgb(112, 125, 136);
}
fieldset > div.subfield {
padding-left: 15px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.field input ~ span,
.subfield input ~ span {
.field input ~ span {
position: relative;
top: -27px;
pointer-events: none;
cursor: text;
}
.field input:not(:placeholder-shown) ~ span:not(.error),
.subfield input:not(:placeholder-shown) ~ span:not(.error),
.field select:not(:placeholder-shown) ~ span:not(.error),
.subfield select:not(:placeholder-shown) ~ span:not(.error) {
.field select:not(:placeholder-shown) ~ span:not(.error) {
visibility: hidden;
}
.field input ~ span:not(.error),
.subfield input ~ span:not(.error),
.field select ~ span:not(.error),
.subfield select ~ span:not(.error) {
.field select ~ span:not(.error) {
opacity: .7;
}
.field input:focus ~ span,
.subfield input:focus ~ span,
.field select:focus ~ span,
.subfield select:focus ~ span {
.field select:focus ~ span {
visibility: hidden;
}
.subfield .input {
margin-bottom: -15px;
}
.subfield .error {
float: right;
margin-right: 30px;
}
.subfield .input textarea {
margin-bottom: 16px;
}
.slapos-show-raw-parameter,
.slapos-parameter-dict-key,
.slapos-show-form {
cursor: pointer;
font-style: italic;
color: #000;
}
.slapos-show-form,
.slapos-show-raw-parameter {
margin: 4px auto;
padding-left: 24px; /* add an icon? */
}
label.slapos-parameter-dict-key {
text-transform: capitalize;
}
label.slapos-parameter-dict-key ~ div.input label {
color: rgb(112, 125, 136);
}
.slapos-parameter-dict-key label {
color: rgb(112, 125, 136);
}
/* add button */
.slapos-parameter-dict-key ~ .subfield .input {
position: relative;
margin-bottom: 0;
}
.input .add-sub-form {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
text-decoration: none;
button.slapos-show-raw-parameter,
button.slapos-show-form {
cursor: pointer;
color: #000;
margin: 0;
color: #212529;
padding: 3pt;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em;
display: inline-block;
margin-right: 6pt;
}
.add-sub-form:before {
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='50px' height='50px'><path fill='#000' d='m6,17.16589l12.55862,0l0,-15.274l12.88276,0l0,15.274l12.55862,0l0,15.66822l-12.55862,0l0,15.274l-12.88276,0l0,-15.274l-12.55862,0l0,-15.66822z' fill-opacity='0.5'/></svg>");
background-size: contain;
content: "";
width: 16px;
height: 16px;
position: absolute;
background-repeat: no-repeat;
top: 8px;
left: 4px;
button.slapos-show-raw-parameter:before,
button.slapos-show-form:before {
padding-right: 0.2em;
}
.subfield {
min-height: 5em;
}
.subfield select {
margin-bottom: 0;
}
.subfield .input select + span {
position: relative;
top: -1.9em;
opacity: .7;
button.slapos-show-raw-parameter[disabled],
button.slapos-show-form[disabled] {
color: #999;
}
select.readonly {
......@@ -193,3 +69,7 @@ select.readonly {
pointer-events: none;
touch-action: none;
}
div.slapos-parameter-editor-button {
padding: 10px;
}
\ No newline at end of file
......@@ -71,7 +71,9 @@
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/css</string> </value>
<value>
<none/>
</value>
</item>
<item>
<key> <string>creators</string> </key>
......@@ -271,7 +273,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1004.33923.4937.19370</string> </value>
<value> <string>1007.63569.60065.35225</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -291,7 +293,7 @@
</tuple>
<state>
<tuple>
<float>1669223474.78</float>
<float>1682452927.16</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -28,14 +28,19 @@
<input type=hidden name="serialisation_type" class="slapos-serialisation-type">
</div>
</div>
<div class="field" title="hide_show_button">
<div class="field slapos-parameter-editor-button" title="hide_show_button">
<div class="input">
<button type="button" class="slapos-show-form display-none"> Show Parameter Form </button>
<button type="button" class="slapos-show-raw-parameter"> Show Parameter XML</button>
<button type="button" class="slapos-show-form ui-btn-icon-left ui-icon-spinner"> Parameter Form </button>
<button type="button" class="slapos-show-raw-parameter ui-btn-icon-left ui-icon-code" disabled> Raw XML</button>
</div>
</div>
</fieldset>
<fieldset id="parameter-main"> </fieldset>
<fieldset id="parameter-main">
<div class="parameter_json_form"
data-gadget-url="gadget_erp5_page_slap_json_form.html"
data-gadget-scope="json_form"> </div>
<div class="failover-textarea"> </div>
</fieldset>
<fieldset id="parameter-optional"> </fieldset>
<fieldset id="parameter-xml">
<input type=hidden name="parameter_hash" class="parameter_hash_output">
......
......@@ -240,7 +240,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1004.26625.54107.30429</string> </value>
<value> <string>1007.63568.41363.15786</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -260,7 +260,7 @@
</tuple>
<state>
<tuple>
<float>1669223665.45</float>
<float>1682453638.42</float>
<string>UTC</string>
</tuple>
</state>
......
/*jslint nomen: true, maxlen: 200, indent: 2, unparam: true*/
/*global rJS, console, window, document, RSVP, btoa, atob, XMLSerializer,
/*global rJS, console, window, btoa, atob, XMLSerializer,
DOMParser, URI, vkbeautify, domsugar, Boolean */
(function (window, document, rJS, XMLSerializer, DOMParser, vkbeautify,
(function (window, rJS, XMLSerializer, DOMParser, vkbeautify,
domsugar, Boolean, URI) {
"use strict";
......@@ -72,494 +72,6 @@
);
}
function render_selection(json_field, default_value, is_required, editable) {
var input,
option_list = [domsugar('option', {
value: "",
selected: (default_value === undefined)
})],
option_index,
selected,
is_selected = (default_value === undefined),
data_format = "string",
property_dict = {
size: 1,
"placeholder": " ",
"class": "slapos-parameter"
};
if (json_field.type === "integer" || json_field.type === "number") {
data_format = "number";
} else if (json_field.type === "boolean") {
data_format = "boolean";
}
if (default_value === undefined) {
default_value = "";
}
for (option_index in json_field['enum']) {
if (json_field['enum'].hasOwnProperty(option_index)) {
selected = (json_field['enum'][option_index].toString() === default_value.toString());
is_selected = (is_selected || selected);
option_list.push(domsugar('option', {
value: json_field['enum'][option_index],
text: json_field['enum'][option_index],
"data-format": data_format,
selected: selected
}));
}
}
if (!is_selected) {
// The default value should be included even if it is
// outside the enum.
option_list.push(domsugar('option', {
value: default_value,
text: default_value,
"data-format": data_format,
selected: true
}));
}
property_dict["data-format"] = data_format;
if (is_required) {
property_dict.required = true;
}
input = domsugar('select', property_dict, option_list);
if (!editable) {
input.classList.add("readonly");
input["aria-disabled"] = "true";
input["tab-index"] = "-1";
}
return input;
}
function render_selection_oneof(json_field, default_value, is_required, editable) {
var input,
option_list = [domsugar('option', {
value: "",
selected: (default_value === undefined)
})],
property_dict = {
size: 1,
"placeholder": " ",
"class": "slapos-parameter"
};
json_field.oneOf.forEach(function (element) {
if ((element['const'] !== undefined) && (element.title !== undefined)) {
var value;
if ((json_field.type === 'array') || (json_field.type === 'object')) {
// Support for unusual types
value = JSON.stringify(element['const']);
} else {
value = element['const'];
}
option_list.push(domsugar('option', {
value: value,
text: element.title,
selected: (value === default_value)
}));
}
});
if (is_required) {
property_dict.required = true;
}
input = domsugar('select', property_dict, option_list);
if (!editable) {
input.classList.add("readonly");
input["aria-disabled"] = "true";
input["tab-index"] = "-1";
}
return input;
}
function render_textarea(json_field, default_value, data_format, is_required, editable) {
var input, property_dict = {
"data-format": data_format,
"placeholder": " ",
"class": "slapos-parameter"
};
if (default_value !== undefined) {
if (default_value instanceof Array) {
property_dict.value = default_value.join("\n");
} else {
property_dict.value = default_value;
}
}
if (is_required) {
property_dict.required = true;
}
input = domsugar('textarea', property_dict);
if (!editable) {
input.setAttribute('readonly', true);
}
return input;
}
function render_field(json_field, default_value, is_required, editable) {
var input,
data_format,
domsugar_input_dict = {"placeholder": " ", "class": "slapos-parameter"};
if (json_field['enum'] !== undefined) {
return render_selection(json_field, default_value, is_required, editable);
}
if (json_field.oneOf !== undefined) {
return render_selection_oneof(json_field, default_value, is_required, editable);
}
if (json_field.type === "boolean") {
json_field['enum'] = [true, false];
if (default_value === "true") {
default_value = true;
}
if (default_value === "false") {
default_value = false;
}
return render_selection(json_field, default_value, is_required, editable);
}
if (json_field.type === "array") {
data_format = json_field.type;
if (json_field.items !== undefined) {
if (json_field.items.type === "number" || json_field.items.type === "integer") {
data_format = "array-number";
}
}
return render_textarea(json_field, default_value, data_format, is_required, editable);
}
if (json_field.type === "string" && json_field.textarea === true) {
return render_textarea(json_field, default_value, "string", is_required, editable);
}
if (default_value !== undefined) {
domsugar_input_dict.value = default_value;
}
if (json_field.type === "integer") {
domsugar_input_dict.type = "number";
} else if (json_field.type === "number") {
domsugar_input_dict.type = "number";
domsugar_input_dict.step = "any";
} else if (json_field.type === "hidden") {
domsugar_input_dict.type = "hidden";
} else {
domsugar_input_dict.type = "text";
}
if (is_required) {
domsugar_input_dict.required = true;
}
input = domsugar('input', domsugar_input_dict);
if (!editable) {
input.setAttribute('readonly', true);
}
return input;
}
function render_subform(json_field, default_dict, root, path, editable) {
var div_input,
key,
div,
label,
input,
default_value,
default_used_list = [],
is_required;
if (default_dict === undefined) {
default_dict = {};
}
if (path === undefined) {
path = "/";
}
if (json_field.patternProperties !== undefined) {
if (json_field.patternProperties['.*'] !== undefined) {
div = domsugar("div", {
"class": "subfield",
title: json_field.description
});
if (editable) {
div_input = domsugar("div", {
"class": "input"
}, [
domsugar('input', {
type: "text",
// Name is only meaningfull to automate tests
name: "ADD" + path
}),
domsugar('button', {
value: btoa(JSON.stringify(json_field.patternProperties['.*'])),
"class": "add-sub-form",
type: "button",
name: path,
text: "+"
})
]);
div.appendChild(div_input);
}
for (default_value in default_dict) {
if (default_dict.hasOwnProperty(default_value)) {
if (editable) {
label = domsugar('label', {
text: default_value,
'class': "slapos-parameter-dict-key"
}, [
domsugar('span', {
text: "×",
"class": "bt_close CLOSE" + path + "/" + default_value,
title: "Remove this parameter section."
})
]);
} else {
label = domsugar('label', {
text: default_value,
'class': "slapos-parameter-dict-key"
});
}
div.appendChild(render_subform(
json_field.patternProperties['.*'],
default_dict[default_value],
domsugar("div", {
"class": "slapos-parameter-dict-key"
}, [label]),
path + "/" + default_value,
editable
));
}
}
root.appendChild(div);
return div;
}
}
// Expand by force the allOf recomposing the properties and required.
for (key in json_field.allOf) {
if (json_field.allOf.hasOwnProperty(key)) {
if (json_field.properties === undefined) {
json_field.properties = json_field.allOf[key].properties;
} else if (json_field.allOf[key].properties !== undefined) {
json_field.properties = Object.assign({},
json_field.properties,
json_field.allOf[key].properties
);
}
if (json_field.required === undefined) {
json_field.required = json_field.allOf[key].required;
} else if (json_field.allOf[key].required !== undefined) {
json_field.required.push.apply(
json_field.required,
json_field.allOf[key].required
);
}
}
}
for (key in json_field.properties) {
if (json_field.properties.hasOwnProperty(key)) {
if (editable || default_dict[key] !== undefined) {
label = domsugar("label", {
'text': json_field.properties[key].title
});
is_required = false;
if ((Array.isArray(json_field.required)) && (json_field.required.includes(key))) {
is_required = true;
}
if (json_field.properties[key].type === 'object') {
label.setAttribute("class", "slapos-parameter-dict-key");
div_input = render_subform(json_field.properties[key],
default_dict[key],
domsugar("div", {"class": "input"}),
path + "/" + key,
editable);
} else {
input = render_field(
json_field.properties[key],
default_dict[key],
is_required,
editable
);
input.name = path + "/" + key;
div_input = domsugar("div", {"class": "input"}, [input]);
}
default_used_list.push(key);
if (json_field.properties[key]['default'] !== undefined) {
div_input.appendChild(
domsugar("span",
{'text': '(default = ' + json_field.properties[key]['default'] + ')'})
);
}
div_input.appendChild(domsugar("span", {'class': 'error'}));
root.appendChild(
domsugar("div", {
"class": "subfield",
title: json_field.properties[key].description
}, [label, div_input])
);
}
}
}
for (key in default_dict) {
if (default_dict.hasOwnProperty(key)) {
if (default_used_list.indexOf(key) < 0) {
if (typeof default_dict[key] === 'object') {
div = domsugar("div", {title: key}, [
domsugar("label", {
text: key,
"class": "slapos-parameter-dict-key"
}),
render_subform({},
default_dict[key],
domsugar("div", {"class": "input"}),
path + "/" + key,
editable)
]);
} else {
input = render_field({"type": "string", "textarea": true}, default_dict[key], false, editable);
input.name = path + "/" + key;
div = domsugar("div", {
title: key,
"class": "subfield"
}, [
domsugar("label", {text: key}),
domsugar("div", {"class": "input"}, [
input,
domsugar("span", {text: '(Not part of the schema)'}),
domsugar("span", {'class': 'error'})
])
]);
}
default_used_list.push(key);
root.appendChild(div);
}
}
}
return root;
}
function getFormValuesAsJSONDict(element) {
var json_dict = {},
entry,
entry_list,
multi_level_dict = {};
element.querySelectorAll(".slapos-parameter").forEach(function (input, index) {
var index_e, data_format = input.getAttribute("data-format");
if (input.value !== "") {
if (input.type === 'number') {
json_dict[input.name] = parseFloat(input.value);
} else if (input.tagName === "TEXTAREA") {
if (data_format === "string") {
json_dict[input.name] = input.value;
} else if (data_format === "array") {
json_dict[input.name] = input.value.split('\n');
} else if (data_format === "array-number") {
json_dict[input.name] = [];
entry_list = input.value.split("\n");
for (index_e in entry_list) {
if (entry_list.hasOwnProperty(index_e)) {
if (isNaN(parseFloat(entry_list[index_e]))) {
json_dict[input.name].push(entry_list[index_e]);
} else {
json_dict[input.name].push(parseFloat(entry_list[index_e]));
}
}
}
} else {
json_dict[input.name] = input.value.split('\n');
}
} else if (input.tagName === "SELECT") {
if (data_format === "number" || data_format === "integer") {
// Integer must use parseFloat, otherwise the value is rounded
// loosing user's input.
if (isNaN(parseFloat(input.value))) {
json_dict[input.name] = input.value;
} else {
json_dict[input.name] = parseFloat(input.value);
}
} else if (input.getAttribute("data-format") === "boolean") {
if (input.value === "true") {
json_dict[input.name] = Boolean(input.value);
} else if (input.value === "false") {
json_dict[input.name] = false;
} else {
json_dict[input.name] = input.value;
}
} else {
json_dict[input.name] = input.value;
}
} else {
json_dict[input.name] = input.value;
}
}
});
function convertOnMultiLevel(key, value, d) {
var i,
kk,
key_list = key.split("/");
for (i = 2; i < key_list.length; i += 1) {
kk = key_list[i];
if (i === key_list.length - 1) {
d[kk] = value;
} else {
if (!d.hasOwnProperty(kk)) {
d[kk] = {};
}
d = d[kk];
}
}
}
for (entry in json_dict) {
if (json_dict.hasOwnProperty(entry)) {
convertOnMultiLevel(entry, json_dict[entry], multi_level_dict);
}
}
return multi_level_dict;
}
function collapseParameter(element) {
element.parentNode.querySelectorAll("div.subfield").forEach(function (div, i) {
div.classList.toggle("display-none");
});
element.classList.toggle("slapos-parameter-dict-key-colapse");
return element;
}
function removeSubParameter(element) {
element.parentNode.parentNode.remove();
return false;
}
function addSubForm(gadget, element) {
var subform_json = JSON.parse(atob(element.value)),
input_text = element.parentNode.querySelector("input[type='text']"),
div;
if (input_text.value === "") {
return false;
}
div = domsugar('div', {
'class': "slapos-parameter-dict-key"
}, [domsugar('label', {
'class': "slapos-parameter-dict-key",
text: input_text.value
})]);
div = render_subform(subform_json, {}, div, element.name + "/" + input_text.value, gadget.state.editable);
element.parentNode.parentNode.insertBefore(div, element.parentNode.parentNode.children[1]);
return div;
}
function getSoftwareTypeFromForm(element) {
var input = element.querySelector(".slapos-software-type");
......@@ -578,27 +90,13 @@
return "";
}
function getSchemaUrlFromForm(element) {
var input = element.querySelector(".parameter_schema_url");
if (input !== undefined && input !== null) {
return input.value;
}
return "";
}
function showParameterForm(g) {
var e = g.element.getElementsByTagName('select')[0],
to_hide = g.element.querySelector("button.slapos-show-form"),
to_show = g.element.querySelector("button.slapos-show-raw-parameter");
var e = g.element.getElementsByTagName('select')[0];
if (e === undefined) {
throw new Error("Select not found.");
}
to_hide.classList.add("display-none");
to_show.classList.remove("display-none");
return g.changeState({
display_step: DISPLAY_JSON_FORM,
softwareindex: e.selectedOptions[0]["data-id"],
......@@ -608,12 +106,6 @@
}
function showRawParameter(g) {
var e = g.element.querySelector("button.slapos-show-raw-parameter"),
to_show = g.element.querySelector("button.slapos-show-form");
e.classList.add("display-none");
to_show.classList.remove("display-none");
return g.changeState({
display_step: DISPLAY_RAW_XML,
// Force refresh in any case
......@@ -621,31 +113,11 @@
});
}
function updateParameterForm(g) {
var e = g.element.getElementsByTagName('select')[0],
parameter_shared = g.element.querySelector('input.parameter_shared');
if (e === undefined) {
throw new Error("Select not found.");
}
parameter_shared.value = e.selectedOptions[0]["data-shared"];
return g.changeState({
softwareindex: e.selectedOptions[0]["data-id"],
// Force refresh in any case
render_timestamp: new Date().getTime()
});
}
/////////////////////////////////////////////////////
// check the form validity
/////////////////////////////////////////////////////
function checkValidity(g) {
var json_url = g.state.json_url,
software_type = getSoftwareTypeFromForm(g.element),
json_dict = getFormValuesAsJSONDict(g.element),
schema_url = getSchemaUrlFromForm(g.element),
serialisation_type = getSerialisationTypeFromForm(g.element);
var software_type = getSoftwareTypeFromForm(g.element);
if (software_type === "") {
if (g.state.shared) {
......@@ -654,67 +126,20 @@
throw new Error("The software type is not part of the json (" + software_type + ")");
}
return g.getBaseUrl(json_url)
.push(function (base_url) {
return g.validateJSON(base_url, schema_url, json_dict);
return g.getDeclaredGadget('json_form')
.push(function (gadget) {
return gadget.getContent();
})
.push(function (validation) {
var error_index,
parameter_hash_input = g.element.querySelectorAll('.parameter_hash_output')[0],
field_name,
div,
xml_output,
input_field,
error_dict;
g.element.querySelectorAll("span.error").forEach(function (span, i) {
span.textContent = "";
});
g.element.querySelectorAll("div.error-input").forEach(function (div, i) {
div.setAttribute("class", "");
});
.push(function (json_dict) {
var parameter_hash_input = g.element.querySelectorAll('.parameter_hash_output')[0],
serialisation_type = getSerialisationTypeFromForm(g.element),
xml_output;
if (serialisation_type === "json-in-xml") {
xml_output = jsonDictToParameterJSONInXML(json_dict);
} else {
xml_output = jsonDictToParameterXML(json_dict);
}
parameter_hash_input.value = btoa(xml_output);
// Update fields if errors exist
for (error_index in validation.errors) {
if (validation.errors.hasOwnProperty(error_index)) {
error_dict = validation.errors[error_index];
// error_dict = { error : "", instanceLocation: "#", keyword: "", keywordLocation: "" }
field_name = error_dict.instanceLocation.slice(1);
if (field_name !== "") {
input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
if (input_field === null) {
field_name = field_name.split("/").slice(0, -1).join("/");
input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
}
if (input_field !== null) {
div = input_field.parentNode;
div.setAttribute("class", "slapos-parameter error-input");
div.querySelector("span.error").textContent = validation.errors[error_index].error;
}
} else if (error_dict.keyword === "required") {
// Specific use case for required
field_name = "/" + error_dict.key;
input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
if (input_field === null) {
field_name = field_name.split("/").slice(0, -1).join("/");
input_field = g.element.querySelector(".slapos-parameter[name='/" + field_name + "']");
}
if (input_field !== null) {
div = input_field.parentNode;
div.setAttribute("class", "slapos-parameter error-input");
div.querySelector("span.error").textContent = error_dict.error;
}
}
}
}
return xml_output;
});
}
......@@ -723,22 +148,19 @@
// main render display functions
/////////////////////////////////////////////////////
function renderDisplayRawXml(g, error_text) {
var fieldset,
fieldset_list = g.element.querySelectorAll('fieldset'),
var fieldset = g.element.querySelector('fieldset.parameter-optional'),
failover_div = g.element.querySelector('div.failover-textarea'),
div_error,
textarea,
show_raw_button = g.element.querySelector("button.slapos-show-raw-parameter"),
show_text_button = g.element.querySelector("button.slapos-show-raw-parameter"),
show_form_button = g.element.querySelector("button.slapos-show-form");
if (error_text) {
if (show_raw_button !== null) {
show_raw_button.classList.add("display-none");
}
if (show_form_button !== null) {
show_form_button.classList.remove("display-none");
}
show_text_button.disabled = 1;
show_text_button.classList.remove("ui-icon-code");
show_text_button.classList.add("ui-icon-spinner");
show_form_button.disabled = 0;
if (error_text) {
div_error = domsugar('div', {
'class': 'error'
}, [
......@@ -768,23 +190,27 @@
if (!g.state.editable) {
textarea.setAttribute("readonly", true);
}
fieldset = domsugar('fieldset', [
domsugar('div', {
'class': 'field'
}, [
textarea
]),
// div error
div_error
]);
// Do not hide the Software type, let the user edit it.
fieldset_list[1].parentNode.replaceChild(
fieldset,
fieldset_list[1]
);
fieldset_list[2].innerHTML = '';
return fieldset;
return g.renderSubForm("", {}, {}, true)
.push(function () {
// Do not hide the Software type, let the user edit it.
failover_div = domsugar(failover_div, {}, [
domsugar('div', {
'class': 'field'
}, [
textarea
]),
// div error
div_error
]);
fieldset = domsugar(fieldset);
return failover_div;
})
.push(function (failover_div) {
show_text_button.classList.remove("ui-icon-spinner");
show_text_button.classList.add("ui-icon-code");
return failover_div;
});
}
function renderDisplayJsonForm(gadget) {
......@@ -796,20 +222,18 @@
softwaretype = gadget.state.softwaretype,
softwareindex = gadget.state.softwareindex,
editable = gadget.state.editable,
to_hide = gadget.element.querySelector("button.slapos-show-form"),
to_show = gadget.element.querySelector("button.slapos-show-raw-parameter");
show_form_button = gadget.element.querySelector("button.slapos-show-form"),
show_text_button = gadget.element.querySelector("button.slapos-show-raw-parameter");
show_form_button.disabled = 1;
show_form_button.classList.remove("ui-icon-th-list");
show_form_button.classList.add("ui-icon-spinner");
show_text_button.disabled = 0;
if (json_url === undefined) {
throw new Error("undefined json_url");
}
if (to_hide !== null) {
to_hide.classList.add("display-none");
}
if (to_show !== null) {
to_show.classList.remove("display-none");
}
return gadget.loadSoftwareJSON(json_url)
.push(function (json) {
var option_index,
......@@ -822,7 +246,13 @@
s_input = gadget.element.querySelector('input.slapos-serialisation-type'),
selection_option_list = [],
lowest_index = 999,
lowest_option_index;
lowest_option_index,
parameter_json_schema_url,
parameter_dict = {},
parameter_list,
json_url_uri,
prefix,
parameter_entry;
if (!editable || gadget.state.restricted_softwaretype === true) {
input.classList.add("readonly");
......@@ -951,11 +381,7 @@
// Save current schema on the field
parameter_schema_url.value = json['software-type'][option_selected_index].request;
return parameter_schema_url.value;
})
.push(function (parameter_json_schema_url) {
var parameter_dict = {}, parameter_list, json_url_uri, prefix, parameter_entry;
parameter_json_schema_url = parameter_schema_url.value;
if (parameter_xml !== undefined) {
if (serialisation === "json-in-xml") {
......@@ -985,16 +411,24 @@
'text/xml'
).querySelector("parameter[id='_']");
if (parameter_entry !== null) {
throw new Error("The current parameter values should NOT contains _ parameter (xml).");
}
parseDocumentStringOrFail(
parameter_list = parseDocumentStringOrFail(
parameter_xml,
'text/xml'
).querySelectorAll("parameter")
.forEach(function (element, index) {
parameter_dict[element.id] = element.textContent;
});
).querySelectorAll("parameter");
if (parameter_entry !== null) {
if (parameter_entry.textContent !== "{}") {
throw new Error("The current parameter values should NOT contains _ parameter (xml).");
}
}
parameter_list.forEach(
function (element, index) {
if (!((element.id === "_") && (element.textContent === "{}"))) {
parameter_dict[element.id] = element.textContent;
}
}
);
} else {
throw new Error("Unknown serialisation: " + serialisation);
}
......@@ -1010,34 +444,26 @@
}
return gadget.loadJSONSchema(parameter_json_schema_url, serialisation)
.push(function (json) {
var fieldset_list = gadget.element.querySelectorAll('fieldset'),
fieldset = document.createElement("fieldset");
fieldset = render_subform(json, parameter_dict, fieldset, undefined, editable);
fieldset_list[1].parentNode.replaceChild(
fieldset,
fieldset_list[1]
// Reset failover text area
domsugar(gadget.element.querySelector('div.failover-textarea'));
return gadget.renderSubForm(
parameter_json_schema_url,
json,
parameter_dict,
editable
);
return fieldset_list;
});
})
.push(function () {
var i, div_list = gadget.element.querySelectorAll('.slapos-parameter-dict-key > div'),
label_list = gadget.element.querySelectorAll('label.slapos-parameter-dict-key');
for (i = 0; i < div_list.length; i = i + 1) {
// This should be replaced by a proper class hidden-div
div_list[i].classList.add('display-none');
}
for (i = 0; i < label_list.length; i = i + 1) {
label_list[i].classList.add("slapos-parameter-dict-key-colapse");
}
.push(function (changed) {
show_form_button.classList.remove("ui-icon-spinner");
show_form_button.classList.add("ui-icon-th-list");
return changed;
})
.fail(function (error) {
console.warn(error);
console.log(error.stack);
show_form_button.classList.remove("ui-icon-spinner");
show_form_button.classList.add("ui-icon-th-list");
return renderDisplayRawXml(gadget, error.toString());
});
}
......@@ -1056,23 +482,22 @@
});
})
.declareMethod("validateJSON", function (base_url, schema_url, generated_json) {
.declareMethod("loadSoftwareJSON", function (url) {
return this.getDeclaredGadget('loadschema')
.push(function (gadget) {
return gadget.validateJSON(base_url, schema_url, generated_json);
return gadget.loadSoftwareJSON(url);
});
})
.declareMethod("getBaseUrl", function (url) {
return this.getDeclaredGadget('loadschema')
.declareMethod("renderSubForm", function (parameter_json_schema_url, json_field, default_dict, editable) {
return this.getDeclaredGadget('json_form')
.push(function (gadget) {
return gadget.getBaseUrl(url);
});
})
.declareMethod("loadSoftwareJSON", function (url) {
return this.getDeclaredGadget('loadschema')
.push(function (gadget) {
return gadget.loadSoftwareJSON(url);
return gadget.render({
schema_url: parameter_json_schema_url,
json_field: json_field,
default_dict: default_dict,
editable: editable
});
});
})
......@@ -1099,7 +524,6 @@
software_type_list.push(options.value.parameter.softwaretype);
}
return this.changeState({
// Not used parameters
// hidden: options.hidden,
......@@ -1129,19 +553,21 @@
.onEvent("change", function (evt) {
var gadget = this,
software_type_element = gadget.element.getElementsByTagName('select')[0];
software_type_element = gadget.element.getElementsByTagName('select')[0],
parameter_shared = gadget.element.querySelector('input.parameter_shared');
if (evt.target === software_type_element) {
return updateParameterForm(gadget);
}
// @ts-ignore
if (evt.target.className.indexOf("slapos-parameter") !== -1) {
// getContent is protected by a mutex which prevent
// onchangestate to be called in parallel
return gadget.getContent();
parameter_shared.value = software_type_element.selectedOptions[0]["data-shared"];
// call get content to ensure data is saved.
return gadget.getContent()
.push(function () {
return gadget.changeState({
softwareindex: software_type_element.selectedOptions[0]["data-id"],
// Force refresh in any case
render_timestamp: new Date().getTime()
});
});
}
}, false, false)
.onEvent("click", function (evt) {
......@@ -1151,18 +577,6 @@
// @ts-ignore
tag_name = evt.target.tagName;
if ((tag_name === 'LABEL') &&
// @ts-ignore
(evt.target.className.indexOf("slapos-parameter-dict-key") !== -1)) {
return collapseParameter(evt.target);
}
if ((tag_name === 'SPAN') &&
// @ts-ignore
(evt.target.className.indexOf("bt_close") !== -1)) {
return removeSubParameter(evt.target);
}
if (tag_name === 'BUTTON') {
// Disable any button. It must be managed by this gadget
evt.preventDefault();
......@@ -1189,15 +603,6 @@
return showRawParameter(gadget);
});
}
if ((tag_name === 'BUTTON') &&
// @ts-ignore
(evt.target.className.indexOf("add-sub-form") !== -1)) {
return queue
.push(function () {
return addSubForm(gadget, evt.target);
});
}
}, false, false)
.declareMethod('getContent', function () {
......@@ -1237,5 +642,5 @@
});
}, {mutex: 'statechange'});
}(window, document, rJS, XMLSerializer, DOMParser, vkbeautify,
domsugar, Boolean, URI));
}(window, rJS, XMLSerializer, DOMParser, vkbeautify,
domsugar, Boolean, URI));
......@@ -284,7 +284,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1005.42112.63718.7782</string> </value>
<value> <string>1007.64857.33700.1467</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -304,7 +304,7 @@
</tuple>
<state>
<tuple>
<float>1676052985.24</float>
<float>1682532947.32</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -28,14 +28,19 @@
<input type=hidden name="serialisation_type" class="slapos-serialisation-type">
</div>
</div>
<div class="field" title="hide_show_button">
<div class="field slapos-parameter-editor-button" title="hide_show_button">
<div class="input">
<button type="button" class="slapos-show-form hidden-button"> 展开参数表格 </button>
<button type="button" class="slapos-show-raw-parameter"> 展开参数XML</button>
<button type="button" class="slapos-show-form ui-btn-icon-left ui-icon-spinner"> 展开参数表格 </button>
<button type="button" class="slapos-show-raw-parameterui-btn-icon-left ui-icon-code" disabled> 展开参数XML</button>
</div>
</div>
</fieldset>
<fieldset id="parameter-main"> </fieldset>
<fieldset id="parameter-main">
<div class="parameter_json_form"
data-gadget-url="gadget_erp5_page_slap_json_form.html"
data-gadget-scope="json_form"> </div>
<div class="failover-textarea"> </div>
</fieldset>
<fieldset id="parameter-optional"> </fieldset>
<fieldset id="parameter-xml">
<input type=hidden name="parameter_hash" class="parameter_hash_output">
......
image_module/gadget_slapos_panel_png
image_module/gadget_slapos_invoice_logo_png
image_module/gadget_slapos_panel_png
web_page_module/gadget_erp5_page_slap_cloud_contract_view_html
web_page_module/gadget_erp5_page_slap_cloud_contract_view_js
web_page_module/gadget_erp5_page_slap_request_contract_activation_html
web_page_module/gadget_erp5_page_slap_request_contract_activation_js
web_page_module/gadget_erp5_page_slap_reject_upgrade_decision_html
web_page_module/gadget_erp5_page_slap_reject_upgrade_decision_js
web_page_module/gadget_erp5_page_slap_request_contract_activation_html
web_page_module/gadget_erp5_page_slap_request_contract_activation_js
web_page_module/rjs_gadget_erp5_attention_point_css
web_page_module/rjs_gadget_erp5_attention_point_html
web_page_module/rjs_gadget_erp5_page_slap_language_view_html
web_page_module/rjs_gadget_erp5_page_slap_language_view_js
web_page_module/rjs_gadget_erp5_attention_point_js
web_page_module/rjs_gadget_slapos_annotated_helper_css
web_page_module/rjs_gadget_slapos_annotated_helper_html
web_page_module/rjs_gadget_slapos_annotated_helper_js
web_page_module/rjs_gadget_erp5_page_map_css
web_page_module/rjs_gadget_erp5_page_map_html
web_page_module/rjs_gadget_erp5_page_map_js
web_page_module/rjs_gadget_erp5_page_slap_accept_upgrade_decision_html
web_page_module/rjs_gadget_erp5_page_slap_accept_upgrade_decision_js
web_page_module/rjs_gadget_erp5_page_slap_access_denied_view_html
web_page_module/rjs_gadget_erp5_page_slap_access_denied_view_js
web_page_module/rjs_gadget_erp5_page_slap_add_compute_node_html
web_page_module/rjs_gadget_erp5_page_slap_add_compute_node_js
web_page_module/rjs_gadget_erp5_page_slap_add_instance_tree_html
......@@ -47,14 +44,14 @@ web_page_module/rjs_gadget_erp5_page_slap_compute_node_get_token_html
web_page_module/rjs_gadget_erp5_page_slap_compute_node_get_token_js
web_page_module/rjs_gadget_erp5_page_slap_compute_node_list_html
web_page_module/rjs_gadget_erp5_page_slap_compute_node_list_js
web_page_module/rjs_gadget_erp5_page_slap_computer_network_view_html
web_page_module/rjs_gadget_erp5_page_slap_computer_network_view_js
web_page_module/rjs_gadget_erp5_page_slap_compute_node_request_certificate_html
web_page_module/rjs_gadget_erp5_page_slap_compute_node_request_certificate_js
web_page_module/rjs_gadget_erp5_page_slap_compute_node_revoke_certificate_html
web_page_module/rjs_gadget_erp5_page_slap_compute_node_revoke_certificate_js
web_page_module/rjs_gadget_erp5_page_slap_compute_node_view_html
web_page_module/rjs_gadget_erp5_page_slap_compute_node_view_js
web_page_module/rjs_gadget_erp5_page_slap_computer_network_view_html
web_page_module/rjs_gadget_erp5_page_slap_computer_network_view_js
web_page_module/rjs_gadget_erp5_page_slap_controller_html
web_page_module/rjs_gadget_erp5_page_slap_controller_js
web_page_module/rjs_gadget_erp5_page_slap_delete_network_html
......@@ -75,14 +72,17 @@ web_page_module/rjs_gadget_erp5_page_slap_google_login_view_html
web_page_module/rjs_gadget_erp5_page_slap_google_login_view_js
web_page_module/rjs_gadget_erp5_page_slap_instance_tree_view_html
web_page_module/rjs_gadget_erp5_page_slap_instance_tree_view_js
web_page_module/rjs_gadget_erp5_page_slap_access_denied_view_html
web_page_module/rjs_gadget_erp5_page_slap_access_denied_view_js
web_page_module/rjs_gadget_erp5_page_slap_intent_html
web_page_module/rjs_gadget_erp5_page_slap_intent_js
web_page_module/rjs_gadget_erp5_page_slap_invalidate_login_html
web_page_module/rjs_gadget_erp5_page_slap_invalidate_login_js
web_page_module/rjs_gadget_erp5_page_slap_invoice_list_html
web_page_module/rjs_gadget_erp5_page_slap_invoice_list_js
web_page_module/rjs_gadget_erp5_page_slap_json_form_css
web_page_module/rjs_gadget_erp5_page_slap_json_form_html
web_page_module/rjs_gadget_erp5_page_slap_json_form_js
web_page_module/rjs_gadget_erp5_page_slap_language_view_html
web_page_module/rjs_gadget_erp5_page_slap_language_view_js
web_page_module/rjs_gadget_erp5_page_slap_load_schema_html
web_page_module/rjs_gadget_erp5_page_slap_load_schema_js
web_page_module/rjs_gadget_erp5_page_slap_network_list_html
......@@ -141,6 +141,10 @@ web_page_module/rjs_gadget_erp5_page_slap_start_instance_tree_js
web_page_module/rjs_gadget_erp5_page_slap_stop_instance_tree_html
web_page_module/rjs_gadget_erp5_page_slap_stop_instance_tree_js
web_page_module/rjs_gadget_erp5_page_slap_support_request_view_html
web_page_module/rjs_gadget_erp5_page_slap_test_parameter_form_html
web_page_module/rjs_gadget_erp5_page_slap_test_parameter_form_js
web_page_module/rjs_gadget_erp5_page_slap_test_readonly_parameter_form_html
web_page_module/rjs_gadget_erp5_page_slap_test_readonly_parameter_form_js
web_page_module/rjs_gadget_erp5_page_slap_ticket_list_html
web_page_module/rjs_gadget_erp5_page_slap_ticket_list_js
web_page_module/rjs_gadget_erp5_page_slap_ticket_view_js
......@@ -165,10 +169,12 @@ web_page_module/rjs_gadget_erp5_pt_form_slapos_index_js
web_page_module/rjs_gadget_slapos_alert_listbox_field_css
web_page_module/rjs_gadget_slapos_alert_listbox_field_html
web_page_module/rjs_gadget_slapos_alert_listbox_field_js
web_page_module/rjs_gadget_slapos_annotated_helper_css
web_page_module/rjs_gadget_slapos_annotated_helper_html
web_page_module/rjs_gadget_slapos_annotated_helper_js
web_page_module/rjs_gadget_slapos_appcache
web_page_module/rjs_gadget_slapos_compute_node_map_html
web_page_module/rjs_gadget_slapos_compute_node_map_js
web_page_module/rjs_gadget_slapos_status_css
web_page_module/rjs_gadget_slapos_event_discussion_entry_css
web_page_module/rjs_gadget_slapos_event_discussion_entry_html
web_page_module/rjs_gadget_slapos_event_discussion_entry_js
......@@ -180,9 +186,11 @@ web_page_module/rjs_gadget_slapos_invoice_state_html
web_page_module/rjs_gadget_slapos_invoice_state_js
web_page_module/rjs_gadget_slapos_label_listbox_field_html
web_page_module/rjs_gadget_slapos_label_listbox_field_js
web_page_module/rjs_gadget_slapos_login_page_css
web_page_module/rjs_gadget_slapos_panel_html
web_page_module/rjs_gadget_slapos_panel_js
web_page_module/rjs_gadget_slapos_payment_result_js**
web_page_module/rjs_gadget_slapos_status_css
web_page_module/rjs_gadget_slapos_status_html
web_page_module/rjs_gadget_slapos_status_js
web_page_module/rjs_gadget_slapos_translation_data_js
......@@ -194,11 +202,6 @@ web_page_module/rjs_slapos_load_meta_schema_json
web_page_module/rjs_slapos_load_meta_schema_xml_in_json_json
web_page_module/rjs_slapos_load_meta_schema_xml_json
web_page_module/rjs_slapos_load_software_schema_json
web_page_module/rjs_gadget_slapos_login_page_css
web_page_module/rjs_gadget_erp5_page_slap_test_parameter_form_js
web_page_module/rjs_gadget_erp5_page_slap_test_parameter_form_html
web_page_module/rjs_gadget_erp5_page_slap_test_readonly_parameter_form_html
web_page_module/rjs_gadget_erp5_page_slap_test_readonly_parameter_form_js
web_page_module/rjs_vkbeautify_js
web_page_module/slapos_admin_front_page
web_site_module/hostingjs
......
web_site_module/hateoas
web_site_module/hateoas/**
web_site_module/renderjs_runner
portal_types/ERP5 Form
portal_propery_sheets/SlapOSHateoasSystemPreference
portal_types/ERP5 Form
web_page_module/rjs_gadget_erp5_panel_shortcut_html
web_page_module/rjs_gadget_erp5_panel_shortcut_js
web_page_module/slapos_admin_front_page
\ No newline at end of file
web_page_module/slapos_admin_front_page
web_site_module/hateoas
web_site_module/hateoas/**
web_site_module/renderjs_runner
\ No newline at end of file
......@@ -79,6 +79,9 @@ web_page_module/rjs_gadget_erp5_page_slap_invalidate_login_html
web_page_module/rjs_gadget_erp5_page_slap_invalidate_login_js
web_page_module/rjs_gadget_erp5_page_slap_invoice_list_html
web_page_module/rjs_gadget_erp5_page_slap_invoice_list_js
web_page_module/rjs_gadget_erp5_page_slap_json_form_css
web_page_module/rjs_gadget_erp5_page_slap_json_form_html
web_page_module/rjs_gadget_erp5_page_slap_json_form_js
web_page_module/rjs_gadget_erp5_page_slap_language_view_html
web_page_module/rjs_gadget_erp5_page_slap_language_view_js
web_page_module/rjs_gadget_erp5_page_slap_load_schema_html
......
......@@ -142,17 +142,17 @@
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//button[@class="slapos-show-form"]</td>
<td>//button[contains(@class,"slapos-show-form")]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//button[@class="slapos-show-form"]</td>
<td>//button[contains(@class,"slapos-show-form")]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//button[@class="slapos-show-form"]</td>
<td>//button[contains(@class,"slapos-show-form")]</td>
<td></td>
</tr>
......@@ -228,17 +228,17 @@
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//button[@class="slapos-show-form"]</td>
<td>//button[contains(@class,"slapos-show-form")]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//button[@class="slapos-show-form"]</td>
<td>//button[contains(@class,"slapos-show-form")]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//button[@class="slapos-show-form"]</td>
<td>//button[contains(@class,"slapos-show-form")]</td>
<td></td>
</tr>
......
......@@ -50,7 +50,6 @@
<td>//textarea[@name="text_content" and contains(text(), '&lt;instance/&gt;')]</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>//textarea[@name="text_content"]</td>
......@@ -105,9 +104,29 @@
<td></td>
</tr>
<tr>
<td>assertValue</td>
<td>//textarea[@name="text_content"]</td>
<td>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt;&lt;instance&gt;&lt;parameter id=&quot;simple-string&quot;&gt;1024&lt;/parameter&gt;&lt;/instance&gt;</td>
<td>waitForElementPresent</td>
<td>//textarea[@name="text_content" and contains(text(), '&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//textarea[@name="text_content" and contains(text(), '&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//textarea[@name="text_content" and contains(text(), '&lt;instance&gt;')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//textarea[@name="text_content" and contains(text(), '&lt;parameter id=&quot;simple-string&quot;&gt;1024&lt;/parameter&gt;')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//textarea[@name="text_content" and contains(text(), '&lt;/instance&gt;')]</td>
<td></td>
</tr>
<tal:block metal:use-macro="here/Zuite_SlapOSParameterCommonTemplate/macros/show_form_parameter" />
......@@ -133,9 +152,29 @@
<td></td>
</tr>
<tr>
<td>assertValue</td>
<td>//textarea[@name="text_content"]</td>
<td>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt;&lt;instance&gt;&lt;parameter id=&quot;simple-string&quot;&gt;1024&lt;/parameter&gt;&lt;/instance&gt;</td>
<td>waitForElementPresent</td>
<td>//textarea[@name="text_content" and contains(text(), '&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//textarea[@name="text_content" and contains(text(), '&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//textarea[@name="text_content" and contains(text(), '&lt;instance&gt;')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//textarea[@name="text_content" and contains(text(), '&lt;parameter id=&quot;simple-string&quot;&gt;1024&lt;/parameter&gt;')]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//textarea[@name="text_content" and contains(text(), '&lt;/instance&gt;')]</td>
<td></td>
</tr>
<tr>
<td>pause</td>
......
......@@ -90,7 +90,7 @@
<tr>
<td>fireEvent</td>
<td>//input[@name="//simple-string" and @type="text"]</td>
<td>change</td>
<td>click</td>
</tr>
<tr>
<td colspan="3"><b> Assert Error Messages</b></td>
......
......@@ -83,6 +83,16 @@
<td>//input[@name='url_string']</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//textarea[@name='text_content']</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//textarea[@name='text_content']</td>
<td></td>
</tr>
</tal:block>
<tal:block metal:define-macro="init_readonly">
......@@ -113,6 +123,16 @@
<td>//textarea[@name='parameter_output']</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//textarea[@name='text_content']</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//textarea[@name='text_content']</td>
<td></td>
</tr>
</tal:block>
<tal:block metal:define-macro="click_proceed_and_wait_for_hash">
......@@ -143,19 +163,19 @@
<tr>
<td>waitForElementPresent</td>
<td>//div[contains(@data-gadget-url,
'gadget_erp5_page_slap_parameter_form.html')]//button[@class="slapos-show-raw-parameter"]</td>
'gadget_erp5_page_slap_parameter_form.html')]//button[contains(@class, "slapos-show-raw-parameter")]</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//div[contains(@data-gadget-url,
'gadget_erp5_page_slap_parameter_form.html')]//button[@class="slapos-show-raw-parameter"]</td>
'gadget_erp5_page_slap_parameter_form.html')]//button[contains(@class, "slapos-show-raw-parameter")]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//div[contains(@data-gadget-url,
'gadget_erp5_page_slap_parameter_form.html')]//button[@class="slapos-show-raw-parameter"]</td>
'gadget_erp5_page_slap_parameter_form.html')]//button[contains(@class, "slapos-show-raw-parameter")]</td>
<td></td>
</tr>
<tr>
......@@ -174,19 +194,19 @@
<tr>
<td>waitForElementPresent</td>
<td>//div[contains(@data-gadget-url,
'gadget_erp5_page_slap_parameter_form.html')]//button[@class="slapos-show-form"]</td>
'gadget_erp5_page_slap_parameter_form.html')]//button[contains(@class, "slapos-show-form")]</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//div[contains(@data-gadget-url,
'gadget_erp5_page_slap_parameter_form.html')]//button[@class="slapos-show-form"]</td>
'gadget_erp5_page_slap_parameter_form.html')]//button[contains(@class, "slapos-show-form")]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//div[contains(@data-gadget-url,
'gadget_erp5_page_slap_parameter_form.html')]//button[@class="slapos-show-form"]</td>
'gadget_erp5_page_slap_parameter_form.html')]//button[contains(@class, "slapos-show-form")]</td>
<td></td>
</tr>
</tal:block>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment