Search code examples
javascripthtmlfabricjs

Re-rendering a sub-classed fabric object after changing its properties


I have sub-classed the fabric.Rect "class". It has some properties like the the type, name and area.

var RoomRect = fabric.util.createClass(fabric.Rect, {

    initialize: function(options) {
      options || (options = { });
  
      this.callSuper('initialize', options);

      // type is either one of HALL, BEDROOM, TOILET, etc.
      this.set('type', options.type || '');

      // name is user defined and also a default name is generated if the name has not been specified
      // the name acts as a unique identifier
      this.set('name', options.name || '');

      // area is in sq. meters
      this.set('area', options.area || 0);
    },
  
    toObject: function() {
      return fabric.util.object.extend(this.callSuper('toObject'), {
        type: this.get('type'),
        name: this.get('name'),
        area: this.get('area')
      });
    },
    
    _render: function(ctx) {
      this.callSuper('_render', ctx);

      ctx.font = '1.3em Lato';
      ctx.fillStyle = '#383838';
      ctx.fillText("Type: "+this.type, -this.width/2+15, -this.height/2 + 20);
      ctx.fillText("Name: "+this.name, -this.width/2+15, -this.height/2 + 40);
      ctx.fillText("Area: "+this.area+" sq. m.", -this.width/2+15, -this.height/2 + 60);
    }
  });

When this rectangle is double clicked a prompt is shown, wherein the user can change the name and area properties. If the user clicks on save, following code is triggered. It changes the properties and renders everything again.

function saveRoomInfo(){
        if(SELECTED_ITEM == null){return;}
        // get the data from the form and put it into the object
        
        var roomInfoForm_name = document.getElementById('roomName');
        var newName = roomInfoForm_name.value;

        var roomInfoForm_area = document.getElementById('roomArea');
        var newArea = roomInfoForm_area.value;

        SELECTED_ITEM.set('name',newName);
        SELECTED_ITEM.set('area',newArea);
        
        fabricCanvas.renderAll();
        
    }

The problem here is that, the changes are made in the object(confirmed via debugger) but the changes are not done in the user interface(the rectangle object).

Another question, is it a good practice to render everything again. Can I selectively re-render one object. I could delete the old object and render a new one with different properties, but that would require changes in other data structures as well.


Solution

  • In newer versions of fabricjs it wont render all objects, you need to set dirty : true or put the property in cacheProperties which affect cache canvas to render the cache. And don't use type property, as it is used to search the respective class while loading from JSON.

    DEMO

    fabric.RoomRect = fabric.util.createClass(fabric.Rect, {
      type: 'roomRect',
    
      initialize: function(options) {
        options || (options = {});
    
        this.callSuper('initialize', options);
        // type is either one of HALL, BEDROOM, TOILET, etc.
        this.roomRectType = options.roomRectType || '';
        // name is user defined and also a default name is generated if the name has not been specified
        // the name acts as a unique identifier
        this.name = options.name || '';
        // area is in sq. meters
        this.area = options.area || '';
      },
    
      toObject: function() {
        return fabric.util.object.extend(this.callSuper('toObject'), {
          roomRectType: this.get('roomRectType'),
          name: this.get('name'),
          area: this.get('area')
        });
      },
    
      _render: function(ctx) {
        this.callSuper('_render', ctx);
    
        ctx.font = '1.3em Lato';
        ctx.fillStyle = '#383838';
        ctx.fillText("Type: " + this.roomRectType, -this.width / 2 + 15, -this.height / 2 + 20);
        ctx.fillText("Name: " + this.name, -this.width / 2 + 15, -this.height / 2 + 40);
        ctx.fillText("Area: " + this.area + " sq. m.", -this.width / 2 + 15, -this.height / 2 + 60);
      }
    });
    
    var canvas = new fabric.Canvas('c');
    var roomrect = new fabric.RoomRect({
      left: 10,
      top: 10,
      width: 100,
      height: 100,
      fill: '',
      stroke: 'red'
    });
    canvas.add(roomrect);
    setTimeout(function() {
      roomrect.set({
        roomRectType: 'room',
        name: 'test',
        area: 100,
        dirty: true
      });
      canvas.requestRenderAll();
    }, 2000)
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.2/fabric.js"></script>
    <canvas width=300 height=300 id='c'></canvas>