I think there's a few ways to skin this cat, but the most robust way would probably be to listen to the the selectElements
event for the BaseElementSelectInput
instances bound to each field's container element (the instance will be bound to the <div class="elementselect">
node, except for Categories fields where it'll be bound to the <div class="categoriesfield">
node, for some reason) and can be accessed by calling $('.elementselect').data('elementSelect')
.
You can use the .on()
method to listen to the actual events, here's one way to do it:
$('.elementselect, .categoriesfield').each(function () {
$(this).data('elementSelect').on('selectElements', onSelectElements);
});
function onSelectElements(e) {
var $field = $(e.target);
var selectedElements = e.elements;
}
HUGE CAVEAT
Depending on when your plugin's JavaScript runs, the elementSelect
data attribute for the element select input DOM nodes may return null
.
This is because Craft actually initialises the BaseElementSelectInput instances in some inline JavaScript injected into the source, and this JavaScript is, unfortunately, written to the page after your plugin's JavaScript resources are queued up (after any inline JavaScript your plugin adds, too).
In other words, if you need to hook into the BaseElementSelctInput instances on pageload, you'll need to poll for the data('elementSelect)
attribute (ew, I know):
$('.elementselect, '.categoriesfield').each(function () {
var now = new Date().getTime();
var getElementSelect = (function () {
var elementSelect = $(this).data('elementSelect');
if (elementSelect) {
// Woo, I can haz elementSelect
elementSelect.on('selectElements', onSelectElements);
} else if (new Date().getTime() - now < 5000) {
// Poll for 5 secs (then give up)
Garnish.requestAnimationFrame(getElementSelect);
}
}).bind(this)
getElementSelect();
});
Hack alert: If you don't want to poll for the elementSelect instances, there is another way. Credit due, I learnt this trick from looking at Benjamin Fleming's source code:
var elementSelectInit = Craft.BaseElementSelectInput.prototype.init;
Craft.BaseElementSelectInput.prototype.init = function () {
elementSelectInit.apply(this, arguments);
this.on('selectElements', onSelectElements);
this.on('removeElements', onRemoveElements);
}
function onSelectElements (e) {
console.log(e);
}
What the above does, is to basically hijack the BaseElementSelectInput class' init
method, in order to add some custom event listeners when each BaseElementSelectInput instance is initialised. Works like a charm and – even if it's a huge hack – is quite a bit cleaner than the polling method, in my opinion.