Headless CMS: Enable In-Context Preview and Editing in an External Application

Do you have an existing application that has content in it that you want to manage but you don’t want to completely rebuild in a CMS? This use case is more common than you can imagine. Developers start building an application only much later to find it would benefit from CMS authoring capabilities like in-context editing and preview. What’s the solution? Rebuild the application? No. Crafter CMS is a headless CMS that’s front-end agnostic. It can plug into any application.

Let’s look at a very simple example. Just recently I posted a blog that used Node JS an external application that called Crafter CMS headless CMS capabilities for content via APIs.

In the screenshot above the content “Hi, I’m Editorial by HTML5 UP” is being served to the NodeJS application via APIs.

Content authors are used to working with in-context editing and preview as seen below:

In this blog, I’ll extend the example above to show how to enable those features for your content authors. The techniques shown in this blog are applicable to any web-based front-end technology. Crafter CMS provides additional help for specific frameworks like React JS. These are/will be covered in other blogs.

Getting Started

Follow the instructions in this blog to get an external application that pulls editable content from Crafter CMS via API. If you prefer to work the browser rather than Node JS, here is another example of a simple application that runs entirely in the browser. For this blog, I’ll use the Node JS example.

Once you have completed this step you should be able to:

  1. Make a change to the hero text in the editorial website via Crafter Studio
  2. See the change in your external application running in Node JS

Enabling Preview

The next step is to enable preview for content authors so they can see how the content will look in the external application. To keep things simple in our example I will change the Editorial site to preview the external application. In some cases, you may wish to leave the Editorial site/authoring experience as is and create a new project for the External app. Let’s keep to the basics.

Step 1: Configure the Site Preview

  1. In the Editorial site, click on the Sidebar to open the Sidebar menu.
  2. Click Site Config to go to site administration.
  3. On the Site Config console, click Configuration, then choose Site Configuration from the drop-down.
  4. Add the following line to the XML in the location shown and click Save.
    <previewServer>http://localhost:3000</previewServer>

  5. Click the Crafter logo to return to the content authoring for the site.
  6. Go to preview for the home page and you will see your Node JS application showing in preview.

With the configuration made, Crafter Studio now looks to the external application for its preview. As long as the external application is pointing to APIs running the Crafter Engine supporting preview you will be able to make edits and see them in the preview.

Enabling In-Context Editing

Crafter Studio makes it easy to get to the content you want to edit. The Sidebar, Dashboard and other UI elements make navigating to the content quick and easy. In-Context Editing is the ability to click on elements in your preview, edit them and see the change. This is a much more visual way of locating and editing content because its location in the information architecture doesn’t matter.

In Crafter Studio you turn on in-context editing by clicking on the pencil in the toolbar at the top of the screen. This enables pencils throughout your preview that allows you to edit the related elements directly as shown in the picture above.

In-Context editing is an integration with Crafter Studio, there’s a collaboration of sorts between the Studio application and the site/application being previewed that enables the in-context editing features like pencils and drag and drop. In this blog, we’re going to show a simple, framework agnostic way to support this integration. Crafter CMS has additional libraries for specific frameworks like React JS, Angular, Freemarker etc that encapsulate many of these details.

Step 1: Enable Communication

To enable communication between your application preview and Crafter Studio you must include a tiny piece of Javascript we call the “Guest” library.

To do this add the following include to the bottom of your page

<script src=”http:/studio/static-assets/libs/requirejs/require.js” data-main=”http:/studio/overlayhook?page=x&site=id&cs.js”></script>

In our Node JS example we’re going to edit directly though the application. To enable this, add the following to your package.json:

“proxy”: {
“/api”: { “target”: “http://localhost:8080” },
“/studio”: { “target”: “http://localhost:8080” },
“/static-assets”: { “target”: “http://localhost:8080” }
},

Step 2: Indicate Where the In-context Editing Regions “Pencils” Belong

To enable point and click editing on content you must indicate where the regions are and what content the regions relate to. To do this in HTML5 applications Crafter Studio (and the Guest library) uses HTML5 attributes.

The following attributes are used:

  • data-studio-ice: This property marks the element as the container for in-context editing. No value is required.
  • data-studio-component-path: This is the path of the content object. Example: “/site/products/a-component.xml”
  • data-studio-ice-path: This is the path of the content object. Example: “/site/products/a-component.xml”
  • data-studio-component: This is the content type name. Example: “/component/product”

Example Container element:

<div data-studio-component-path=”/site/products/a-component.xml”
data-studio-component=”/component/product”
data-studio-ice=””
data-studio-ice-path=”/site/products/a-component.xml”>
INNER HTML
</div>

Step 3: Try it!

Once you have marked elements in the application with the proper metadata you are ready to begin editing.

  1. Log in to Crafter Studio (for our example: http://localhost:3000/studio)
  2. Go to the preview for the application
  3. Turn on in-context editing by clicking on the pencil icon in the main navigation menu.

  4. Click the pencils and start editing🙂  It’s as easy as that.

Conclusion

Many times developers want to content enable an application that is external to their CMS. Either the application was built before it needed CMS features or the developers built outside a CMS to avoid being constrained by traditional CMS frameworks. Headless CMS platforms are Content / API first CMS architectures that loosely couple your application to the CMS. This new architecture has enabled a host of new content managed channels including native mobile apps and new user-friendly app frameworks like ReactJS and AngularJS. The problem is that almost no CMS out there supports preview and editing of the applications and channels that pull content from them. Here Crafter CMS is different. Crafter CMS has always been decoupled and its architecture has always been agnostic to the frontend. Crafter CMS is a headless CMS that supports the authoring experience your content authors are accustomed to.

CMS for SPAs Tutorial: Using React and Node JS with Crafter CMS

The most traditional, full-featured CMS platforms are not designed to handle headless content and most headless CMS platforms aren’t full-featured and have only basic authoring support.

Crafter CMS is both full-featured and fully supports headless CMS capabilities. That’s pretty unique. In this tutorial, you will learn how to create a content-rich, ReactJS-based Single Page Application (SPA) with in-context editing and other full-featured authoring capabilities.

Our React Application

We need an application to base our tutorial on. For our example we have the following criteria:

  • An existing, open source application that serves a content-rich use case where the content in the application is likely to be updated regularly by non-developers.
  • The application is sophisticated enough to be interesting but not too complex to be understood.
  • The application looks good and is responsive.
  • The application is based on React JS.

With these criteria in mind, I found this terrific open source React-based shopping cart application by Jefferson Ribeiro: https://github.com/jeffersonRibeiro/react-shopping-cart, Thanks, Jefferson!

We’re going to break our tutorial down into a number of broad steps:

Step 1: Download and run the application locally. First, thing’s first let’s get our local application development environment (React and NodeJS) up and running. Here we’ll see that the application already relies on a RESTful service to acquire its content.

Step 2: Create a project in Crafter CMS and set it up to preview our React application running in Node JS. Now that our application is running let’s get the CMS set up and previewing our application.

Step 3: Model a product in Crafter CMS. We have an application and we have a CMS. It’s time to connect them. Here we’ll learn how to model content and we’ll do some minor configuration along the way to customize the Studio UI for the use case.

Step 4: Create a basic RESTful service. Now that we have content, we can create a RESTful service to return the content from the CMS as JSON. Once our service is in place we’ll modify the application to consume our service instead of the service it uses out of the box.

Step 5: Extend the service to include images managed by authors. The out of the box application uses statically managed images and convention. We’ll add images to the CMS to demonstrate extending our content model and app.

Step 6: Add in-context editing to the solution. Now that the app’s content is driven by the CMS we want to make it easier for the content authors to find and edit their content. Making content editable directly through the app is one of the best ways. In this step, we’ll make the small modifications and configurations necessary to support this.

Step 7: Considerations for operationalizing the solution. Now that we have a working solution we’ll talk through some additional thoughts around development process integration and production operations.

Let’s get started!

Prerequisites for the Tutorial

  • NPM / Yarn installed
    • NPM will help you easily download the proper dependencies. You can also use NPM or Yarn to build and execute your application.
    • In our example, I will use Yarn to manage my package.json file and to assist me in building my application.
  • Access to a running Crafter Studio instance.
    • You will need access to create a site and modify its configuration.

Step 1: Download and run the application locally

The first step is to download the application and make sure we’ve got a local environment that will allow us to modify and run the application. This step does not involve the CMS. In many ways, it emulates a common real-world scenario in which the need for authors to modify content without the help of develops is discovered late in the development process or even post-launch. Let’s begin.

  1. On your local machine clone Jefferson Ribeiro’s project: https://github.com/jeffersonRibeiro/react-shopping-cart,
  2. Change directory into the react-shopping-cart folder that was created and run yarn install
    This will install the libraries necessary to run your application.
  3. Run the React application by executing yarn start.
    This will start a NodeJS and run the react application.
  4. Open your browser to http://localhost:3000

This application is pulling content for the product catalog in a JSON feed format from the following URL:https://react-shopping-cart-67954.firebaseio.com/products.json

Step 2: Create a project in Crafter CMS and set it up to preview our React application running in Node JS

Now that our application is running let’s get the CMS set up and previewing our application. The first thing that you need to accomplish this is a running instance of Crafter Studio, the authoring component of Crafter CMS. You can learn how to get this set up here.

  1. Log in to Crafter Studio as the administrator and on Site Dashboard, click the Create Site button.
  2. Following the picture above, populate the dialog. Give the site an ID and select “Empty” blueprint then click the Create button.  Once complete, you will see your empty site previewed in the Crafter Studio application.  Now we want to tell Crafter Studio that our application is not running in Crafter Engine, but instead on NodeJS @ port 3000.
  3. Open the sidebar by clicking the Sidebar button in the navigation.  Then navigate to the site configuration panel by clicking on the Site Config item in the Sidebar menu.

  4. Open the main Studio configuration for the site by clicking the Configuration menu item then selecting the Site Configuration option in the drop-down.
  5. Add the following tag (<previewServer>http://localhost:3000</previewServer>) to the configuration as shown below and then click the Save button.
  6. Click on the Crafter logo up in the upper left-hand corner to return to the dashboard.
  7. In the Sidebar, open Pages and click on Home Page and you will see your site that is running in NodeJS within the preview window of Crafter Studio. At this point, we have both our application (running in NodeJS) and our CMS running with the ability to preview our application.  That means we can continue to use our standard development tools and practices for the React application while integrating it into the CMS.

Step 3: Model a product in Crafter CMS

Now that the two main components (our app and our CMS) are up and running it’s time to connect them.  To start we need to describe the structure of our content, in this case, a product.  To understand what the app is looking for we can examine the UI or because we already know the application is reading a RESTful JSON API from Firebase we can look at that.

Step 3 A: model the content


It’s clear that each product has a title, a description, a style, a price and so on.  To model these in the CMS we must add a new content type.

  1. Open the Content Type administration tool by opening the Sidebar and clicking on the Site Config item within the Sidebar. In the Site Config panel, click the Content Types item, then click on Create New Type
  2. Fill out the dialog by labeling the content type “Product“, select “Component” for the type and then click the Create button.  Once you click create you will be taken to the schema editor.
  3. Drag and drop the controls and from the Controls area to model the product.
  4. Remember to set property values for each control including constraints. To configure a control: click on it and its properties will appear in the property sheet on the right.

  5. Note that for our product sizes I chose to give the author a list of sizes to click on.  Since a product may come in many sizes I’ve used a group checkbox control.
  6. Group checkboxes require a data source.  To keep things really simple let’s supply a static list of sizes with the key-value data source.  Drag the data source from Data Sources and drop it on the form editor under the purple data sources region.
  7. Click the Options property in the data source and then add a row for each option as shown above.
  8. Remember to hook the data source to the control.  To do this, click on the group checkbox control and find the Data Source property.  Select your data source.
  9. Once the model is complete, click the Save button at the bottom of the screen.
  10. Create a content and the sidebar config.

Step 3 B: Configure the UI for Products

Once you save your content type it will be available for content authors to use it.  Studio does this automatically. That said, the more tailored the UI is for your content authors, the easier it will be for them to use it.
Note that this step is not necessary to complete the integration but it does make a difference for authors.

  1. If you click the Crafter CMS logo, you will be taken to the dashboard.  Open the Pages folder on the Sidebar and note that you can right-click on the Home page.  In the drop-down menu, you will find an option for “New Content” as shown below.
  2. Click on the New Content option and you will see your new content type as shown below.A product doesn’t really map to any of the out-of-the-box content folders.  It’s not a page and it’s not a component.  What we want is a unique store just for products.  Cancel the create operation and let’s set up a folder for products and then tie the product content type to it.
  3. Using the Sidebar, navigate to the Site Config panel and then to the Configuration tool.
    Select the Sidebar Configuration tool.
  4. In the configuration, you will note the various modules shown in the Sidebar navigation.  Add a new module block as shown below for products.  You’ll note that at first, we’ll “root” the folder at “/site.”  We’re going to change this later but for now, setting it up this way will allow us to create additional folder structure within the CMS.  Add the module configuration snippet and click the Save button.

    <modulehook>
      <name>wcm-root-folder</name>
      <params>
        <label>Products</label>
        <path>/site</path>
        <showRootItem>true</showRootItem>
        <onClick>preview</onClick>
       </params>
    </modulehook>
  5. Once complete, navigate back out to the dashboard via the Crafter CMS logo and open the Sidebar.  You will find your new Products folder displayed.
  6. Open the products folder, right-click the site folder and then click the New Folder option.  When prompted for a name, enter products and click the Create button.
  7. Now, return to the Sidebar Configuration.  Let’s update the folder so that it shows only the products.  We do this by modifying the path parameter for the module.  Further, we’ll add some custom icons to help visually differentiate the folder within the sidebar.

    <modulehook>
      <name>wcm-root-folder</name>
      <params>
        <label>Products</label>
        <module-icon-open>
          <class>fa-shopping-bag</class>
        </module-icon-open>
        <module-icon-closed>
          <class>fa-shopping-bag</class>
        </module-icon-closed>
        <path>/site/products</path>
        <showRootItem>true</showRootItem>
        <onClick>preview</onClick>
      </params>
    </modulehook>
  8. Now let’s update the Studio Site config so that Studio better understands the content found under the /site/products path.  In the Configuration tool open Site ConfigurationAdd configuration under folders and patterns as shown below:
    
    
    <folders>
      <folder name="Pages" path="/website" read-direct-children="false" attach-root-prefix="true"/>
      <folder name="Products" path="/products" read-direct-children="false" attach-root-prefix="true"/>
      <folder name="Components" path="/components" read-direct-children="false" attach-root-prefix="true"/>
      <folder name="Assets" path="/static-assets" read-direct-children="false" attach-root-prefix="false"/>
      <folder name="Templates" path="/templates" read-direct-children="false" attach-root-prefix="false"/>
    </folders>
    ...
    <pattern-group name="component">
      <pattern>/site/components/([^&lt;]+)\.xml</pattern>
      <pattern>/site/products/([^&lt;]+)\.xml</pattern>
      <pattern>/site/system/page-components/([^&lt;]+)\.xml</pattern>
      <pattern>/site/component-bindings/([^&lt;]+)\.xml</pattern>
      <pattern>/site/indexes/([^&lt;]+)\.xml</pattern>
      <pattern>/site/resources/([^&lt;]+)\.xml</pattern>
    </pattern-group>
  9. Once you save this file and return to the dashboard or preview you will note that Products now has its own icon and when you open it, it is specifically rooted in the products folder in the repository.
  10. Modify the content types to tie them to the IA.  An author should not be able to create a product anywhere except under products.  Other content types may also be tied to specific folders.  Let’s look at how we can configure this.In the content type editor for a product, under the Basic Properties for the type, find the Configuration Property.  Click the pencil to open the editor for the configuration.
    Add the following XML snippet to the content type configuration:

    <paths>
      <includes>
        <pattern>^/site/products/.*</pattern>
      </includes>
    </paths>


    Open the other 2 content types and add the following snippet.  This snippet hides these types because it does not make sense for authors to be able to create new objects of these types.

    <paths>
      <includes>
        <pattern>^hidden</pattern>
      </includes>
    </paths>
  11. Navigate to the dashboard and try to create content in the products area.  If the previous steps were performed correctly the form for the Product content type will be automatically launched as soon as you click the New Content button.  Studio understands (based on the configuration) that this is the only type that is allowed to be used here and thus defaults to it.

    The form will automatically open:

 

Step 3 C: Create Product Content

Now that your authoring tools are set up to make it easy to add products it’s time to create some product content.  Using the New content option, and following the content on the site (or in the JSON response) create at least two products by right clicking on the product folder then filling out the product form and saving.


  1. You can easily create new content by right-clicking on the products folder and selecting the New Content option.
  2. Create as many products as you like by filling out the Product form and clicking the Save and Close button.  You will need at least 2 products for the example to work well.

Step 4: Create a basic RESTful service

Now that we have content, we can create a RESTful service to return the content from the CMS as JSON. Once our service is in place we’ll modify the application to consume our service instead of the service it uses out of the box.

Step 4-1: Create the Service

Crafter allows us to easily create RESTful services through scripting.  By putting the Groovy script in the right location with the right naming convention we define an RESTful endpoint. https://docs.craftercms.org/en/3.0/developers/projects/engine/api/groovy-api.html

  1. In the Sidebar, open the Scripts folder and then open the rest folder.
  2. On the rest folder right click and choose Create Controller option.
  3. Provide your service name and http method you want.  In our case our service is going to return products so our service name is “products” and the HTTP method the service will respond on is a GET so we append .get to the name.
    1. This is a convention in Crafter CMS.  Under the rest folder we can create any number of sub folders and the file name of the RESTful endpoint is named by the script as NAME.HTTPD_METHOD.groovy.
    2. all standard request methods are supported ex: get, post, put, delete,
  4. Click Create and then supply some basic code in your controller as shown below.
    This code will result in an object with an empty products array.  By returning the result value at the end of the script Crafter knows to marshal this object in to JSON for us when the service is invoked via the URL.
  5. Test the service.
    1. Go to http://localhost:8080/api/products.json in your browser and you will see the JSON representation of the object returned by the script.
  6. Now we that we see our service working we can update it to return the content we want in the structure we want.
    1. Right click on the controller and then click Edit.
    2. Update the code as show below.
def result = [:]

def queryStatement = 'content-type:"/component/product"'

def query = searchService.createQuery()
 query = query.setQuery(queryStatement)
 // limit returned fields if you like
 //query.addParam("fl", "localid:cmsId,sku,title,style,description,price,installments,freeShipping,sizes.item.key")

def executedQuery = searchService.search(query)
def itemsFound = executedQuery.response.numFound

result.products = []

executedQuery.response.documents.eachWithIndex { document, idx ->
 def product = [ 
 id: idx,
 cmsId: document.localId,
 sku: document.sku, 
 title: document.title, 
 style: document.style,
 description: document.description,
 price: getPrice(document),            // potentially get the price from external system
 installments: getInventory(document), // potentially get inventory from external system
 isFreeShipping: document.freeShipping,
 availableSizes: document["sizes.item.key"],
 currencyId: "USD", // hard code USD for now
 currencyFormat: "\$", // hard code currency format for now
 ]
 
 result.products.add(product)
}

return result

def getPrice(document) {
 // simple example of abstracting where price comes from
 return new Float(document.price)
}

def getInventory(document) {
 // simple example of abstracting where inventory comes from
 return new Integer(document.installments)
}

 

The code above makes a query to Crafter Search / Solr and then iterates over the results to format, structure and possibly embellish them with additional data.  Note, that if there is no need to restructure or augment the results differently than the Solr response the results could be returned directly without any additional code.  Simple field name mappings can be done with the fl query parameter.

7. Invoke the response and confirm it now meets the structure and contains the content that the application expects.

Go to http://localhost:8080/api/products.json in your browser and you will see the JSON representation of the object returned by the script.

Step 4-2: Modify the App to use the Service

Now that we have a service which dynamically provides the products that are created and edited/managed by the authors in the CMS we can update our React Application to use it.

To do this:

  1. Change line 5 in src/store/actions/productActions.js to

    const productsAPI = "/api/products.json"
  2. Add the following to package.json
    "proxy": {
      "/api": { "target": "http://localhost:8080" },
      "/studio": { "target": "http://localhost:8080" },
      "/static-assets": { "target": "http://localhost:8080" }
    },
  3. Preview the application at http://localhost:3000
    Note the content showing in the store is from the CMS

Step 5: Extend the service to include images managed by authors

The out of the box application uses statically managed images and convention. We’ll add images to the CMS to demonstrate extending our content model and app. This step is optional and is simply meant to show how you can extend the functionality if choose.

  1. In the product content model:
    1. Add image pickers for both large and small images
    2. Add a data source for picking images from the desktop.
    3. Configure the data source to put images in /static-assets/images
    4. Click on the image picker properties and connect the data sources to the image pickers
  2. Test the forms by updating your products with image content.
  3. Update your RESTful product service to return image values.

    def product = [ 
     id: idx,
     cmsId: document.localId,
     sku: document.sku, 
     title: document.title, 
     style: document.style,
     description: document.description,
     price: getPrice(document), // potentially get the price from external system
     installments: getInventory(document), // potentially get inventory from external system
     isFreeShipping: document.freeShipping,
     availableSizes: document["sizes.item.key"],
     currencyId: "USD", // hard code USD for now
     currencyFormat: "\$", // hard code currency format for now
     largeImage: document.largeImage,
     smallImage: document.smallImage
    ]
  4. Update the application to pull the images from the service rather than assume their location in the application.
    1. Update the file src/components/Product.js line 34
      <Thumb
       classes="shelf-item__thumb"
       src={product.largeImage}
       alt={product.title} />
    2. Update the file: src/components/Thumb.js
      import React from 'react';
      import PropTypes from "prop-types";
      
      
      const Thumb = props => {
       return (
       <div className={props.classes}>
       <img src={props.src} alt={props.alt} title={props.title} />
       </div>
       );
      };
      
      Thumb.propTypes = {
       alt: PropTypes.string,
       title: PropTypes.string,
       classes: PropTypes.string,
       src: PropTypes.string.isRequired,
      };
      
      export default Thumb;
  5. Preview the application at http://localhost:3000
    Note the content showing in the store is from the CMS

Step 6: Add in-context editing to the solution

Now that the app’s content is driven by the CMS we want to make it is easier for the content authors to find and edit their content. Making content editable directly through the app is one of the best ways. In this step, we’ll make the small modifications and configurations necessary to support this.

  1. Note that in our service we are returning a property called cmsId.  This property will be used by the app to identify the content that the editing tools will be associated with.



6.1 Indicate Where the In-context Editing Regions “Pencils” Belong

To enable point and click on content editing on content, you must indicate where the regions are and what content the regions relate to. To do this in HTML5 applications Crafter Studio (and the Guest library) uses HTML5 attributes.

The following attributes are used:

  • data-studio-ice: This property marks the element as the container for in-context editing. No value is required.
  • data-studio-component-path: This is the path of the content object. Example: “/site/products/a-component.xml”
  • data-studio-ice-path: This is the path of the content object. Example: “/site/products/a-component.xml”
  • data-studio-component: This is the content type name. Example: “/component/product”

Example Container element:

<div data-studio-component-path=”/site/products/a-component.xml”
data-studio-component=”/component/product”
data-studio-ice=””
data-studio-ice-path=”/site/products/a-component.xml”>
INNER HTML
</div>

  1. Update the application to pull the images from the service rather than assume their location in the application.
    1. Update the file src/components/Product.js line 34
      <Thumb
       classes="shelf-item__thumb"
       src={product.largeImage}
       alt={product.title}
       cmsId={product.cmsId} />
    2. Update the file: src/components/Thumb.js
      import React from 'react';
      import PropTypes from "prop-types";
      
      
      const Thumb = props => {
       return (
       <div className={props.classes} 
       data-studio-component-path={props.cmsId} 
       data-studio-component="/component/product" 
       data-studio-ice="" 
       data-studio-ice-path={props.cmsId}>
       <img src={props.src} alt={props.alt} title={props.title} />
       </div>
       );
      };
      
      Thumb.propTypes = {
       alt: PropTypes.string,
       title: PropTypes.string,
       classes: PropTypes.string,
       cmsId: PropTypes.string,
       src: PropTypes.string.isRequired,
      };
      
      export default Thumb;
  2. Preview the application at http://localhost:3000/studio
    Note the content showing in the store is from the CMS

  3. Turn the pencils on by clicking on the pencil in the main toolbar.
  4. Hover over each product content item.
  5. Click the pencils to edit and the user will get the form where they can edit the content.
  6. Update the content.
  7. Click the Save and Close button. On save the preview will update to reflect the changes.
  8. That’s it! The preview updates.


Conclusion

 

React has become the de facto way to build highly usable web-based applications.  React helps developers create Single Page Applications that are fast and extremely usable for end users.  When those applications need content that’s managed by business users, developers have turned to headless CMS solutions to supply the content.  Unfortunately, most Headless CMS platforms do not provide full-featured editing tools like in-context editing, preview, and drag-and-drop.  Content editors are used to these tools and when they are missing it can be frustrating.

With Crafter CMS, everybody wins.  Content authors get the experience they are used to.  Developers get the architecture and development frameworks and the process they want.