ReactJS and Orchard Collaboration, Part 1

It is a while that I didn’t touch this web-blog. Now, finally I have time to write some posts regarding technical parts of Orchard Collaboration. In this post, I go into the detail of using Reactjs inside Orchard Collaboration. Few months ago, I demo the idea in the Orchard weekly meeting, but I didn’t have time to write about it.

Orchard as a CMS, is very flexible in generating Content. Orchard Views are based on Shape pattern. Shapes provide a flexible mechanism to render any kind of data. A Shape can contain other shapes, it can be configured to use alternatives, we can put wrappers around the shapes..., it has many features. The following diagram illustrates how Shapes in Orchard works.

But in many use-cases, we need interactive content with Rich UI and in complex scenarios, using the old JQuery stuff alongside Razor views is not enough, we need a modern client side technology. My requirement regarding selecting a suitable client side technology is keeping all of the benefits provided by Orchard and its amazing Shape pattern. Most of the client-side technologies relies on Server-side REST API, that provide pure data to the client, and in the client, the views have been populated by the data received from the server. This architecture doesn’t care about the server side rendering and in our case for Orchard Shapes.

What I was looking for, is sending the Shapes as JSON data and render them in the client side using javascript. In this approach, we can enjoy rich-UI in the client along-side all of the functionality provided by Shapes.

After analyzing some client side technologies, I discovered that we can create a bridge between Orchard Shapes and Reactjs components. Reactjs components can do the same thing done by Razor views. The following diagram represents the idea.

To achieve the goal, I create a ReactJS component that does exactly the same thing that is done by Display helper method in Razor. Here is its code: (of course it doesn’t have all of the functionality of the Display Helper function, but it covers the main functionality.

var Display = React.createClass({
        render: function () {

            var shape = this.props.shape;
            var root = this.props.root;

            var containerTag = this.props.containerTag;

            containerTag = containerTag || "div";
            var notWrap = this.props.notWrap || false;

            // render nothing if there is no shape
            if (shape == null) {
                return null;
            }

            var zoneTypes = ["Content", "ContentZone"];
            if (shape.Metadata &&
                zoneTypes.indexOf(shape.Metadata.Type) < 0) {
                var componentName = shape.Metadata.Type;

                if (shape.Metadata.Alternates && shape.Metadata.Alternates.length > 0) {
                    for (var i = 0; i < shape.Metadata.Alternates.length; i++) {
                        if (orchardcollaboration.react.allComponents[shape.Metadata.Alternates[i]] != null) {
                            componentName = shape.Metadata.Alternates[i];
                            break;
                        }
                    }
                }

                if (typeof orchardcollaboration.react.allComponents[componentName] !== "undefined") {
                    return React.createElement(orchardcollaboration.react.allComponents[componentName], { Model: shape.Model, root: root, Classes: shape.Classes, ContentPart: shape.ContentPart, ContentItem: shape.ContentItem });
                }
            };

            if (shape.Items && shape.Items.length > 0) {
                var items = shape.Items.map(function (item) {
                    return React.createElement(Display, { shape: item, root: root });
                });

                return items.length == 1 ? items[0] : React.createElement(containerTag, null, items);
            }

            if (shape.length && shape.length > 0) {
                var items = shape.map(function (item) {
                    return React.createElement(Display, { shape: item, root: root });
                });

                return items.length == 1 ? items[0] : React.createElement(containerTag, null, items);
            }
            if (shape.Content && shape.Content.length > 0) {
                shape.Content.sort(function (a, b) {
                    if (a.Metadata.Position > b.Metadata.Position) {
                        return 1;
                    }
                    else {
                        return -1;
                    }
                })

                var contentItems = shape.Content;
                if (notWrap) {
                    contentItems = [shape.Content[0]];
                    shape.Content[0].NextSiblings = shape.Content.slice(1, shape.Content.length - 1);
                }

                var contentItemsComponents = contentItems.map(function (content) {
                    return React.createElement(Display, { shape: content, root: root, Classes: shape.Classes });
                });

                return contentItemsComponents.length == 1 ? contentItemsComponents[0] : React.createElement(containerTag, null, contentItemsComponents);
            }

            return null;
        }
    });
    orchardcollaboration.react.allComponents.Display = Display;

Corresponding to any Shape (View) that we have in Razor, I created a Reactjs component. The Display component successfully find them and render them. Milestone part in the Orchardcollaboration have been implemented using this approach. Another thing that we also have to do is serializing the Shapes and send them as JSON to the client. In the next post, I will explain the steps needed to serialize Shapes.


No Comments

Post Reply