Create a Skuid Custom Component

It’s true: we think Skuid’s standard components are pretty handy just by themselves. But another one of Skuid’s superpowers lies with you, the developers, and how you extend Skuid by creating new custom components.

In contrast to snippets—which can be used to do various useful DOM or data manipulations—you’ll likely be creating custom components to display, edit, and transform data in very specific ways. As such, you’ll need to harness Skuid’s Javascript API to manipulate model data and craft powerful Skuid custom components.

For this custom component construction overview, we will be covering the basics of several API calls. Be sure to refer to their appropriate pages within our API reference guide for further information.

Another key difference between snippets and custom components is that, as opposed to creating an in-line resource to store your component code, it is best practice to create and upload a Skuid component pack containing your component code. We will cover the process of building your own component packs in a different topic, and we recommend learning the basics of building components first. In short, don’t panic—we’ve got you covered.

For the purpose of this overview, we’ll walk through the essential concepts you’ll need to know for every component you build, as well as the essential pieces of code you’ll need to create a custom component that meets your needs.

You can follow along with this conceptual overview by uploading and using this “firstcustom” component pack. You’ll also find the files described below inside the .zip file.

Warning

If you are following along using the firstcustom component pack, be sure to upload it using the method described in the Manage and Upload Component Packs tutorial.

If you add the component pack using the New Component Pack button, Skuid will create a sample component pack which appears to be the same, but contains different code without this tutorial’s comments.

If you find your example component is acting differently than described here, check your static resources and Component Packs tab.

Essential concepts when building custom components

Warning

When working on custom components, some of the terminology you’ve encountered during declarative Skuid Page development takes a new meaning to reflect its role in the API reference. Pay close attention!

As a Skuid developer, you’ve moved into a new realm where components are no longer simply “components.” When you look at a Skuid page at runtime or in the App Composer, there are actually two (or three) JavaScript objects that work together to display Skuid components:

  • A skuid.componentType object: this stores a map of registered component types, such as a table component, a field editor, or a custom component.
  • A skuid.component.Component object: the JavaScript object that points to a specific type of component on the componentType map and creates an instance of that component type based on the XML construction of your page (how you’ve declaratively built your page in the App Composer)
  • A skuid.builder.core.Builder object: An optional JavaScript object used in the App Composer that creates a visual representation of a component’s runtime form. This object allows Skuid users to visually tweak a component’s properties, which in turn writes the proper XML to the Skuid page.

Why are things constructed this way?

When you build custom components, you’re actually building a type (or “class”) of component, which you must register inside the Skuid componentType object. Think of the componentType object as similar to a library, where instances of components check out the code for a specific type of component. This is why, when you write code for your custom component, you must encase it in a JavaScript function that registers it with the componentType object.

This also explains why there is a separate skuid.component.Component JavaScript object: by referencing a registered componentType that exists within the componentTypes object, the skuid.component.Component object allows Skuid to create multiple instances of that component type on the page. This means your skuid.component.Component object handles other things, such as conditional rendering and errors. (Since skuid.component.Component objects are generated at runtime, we will not cover their intricacies and how they relate to custom component construction in this overview.)

Using optional Builder objects

A builder’s main purpose is to allow Skuid users to easily edit the XML of a component node through the visual representation of a component. The builder code creates the draggable item you see in your component list, and putting it on a page generates an XML node that references a registered component type. While they are optional—you can use Skuid’s Custom component to generate an XML node for any component type—if you want to add any user-configurable attributes to a component, your custom component must have builder.

Builder code can be simple or complex. You may want to approximate a component’s appearance; display sample data; allow your users to tweak the elements of the component that are displayed (fields, filters, etc.); or any combination of the above. Anything you see on the App Composer canvas or in the component properties pane is coded within the builder.

In that way, a builder is an entirely separate component—it does not pull data from your registered component type to assemble itself; instead, the builder file contains unique code to create that visual representation and then simply references the component type by its ID.

Here’s an example: when you drag a Table component onto the canvas, you’re creating an XML node that references a component type called “skootable,” and you’ll now see a representation of a table that you can edit in the canvas.

The changes you make to that representation are saved to the XML, all thanks to the skootable builder, which also provides …

  • The ID, icon, and description of the component, as displayed within the Components sidebar and component properties pane
  • The DOM elements that allow users to preview how components look in a page layout
  • The individual variables and input fields that users can configure within the component properties pane

In a nutshell, builders enable users to easily edit XML without even realizing it. While they are not mandatory, they can be a powerful piece of a custom component that requires as much attention as the component code itself.

We admit it: this may seem confusing, but these terms—with these specific definitions—are consistently used when working with Skuid’s Javascript API and building Skuid custom components. Just remember: if things become unclear, refer back to these definitions to get re-oriented to the terminology used with custom component development.

Got it? Great. Now let’s build your first component.

Assemble component’s runtime code using JavaScript

As noted above, you’ll be writing your custom component’s code in JavaScript and registering it as a new type of component. The key here is that you’ll do both of these things by encasing your code within the skuid.componentType.register() function. For example, note the JavaScript in line 1, below.

1
2
3
4
skuid.componentType.register("componentpackid_componentid", function (element,xmlDef){
// If you run this on a Skuid page, you'll have technically created a custom component
// that does nothing, yet exists. Talk about a philosophical crisis. Congrats!
});

You’ll notice that this function has two parameters:

  1. The ID of your custom component, known as typeName.
    1. It is best practice to prepend your component IDs with their component pack IDs in order to avoid any component ID conflicts. For example, if you are building a component called “sayhello” within a component pack called “firstcustom,” then you would make your component id “firstcustom__sayhello.”

Warning

Component IDs must be entirely lower case.

  1. The custom component code, known as typeDefinition. It is written as another function with its own parameters. For this introduction, you’ll only need to know the basics of two:
    • Element: the actual DOM element created at runtime
    • xmlDef: the user configurable attributes that will correlate with our builder

If you want a more detailed breakdown of this function, feel free to hop over to our API reference guide.

Assembly

So here’s where you get creative: write JavaScript and use the Skuid JavaScript API reference to identify the API calls that will perform the functions you want for your custom component.

Note

It is often useful to test your code in your browser’s console.

Let’s look at an example of a basic, but fully functioning custom component’s runtime code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(function(skuid){
  skuid.componentType.register("firstcustom__sayhello",function(element,xmlDef){
    // We are setting the HTML content of our component's DOM element
    // to be the phrase "Hello " and adding one user configurable attribute:
    // the name of a person that is specified in the XML Definition
    // of our component through a builder. Then we wrap it all in bold.
    // Without a builder, this will simply display “Hello undefined”
    element.html("<b>Hello " + xmlDef.attr("person") + "</b>");
  });
})(skuid);

While this is a simple example, it—in combination with the builder we’ll be creating below—demonstrates how your element and xmlDef parameters work together to create a configurable custom component.

When you’re finished with your runtime code, save it as runtime.js.

Assemble your component’s builder in JavaScript (optional)

Once you have written your component’s runtime code—and wrapped it in the skuid.componentType.register() function—you could consider your work finished. If you used the example above, you could simply drop Skuid’s Custom component into your page and have it point to the firstcustom__sayhello component type. However, since you have an extra XML definition that is not defined in your runtime code, you’ll have a component that says

“Hello undefined”

And that is the beauty of creating builders for your components—users can customize those XML definitions within the App Composer. So let’s see how we can make our basic component a little more friendly.

Creating builders and registering is a very similar to creating and registering component types:

  • You’ll encase your code in the skuid.builder.core.registerBuilder() function to register it within Skuid so Skuid can see it.
  • You assemble the builder itself by encasing its code in the new skuid.builder.core.Builder() constructor.

From there, you’ll be commonly use the following functions from the Skuid JavaScript API:

  • componentRenderer: This function builds the component’s preview on your page canvas.
  • propertiesRenderer: This function builds the properties pane users see when clicking on the component.
  • defaultStateGenerator: This function will create the default XML attributes for the component when it’s dropped into the page. While it’s important to note this function is not needed to initialize XML attributes (they will default to undefined), it is used to make life easier for the user.
    • One common use case: Many standard Skuid components will default to the last “clicked” model—this is set within the defaultStateGenerator function.

We’ll add one degree of flexibility to our component: the name of the person to say hello to. By writing a builder, we’ll be writing the code that allows the component to show up in our App Composer, as well as the code allowing for at least one customizable attribute to exist in the component’s properties pane.

So what would a relatively simple builder look like? Here’s some commented code for our firstcustom__sayhello builder!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
(function(skuid) {
  // First, we'll register the component using skuid.builder.core.registerBuilder
  // This function—as its name implies—will register a builder for the function you are creating.
  // It uses the "new skuid.builder.core.Builder" constructor as its parameter.
  // By doing this, we are both creating and registering a new component in this one section of code.
  skuid.builder.core.registerBuilder(new skuid.builder.core.Builder({
    // Within our Builder constructor, we'll define some basic, self-explanatory parameters.
    id: "firstcustom__sayhello",
    name: "Say Hello",
    icon: "sk-icon-contact",
    description: "This component says Hello to someone",
    // Next, you'll define your componentRenderer.
    // This section of code displays within the canvas of the App Composer.
    // You can use it to create a visual representation of your component with any level of accuracy.
    // More advanced components, such as tables, will utilize buttons
    // and other elements, allowing users to intuitively fine-tune their XML.
    // For this basic component, we'll simply show how the component will appear on the page.
    componentRenderer: function(component) {
      component.setTitle(component.builder.name); // Instead of having a long, specific ID, we'll use the more friendly component name.
      component.body.html(
        "Hello " + component.state.attr("person") + "!</div>"
      );
    },
    // The propertiesRenderer determines how the component's properties
    // will be displayed within the Properties pane of the App Composer.
    propertiesRenderer: function (propertiesObj,component) {
      propertiesObj.setTitle("Say Hello Component Properties");
      var state = component.state;
      // propCategories are essentially the tabs you see in the Properties pane
      // They are named as they are to indicate that you should group your properties
      // by their category of usage. For example, leaving optional "Advanced" properties
      // in a separate category/tab.
      // We'll create the variable here so we can push them to the properties pane later.
      var propCategories = [];
      // The propsList is an array of properties that relate to
      // the component.state.attr as seen in your componentRenderer above
      // and the xmlDef.attr you see in your runtime code.
      // This where you'll write the editable fields for your properties pane.
      var propsList = [
        {
          id: "person",
          type: "string",
          label: "Person to say Hello to",
          helptext: "Pick a name, any name!",
          onChange: function(){
          component.refresh();
          }
        }
      ];
      // This will push our first property category to the properties pane
      // As you can see, this is where you'll actually be naming the propCategories
      // from the variable you declared earlier.
      // Note: If you only want to have one property category, you can leave its name blank.
      propCategories.push({
        name: "Basic",
        props: propsList,
      });
      // To illustrate how to have multiple property categories,
      // we'll create an "Advanced" category that allows users to set
      // 1. A unique ID, useful differentiating multiple instances of a component.
      // 2. The CSS class of the component instance
      // Skuid has boilerplate code representing these properties, so we'll insert them
      // instead of writing it all out ourselves.
      // As opposed to storing these in a variable, we'll just write them in as an array.
      propCategories.push({
        name : 'Advanced',
        props : [skuid.builder.core.coreProps.uniqueIdProp({component:component}),skuid.builder.desktop.cssClassProp]
      })
      propertiesObj.applyPropsWithCategories(propCategories,state);
    },
    // Your defaultStateGenerator, as its name implies, creates
    // the XML definitions when your component is first dragged
    // on to the App Composer's canvas.
    // For this example, let's set default person to "User."
    defaultStateGenerator: function() {
      return skuid.utils.makeXMLDoc("<firstcustom__sayhello person='User' />");
    }
  }));
})(skuid);

While this is a basic example, each function within a builder can be as involved or as simple as suits your needs. Here we have constructed a very simple component, but—as shown by Skuid’s extensive and powerful components—your imagination is the limit.

When you’re finished coding your builder, save it as builders.js.

Be sure to explore the skuid.builder.core API, as well as other API calls, to learn more about the key element behind Skuid builders.

Styling your components with CSS

In addition to coding the behavior of your component using Javascript, you can style both your runtime component and your builder using CSS. While this may be as complex or simple as you desire, let’s keep it easy for this basic component.

1
2
3
4
.hello-content {
  color: black;
  font-size: 18pt;
}

While this CSS will work for both our runtime and our builder component styling, do note that you can use different CSS for each, or choose to style only one or the other. Simply save and title your files accordingly.

It is best practice to make sure the names of your .js and .css files match up—particularly for when you will be referencing them within a component pack manifest. As our .js files are named runtime.js and builders.js, save the above CSS into two files named runtime.css and builders.css.

Using your custom components

Whether you’ve followed along by editing the code examples above in your IDE of choice, or if you’ve downloaded the example component pack file linked at the beginning of this topic, to use custom components you must archive them into a component pack.

Component packs, and the files within them, are a topic unto themselves, and you can learn more about how to build them in this tutorial.

More Resources and Projects

Now that we’ve covered how to create your first component, try using and analyzing some more complex projects:

And as always, feel free to ask questions on community.skuid.com.