BuildFire SDK Plugin Boilerplate Template Update
Created On | Developer | |
---|---|---|
05/15/2023 | Mahmoud AlSharif | malsahrif@madaincorp.com |
The need for updating the currently used plugin template has increased as many new technologies have emerged or changed. Over the time of developing and observing, a pattern of development behaviors has been noticed, and recently a set of BuildFire standards and best practices are being applied to almost every shipped plugin, also an overall understanding has evolved of how plugins structure should look like and which BuildFire services and/or components should be used in most use cases.
All things considered, a need for a batteries-included template has emerged that will introduce a wide range of features and tools that are ready to use out of the box, meaning that plugin developers don't have to spend as much time and effort setting up their development environment instead they can focus on building the plugin and take advantage of the many built-in features the template will provide.
Overall, the goal here is to help in shipping plugins faster, better development experience, and force a set of standards and best practices to follow.
Most plugin developers use a data access layer that mainly consists of a model (entity) and a repository, introducing this file would contribute to getting the developer familiar with the "BuildFire way", and developing faster for the experienced ones.
The following model includes all of the base attributes in addition to one method that would generate a data entity with BuildFire indexes.
/widget/global/js/models/Post.js
export default class Post {
constructor(data = {}) {
this.id = data.id || undefined;
this.content = data.content || '';
this.createdOn = data.createdOn || new Date();
this.createdBy = data.createdBy || null;
this.lastUpdatedOn = data.lastUpdatedOn || new Date();
this.lastUpdatedBy = data.lastUpdatedBy || null;
this.deletedOn = data.deletedOn || null;
this.deletedBy = data.deletedBy || null;
this.isActive = data.isActive || 1;
}
toJSON() {
return {
id: this.id,
content: this.content,
createdOn: this.createdOn,
createdBy: this.createdBy,
lastUpdatedOn: this.lastUpdatedOn,
lastUpdatedBy: this.lastUpdatedBy,
deletedOn: this.deletedOn,
deletedBy: this.deletedBy,
isActive: this.isActive,
_buildfire: {
index: {
date1: this.createdOn,
},
},
};
}
}
/widget/global/js/bases/BaseRepository.js
export default class BaseRepository {
validate() {
return { isValid: false, errors: ['missing_validate_function'] };
}
/**
* base save
* @param {Object} payload
* @returns {Promise}
*/
save(payload) {
const { isValid, errors } = this.validate(payload);
if (!isValid) {
return reject(errors);
}
// save BL
}
/**
* base update
* @param {Object} payload
* @returns {Promise}
*/
update(payload) {
// update BL
}
}
The following sample repository file contains a complete CRUD methods reference that is ready to be used right away.
/widget/global/js/repositories/Posts.js
class Posts extends BaseRepository {
validate(data = {}) {
const errors = [];
if (!data.name) errors.push('missing key "name"');
if (typeof data.name !== 'string') errors.push('"name" must be of type string');
return { isValid: !errors.length, errors };
}
}
export default new PostRepository();
/widget/global/
Having a redundant method or even a whole file is a common plugin developer mistake, many BuildFire services files such as LanguageManager, AuthManager, and AnalyticsManager are being used in multiple modules, this promotes using a tree-shakeable code.
- Using Jasmine framework, will add its dependencies at
src/control/tests/lib/
- Sample spec at
src/control/tests/spec/
No major changes should be made to the current default bundling, the door remains open for future ecosystem enhancements including the introduction of new plugins or upgrading the core.
The below tools would be ready to be used out of the box, their packages and config files will be included, however, they are optional and plugin developers can opt not to use them.
Should be used as the main linter tool for code quality concerns.
.eslintrc.json
{
"extends": ["eslint:recommended"],
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"env": {
"browser": true,
"node": true,
"es6":true
},
"globals": {
"buildfire":true
}
}
Should be used for code formatting concerns.
The template should support CSS Injection by default with minimal settings:
plugin.json
"cssInjection": {
"enabled": true,
"layouts": [
{
"name": "Layout",
"imageUrl": "resources/layouts/layout.png",
"cssPath": "widget/style/layouts/layout.css"
},
]
}
src/widget/index.html
<meta name="buildfire" content="enablePluginJsonLoad">
Enabling BuildFire-supported localization with base configurations:
plugin.json
"language": {
"enabled": true,
"languageJsonPath": "resources/languages.json"
}
src/resources/language.json
{
"mainInfo": "main info of language tab",
"sections": {
"YOUR_SECTION_KEY": {
"title": "Your Title",
"labels": {
"YOUR_LABEL_KEY": {
"title": "Section Title",
"defaultValue": "Default Value"
}
}
}
}
}
src/widget/index.html
<meta name="buildfire" content="enablePluginJsonLoad">
Code snippets related to commonly used BuildFire services/features, these blocks should be well described and easily removed if not needed
/widget/global/js/deeplinking.js
/**
* @description BuildFire sharing and deep linking
* See {@link https://sdk.buildfire.com/docs/share-links-and-deep-linking}
**/
buildfire.deeplink.getData((deepLinkData) => {
// handle deeplink init
});
buildfire.deeplink.onUpdate((deepLinkData) => {
// handle deeplink update
}, true);
const share = () => {
buildfire.deeplink.generateUrl(
{
title: 'Title',
description: 'Description',
imageUrl: '',
data: { itemId: 'xyz'},
},
(err, result) => {
if (err) return console.error(err);
buildfire.device.share({
subject: 'Title',
text: 'Description',
link: result.url
}, (err, result) => {
if (err) console.error(err);
if (result) console.log(result);
});
}
);
};
/widget/global/js/auth.js
/**
* @description Authentication changes handlers
* See {@link https://sdk.buildfire.com/docs/auth#onlogin-}
**/
buildfire.auth.onLogin(AuthManager.onUserChange, true);
buildfire.auth.onLogout(AuthManager.onUserChange, true);
/widget/global/js/syncing.js
/**
* @description Sync changes with the widget
* See {@link https://sdk.buildfire.com/docs/messaging-to-sync-your-control-to-widget}
**/
buildfire.messaging.sendMessageToWidget({ scope: 'new_entries' });
/**
* @description Sync control changes
* See {@link https://sdk.buildfire.com/docs/messaging-to-sync-your-control-to-widget#onreceivedmessage}
**/
buildfire.messaging.onReceivedMessage = (message) => {
// If the domain is specified, then refresh a specific area only!
// if (message.scope === 'new_entries') fetch();
// Reload the whole app otherwise
// else window.location.reload();
};
/**
* @description control datastore changes
* See {@link https://sdk.buildfire.com/docs/datastore#onupdate-}
**/
buildfire.datastore.onUpdate((event) => {
console.log("Data has been updated ", event);
});
widget/style/layouts/layout.css
@media only screen and (min-device-width: 768px) {
// tablet customizations
}
widget/style/layouts/layout.css
html[safe-area="true"] body {
padding-top: calc(var(--base-padding) + constant(safe-area-inset-top)) !important;
padding-top: calc(var(--base-padding) + env(safe-area-inset-top)) !important;
padding-bottom: calc(var(--base-padding) + constant(safe-area-inset-bottom)) !important;
padding-bottom: calc(var(--base-padding) + env(safe-area-inset-bottom)) !important;
}
/widget/global/js/app-theme.js
/**
* @description Handle app theme changes
* See {@link https://sdk.buildfire.com/docs/appearance#getapptheme-}
**/
const getAppTheme = () => {
return new Promise((resolve, reject) => {
buildfire.appearance.getAppTheme((err, appTheme) => {
if (err) return console.error(err);
state.appTheme = appTheme;
resolve(appTheme);
});
buildfire.appearance.onUpdate((appTheme) => {
state.appTheme = appTheme;
}, true);
});
};
plugin.json
{
"supportSite":"",
"pluginKeywords": "keyword1,keyword2,keyword3",
}
Need to be spec'ed!
The features below involve adding handlers that can be reused whenever the business logic requires, and including them with the boilerplate promotes consistency and usability.
/widget/global/js/AnalyticsManager.js
/src/shared/AuthManager.js
The bookmarking feature has proven useful in many production plugins, the class would expose add
, delete
, and getAll
functions.
/widget/global/js/BookmarksManager.js
Only openDialog
, get
and a deep link handler will be added
/widget/global/js/NotesManager.js
- Language Settings feature
- BuildFire Skeleton
- TinyMCE
The current default template should be renamed to pluginBackboneTemplate
or a better name, and a new repository named defaultPluginTemplate will be created to host this new template, gulpPluginTemplate should get deprecated as well.
Example commands:
Initializing without specifying any preferences would clone this new template (default)
buildfire plugin init sample-plugin
Specifying
gulp
would also clone this new template
buildfire plugin init sample-plugin gulp
We can proceed without having to make changes to the CLI
- If no changes are made to the CLI, then what about point number 2 above?
BuildFire SDK documentation should well-document how to get started with this template and any necessary information, I recommend getting rid of the medium article link here, and instead write a new inhouse Get started page
src/widget/js/state.js
{
// ...
get context() { return buildfire.getContext(); },
// ...
}
Date | Developer | Description |
---|---|---|
05/15/2023 | Mahmoud AlSharif | Document Creation |
05/17/2023 | Mahmoud AlSharif | Teamleader feedback |
05/21/2023 | Mahmoud AlSharif | CTO feedback |
05/25/2023 | Mahmoud AlSharif | CTO feedback #2 |