LStarky LStarky - 1 month ago 13
jQuery Question

Adding generic event listener with specific callback

In an Aurelia viewmodel component, I have the following JQuery code that works to capture Ctrl+S or Ctrl+Enter while a modal is visible and call the save function:

$(window).bind('keydown', function(event) {
if (event.ctrlKey || event.metaKey) { // Ctrl + ___
if ((event.which == 83) || (event.which == 115) || (event.which == 10) || (event.which == 13)) { // Ctrl+Enter or Ctrl+S
// Save button
event.preventDefault();
if ($(self.edit_calendar).is(':visible')) {
self.saveCalendar();
}
}
}
});


However, I foresee adding a similar function to 40+ viewmodels, and that doesn't seem very DRY and adds some ugly code to each of my viewmodels. I would like to create a generic addEventListener function in a singleton class to easily call from each of my views. Here's what I have in mind:

addListenerSave(visible, callback) {
// Add an event listener to redirect keyboard shortcuts to specific actions
console.log("addListenerSave()");
$(window).bind('keydown', function(event) {
if (event.ctrlKey || event.metaKey) { // Ctrl + ___
if ((event.which == 83) || (event.which == 115) || (event.which == 10) || (event.which == 13)) { // Ctrl+Enter or Ctrl+S
// Save button
event.preventDefault();
if ($(visible).is(':visible')) {
console.log("Keyboard shortcut: Save");
callback();
}
}
}
});
}


Then, in my individual components, I should only need the following code on instantiation (in the attached() component life cycle):

this.config.addListenerSave(this.edit_calendar, this.saveCalendar);


However, this does not work. saveCalendar() is called but maybe from another scope/context, so I get an error inside saveCalendar that says "Cannot read property 'selectedId' of undefined". This is referring to the saveCalendar() code
if (this.selectedId)...
. What am I doing wrong?

Finally, should I also be removing this event listener when my Aurelia component is detached? How?

One alternate idea I had was to use Aurelia's eventAggregator to create a global event listener that always is listening for Ctrl+S / Ctrl+Enter and then publishing a message that can be subscribed in each component.

Answer

I successfully implemented the alternate solution of adding a global event listener that uses Aurelia's EventAggregator to share Ctrl+S/Ctrl+Enter. Original question still stands but perhaps it wasn't the best approach anyway. Here's my solution:

config.js (global singleton class)

@inject(EventAggregator)
export class Config {
  constructor(eventAggregator) {
    var self = this;
    this.eventAggregator = eventAggregator;
    // listen for Ctrl+S or Ctrl+Enter and publish event
    window.addEventListener("keydown", function(event) {
      if (event.ctrlKey || event.metaKey) { // Ctrl + ___
        if ((event.keyCode == 83) || (event.keyCode == 115) || (event.keyCode == 10) || (event.keyCode == 13)) {  // Ctrl+Enter or Ctrl+S
          // Save button
          console.log("Publishing ewKeyboardShortcutSave...");
          event.preventDefault();
          self.eventAggregator.publish('ewKeyboardShortcutSave', true);
        }
      }
    });
  }
}

Then, inside my component viewmodel calendar.js:

@inject(EventAggregator)
export class Calendar {
  constructor(eventAggregator) {
    this.eventAggregator = eventAggregator;
  }
  attached() {
    var self = this;
    // Ctrl+Enter is save
    this.eventAggregator.subscribe('ewKeyboardShortcutSave', response => {
      console.log("I heard ewKeyboardShortcutSave: " + response);
      if ($(self.edit_calendar).is(':visible')) {
        self.saveCalendar();
      }
    });
  }
}

Works like a charm, and now I can freely add more component event listeners and even extend the functionality to add a global listener for Ctrl+F (for find), etc.

Comments