LStarky
LStarky

Reputation: 2777

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 or Ctrl+Enter and then publishing a message that can be subscribed in each component.

Upvotes: 0

Views: 2488

Answers (2)

user677526
user677526

Reputation:

To answer your original question, you're on the right track - but due to the semantics of this in JavaScript, you'll need to bind your function. (If you're coming from a C# perspective, it may help to think that all functions in JavaScript are essentially extension methods; as such, passing functions can be VERY powerful.) It's easy to miss this because of the new ES6 class syntax.

This should work to mitigate your issue:

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

That said, your solution using Aurelia's Event Aggregator is a much better fit for your use case and much more scalable. I thought I'd post this answer to address the original issue, which was simply a matter of function scope.

Upvotes: 1

LStarky
LStarky

Reputation: 2777

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.

Upvotes: 0

Related Questions