diff --git a/doc/PROTOCOL.md b/doc/PROTOCOL.md index 9af56bfb66b60e9aa25446301a2f5b46c7034a85..22cc5649efbd48416a4ff799be85de9db05f5223 100644 --- a/doc/PROTOCOL.md +++ b/doc/PROTOCOL.md @@ -133,6 +133,51 @@ having the following structure radio button `value`-attribute to be activated in `"value"`. +### Form Group Configuration Response + +As part of the server response, the JSON stream may contain a key +`element-update`. This key stores information on how to modify HTML elements identified by `id`. Modifying in this +context refers to: + + * Setting attribute values + * Deleting attributes + * Setting content of a HTML element. + +The content of `element-update` is outlined below + + { + ... + "element-update" : { + "<element_id1>": { + "attr": { + "<attr_name1>": "<value1>" | null, + ... + "<attr_nameN>": "<valueN>" | null + }, + "content": "<element_content>" + }, + ... + "<element_idN>": { + "attr": { + "<attr_name1>": "<value1>" | null, + ... + "<attr_nameN>": "<valueN>" | null + }, + "content": "<element_content>" + } + }, + ... + } + +The presence of `element-update` is optional. `<element_idN>` refers to the element's `id`-attribute value. It used +to uniquely identify the HTML element in the DOM. The properties `"attr"` and `"content"` are both optional. + +Supplying `null` as value for `"<attr_nameN>"` will remove the attribute from the HTLM element identified by +`"<element_idN>"`. + +If the element has no `"<attr_nameN>"` attribute, it will be created. In any case, the attribute's value will be set +to the value specified by `"<valueN>"`. See above for handling of `null` value. + ### Redirection Response Depending on the request, the server may return redirection diff --git a/javascript/src/ElementUpdate.js b/javascript/src/ElementUpdate.js new file mode 100644 index 0000000000000000000000000000000000000000..177199fdef1c4e274e838b597fd560659ea4ca5a --- /dev/null +++ b/javascript/src/ElementUpdate.js @@ -0,0 +1,90 @@ +/** + * @author Rafael Ostertag <rafael.ostertag@math.uzh.ch> + */ + +/* global $ */ +/* global console */ + +/* @depend Utils.js */ + +var QfqNS = QfqNS || {}; + +(function (n) { + 'use strict'; + + /** + * Update HTML elements by a given id. Supports adding, setting, and removing attributes as well setting the + * text enclosed by the element. + * + * @type {{}} + */ + n.ElementUpdate = {}; + + + /** + * Update all elements according to configuration. + * + * @param config JSON configuration + * @public + */ + n.ElementUpdate.updateAll = function (config) { + for (var idName in config) { + if (!config.hasOwnProperty(idName)) { + continue; + } + + n.ElementUpdate.update(idName, config[idName]); + } + }; + + /** + * + * @param elementId id of the element to update + * @param config configuration + */ + n.ElementUpdate.update = function (elementId, config) { + var $element = n.ElementUpdate.$getElementById(elementId); + + if (config.attr) { + n.ElementUpdate.handleAttributeUpdate($element, config.attr); + } + + if (config.content) { + n.ElementUpdate.setElementText($element, config.content); + } + + }; + + n.ElementUpdate.$getElementById = function (id) { + return $("#" + n.escapeJqueryIdSelector(id)); + }; + + n.ElementUpdate.handleAttributeUpdate = function ($element, attributes) { + var attributeValue; + for (var attributeName in attributes) { + if (!attributes.hasOwnProperty(attributeName)) { + continue; + } + + attributeValue = attributes[attributeName]; + + if (attributeValue === null) { + n.ElementUpdate.deleteAttribute($element, attributeName); + } else { + n.ElementUpdate.setAttribute($element, attributeName, attributeValue); + } + } + }; + + n.ElementUpdate.setAttribute = function ($element, attributeName, attributeValue) { + $element.attr(attributeName, attributeValue); + }; + + n.ElementUpdate.deleteAttribute = function ($element, attributeName) { + $element.removeAttr(attributeName); + }; + + n.ElementUpdate.setElementText = function ($element, text) { + $element.empty().append($.parseHTML(text)); + }; +})(QfqNS); diff --git a/javascript/src/QfqForm.js b/javascript/src/QfqForm.js index 408098e266543ae336e8e16c910a5fa2e1539ddb..b54db6a6663753d2eca31f8b0dbb7934606a0d3d 100644 --- a/javascript/src/QfqForm.js +++ b/javascript/src/QfqForm.js @@ -5,6 +5,7 @@ /* global $ */ /* global EventEmitter */ /* @depend QfqEvents.js */ +/* @depend ElementUpdate.js */ /** * Qfq Namespace @@ -78,9 +79,9 @@ var QfqNS = QfqNS || {}; n.Helper.showAjaxError(null, obj.textStatus, obj.errorThrown); }); - var configurationData = this.readElementConfigurationData(); + var configurationData = this.readFormConfigurationData(); - this.applyElementConfiguration(configurationData); + this.applyFormConfiguration(configurationData); // Initialize jqxDateTimeInput elements. n.Helper.jqxDateTimeInput(); @@ -135,7 +136,7 @@ var QfqNS = QfqNS || {}; * * @private */ - n.QfqForm.prototype.readElementConfigurationData = function () { + n.QfqForm.prototype.readFormConfigurationData = function () { var $configuredElements = $("#" + this.formId + " [data-hidden],#" + this.formId + " [data-disabled],#" + this.formId + " [data-required]"); var configurationArray = []; @@ -296,7 +297,8 @@ var QfqNS = QfqNS || {}; } - this.applyElementConfiguration(data['form-update']); + this.applyFormConfiguration(data['form-update']); + this.applyElementConfiguration(data['element-update']); return; } @@ -650,7 +652,8 @@ var QfqNS = QfqNS || {}; // do we have to update the HTML Form? if (data['form-update']) { - this.applyElementConfiguration(data['form-update']); + this.applyFormConfiguration(data['form-update']); + this.applyElementConfiguration(data['element-update']); } if (data.redirect === "url" || data['redirect-url']) { @@ -782,7 +785,7 @@ var QfqNS = QfqNS || {}; * * @param configuration {array} array of objects. */ - n.QfqForm.prototype.applyElementConfiguration = function (configuration) { + n.QfqForm.prototype.applyFormConfiguration = function (configuration) { var arrayLength = configuration.length; for (var i = 0; i < arrayLength; i++) { var configurationItem = configuration[i]; @@ -821,6 +824,14 @@ var QfqNS = QfqNS || {}; } }; + n.QfqForm.prototype.applyElementConfiguration = function (configuration) { + if (!configuration) { + return; + } + + n.ElementUpdate.updateAll(configuration); + }; + /** * @private * @param triggeredBy diff --git a/javascript/src/Utils.js b/javascript/src/Utils.js index 9f253860e313b179adb8507bb1ad88f63f354145..1ff047c02b5af654282dcd39ffeb0fcc4ae93ac7 100644 --- a/javascript/src/Utils.js +++ b/javascript/src/Utils.js @@ -14,6 +14,6 @@ var QfqNS = QfqNS || {}; 'use strict'; n.escapeJqueryIdSelector = function (idSelector) { - return idSelector.replace(/(:)/, "\\$1"); + return idSelector.replace(/(:|\.)/, "\\$1"); }; })(QfqNS); \ No newline at end of file diff --git a/mockup/elementupdate.html b/mockup/elementupdate.html new file mode 100644 index 0000000000000000000000000000000000000000..2e770e4cf8b7657517d52ea3ce3466ef205d3e61 --- /dev/null +++ b/mockup/elementupdate.html @@ -0,0 +1,204 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + + <link rel="stylesheet" href="../css/bootstrap.min.css"> + <link rel="stylesheet" href="../css/bootstrap-theme.min.css"> + <link rel="stylesheet" href="../css/jqx.base.css"> + <link rel="stylesheet" href="../css/jqx.darkblue.css"> + <link rel="stylesheet" href="../css/qfq-bs.css"> + <title>Element Configuration</title> +</head> +<body> + +<label>Refresh URL + <select name="refreshUrl" id="refreshUrl"> + <option>404 error</option> + <option>form_refresh.json</option> + <option>form_refresh_error.json</option> + </select> +</label> + +<div style="float: right"> +<pre id="sample"> + +</pre> + <button id="copy">Copy</button> +</div> +<p> + <textarea name="configuration" rows="20" cols="80" id="configuration"></textarea> +</p> + +<p> + <button id="applyconfig">Apply</button> +</p> + + +<div class="container-fluid"> + <div class="row hidden-xs"> + <div class="col-md-12"> + <h1>Title with a long text</h1> + </div> + </div> + + + <form id="myForm" class="form-horizontal" data-toggle="validator"> + + <div id="formgroup1" class="form-group"> + <div class="col-md-2"> + <label for="text" class="control-label">Text input (name: text)</label> + </div> + + <div class="col-md-6"> + <input id="text" type="text" class="form-control" name="text" data-disabled="true"> + </div> + + </div> + + <div class="form-group"> + <div class="col-md-2"> + <label for="select" class="control-label">Select (name: select)</label> + </div> + <input type="hidden" name="select"> + <div class="col-md-6"> + <select id="select" class="form-control" name="select"> + <option>a</option> + <option>b</option> + <option>c</option> + </select> + </div> + </div> + + <div class="form-group"> + <div class="col-md-2"> + <b class="control-label"> + Radio (name: radio) + </b> + </div> + + <div class="col-md-6"> + <div class="radio"> + <label> + <input type="radio" name="radio" value="a">a + </label> + </div> + <input type="hidden" name="radio"> + <div class="radio"> + <label> + <input type="radio" name="radio" value="b">b + </label> + </div> + </div> + <div class="col-md-4"> + <p class="help-block"></p> + </div> + </div> + + <div class="form-group"> + <div class="col-md-2"> + <b class="control-label"> + Checkbox (name: checkbox) + </b> + </div> + + <div class="col-md-6"> + <input type="hidden" name="checkbox"> + <div class="checkbox"> + <label> + <input type="checkbox" id="checkbox" name="checkbox" data-hidden="no"> + </label> + + <p class="help-block"></p> + </div> + </div> + </div> + + <div class="form-group"> + <div class="col-md-2"> + <b class="control-label"> + Data reload trigger + </b> + </div> + + <div class="col-md-6"> + <div class="checkbox"> + <label> + <input type="checkbox" id="checkbox2" name="trigger" data-load=""> + </label> + + <p class="help-block"> + Changing the value of this checkbox triggers reload + </p> + </div> + </div> + </div> + + <div class="form-group"> + <div class="col-md-2"> + <b class="control-label"> + Checkbox 3 test + </b> + </div> + + + <div class="col-md-6"> + <div class="checkbox"> + <label> + <input name='checkbox3_1' type="checkbox" value="reminder_value"> + </label> + + </div> + <div class="checkbox"> + <label> + <input name='checkbox3_2' type="checkbox" value="reminder_value"> + </label> + + </div> + <div class="checkbox"> + <label> + <input name='checkbox3_3' type="checkbox" value="reminder_value"> + </label> + + </div> + </div> + <div class="col-md-4"> + + </div> + </div> + </form> +</div> + +<script src="../js/jquery.min.js"></script> +<script src="../js/bootstrap.min.js"></script> +<script src="../js/validator.min.js"></script> +<script src="../js/jqx-all.js"></script> +<script src="../js/EventEmitter.min.js"></script> +<script src="../js/qfq.debug.js"></script> +<script type="text/javascript"> + $(function () { + var form = new QfqNS.QfqForm("myForm", 'none', 'none', 'api/' + $("#refreshUrl").val()); + QfqNS.Log.level = 0; + + $("#applyconfig").click(function () { + var configAsText = $("#configuration").val(); + var configAsJson = JSON.parse(configAsText); + + QfqNS.ElementUpdate.updateAll(configAsJson); + }); + + $("#copy").click(function () { + $("#configuration").val($("#sample").text()); + }); + + $("#refreshUrl").on("change", function (evt) { + form.dataRefreshUrl = 'api/' + $(evt.target).val(); + }); + + + }); +</script> +</body> +</html> \ No newline at end of file diff --git a/mockup/elementconfiguration.html b/mockup/formupdate.html similarity index 99% rename from mockup/elementconfiguration.html rename to mockup/formupdate.html index dd517d4a05376484b35c154ef314036add095cf1..4d95209888b626f9ad3737d2756e13dc6fd22c7b 100644 --- a/mockup/elementconfiguration.html +++ b/mockup/formupdate.html @@ -222,7 +222,7 @@ var configAsText = $("#configuration").val(); var configAsJson = JSON.parse(configAsText); - form.applyElementConfiguration(configAsJson); + form.applyFormConfiguration(configAsJson); }); $("#copy").click(function () { diff --git a/mockup/personmock.html b/mockup/personmock.html index 23e7188616b06e1c40c2c877ccc75b3e3bcd91fe..32c7b976b7e27b59c9b63c27936e0870b0bffcd1 100644 --- a/mockup/personmock.html +++ b/mockup/personmock.html @@ -91,7 +91,8 @@ <div class="col-md-2 "> <div class="btn-toolbar pull-right" role="toolbar"> <div class="btn-group" role="group"> - <button id="save-button" type="button" class="btn btn-default navbar-btn"><span + <button id="save-button" type="button" class="btn btn-default navbar-btn" + data-class-on-change="wdc"><span class="glyphicon glyphicon-ok"></span></button> <button id="close-button" type="button" class="btn btn-default navbar-btn"><span class="glyphicon glyphicon-remove"></span></button>