onAction() should accept a parameter as a field value

I have a tableField that shows the name of the person that created the record.
With a click on that field I want to open that person’s record.

So I created a small generic gotoPerson() function that performs this task.

/**
 * @param {JSEvent} event
 * @param {String} sUUID - UUID of a person
 * @public
 * @properties={typeid:24,uuid:"B4D8147E-ACA5-468B-95CD-AD41140567C1"}
 */
function gotoPerson(event, sUUID) {
	
	if (sUUID) {
		
		scopes.nav.showForm('frm_person_general_dtl', sUUID);
	}
}

When I want to hook my onAction() event of the tableField to gotoPerson(), my problem is that my second parameter creation_user_id is assumed to be a string and automatically surrounded by quotes.
But I want to provide the value of this field to the function, but how?
I already tried to put %% around it, but that gets also surrounded by quotes.
(What I want to avoid is to code an explicit function inside the form.js for this simple task, instead I want to call the function directly by using the onAction() property.)

gotoPerson.png

Bernd.N:
(What I want to avoid is to code an explicit function inside the form.js for this simple task, instead I want to call the function directly by using the onAction() property.)

Not sure I understand your reasoning. Why would the following code be too explicit? You already pass all the info you need with the JSEvent.

/**
 * @param {JSEvent} event
 * @public
 * @properties={typeid:24,uuid:"B4D8147E-ACA5-468B-95CD-AD41140567C1"}
 */
function gotoPerson(event) {
	if (forms[event.getFormName()].foundset.creation_user_id) {
		scopes.nav.showForm('frm_person_general_dtl', forms[event.getFormName()].foundset.creation_user_id);
	}
}

Or are you saying this column name might be different per form?
Then you do pass the name of the column as you already are doing:

/**
 * @param {JSEvent} event
 * @param {String} columnName
 * @public
 * @properties={typeid:24,uuid:"B4D8147E-ACA5-468B-95CD-AD41140567C1"}
 */
function gotoPerson(event, columnName) {
	if (forms[event.getFormName()].foundset[columnName]) {
		scopes.nav.showForm('frm_person_general_dtl', forms[event.getFormName()].foundset[columnName]);
	}
}

Hope this helps

Thanks, Robert, that is a solution that will solve my task.
I am still curious if it would be possible to pass a field value directly to a function that I call in a form event, but if not, I can still use this generic solution you provided.

I would rename the function then formGotoPerson(), because it may be called only in the context of a form that holds that field, as it relies on that.

Hi Bernd,

As far as I know you can only pass a literal this way. If you really want to pass objects or merge-codes I suggest you file a feature request.

There is a property where it is already possible to tell Servoy to use the field value instead of a literal, that is the toolTipText property of a field.
I use it often when a table column is potentially too small, and put %%field_name%% into it.
Then the tooltip does not show “field_name” but it shows the full content of that field.
This works with relations too, like %%orders_to_customers.cus_name%%

The same mechanism/notation might be handy for such event parameters.
Maybe if someone from Servoy reads this, he/she might want to comment if a feature request would be promising.

The advantage of such a mechanism is that one spares to code explicit functions in the form.js

Bernd.N:
The advantage of such a mechanism is that one spares to code explicit functions in the form.js

That depends on your skills as a programmer.
Having to bind parameters over and over again to make your code work isn’t the solution: easy to forget, hard to write unit tests for, absence of JSDocs, etc., etc.
The JSEvent is added by default, and the amount of work to write just a bit of code to extract the dataprovider from the JSEvent is next to nothing.

When you need this a lot, the extraction part should be a scoped function itself, so you don’t even have to think about it.
Combine this with some unit-tests and you’ll be faster and safer than ever writing your code.

Good idea, Marc, I added a second function for extraction and it works fine.
getFormFieldValue() is so far without any safeguard that checks the existence of forms[event.getFormName()].
I want to wait and look if that is ever needed.

What kind of unitTests would you write for this two functions?

/**
 * @param {JSEvent} event
 * @param {String} sUUIDField - field name that holds the UUID of a person
 * @public
 * @properties={typeid:24,uuid:"B4D8147E-ACA5-468B-95CD-AD41140567C1"}
 */
function formGotoPerson(event, sUUIDField) {

	var sUUID = getFormFieldValue(event, sUUIDField);

	if (sUUID) {
	   
		scopes.nav.showForm('frm_person_general_dtl', sUUID);
	}
}

/**
 * @param {JSEvent} event
 * @param {String} sFieldname
 * @public
 * @properties={typeid:24,uuid:"1C794292-5E5C-4634-A5BE-D7325E6D1C57"}
 */
function getFormFieldValue(event, sFieldname) {

	return forms[event.getFormName()].foundset[sFieldname];
}

unit tests are specific functions that can test the behaviour of the functions you write.
So in case you change any existing functions, you can easily re-test the behaviour and when all tests pass, you know your solution will not break.

I’m just wondering what the dataProvider is of the field you click on.
I would expect that to be the creationUserID and you show the name using a relation or valuelist.
In this case you should strip down your code even more and use the event.getSource() function to find out the element.
Based on the element you can grab the dataprovider.

You are right, I use a relation, in this case the dataprovider of that table field is weblinks_to_persons.clcs_ps_full_name
(clcs is our prefix notation for calculated stored field, so that we know that it is a stored calculation)

Then I’d do something like this:

/**
 * @param {JSEvent} event
 *
 * @properties={typeid:24,uuid:"13A523BF-EB6B-4366-B56E-AA32AF0ACCDD"}
 */
function getOnActionDataProviderValue(event) {
	if(event) {
		var _value;
		var _sForm = event.getFormName();
		var _source = event.getSource();
		if(_source && _source.getDataProviderID()) {
			var _sDataProvider = _source.getDataProviderID();
			
			if(_sForm) {
				_value = forms[_sForm].foundset[_sDataProvider];
			}
		}
	}
	
	return _value;
}

And then:

/**
* @param {JSEvent} event
* @public
* @properties={typeid:24,uuid:"B4D8147E-ACA5-468B-95CD-AD41140567C1"}
*/
function gotoPerson(event) {
   var _sID = getOnActionDataProviderValue(event);
   if (_sID) {
      scopes.nav.showForm('frm_person_general_dtl', _sID);
   }
}

The getOnActionDataProviderValue function can be used over and over again for similar situations without changing 1 single bit of code.

I tried the code, but I get the dataprovider weblinks_to_persons.clcs_ps_full_name and not the needed value of creation_user_id, as that is not inside the dataprovider of that table field.
But to give the function a hint with a second parameter as done above, is totally fine and no work at all.
Thanks a lot to all contributers for all the tips!

Bernd.N:
I tried the code, but I get the dataprovider weblinks_to_persons.clcs_ps_full_name and not the needed value of creation_user_id, as that is not inside the dataprovider of that table field

Ah yes, overlooked that bit.
You can get around that by using a valuelist of users in order to have the username displayed from the user_id.
That might even have positive effects on performance in tableviews as Servoy doesn’t need to re-evaluate the relation for each record.

However, I don’t know if you allow find/search on this field, but this will probably change that behaviour…

Yes, we do find/search on all fields in a table with a generic quicksearch function, and with a more advanced filtering function when more than one field is involved.

Do valuelists also improve the performance when they are large? One of our customers has 800 users, so I would hesitate to build that valuelist.
I would hope that when Servoy builds the foundset for that table form, it will do so by using a normal SQL Join and not re-evaluate the relation for each record separatly.

When it comes to business partners, there are > 6.000, and I would fear that such large valuelists would need too much server or client memory (though I do not know so far where such valuelists are created and stored, but my guess is inside the server).

Therefore, due to my feeling, I ask my developers to avoid such big valuelists. And inside the UI we use a picklist component when it comes to pick a customer or person.

But anyway, the previous solution with the additional parameter works perfect. :)

Bernd, with regard to the value lists, assuming that they don’t change during the course of a ‘session’, you can call a method ‘onStartup’ that loads the value lists using SQL and then they are always ready & Servoy doesn’t need to dynamically load them every time they appear on a form.
I do this for one of my client solutions and it really improved the performance.

Here are the basic steps

  1. create empty value lists (i.e. set to custom value, but with nothing in it
  2. in your onStartup call a method, like loadValueLists
  3. in function loadValueLists call each method that loads each value list
  4. you can load either single value lists, or ones that have display & return values, like this
function loadPaidUsingValueList ( )
{
	var query = "SELECT paid_using FROM vl_paid_using ORDER BY sort_order ASC";
	var dataset = databaseManager.getDataSetByQuery ( server_name, query, null, 1000 );
	application.setValueListItems ( 'vl_paid_using', dataset );
}

function loadProductsValueList ( )
{
	var query1 = "SELECT item_number + ' ' + description FROM stock WHERE (archived = 0) AND (item_number IS NOT NULL) ORDER BY item_number";
	var dataset1 = databaseManager.getDataSetByQuery ( server_name, query1, null, 10000 );
	var displayDataArray = dataset1.getColumnAsArray ( 1 );
	var query2 = "SELECT stockid FROM stock WHERE (archived = 0) AND (item_number IS NOT NULL) ORDER BY item_number";
	var dataset2 = databaseManager.getDataSetByQuery ( server_name, query2, null, 10000 );
	var realDataArray = dataset2.getColumnAsArray ( 1 );
	application.setValueListItems ( 'ProductsUnarchived', displayDataArray, realDataArray );
}

This also overcomes Servoy’s limitation of number of values…

Rafi

Thanks for the tip!
Is a valuelist really loaded fresh from DB each time when a form shows, or just only once at form.onShow/firstShow ?
When the first is true, I can imagine that it slows down a solution, while the second is totally fine, as then a valuelist is loaded only when it is needed.

Hi

Bernd.N:
Thanks for the tip!
Is a valuelist really loaded fresh from DB each time when a form shows, or just only once at form.onShow/firstShow ?
When the first is true, I can imagine that it slows down a solution, while the second is totally fine, as then a valuelist is loaded only when it is needed.

I can’t say for sure, but if anything might have changed in it, I guess Servoy may reload it.
Might be an idea to check the ‘Performance’ page of Admin clearing, it, then displaying your forms with Vls, then changing form, clearing perf. then checking again and seeing if it might be reloading??
(Otherwise, someone from Servoy might want to say).
I do know that for my client, it improved performance, YMMV.
You could test as well by creating ‘static’ copies of your current value lists as above, and then re-assigning them on your form (but keeping the original ones to assign back to if no speed gain) ;-)

Thanks for the tip!
I tested it, and a second call of a form that has a specific table-related valuelist does not fire an additional SELECT on the DB.
However I noted that we use too much valuelists for large tables, as each of them certainly result in such a SELECT. We will refactor them to picklists that fire only in the moment when the user actually uses them.

I also saw this message in the console, seems to be new in the latest Servoy edition, and this adds to my feeling that valuelists are not the proper tool for large tables:
Valuelist vl_ps_employee_full_name fully loaded with 500 rows, more rows are discarded!!

I also did this performance test with http://localhost:8080/servoy-admin/performance on startup and got again a lot of valueable information where we can improve things.
That tool is really a great help.

Bernd.N:
Thanks for the tip!
I tested it, and a second call of a form that has a specific table-related valuelist does not fire an additional SELECT on the DB.
However I noted that we use too much valuelists for large tables, as each of them certainly result in such a SELECT. We will refactor them to picklists that fire only in the moment when the user actually uses them.

I also did this performance test with http://localhost:8080/servoy-admin/performance on startup and got again a lot of valueable information where we can improve things.
That tool is really a great help.

Happy to help and glad you have found ways to improve things. ;-)