Best Practice to add UI enhancements to multiple B

2019-04-15 09:03发布

So I was what the best way for all views in an application to have actions performed on an element.

In a non single page application you would run say:

$(document).ready(function() { 
    $('.autosize').autosize();
});

to apply autosize function to all elements with the autosize class on every page.

Now in a Backbone Marionette app to do this you could perform that in each view with onDomRefresh or similar but for things that affect 90% of views you'd want this to run automatically somehow.

I don't think there's a way that an Application object can listen to all onDomRefresh events which would potentially solve it. I've consider overloading Marionette.MonitorDOMRefreshto add this in but it doesn't feel like a Backbone approach.

Other things I considered were sub-classing each of the marionette views to add mixins for loading different groups of UI elements.

I figured other people must have experienced this scenario so was interested what approaches have been used.

3条回答
虎瘦雄心在
2楼-- · 2019-04-15 09:23

In CoffeeScript I think you could also do:

extend = (obj, mixin) ->
  obj[name] = method for name, method of mixin        
  obj

include = (klass, mixin) ->
  extend klass.prototype, mixin

include Marionette.View,
  onDomRefresh: () -> @$('.autosize').autosize()

Which should cover all the view types. Haven't tested this specifically, but just did something very similar to add functionality to Marionette's Layout view. Extend / include pattern at http://arcturo.github.io/library/coffeescript/03_classes.html. Of course this should all be doable in straight up JS too.

UPDATE:

Actually, since we have Underscore available to us we don't need to manually define the include and extend methods. We can just say:

_.extend Marionette.View.prototype,
  onDomRefresh: () -> @$('.autosize').autosize()
查看更多
We Are One
3楼-- · 2019-04-15 09:36

So I couldn't really find any solutions that really solved my problem, despite some helpful chats with @julio_menedez and @marionettejs on Twitter. With a really good idea being using Polymer but wasn't suitable as I need to support older IE's.

So instead I headed into the dangerous world of monkey patching to solve it (Bear in mind I might need to iron out some wrinkles with this still, just finished writing it and not fully tested it - I'll update accordingly)

In Coffeescript: (javascript version at the bottom)

# Monkey patching the Marionette View.. sorry!

# this is the only Marionette view which doesn't have it's own constructor
Marionette.ItemView = Marionette.ItemView.extend
  constructor: ->
    Marionette.View.prototype.constructor.apply @, Array.prototype.slice.call(arguments, 0)

original_view_constructor = Marionette.View.prototype.constructor
Marionette.View.EventAggregator = event_aggregator = _.extend {}, Backbone.Events

# all the other constructors call this so we can hijack it
Marionette.View.prototype.constructor = ->
  event_aggregator.listenTo @, 'all', =>
    args_array = Array.prototype.slice.call arguments, 0
    event_aggregator.trigger.apply event_aggregator, [ 'view:' + args_array[0], @ ].concat(args_array.slice(1))
    event_aggregator.stopListening @ if args_array[0] == 'close'
  original_view_constructor.apply @, Array.prototype.slice.call(arguments, 0)

And then to use I just setup a listener in my application object to catch view events I need. e.g:

@listenTo Marionette.View.EventAggregator, 'view:dom:refresh', (view) ->
  view.$('div').css('backgroundColor', 'red');

So in my view these are the pros and cons of this technique:

Pros:

  • Can listen to all view events without injecting all view classes or subclassing all view classes
  • Simple to use
  • Objects don't need to opt-in to using it at all

Cons

  • Uses monkey patching, dangerous to Marionette API Changes
  • Uses Marionette namespacing so vulnerable to a future Marionette namespace collision
  • Takes dealing with views out of view context
  • Having an event aggregator object isn't something seen elsewhere in Backbone/Marionette (afaiw) so breaks a pattern (update - something similar is seen with Backbone.history)

Anyway I'm welcome to feedback, alternatives, criticism :-) and hope maybe this helps someone else in the same situation

Javascript:

(function() {
  var event_aggregator, original_view_constructor;

  Marionette.ItemView = Marionette.ItemView.extend({
    constructor: function() {
      return Marionette.View.prototype.constructor.apply(this, Array.prototype.slice.call(arguments, 0));
    }
  });

  original_view_constructor = Marionette.View.prototype.constructor;

  Marionette.View.EventAggregator = event_aggregator = _.extend({}, Backbone.Events);

  Marionette.View.prototype.constructor = function() {
    var _this = this;
    event_aggregator.listenTo(this, 'all', function() {
      var args_array;
      args_array = Array.prototype.slice.call(arguments, 0);
      event_aggregator.trigger.apply(event_aggregator, ['view:' + args_array[0], _this].concat(args_array.slice(1)));
      if (args_array[0] === 'close') {
        return event_aggregator.stopListening(_this);
      }
    });
    return original_view_constructor.apply(this, Array.prototype.slice.call(arguments, 0));
  };

}).call(this);
查看更多
成全新的幸福
4楼-- · 2019-04-15 09:48

Just make a base View class and inherit from it every view class that needs the autosize enhancement.

var AutosizeBaseView = Backbone.Marionette.ItemView.extend({
    onDomRefresh: function(){
        this.$('.autosize').autosize();
    }
});

then make your classes like this:

var SomeView = AutosizeBaseView.extend({
});
查看更多
登录 后发表回答