Building your first Live Center inline Extension

Building your first Live Center inline extension

The Live Center enables you to build out your own extensions to add custom functionality within the text of a post. The implementation is twofold: an inline editor, and an inline renderer. The editor is used within the actual Live Center for journalists to interact with, and the renderer is used to render the element in a channel.

In the image below, the inline editor powers the functionality on the left, and the inline renderer powers the functionality on the right as well as any logic for the clients.

When adding an inline editor as an extension, the extension will only be available internally for users of the live center, whereas a renderer added as an extension will be available both internally and publicly.

Inline extensions use custom <ncpost-content> elements with attributes carrying the data required to render them. The editors writes attributes on the elements, and the renderers reads them.

Inline renderers should not be confused with post renderers. A post renderer deals with JSON data, whereas inline renderers is powered by the “ExtendedContentRenderer” (which is a post renderer), and deals with <ncpost-content> elements (referred to as media elements) exclusively. See https://livecentersupport.norkon.net/article/22-creating-extensions for more information.

Inline Editor

To add an extension, navigate to https://livecenter.norkon.net/LiveCenter/#!/Extensions/

There, you can add a new extension and select “inline editor”. In the JavaScript area we need to provide code that returns an object that supports the functionality required by the Live Center to use the editor. The provided JavaScript will be run in an isolated function scope, so all the variables declared in the scope are private and not exposed to any other parts of the site.

The outline of the required properties on the returned object is found below the JavaScript area:

Return an object that implements the following interface:
 
Essential:
interface InlineEditor {
    title: string,
    typeKey: string
}

Extended:
interface InlineEditor {
    title: string,
    fontAwesomeClass?: string,
    typeKey: string,
    description?: string,

    renderInlinePanel?(element: HTMLElement, mediaElement: HTMLElement, renderPreviewFunc: () => any);
    renderAddPanel?(element: HTMLElement, mediaElement: HTMLElement, submitFunc: (success: boolean) => any);
}

Simplest form of an inline editor extension

The simplest form of an inline extension only includes a title and a typeKey, with no functionality:

return {
   title: "My inline extension",
   typeKey: "INLINE-TEST"
};

This will show up for any users of the Live Center like this:

And when selected, it will show up in the editor:

The HTML for the post is:

<p>Hello</p>

<ncpost-content data-type="INLINE-TEST"></ncpost-content>

<p>Hello!</p>

Extended Inline Editor

The inline editor supports more properties and functions to extend the functionality further. The optional “description” property will be used as a sub-text below the title if present, and the “fontAwesomeClass” adds any font-awesome (4.x) icon to the right of the title.

Typically, you would want the users to either add parameters to an inline element on creation, or after it’s created, and that’s where the “renderAddPanel” and “renderInlinePanel” comes in.

renderAddPanel

The optional “renderAddPanel” function on the editor object allows you to build out any UI you would need prior to adding the inline element to the post.

The function takes three arguments:

  • element: the HTML DOM element you can render any UI to
  • mediaElement: the HTML DOM element of type “ncpost-content” that will be appended to the post. Only attributes are supported for media elements – any innerHTML will be overwritten.
  • submitFunc: a function you can call on either success or failure that you pass as a boolean. On success it will add the media element to the post, and on fail it will cancel.

You may use any front-end framework to work with “element”. jQuery, Angular 1.7 and D3 is included by default in the Live Center, and you can add any other library as an “internal library” extension.

The YouTube inline editor extension, for example, uses renderAddPanel to give users a way to provide a YouTube URL prior to adding anything to the post:

public renderAddPanel(element: HTMLElement, mediaElement: HTMLElement, submitFunc: (success: boolean) => any) {
    var div = $("<div class='input-group'></div>");
    var field = $("<input class='form-control'  placeholder='" + NcLiveCenter.translate("youTubePlaceholder") + "'/>");
    var addButton = $("<button class='btn btn-success'>" + NcLiveCenter.translate("youTubeAdd") + "</button>");

    div.append(field);
    div.append($("<span class='input-group-btn'></span>").append(addButton));

    // logic for escaping panel!
    field[0].addEventListener("keyup", (event) => {
        if (event.keyCode === 27) {
            submitFunc(false);
        }
    });

    addButton.click(() => {
        var rawLink: string = field.val();

        function getId(rawLink) {
            var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
            var match = rawLink.match(regExp);

            if (match && match[2].length == 11) {
                return match[2];
            } else {
                return false;
            }
        }

        const foundId = getId(rawLink);

        //Pasting of an iframe
        if (rawLink.indexOf("<iframe") === 0) {

            mediaElement.setAttribute("data-iframe", rawLink + "");
            mediaElement.setAttribute("data-src", foundId + "");
            mediaElement.setAttribute("data-priority", "100");
            submitFunc(true);

        } else if (foundId) {

            mediaElement.setAttribute("data-src", foundId + "");
            mediaElement.setAttribute("data-priority", "100");
            submitFunc(true);

        } else {
            submitFunc(false);
        }

    });

    element.appendChild(div[0]);
    field.focus();
}

renderInlinePanel

The optional “renderInlinePanel” function on the editor object allows you to build out any UI you would need after an element is added to a post.

The function takes three arguments:

  •  element: the HTML DOM element you can render any UI to
  •  mediaElement: the HTML DOM element of type “ncpost-content” that is included in the post. You can mutate this object and update any attributes
  •  renderPreviewFunc: a function you can call to update the preview.

The inline image extension, for example, uses renderInlinePanel to give users a way to add a description, source or a URL to an image after it’s added to the post: 

Example implementation of the image editors renderInlinePanel function, using TypeScript, jQuery, and NcHtml (our internal DOM manipulation convenience library):

public renderInlinePanel(element: HTMLElement, mediaElement: HTMLElement, updatePreview: () => any) {

    const row = NcHtml.append(element, "div.row", { style: "margin-top: 10px" });
    const left = NcHtml.append(row, "div.col-sm-4", { style: "padding-right: 0; text-align: center" });
    const right = NcHtml.append(row, "div.col-sm-8");

    NcHtml.append(left, "img", {
        style: "max-width: 100%; max-height: 140px; margin-bottom: 10px;",
        src: mediaElement.getAttribute("data-src")
    });


    let div = $("<div class='nc-input-box'><div class='input-icon'><i class='fa fa-pencil fa-fw'></i></div></div>");
    const desc = $("<input class='form-control input-field' placeholder='" + NcLiveCenter.translate("imageInlineDescLabel") + "'/>");
    desc.val(mediaElement.getAttribute("data-caption"));
    div.append(desc);
    $(right).append(div);

    div = $("<div class='nc-input-box' style='margin-top: 8px;'><div class='input-icon'><i class='fa fa-quote-left fa-fw'></i></div></div>");
    const source = $("<input class='form-control input-field' placeholder='" + NcLiveCenter.translate("imageInlineSourceLabel") + "'/>");
    source.val(mediaElement.getAttribute("data-source"));
    div.append(source);
    $(right).append(div);

    div = $("<div class='nc-input-box' style='margin-top: 8px;'><div class='input-icon'><i class='fa fa-chain fa-fw'></i></div></div>");
    const link = $("<input class='form-control input-field' placeholder='" + NcLiveCenter.translate("toolbarUrl") + "'/>");
    link.val(mediaElement.getAttribute("data-url"));
    div.append(link);
    $(right).append(div);

    function update() {
        mediaElement.setAttribute("data-caption", desc.val());
        mediaElement.setAttribute("data-source", source.val());
        mediaElement.setAttribute("data-url", link.val());
        updatePreview();
    }

    source.keyup(() => update());
    desc.keyup(() => update());
    link.keyup(() => update());

    source.change(() => update());
    desc.change(() => update());
    link.change(() => update());
}

Inline Renderer

Similarly to the editor, we need to add an inline renderer extension at https://livecenter.norkon.net/LiveCenter/#!/Extensions/ to have it show up in the Live Center and act on any media elements added by inline editors. The inline renderers are exposed both to the internal Live Center, and publicly through the public JavaScript extensions endpoint. You may choose to bundle your renderer with your own code and not use the extension endpoint at all, in which case you need to let NcPosts know of the existence of your inline renderer.

If you’re using the NcPosts.start function and want to add an inline renderer manually without using extensions, you can provide a “extraInlineRenderers” property as an array, with an instance of your inline renderer as an element in the array. See https://livecenterdemo.norkon.net/Docs/StartFunction for more information.

Note: if you’re using live center feeds in native apps and want inline functionality, you need to implement native implementations using the media elements.

Note: if you’re using the JavaScript extension API endpoint (also used by framed embeds), there is a cache of 1 minute for public extensions. Hence, it may take some time for your extension changes to reach your users.

The object we need to return to power an inline render is also defined below the JavaScript input area:

Return an object that implements the following interface: 

Essential:
interface InlineRenderer {
    typeKey: string,
    renderElement(element: HTMLElement, mediaElement: HTMLElement);
}

Extended:
interface InlineRenderer {
    typeKey: string,
    preRender?: boolean,
    renderElement(element: HTMLElement, mediaElement: HTMLElement, domReadyRegistrator: (callback: any) => void): (Updater | void);
}

Simplest form of an inline renderer extension

We’ll add a renderer for the “INLINE-TEST” renderer defined above:

return {

   typeKey: "INLINE-TEST",

   renderElement: function(element){

        element.innerText = "Hello world!";

   }

}

When saved, adding the inline extension to a post now results in this:

Extended Inline Renderer

The extended inline renderer comes with one more property you can set: preRender. If preRender is set to true, it will generate the innerHTML of the mediaElement before sending it out to any clients using the inline renderers renderElement function. Not all renderers support this, so if your renderer does, you can opt in for performance.

The renderElement also comes with an additional argument you can use: domReadyRegistrator. This allows you to postpone any rendering of the element until the document is ready. If you’re rendering charts, for instance, it’s a good idea to wait for the dom to be ready, so the with of the post is calculated prior to any drawing.

The renderElement can also return an object that can be used to trigger any updates. This enables charts, for instance, to animate on changes instead of re-render the entire element. If no object is returned, the entire element will be re-rendered on change.

The optional object returned from renderElement should contain a function called “update”, and the update function will receive a new mediaElement as a single argument. If the update function returns a truthy value the update is considered a success and no futher action is taken. If it returns a falsy value it will re-render the entire element.

The horizontal bar chart extension uses this functionality to animate changes instead of re-rendering everything:

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.

Still need help? How can we help? How can we help?