Design for a "notification system" rendering different templates in an outlet on the header


#1

I’m trying to design a flexible notification system for our app. The functional goal is to notify users when certain [server] actions have completed or error-ed and allow the user to navigate or trigger a response action. I’m not worried with queuing multiple message. Last in wins. I cut out some of the mockups to help illustrate it.

Imagine a header, and depending on the scenario, one of the 5 messages above would be rendered. In some cases the message would contain an action that needs triggered (e.g. Undo), and in other cases it would render a {{link-to}} (e.g. Interview Sorting). And it’s conceivable as the app grows we will continue to add different messages.

So I’m looking for feedback on my technical design. I spent a few hours and tried a few different things. I didn’t really love any of the designs, but this is minified version of where I landed.

Edit: Made a JSBin: http://emberjs.jsbin.com/womupenoci/2/edit?html,js,output

//# app/routes/application.js

export default Ember.Route.extend({
  actions: {
    showNotification: function(name, msg) {
      // We prepend notifications with "/notifications" to keep them organized
      if(name.indexOf("notifications") === -1) { 
        name = "notifications" + name.capitalize(); 
      }
      this.render(name, {
        into: 'application',
        outlet: 'notification'
      });
      this.controllerFor(name).set('msg', msg);
    },
    hideNotification: function() {
      return this.disconnectOutlet({
        outlet: 'notification',
        parentView: 'application'
      });
    }
  }
});

//# app/controllers/wall.js

export Ember.Controller.extend({
  actions: {
    doCopyItems: function() {
      var copyPromise = this.doCopyItemsStuff();
      copyPromise.then(function(result) {
        this.send('showNotification', 'copyWallItems', {
          copyCount: result.copyCount
          destWall: this.get('wall'),
          undoCallback: function() {
            this.triggerUndo();
          }.bind(this)
        });
      }.bind(this));
    }
  }
});

//# app/templates/notificationsCopyWallItems.hbs

{{msg.copyCount}} copies sent to {{#link-to 'wall' msg.destWall}} {{msg.destWall.name}} {{/link-to}}. 

<a {{action "triggerUndoCallback"}}>Undo</a>

<a class="close-x" {{action "hideNotification"}}>X</a>

So in this design, the application route simple renders the provided template/controller and passes along a message. Each template/controller would handle their layout and actions accordingly. The different notification types would share no code between them.

Does this make sense? Is there a better way to structure it? ~

Thanks!


#2

I would do something like this:

I didn’t really take it the whole way, but I think you get the idea.


#3

@jasonmit Hey thanks a lot for taking the time to put all of that together!

I actually came up with a near identical implementation, except I used an Ember.ContainerView. One thing that didn’t occur to me was using injection to make it easier. I like that! +1

So I think this too is a perfectly viable design. I didn’t love it or hate it more than the one I posted. I’m curious is there anything about your JSBin that you find to be more ideal, or reason you might choose it over the former?

Just looking for some insight into this design, I can’t really put my finger on any solid pros/cons (other than code structure).


#4

I actually didn’t see your JSBin, but the one reason I would go the way of the “service” is decoupling the notification logic from the rest of the application.

One thing I didn’t do in my example, is to create a “model” versus creating a view. This notification factory would return a promise where the promise is resolved/rejected when: the user closes it, its destroyed by the scheduler, the user clicks undo, etc.

This would eliminate the weird sending of actions around from the template and keep the business logic isolated to the thingy creating the notification. Think in the case of undo, where undo may have a pretty unique implementation depending on where you are in your application. This would allow for an API like:

this.notify.add('error', {
  /* optional options such as overriding the templateName
     or defaultTimeout */
}).then(function (result)  { 
  /* perhaps result could return some object describing how it was resolved */
});

#5

@jasonmit Hey thanks again for the response. I think it’s been a good back and forth. I now have a design I can land on. Cheers!