Include a calendar in one screen

Hello,

I am aware this goes out of React Studio scope but I would like to expand a little bit more my project and add a few things that are not included within RS. A simple example would be an interactive calendar. It seems that “react-calendar” would be a good choice and it seems to be easy to use.

https://www.npmjs.com/package/react-calendar

I am going to experiment and try by myself but I would love some guidance if someone is kind enough to let me know the best approach to include it. So after I install the package and include the following line in the top of the code:

import Calendar from ‘react-calendar’

Where in the “code structure” should I add the code for the calendar that I can read in the documentation? Should I use “Script editor” or “Code glance” to do that? And then, which would be the proper “place” to write the code for the calendar?

Thank you!

Hi,

Your question comes at a good time. We are preparing a series of articles and videos teaching people how to wrap a React component from an npm package.

If you want, you (and anyone else reading this) can be a beta tester to help us make sure the article answers all necessary questions. We are still working on the video.

Please be sure to tell us how this tutorial worked for you, and what we can do to make it better.

We will upload the finished demo plugin to the plugin store so you can compare your plugin with ours if you need to debug.

Best regards,
Adam from Neonto

===

Wrapping components from npm in React Studio

One of the best features of Neonto’s React Studio is the ability to make a plugin out of a component from an npm library.

By doing this you have access to a wide variety of high-quality code for just about any feature you can think of.

Most of the time it is on the very easy end of the coding scale. In this tutorial we will create a bar chart plugin by wrapping the bar chart component from the popular Recharts library.

The anatomy of a plugin

The plugin is a bundle of files with the extension .plugin. The file structure looks like this:
Contents
├── Info.plist
├── Executables
│ ├── Main.js
│ └── studio-ui.js*
└── Resources
├── js
│ └── mustache.js
└── logo_raw.png*
The files with asterisks (*) are optional.

Info.plist is a holdover from Neonto’s origins on the Mac. It contains several values stored in Apple’s XML plist format. The only 2 you need to worry about are CFBundleName (the name of your plugin) and CFBundleIndentifier (a unique ID).

mustache.js is also unused (in React Studio). It is there because Neonto’s plugin format is language and framework independent, and a single plugin can contain code for several targets. For example in Native Studio (another Neonto product) plugins use mustache for iOS and Android code generation. React Studio uses template literals for inline templating instead.

Main.js is where all the action takes place. The entire plugin can be written in it. Or… just like any other Javascript program, you can separate parts of the program out into other files for readability. In the example above this has been done with the code for the InspectorUI.

Let’s get down to writing code

Whether using just Main.js, or separating the code into multiple files, there are 5 main sections in a React Studio plugin:

  1. Plugin description
  2. Inspector UI
  3. Preview
  4. Custom interactions
  5. Code generation
    For our example, we will put everything into Main.js.

Plugin description

Every plugin should start with its name, a brief description, and a default name to be used for it when it is dragged onto the canvas. Let’s name ours Recharts Bar Chart. We will also add a description, default name.

Finally, we will tell React Studio that this plugin is of type element (for more info on types read here).

Start your Main.js with this code snippet:

// -- plugin info requested by host app --
this.describePlugin = function(id, lang) {
  switch (id) {
    case 'displayName':
      return "Recharts Bar Chart";

case 'shortDisplayText':
  return "Create bar charts using recharts npm library";

case 'defaultNameForNewInstance':
  return "rechartsBar";
  }
}

this.__pluginHostId = "com.neonto.studio.element";

Inspector UI

The inspector UI is the rightmost pane in React Studio, and it is where you expose the “knobs” that you want to let the user “turn” on your plugin. We are going to use only 4 of the 15 different types of controls available (read this for information on all of them):

datasheet-picker

Will be used to select the datasheet that holds the data to be charted

textinput

Will be used twice. The first time to enter the name of the datasheet column that contains the labels for the x-axis, the second to enter the name of the datasheet column that contains the values for the bars (y-axis).

color-picker

Will be used to pick the color of the bars

label

Will be used to create some descriptive lies to help the user.
Add the following code to your Main.js:

// -- inspector UI --

this.inspectorUIDefinition = [
  {
    "type": "label",
    "text": "Choose a data sheet that provides the data to display:"
  },

  {
    "type": "datasheet-picker",
    "id": "linkedDataSheet",
    "actionBinding": "this._onUIChange"
  },

  {
    "type": "label",
    "text": "Use this column from data sheet for bar names/categories:"
  },

  {
    "type": "textinput",
    "id": "linkedBarNames",
    "actionBinding": "this._onUIChange",
  },
 
  {
    "type": "label",
    "text": "Use this column from data sheet for bar values:"
  },

  {
    "type": "textinput",
    "id": "linkedBarValues",
    "actionBinding": "this._onUIChange",
  },

  {
    "paddingTop": 20,
    "type": "label",
    "text": "Following settings affect the graph's look:"
  },
 
  {
    "paddingTop": 20,
    "type": "label",
    "text": "Colors:"
  },

  {
    "type": "color-picker",
    "id": "baseColor",
    "actionBinding": "this._onUIChange",
    "label": "Column color"
  },
];

The color-picker returns a color array that we need to convert to HTML rgba.

// utility function to write HTML colors
this._rgbaFromColorArray = function(c) {
  return `rgba(${255*c[0]}, ${255*c[1]}, ${255*c[2]}, ${c[3]})`;
}

If you wish to set any default values you can do so using this entry point:

// -- private variables --
// these are any default values we wish to set
this._data = {
  linkedDataSheet: "",
  linkedBarNames: "country",
  linkedBarValues: "value",
};

// -- persistence, i.e. saving and loading --

this.persist = function() {
  return this._data;
}

this.unpersist = function(data) {
  this._data = data;
}

Now we need to add some code that will take these controls and draw them in the Inspector pane of React Studio. This code is always the same for all your plugins. There is no need to change it. It is commented to explain what each entry point does.

this._accessorForDataKey = function(key) {
  // This method creates unique keys for each of the controls above 
  // Both onCreateUI and onUIChange (see below) will call this method.
  
  var accessorsByControlType = {
    'textinput': 'text',
    'checkbox': 'checked',
    'numberinput': 'numberValue',
    'multibutton': 'numberValue',
    'color-picker': 'rgbaArrayValue',
    'element-picker': 'elementId',
    'screen-picker': 'screenName',
    'dataslot-picker': 'dataSlotName',
    'datasheet-picker': 'dataSheetName'
  }
  var accessorsByControlId = {};
  for (var control of this.inspectorUIDefinition) {
    var prop = accessorsByControlType[control.type];
    if (prop && control.id)
      accessorsByControlId[control.id] = prop;
  }
  return accessorsByControlId[key];
}

this.onCreateUI = function() {
  // Bind values in this._data (see below) to UI automatically using "_accessorForDataKey" above.
  var ui = this.getUI();
  for (var controlId in this._data) {
    var prop = this._accessorForDataKey(controlId);
    if (prop) ui.getChildById(controlId)[prop] = this._data[controlId];
  }
}

this._onUIChange = function(controlId) {
  // This will take the user entered values and bind them using "_accessorForDataKey".
  var ui = this.getUI();
  var prop = this._accessorForDataKey(controlId);
  if (prop) {
    console.log("updated: "+controlId);
    this._data[controlId] = ui.getChildById(controlId)[prop];
  } else {
    console.log("** no data property found for controlId "+controlId);
  }
}

Preview

We will skip over creating a preview for this tutorial. The plugin will work fine without it. We will cover preview generation in future tutorials.

Custom interactions

This section would be used if we wanted to make some functionality in our plugin available to other elements in React Studio. For example a camera plugin would expose a “shoot” method that could be accessed as an Interaction by a Button element.

Code generation

Now the fun part: code generation.
Let’s tell the React Studio design compiler that we are wrapping an existing component.

this.writesCustomReactWebComponent = false;

Because we declared this to be false, the design compiler will look for the next two entry points, this.getReactWebRenderMethodSetupCode and this.getReactWebJSXCode.

this.getReactWebRenderMethodSetupCode will run once per render.

But before we get to that, we must first tell React Studio which npm package we want to use, and which components to import from it.

If you go to the recharts simple bar chart code sandbox you will see this:

We will simplify it just a bit by removing the CartesianGrid, Tooltip, and Legend components.

// -- code generation, React web --

// what react library(s) do we want?
this.getReactWebPackages = function() {
  return {
    "recharts": "^2.0.9"
  };
}

// what React components do we want?
this.getReactWebImports = function(exporter) {
    var arr = [
    { varName: "{ ResponsiveContainer, BarChart, Bar, XAxis, YAxis}", path: "recharts" }
  ];

`    return arr;`
} 

The most important thing to be aware of is that this entry point is implemented as a function, so we have to consider two “scopes of execution”: the first execution is happening at “compile time” when the design compiler reads the “outer” Javascript that culminates in the evaluation of the template literal in the return statement. The second execution is when the code generated by the template literal is evaluated at runtime in the users browser.

// boilerplate code for wrappers, see 
// https://docs.neonto.com/plugins/apiref/element.html#specific-to-reactjs-target
this.writesCustomReactWebComponent = false;


// This generated code will be called once per every render
this.getReactWebRenderMethodSetupCode = function(exporter, elementName) {
  
  // grab the contents of the datasheet pointed at in this.inspectorUIDefinition
  var dataSheetCode = exporter.valueAccessForDataSheetByName(this._data.linkedDataSheet);
  
  // create the NAME of a variable dynamically 
  // it is based on the the name we gave the chart plugin when we dropped it on the canvas
  const sheetVarName = `sheet_${elementName}`;
  
  // stuff that name into property of the parent object 
  // this is so we can access it from within getReactWebJSXCode()
  this._reactRenderDataVarName = sheetVarName;
  
  // this is the template literal that will execute in the runtime environment. 
  // we create an array out of the items oject within the datasheet json object
  return `const ${sheetVarName} = ${dataSheetCode}.items;`
 }

Now we want to use this data, and also the constants entered into the Inspector UI, as props for the recharts barChart component. Again, note that we are writing some Javascript (var jsx)that allows us to evaluate a template literal which is what is returned by the sun to the diagnosis compiler for execution in the user’s browser at runtime.

Going back to the recharts code sandbox we see:

Because we removed the CartesianGrid, Tooltip, and Legend imports, we must also remove the components from the return statement. We will also remove the margin property of the BarChart component just to clean things up a bit. We can set margins in react Studio.

this.getReactWebJSXCode = function(exporter) {
  // The next 4 lines prepare text to be inserted into the template literal 
  // (for execution at runtime)
  
  //grab the NAME of the variable that contains the data 
  var chartData = this._reactRenderDataVarName
  
  // grab the actual values the have been entered into the Inspecter UI
  var labels = this._data.linkedBarNames  
  var bars = this._data.linkedBarValues;
  var color = this._rgbaFromColorArray(this._data.baseColor);

  // prepare the JSX code by inserting the above values into the template literal
  // this forms the main body of the render(return()) for the plugin/component
  var jsx = 
`
<ResponsiveContainer width="100%" height="100%"> 
    <BarChart data={${chartData}}>
        <XAxis dataKey="${labels}" axisLine={false} tickLine={0} />
        <YAxis axisLine = {false} tickLine={0} />
        <Bar dataKey="${bars}" fill="${color}" />
    </BarChart>
</ResponsiveContainer>
`;
  return jsx;
}

The last thing we need to do is to tell the design compiler what our default size is, and if that can be over-ridden by the layout controls of react Studio.

this.defaultContentSizeInWebPixels = [500, 300];
this.hasFixedContentAspectRatio = false;

Your plugin is now ready to use.

Using your plugin

Create a datasheet and choose ”Add mock data -> Countries” and then ”Add mock data -> Random numbers (range 0 to 100)”. Then delete most of the rows (so that the chart is not over populated).

Now drag your plugin onto the canvas of your Start screen, and choose the datasheet you created from the datasheet picker. If you added the mock data as described above, you can leave the default common names as they are. Pick a nice color for the bars in the car chart.

Now click “Open in Web Browser”, and you will have a nice bar chart to look at.

1 Like

Instead of uploading the plugin to the store, please a link to it here.

https://www.dropbox.com/s/o0tzlalvftulyua/rechartsTut.plugin.zip?dl=1

Adam

Thank you so much! That is a lot of information to digest for me. I am going to read it and try to implement some basic calendar usign the Recharts example. I believe that gives a lot of potential to include extra content!

Thank you so much and I will come back here with my progress.

Adam, this is really cool. Helps a lot!