Task List: save button for embedded forms


#1

Hello,

Is it possible to assign a javascript function for the save button that is built in into embedded forms in Camunda Task List?

Thanks in advance,
Deniss


#2

Hi Deniss,

I believe so. I have not use the save function, however I have made use of the details in this [1] reference quite extensively.

regards

Rob

[1] https://docs.camunda.org/manual/7.7/reference/embedded-forms/lifecycle/#event-listeners


#3

@Webcyberrob,

Duh!!! Thanks, Rob! I was looking at it and totally missed it. Thanks again!!!

Kind regards,
Deniss


#4

Hi @Deniss_Makarenkov, @Webcyberrob and other users.

I would like to save the current state of my embedded task form in Tasklist.
I suppose that I must hook into the lifecycle of Camunda SDK JS Form and use the Event Listener which stores locally the values of variables.
My question is which one of these 2 Event Listeners (‘store’ or ‘variables-stored’) must I use for this purpose?
And what exactly must I write inside (as a custom JavaScript) to handle the event?

My script:

<script cam-script type="text/form-script">
				var product = $scope.product = [];																	// Custom JavaScript creates a JavaScript Object and binds it to the current AngularJS $scope of the form as a variable named "product".															                                                        
				$scope.addProduct = function () {																	// We make a function named "addProduct".
					var product = {};																				// We add a new "product" to the Array.
					product.Category = $scope.Category;
					product.Description = $scope.Description;
					if (!!$scope.camForm.fields[0].element[0].files[0]) {											// If the file is uploaded,
						product.Details = $scope.camForm.fields[0].element[0].files[0].name;						// it returns file's "name".
					} else {																						// If the file is not uploaded,
						return;																						// it returns "undefined".
					}
					product.Price = $scope.Price;
					$scope.product.push(product);																	// We use the value of the "product" input field to add a new "product" to the Array.
					$scope.Category = "";																			// We clear the TextBox "Κατηγορία".
					$scope.Description = "";																		// We clear the TextBox "Περιγραφή".
					$scope.Details = "";																			// We clear file's "name".
					$scope.Price = "";																				// We clear the TextBox "Τιμή (€)".
				};
				$scope.removeProduct = function (index) {															// We make a function named "removeProduct". 					
					var category = $scope.product[index].Category;													// We find product's "Category" using "index" from the Array and binds it to the current AngularJS $scope of the form as a variable named "category".
					$scope.product.splice(index, 1);																// We use an "index" to remove a "product" from the Array.
				}
				$scope.isAddFormValid = function () {																// We make a function named "isAddFormValid".
					return ($scope.Category &&
						    $scope.Description &&
							$scope.camForm.fields[0].element[0].files[0] &&
							$scope.Price) ? true : false;															// If all of the 4 input fields of variable "product" are filled in, the "isAddFormValid" function (expression) returns "true", otherwise the function returns "false".
				}
				camForm.on('form-loaded', function() {																// We hook into the lifecycle of Camunda SDK JS Form.
					camForm.variableManager.createVariable ({														// We "create" (declare) a new "process" variable  
						name:'product',																				// named 'product' and
						type:'json',																				// provide as type information 'json' used for serialization.
						value:product
					});
				});
				camForm.on('submit', function(evt) {																// We hook into the lifecycle of Camunda SDK JS Form.
					if (product.length<1) {																			// If no any "product" is added,
						evt.submitPrevented = true;																	// an event handler prevents the form from being submitted by setting the property "submitPrevented" to 'true'.
					}
				});
			</script>

Thanks in advance,
Steve


#5

This might help:


#6

Hi @felix-mueller and other users.

I 've recently added in my script what @falko mentioned in his github example but when I press on Save button, I take the following error message from my server:

image

The server error message is the following:
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)

I don’t know if the error has to do with my script :confused:
I also uploaded my server log file in case anyone could help.

Thank you,
Steve

catalina.2018-06-12.log (32.8 KB)


#7

Hi @steftriant

can you share your complete embedded-form code and also try it once without the file input?

Best
Felix


#8

Hi @felix-mueller

Yes, of course. I also tried it without the file input, the server doesn’t report any internal error but when I press on Save, I see nothing. The only thing that changed has to do with the History Tab, in which I see the following (every time I press on Save):

My complete embedded-form code:

<!DOCTYPE html>
<html lang="en">
	<head>																																
		<meta charset="UTF-8">																											
		<meta name="viewport" content="width=device-width, initial-scale=1.0">															
		<meta http-equiv="X-UA-Compatible" content="ie=edge">																			
		<title>Insert products and specifications</title> 																										
	</head>
	<body>
		<form role="form" name="insertForm" accept-charset="utf-8">
			<script cam-script type="text/form-script">
				var product = $scope.product = [];																														// Custom JavaScript creates a JavaScript Object and binds it to the current AngularJS $scope of the form as a variable named "product".															                                                        
				$scope.addProduct = function () {																														// We make a function named "addProduct".
					var product = {};																																	// We add a new "product" to the Array.
					product.Description = $scope.Description;
					if (!!$scope.camForm.fields[0].element[0].files[0]) {																								// If the file is uploaded,
						product.Details = $scope.camForm.fields[0].element[0].files[0].name;																			// it returns file's "name".
					} else {																																			// If the file is not uploaded,
						return;																																			// it returns "undefined".
					}
					product.Price = $scope.Price;
					$scope.product.push(product);																														// We use the value of the "product" input field to add a new "product" to the Array.
					$scope.Description = "";																															// We clear the TextBox "Περιγραφή".
					$scope.Details = "";																																// We clear the TextBox "Λεπτομέρειες".
					$scope.Price = "";																																	// We clear the TextBox "Τιμή (€)".
				};
				$scope.removeProduct = function (index) {																												// We make a function named "removeProduct". 					
					var category = $scope.product[index].Category;																										// We find product's "Category" using "index" from the Array and binds it to the current AngularJS $scope of the form as a variable named "category".
					$scope.product.splice(index, 1);																													// We use an "index" to remove a "product" from the Array.
				}
				$scope.isAddFormValid = function () {																													// We make a function named "isAddFormValid".
					return ($scope.Description &&
							$scope.camForm.fields[0].element[0].files[0] &&
							$scope.Price) ? true : false;																												// If all of the 3 input fields of variable "product" are filled in, the "isAddFormValid" function (expression) returns "true", otherwise the function returns "false".
				}
				inject(['$http', 'Uri', function($http, Uri) {
					camForm.on('form-loaded', function() {																													// We hook into the lifecycle of Camunda SDK JS Form.
						camForm.variableManager.createVariable ({																										// We "create" (declare) a new "process" variable  
							name:'product',																																// named 'product' and
							type:'json',																																// provide as type information 'json' used for serialization.
							value:product
						});
					});
					camForm.on('store', function(evt) {																													// We hook into the lifecycle of Camunda SDK JS Form.																													
						evt.retrieveVariables()
						var varManager = evt.variableManager;
						var vars = varManager.variables;
						var variableData = {};
						for(var v in vars) {
							if(varManager.isDirty(v)) {
								var val = vars[v].value;
								if(varManager.isJsonVariable(v)) {
									val = JSON.stringify(val);
								}
								variableData[v] = {
									value: val,
									type: vars[v].type,
									valueInfo: vars[v].valueInfo
								};
							}
						}
						var data = { modifications: variableData };
						var config = {
							headers : {
								'Content-Type': 'application/json'
					  		}
						};
						$http.post(Uri.appUri('engine://engine/:engine/task/' + camForm.taskId + '/variables'), data, config);
						evt.storePrevented = true;																														// An event handler may prevent the "store" from being executed by setting the property "storePrevented" to 'true'.
					});
					camForm.on('submit', function(evt) {																													// We hook into the lifecycle of Camunda SDK JS Form.
						if (product.length<1) {																															// If no any "product" is added,
							evt.submitPrevented = true;																													// an event handler prevents the form from being submitted by setting the property "submitPrevented" to 'true'.
						}
					});
				}]);
			</script>
			<h2><b>Λίστα Προϊόντων</b></h2>																																<!-- We set the heading of the HTML Table. -->
			<div>													
				<table style="width:100%;">																								
					<thead>																																				<!-- We group the "header" content in the HTML Table. -->
						<tr>																																			<!-- The "header" content of the HTML Table is not repeated. -->																											
							<th>Περιγραφή</th>
							<th>Λεπτομέρειες</th>
							<th style="width:75px;">Τιμή (€)</th>
							<th></th>
						</tr>
					</thead>
					<tbody ng-repeat="x in product track by $index">																									<!-- The HTML Table is populated from the JSON Array "product", using a "ng-repeat" directive which is assigned to each row of the Table in order to repeat all the objects of the Array. -->
						<tr>																																			<!-- Each row of the HTML Table consists of 3 HTML fields and 1 button. -->													
							<td style="width:100%; padding:0px 0px 0px 0px"><input style="width:100%;" type="text" value="{{x.Description}}" /></td>	
							<td><input type="text" value="{{x.Details}}" /></td>
							<td><input style="width:75px;" type="number" value="{{x.Price}}" /></td>
							<td><input type="button" ng-click="removeProduct($index)" value="Remove" /></td>															<!-- The "ng-click" directive is assigned to the "Remove" button and calls the function named "removeProduct" with the current "$index" when this button is clicked. -->
						</tr>
					</tbody>
				</table>
			</div>
			<hr>																																						<!-- We separate the HTML content of the page. -->
			<div>
				<h2><b>Καταχώρησε νέο προϊόν</b></h2>																													<!-- We set the heading of the HTML Form. -->
				<div class="row">																																		<!-- We set the "1st row" of the HTML Form. -->	
					<div class="col-md-6">																								
						<div class="form-group">
							<label class="control-label" for="description">Περιγραφή</label>
							<div class="controls">
								<input id="description" type="text" 
									   onkeypress="this.style.width = ((this.value.length + 1) * 8) + 'px';" ng-model="Description" />									<!-- When the value of the input field "Περιγραφή" changes, is bound to the created variable "Description" in AngularJS by the "ng-model" directive. -->	
							</div>
						</div>
					</div>
				</div>
				<div class="row">																																		<!-- We set the "2nd row" of the HTML Form. -->
					<div class="col-md-6">
						<div class="form-group">
							<label class="control-label" for="details">Λεπτομέρειες</label>
							<div class="controls">
								<input id="details" type="file"
							   		   cam-variable-name="Details"
							           	   cam-variable-type="File"
							           	   cam-max-filesize="10000000" ng-model="Details" />																				<!-- When the value of the input field "Λεπτομέρειες" changes, is bound to the created variable "Details" in AngularJS by the "ng-model" directive. -->
							</div>
						</div>
					</div>
				</div>
				<div class="row">
					<div class="col-md-6">
						<div class="form-group">
							<label class="control-label" for="price">Τιμή (€)</label>
							<div class="controls">
								<input style="width:75px;" id="price" type="number" min="0" ng-model="Price" />																							<!-- When the value of the input field "Τιμή (€)" changes, is bound to the created variable "Price" in AngularJS by the "ng-model" directive. -->
							</div>
						</div>
					</div>
				</div>
				<div class="row">																																		<!-- We set the "3rd row" of the HTML Form. -->
					<div class="col-md-4">																																<!-- We use "md" for medium screen devices of width equal to or greater than 992px and "4" for adding 4 columns. -->
						<div class="controls">
							<input type="button" ng-click="addProduct()" ng-show="isAddFormValid()" value="Add" />														<!-- The "ng-show" directive shows the input element ("Add" button) only if the "isAddFormValid()" function (expression) returns "true". The "ng-click" directive is assigned to the "Add" button and calls the function named "addProduct()" when this button is clicked. -->
						</div>
					</div>
				</div>
			</div>
		</form>
	</body>
</html>

I tried many changes in my code but with no any success :confused:

You can also find my html file (insert-products-and-specifications) in “My-first-repo” in github repository.

Thank you very much,
Steve


#9

Hi @steftriant
what do you mean by “I see nothing”?
Can you see that the updated variables were persisted to the database?
What do you think should happen when you click on the save button?

Best
Felix


#10

Hi @felix-mueller.

Thanks for your feedback.

What I expect to see (by pressing on Save) is to be able to see again my already added values in case I decide to switch to another tab of Tasklist (or log out to complete the task later) and return back in the Form tab.
When I say “nothing”, I mean that those values aren’t displayed in my Form, when I return back.
In addition, when I press on Save, no any message (of storing) is displayed (instead, I see the characteristic red block sign).

Is it normal? :confused:
Is it right my script?

Best,
Steve


#11

Hi @steftriant
in your form code that you provided, I cannot see that you actually have logic to reload the existing variables from the server. That’s most probably why the values are not reloaded when you change the task or reload the tasklist. To fix this you should check this one: https://docs.camunda.org/manual/7.8/reference/embedded-forms/json-data/#fetching-an-existing-json-variable
Note that some basic AngularJS knowledge is needed for this.

You could inspect the task / process instance variable by using the rest api to validate that your values got saved e.g. using https://docs.camunda.org/manual/7.9/reference/rest/variable-instance/get-query/

Hope this helps.

Best
Felix


#12

Hi @felix-mueller.

I 'm aware of course of this Docs reference (about the fetching of json variables from server) but I have the impression that this is applied only in case you complete your task and continue with the next one in process.
@falko.menge’s github example (which I exactly followed) doesn’t provide something like this :face_with_raised_eyebrow:
As for your suggested variable inspection, I’m going to take it into account because I’m not aware of it.

Thank you,
Steve