I am sorry to bring this back again but I am implementing one detail related with the text editor plugin. Everything is working great but recently I have included the routing parameter option just to be able to send a url as a deep link to a specific post that has instructions for some task. It works fine except for the fact that the html content is loaded from a dataslot and that works just fine if you are navigating within the app, but it won’t work if you visit the URL directly from a provided link.
The solution is easy, instead of loading the content from a dataSlot, is should load it from a datasheet. I have tried if it works with some other text fields in the screen and it does. So I would need to change the plugin code to load the content instead of a dataSlot but from a field/value from a dataSheet.
I will dedicate some time to read the documentation this afternoon but it was quite difficult for me last time I approached it so I would really appreciate some guidance. Here I have the code from my plugin:
// -- plugin info requested by host app --
this.describePlugin = function(id, lang) {
switch (id) {
case 'displayName':
return "bubble-react-Quill";
case 'shortDisplayText':
return "bubble-react-Quill";
case 'defaultNameForNewInstance':
return "bubbleReactQuill";
}
}
// -- private variables --
// Here we store internally the values that the user can manipulate in the inspector UI, specified below.
this._data = {
ds_bubble: "",
};
// -- inspector UI --
this.inspectorUIDefinition = [
{
"type": "label",
"text": "A link to a data slot:"
}, {
"type": "dataslot-picker",
"id": "ds_bubble",
"actionBinding": "this._onUIChange"
},
];
this._accessorForDataKey = function(key) {
// This method provides a convenient way to map controls declared in "inspectorUIDefinition" to properties specific for each control type.
// Both onCreateUI and onUIChange will call this method.
// It is assumed here that your _data object will store values with the same property names as each controlId.
// You don't have to use this for your own UIs, but it's a convenient way to get two-way binding between the UI and your _data object.
//
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 private _data 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 is the "actionBinding" specified for controls where we want to use the automatic binding to _data.
var ui = this.getUI();
var prop = this._accessorForDataKey(controlId);
if (prop) {
this._data[controlId] = ui.getChildById(controlId)[prop];
} else {
console.log("** no data property found for controlId "+controlId);
}
}
// -- persistence, i.e. saving and loading --
this.persist = function() {
// The object returned here must be serializable to JSON.
// Our private _data object fills this requirement fine.
return this._data;
}
this.unpersist = function(data) {
// If your plugin has multiple versions and you've added properties to your _data object,
// you may want to do a check here to see if the incoming "data" was written using an old version of the plugin
// and fill in any missing properties.
this._data = data;
}
// -- plugin preview --
this.renderIcon = function(canvas) {
this._renderPreview(canvas, false);
};
this.renderEditingCanvasPreview = function(canvas, controller) {
this._renderPreview(canvas, true, controller);
}
this._renderPreview = function(canvas, showText, controller) {
var ctx = canvas.getContext('2d');
var w = canvas.width;
var h = canvas.height;
ctx.save();
// When drawing to an editing canvas, the output context may be a high-DPI ("Retina") display.
// We'd like to draw in familiar "web pixels", so we need to find out the display's render scale factor.
// Currently on Mac, this scale factor will be 2 if it's a Retina display, 1 otherwise.
var displayScale = 1;
if (controller && (displayScale = controller.renderPixelsPerWebPixel)) {
ctx.scale(displayScale, displayScale);
}
if (showText) { // fill background with color
var color = this._data.exampleColorValue;
if (color && color.length >= 4) {
ctx.fillStyle = `rgba(${255*color[0]}, ${255*color[1]}, ${255*color[2]}, ${color[3]})`;
} else {
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
}
ctx.fillRect(0, 0, w, h);
}
if (this.icon == null) {
var path = Plugin.getPathForResource("camera_icon.png");
this.icon = Plugin.loadImage(path);
}
if (this.icon) {
var iconW = this.icon.width;
var iconH = this.icon.height;
var aspectScale = Math.min(w/iconW, h/iconH);
var scale = (showText ? 0.7 : 0.6) * aspectScale; // add some margin around icon
iconW *= scale;
iconH *= scale;
ctx.save();
ctx.globalAlpha = (showText) ? 0.8 : 0.5;
ctx.drawImage(this.icon, (w-iconW)/2, (h-iconH)/2, iconW, iconH);
ctx.restore();
}
if (showText) { // render a label at the bottom
var fontSize = (h > 40) ? 30 : 15;
ctx.fillStyle = "#ffffff";
ctx.font = fontSize+"px Helvetica";
ctx.textAlign = "center";
ctx.fillText("Camera", w*0.5, h - fontSize/3);
}
ctx.restore();
}
// -- code generation, platform-independent --
this.getLinkedElements = function() {
// Using this entry point, we tell the host app that we need references to other elements within the same screen / block.
// For each element that we're linking to, we provide "elementId" and "propertyName" that should contain the reference.
// This way the Design Compiler on the host app will know to write those values into the generated classes.
// When we're generating code, we ask the Design Compiler to provide code for these properties using "exporter.getPropertyDeclsForLinkedElements()"
return [
//{
// "elementId": this._data.linkedElementId,
// "propertyName": "linkedElement",
//}
];
}
// -- code generation, React web --
this.getReactWebPackages = function() {
return {"react-quill": "^2.0.0-beta.4"};
}
this.getReactWebComponentName = function() {
// Preferred class name for this component.
// 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 below, we must use the className value provided as a parameter.
return "bubbleEditor";
}
this.writesCustomReactWebComponent = true;
// Data links
this.reactWebDataLinkKeys = [
];
this.exportAsReactWebComponent = function(className, exporter) {
var template = Plugin.readResourceFile("templates-web/component-template.js", 'utf8');
var view = {
"CLASSNAME": className
};
var code = this.Mustache.render(template, view);
exporter.writeSourceCode(className + ".js", code);
}
// generate calling props for component
this.getReactWebCustomJSXAttrs = function (exporter) {
var valueChangeCode = exporter.getCallbackForInteractEvent('valueChange');
var valueCode = exporter.valueAccessForDataSlotByName(this._data.ds_bubble);
if (valueChangeCode){
var jsx = `onChange={${valueChangeCode}}`;
}
if (valueCode) {
jsx += `value={${valueCode}}`;
}
return jsx;
}
// WebInteractEvent
// IDs for the default interaction for plugins in React Studio
this.reactWebInteractEventsForStudioUI = [
"valueChange"
];
this.describeReactWebInteractEvent = function(exporter, interactionId) {
switch (interactionId) {
case 'valueChange':
return {
actionMethod: {
arguments: ['newValue'],
getDataExpression: 'newValue'
}
};
}
return null;
}
this.getReactWebCSS = function(exporter, baseSelector, screenFormatId) {
return ''+`
${baseSelector} {
margin: 1rem 4rem;
}
${baseSelector} .ql-container {
border-bottom-left-radius: 0.5em;
border-bottom-right-radius: 0.5em;
background: white;
}
${baseSelector} .ql-editor {
min-height: 25em;
height: 100%;
margin-top: 0px;
bakground-color: white;
}`
}
And this is the component-template:
import React, { Component } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.bubble.css';
export default class {{{CLASSNAME}}} extends Component {
constructor(props) {
super(props);
this.state = {editorHtml: (this.props.value !== undefined) ? this.props.value: ''}
this.handleChange = this.handleChange.bind(this)
}
handleChange (html) {
this.setState({ editorHtml: html });
this.props.onChange(html);
}
render() {
return (
<ReactQuill
theme="bubble"
onChange={this.handleChange}
value={this.state.editorHtml}
readOnly={true}
/>
);
}
}
Thanks!