How do I build a Custom Component that can contain Skuid components

  • 2
  • Question
  • Updated 4 years ago
  • Answered
I think I have worked out the basics for creating custom components (which has the potential to make Skuid hugely powerful for the project we are looking at!).
I would like to build a component that allows you to drag a Skuid component into it. What is involved in doing this?
e.g. In it's simplest form, if I wanted to build a component that does something like:
-------------------------------------------------------------------

-------------------------------------------------------------------
then how would I go about building it?
Photo of Ant Belsham

Ant Belsham

  • 1,022 Points 1k badge 2x thumb

Posted 5 years ago

  • 2
Photo of Ant Belsham

Ant Belsham

  • 1,022 Points 1k badge 2x thumb
Sorry, the text in my example didn't show. Lets try again:

-------------------------------------------------------------------------------
Some text generated by custom component here
And a skuid component like a table that is dragged on here
-------------------------------------------------------------------------------
Photo of Zach McElrath

Zach McElrath, Employee

  • 48,984 Points 20k badge 2x thumb
Ant,

There are two things required for this to work:

(1) The Builder for your Custom Component Type must contain a "Component Acceptor", a region into which you can drag other Skuid Components.

(2) The Runtime code for your Custom Component Type must process these "child components" and instantiate them.

We'll tackle (1) first, as it's quite simple. Here's the Builder code for an example component that, as you describe above, has a single Component Acceptor as its body, with some text above it:

//
// sample component BuilderJS
//
(function(skuid) {
var $ = skuid.$;
skuid.builder.core.registerBuilder(new skuid.builder.core.Builder({
  id : 'sample',
  name : 'sample',
  icon : 'ui-silk-tux',
  description : 'Lets you add child components to it',
  hideFromComponentsList : false,
  componentRenderer : function(component) {
    component.setTitle(component.builder.name);
    component.body.css('padding','8px').css('word-wrap','break-word');
    // Add in a child components acceptor area.
    
    // If we do NOT have a child 'components' area,
    // we MUST add it in.
    if (!component.state.children('components').length) {
         component.state.append(
             skuid.utils.makeXMLDoc('<components/>')
          );
    }
    
    var acceptor = new skuid.builder.ComponentAcceptor(
      // The stored XML state
      component.state.children('components')
    );
    component.body.append(acceptor.element);
    
  },
  propertiesRenderer : function(propertiesObj,component) {
    propertiesObj.setTitle('Sample Component Properties');
    
    var propsList = [
      {
        id : 'model',
        type : 'model',
        label : 'Model to Use in this Component',
        required : true
      }
    ];    
propertiesObj.body.append(
skuid.builder.buildPropsEditor(component.state,propsList)
);
  },
  defaultStateGenerator : function() {
    return skuid.utils.makeXMLDoc('<sample/>');
  }
  
}));
})(skuid);


skuid.builder.ComponentAcceptor is the JavaScript object that makes all of the Component Accepting magic happen. It looks over any child components, stored as children of a child node, and renders them. Then, as new components are added, or existing components are removed, it removes them from this XML node. To dynamically instantiate these child Skuid components at runtime, you create 2 things: (1) a very minimal Apex Class for your Component in order to ensure that all child Skuid Components are properly processed --- in particular, so that Custom Labels used to generate these Components will be loaded, Drawer/Popup Actions that are contained in child Skuid core components are properly loaded, etc. (2) a JavaScript component definition.

//
// Apex Class: Sample.cls
//
global with sharing class Sample extends skuid.SkuidponentImpl {
  global override void generate() {       
     Dom.XMLNode components = data.getChildElement('components',null);
      if (components != null) this.append(components.getChildElements());
  }
}

//
// JavaScript:
//

(function(skuid){

  var $ = skuid.$;

  skuid.componentType.register('sample',function(domElement,xmlConfig,component){

// Establish a shorthand for the DOM element we will be building to,
// (actually a jQuery object wrapper around that domElement) 
var self = domElement;
// Get the name of the model we want to work with from our component definition
var modelName = xmlConfig.attr('model');
// Use the Skuid JavaScript API
// to get a reference to the requested Model.
self.model = skuid.model.getModel(modelName);
// Append child components, if we have any
if (xmlConfig.children('components').length) {
// Make a container for our components
var container = $('<div>'),
// Process child components
childElements = [],
childComponents = [];
// Define some context to pass in to our children
var context = {
model: self.model,
component: component
};
xmlConfig.children('components').first().children().each(function(){
// Create a new Component using this XML Definition,
// and passing in some context.
var childComponent = skuid.component.factory({
definition: this,
context: context
});
// Add the component's generated DOM element
// to our list of DOM elements
// to stick inside the Popup
childElements.push(childComponent.element);
childComponents.push(childComponent);
});
container.append(childElements);
// Append our container to ourself
self.append(container);
}

});
})( skuid );
(Edited)
Photo of Ant Belsham

Ant Belsham

  • 1,022 Points 1k badge 2x thumb
Thanks Zach. I'm really impressed with how responsive the Skuid team have been on this forum, and how in depth your answers are. Really starting to get some confidence in Skuid for our application!

I tried this code, and it works perfectly in the builder. However, nothing seems to render when I go into preview mode. Is this the bug you were referring to or is there another issue? Is there a way to get around this? Really keen to see if we can make some of our custom components work directly in Skuid rather than having to bury them is visualforce pages inside iframes!
Photo of Ant Belsham

Ant Belsham

  • 1,022 Points 1k badge 2x thumb
I've just gone back to the developer guide and tried to implement the sayhello component demo using the apex class method to register the component. And I can't get this to work either (it works for me with the javascript method). Is there something that I have to do to run the apex HelloWorld class to register the component?
Photo of Zach McElrath

Zach McElrath, Employee

  • 48,984 Points 20k badge 2x thumb
Hi Ant -- yeah there appear to be bugs with the Apex class method right now. The JavaScript method seems to work, but the only problem is that the "child components" don't get handed the Custom Labels that they need, and so there are some problems with the rendering. This is why you have to have the Apex portion as well, so that the Custom Labels of child components get processed and added in to the page. We're looking at why this isn't working.
Photo of Ant Belsham

Ant Belsham

  • 1,022 Points 1k badge 2x thumb
Thanks Zach. Keen to hear when there is a fix to this as really want to try some custom components directly in Skuid rather than reverting to visualforce!
Photo of Ant Belsham

Ant Belsham

  • 1,022 Points 1k badge 2x thumb
This is working as of 2.48. We have our first drag and drop component built. Thanks!
Photo of Anna Wiersema

Anna Wiersema

  • 10,890 Points 10k badge 2x thumb
That's so exciting! Way to go!
Photo of Dan A

Dan A

  • 314 Points 250 badge 2x thumb
Hi Zack,

I have these examples working, however I cannot find a way to generate the component content or to pass any information to the JS enclosure from the apex class. For instance, this.htmlOutput does not seem to do anything if there is a JS component registered with an Apex class, or where there is only an Apex class.

In my specific use case, and it may be naive, I have a common chunk of Mustached' HTML which I want to store as a document in Salesforce, pull this document in the Apex class that drives a Skuid custom component, and then pass the contents to the javascript implementation of the customer component so I can render to the DOM.

Obviously I could store this chunk of HTML as a string in javascript, however the long term maintainability of this compromise is of concern.

Alternatively I could use an AJAX call to fetch and inject this HTML, however my feeling from a general web development viewpoint is that this is unacceptable given that this HTML could be for critical parts of the page, and without tying up the browser with a synchronous request could result in these chunks loading with a delay which may cause strange shuffling in the DOM. I'd rather have the content rendered when the custom component is rendered, although perhaps there is a fundamental reason with regards the Skuid architecture that I am overlooking.

Any feedback would be greatly appreciated.

Kind Regards,
Dan Arnison
Photo of Glenn Elliott

Glenn Elliott, Champion

  • 7,738 Points 5k badge 2x thumb
To add some extra context, Dan's question here relates to mine from a few days ago here: http://community.skuidify.com/skuid/t...

The approach that Zach outlined there works, but Dan and I are keen if possible to store our reusable chunks of HTML as static resources, for the sake of easy maintenance. What's the simplest way to do that?

Thx.
Photo of Moshe Karmel

Moshe Karmel, Champion

  • 8,646 Points 5k badge 2x thumb
Hey Zach I want to try something a little different here. I would like to be able to create a custom field component that can be dragged into a field editor. Is this something that you can give the ability to do? Is there a parameter in the builder that can make my component field editor acceptable?
Photo of Zach McElrath

Zach McElrath, Employee

  • 48,984 Points 20k badge 2x thumb
Sorry Moshe, no there is not a way to do this yet. You could post it separately as an Idea though, so that others could vote it up, as I've talked to others who would like to do something similar to this as well.