Composition over inheritance, which is a more convenient way to add additional functions to a view without resorting to inheritance

In the past, I read a lot about compositability over inheritance, and I completely sell this concept and use this principle a lot in my code.

However, I run into problems in everyday work, where inheritance tends to creep in views, and I'm struggling to figure out how I can implement something more complex (the fact that I use Backbone on my day does not help for day work). They are usually when I want to use all the features of an existing Backbone view, adding some extra features on top.

Take this hypothetical example, where we have an e-commerce type page with several views Product, each of which is a set of options available for the basket for a specific product:

var ProductView = (function(Backbone, JST) {
  'use strict';

  return Backbone.View.extend({
    className: 'product',
    template: JST['application/templates/product']

    initialize: function(options) {
      this.options = options || {};
      this.collection.fetch();
      this.listenTo(this.collection, 'loaded', this.render);
    },

    render: function() {
      this.$el.html(
        this.template(this.collection)
      );

      return this;
    },
  }, {
    create: function(el) {
      var endpoint = '/api/options/' + el.getAttribute('data-basket-id') + '/' + el.getAttribute('data-product-id');

      new ProductView({
        el: el,
        collection: new ProductCollection(null, { url: endpoint })
      });
    }
  });
})(Backbone, JST);

Suppose we want to display some products that require the visitor to be requested using the confirmation window (say, for insurance reasons, this particular product must be sold with insurance, so we need to send a request to the user about this when they add it to their basket):

var InsuranceProductView = (function (_, ProductView) {
  'use strict';

  return ProductView.extend({
    consentTemplate: JST['application/templates/product/insurance_consent'],

    initialize: function (options) {
      this.listenTo(this.model, 'change:selected', function (model) {
        if (!model.get('selected')) {
          this.removeMessage()
        }
      });

      ProductView.prototype.initialize.apply(this, arguments);
    },

    events: function () {
      return _.extend({}, ProductView.prototype.events, {
        'change input[type=radio]': function () {
          this.el.parentElement.appendChild(this.consentTemplate());
        },
        'change .insurance__accept': function () {
          ProductView.prototype.onChange.apply(this);
        },
      });
    },

    removeMessage: function () {
      var message = this.el.parentElement.querySelector('.insurance__consent');
      message.parentNode.removeChild(message);
    },
  });
})(_, ProductView);

Is there a more complicated way to write this? Or is it a situation where the right thing to break through inheritance is?

+4
source share
1

. , , .

, . Backbone, , .

- , initialize . events.

var ProductView = Backbone.View.extend({
    className: 'product',
    template: JST['application/templates/product'],
    events: {},

    constructor: function(options) {
        // make parent event the default, but leave the event hash property
        // for the child view
        _.extend({
            "click .example-parent-event": "onParentEvent"
        }, this.events);

        this.options = options || {};
        this.collection.fetch();
        this.listenTo(this.collection, 'loaded', this.render);

        ProductView.__super__.constructor.apply(this, arguments);
    },

    /* ...snip... */
});

:

var InsuranceProductView = ProductView.extend({
    consentTemplate: JST['application/templates/product/insurance_consent'],

    events:{
        'change input[type=radio]': 'showConsent',
        'change .insurance__accept': 'onInsuranceAccept'
    }

    initialize: function(options) {
        this.listenTo(this.model, 'change:selected', function(model) {
            if (!model.get('selected')) {
                this.removeMessage()
            }
        });
    },

    showConsent: function() {
        // I personally don't like when component go out of their root element.
        this.el.parentElement.appendChild(this.consentTemplate());
    },

    onInsuranceAccept: function() {
        InsuranceProductView.__super__.onChange.apply(this);
    },

    removeMessage: function() {
        var message = this.el.parentElement.querySelector('.insurance__consent');
        message.parentNode.removeChild(message);
    },
});

, Backbone extend __super__ . , , - .


, .

, , :

var FoodMenu = Backbone.View.extend({
    template: '<div class="food-search"></div><div class="food-search-list"></div>',

    // abstracting selectors out of the view logic
    regions: {
        search: ".food-search",
        foodlist: ".food-search-list",
    },

    initialize: function() {

        // build your view with other components
        this.view = {
            search: new TextBox({
                label: 'Search foods',
                labelposition: 'top',
            }),
            foodlist: new FoodList({
                title: "Search results",
            })
        };
    },

    render: function() {
        this.$el.empty().append(this.template);

        // Caching scoped jquery element from 'regions' into `this.zone`.
        this.generateZones();
        var view = this.view,
            zone = this.zone;
        this.assign(view.search, zone.$search)
            .assign(view.foodlist, zone.$foodlist);

        return this;
    },

});
0

Source: https://habr.com/ru/post/1659719/


All Articles