Understand Visual Widgets

Understand Visual Widgets

In the following tutorial you will learn to understand and use Rhino Visual Widgets.


Overview

What are Visual Widgets?

Visual Widgets are small independent working program code snippets with top performance. They provide flexibility, pose economical resources and they are completely customizable.

Why use Visual Widgets?

Visual Widgets allow for content editors to flexibly create and design web content. Our base code already contains many different examples including banners, carousels, accordions and more. Widgets may be instantiated to save loading time altogether while offering the opportunity of creating a highly intricate site. Their functionality can be expanded by defining flex-attributes unique to every widget. By using the Visual Widget editor any user can easily edit their own Visual Widget without having to touch the code.

The first simple visual widget - a headline

Create descriptor content

What does the descriptor do?

Basically, the descriptor itself does not do anything. Rather than that it is a list of attributes/properties that the respective widgets should be initialized with. Its standard mime type is JSON.

{
  "renderer" : {
    "rendererClass" : "manual.ikona.visualwidgets.step1.DemoRenderer"
  },
  "validators" : [
    { "validatorClass" : "rhino.visualwidgets.validators.DefaultValidator" }
  ],
  "dataResolver" : {
    "dataResolverClass" : "rhino.visualwidgets.DefaultDataResolver"
  },
  "properties" : {
    "title" : {
      "defaultValue" : "",
      "required" : true,
      "type" : "string",
      "editor" : {
        "editorClass" : "rhino.smp.visualwidgets.editors.StringSingleLineEditorWidget",
        "displayLabel" : "Title"
      }
    }
  }
}
Descriptor attributes explanation
  • renderer (Implement in descriptor "rendererClass" path to your renderer)
  • validators (Get the value by resolving and validate values)
  • dataResolver (Parse properties for bindings and check if property has a binding)
  • properties (Define properties and get the property from object)
Example for descriptor properties
"properties": {
  "title": {
    "defaultValue": "",
    "required": true,
    "type": "string",
    "editor": {
      "tags": "Title",
      "editorClass": "rhino.smp.visualwidgets.editors.StringSingleLineEditorWidget",
      "displayLabel": "Title"
    }
  }
}
Descriptor attributes properties explanation
  • "title" - defines a title property for the widget
  • "defaultValue" - this value is used as fallback if no value is defined in the instance
  • "required" - defines whether the value must be supplied
  • "type" - data type
  • "editorClass" - choose editor class corresponding with data type
  • "displayLabel" - label for the editor input

Create the renderer

What does the renderer do?

The renderer is a server-side applet which renders any DOM information provided by the Visual Widget while including any available descriptor. It is a JavaScript file.

Renderer attributes explanation

  • descriptor (Define widgets attributes)
  • entity (To create an entity in JavaScript, you need a constructor pattern)
  • resolver (Resolver get properties and them values)

You have to implement this.render as function(descriptor, entity, resolver) ...

this.render = function (descriptor, entity, resolver) {
  
  //retrieve title from resolver
  var title = resolver.get("title");

  return "<h1>" + title + "</h1>";
};

Create the instance:

You have to create manual.ikona.visualwidgets.step1.DemoInstance


{
  "title": "Your title content step 1"
}

The result

Create test page...

... and embed this instance into:

...
  #{rhino.visualwidgets.render("manual.ikona.visualwidgets.step1.DemoInstance")}
...

The result should look like:

<h1>Your title content step 1</h1>

Using the Visual Widget Editor

Step 1: Select in staging dropdown menu SMP and select Rhino Visual Widgets.

Step 2: Select in menu your page and go to add widget.

Step 3: Create new visual widget.

Step 4: Open editor set your title content and save it.


Naming convention

The resulting FQN will be [namespace] + ".visualwidgets" + [type] + "." + [name], e.g. testing.visualwidgets.accordion.TestWidget.


The second simple visual widget - using template

Create two templates and implement rino visual widget get method into:

<div class="container">
  <div class="row">
    <div class="col-sm-6">
      #{rvwget('title')} #{rvwget('image')}
      <p>Default template</p>
    </div>
  </div>
</div>


Use #{rvwget()} method to display data in HTML.

<div class="container">
  <div class="row">
    <div class="col-sm-6">
      <h4>Your title content step 1</h4>
      #{rvwget('image')}
      <p>Custom template</p>
    </div>
  </div>
</div>

The third visual widget - using template with widget functions

Create widget:

panther.ensureDomPath("manual.ikona.visualwidgets.step3");

manual.ikona.visualwidgets.step3.DemoWidget = (function (panther) {

  panther.widgets.AbstractWidget.extend(DemoWidget);

  function DemoWidget(widgets) {

    DemoWidget.prototype.super.constructor.call(this, widgets);
    this.popoverAction = null;
  }

  DemoWidget.prototype.init = function (element) {

    DemoWidget.prototype.super.init.call(this, element);
    console.log('Ok widget get instantiated.');
  };

  DemoWidget.prototype.clickButtonAction = function (event, action) {
    
    event.preventDefault();
    this.action.popoverAction.popover();
  };

  return DemoWidget;
})(panther);

Create template and implement your DemoWidget into template:


<div class="container" data-widget="#{portal.remap('manual.ikona.visualwidgets.step3.DemoWidget')}">
  <div class="row">
    <div class="col-sm-6">
      #{rvwget('title')} #{rvwget('image')}
      <p>Custom template</p>
      <a href="#" role="button" class="btn btn-default popovers" 
		 data-toggle="popover" title="popover title" data-content="Some content inside the popover. <br><a href='http://www.jenomics.de/' target='_blank' title='link'>Link on content</a>"
data-original-title="Title" data-demo="PopoverAction">Popover with link inside</a>
</div>
</div>
</div>

The fourth visual widget - using bindings

The most important reason to use a JSON binding is for interoperability with other components or external systems that use JSON objects.This is because JSON bindings expose JSON object.

Create descriptor content

{
  "renderer": {
    "rendererClass": "manual.ikona.visualwidgets.step4.DemoRenderer",
    "templateFlavors": {
      "default": "manual.ikona.visualwidgets.step4.DefaultTemplate",
      "custom": "manual.ikona.visualwidgets.step4.CustomTemplate"
    }
  },
  "validators": [
    {
      "validatorClass": "rhino.visualwidgets.validators.DefaultValidator"
    }
  ],
  "dataResolver": {
    "dataResolverClass": "rhino.visualwidgets.DefaultDataResolver"
  },
  "properties": {
    "templateFlavor": {
      "defaultValue": "default",
      "required": true,
      "type": "stringEnum",
      "values": [
        "default",
        "custom"
      ],
      "editor": {
        "editorClass": "rhino.smp.visualwidgets.editors.StringEnumEditorWidget",
        "displayLabel": "Flavor",
        "valueDisplayLabels": [
          "Default",
          "Custom"
        ]
      }
    },
    "categoryName": {
      "defaultValue": null,
      "required": true,
      "type": "string",
      "editor": {
        "editorClass": "rhino.smp.visualwidgets.editors.SelectCategoryEditorWidget",
        "displayLabel": "Category"
      }
    },
    "category": {
      "defaultValue": null,
      "required": true,
      "type": "category",
      "binding": {
        "type": "asCategory",
        "property": "categoryName"
      }
    },
    "title": {
      "defaultValue": null,
      "required": true,
      "type": "string",
      "binding": {
        "type": "asProperty",
        "referenceProperty": "category",
        "valueProperty": "title"
      }
    },
    "linkTitle": {
      "defaultValue": null,
      "required": false,
      "type": "string",
      "maxLength": 50,
      "binding": {
        "type": "asDefault",
        "property": "title"
      },
      "editor": {
        "editorClass": "rhino.smp.visualwidgets.editors.StringSingleLineEditorWidget",
        "tags": "Link",
        "displayLabel": "Link Titel"
      }
    },
    "url": {
      "defaultValue": null,
      "required": true,
      "type": "string",
      "binding": {
        "type": "asProperty",
        "referenceProperty": "category",
        "valueProperty": "url"
      }
    },
    "price": {
      "defaultValue": null,
      "required": true,
      "type": "string",
      "binding": {
        "type": "asMoney",
        "property": "priceInCent"
      }
    },
    "priceInCent": {
      "defaultValue": null,
      "required": true,
      "type": "number",
      "binding": {
        "type": "asProperty",
        "referenceProperty": "category",
        "valueProperty": "lowestPrice"
      }
    },
    "currency": {
      "defaultValue": null,
      "required": true,
      "type": "product",
      "binding": {
        "type": "asProperty",
        "referenceProperty": "category",
        "valueProperty": "currency"
      }
    },
    "imageId": {
      "defaultValue": null,
      "required": false,
      "type": "uuid",
      "binding": {
        "type": "asProperty",
        "referenceProperty": "category",
        "valueProperty": "imageId"
      }
    },
    "maxImageWidth": {
      "defaultValue": 48,
      "required": true,
      "type": "integer",
      "editor": {
        "editorClass": "rhino.smp.visualwidgets.editors.IntegerEditorWidget",
        "displayLabel": "Max Image Width"
      }
    },
    "maxImageHeight": {
      "defaultValue": 48,
      "required": true,
      "type": "integer",
      "editor": {
        "editorClass": "rhino.smp.visualwidgets.editors.IntegerEditorWidget",
        "displayLabel": "Max Image Height"
      }
    },
    "description": {
      "defaultValue": "",
      "required": false,
      "type": "string",
      "binding": {
        "type": "asProperty",
        "referenceProperty": "category",
        "valueProperty": "description"
      }
    },
    "shortDescription": {
      "defaultValue": "",
      "required": false,
      "type": "string",
      "binding": {
        "type": "asProperty",
        "referenceProperty": "category",
        "valueProperty": "shortDescription"
      }
    }
  }
}

Create the renderer

this.render = function (descriptor, entity, resolver) {

  //retrieve templateFlavors from descriptor
  var templateFlavors = descriptor.renderer.templateFlavors;

  //ensure templateFlavors is not null
  if (!templateFlavors) {
    throw new Error("Descriptor descriptor.renderer.templateFlavors is null");
  }

  //retrieve attribute templateFlavor from resolver
  var templateFlavor = resolver.get("templateFlavor");

  //ensure templateFlavor is not null
  if (!templateFlavor) {
    throw new Error("Resolver attribute templateFlavor is null");
  }

  //resolve template from flavor
  var templateName = templateFlavors[templateFlavor];

  //ensure template is not null
  if (!templateName) {
    throw new Error("Template flavor " + templateName + " not found");
  }

  //try to resolve a remapping from portal
  var rendererTemplateContentName = portals.current.get(templateName);

  //default to itself if no remapping is given
  if (!rendererTemplateContentName) {
    rendererTemplateContentName = templateName;
  }

  var rendererTemplateContent = contents.findByName(rendererTemplateContentName);

  if (!rendererTemplateContent) {
    throw new Error("Renderer template content could not get resolved - " + rendererTemplateContentName);
  }

  var category = resolver.get("category");
  var title = resolver.get("title");
  var linkTitle = resolver.get("linkTitle");
  var url = resolver.get("url");
  var price = resolver.get("price");
  var currency = resolver.get("currency");
  var imageId = resolver.get("imageId");
  var maxImageWidth = resolver.get("maxImageWidth");
  var maxImageHeight = resolver.get("maxImageHeight");
  var description = resolver.get("description");
  var shortDescription = resolver.get("shortDescription");

  //set binding for attributeList
  resolver.bindings.attribute = category, title, linkTitle, url, price, currency, imageId, maxImageWidth, maxImageHeight, description, shortDescription;
  
  return rendererTemplateContent.content;
};

Create template

<div class="container">
  <div class="row">
    <div class="col-sm-6">
      <p>Custom template</p>
      <h4>#{rvwget('title')}</h4>
      <h4>#{rvwget('description')}</h4>
      <a href="#{rvwget('url')}">
        <img class="img-responsive" src="/static/image/get?id=#{rvwget('imageId')}&size=#{rvwget('maxImageWidth')}x#{rvwget('maxImageHeight')}"
          alt="#{rvwget('title')}" />
      </a>
      <p>ab #{rvwget('price')} #{rvwget('currency')}</p>
      <p>
        <a class="btn btn-primary" href="#{rvwget('url')}">
          <span class="fa fa-chevron-circle-right"></span> #{rvwget('linkTitle')}</a>
      </p>
    </div>
  </div>
</div>