Gems: Open Sourcing Gems Modules

Gems Modules were designed to make task interfaces reusable, extendable, and customizable to meet the needs of complex tasks

Today we are open sourcing the first iteration of our Gems Modules and Gems UI Kit. This release includes our format to build task interfaces with Gems Modules, a playground to try it out for yourself, our UI library, and a subset of our component library. For a visual representation, please view our storybook here. To view the source code, please take a look at our github.

Requesters waste incredible amounts of time and resources building their own UI/UX because platforms provide insufficient alternatives for somewhat complicated tasks. Our hopes are that Gems Modules and their continued open source development make it easier for requesters to launch meaningful tasks.

You can read more about the motivation for Gems Modules here.

How it works

Disclaimer: This post requires prerequisite knowledge of JavaScript, React, and common programming practices.

Gems Modules are built with React components. They have an associated JSON allowing them to be configured and imported to applications easily. Here is a basic example of using a Gems Module in a 3rd part React application:

import { Form, Modules } from '@gemsorg/components/modules;

const form = {
modules: [
{
"name": "text",
"type": "text",
"placeholder": "Simple text"
}
]
}

class CustomForm extends Component {
handleSubmit = values => {
console.log(values);
};

render() {
return (
<Form form={form} onSubmit={this.handleSubmit}>
{moduleProps => (
<Module {...moduleProps} />
)}
</Form>
);
}

The above snippet shows a boilerplate implementation of rendering a simple text field. However, the above code can be edited to display any number of modules. To customize, change or add new modules we edit the JSON in the modules array in the form object. Let’s take a look at this JSON more closely:

const form = {
modules: [
{
“name”: “text”,
“type”: “text”,
“placeholder”: “Simple text”
}
]
}

The type key is a required parameter that specifies which component will be rendered. We are using type ‘text’ which corresponds to an input field with type text. The name key is also a required parameter. The name key determines how the output data will look. When a worker completes a task, the data submitted will take the format “name”: “data”. It is important to name components something that makes sense so you know what data was submitted. The placeholder key is a custom field required for the text component. It corresponds to a placeholder text for our input field. What is the output? This renders a simple input field to the screen with the placeholder “Simple text”.

By combining other modules we can make complex tasks.

{
“modules”: [
{
“name”: “description”,
“type”: “description”,
“content”: “Write a trivia question with three multiple-choice answers. One answer should be correct and fact-checked, and two answers should be incorrect.”
},
{
“name”: “question”,
“type”: “text”,
“validation”: [
“isRequired”
],
“placeholder”: “Question”
},
{
“name”: “correctAnswer”,
“type”: “text”,
“validation”: [
“isRequired”
],
“placeholder”: “Correct Answer”
},
{
“name”: “incorrectAnswerA”,
“type”: “text”,
“placeholder”: “Optional Incorrect Answer”
},
{
“name”: “submit”,
“type”: “submit”,
“caption”: “Next”,
“justify”: “center”
}
]
}

This above JSON will display a task that asks a user to create a trivia question and has inputs for the question, answer, and incorrect answers. Each JSON object in the modules array corresponds to a different module that will be added to our interface.

The full list of available modules is available in our storybook.

Playground

The playground is a simple way for you to test building a task interface with our existing modules. On the left hand side is the json format describing which modules should be displayed. By clicking the plus button you can add new modules to the form. On the right hand side is the output that is shown to the user. You can play around with playground more here.

Custom Modules

Let’s go back to the trivia question example. The trivia question example consisted of a form with several input fields; the first field was for the question, the second field was for the correct answer, and the last two fields were for incorrect answers. The requester could have wanted to allow the worker to submit as many incorrect answers as needed for the question; instead of 2 incorrect answers, the worker could submit n number of incorrect answers by adding and removing fields. The requester needs a custom component to add and remove input fields.

For 3rd party applications the easiest way to do this would be to:

  1. Build the Modules React components
  2. Register the new Module through the controls prop
  3. Add their new custom component to the form object

Here is a quick example of a theoretical custom module on a 3rd party application:

import React from 'react';
import { Form, Modules, moduleControls } from '@gemsorg/components/modules;

// your custom component
class MyInputModule extends Component {
static propTypes = {
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
};

handleChange = ({ target }) => {
const { name, onChange } = this.props;
onChange(name, target.value);
};

render() {
const { type, value } = this.props;
return <input type={inputType} onChange={this.handleChange} value={value} />;
}
}

// override input field type with MyInputModule
const controls = {
...moduleControls,
input: MyInputModule,
}
const form = {
modules: [
{
type: 'input'.
name: 'input',
}
]
}
const CustomForm = ({ onSubmit }) => (
<Form form={form} onSubmit={this.handleSubmit}>
{moduleProps => <Module controls={controls} {...moduleProps} />}
</Form>
)

In the above snippet, we define our React component MyInputModule. This is what will be displayed in our form. Below we will register our component as a Module and assign a type. We will give it type ‘input’.

const controls = {
...moduleControls,
input: MyInputModule,
}

Now we can use type ‘input’ to import our Module into any interface.

const form = {
modules: [
{
type: 'input',
name: 'input'
}
]
}

Congrats! We have just built our first 3rd party Module. If you are proud of your module and would like to share, we welcome contributions of new modules to our github repo. This allows all requesters to benefit from past work and promote better task interfaces.

Recap

The module format helps solve the problem of building reusable interfaces that can also be highly customizable, storable and extendible. We currently use the format for task interfaces, verification interfaces, as well as our task on-boarding experience. If you are interested in learning more please check out our github repo, our storybook, or shoot us a message on Telegram.

Gems Modules you say? Come learn more about Gems: