Backbone单击事件绑定未绑定到DOM元素

时间:2013-04-02 15:31:53

标签: backbone.js requirejs underscore.js

我有这些被称为beats的div,我想在点击它们时注册。但是,我似乎无法通过单击它们或在控制台中的特定div上调用JQuery click事件来让它们注册一个单击。无论哪种方式,都没有注册。

measureView.js创建了这个beatView,它在父测量中创建了一个节拍。

beatView.js:

//filename: views/beats/beatView.js
/*  This is the view for a single beat, which is contained in a measure view. */
define([ 'jquery', 'underscore',      'backbone',      'backbone/models/beat',      'text!backbone/templates/measures/audioMeasures.html',      'text!backbone/templates/beats/linearBarBeats.html',      'text!backbone/templates/beats/linearBarSVGBeats.html',      'text!backbone/templates/beats/circularPieBeats.html',      'app/dispatch',      'app/log'
], function($, _, Backbone, BeatModel, audioMeasuresTemplate, linearBarBeatsTemplate, linearBarSVGBeatsTemplate, circularPieBeatsTemplate, dispatch, log){
  return Backbone.View.extend({
    //registering backbone's click event to our toggle() function.
    events : {
      'click' : 'toggle'
    },

    //The constructor takes options because these views are created by measuresView objects.
    initialize: function(options){
      if (options) {
        console.log('options :');
        console.warn(options);
        this.model = options.model;
        // this.parentEl should be measure.cid
        this.measureBeatHolder = options.parentElHolder;
      } else {
        console.log('never really getting here');
        this.model = new BeatModel;
      }    
      this.render();
    },

    //We use css classes to control the color of the beat. A beat is essentially an empty div.
    render: function(toggle){
      var state = this.getSelectionBooleanCSS();
      if (toggle) {
        $('#beat'+toggle).removeClass(state);
        $('#beat'+toggle).addClass(this.switchSelectionBooleanValue());
      } else {
        var compiledTemplate = _.template(this.representations[this.currentBeatRepresentation], {beat: this.model, beatAngle: this.beatAngle, state: state});
        $(this.measureBeatHolder).append( compiledTemplate );
        return this;
      }
    },

    getSelectionBooleanCSS: function(){
      if (this.model.get("selected")) {
        return "ON";
      } else {
        return "OFF";
      }
    },

    switchSelectionBooleanValue: function(){
      if (this.model.get('selected') == true) {
        this.model.set('selected', "false");
      } else {
        this.model.set('selected', "true");
      }
      return this.model.get('selected');
    },

    /*
      This is called when a beat is clicked.
      It does a number of things:
      1. toggles the model's selected field.
      2. re-renders the beat.
      3. prints a console message.
      4. tells log to send a log of the click event.
      5. triggers a beatClicked event.
    */

    toggle: function(){
      console.log('getting to toggle function');
      var selectedBool = this.model.get("selected");
      this.model.set("selected", !selectedBool);
      var newBool = this.model.get("selected");
      this.render(this.model.cid);
      dispatch.trigger('beatClicked.event');
    }
  });
});

用于参考:

beatModel:

//filename: models/beat.js
/*
  This is the beat model.
  It only knows about whether or not it
  is selected.
*/
define([
  'underscore',
  'backbone'
], function(_, Backbone) {
  var beatModel = Backbone.Model.extend({
    defaults: {
      selected: false,
      state: 'OFF'
    },
    initialize: function(){
    },
    getStyleClass: function() {
      if (this.selected) {
        return 'ON';
      }
      else {
        return 'OFF';
      }
    }
  });

  return beatModel;
});

measureModel:

//filename: models/measure.js
/*
  This is the measure model.
  A component has a collection of these models.
  these models have a collection of beats.
*/
define([
  'underscore',
  'backbone',
  'backbone/collections/beats'
], function(_, Backbone, beatsCollection) {
  var measureModel = Backbone.Model.extend({
    defaults: {
      label: '0/4',
      beats: beatsCollection,
      numberOfBeats: 0,
      divisions: 8
    },
    initialize: function(){     
    }

  });

  return measureModel;
});

measureView.js:

// Filename: views/measures/measuresView.js
/*
  This is the MeasuresView.

  This is contained in a ComponentsView.
*/
define([
  'jquery',
  'underscore',
  'backbone',
  'backbone/collections/measures',
  'backbone/collections/beats',
  'backbone/models/measure',
  'backbone/views/beats/beatView',
  'text!backbone/templates/measures/audioMeasures.html',
  'text!backbone/templates/measures/linearBarMeasures.html',
  'text!backbone/templates/measures/linearBarSVGMeasures.html',
  'text!backbone/templates/measures/circularPieMeasures.html',
  'app/dispatch',
  'app/state',
  'app/log'
], function($, _, Backbone, MeasureModel, BeatsCollection, MeasuresCollection, beatView, audioMeasuresTemplate, linearBarMeasuresTemplate, linearBarSVGMeasuresTemplate, circularPieMeasuresTemplate, dispatch, state, log){
  return Backbone.View.extend({
    // el: $('.component'),

    // The different representations
    representations: {
      "audio": audioMeasuresTemplate,
      "linear-bar": linearBarMeasuresTemplate,
      "linear-bar-svg": linearBarSVGMeasuresTemplate,
      "circular-pie": circularPieMeasuresTemplate
    },

    currentMeasureRepresentation: 'linear-bar',

    //registering click events to add and remove measures.
    events : {
      'click .addMeasure' : 'add',
      'click .delete' : 'remove'
    },

    initialize: function(options){
      //if we're being created by a componentView, we are
      //passed in options. Otherwise we create a single
      //measure and add it to our collection.
      if (options) {
        this.measuresCollection = options.collection;
        this.parent = options.parent;
        this.el = options.el;
      }
      // else {
      //   this.measure = new BeatsCollection;

      //   for (var i = 0; i < 4; i++) {
      //     this.measure.add();
      //   }

      //   this.measuresCollection = new MeasuresCollection;
      //   this.measuresCollection.add({beats: this.measure});
      // }

      if (options["template-key"]) {
        this.currentBeatRepresentation = options["template-key"];
      }

      //registering a callback for signatureChange events.
      dispatch.on('signatureChange.event', this.reconfigure, this);
      //Dispatch listeners
      dispatch.on('measureRepresentation.event', this.changeMeasureRepresentation, this);

      this.render();

      //Determines the intial beat width based on the global signature. Has to be below this.render()
      this.calcBeatWidth(this.parent.get('signature'));
    },

    changeMeasureRepresentation: function(representation) {
      this.currentMeasureRepresentation = representation;
      this.render();      
    },

    render: function(){
      $(this.el).html('<div class="addMeasure">+</div>');
      var measureCount = 1;
      //we create a BeatsView for each measure.
      _.each(this.measuresCollection.models, function(measure) {
        // when representation button changes, the current representation template will get updated
        var compiledTemplate = _.template( this.representations[this.currentMeasureRepresentation], {measure: measure, beatHolder:"beatHolder"+measure.cid, measureCount:measureCount, measureAngle: 360.0 } );
        $(this.el).find('.addMeasure').before( compiledTemplate );
          console.log('measure beats: ');
          console.warn(measure.get('beats').models);
            _.each(measure.get('beats').models, function(beat) {
              // console.warn("#beat"+beat.cid.toString());
              new beatView({model:beat, parentElHolder:'#beatHolder'+measure.cid, parentCID:measure.cid, singleBeat:"#beat"+beat.cid});
            }, this);
        measureCount ++;
      }, this);
      return this;
    },

    /*
      This is called when the user clicks on the plus to add a new measure.

      It creates a new measure and adds it to the component.
      It generates a string representing the id of the measure and the ids of
      its beats and logs the creation.

      Lastly, it triggers a stopRequest, because we can't continue playing until
      all the durations get recalculated to reflect this new measure.
    */
    add: function(){
        console.log('add measure');
        var newMeasure = new BeatsCollection;

        for (var i = 0; i < this.parent.get('signature'); i++) {
          newMeasure.add();
        }

        this.measuresCollection.add({beats: newMeasure});

        //Logging
        name = 'measure' + _.last(this.measuresCollection.models).cid + '.';
        _.each(newMeasure.models, function(beats) {
          name = name + 'beat'+ beats.cid + '.';
        }, this);
        log.sendLog([[3, "Added a measure: "+name]]);

        //Render
        this.render();
        //Dispatch
        dispatch.trigger('stopRequest.event', 'off');
    },

    /*
      This is called when the user clicks on the minus to remove a measure.
    */
    remove: function(ev){
      if ($('#measure'+this.measuresCollection.models[0].cid).parent()) {
        //removing the last measure isn't allowed.
        if(this.measuresCollection.models.length == 1) {
          console.log('Can\'t remove the last measure!');
          return;
        }
        console.log('remove measure');

        //we remove the measure and get its model.
        var model = this.measuresCollection.get($(ev.target).parents('.measure').attr('id').replace('measure',''));
        this.measuresCollection.remove(model);

        //send a log event showing the removal.
        log.sendLog([[3, "Removed a measure: measure"+model.cid]]);

        //re-render the view.
        this.render();

        //trigger a stop request to stop playback.
        dispatch.trigger('stopRequest.event', 'off');
        dispatch.trigger('signatureChange.event', this.parent.get('signature'));
      }
    },
    // This is triggered by signatureChange events.
    reconfigure: function(signature) {
      console.log('MeasureView.reconfigure(signature) : signature=' +signature);
      /* if the containing component is selected, this
         triggers a request event to stop the sound.

         Then this destroys the beat collection and creates
         a new collection with the number of beats specified
         by the signature parameter.
      */
      if ($(this.parent).hasClass('selected')) {
        dispatch.trigger('stopRequest.event', 'off');
        this.measure.reset();

        for (var i = 0; i < signature; i++) {
          this.measure.add();
        }
        //re-render the view.
        this.render();

        //recalculate the widths for each beat.
        this.calcBeatWidth(signature);
        dispatch.trigger('signatureChange.event', this.parent.get('signature'));

      }
    },

    //This determines the width of each beat based on the
    //number of beats per measure or 'signature'.
    calcBeatWidth: function(signature) {
      if ($(this.el).hasClass('selected')) {
        var px = 100/$('.measure').css('width').replace(/[^-\d\.]/g, '');
        var beatWidth = (100 - ((signature*1+1)*px))/signature;

        $(this.el).children('.beat').css({
          'width' : beatWidth+'%'
        });
      }
    }
  });
});

3 个答案:

答案 0 :(得分:3)

Backbone获取您的events对象,并将所有这些事件类型和选择器委托给视图el。您希望事件注册的任何HTML都需要插入到视图的el中,并且el需要插入页面。

通常我会设置我的观点:

var myView = Backbone.View.extend({

  id: 'myView',

  events: {
    'click li' : 'myEventCallback'
  },

  initialize: function() {

    $('body').append(this.el); //el on the page now
    this.render(); //fills up el with useful markup
  },

  render: function() {
    //fills el with useful markup from a template
    this.el.html( JST['myTemplate' ]() ); 
  },

  myEventCallback: function() {
    //code for handling click events on the li's inside the el of this view
  }
});

答案 1 :(得分:0)

感谢@AlexMcp和@PaulHoenecke的贡献。

我最终通过渲染

中的JQuery代理函数将上下文传递给自己
    // add click handler to this beat
    $("#beat"+this.model.cid).click($.proxy(this.toggle, this));

完成beatView.js文件:

//filename: views/beats/beatView.js
/*
  This is the view for a single beat, which
  is contained in a measure view.
*/
define([
  'jquery',
  'underscore',
  'backbone',
  'backbone/models/beat',
  'text!backbone/templates/measures/audioMeasures.html',
  'text!backbone/templates/beats/linearBarBeats.html',
  'text!backbone/templates/beats/linearBarSVGBeats.html',
  'text!backbone/templates/beats/circularPieBeats.html',
  'app/dispatch',
  'app/log'
], function($, _, Backbone, BeatModel, audioMeasuresTemplate, linearBarBeatsTemplate, linearBarSVGBeatsTemplate, circularPieBeatsTemplate, dispatch, log){
  return Backbone.View.extend({

    /* TODO still issues with this
      el: '.beat',
      registering backbone's click event to our toggle() function.
       events : {
         'click' : 'toggle'
       },
    */

    // The different representations
    representations: {
      "audio": audioMeasuresTemplate,
      "linear-bar": linearBarBeatsTemplate,
      "linear-bar-svg": linearBarSVGBeatsTemplate,
      "circular-pie": circularPieBeatsTemplate
    },
    currentBeatRepresentation: 'linear-bar',
    beatAngle: 90,

    //The constructor takes options because these views are created
    //by measuresView objects.
    initialize: function(options){
      if (options) {
        // TODO: need to take in an option about currentBeatRep
        // TODO: maybe need to respond to a representation changed event (change this.currentBeatRepresentation and rerender)

        console.log('options :');
        console.warn(options);
        this.model = options.model;

        // this is the html element into which this class should render its template
        this.measureBeatHolder = options.parentElHolder;
        this.el = options.singleBeat;
        this.parent = options.parent;
      } else {
        console.error('should not be in here!');
        this.model = new BeatModel;
      }
      this.render();
    },

    //We use css classes to control the color of the beat.
    //A beat is essentially an empty div.
    render: function(toggle){
      // the current state of the beat (is it ON or OFF?)
      var state = this.getSelectionBooleanCSS();

      // if render is being called from the toggle function, we may want to do something different
      if (toggle) {
        $('#beat'+toggle).toggleClass("ON");
        $('#beat'+toggle).toggleClass("OFF");
      } else {
        // this is reached during the initial rendering of the page

        // compile the template for this beat (respect the current representation)
        var compiledTemplate = _.template(this.representations[this.currentBeatRepresentation], {beat: this.model, beatAngle: this.beatAngle, state: state});
        // append the compiled template to the measureBeatHolder
        $(this.measureBeatHolder).append( compiledTemplate );
        // add click handler to this beat
        $("#beat"+this.model.cid).click($.proxy(this.toggle, this));
        // $(this.parentEl).append(compiledTemplate);
        return this;
      }
    },

    getSelectionBooleanCSS: function(){
      if (this.model.get("selected")) {
        return "ON";
      } else {
        return "OFF";
      }
    },

    /*
      This is called when a beat is clicked.
      It does a number of things:
      1. toggles the model's selected field.
      2. re-renders the beat.
      3. prints a console message.
      4. tells log to send a log of the click event.
      5. triggers a beatClicked event.
    */
    toggle: function(){
      //switch the selected boolean value on the model
      this.model.set('selected', !this.model.get('selected'));
      //re-render it, passing the clicked beat to render()
      this.render(this.model.cid);
      // log.sendLog([[1, "beat" + this.model.cid + " toggled: "+!bool]]);
      dispatch.trigger('beatClicked.event');
    }
  });
});

答案 2 :(得分:0)

实际上......您需要做的就是在视图的初始化函数中定义事件。