Contentservice.io

Adding to a project

Creating a new project (in seconds)

Could not be much simpler…

vue create my-project
cd my-project
vue add contentservice
yarn install

You can then view the site in development mode:

yarn serve

Open your browser at http://localhost:8080.

To edit the content of the page press control-option-escape on a Mac or control-alt-escape on Windows.

Note that the content shown is example data. Change it as you wish, but be aware that other people will see anything you enter, and also that the content gets reset regularly without warning so your changes may disappear.

To customise the site, or edit your own content, see the ContentService documentation.

For more information about this and other prefabricated application components, see the ToolTwist website.

Add to a VueJS project

Assuming you have a working VueJS project (similar to one created by Vue CLI):

"dependencies": {
...
"@babel/runtime-corejs2": "^7.4.5",
"bulma": "^0.7.2",
"jquery": "^3.4.1",
"vue-contentservice": "^0.1.76",
"vue-split-panel": "^1.0.4"
...
}
import ContentService from "vue-contentservice"

// Froala (rich text editor) requires jQuery.
const $ = require('jquery')
window.$ = $
window.jQuery = $

require('font-awesome/css/font-awesome.min.css')
require('bulma/css/bulma.min.css')
require('vue-contentservice/dist/vue-contentservice.css')

Vue.use(ContentService, {
//protocol: 'https',
host: 'uat.crowdhound.io',
//port: 443,
version: '2.0',
apikey: 'API10O0X1NS8FWUTO3FXKN15ZOR09',
//froalaActivationKey: FroalaKey
defaultIconPack: 'fa'
});
}
<template>
<div id="app">
<content-layout-editor :editable="editable" :anchor="anchor">
</content-layout-editor>
</div>
</template>

<script>
export default {
name: 'ContentServiceExample',
data: function () {
return {
editable: true,
anchor: 'contentservice.example.layout',
}
}
}
</script>

Adding to a webpage

Adding ContentService to a page can be done in three steps.

In this default mode, pressing Control-Option-Escapeon a Mac or Control-Alt-Escape on Windows, while on the page will enter layout editing mode.

See this jsfiddle for an example or if you wish to to experiment.

Adding to a Nuxt project

Assuming you have a working Nuxt project:

<template>
<div id="app">
<content-layout-editor :editable="editable" :anchor="anchor">
</content-layout-editor>
</div>
</template>

<script>
export default {
name: 'ContentServiceExample',
data: function () {
return {
leftPane: false,
editable: true,
anchor: 'contentservice.example.layout',
}
}
}
</script>

A more configurable option

To simplify deploying in different stages of development (test, UAT, staging, production) we recommend keeping the configuration external to the application. One way to do this is the place the config in a separate directory that can be overwritten by your build scripts as they deploy to different environments.

This approach also allows you to combine the configs for multiple plugins (for example, LoginService, ContentService and Crowdhound) into one config file.

import Vue from 'vue'

import Contentservice from 'vue-contentservice'

// Load the configuration.
// This file will be overwritten during Docker builds.
import Config from '../protected-config/websiteConfig'
import FroalaKey from '../protected-config/froalaKey'


let options = null
if (Config.contentservice) {
options = {
protocol: Config.contentservice.protocol,
host: Config.contentservice.host,
port: Config.contentservice.port,
version: Config.contentservice.version,
apikey: Config.contentservice.apikey,

// Use font-awesome version 5
defaultIconPack: 'fas',

// If you choose to use Froala
froalaActivationKey: FroalaKey
}
} else {
console.error('Missing contentservice configuration in protected-config/websiteConfig.js')
console.error('Contentservice will be disabled.')
}

Vue.use(Contentservice, options)

A combined configuration can then go in protected-config/websiteConfig.js, something like this:

/*
* This file gets overwritten during production deployments,
* using a config file copied from /secure-config/website/overlay.
*/
module.exports = {
website : {
protocol: 'http',
host: 'localhost',
port: 3000,
},
api: {
protocol: 'http',
host: 'localhost',
port: 8000,
version: 'v1',
},
loginservice: {
// Local testing
host: 'localhost',
port: 9090,
apikey: 'API10O0X1NS8FWUTO3FXKN15ZOR09',
version: 'v2',
registerPath: '/mbc#new-user',
forgotPath: '/change-password'
},
contentservice: {
host: 'uat.crowdhound.io',
version: '2.0',
apikey: 'API10O0X1NS8FWUTO3FXKN15ZOR09',
//froalaActivationKey: FroalaKey
},
}

Managing your own content

The code so far has used the APIKEY of the Content Service demonstration account. This content can be edited by anyone, and gets reset periodically.

To display edit your own content, create an account on the Tooltwist website and get your own APIKEY.

The content that is displayed on a page is specified by the ‘anchor’ attribute, that acts as a key to identify your specific content. You can use almost any value for the anchor, but we suggest you create a simple naming convention to keep things simple, for example

application.page.layout

Managed Content

Full Page Content Management

The examples so far have all allowed full page content management, allowing ‘widgets’ to be added to the page.

<template>
<div id="app">
<content-layout-editor :editable="editable" :anchor="anchor">
</content-layout-editor>
</div>
</template>

<script>
export default {
name: 'ContentServiceExample',
data: function () {
return {
editable: true,
anchor: 'contentservice.example.layout',
}
}
}
</script>

The Left pane

When a page is being edited, an extra pane appears on the right, containing a toolbox of widgets and allowing you to enter properties for widgets already on the page. This right pane can be resized by dragging it’s border.

In some cases you may wish to have a similar pane on the left side of the page. This can be done simply by a slot to your template.

<template>
<div id="app">
<content-layout-editor :editable="editable" :anchor="anchor">

<template slot="left-pane">
This is a pane on the left!
</template>

</content-layout-editor>
</div>
</template>

<script>
export default {
name: 'ContentWithLeftPane',
data: function () {
return {
editable: true,
anchor: 'contentservice.example.layout',
}
}
}
</script>

Styling the page

The generated code includes classes that you may define:

Class Notes
.c-triple-pane The entire content area, including the side panes.
.c-editing-layout Set when in content management mode.
.c-not-editing-layout Normal mode, when not laying out the page.
.c-has-left-pane Set when the left pane is being used.
.c-left-pane Left pane area
.c-middle-pane Use with care.
.c-right-pane Use with care.

Remote Content Management

In some cases you may wish to display managed content on your site, but want that content management to occur on the site itself. In this case you can use Remote Content Management.

With Remote Content Management (RCM) you can log in to your Tooltwist account and modify the content from there. Your changes will “magically” appear on your website.

Content management from the site itself is easy, so why would you want to do this?

a) It may be because you don’t want the site to have the ‘weight’ of the logic required to edit pages (i.e. faster load times, or lower memory usage)

b) You do not want to add login functionality to your site, or

c) You prefer the tighter security of the Tooltwist admin console, and want to make it as hard as possible for hackers.

Adding RCM to your own site

If your application already has an administration section, or if you don’t want your users to log into a Tooltwist admin account (because they might modify the wrong thing!) then it makes sense to provide Content Management functionality in your admin website.

Content Service provides components to make this easy.

Only Adding Specific Components to a Page

Widgets for ContentService are written such that they can operation in three modes:

Creating your own Widget

Step 1 - Property Component

In this step, we create a component that will allow the user to edit the properties of your widget, in the properties panel on the right side of the editor.

SimpleWidgetProps.vue

<template lang="pug">
.c-property-element(:class="propertyClass")
.tt-property-header(@click="setExpandedElement")
| Simple Widget

// Transition is for animation only and can be removed if not needed
transition(name="c-property-list-transition")
.c-element-properties(v-show="isExpandedElement")
.tt-property
.c-property-label Class
.c-property-value
input.input(v-model="clas")
.tt-property
.c-property-label Style
.c-property-value
input.input(v-model="style")
// Use this template to define a new property you want to add,
// and create matching getter/setter as a computed field, similar
// to those for clas and style.
//- .tt-property
//- .c-property-label <<NameOfProperty>>
//- .c-property-value
//- input.input(v-model="<<TargetModel>>")
</template>

<script>
import PropertyMixins from 'vue-contentservice/src/mixins/PropertyMixins'

export default {
name: 'simple-widget', // Rename this to the name of your Widget
mixins: [ PropertyMixins ],
computed: {

// We cannot update the element directly - it must be updated
// with this.$content.setProperty( ).
clas: {
get () {
let value = this.element['class']
return value ? value : ''
},
set (value) {
this.$content.setProperty({ vm: this, element: this.element, name: 'class', value })
}
},
style: {
get () {
let value = this.element['style']
return value ? value : ''
},
set (value) {
this.$content.setProperty({ vm: this, element: this.element, name: 'style', value })
}
},
},
}
</script>

<style lang="scss" scoped>
.c-property-value {
input.input {
margin-top: 2px;
font-size: 9px;
}
}
</style>

Step 2 - Widget Component

In this step we create the component that displays the actual widget. The way the widget is displayed can be varied as you wish, depending on the properties entered using your properties component.

This widget needs to be able to work in three different modes:

  1. normal display on a web pages
  2. edit mode in the editor
  3. design mode in the editor

You will see in the code below how these three models can be separated. In this example, the output of the widget is the same for both edit and design mode. You can split this code as required.

Note: When creating a new element, as much as possible prefix the class with c-property as to distinguish the element under the content service. Also, make sure that the style is scoped as to enclose the styles to the widget only.

SimpleWidget.vue

<template lang="pug">
.c-content-simplewidget(:class="editModeClass")
span(v-if="extraDebug")
| &lt;simple-widget&gt;
br

// For Design and Editing mode display
// These modes can be separated
template(v-if="isDesignMode || isEditMode")
div(v-on:click.stop="select(element)")
// This element contains the useful icons and functionalities
// such as remove, copy, and download
.c-layout-mode-heading
edit-bar-icons(:element="element")
| Simple Widget

// Any html that you want to display in Design and Edit mode can go
// here. Your widget properties may be accessed by creating a computed
// 'getter'. See the 'style' computed property for an example.
div(:class="myClass", :style="myStyle", @click.stop="selectThisElement")
| Hello world, this is a simple widget on editing/designing mode

// For View (Live) mode display
template(v-else)
// This is the actual implementation of the widget
// - what you want to display to the user
.simple-widget(:class="myClass", :style="myStyle")
| Hello world, this is a simple widget!
</template>

<script>
import ContentMixins from 'vue-contentservice/src/mixins/ContentMixins'
import CutAndPasteMixins from 'vue-contentservice/src/mixins/CutAndPasteMixins'

export default {
name: 'simple-widget',
props: {
element: {
type: Object,
required: true
}
},
mixins: [ ContentMixins, CutAndPasteMixins ],
data: function () {
return {
}
},
computed: {
style: {
get () {
let value = this.element['style']
return value ? value : ''
}
},

myClass: function () {
if (this.element.placeholder && this.element.placeholder.startsWith('tEntryTime')) {
console.log(`inputClass()`, this.element);
}

var obj = { }
let classesForElement = this.element['class']
if (classesForElement) {
// console.log(`classesForElement=${classesForElement}`);
classesForElement.split(' ').forEach(clas => {
let classname = clas.trim()
if (classname) {
obj[classname] = true
}
})
} else {
obj['branding-logo-default'] = true
}
return obj
},

myStyle: function () {
let style = this.element['style'] + ';'
// width
try {
let num = parseInt(this.element['width'])
if (num >= 20) {
style += `width:${num}px;`
}
} catch (e) { }

// height
try {
let num = parseInt(this.element['height'])
if (num >= 20) {
style += `height:${num}px;`
}
} catch (e) { }
return style
}
}
}
</script>

<style lang="scss" scoped>
</style>

Step 3 - Register the Components

Once you have the property and display widget components created, you need to register them with Contentservice using $content.registerWidget(). The place you do this will depend upon the framework you are using.

Nuxt Plugin

For a Nuxt project the following code could be installed at /plugins/CustomWidgetConfig.js:

import Vue from 'vue'

import SimpleWidget from '/path/to/the/widget/SimpleWidget.vue'
import SimpleWidgetProps from '/path/to/the/widget/SimpleWidgetProps.vue'

let tmpvue = new Vue()
let $content = tmpvue.$content

$content.registerWidget(Vue, {
name: 'simpleWidget',
label: 'Simple Widget',
category: '',
iconClass: 'fa fa-file-image', // This can be customised
iconClass5: 'far fa-file-image', // This can be customised
dragtype: 'component',

// Register native Vue templates
componentName: 'content-simplewidget',
component: SimpleWidget,
propertyComponent: SimpleWidgetProps,

// Identical structure to a CUT or COPY from edit mode.
data: {
type: "contentservice.io",
version: "1.0",
source: "toolbox",
layout: {
type: 'simpleWidget',
label: 'Simple Widget',
children: [ ]
}
}
})

The plugin needs to be included in nuxt.config.js:

...
plugins: [
...
{ src: '~/plugins/vue-contentservice', ssr: false },
{ src: '~/path/to/config/CustomWidgetConfig', ssr: false }
]

Note: Be careful, The config file might also contain a plugins array for Webpack. Make sure you update the correct one.

Step 4 - Test your widget

Once you’re done following all the steps above, it’s time to test the newly created widget. Edit any page using Contentservice and you should see your widget in the Toolbox.

Creating an NPM Package

Reusable Widget Library

  1. ContentService.io adds CMS functionality to VueJS and Nuxt projects.
  2. ContentService can be extended with custom widgets, allowing you to create reusable, application-specific widgets.
  3. This project provides a template for creating npm packages of reusable custom widgets.

What You Get

When you use this template, you will get an npm package that can be used in VueJS and Nuxt projects:

Getting Started

Quick Start

Clone this project, and push it to your own repo, then play at will:

git clone https://github.com/tooltwist/vue-whatever.git myProject
cd myProject
rm -rf .git; git init; git add .; git commit -m 'Cloned from vue-whatever'
git remote add origin <your repos URL>
git push --set-upstream origin master

Open the project in your edit and…

  1. Globally substitute ‘whatever’ with your project name with the first character in lower case.
  2. Globally substitute ‘Whatever’ with your project name with the first character in upper case.
  3. git mv src/lib/Whatever.js src/lib/Projectname.js
  4. git mv src/store/WhateverStore.js src/store/ProjectnameStore.js
  5. git mv src/components/widgets/ContentWhatever.vue src/components/widgets/ContentProjectname.vue
  6. git mv src/components/widgets/ContentWhateverProps.vue src/components/widgets/ContentProjectnameProps.vue
  7. Update the description in package.json
  8. Modify README.md to explain what this widget library does.

You can test your widget using:

yarn install
yarn serve

To create new widgets, you can copy and modify the example widget files in src/components/widgets:

Commit to github, then publish at will:

yarn build-bundle
yarn patch-release

Using this template

Use this template to create an npm package that can be added to VueJS or Nuxt projects that utilise Contentservice.io.

Step 1 - Create your own empty repo in Github

For the sake of this documentation, we’ll assume your new project will be called vue-xxxx.

Step 2 - Clone this template and save it as your own repository

cd /Development/Project   (or somewhere else)
git clone https://github.com/tooltwist/vue-whatever.git vue-xxxx
cd vue-xxxx

Step 3 - Clone this template and save it as your own repository

rm -r .git
git init
git add .
git commit -m "first commit"
git remote add origin https://github.com/tooltwist/vue-xxxx.git
git push -u origin master

Step 4 - Define your widgets

Developing locally

Sometimes the best, or only, place to develop and debug a package is as it is being within another application.

The “make change, publish to npm, pull to project” process is too slow, and risks having a broken or debug version of the package being deployed by other users of the package.

A better solution is to clone the project directly into the node_modules directory of your project and make changes there.

Step 1 - Install your project

cd <projectDir>/node_modules  
rm -r vue-xxxx
git clone https://github.com/xxxx/vue-xxxx.git
cd vue-xxxx
npm install
npm run build-bundle

Step 2 - Option A

After each code change, rebuild the bundle.

npm run build-bundle

Step 2 - Option B

For many projects, this option will allow live reload of the application each time you change the code, however it will not republish the artifacts in the dist directory.

rm -r node_modules

Modify package.json, and change the main entry point from:

"main": "./dist/vue-xxxx.common.js",

to…

"main": "./src/components/index.js",

This option however will NOT update the CSS file vue-xxxx/dist/vue-xxxx.css. To rebuild this file you will need to run npm install and npm run build-bundle again.

Beware

If you run npm-install in your project directory, it will overwrite any changes you’ve made to your package.

Do not check in package.json if you’ve made the changes mentioned in Option 2B.

If you are using option 2B and see the error Module build failed: TypeError: this.setDynamic is not a function then remove the node_modules directory in your vue-xxx package directory (not the main project) and restart your server.

Creating widgets

Widgets required two Vue files:

  1. The widget as it is displayed on a page
  2. A properties entry area, where the user can enter details while editing the page.