Giter Club home page Giter Club logo

angular-standalone-renderer's Introduction

Progress® Sitefinity® CMS sample angular standalone renderer app

NOTE: Latest supported version: Sitefinity CMS 14.2.7924.0

Overview

The sample code in this repo implements a decoupled frontend SPA renderer for Sitefinity CMS. It uses the Sitefinity Layout API services to render the layout and widget content in Sitefinity MVC pages. This implementation also works with personalized pages and personalized widgets to enable per-user content personalization. The sample code uses the Angular framework.

Who is this sample for

Angular developers that wish to develop with Sitefinity CMS and the Angular framework and utilize the WYSIWYG page editor.

How does it work

The WYSIWYG page editor of Sitefinity works with reusable components called widgets. Leveraging the power of the editor, developers can build custom SPA frontends for their users. This sample demonstrates how to integrate a custom front-end framework (such as Angular) in the page editor.

The whole renderer framework for the angular renderer is already built (including integration with the WYSIWYG editor), so all there is to do is just write 'Angular widgets'. Developing widgets for the Angular Renderer is just like developing plain Angular Components. There are some integration points which we will go through. For this purpose find the bellow Hello World tutorial

Hello World sample

Building the component

In order to build our own custom widget, we need to first create a folder that will hold our files. We will name our widget - ‘Hello World’ and it will be placed in the ‘hello-world' folder(under src/app/components). We then create a file in that folder called ‘hello-world.component.ts’ that will host our angular component. It will have the following content:

import { Component } from "@angular/core";
import { BaseComponent } from "../base.component";
import { HelloWorldEntity } from "./hello-world-entity";

@Component({
    templateUrl: "hello-world.component.html",
    selector: "app-hello"
})
export class HelloWorldComponent extends BaseComponent<HelloWorldEntity> {

}

We need to add this component to our app.module.ts file in both 'declarations' and 'entrycomponents'. Additionally add the 'app-hello' tag name to the styles.scss file to make the tag a block element.

Building the entity

Create a file called 'hello-world-entity.ts' with the following contents:

export class HelloWorldEntity {
    Message!: string;
}

**The entity class will hold the properties that are populated through our designer. We will have a single property called 'Message'. **

Building the html file

We add a simple html file called - 'hello-world.component.html' with the following contents:

<h1>{{Properties.Message}}</h1>

The data entered from the designer will be under the Properties object

Building the designer

Second - we need to define the designer. This is done by creating a 'designer-metadata.json file' (name does not matter) and it will hold the metadata that will be read from the widget designer in order to construct the designer.

{
   "Name":"HelloWorld",
   "Caption":"HelloWorld",
   "PropertyMetadata":[
      {
         "Name":"Basic",
         "Sections":[
            {
               "Name":"Main",
               "Title":null,
               "Properties":[
                  {
                     "Name":"Message",
                     "DefaultValue":null,
                     "Title":"Message",
                     "Type":"string",
                     "SectionName":null,
                     "CategoryName":null,
                     "Properties":{
                        
                     },
                     "TypeChildProperties":[
                        
                     ],
                     "Position":0
                  }
               ],
               "CategoryName":"Basic"
            }
         ]
      },
      {
         "Name":"Advanced",
         "Sections":[
            {
               "Name":"AdvancedMain",
               "Title":null,
               "Properties":[
                  
               ],
               "CategoryName":null
            }
         ]
      }
   ],
   "PropertyMetadataFlat":[
      {
         "Name":"Message",
         "DefaultValue":null,
         "Title":"Message",
         "Type":"string",
         "SectionName":null,
         "CategoryName":null,
         "Properties":{
            
         },
         "TypeChildProperties":[
            
         ],
         "Position":0
      }
   ]
}

Registration with the renderer framework

Once we have the above two files ready, we need to register the component implementation and the designer metadata with the Angular Renderer.

For the component we need to go to the file render-widget-service and to add a new entry to the TYPES_MAP object like so:

import { HelloWorldComponent } from "../components/hello-world/hello-world.component";

export const TYPES_MAP: { [key: string]: Function } = {
    "SitefinityContentBlock": ContentComponent,
    "SitefinitySection": SectionComponent,
    "SitefinityContentList": ContentListComponent,
    "HelloWorld": HelloWorldComponent
};

For the designer we need to go to the file renderer-contract and in the metadataMap object to add a new entry like so:

import helloWorldJson from '../components/hello-world/designer-metadata.json';

@Injectable()
export class RendererContractImpl implements RendererContract {

    private metadataMap: { [key: string]: any } = {
        "SitefinityContentBlock": sitefinityContentBlockJson,
        "SitefinitySection": sitefinitySectionJson,
        "SitefinityContentList": sitefinityContentListJson,
        "HelloWorld": helloWorldJson
    }

Finally we need to register the widget to be shown in the widget selector interface. Go to the file content-widgets.json and add a new entry after the SitefinityContentBlock registration:

{ 
    "name": "HelloWorld", 
    "addWidgetName": null, 
    "addWidgetTitle": null, 
    "title": "Hello world", 
    "initialProperties": [] 
} 

Notice that everywhere above we are using the 'HelloWorld' name to register our component. This is a unique identifier for out component and is used everywhere where it is referenced.

Deployment

In order to minimize the cost and not host two applications (as the case with the .NET Renderer), the developer can host the production files on the file system of the CMS application under the following folder template(casing is important for the renderer folder):

/sitefinity/public/renderers/{rendererName}
/sitefinity/public/renderers/Angular
/sitefinity/public/renderers/React

The above folders can be used for development as well. Just configure the output folder for the build. After the files are deployed, reloading a page will take into account the new files.

NOTE Be sure to configure the deployUrl property in angular.json. Currently it is configured as '/sitefinity/public/renderers/Angular' in two places. Both need to be replaced if you plan on develop with the Sitefinity .NET Renderer to '/sfrenderer/renderers/Angular'

Deep dive

Building the component

All of the components must inherit from the BaseComponent class and must define their own entity class as a first generic argument. This 'entity' class will hold the properties that will be populated through the widget designer interface. So, when defining an 'Angular Widget', you will be working with the values that are entered through the automatically generated widget designer. Once your widget inherits from the base component you will automaitcally have access to the Model property, where TEntityType is the type of the object that will be populated through the designer interface. When the widgets are rendered in 'edit mode', the widget wrapping element is decorated with some additional attributes. These attributes are added only in edit mode and are not added on the live rendering. They are needed so the page editor knows which type of widget is currently being rendered.

Each widget must have a wrapper tag. Having two tags side by side on root level of the html of the component is not supported.

Building the designer

The fields that appear are defined through a JSON file in the 'renderer-contract.ts' file. All the capabilities of the automatic widget designers are available for all the types of renderers delivering a universal UI. We have provided an all-properties.json file that holds all the available combinations for properties that you can use for your widgets, and you can simply copy-paste from there. Additionally, we have provided three widget implementations that each have their own designer file as a reference.

Some clarification on the schema of the json file. It holds the followin properites:

  1. Name – This much match the name of the widget with which it is registered in the renderer-contract.ts file and the render-widget-service.ts file.
  2. Caption – The user friendly name of the widget that will be presented when the designer is opened
  3. PropertyMetadata and PropertyMetadata flat both hold the metadata for all of the properites, with the exception that PropertyMetadata holds the metadata in a hierarchical manner. This property is used to construct widget designers. Whereas the flat collection is used for validation.
  4. The more used metadata properties that define each field are: Name – the name of the field, DefaultValue – the default value Title – user friendly name of the field, Type - the type of the property – see the all-properties.json file for all types of fields. SectionName: the name of the section in which this field belongs CategoryName: null for Basic or Advanced Properties: holds additional metadata properties - see the all-properties.json file for more examples.

angular-standalone-renderer's People

Stargazers

 avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

angular-standalone-renderer's Issues

net::ERR_CONTENT_DECODING_FAILED 200 (OK)

hi, i'm getting this error after creating a widget from an angular component:

GET http://localhost:27198/sitefinity/public/renderers/angular/main.4c89fa04e8163b5a.js net::ERR_CONTENT_DECODING_FAILED 200 (OK)
GET http://localhost:27198/sitefinity/public/renderers/angular/polyfills.0b090f6bcb5eabc6.js ::ERR_CONTENT_DECODING_FAILED 200 (OK)
GET http://localhost:27198/sitefinity/public/renderers/angular/assets/css/bootstrap.min.css net::ERR_CONTENT_DECODING_FAILED 200 (OK)
GET http://localhost:27198/sitefinity/public/renderers/angular/assets/css/main.min.css net::ERR_CONTENT_DECODING_FAILED 200 (OK)

this doesn't happen if i create a simple component like HelloWorld, but if i add a more complex component with a lot of .js and .css i'm getting this error. how can i solve this? thanks

No contract for communication with the zone editor was found on the rendered page.

hi.
after creating a template, and going to select a widget I got this message:
No contract for communication with the zone editor was found on the rendered page.

and in the console i got this:

GET http://localhost:58289/styles.ef46db3751d8e999.css net::ERR_ABORTED 404 (Not Found)
GET http://localhost:58289/runtime.6a9ae211c5621779.js net::ERR_ABORTED 404 (Not Found)
GET http://localhost:58289/main.88ce3c915b5c1500.js net::ERR_ABORTED 404 (Not Found)
GET http://localhost:58289/polyfills.0b090f6bcb5eabc6.js net::ERR_ABORTED 404 (Not Found)

how can i solve this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.