[education/rkward] /: Allow addChangeCommand to operate on arrays, and return command parameter, for convenience.

Thomas Friedrichsmeier <[email protected]>
Newsgroups gmane.comp.kde.cvs,gmane.comp.kde.doc
Message-ID <[email protected]>
Git commit d079f909c6a845e1fd8de5b9e01cade4ff20a2c0 by Thomas Friedrichsmeier.
Committed on 08/05/2025 at 18:31.
Pushed by tfry into branch 'master'.

Allow addChangeCommand to operate on arrays, and return command parameter, for convenience.

M  +1    -1    ChangeLog
M  +14   -6    doc/rkwardplugins/index.docbook
M  +2    -5    rkward/plugins/data/level_select.xml
M  +5    -6    rkward/plugins/data/limit_vector_length.xml
M  +4    -8    rkward/plugins/data/one_var_tabulation.xml
M  +2    -4    rkward/plugins/plots/barplot.xml
M  +2    -4    rkward/plugins/plots/dotchart.xml
M  +2    -4    rkward/plugins/plots/pareto.xml
M  +2    -4    rkward/plugins/plots/piechart.xml
M  +15   -12   rkward/scriptbackends/rkcomponentscripting.cpp
M  +1    -1    rkward/scriptbackends/rkcomponentscripting.h
M  +7    -2    rkward/scriptbackends/rkcomponentscripting.js

https://invent.kde.org/education/rkward/-/commit/d079f909c6a845e1fd8de5b9e01cade4ff20a2c0

diff --git a/ChangeLog b/ChangeLog
index 613c02f98..b62a1734d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,5 @@
 --- Version 0.8.2 - UNRELEASED
-- Added: Plugins: Enhance addChangeCommand() to accept callable objects, directly
+- Added: Plugins: Enhance addChangeCommand() to allow writing more readable GUI logic code
 - Added: Plugins: Simplify running R commands inside GUI logic code
 - Fixed: Crash on script errors in plugins
 - Fixed: Printing of captured R messages/warnings in plugins was broken
diff --git a/doc/rkwardplugins/index.docbook b/doc/rkwardplugins/index.docbook
index 7b1fa3041..240577508 100644
--- a/doc/rkwardplugins/index.docbook
+++ b/doc/rkwardplugins/index.docbook
@@ -942,8 +942,16 @@ R code.
[suppressed due to size limit]
 	</para>
 	<note><para>
-		Alternatively, <command>gui.addChangeCommmand()</command> accepts a string to be evaluated on changes, e.g. <replaceable>"my_update_function()"</replaceable>. This can be useful, if you want to call the same function for changes in several different UI elements.
-	</para></note>
+		If the same function should be invoked for changes in several elements, you can also pass an array of the respective <parameter>id=</parameter>s to <command>gui.addChangeCommmand()</command>. Further, for convenience, <command>gui.addChangeCommmand()</command> returns its second parameter, which
+		is useful, if you want to refer to this function elsewhere (e.g. to call it once during initialization). E.g.:
+	</para>
+	<programlisting>
+	let update = gui.addChangeCommand(["mode.string", "y.available"], function() {
+		// do something on each change of mode.string or y.available
+	});
+	update(); // do the same once during intialization
+	</programlisting>
+	</note>
 	<para>
 		The scripted approach to &GUI; logic becomes particularly useful when you want to change the available option according to the type of object that the user has selected. See <link linkend="guilogic_functions">the reference</link> for available functions.
 	</para>
@@ -1660,8 +1668,7 @@ This chapter contains information on some topics that are useful only to certain
 	<para>With this in mind, here is the general pattern. You will use this inside a <link linkend="logic_scripted">scripted UI logic</link> section:</para>
 	<programlisting>
 		&lt;script&gt;&lt;![CDATA[
-				gui.addChangeCommand ("variable", "update ()");
-				update = function () {
+				let update = gui.addChangeCommand ("variable", function () {
 					gui.setValue ("selector.enabled", 0);
 					variable = gui.getValue ("variable");
 					if (variable == "") return;
@@ -1675,7 +1682,7 @@ This chapter contains information on some topics that are useful only to certain
 						// if the command failed e.g.:
 						gui.setListValue ("selector.available", Array ("ERROR:", msg));
 					});
-				}
+				});
 		]]&gt;&lt;/script&gt;
 	</programlisting>
 	<para>Here, <parameter>variable</parameter> is a property holding an object name (&eg; inside a <command>&lt;varslot&gt;</command>). Whenever that changes, you will want to update the display
@@ -4483,7 +4490,8 @@ different types, using modifiers may lead to errors. For <replaceable>fixed_valu
 	<varlistentry><term>getList(id)</term><listitem><para>Returns the value of the given child property as an array of strings (if possible). Returns the value of this property, if ID is omitted.</para></listitem></varlistentry>
 	<varlistentry><term>setValue(id, value)</term><listitem><para>Set the value of the given child property to <emphasis>value</emphasis>.</para></listitem></varlistentry>
 	<varlistentry><term>getChild(id)</term><listitem><para>Return an instance of the child-property with the given <emphasis>id</emphasis>.</para></listitem></varlistentry>
-	<varlistentry><term>addChangeCommand(id, command)</term><listitem><para>Execute <emphasis>command</emphasis> whenever the child property given by <emphasis>id</emphasis> changes. Command can either be a string to be evaluated, or callable value (usually a function).</para></listitem></varlistentry>
[suppressed due to size limit]
+	<para>The function returns the parameter <replaceable>command</replaceable>, for convenience (so you can e.g. assign it to a variable and/or call it during initialization).</para></listitem></varlistentry>
 	</variablelist></para></listitem>
 </varlistentry>
 <varlistentry><term>Class "RObject"</term>
diff --git a/rkward/plugins/data/level_select.xml b/rkward/plugins/data/level_select.xml
index 6741cb4d2..d95e994c2 100644
--- a/rkward/plugins/data/level_select.xml
+++ b/rkward/plugins/data/level_select.xml
@@ -21,10 +21,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<script><![CDATA[
 				gui.setValue ("limitnote.visible", false);
 
-				gui.addChangeCommand ("variable", "update ()");
-				gui.addChangeCommand ("limit", "update ()");
-				gui.addChangeCommand ("custom_expression", "update ()");
-				update = function () {
+				gui.addChangeCommand(["variable", "limit", "custom_expression"], function() {
 					gui.setValue ("selector.available", "");
 					gui.setValue ("selector.enabled", 0);
 
@@ -56,7 +53,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 						.catch(err => {
 							gui.setListValue("selector.available", Array("ERROR:", err));
 						});
-				}
+				});
 		]]></script>
 	</logic>
 	<dialog label="Level selector">
diff --git a/rkward/plugins/data/limit_vector_length.xml b/rkward/plugins/data/limit_vector_length.xml
index cff8fac8c..a582fd5fd 100644
--- a/rkward/plugins/data/limit_vector_length.xml
+++ b/rkward/plugins/data/limit_vector_length.xml
@@ -16,12 +16,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect client="custom_stat.enabled" governor="is_custom_stat"/>
 
 		<script><![CDATA[
-				gui.addChangeCommand ("sorting.string", "updateDescription ()");
-				gui.addChangeCommand ("cutoff.int", "updateDescription ()");
-				updateDescription = function () {
-					gui.setValue ("parameters", '"Limit"="' + gui.getValue ("cutoff.int") + ' ' + gui.getValue ("sorting.string") + ' values"');
-				}
-				updateDescription ();
+				updateDescription = function() {
+					gui.setValue("parameters", `"Limit"="${gui.getValue("cutoff.int")} ${gui.getValue("sorting.string")} values"`);
+				};
+				gui.addChangeCommand(["sorting.string", "cutoff.int"], updateDescription);
+				updateDescription();
 		]]></script>
 	</logic>
 	<dialog label="Limit Vector Length">
diff --git a/rkward/plugins/data/one_var_tabulation.xml b/rkward/plugins/data/one_var_tabulation.xml
index d78320a30..98ca983a1 100644
--- a/rkward/plugins/data/one_var_tabulation.xml
+++ b/rkward/plugins/data/one_var_tabulation.xml
@@ -21,10 +21,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect client="custom_stat.enabled" governor="is_custom"/>
 
 		<script><![CDATA[
-			gui.addChangeCommand ("stat.string", "updateFunLabel ()");
-			gui.addChangeCommand ("custom_stat.text", "updateFunLabel ()");
-			gui.addChangeCommand ("outcome.available", "updateFunLabel ()");
-			updateFunLabel = function () {
+			updateFunLabel = gui.addChangeCommand(["stat.string", "custom_stat.text", "outcome.available"], function() {
 				var stat = gui.getValue ("stat.string");
 				var label;
 				if (stat == "freq") {
@@ -39,12 +36,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
 				}
 				gui.setValue ("fun_label", label);
 				updateDescription ();
-			}
+			});
 
-			gui.addChangeCommand ("groups.available", "updateDescription ()");
-			updateDescription = function () {
+			updateDescription = gui.addChangeCommand("groups.available", function() {
 				gui.setValue ("parameters", i18n ("Tabulation groups") + '=paste (names (groups), collapse=' + i18nc ("Tabulate X by Y [by Z [...]]", " by ") + '), ' + i18n ("Tabulation statistic") + '=' + gui.getValue ("fun_label"));
-			}
+			});
 			updateFunLabel ();
 		]]></script>
 	</logic>
diff --git a/rkward/plugins/plots/barplot.xml b/rkward/plugins/plots/barplot.xml
index 8218c4dba..9f8075779 100644
--- a/rkward/plugins/plots/barplot.xml
+++ b/rkward/plugins/plots/barplot.xml
@@ -19,9 +19,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 			<connect client="x.enabled" governor="tabulate.checked.not"/>
 			<connect client="tabulate_options.varsource.selected" governor="vars.selected" />
 			<script><![CDATA[
-				gui.addChangeCommand ("tabulate.checked", "updateFunLabel ()");
-				gui.addChangeCommand ("tabulate_options.fun_label", "updateFunLabel ()");
-				updateFunLabel = function () {
+				updateFunLabel = gui.addChangeCommand(["tabulate.checked", "tabulate_options.fun_label"], function() {
 					if (gui.getValue ("tabulate.checked")) {
 						gui.setValue ("barplot_embed.plotoptions.default_ylab", gui.getValue ("tabulate_options.fun_label"));
 						gui.setValue ("barplot_embed.plotoptions.default_xlab", "title");
@@ -29,7 +27,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 						gui.setValue ("barplot_embed.plotoptions.default_ylab", "");
 						gui.setValue ("barplot_embed.plotoptions.default_xlab", "");
 					}
-				}
+				});
 				updateFunLabel ();
 			]]></script>
 		</logic>
diff --git a/rkward/plugins/plots/dotchart.xml b/rkward/plugins/plots/dotchart.xml
index 4e4f05479..b648eb9c7 100644
--- a/rkward/plugins/plots/dotchart.xml
+++ b/rkward/plugins/plots/dotchart.xml
@@ -22,9 +22,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect client="tabulate_options.varsource.selected" governor="vars.selected" />
 
 		<script><![CDATA[
-			gui.addChangeCommand ("tabulate.checked", "updateFunLabel ()");
-			gui.addChangeCommand ("tabulate_options.fun_label", "updateFunLabel ()");
-			updateFunLabel = function () {
+			updateFunLabel = gui.addChangeCommand(["tabulate.checked", "tabulate_options.fun_label"], function() {
 				if (gui.getValue ("tabulate.checked")) {
 					gui.setValue ("plotoptions.default_xlab", gui.getValue ("tabulate_options.fun_label"));
 					gui.setValue ("plotoptions.default_ylab", "title");
@@ -32,7 +30,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 					gui.setValue ("plotoptions.default_xlab", "");
 					gui.setValue ("plotoptions.default_ylab", "");
 				}
-			}
+			});
 			updateFunLabel ();
 		]]></script>
 	</logic>
diff --git a/rkward/plugins/plots/pareto.xml b/rkward/plugins/plots/pareto.xml
index dc3271e59..b6b7118b5 100644
--- a/rkward/plugins/plots/pareto.xml
+++ b/rkward/plugins/plots/pareto.xml
@@ -16,15 +16,13 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect client="x.enabled" governor="tabulate.checked.not"/>
 		<connect client="tabulate_options.varsource.selected" governor="vars.selected" />
 		<script><![CDATA[
-			gui.addChangeCommand ("tabulate.checked", "updateFunLabel ()");
-			gui.addChangeCommand ("tabulate_options.fun_label", "updateFunLabel ()");
-			updateFunLabel = function () {
+			updateFunLabel = gui.addChangeCommand(["tabulate.checked", "tabulate_options.fun_label"], function() {
 				if (gui.getValue ("tabulate.checked")) {
 					gui.setValue ("plotoptions.default_ylab", gui.getValue ("tabulate_options.fun_label"));
 				} else {
 					gui.setValue ("plotoptions.default_ylab", quote ("Frequency"));
 				}
-			}
+			});
 			updateFunLabel ();
 		]]></script>
 	</logic>
diff --git a/rkward/plugins/plots/piechart.xml b/rkward/plugins/plots/piechart.xml
index 4fae6856c..17a21f19f 100644
--- a/rkward/plugins/plots/piechart.xml
+++ b/rkward/plugins/plots/piechart.xml
@@ -27,9 +27,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect client="angle_inc.enabled" governor="density_0.not"/>
 
 		<script><![CDATA[
-			gui.addChangeCommand ("tabulate.checked", "updateFunLabel ()");
-			gui.addChangeCommand ("tabulate_options.fun_label", "updateFunLabel ()");
-			updateFunLabel = function () {
+			updateFunLabel = gui.addChangeCommand(["tabulate.checked", "tabulate_options.fun_label"], function() {
 				if (gui.getValue ("tabulate.checked")) {
 					gui.setValue ("plotoptions.default_main", "title");
 					gui.setValue ("plotoptions.default_sub", gui.getValue ("tabulate_options.fun_label"));
@@ -37,7 +35,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 					gui.setValue ("plotoptions.default_main", "");
 					gui.setValue ("plotoptions.default_sub", "");
 				}
-			}
+			});
 			updateFunLabel ();
 		]]></script>
 	</logic>
diff --git a/rkward/scriptbackends/rkcomponentscripting.cpp b/rkward/scriptbackends/rkcomponentscripting.cpp
index a65642d4a..57af6dd3b 100644
--- a/rkward/scriptbackends/rkcomponentscripting.cpp
+++ b/rkward/scriptbackends/rkcomponentscripting.cpp
@@ -92,16 +92,9 @@ void RKComponentScriptingProxy::evaluate(const QString &code, const QString &fil
 	handleScriptError(result, filename);
 }
 
-void RKComponentScriptingProxy::addChangeCommand(const QString &changed_id, const QJSValue &command) {
+void RKComponentScriptingProxy::addChangeCommand(const QStringList &changed_ids, const QJSValue &command) {
 	RK_TRACE(PHP);
 
-	QString remainder;
-	RKComponentBase *base = component->lookupComponent(changed_id, &remainder);
-	if (!remainder.isEmpty()) {
-		evaluate(QStringLiteral("error ('No such property %1 (failed portion was %2)');\n").arg(changed_id, remainder));
-		return;
-	}
-
 	auto callback = [this, command](RKComponentBase *) {
 		if (command.isCallable()) {
 			auto res = command.call();
@@ -111,10 +104,20 @@ void RKComponentScriptingProxy::addChangeCommand(const QString &changed_id, cons
 		}
 	};
 
-	if (base->isComponent()) {
-		connect(static_cast<RKComponent *>(base), &RKComponent::componentChanged, this, callback);
-	} else {
-		connect(static_cast<RKComponentPropertyBase *>(base), &RKComponentPropertyBase::valueChanged, this, callback);
+	for (const QString &changed_id : changed_ids) {
+		QString remainder;
+		RKComponentBase *base = component->lookupComponent(changed_id, &remainder);
+		if (!remainder.isEmpty()) {
+			evaluate(QStringLiteral("error ('No such property %1 (failed portion was %2)');\n").arg(changed_id, remainder));
+			return;
+		}
+
+
+		if (base->isComponent()) {
+			connect(static_cast<RKComponent *>(base), &RKComponent::componentChanged, this, callback);
+		} else {
+			connect(static_cast<RKComponentPropertyBase *>(base), &RKComponentPropertyBase::valueChanged, this, callback);
+		}
 	}
 }
 
diff --git a/rkward/scriptbackends/rkcomponentscripting.h b/rkward/scriptbackends/rkcomponentscripting.h
index ba38739db..10b8ac5fd 100644
--- a/rkward/scriptbackends/rkcomponentscripting.h
+++ b/rkward/scriptbackends/rkcomponentscripting.h
@@ -36,7 +36,7 @@ class RKComponentScriptingProxy : public QObject {
   public:
 	// these are meant to be called from the script
 	Q_INVOKABLE void include(const QString &filename);
-	Q_INVOKABLE void addChangeCommand(const QString &changed_id, const QJSValue &command);
+	Q_INVOKABLE void addChangeCommand(const QStringList &changed_ids, const QJSValue &command);
 	/** @returns id of the command issued. */
 	Q_INVOKABLE QVariant doRCommand(const QString &command, const QString &callback);
 	Q_INVOKABLE void doRCommand2(const QString &command, const QString &id, const QJSValue resolve, const QJSValue reject);
diff --git a/rkward/scriptbackends/rkcomponentscripting.js b/rkward/scriptbackends/rkcomponentscripting.js
index 720c63f83..92e42f069 100644
--- a/rkward/scriptbackends/rkcomponentscripting.js
+++ b/rkward/scriptbackends/rkcomponentscripting.js
@@ -60,8 +60,13 @@ function Component(id) {
 		return (new Component(this.absoluteId(id)));
 	};
 
-	this.addChangeCommand = function(id, command) {
-		_rkward.addChangeCommand(this.absoluteId(id), command);
+	this.addChangeCommand = function(ids, command) {
+		if (Array.isArray(ids)) {
+			_rkward.addChangeCommand(ids.map((id) => this.absoluteId(id)), command);
+		} else {
+			_rkward.addChangeCommand(Array(this.absoluteId(ids)), command);
+		}
+		return command;
 	};
 };
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.