Reusable Components

Creating and using reusable components are the key element between a successful project and being unable to minimize the effort through the lifetime of project. You can find examples of reusable component even outside of the Software World. Fields like Sales, Animation, Graphics Design or even Film Industry create and re-use some of their actions to ease their workflow.

When You Will Need Components

  • When you are designing ListViewItem and GridViewItem. These views use RecyclerView, therefore it is required to use reusable components.

  • When you create SliderDrawer. Since it is a global view and acts as a main navigator through the project, creating a component for each section would help to ease out the process.

  • When you have a design pattern like Cards, Profiles or any potentially repeatable UI/UX action, it is strongly recommended to create a component, then to create the same thing over and over again.

If your design requires to have a different color than default palette, make sure to define them on a class rather than creating a separate component.

More info about using UI Editor and Classes can be found below:

How To Create and Use Components

Simply head into the Library tab on the Smartface WHSIWYG editor.

Create And Use Components from UI Editor

  • From the Project Explorer, open the Library pane.

  • Drag a FlexLayout from the toolbox to the Library. This layout represents your first library component.

  • Double click on the FlexLayout that you just moved. This will enable you to insert other components inside your layout.

  • Drag a Button inside your layout, then save your changes.

  • There you have it! You can use this library component across all of your pages.

  • Now drag flexlayout1 (which is your library component), from the library pane to your page.

  • Don't forget that a library component update will be reflected to all pages using this component. This is the primary advantage of using a library.

  • As an example, change the background color of your button of flexlayout1

Adding Code to the Created Component

Only using UI will not be enough if you want to set things programmatically or want anything dynamic. In that case, you should wrap your code and logic inside of the component file. When you create a component from Smartface UI Editor, Smartface UI Editor will create a file with the component name for you.

This is how the default component page looks like:

scripts/components/FlexLayout1.ts
import FlexLayout1Design from 'generated/my-components/FlexLayout1';
export default class FlexLayout1 extends FlexLayout1Design {
pageName?: string | undefined;
constructor(props?: any, pageName?: string) {
// Initalizes super class for this scope
super(props);
this.pageName = pageName;
this.button1.text = "Smartace"; // This was added from the Smartface UI Editor
}
}

You can add functions, properties or anything you wish inside of the component. You can check out an example in this section.

To use Drag&Drop elements in code, please refer to this document:

Create&Use Components From Code

This method is not recommended unless you have a strictly specific action to take.

If you want to create a component in runtime and add it to your page, you should emulate the same steps that UI Editor does for you.

You need to create the file like scripts/components/FlexLayout.ts to keep your code clean.

Component Code
Page Code
Component Code
scripts/components/FlexLayout1.ts
import FlexLayout from '@smartface/native/ui/flexlayout';
import Button from '@smartface/native/ui/button';
/**
* Since this file wasn't created from Smartface UI Editor,
* you should manually extend the Smartface Component yourself.
*/
export default class FlexLayout1 extends FlexLayout {
pageName?: string | undefined;
button1: Button; // You can set this to private if you don't want it to be accssed.
constructor(props?: any, pageName?: string) {
super(props);
this.pageName = pageName;
this.top = 30; // Direct assignment is fine because this is constructor of the component.
this.height = 20;
// Add another view like Button or whatever you want to.
this.button1 = new Button();
this.addChild(this.button1, 'button1', '.sf-button');
}
changeStyle() {
// 3rd parameter is optional and is used to give class name
// 4th parameter is optional and is used to give user properties
this.dispatch(myFlexlayout, "myFlexlayout", ".sf-flexlayout", {
top: 60,
height: 10,
flexProps: {
alignSelf: "CENTER",
positionType: "ABSOLUTE",
}
});
this.applyLayout();
}
}
Page Code
scripts/pages/page1.ts
import Page1Design from 'generated/pages/page1';
import FlexLayout1 from 'components/FlexLayout1';
export default class Page1 extends Page1Design {
flexLayout1: FlexLayout1;
constructor() {
super();
this.onShow = onShow.bind(this, this.onShow.bind(this));
this.onLoad = onLoad.bind(this, this.onLoad.bind(this));
}
initFlexLayout() {
const flexlayout1 = new FlexLayout1();
// 2nd parameter is to assign an ID for Context
// 3rd parameter is optional and is used to give class name
this.addChild(flexlayout1, "flexlayout1", ".sf-flexlayout");
this.flexLayout1 = flexLayout1;
this.flexLayout1.button1.text = "Smartface";
this.layout.applyLayout();
}
}
function onShow(superOnShow: () => void) {
superOnShow();
this.flexLayout1.changeStyle(); // Change style on runtime
}
function onLoad(superOnLoad: () => void) {
superOnLoad();
this.initFlexLayout();
}

To learn more about what dispatch and other parameters are, refer to the document below:

To learn more about what onLoad or onShow means, refer to the document below:

If you are going to add another view inside of your component, you should do it in component file itself, not the page file.

Tips & Tricks about Reusable Components

One of the main purpose of creating reusable component is to use it inside of ListView or GridView. Kindly refer to the document below to learn more:

On Smartface, all UI components inherit the properties one of the following parent components:

View Component

If a component is derived from View, it means the component cannot have child components. E.g. a Button is a component which inherits properties of View, therefore you cannot add child component inside of a Button

ViewGroup Component

If a component is derived from ViewGroup, it means the component is a container component and can receive child component. E.g. a FlexLayout can have child components inside of it.

ListViewItem and GridViewItem are components which are derived from ViewGroup, however they are components with a different purpose, therefore they are partially exempt from this practice. Continue reading to learn the best practice on how to use them.

How to Specify Where a Component Belongs

Method 1

Check the hierarchy specified at API Documentation of the component. It is located on the right side of the component page. Example for button:

Method 2

You can also specify which class the UI component inherits by using the power of Typescript.

  • Type the name of the desired component to any scope

  • Let the Typescript Language Features auto-import the class for you

  • Hover the component by pressing down ctrlcmd (ctrl on Windows/Linux, cmd on Mac)

  • The declaration of the component will be shown.

Alternatively, you can directly go into the module in node_modules and directly delve into the d.ts of the component itself.

Using View Based Components as Library Component

When a component is created & reused, the newly reused component will share all of the state the library component has at that time. The parent component will be duplicated and child components will share the same reference as the Library Component.

Therefore, when the developer wants to change something in the component, the following changes will not be applied to the parent of the reused component.

  • User properties of the parent component

  • Classes of the parent component (the changes in the class itself will be applied)

Every change which are made in the child components will be applied to every reused component.

Example Case

Let's say we need to have a Button red colored and rounded. It should also have an extended functionality(e.g prompting a dialog on press)

❌ Don't

Creating a View based Class like this is a bad practice. As explained above, if you need to change about this library class, since the properties will be duplicated instead of being referenced, the other places which use this component will not receive the changes.

✅ Do

Wrap the component with a FlexLayout instead and use FlexGrow to expand your component to your needs. This way, your changes on the Button will be applied seamlessly. You can also apply extra properties to the parent component if it is needed.

💭 If you Only Need to Change Styles

Simply create a class for your button and use them with normal button. For more information about classes, refer to the document below:

Nested Components

You can use library components inside other library components to maximize reusability.

Best Practice for ListViewItem and GridViewItem

The created ListViewItem and GridViewItem components can not be used outside of their respective ListView or GridView. Therefore, when you are implementing them, instead of having your components like this,

Wrap them in a FlexLayout and nest them within the ListViewItem or GridViewItem like this:

Together with organized way to collect the components, this has two main benefits:

  • Since ListViewItem and GridViewItem are special components, when supporting multiple themes they might not be updated property during the theme switch. However, underlying components will not have this problem.

  • If you want to use the same component in e.g. ScrollView, you don't need to redesign it anymore, you can simply use the inner component directly.

The caveat is, when writing component code you have to define functions two times for both FlexLayout and ListViewItem code. You can achieve this by calling the underlying component function in the parent function. E.g. if we want to change the text of the label:

FlexLayout Code
ListViewItem Code
FlexLayout Code
scripts/components/FlMyItem.ts
import FlMyItemDesign from 'generated/my-components/FlMyItem';
export default class FlMyItem extends FlMyItemDesign {
pageName?: string | undefined;
constructor(props?: any, pageName?: string) {
// Initalizes super class for this scope
super(props);
this.pageName = pageName;
}
setLabelText(text: string): void {
this.label1.text = text;
}
}
ListViewItem Code
scripts/pages/LviMyItem.ts
import LviMyItemDesign from 'generated/my-components/LviMyItem';
export default class LviMyItem extends LviMyItemDesign {
pageName?: string | undefined;
constructor(props?: any, pageName?: string) {
// Initalizes super class for this scope
super(props);
this.pageName = pageName;
}
setLabelText(text: string) {
this.flMyItem.setLabelText(text);
}
}

Now, you can use the same function in both situations.

Type-Safe Approach

The example above serves the purpose but it isn't an ideal solution if you want your functions to be type-safe and use a model-like approach. You can create a file like myItem.d.ts and declare your interface there.

It is better to gather those file in a single folder. A good folder name can be models.

scripts/models/myItem.d.ts
declare interface IMyItem {
setLabelText(text: string): void;
setImageURL(URL: string): void;
}

After that, we can implement our new IMyItem interface like this:

FlexLayout Code
ListViewItem Code
FlexLayout Code
scripts/components/FlMyItem.ts
import FlMyItemDesign from 'generated/my-components/FlMyItem';
export default class FlMyItem extends FlMyItemDesign implements IMyItem {
pageName?: string | undefined;
constructor(props?: any, pageName?: string) {
// Initalizes super class for this scope
super(props);
this.pageName = pageName;
}
setLabelText(text: string): void {
this.label1.text = text;
}
}
ListViewItem Code
scripts/pages/LviMyItem.ts
import LviMyItemDesign from 'generated/my-components/LviMyItem';
export default class LviMyItem extends LviMyItemDesign implements IMyItem {
pageName?: string | undefined;
constructor(props?: any, pageName?: string) {
// Initalizes super class for this scope
super(props);
this.pageName = pageName;
}
setLabelText(text: string) {
this.flMyItem.setLabelText(text);
}
}

After we implement this, the Typescript compiler will raise error because we didn't implement our new function yet. Simply use Quick Fix.. method to add missing functions.

If you need to have custom types as typing parameters, you need to export the interface and import in both LviMyItem.ts and FlMyItem.tsfiles.

You can find the best practices about declaration on DefinitelyTyped Best Practices.