Monday 4 November 2019

Keep jQuery UI Dialog button text in HTML, not in JS initializers

It's long irked me that the jQuery UI Dialog class expects one to specify what text appears on a Dialog's buttons in the options initializer:
$('#something').dialog({ buttons: [ { text: 'Apply', click: function() { /* ... */ } }, { text: 'Cancel', click: function() { /* ... */ } }, ] });
This has problems:
  • It's a confusion of responsibilities between View and Controller roles. Specifying button text is very much "View territory", while defining dialog actions belongs to the Controller role.
  • It obstructs internationalization. If one now wanted a German version of the web app, it wouldn't be enough simply to translate everything in the HTML body. One would have to also maintain multiple versions of the page scripts, one for each (natural) language.
$('#something').dialog({ buttons: [ { text: 'Anwenden', click: function() { /* ... */ } }, { text: 'Abbrechen', click: function() { /* ... */ } }, ] });
Yuk.
All problems in computer science can be solved by another level of indirection.
- David Wheeler
I figured out a way to separate concerns by (ab)using a button's class attribute. Inside the dialog-defining HTML, I explicitly list the buttons in a container marked as being of class translatable-buttons:
<div class="translatable-buttons"> <button class="translatable-apply" type="button">Apply</button> <button class="translatable-delete" type="button">Delete</button> <button class="translatable-cancel" type="button">Cancel</button> </div>
These explicitly marked-up buttons shouldn't be shown in the dialog though, since jQuery UI wants to add the dialog's buttons itself, so apply some CSS:
.translatable-buttons { display: none; }
Buttons have to do something when pressed:
var buttonspecs = { 'translatable-apply': { click: function() {}, }, 'translatable-delete': { click: function() {}, }, 'translatable-cancel': { click: function() {}, }, };
And finally jQuery UI needs to see this in a language it can understand, so a bit of list processing glues it all together:
var buttons = []; // Grab button text from HTML and add it as a text: property. $.each(buttonspecs, function(i, buttonspec) { buttons.push($.extend({}, buttonspec, { text: $('.translatable-buttons button.'+i, speciesdialog).text() })); }); var speciesdialog = $('#speciesdialog').dialog({ buttons: buttons, });
The key is to define the behaviours in the page script and link it to an abstract button name, rather than to concrete button text. This is the purpose for the translatable-apply etc. keys in the buttonspecs object. The $.each call then merges these button behaviours with the button text read from the DOM and concatenates all the results into the buttons array.

The excerpts shown here are from my chemistry calculator, in the Keq section.