Search code examples
javascriptapostrophe-cms

Can Custom Schema Field Types Run Code After Render?


I'm attempting to add a new schema type to my Apostrophe-CMS project. I'm essentially trying to use Fabric.js to add an image designer as part of the pieces editor modal. I have a piece type that stores the JSON generated by Fabric.js as a field, and want the editor to show up in place of the field (or as the field) when editing the piece itself.

I have this partially working (following the guide at https://docs.apostrophecms.org/howtos/custom-schema-field-types.html). However, I'm running into an issue. The populate method of addFieldType seems to get called before the actual fieldset is rendered on the page. This would normally be fine since most fields have basic (if any) logic applied to them at render time, but in this case Fabric.js seems to require the canvas element to be present on the page, otherwise it can't connect to it correctly and fails to load.

I noticed in https://github.com/apostrophecms/apostrophe/blob/b22af9320169bf3af26cd17642373ae1e331a990/lib/modules/apostrophe-schemas/public/js/user.js on line 80 that it seems to imply that there's a way to override afterPopulate in custom field types. I can't seem to get this to work - I've tried adding it similarly to populate (seen below) but it doesn't ever get called (which makes sense looking at line 44 of the same file, since the only thing that gets called at that point on fieldType is populate).

Is there a way to override afterPopulate from a custom field type, or is there some other way I should be handling this? Right now I'm essentially adding a 500ms timeout in populate before initializing Fabric, which does work, but seems incredibly hacky. Here's the code that I've added so far:

apos.define('fabric-editor', {

afterConstruct: function(self) {
  self.addFieldType();
},

construct: function(self, options) {

  self.addFieldType = function() {
    apos.schemas.addFieldType({
      name: 'fabric-editor',
      afterPopulate: self.afterPopulate,
      populate: self.populate,
      convert: self.convert
    });
  };

  self.afterPopulate = function($el, schema, object, callback) {
    callback();
  }

  self.populate = function(object, name, $field, $el, field, callback) {
    var $fieldset = apos.schemas.findFieldset($el, name);
    callback();
    setTimeout(function () {
        var canvas = $fieldset.find('#fabric-canvas');
        if (canvas.length > 0) {
            self.fabricCanvas = new fabric.Canvas(canvas[0]);
        }

        fabric.Object.prototype.transparentCorners = false;
        fabric.Object.prototype.cornerColor = 'blue';
        fabric.Object.prototype.cornerStyle = 'circle';
        self.addRectangle();
    }, 500)
  };

  self.addRectangle = function (ev) {
    var rect = new fabric.Rect({
        left: 10,
        top: 10,
        fill: 'red',
        width: 100,
        height: 100
    });

    self.fabricCanvas.add(rect).renderAll().setActiveObject(rect);
  }
}

});


Solution

  • self.afterPopulate belongs to the apostrophe-schemas module, so you would need to overwrite it in a project-level lib/modules/apostrophe-schemas/public/js/user.js file.

    I don't think that a field's populate function is called before the field is on the page, however, since it passes the form element in as an argument.