Commit 69ac6b36 authored by Cédric Le Ninivin's avatar Cédric Le Ninivin

erp5_api_style: Updated API interface

* Introduce Separate JSON Schema Form Editor Gadget
* Introduce Separate JSON Schema Text Editor Gadget
* Add djv for simple validation in Text Editor Gadget
* Introduce Switch Editor Gadget to easily switch from form to text
* Always displays response
* Lint code
* Remove manual html manipulation and use domsugar
* Display API related information at the top
parent 68d8923f
This diff is collapsed.
This diff is collapsed.
<!DOCTYPE html>
<html>
<head>
<!--
data-i18n=Workflows
data-i18n=Actions
data-i18n=Clone
data-i18n=Delete
-->
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<title>ERP5 Page Action</title>
<link rel="http://www.renderjs.org/rel/interface" href="interface_page.html">
<!-- renderjs -->
<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="jiodev.js" type="text/javascript"></script>
<!-- custom script -->
<script src="gadget_global.js" type="text/javascript"></script>
<script src="gadget_erp5_global.js" type="text/javascript"></script>
<script src="gadget_erp5_json_schema_form_editor.js" type="text/javascript"></script>
</head>
<body>
<div class="json_form_container"></div>
</body>
</html>
\ No newline at end of file
/*global window, rJS, RSVP, console*/
/*jslint nomen: true, indent: 2, maxerr: 3 */
(function (window, rJS, RSVP) {
"use strict";
function renderJSONFormSchema(gadget) {
var queue,
json_schema_gadget;
if ((!gadget.state.is_refresh)) {
queue = gadget.declareGadget(
rJS.getAbsoluteURL(
"react-jsonschema-form-gadget.html",
gadget.__path
),
{
scope: "form_schema",
element: gadget.element.querySelector('.json_form_container')
}
);
} else {
queue = gadget.getDeclaredGadget("form_schema");
}
return queue
.push(function (result) {
json_schema_gadget = result;
if (gadget.state.is_refresh) {
// Delete the previous form content when refreshing
// to prevent loosing user modification
delete gadget.state.options.form_content;
}
return json_schema_gadget.render({
value: gadget.state.value,
key: gadget.state.key,
schema: gadget.state.schema_url,
readonly: !gadget.state.editable
});
});
}
rJS(window)
/////////////////////////////////////////////////////////////////
// Acquired methods
/////////////////////////////////////////////////////////////////
.declareMethod("render", function (options) {
if (options.editable === undefined) {
options.editable = true;
}
return this.changeState({
schema_url: options.schema_url,
editable: options.editable,
value: options.value || "{}",
key: options.key
});
})
.onStateChange(function () {
var gadget = this;
gadget.properties = {};
return new RSVP.Queue()
.push(function () {
return renderJSONFormSchema(gadget);
});
})
.declareMethod("getContent", function () {
var gadget = this;
if (!gadget.state.editable) {
return {};
}
return gadget.getDeclaredGadget("form_schema")
.push(function (result) {
return result.getContent();
});
}, {mutex: 'changestate'})
.declareMethod("checkValidity", function () {
var gadget = this;
if (gadget.state.editable) {
return gadget.getDeclaredGadget("form_schema")
.push(function (result) {
if (result.checkValidity !== undefined) {
return result.checkValidity();
}
return true;
});
}
return true;
}, {mutex: 'changestate'});
}(window, rJS, RSVP));
div[data-gadget-url$="gadget-erp5-json-schema-switch-editor.html"] button {
color: #212529;
padding: 3pt;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em;
display: inline-block;
margin-right: 6pt;
}
div[data-gadget-url$="gadget-erp5-json-schema-switch-editor.html"] button:disabled,
div[data-gadget-url$="gadget-erp5-json-schema-switch-editor.html"] button[disabled] {
color: #999999;
}
div[data-gadget-url$="gadget-erp5-json-schema-switch-editor.html"] button:before {
padding-right: 0.2em;
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<!--
data-i18n=Added Files
data-i18n=Modified Files
data-i18n=Removed Files
data-i18n=Tree
data-i18n=Diff
data-i18n=Changelog
data-i18n=Expand
data-i18n=Push
-->
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>VCS status gadget</title>
<link rel=stylesheet href="gadget-erp5-json-schema-switch-editor.css">
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<script src="jio.js" type="text/javascript"></script>
<script src="domsugar.js" type="text/javascript"></script>
<script src="gadget-erp5-json-schema-switch-editor.js" type="text/javascript"></script>
</head>
<body>
<div class="switchheader"></div>
<div class="switchbody"></div>
</body>
</html>
/*global window, rJS, RSVP, domsugar,
FormData */
/*jslint nomen: true, maxlen:80, indent:2*/
(function (window, rJS, RSVP, domsugar) {
"use strict";
var DISPLAY_FORM_EDITOR = 'display_form_editor',
DISPLAY_TEXT_EDITOR = 'display_text_editor';
function renderGadgetHeader(gadget, loading) {
var element_list = [],
form_icon = 'ui-icon-th-list',
text_icon = 'ui-icon-code';
if (loading) {
if (gadget.state.display_step === DISPLAY_FORM_EDITOR) {
form_icon = 'ui-icon-spinner';
} else if (gadget.state.display_step === DISPLAY_TEXT_EDITOR) {
text_icon = 'ui-icon-spinner';
} else {
throw new Error("Can't render header state " +
gadget.state.display_step);
}
}
element_list.push(
domsugar('button', {
type: 'button',
text: "Form Editor",
disabled: (gadget.state.display_step === DISPLAY_FORM_EDITOR),
"class": 'display-form-editor-btn ui-btn-icon-left ' + form_icon
}),
domsugar('button', {
type: 'button',
text: "Raw Editor",
disabled: (gadget.state.display_step === DISPLAY_TEXT_EDITOR),
"class": 'display-text-editor-btn ui-btn-icon-left ' + text_icon
})
);
domsugar(gadget.element.querySelector('div.switchheader'), element_list);
}
function renderEditorGadget(gadget, gadget_url, new_editor) {
var element = gadget.element.querySelector('div.switchbody'),
queue = new RSVP.Queue(),
display_schema_gadget;
renderGadgetHeader(gadget, true);
return queue
.push(function () {
if (new_editor) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
return gadget.declareGadget(
rJS.getAbsoluteURL(
gadget_url,
gadget.__path
),
{
scope: "editor_gadget",
element: element
}
);
}
return gadget.getDeclaredGadget("editor_gadget");
})
.push(function (result) {
display_schema_gadget = result;
return display_schema_gadget.render({
key: gadget.state.key,
value: gadget.state.value,
editable: gadget.state.editable,
schema_url: gadget.state.schema_url
});
})
.push(function () {
return renderGadgetHeader(gadget, false);
});
}
function renderFormEditorGadget(gadget, new_editor) {
return renderEditorGadget(
gadget,
"gadget_erp5_json_schema_form_editor.html",
new_editor
);
}
function renderTextEditorGadget(gadget, new_editor) {
return renderEditorGadget(
gadget,
"gadget_erp5_json_schema_text_editor.html",
new_editor
);
}
rJS(window)
.declareAcquiredMethod("getTranslationList", "getTranslationList")
.declareMethod('render', function (options) {
return this.changeState({
display_step: DISPLAY_FORM_EDITOR,
// Only build the bt5 during the first query
key: options.key,
value: options.value || JSON.stringify({}),
schema_url: options.schema_url,
editable: (options.editable === undefined) ? true : options.editable
});
})
.onStateChange(function (modification_dict) {
var gadget = this,
new_editor = false;
if (modification_dict.hasOwnProperty("display_step")) {
new_editor = true;
}
if (gadget.state.display_step === DISPLAY_FORM_EDITOR) {
return renderFormEditorGadget(gadget, new_editor);
}
if (modification_dict.display_step === DISPLAY_TEXT_EDITOR) {
return renderTextEditorGadget(gadget, new_editor);
}
if (modification_dict.hasOwnProperty('display_step')) {
throw new Error('Unhandled display step: ' + gadget.state.display_step);
}
})
.onEvent("click", function (evt) {
// Only handle click on BUTTON and IMG element
var gadget = this,
tag_name = evt.target.tagName,
queue;
if (tag_name !== 'BUTTON') {
return;
}
// 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 (evt.target.className.indexOf("display-form-editor-btn") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_step: DISPLAY_FORM_EDITOR
});
});
}
if (evt.target.className.indexOf("display-text-editor-btn") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_step: DISPLAY_TEXT_EDITOR
});
});
}
throw new Error('Unhandled button: ' + evt.target.textContent);
}, false, false)
//////////////////////////////////////////////////
// Used when submitting the form
//////////////////////////////////////////////////
.declareMethod('getContent', function () {
var gadget = this,
display_step = gadget.state.display_step,
queue;
if (!gadget.state.editable) {
return {};
}
if (gadget.state.display_step === DISPLAY_FORM_EDITOR ||
gadget.state.display_step === DISPLAY_TEXT_EDITOR) {
queue = new RSVP.Queue(gadget.getDeclaredGadget("editor_gadget"));
} else {
throw new Error('getContent form not handled: ' + display_step);
}
return queue
.push(function (editor_gadget) {
return editor_gadget.getContent();
})
.push(function (value) {
var result = {};
gadget.state.value = value[gadget.state.key];
result[gadget.state.key] = gadget.state.value;
return result;
});
}, {mutex: 'changestate'})
.declareMethod('checkValidity', function () {
var gadget = this,
display_step = gadget.state.display_step,
queue;
if (gadget.state.display_step === DISPLAY_FORM_EDITOR ||
gadget.state.display_step === DISPLAY_TEXT_EDITOR) {
queue = new RSVP.Queue(gadget.getDeclaredGadget("editor_gadget"));
} else {
throw new Error('getContent form not handled: ' + display_step);
}
return queue
.push(function (editor_gadget) {
return editor_gadget.checkValidity();
});
}, {mutex: 'changestate'});
}(window, rJS, RSVP, domsugar));
<!DOCTYPE html>
<html>
<head>
<!--
data-i18n=Workflows
data-i18n=Actions
data-i18n=Clone
data-i18n=Delete
-->
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<title>JSON Schema Text Editor</title>
<link rel="http://www.renderjs.org/rel/interface" href="interface_page.html">
<!-- renderjs -->
<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="jiodev.js" type="text/javascript"></script>
<!-- custom script -->
<script src="gadget_global.js" type="text/javascript"></script>
<script src="gadget_erp5_global.js" type="text/javascript"></script>
<script src="djv.js" type="text/javascript"></script>
<script src="gadget_erp5_json_schema_text_editor.js" type="text/javascript"></script>
</head>
<body>
<div class="field_container">
<div class="left">
<section class="ui-content-header-plain"><h3>Schema</h3></section>
<div class="display-schema"></div>
</div><div class="right">
<section class="ui-content-header-plain"><h3>JSON</h3></section>
<div class=" display-json-editor"></div>
</div>
</div>
</body>
</html>
\ No newline at end of file
/*global window, rJS, RSVP, console, jIO, djv*/
/*jslint nomen: true, indent: 2, maxerr: 3 */
(function (window, rJS, RSVP, jIO, Djv) {
"use strict";
function fetchContent(url) {
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
url: url
});
})
.push(function (evt) {
return evt.target.response;
});
}
function renderDisplayCodeMirror(
gadget,
element_klass,
data,
scope,
editable
) {
var element = gadget.element.querySelector(element_klass),
queue = new RSVP.Queue(),
display_schema_gadget;
return queue
.push(function () {
if ((!gadget.state.is_refresh)) {
return gadget.declareGadget(
rJS.getAbsoluteURL(
"gadget_erp5_field_gadget.html",
gadget.__path
),
{
scope: scope,
element: element
}
);
}
return gadget.getDeclaredGadget("display_schema");
})
.push(function (result) {
display_schema_gadget = result;
if (gadget.state.is_refresh) {
// Delete the previous form content when refreshing
// to prevent loosing user modification
delete gadget.state.options.form_content;
}
return display_schema_gadget.render({
field_json: {
key: gadget.state.key,
"default": data,
editable: editable,
url: rJS.getAbsoluteURL(
"codemirror.gadget.html",
gadget.__path
),
sandbox: "iframe",
renderjs_extra: '{"portal_type": "JSON Form"}'
}
});
});
}
function returnSchemaData(gadget) {
if (gadget.properties.schema_data) {
return gadget.properties.schema_data;
}
if (gadget.state.schema_url) {
return fetchContent(gadget.state.schema_url);
}
return "{}";
}
function renderDisplaySchema(gadget) {
var queue = new RSVP.Queue();
return queue
.push(function () {
return returnSchemaData(gadget);
})
.push(function (result) {
gadget.properties.schema_data = result;
return renderDisplayCodeMirror(
gadget,
'.display-schema',
result,
'display_schema_data',
false
);
});
}
function renderDisplayJSONTextEditor(gadget) {
return renderDisplayCodeMirror(
gadget,
'.display-json-editor',
gadget.state.value,
'display_json_editor',
true
);
}
rJS(window)
/////////////////////////////////////////////////////////////////
// declared methods
/////////////////////////////////////////////////////////////////
.declareMethod("render", function (options) {
if (options.editable === undefined) {
options.editable = true;
}
return this.changeState({
schema_url: options.schema_url,
editable: options.editable,
value: options.value || "{}",
key: options.key
});
})
.onStateChange(function () {
var gadget = this,
queue = new RSVP.Queue();
gadget.properties = {};
return queue
.push(function () {
return RSVP.all([
renderDisplaySchema(gadget),
renderDisplayJSONTextEditor(gadget)
]);
});
})
.declareMethod("getContent", function () {
var gadget = this;
if (!gadget.state.editable) {
return {};
}
return gadget.getDeclaredGadget("display_json_editor")
.push(function (result) {
return result.getContent();
});
}, {mutex: 'changestate'})
.declareMethod("checkValidity", function () {
var gadget = this;
if (gadget.state.editable) {
return gadget.getDeclaredGadget("display_json_editor")
.push(function (result) {
return RSVP.all([result.getContent(), returnSchemaData(gadget)]);
})
.push(function (result_list) {
var data = JSON.parse(result_list[0][gadget.state.key]),
schema = JSON.parse(result_list[1]),
djv = new Djv(),
result;
djv.addSchema('schema', {base: schema});
result = djv.validate('schema#/base', data);
console.log(result);
return result === undefined;
});
}
return true;
}, {mutex: 'changestate'});
}(window, rJS, RSVP, jIO, djv));
......@@ -52,7 +52,7 @@
action_json_url,
group_list;
action_json_url = rJS.getAbsoluteURL(
"api/jIOWebSection_getAPIJSONHyperSchema",
"api/",
gadget.__path
)
// Get the whole view as attachment because actions can change based on
......@@ -108,7 +108,8 @@
url_for_kw_list.push({command: 'display_with_history_and_cancel', options: {
page: "json_form_schema_api",
api_url: api_action_list.base + group_list[i][j].href,
schema_url: group_list[i][j].targetSchema
schema_url: group_list[i][j].targetSchema,
api_title: group_list[i][j].title
}});
}
}
......@@ -126,6 +127,12 @@
k = 0,
dom_list = [],
link_list;
dom_list.push(domsugar(null, [
domsugar('section', {class: 'ui-content-header-plain'}, [
domsugar('h3', ["API JSON Schema List:"])
])
]))
for (i = 0; i < group_list.length; i += 3) {
link_list = [];
......
......@@ -242,7 +242,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>994.51406.18049.19046</string> </value>
<value> <string>999.51516.42945.26350</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -260,7 +260,7 @@
</tuple>
<state>
<tuple>
<float>1631641898.4</float>
<float>1650878676.13</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -26,13 +26,12 @@
</head>
<body>
<div class="form_container"></div>
<div class="dialog_button_container"></div>
<div class="data-response ui-screen-hidden">
<br/>
<h2>Response:</h2>
<br/>
<p><code><pre></pre></code></p>
<div class="api_information_container"></div>
<div class="field_container">
<section class="ui-content-header-plain"><h3>Input</h3></section>
</div>
<div class="json_form_container"></div>
<div class="dialog_button_container"></div>
<div class="data-response"></div>
</body>
</html>
\ No newline at end of file
......@@ -109,11 +109,11 @@
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Gadget ERP5 JSON Form Schema API</string> </value>
<value> <string>gadget_erp5_page_json_form_schema_api.html</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
<value> <string>002</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
......@@ -197,7 +197,7 @@
</tuple>
<state>
<tuple>
<float>1631609814.29</float>
<float>1650877475.1</float>
<string>UTC</string>
</tuple>
</state>
......@@ -246,7 +246,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>994.51622.38713.13380</string> </value>
<value> <string>1000.365.46056.54374</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -264,7 +264,7 @@
</tuple>
<state>
<tuple>
<float>1631654842.15</float>
<float>1651653978.77</float>
<string>UTC</string>
</tuple>
</state>
......@@ -325,7 +325,7 @@
</tuple>
<state>
<tuple>
<float>1631609723.62</float>
<float>1650877206.82</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -105,11 +105,11 @@
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Gadget ERP5 JSON Form Schema API JS</string> </value>
<value> <string>gadget_erp5_page_json_form_schema_api.js</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
<value> <string>002</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
......@@ -193,7 +193,7 @@
</tuple>
<state>
<tuple>
<float>1631609809.01</float>
<float>1650877468.13</float>
<string>UTC</string>
</tuple>
</state>
......@@ -242,7 +242,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>994.54206.36690.57463</string> </value>
<value> <string>1000.376.45673.9557</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -260,7 +260,7 @@
</tuple>
<state>
<tuple>
<float>1631809888.34</float>
<float>1651654628.56</float>
<string>UTC</string>
</tuple>
</state>
......@@ -321,7 +321,7 @@
</tuple>
<state>
<tuple>
<float>1631609784.78</float>
<float>1650877206.04</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -2,4 +2,6 @@ web_page_module/api-style_alldocs-response-schema.json
web_page_module/gadget_erp5_page_jio_api_action_html
web_page_module/gadget_erp5_page_jio_api_action_js
web_page_module/gadget_erp5_page_json_form_schema_api_html
web_page_module/gadget_erp5_page_json_form_schema_api_js
\ No newline at end of file
web_page_module/gadget_erp5_page_json_form_schema_api_js
web_page_module/djv_js
web_page_module/gadget_erp5_json_schema_**
\ No newline at end of file
portal_callables/Base_asJSONTextFromJSON
web_page_module/api-style_alldocs-response-schema.json
web_page_module/djv_js
web_page_module/gadget_erp5_json_schema_**
web_page_module/gadget_erp5_page_jio_api_action_html
web_page_module/gadget_erp5_page_jio_api_action_js
web_page_module/gadget_erp5_page_json_form_schema_api_html
......
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