This GrapesJS plugin integrates various APIs into the editor.
It makes a new expressions UI available to the user so that she can manage custom states on components, linking them to data from a CMS or a data base or an API.
The plugin also has data management feature needed to manage components states, expressions made of tokens, build a query from the component states.
Finally there is a settings dialog to manage the data sources and save them with the website.
This code is part of a larger project: about Silex v3
Discussions, bug reports in Silex community forums or GitHub issues
Features
- Import data from data source (GraphQL APIs) in the editor
- Configure data sources from config
- Dialog to configure data sources from the editor
- Edit component attributes, and dynamic properties (loop, visibility, innerHTML)
- Use states and liquid filters in expressions
- Generate GraphQL query from component states
- Save data sources with the website data
- Compatible with GrapesJS notifications plugin
- Events and API to manage the data, the completion of exrpessions, and the GraphQL query
- Web component to display the expressions and edit them
- GrapesJs commands to handle data sources instead of a programmatic API
- Add more liquid filters
- Add more data sources (REST, Open API)
Expressions are made of tokens, which are the building blocks of the expressions. Tokens are the properties of the data source, like post.data.attributes.title
or post.data.attributes.content
.
States belong to a component, they are expressions which are not output in the final website, they are made to be included in other expressions. Also they are used in the generated GraphQL query.
Attributes are the HTML attributes of a component, like src
or href
or any other attribute. Special attributes are class
and style
which if you put several of them will not override each other but will be merged.
Properties are the dynamic properties of a component which include the HTML content (innerHTML), the visibility (a condition to show or hide the component), a loop property (to repeat the component for each item in a list).
Data source is a service which provides the data to the editor. For now it has to be a GraphQL API, maybe I'll add open API later.
This UI is used to manage the states, attributes and dynamic properties of the components. It is a panel which shows the expressions of the selected component and allows the user to add, edit, and remove them.
This dialog is used to manage the data sources. It allows the user to add, edit, and remove data sources. It also allows the user to test the data sources and (comming in v2) see the data they provide.
The output of this plugin is component states which are stored on the components. This data then needs to be used by other plugins or your application. For example you can implement a "publish" feature to generate pages and data files for a static site generator or CMSs. Or you can make a vue app generator with it, by implementing a "renderer" which takes the states and adds the vue code to the generated website.
Here is how your application can use the data generated by the user with this plugin:
- Components states
import { getStateIds, getState } from './state'
// ...
const component = editor.getSelected()
// Get one specific state
console.log('innerHTML state:', getState(component, 'innerHTML'))
// Display all states of the component
const stateIds = getStateIds(component)
console.log('Alls states:', stateIds.map(stateId => getState(component, stateId)))
// Detect state changes
editor.on('component:state:changed', ({state, component}) => {
console.log('State changed:', {state, component})
})
Here is an example output:
innerHTML state: {"expression":[{"type":"property","propType":"field","fieldId":"post","label":"post","typeIds":["PostEntityResponse"],"dataSourceId":"strapi","kind":"object","options":{"id":"1"}},{"type":"property","propType":"field","fieldId":"data","label":"data","typeIds":["PostEntity"],"dataSourceId":"strapi","kind":"object"},{"type":"property","propType":"field","fieldId":"attributes","label":"attributes","typeIds":["Post"],"dataSourceId":"strapi","kind":"object"},{"type":"property","propType":"field","fieldId":"title","label":"title","typeIds":["String"],"dataSourceId":"strapi","kind":"scalar"}]}
All states: [{"expression":[{"type":"property","propType":"field","fieldId":"post","label":"post","typeIds":["PostEntityResponse"],"dataSourceId":"strapi","kind":"object","options":{"id":"1"}},{"type":"property","propType":"field","fieldId":"data","label":"data","typeIds":["PostEntity"],"dataSourceId":"strapi","kind":"object"},{"type":"property","propType":"field","fieldId":"attributes","label":"attributes","typeIds":["Post"],"dataSourceId":"strapi","kind":"object"},{"type":"property","propType":"field","fieldId":"title","label":"title","typeIds":["String"],"dataSourceId":"strapi","kind":"scalar"}]}]
- GraphQL query to get the data needed for the current page in functino of the data used in the states
// Get the current page
var page = editor.Pages.getSelected()
// Get the GraphQL query
const query = editor.DataSourceManager.getPageQuery(page)
console.log(query)
Here is an example output:
{
"strapi": "posts {\n data {\n attributes {\n title\n content\n}\n}\n}"
}
This plugin suports only GraphQL for now, contribution are welcome for support of other REST specific APIs or more generic Open API
Here is a list of GraphQL APIs you can use, it includes fake data and demo public APIs. Also consider these open source self hostable services:
- Strapi headless CMS
- Directus headless CMS
- Supabase database (a Firebase alternative)
- Tina markdown files to GraphQL tool
- Drupal, WordPress... All have GraphQL support
Contributions welcome for documenting the use of these data sources
<link href="https://unpkg.com/grapesjs/dist/css/grapes.min.css" rel="stylesheet">
<script src="https://unpkg.com/grapesjs"></script>
<script src="https://unpkg.com/@silexlabs/grapesjs-data-source"></script>
<div id="gjs"></div>
const editor = grapesjs.init({
container: '#gjs',
height: '100%',
fromElement: true,
storageManager: false,
plugins: ['@silexlabs/grapesjs-data-source'],
pluginsOpts: {
'@silexlabs/grapesjs-data-source': {
dataSources: [{
id: 'countries',
type: 'graphql',
label: 'Countries',
url: 'https://countries.trevorblades.com/graphql',
method: 'POST',
headers: {},
}],
properties: {
el: () => editor.Panels.getPanel('views-container').view.el,
button: () => editor.Panels.getPanel('views').get('buttons').get('open-tm'),
},
filters: 'liquid',
}
}
});
body, html {
margin: 0;
height: 100%;
}
Use a local strapi to test GraphQL data source
$ cd strapi
$ yarn develop
Strapi admin
http://localhost:1337/admin/
- Login:
alex@test.com
- Password:
test_TEST1
Strapi GraphQL:
http://localhost:1337/graphql
Bearer 456fe45a764921a26a81abd857bf987cd1735fbdbe58951ff5fc45a1c0ed2c52ab920cc0498b17411cd03954da7bb3e62e6bae612024360fb89717bd2274493ce190f3be14cdf47fccd33182fd795a67e48624e37f7276d9f84e98b2ec6945926d7a150e8c5deafa272aa9d9d97ee89e227c1edb1d6740ffd37a16b2298b3cc8
Use this as a data source in the plugin options:
grapesjs.init({
// ...
// Your config here
// ...
plugins: ['@silexlabs/grapesjs-data-source'],
pluginsOpts: {
'@silexlabs/grapesjs-data-source': {
dataSources: [
{
id: 'strapi',
type: 'graphql',
name: 'Strapi',
url: 'http://localhost:1337/graphql',
method: 'POST',
headers: {
'Authorization': 'Bearer 79c9e74b3cf4a9f5ce2836b81fd8aaf8a986b5696769456d3646a3213f5d7228634a1a15a8bbad4e87c09ab864c501499c6f8955cf350e49b89311764009aee68589a4b78f22c06b7e09835b48cd6f21fb84311ce873cd5672bd4652fde3f5f0db6afb258dfe7b93371b7632b551ecdd969256ffc076ab8f735b5d8c7d228825',
'Content-Type': 'application/json',
},
},
],
properties: {
el: () => editor.Panels.getPanel('views-container').view.el,
button: () => editor.Panels.getPanel('views').get('buttons').get('open-tm'),
},
filters: 'liquid',
}
}
});
Here are examples of APIs I tested:
Directus
{
id: 'directus',
type: 'graphql',
name: 'Directus',
url: `https://localhost:8085/graphql`,
method: 'POST',
headers: {
'Authorization': 'Bearer yjgwcj...0c_0zex',
'Content-Type': 'application/json',
},
}
Strapi
{
id: 'strapi',
type: 'graphql',
name: 'Strapi',
url: 'http://localhost:1337/graphql',
method: 'POST',
headers: {
'Authorization': 'Bearer 456fe45a764921a2...6b2298b3cc8',
'Content-Type': 'application/json',
},
}
Supabase (I had a CORS problem, let's discuss this in an issue if you want to give it a try)
{
id: 'supabase',
type: 'graphql',
name: 'Supabase',
url: `https://api.supabase.io/platform/projects/jpslgeqihfj/api/graphql`,
method: 'POST',
headers: {
'Authorization': 'Bearer eyjhbgcioijiuz...tww8imndplsfm',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
},
}
Option | Description | Default |
---|---|---|
dataSources |
List of data sources, see config examples and the plugin code for docs (data source options and GraphQL data source options) | [] |
filters |
The string 'liquidjs' for LiquidJs filters or a list of filters (JS objects like the ones in src/filters/liquid.ts ) |
[] |
view |
Options for the UIs included with this plugin | [] |
view.el |
UI element to attach the expressions UI | .gjs-pn-panel.gjs-pn-views-container |
view.button |
Optional GrapesJs button or a function which returns a button. This button will show/hide the expressions UI, it's just a helper to save you from doing it yourself. | undefined which means no button |
`view.settingsEl | UI element to attach the settings dialog. You can provide a string (css selector), a function which returns a DOM element, or a DOM element directly. | .gjs-pn-views |
view.styles |
CSS styles which are applied to the UI (inserted in a style tag) | See the file src/view/defaultStyles.ts |
view.optionsStyles |
CSS styles which are applied to each "expression selector" UI (inserted in a style tag) | See the file src/view/defaultStyles.ts |
view.defaultFixed |
If true, the UI shows fixed by default or if false it shows expression by default | false |
- CDN
https://unpkg.com/@silexlabs/grapesjs-data-source
- NPM
npm i @silexlabs/grapesjs-data-source
- GIT
git clone https://github.com/silexlabs/grapesjs-data-source.git
Directly in the browser
<link href="https://unpkg.com/grapesjs/dist/css/grapes.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/grapesjs"></script>
<script src="path/to/grapesjs-data-source.min.js"></script>
<div id="gjs"></div>
<script type="text/javascript">
var editor = grapesjs.init({
container: '#gjs',
// ...
plugins: ['@silexlabs/grapesjs-data-source'],
pluginsOpts: {
'@silexlabs/grapesjs-data-source': { /* options */ }
}
});
</script>
Modern javascript
import grapesjs from 'grapesjs';
import plugin from '@silexlabs/grapesjs-data-source';
import 'grapesjs/dist/css/grapes.min.css';
const editor = grapesjs.init({
container : '#gjs',
// ...
plugins: [plugin],
pluginsOpts: {
[plugin]: { /* options */ }
}
// or
plugins: [
editor => plugin(editor, { /* options */ }),
],
});
Clone the repository
$ git clone https://github.com/silexlabs/grapesjs-data-source.git
$ cd grapesjs-data-source
Install dependencies
$ npm i
Start the dev server
$ npm start
Build the source
$ npm run build
Here are the key parts of the plugin:
-
editor.DataSourceManager: A Backbone collection to manage the APIs. This collection holds the different available data sources and their settings (type, url, auth...). This data is provided by the config. The main API of this class is
getDataTree()
to get the data tree -
DataTree: A class to manage component states and generate queries to APIs. Component states are used to build the query needed for the current page, and they can be used to create other states in child components or override a component's attributes or style. This collection is generated from the components attributes, it is not stored with the site data.
-
DataSource: An interface for classes managing an API, abstracting the calls and queries. It includes methods like
getData(query)
andgetTypes()
.
The components with "Loop Template" also have a current
state, similar to dynamic pages.
The plugin's architecture is designed to provide a flexible and efficient way to manage data and rendering in the editor, supporting dynamic content and static site generation. It abstracts the complexities of working with different APIs and provides a unified way to manage component states, templates, and dynamic content.
MIT