# 3pi Widgets

Partially deprecated

If you work on an XPLib Media Group, use the new Widget Providers architecture instead of 3pi.

If a widget is not supported by Marfeel, create a new extension. If it's the first time for this provider, the extension is created in the Tenant's code repository as a custom widget extension. Otherwise, add the extension to Marfeel core.

You can research if this widget already has a custom implementation via Github with this search URL, replacing ${PROVIDER_NAME} with the one you're looking for.

https://github.com/search?q=org%3AMarfeel+filename%3Awidgets%3Ajson+${PROVIDER_NAME}&type=Code

For example for youtube: https://github.com/search?q=org%3AMarfeel+filename%3Awidgets%3Ajson+youtube&type=Code (opens new window)

Deprecated

In the past, we initialized widgets in the src/js/main.js file inside the tenant. This is now deprecated and we do this via 3pi in widgets.json or in the providers.json.

If you find a deprecated implementation of a widget that you need (in main.js), migrate it to MarfeelXP. Find an example of migration here (opens new window).

Align with your Extensibility Chapter representative before beginning the development.

# How to implement

  1. Inside the 3pi-widgets folder create a folder with the widget's name. The structure of the site repository is:
www.example.com
├─── widgets.json
└─── index
     └─── src
        └─── js
            └─── 3pi-widgets
                └─── awesome
                    ├─── awesome-widget.spec.ts
                    └─── awesome-widget.ts
  1. Write a Typescript class with all the required behavior, following the provider's implementation guidelines.

The file must be in under src/js/3pi-widgets/awesome/ in the site code repository.

  1. The custom widget must implement Widget or extends an Abstract that implements Widget.
import {registerWidget} from 'marfeel/3pi-widgets/WidgetRegistry';

import {Widget} from 'marfeel/3pi-widgets/Widget';

function initialize(): void {
  // Do something awesome
}

class AwesomeWidget implements Widget {

    load(element: HTMLElement):Promise<{}> {
        // We must return a promise
        return new Promise(resolve => {
            // Here we want to resize the iframe to an specific height always. If not we would extends AbstractResizableWidget
            (<any>window).parent.postMessage({
                sentinel: 'amp',
                type: 'embed-size',
                height: 651
            }, '*');
            initialize();
            resolve();
        });
    }
}
// We need to register the widget (Must implement Widget or it will fail in compilation time)
registerWidget(AwesomeWidget);

export default AwesomeWidget;

HTML, JSON, PNG, CSS, WOF and SVG files can be import inside 3pi-widgets in typescript in a regular es6 import style:

import template from './commentsTemplate.html';

This is the standard way to import resources into widgets.

  1. Reference the widget you created in the widgets.json file:
{
  "widgets": [{
      "selector": "...",
      "src": "tenantName/marfeelName/awesome-widget.js",
      "height": "34px"
    }
  ]
}

See more about widgets.json file.

  1. Write unit tests for the widget.

Widgets unit tests must be next to the widget file, have the same name as the widget, and also be written in typescript:

├─── awesome-widget.spec.ts
└─── awesome-widget.ts

The testing library is Jasmine (opens new window) and the test runner Karma (opens new window). Versions are the same as in the rest of Marfeel.

Example test file:

import 'marfeel/commons/Polyfills'

import Injector from 'inject-loader!./awesome-widget';

describe('Awesome Widget', function() {
  let AwesomeWidget,
      widgetInstance,
      utilMock,
      widgetContainer;

  beforeEach(() => {
    AwesomeWidget = Injector({
      'marfeel/commons/Util': jasmine.createSpy()
    }).default;

    widgetInstance = new AwesomeWidget();
    widgetContainer = document.createElement('div');
    widgetContainer.id = 'awesomeId';
  });

  it('should load script correctly', () => {
    widgetInstance.load(widgetContainer);

    expect(utilMock.loadScript).toHaveBeenCalled();
  });
});

Place yourself inside the tenant name folder containing the widget and run the test with jspec:

cd example.com/index
jspec --custom
  1. Compile the widget running the following command:
Jinks --customjs

This will generate a .js file that will be injected in the widget iframe:

www.example.com
├─── widgets.json
└─── index
      ├─── awesome-widget.js
      └─── widgets.s.js

# Plugin Implementation examples

# Resizable widget

  1. To implement a custom resizable widget the class should extends AbstractResizableWidget, or extends an Abstract that extends AbstractResizableWidget.
import { loadScript } from 'marfeel/commons/Util';
import AbstractResizableWidget from 'marfeel/3pi-widgets/AbstractResizableWidget';
import { registerWidget } from 'marfeel/3pi-widgets/WidgetRegistry';

const RESIZE_EVENT_NAME = 'my-event-name';
const SCRIPT_URL = 'https://an-external-script';

class AwesomeWidget extends AbstractResizableWidget {
    constructor() {
        super(RESIZE_EVENT_NAME);
    }

    execute(element: HTMLElement):Promise<{}> {
        return new Promise(resolve => {
            loadScript(SCRIPT_URL, resolve);
        });

    }
}

registerWidget(AwesomeWidget);
export default AwesomeWidget;

It's necessary to pass the event name that will fire a resize automatically when the widget is loaded.

Also, in this example, typescript is used but exists a JS dependency (Util).

# Promote a custom widget extension to Marfeel Core

If an extension already exists as Custom and has to be reused for a different Tenant, promote it to Marfeel Core. The steps to follow are:

  1. Add the Typescript implementation in MarfeelXP/Dixie/src/js/marfeel/3pi-widgets/impl/ (opens new window)

  2. Test wild be placed in MarfeelXP/Dixie/src/test/specs/marfeel/3pi-widgets/specs/ (opens new window)

  3. Run the specs running the following command:

 jspec --core
  1. The compiled widget will be in Marfeel tenant: marfeel/nameOfWidget.js

  2. To use it in a tenant, we just need to add it in the widgets.json file as follow:

{
  "widgets": [
    {
      "selector": "...",
      "src": "marfeel/awesome-widget.js"
    }
  ]
}

# Metadata in Widgets

In some cases, a widget could require metadatas that are extracted in Boiler. Those metadatas are also injected in the widget's HTML and can be easily accessible with the getMetada method from WidgetUtils.For example:

For this widget, DigitekaSmartPlayer metadata is needed. As shown below, current item's metadatas are injected in the HTML.

<div class="mrf-metadatas">
    <script class="DigitekaSmartPlayer" type=application/ld+json">
    {"@context":"http://schema.org","@type":"DigitekaSmartPlayer":{"ULTIMEDIA_mdtk":"012345","ULTIMEDIA_target":"ultimedia_wrapper_mobile", "ULTIMEDIA_async":"false"}}
    </script>
</div>

In the widget, the name of the property @type has to be added. In this case: digitekaSmartPlayer

import {getMetadata} from 'marfeel/3pi-widgets/WidgetUtils';
const METADATA_PROPERTY = 'digitekaSmartPlayer';
...
    const data : Object = getMetadata(METADATA_PROPERTY);
...

# Native App considerations

When 3rd party code loaded inside 3pi widgets, uses window.open, the Native App might malfunction due to incompatibilities.

To solve it, use the WidgetUtils.manageWindowOpen (opens new window) method which automatically handles the communication between the iframe and the Native App.

TIP

Since 3pi are loaded inside iframes, the Cordova (opens new window) code is not available within it.

WidgetUtils.manageWindowOpen usage example (opens new window).