Entry points specific to Element plugins

Interactions

publishedInteractActions

An array that describes the actions that this element can perform. Example from the Camera plugin:

this.publishedInteractActions = [
  {
    "id": "shoot",
    "displayName": "Shoot",
    "shortDisplayText": "Take a picture.",
    "isAsync": true
  }, {
    "id": "switch",
    "displayName": "Switch",
    "shortDisplayText": "Switch to front/back facing camera."
  }
];

implementsCustomTouchInteractions

Must be a boolean value to indicate whether the element has custom touch interactions.

Rendering previews

renderEditingCanvasPreview(canvas, controller)

Called to render a visual preview of the plugin element in the host application's editing area.

The canvas parameter is a Canvas object similar to the HTML5 Canvas API. See renderIcon() discussion in Common entry points.

The controller parameter is a Studio-specific object that contains values and methods related to the preview's rendering context. Currently the only relevant value available is controller.renderPixelsPerWebPixel which gives you a scale factor for the display. E.g. Apple's Retina displays will return a value of 2.0 for this.

One easy way to use this value is to scale your preview output:

this.renderEditingCanvasPreview = function(canvas, controller) {
  var ctx = canvas.getContext('2d');
  var w = canvas.width;
  var h = canvas.height;
  ctx.save();

  var scale = 1;
  if (controller && controller.renderPixelsPerWebPixel) {
    scale = controller.renderPixelsPerWebPixel;
    ctx.scale(scale, scale);
  }

	// ... draw something here using standard web pixels regardless of display DPI

  ctx.restore();
}

prerenderedContentRequest

Defines live preview content that the host application should render and store. The value should be defined as an associative array using some user-defined id as a key and content description as a value. The content description should follow the format seen in the following example:

this.prerenderedContentRequest = {
  'preview-default': {
    jsxCode: "<AnimatedBurgerButtons.HamburgerArrow buttonWidth={40} />",
    renderSize: {w: 70, h: 59},  // the burger button has padding around the specified 'buttonWidth'
  },
};

Pre-rendered content can be used in plugin previews using Plugin.getPrerenderedContent(contentKey) function.

takeContentsFromDataLinkageValue(value, typeId)

Allows plugin to react on Data Runtime Linkage values specified in Studio. It's most worthwhile with rendering previews.

Example:

this.takeContentsFromDataLinkageValue = function(value, typeId) {
  if (typeId == "image") {
    this._image = this.loadImage(value); // value is path to image
  }
}

Linking to other project objects

getLinkedElements()

Can be used to inform the host application that specific elements are linked together. Inspector UI elements of type element-picker should be used to obtain required elementId values.

Example:

this.getLinkedElements = function() {
  return [
    {
      "elementId": this._data.targetElementId,
      "propertyName": "targetElement",
    }
  ];
}

On iOS and Android, getLinkedElements should be used to together with the getPropertyDeclsForLinkedElements function in the exporter bridge. getPropertyDeclsForLinkedElements should be called in the exportAsIOSClass/exportAsAndroidClass functions. It generates necessary property/field declarations based on the plugin's implementation of the getLinkedElements function.

Example:

this.exportAsIOSClass = function(className, exporter) {
  var head = ""
    +"#import <UIKit/UIKit.h>\n"
    +"\n"
    +"@interface "+className+" : NSObject\n"
    +"\n";
  var propDecls = exporter.getPropertyDeclsForLinkedElements().toNative();
  for (var i = 0; i < propDecls.length; ++i) {
    head += propDecls[i];
  }
  head += ""
    +"\n"
    +"- (void)scrollVisibleRectDidChangeTo:(CGRect)newRect from:(CGRect)oldRect;\n"
    +"\n"
    +"@end\n";

  var impl = ""
    +"#import \""+className+".h\"\n"
    +"\n"
    +"@implementation "+className+"\n"
    +"\n"
    +"- (void)scrollVisibleRectDidChangeTo:(CGRect)newRect from:(CGRect)oldRect\n"
    +"{\n"
    +"}\n"
    +"\n"
    +"@end\n";

  exporter.writeSourceCode(className+".h", head);
  exporter.writeSourceCode(className+".m", impl);
}

Specific to iOS target

getIOSImports(exporter)

Used to specify additional header files used by the plugin. Will be written to the containing ViewController class. Must be an array.

Example:

this.getIOSImports = function(exporter) {
  return [
    '"myclass.h"'
  ];
}

exportAsIOSClass(className, exporter)

Called to write the element as an iOS Objective-C class.

The exporter object provides a "writeSourceCode" method to write the actual files. Example usage:

this.exportAsIOSClass = function(className, exporter) {
  var head = this._writeObjCHeader(className);
  var impl = this._writeObjCImpl(className);
  exporter.writeSourceCode(className+".h", head);
  exporter.writeSourceCode(className+".m", impl);
}

writeIOSInitCode(exporter, varName)

Called when an element is being instanced so the plugin can inject some code to initialize the element.

The code should use the varName parameter to manipulate the newly created instance in the generated code. Example:

this.writeIOSInitCode = function(exporter, varName) {
  return varName + ".enableSomeStuff = YES;"
}

writeIOSCodeForViewControllerMethod(methodName, exporter, varName)

Called when certain methods are called on the containing view controller object by the operating system. Applicable method names are: viewWillAppear, viewDidAppear and viewDidDisappear.

Example:

this.writeIOSCodeForViewControllerMethod = function(methodName, exporter, varName) {
  if (methodName == "viewWillAppear") {
    return "[self."+varName+" start];\n";
  }
  else if (methodName == "viewDidDisappear") {
    return "[self."+varName+" stop];\n";
  }
}

writeIOSCodeForPublishedInteractAction(actionId, exporter, varName, arg)

Called when an action is performed on the plugin. The possible actions were described by the plugin using "publishedInteractActions" (see above).

Parameter actionId is one of the actions defined by the plugin in "publishedInteractActions".

Parameter varName should be used to refer to the object in the Objective-C code.

Parameter arg is an argument value that was specified by the user in the host application's interface. It is currently always a string.

At its simplest, this method's implementation could simply be a method call within the generated code, something like:

this.writeIOSCodeForPublishedInteractAction = function(actionId, exporter, varName, arg) {
  return "[" + varName + " " + actionId + "]"
}

(Assuming your plugin's Objective-C code implements a method with the name given by 'actionId').

writeIOSTakeValuesFromDataSheetCode(exporter, varName, columnName)

Write code to update the element's content dynamically based on a value from a data sheet column.

Dynamic data in Neonto Studio is represented with data sheets. At runtime, a screen or list item can represent a specific row within a data sheet. The elements within that screen or list item will then "take values" from the data sheet row according to a specific column name. This is called data linkage in the Neonto Studio UI.

Example:

this.writeIOSTakeValuesFromDataSheetCode = function(exporter, varName, columnName) {
  return ""
  + "if ([type isEqual:@\"text\"]) {\n"
  + "    "+varName+".title = value;\n"
  + "}\n";
}

libraryDependenciesInExportedProject_iOS

Used to specify libraries used by the plugin. (Not a method — value must be an array.) Libraries must be located in Contents/Libraries/iOS/ path inside the plugin bundle. The same function can be used to include system libraries too when it's not necessary to copy the libraries inside the plugin bundle.

Example:

this.libraryDependenciesInExportedProject_iOS = [
  "mylib.framework"
];

getIOSEmbeddedBinaries

Copies and applies frameworks to the exporter project. This function will add frameworks specifically to the Embedded Binaries section in the Xcode project. More information: https://developer.apple.com/library/content/technotes/tn2435/_index.html

Example:

this.getIOSEmbeddedBinaries = [
  "test.framework"
];

getIOSModuleImports(exporter)

Should be used together with the getIOSEmbeddedBinaries function.

Example:

this.getIOSModuleImports = function(exporter) {
  return [
    "test"
  ];
}

Specific to Android target

exportAsAndroidClass(className, exporter)

Similar to exportAsIOSClass above. Should write Java files using the exporter.

Example usage:

this.exportAsAndroidClass = function (className,exporter){
  var java = this._writeJavaImpl (className,exporter);
  exporter.writeSourceCode(className+".java",java);
}

writeAndroidInitCode(exporter, varName)

Similar to writeIOSInitCode above.

Example:

this.writeAndroidInitCode = function(exporter, varName) {
  return varName+".enableSomeStuff(true);\n";
}

writeAndroidCodeForPublishedInteractAction(actionId, exporter, varName, arg)

Similar to iOS equivalent above.

Example:

this.writeAndroidCodeForPublishedInteractAction = function(actionId, exporter, varName, arg) {
  return varName + "." + actionId + "();"
}

writeAndroidTakeValuesFromDataSheetCode(exporter, varName, columnName)

Similar to iOS equivalent above.

Example:

this.writeAndroidTakeValuesFromDataSheetCode = function(exporter, varName, columnName) {
  return ""
  + "if ((val = row.get(\""+columnName+"\")) != null && val.get(\"type\").equals(\"text\")) {\n"
  + "    "+varName+".title = (String) val.get(\"value\");\n"
  + "}\n";
}

getAndroidManifestPermissions()

A list of permissions needed by the app to be written to AndroidManifest.xml file in a generated Android Studio project. Must return an array.

Example:

this.getAndroidManifestPermissions = function() {
  return [
    "android.permission.CAMERA",
    "android.permission.WRITE_EXTERNAL_STORAGE"
  ];
}

getAndroidManifestFeatures()

A list of features used by the app to be written to AndroidManifest.xml file in a generated Android Studio project. Must return an array.

Example:

this.getAndroidManifestFeatures = function() {
  return [
    "android.hardware.camera",
    "android.hardware.camera.autofocus"
  ];
}

getAndroidManifestCustomActivities()

A list of custom activities to be written to AndroidManifest.xml file in a generated Android Studio project. Useful for e.g. activity classes defined in custom libraries. Must return a list with activity names as keys and a list of attributes as values.

Example:

this.getAndroidManifestCustomActivities = function() {
  return {
    "com.example.app.MyActivity": { "android:configChanges": "keyboardHidden|orientation" }
  }
}

writeAndroidManifestCustomApplicationElements(exporter)

Write custom elements inside the application element in the exported project's AndroidManifest.xml file.

Example:

this.writeAndroidManifestCustomApplicationElements = function(exporter) {
  return ""
    +"<meta-data\n"
    +" android:name=\"com.example.myIdentifier\"\n"
    +" android:value=\"test\" />\n"
}

getAndroidGradleModuleDependencies(exporter)

Called for additional dependencies to be written to Android Studio project's Module build.gradle file.

Example:

this.getAndroidGradleModuleDependencies = function(exporter) {
  return [
    "'org.osmdroid:osmdroid-android:5.5:release@aar'"
  ];
}

getAndroidGradleProjectDependencies(exporter)

Called for additional dependencies to be written to Android Studio project's Project build.gradle file.

Example:

this.getAndroidGradleProjectDependencies = function(exporter) {
  return [
    "'com.google.gms:google-services:3.0.0'"
  ];
}

writeAndroidCodeForLifecycleMethod(methodName, exporter, varName)

Deprecated. Use writeAndroidCodeForFragmentMethod function instead.

Allows to write code to certain fragment class methods: onResume, onPause, onDestroy, onLowMemory.

Example:

this.writeAndroidCodeForLifecycleMethod = function(methodName, exporter, varName) {
  if (methodName == "onPause") {
    return varName+".pause();";
  }
  else if (methodName == "onResume") {
    return varName+".resume();";
  }
}

writeAndroidCodeForFragmentMethod(methodName, exporter, varName)

Allows to write code to certain fragment class methods: onActivityResult, onResume, onPause, onStop, onDestroy, onLowMemory.

Example:

this.writeAndroidCodeForFragmentMethod = function(methodName, exporter, varName) {
  if (methodName == "onActivityResult") {
    return varName+".onActivityResult(requestCode, resultCode, data);";
  }
}

libraryDependenciesInExportedProject_Android

Used to specify libraries used by the plugin. Libraries must be located in Contents/Libraries/Android/ path inside the plugin bundle.

Example:

this.libraryDependenciesInExportedProject_Android = [
  "mylib.jar"
];

Specific to ReactJS target

getReactWebPackages()

Return dependencies that need to be included in the exported project's package.json file.

Each key is an npm package name that must be imported, and the value is the package version.

Example:

return {
  "some-awesome-package-from-npm": "^1.2.3"
};

getReactWebImports(exporter)

Return dependencies that need to be imported in code where this plugin element will be used.

These should include both external packages you've declared using getReactWebPackages as well as any components that your plugin wants to import dynamically within the Studio project (i.e. components that the user can link in the inspector UI using a component-picker).

Example:

this.getReactWebImports = function(exporter) {
	var arr = [
    { varName: "AwesomeImportedThing", path: "some-awesome-package-from-npm" }
  ];

  // this presumes we have provided a component picker in the UI and have saved
  // its value in 'this._data.customComponent'
	var customComp = exporter.classNameForComponentByName(this._data.customComponent);
	if (customComp) {
		arr.push({ varName: customComp, path: `./${customComp}` });
	}

	return arr;
}

writesCustomReactWebComponent

Must be a boolean value to indicate whether the plugin wants to write a custom React component.

getReactWebJSXCode(exporter)

Called if a plugin returns NO in this.writesCustomReactWebComponent (see above).

When a plugin doesn't want to write an entire custom component, it's typically because the plugin is simply wrapping an npm package. This method gets called so the plugin can write JSX code for whatever component it wraps.

The following example is from the "Rating" plugin that wraps the third party npm-rating package. This code fragment shows some important features:

  • The wrapped component's initialRating prop is linked to a Studio data linkage key (see reactWebDataLinkKeys below), so it can be taken from e.g. a data slot
  • The wrapped component's onChange callback is captured using a custom interaction of type 'valueChange', which becomes exposed in the Studio UI and can be connected with actions there to e.g. save the value into a data slot (see reactWebInteractEventsForStudioUI below).
  • The Studio user can specify a custom component to be used for the icon (the fullSymbol prop). This is linked using an inspector UI element of type component-picker.
this.getReactWebJSXCode = function(exporter) {
  var min = this._data.minValue;
  var max = this._data.maxValue;
  var readonly = this._data.readonly;

  var jsx = `<Rating readonly={${readonly}} start={${min}} stop={${max}}`;

  var valueLinkage = exporter.getExpressionForLinkKey('value');
  if (valueLinkage) {
    jsx += ` initialRating={parseInt(${valueLinkage})}`;
  }

  var onValueChange = exporter.getCallbackForInteraction('valueChange');
  if (onValueChange) {
	jsx += ` onChange={${onValueChange}}`;
  }

  var customComp = exporter.classNameForComponentByName(this._data.customComponent);
  if (customComp) {
    jsx += ` fullSymbol={<${customComp} />}`;
  } else {
    jsx += " fullSymbol={<div style={{fontSize:18, width:20, height:20}}>★</div>}";
  }

  jsx += ` />`;
  return jsx;
}

getReactWebRenderMethodSetupCode(exporter, varName)

Called if a plugin returns NO in this.writesCustomReactWebComponent (see above).

This method allows a plugin to write custom code inside containing component's render method. The generated code will be called once per every render. Anything more complicated rendering related code should be written here to keep the actual JSX part (see this.getReactWebJSXCode above) cleaner.

Example (processes data sheet row to suitable format for ReactTable component):

this.getReactWebRenderMethodSetupCode = function(exporter, varName) {
  var sourceDataSheetName = exporter.nameForDataSheetById(this._data.sourceDataSheet);
  var sourceDataSheetAccess = exporter.valueAccessForDataSheetByName(sourceDataSheetName);
  this._dataVarName = `${varName}_data`;
  this._columnsVarName = `${varName}_columns`;
  return `const ${this._dataVarName} = ${sourceDataSheetAccess}.items;
let ${this._columnsVarName} = [];
if (${this._dataVarName}.length > 0) {
  for (let col in ${this._dataVarName}[0]) {
    if (col == "key") continue;
    ${this._columnsVarName}.push({
      Header: col,
      accessor: col
    });
  }
}
`;
}

getReactWebComponentName()

Called if a plugin returns YES in this.writesCustomReactWebComponent (see above).

Preferred class name for the component written by the plugin. The exporter may still need to modify this (e.g. if there already is a component by this name), so in the actual export method (i.e. this.exportAsReactWebComponent), we must use the className value provided as a parameter.

Example:

this.getReactWebComponentName = function() {
    return "Button";
}

exportAsReactWebComponent(className, exporter)

Called if a plugin returns YES in this.writesCustomReactWebComponent (see above).

This entry point is responsible for writing a class file for the custom component (usually done using exporter.writeSourceCode(className, code) call).

Example:

this.exportAsReactWebComponent = function(className, exporter) {
    var code = `
import React from 'react';

class ${className} extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return <div className="${className}">
      Hello world!
    </div>
  }
}

export default ${className};
`;
    exporter.writeSourceCode(className+".js", code);
}

getReactWebCustomJSXAttrs(exporter)

Called if a plugin returns YES in this.writesCustomReactWebComponent (see above).

Writes custom attributes passed to instances of the written component.

Example:

this.getReactWebCustomJSXAttrs = function(exporter) {
  return "label='Hello'";
}

reactWebDataLinkKeys

An array containing a list of data linkage keys that the plugin wants to make available in the Studio UI.

For example, a plugin that has dynamic content might expose two data linkage keys 'text' and 'image'. The user in the Studio host app can then link these values to e.g. data slots or a component's properties.

Example (for a plugin that only takes a single dynamic value):

this.reactWebDataLinkKeys = [
	"value"
];

reactWebInteractEventsForStudioUI

An array containing a list of Studio interactions that you want to be made available in the Studio UI when your plugin element is selected. Currently supported values are 'valueChange' and 'toggle'.

This is particularly useful when packaging components from npm. Typically a component will let you provide a callback as a prop which then gets called when the component's internal value changes. With the combination of reactWebInteractEventsForStudioUI, describeReactWebInteractEvent and exporter.getCallbackForInteraction (see Exporter), the user in Studio can make something happen based on the changed value — for example saving the new value to a data slot.

Example:

this.reactWebInteractEventsForStudioUI = [
	"valueChange"  // id for the default interaction for plugins in React Studio
];

describeReactWebInteractEvent(exporter, interactionId)

Called to generate the callback code that responds to an interaction exposed to the Studio UI (see above in reactWebInteractEventsForStudioUI).

Must return an object with a property actionMethod that describes a function to be generated using an object with these properties: arguments is an array that lists the data from the component (typically the new value), and getDataExpression is a code expression that will be used as the right-hand side value when the value is stored and passed on depending on what the Studio user has specified (e.g. saving to a data slot). Often you just want to pass on the new value as-is, so you can simply return the name of the first argument you've specified.

Tip: you don't usually need anything else than what's in the example code below. Start with that, and optionally change 'newValue' to something that better describes the value sent from your packaged component.

Example:

this.describeReactWebInteractEvent = function(exporter, interactionId) {
	switch (interactionId) {
		case 'valueChange':
			return {
				actionMethod: {
					arguments: ['newValue'],
					getDataExpression: 'newValue'
				}
			};
	}
	return null;
}

getReactWebCSS(exporter, baseSelector, screenFormatId)

Let's plugin write CSS.

Example:

this.getReactWebCSS = function(exporter, baseSelector, screenFormatId) {
  return ''+
`${baseSelector} button {
  outline: none;
}
`;
}

writeReactWebCodeForPublishedInteractAction(actionId, exporter, varName, arg)

Writes code for interacions specified in plugin's this.publishedInteractActions property.

Example:

this.writeReactWebCodeForPublishedInteractAction = function(actionId, exporter, varName, arg) {
    if (actionId == 'shoot') {
        return varName + ".savePicture()";
    }
}

contentNeedsExplicitHeight

A boolean property that tells the exporter to generate height value for the component (default value is true when the property is not defined). It's useful to set to false for components that determine height dynamically (i.e. a list of varying amount of items).

Example:

this.contentNeedsExplicitHeight = false;