How to migrate from Chamaileon SDK v1 to v2

New usage methods

Install from npm

You can install the Chamaileon SDK package from https://npmjs.com like this:

npm i @chamaileon-sdk/plugins

And use it like this

import chamaileonSdk from "@chamaileon-sdk/plugins";

Or like this

const chamaileonSdk = require("@chamaileon-sdk/plugins");

Import from CDN

You can also import the Chamaileon SDK package from our CDN like this

<script type="module">
	import chamaileonSdk from "https://cdn.chamaileon.io/sdk/##CURRENT_VERSION##/es/plugins.js";
</script>

You can find the newest version number here.

In a script tag

You can also use it in a script tag from our CDN like this

<script src="https://cdn.chamaileon.io/sdk/##CURRENT_VERSION##/umd/plugins.js"></script>

With this we add the library to the window object so you can access it like this:

window.chamaileonSdk({...})

You can find the newest version number here.

New configuration

There are some differences in the configuration compared to the old SDK. The biggest one is that you have to call the chamaileonSdk() function directly instead of chamaileonSdk.init().

See the example below:

Old configuration

const accessTokenRequest = await fetch("https://sdk-api.chamaileon.io/api/v1/tokens/generate", {
	method: "GET",
	headers: {
		"Authorization": `Bearer ${apiKey}`,
	},
});

const accessTokenResponse = await accessTokenRequest.json();
const accessToken = accessTokenResponse.result

const chamaileonPlugins = await window.chamaileonSdk.init({
	mode: "serverless",
	accessToken: accessToken,
	whitelabel: {
		locale: "en",
		urls: {
			splashScreen: "https://chamaileon-sdk.github.io/splashscreen-and-logo-examples/splashScreen.html",
			createLogoJS: "https://chamaileon-sdk.github.io/splashscreen-and-logo-examples/createLogo.js"
		},
		colors: {
			primary: "#2D3291",
			secondary: "#009f4a",
			red: "#ff5546",
			darkBlue: "#2d3291",
			darkGreen: "#00af6e",
			lightGreen: "#50d791",
			weirdGreen: "#50d791",
			pink: "#ff91a0",
			yellow: "#ffd23c"
		}
	}
});

New configuration

async function fetchAccessToken(){
	const accessTokenRequest = await fetch("https://sdk-api.chamaileon.io/api/v1/tokens/generate", {
		method: "GET",
		headers: {
			"Authorization": `Bearer ${apiKey}`,
		},
	});
	if (!accessTokenRequest.ok) {
		throw new Error("SDK token generation failed");
	}
	const accessTokenResponse = await accessTokenRequest.json();
	return accessTokenResponse.result;
}

import createChamaileonPlugins as chamaileonSdk from "@chamaileon-sdk/plugins";

const accessToken = await fetchAccessToken();

const whitelabelConfig =  {
	locale: "en",
	urls: {
			splashScreen: "https://chamaileon-sdk.github.io/splashscreen-and-logo-examples/splashScreen.html",
			createLogoJS: "https://chamaileon-sdk.github.io/splashscreen-and-logo-examples/createLogo.js"
	},
	colors: {
		"primary": "#00C0E7",
		"secondary": "#009f4a",
		"red": "#ff5546",
		"darkBlue": "#2d3291",
		"darkGreen": "#00af6e",
		"lightGreen": "#50d791",
		"weirdGreen": "#50d791",
		"pink": "#ff91a0",
		"yellow": "#ffd23c"
	},
};

const chamaileonPlugins =  await chamaileonSdk({
	...whitelabelConfig,
	accessToken,
	getAccessToken: fetchAccessToken,
});

Difference between the two

SDK parameter added

  • getAccessToken: You have to pass down the access token fetch function to the SDK. This was inline before but for compatibility reasons we removed it from the library.

SDK parameter removed

  • mode: Removed this because we only kept the serverless mode.
  • whitelabel: Removed this and moved every prop from it to the top level.

SDK instance

Plugin wrapper

The plugin-wrapper div now has a z-index of 2001 instead of 10.

After a successful SDK initialization

The difference in the return object after a successful init is the following:

Old SDK return object

{
	openGallery,
	previewEmail,
	editEmail,
	createThumbnail,
	editVariables,
}

New SDK return object

{
	createInlinePlugin,
	createFullscreenPlugin,
	destroy,
}

The createInlinePlugin and createFullscreenPlugin replaces the separated plugin load function. It also means that you have to create every plugin that you need with these and they won't be created automatically.

Until now, every plugin - except the thumbnail - was initialized as fullscreen. From now on, you can chose between inline and fullscreen plugins. The only exception is the thumbnail plugin because it can only be initialized as an inline plugin.

The destroy function was moved to the return value instead of being present as chamaileonSdk.destroy().

Fullscreen initialization

Old fullscreen initialization

await chamaileonPlugins.previewEmail({
	document: {},
	settings: {},
	hooks: {},
});

New fullscreen initialization

await chamaileonPlugins.createFullscreenPlugin({
	plugin: "preview",
	data: { document },
	settings: {},
	hooks: {},
});

Inline initialization

Old inline initialization

await chamaileonPlugins.createThumbnail({
	document: {},
	container: "#email-thumbnail",
});

New inline initialization

await chamaileonPlugins.createInlinePlugin(
	{
		plugin: "thumbnail",
		data: { document },
		settings: {
			height: 640,
			width: 480,
			scale: 0.5
		},
		hooks: {},
	},
	{
		container: document.getElementById("email-thumbnail"),
		dimensions: {
			width: 640,
			height: 480,
			scale: 0.5,
		}
	}
);

Differences

  • The document got moved inside the data property.
  • You have to specify the plugin type in a property instead of a custom function call.
  • If something was defined on a top level property it got moved to the settings object in the new call
  • Inline only changes
    • The container prop got moved into the second object parameter in the call function
    • The container now accepts a HTMLElement as well as a selector

How to match the plugin name with the old function call

Old New
openGallery plugin: "gallery"
previewEmail plugin: "preview"
editEmail plugin: "editor"
createThumbnail plugin: "thumbnail"
editVariables plugin: "variable-editor"

After you created a plugin

We modified the SDK so that most of the time you will only need one instance of every plugin. After you create an instance we will give you the option to modify the data and settings parameter of every plugin on the fly.

A plugin instance returns with the following:

Fullscreen plugin return object

Old fullscreen plugin return object

{
	close,
	updateSettings,
	setDocument,
	...otherMethods
}

New fullscreen plugin return object

{
	methods: {
		updateSettings,
		updateData,
		...otherMethods
	},
	showSplashScreen,
	hideSplashScreen,
	show,
	hide,
	destroy,
};

Inline plugin return object

Old inline plugin return object

{
	destroy,
};

New inline plugin return object

{
	methods: {
		updateSettings,
		updateData,
		...otherMethods
	},
	destroy,
};

Differences

  • The methods that we provided that were on the top level got moved to the methods object
  • The setDocument was renamed to updateData
  • Inline plugins will also return with a methods object as well
  • We added more options to the fullscreen return object
    • showSplashScreen: shows the splash screen inside the plugin iframe
    • hideSplashScreen: hides the splash screen inside the plugin iframe
    • show: shows the plugin iframe
    • hide: hides the plugin iframe

With the show and hide functions you can avoid the old workflow of completely destroying and then re-initializing the same plugin over and over again. From now you can hide the plugin, update it's data and settings and then show it again. If you want you can even show the plugin and display the splash screen while you update the data in the plugin.

You should load every plugin you need after the SDK initialization. The best case scenario is to load it before your user wants to use it. You can then update the data object and the settings later when the user actually wants to interact with the plugin.

How to use the update functions instead of sending data at initialization

Old plugin configuration

chamaileonPlugins.previewEmail({
	document,
	settings: {
		...previewSettings
	},
	hooks: {
		...previewHooks
	},
})

New plugin configuration

const previewInstance = await chamaileonPlugins.createFullscreenPlugin({
	plugin: "preview",
	data: {},
	settings: {},
	hooks: {
		...previewHooks
	},
});

await previewInstance.methods.updateSettings({ ...previewSettings });
await previewInstance.methods.updateData({ document });

Opening a fullscreen plugin

Old fullscreen plugin open

await chamaileonPlugins.previewEmail({
	document: {},
	settings: {},
	hooks: {},
});

New fullscreen plugin open

const previewInstance = await chamaileonPlugins.createFullscreenPlugin({
	plugin: "preview",
	data: { document },
	settings: {},
	hooks: {},
});

previewInstance.showSplashScreen(); // optional
await previewInstance.open();
previewInstance.hideSplashScreen(); // optional

Closing a fullscreen plugin

Old fullscreen plugin close

The old SDK handled the close action and destroyed the plugin instance.

New fullscreen plugin close

const previewInstance = await chamaileonPlugins.createFullscreenPlugin({
	plugin: "preview",
	data: { document },
	settings: {},
	hooks: {
		close: () => {
			return new Promise(resolve => {
				previewInstance.hide();
				resolve();
			});
		},
	},
});

// The only exception for this is the variable editor plugin
// because you have to add your custom button for this and catch it
// on the `onButtonClicked` action
const variableEditorInstance = await chamaileonPlugins.createFullscreenPlugin({
	plugin: "variable-editor",
	data: { document },
	settings: {},
	hooks: {
		onButtonClicked: ({ buttonId }) => {
			return new Promise(resolve => {
				if (buttonId === "close") {
					variableEditorInstance.hide();
					resolve();
				}
				reject();
			});
		},
	},
});

Plugin interface changes

Editor

  • Moves the code element config from settings.elements.content to settings.element.advanced
  • Moves the user top level property inside the settings property
  • Moves the document top level property inside the data property
  • Moves the getDocument top level method inside editorInstance.methods object
  • Moves the getEmailHtml top level method inside editorInstance.methods object
  • Renamed the setDocument method to updateData and moved it inside the editorInstance.methods object
  • Deprecates the getEmailJson method, use the editorInstance.methods.getDocument method instead. The returned returnedObject.body will be the same as the deprecated method's result. We suggest that you save the whole returnObject from now on because it returns the fontFiles and variables as well.
  • Deprecates the emailJson top level property, use the data.document.body property instead
  • Deprecates the title top level property, use the data.document.title property instead
  • Deprecates the autoSaveInterval top level property, use the settings.autoSaveInterval property instead
  • Deprecates the textInsertPluginButtons top level property, use the settings.buttons.textInsert property instead
  • Deprecates the openVariableModal function that was triggerable with the onClick config option on buttons. We have a default place for this button now.
  • Deprecates the onDropdownButtonClicked hook, use the onHeaderButtonClicked hook instead
  • Deprecates the emailJson parameter on onSave hook, use the returned document.body instead
  • Deprecates the emailJson parameter on onAutoSave hook, use the returned document.body instead
  • Deprecates the onBeforeClose and onAfterClose hooks, use the close hook instead
    • Old editor close hooks
      editorConfig.hooks.onBeforeClose = () => {  /* custom logic before the editor is closed */  };
      editorConfig.hooks.onAfterClose = () => { /* custom logic after the editor is closed */ };
      
    • New editor close hook
      editorConfig.hooks.close = () => {
      	/* custom logic before the editor is "closed" */
      	await editorInstance.hide();
      	/* custom logic after the editor is "closed" */
      };
      
  • Deprecates the canLockBlocks top level property, use the settings.addons.blockLock property instead
    • You can enable or disable it now, or even hide it completely
      // Disabled
      settings.addons.blockLock = {
      	enabled: false,
      }
      
      // Hidden
      settings.addons.blockLock = false;
      
  • Deprecates the blockLibraries top level property, use the settings.blockLibraries property instead
    • Removes the accessLevel property and instead you can define the canDeleteBlock, canRenameBlock and canSaveBlock properties.
    • Old blockLibraries config array
      blockLibraries: [
      	{
      		id: "email-blocks",
      		label: "Email's blocks",
      		accessLevel: "readOnly",
      	},
      ]
      
    • New blockLibraries config array
      settings.blockLibraries: [
      	{
      		id: "email-blocks",
      		label: "Email's blocks",
      		canDeleteBlock: false,
      		canRenameBlock: false,
      		canSaveBlock: false,
      	},
      ]
      
  • Deprecates the dropdownButtons top level property, use the settings.buttons.header property instead
    • You have to create a dropdown first and then add the dropdownButtons content to it's items
    • Old dropdownButtons config array
      dropdownButtons: [
      	{
      		id: "test-button-1",
      		label: "Show preview",
      		icon: "short_text",
      	},
      ]
      
    • New dropdownButtons config array
      settings.buttons.header = [
      	{
      		id: "dropdownButtons",
      		type: "dropdown",
      		icon: "dots-vertical",
      		style: "icon",
      		items: [
      			{
      				id: "test-button-1",
      				label: "Show preview",
      				icon: "short_text",
      			}
      		],
      	},
      ];
      
  • Deprecates the default header button configuration, you should set up one through the settings.buttons.header parameter
    • Default config that was included inside the editor
      settings.button.header = [
      	{
      		id: "preview",
      		type: "button",
      		icon: "eye",
      		label: "Preview",
      		color: "primary",
      		style: "outlined",
      	},
      	{
      		id: "dropdownButtons",
      		type: "dropdown",
      		icon: "dots-vertical",
      		style: "icon",
      		items: [],
      	},
      ]
      
  • Deprecates the default header button function that opens the preview, you should initialize a preview instance and use that in the hooks.onHeaderButtonClicked hook
    const previewInstance = await chamaileonPlugins.createFullscreenPlugin({
    	plugin: "preview",
    	data: {},
    	settings: {},
    	hooks: {},
    });
    
    const editorInstance = await chamaileonPlugins.createFullscreenPlugin({
    	plugin: "editor",
    	data: { document },
    	settings: {},
    	hooks: {
    		onHeaderButtonClicked: async ({ buttonId }) => {
    			if (buttonId === "preview") {
    				const currentJSON = await editorInstance.methods.getDocument();
    				await previewInstance.methods.updateData({ document: currentJSON });
    				previewInstance.show();
    			}
    		};
    	},
    });
    

Preview

  • Moves the document top level property inside the data property
  • Deprecates the hideHeader top level property, use the settings.hideHeader property instead
  • Deprecates the title top level property, use the data.document.title property instead

Thumbnail

  • Moves the container property from the first object property into the second. The property now accepts a HTMLElement as well as a selector.
    • Old thumbnail initialization
      await chamaileonPlugins.createThumbnail({
      	document: {},
      	container: "#email-thumbnail",
      	height: 640,
      	width: 480,
      	scale: 0.5,
      	scroll: true,
      });
      
    • New thumbnail initialization
      await chamaileonPlugins.createInlinePlugin(
      	{
      		plugin: "thumbnail",
      		data: { document },
      		settings: {
      			scroll: true,
      		},
      		hooks: {},
      	},
      	{
      		container: document.getElementById("email-thumbnail"),
      		dimensions: {
      			width: 640,
      			height: 480,
      			scale: 0.5,
      		}
      	}
      );
      
  • Renames the DOMHeight hook to sendDOMHeight
  • Changes the getDocumentHeight hook into getDocumentHeight method
  • Moves the height top level property inside the settings property
  • Moves the width top level property inside the settings property
  • Moves the scale top level property inside the settings property
  • Moves the scroll top level property inside the settings property
  • Moves the document top level property inside the data property

Gallery

  • Moves the editImgSrc top level property inside the data as currentImgSrc property
  • Moves the dimensions top level property inside the data property
  • Renames the settings.photoEditorLicece to settings.photoEditorLicense
  • From now the gallery will not return with a Promise that resolves with the selected image. Instead you have to call the pickImage method on the initialized instance instead.
    • Old gallery usage
      const { src }  = await chamaileonPlugins.openGallery({ editImgSrc, dimensions });
      
    • New gallery usage
      const galleryInstance = await chamaileonPlugins.createFullscreenPlugin({
      	plugin: "gallery",
      	data: {
      		currentImgSrc,
      		dimensions,
      	},
      	settings: {},
      	hooks: {},
      });
      
      const { src }  = await galleryInstance.methods.pickImage();
      

Variable Editor

  • Renames setDocument to updateData
  • Renames settings.buttons.textInsertPlugin to settings.buttons.textInsert`
  • Moves the document top level property inside the data property
  • Changed header and footer buttons to have the same config as the other plugins
    • You can have an icon and a label on a button at the same time
    • You can use colors and button styles (the old buttons had a fixed style and a fixed color)
      • Icon buttons had the default style and black color
      • Labeled buttons had the outlined style and black color
    • Old button config
      variableEditorConfig.settings.buttons.header = {
      	left: [
      		{
      			id: "close",
      			icon: "arrow-left"
      		},
      	],
      	right: [
      		{
      			id: "prev",
      			label: "Prev",
      		},
      	],
      };
      
    • New button config
      variableEditorConfig.settings.buttons.header = {
      	left: [
      		{
      			id: "close",
      			icon: "arrow-left",
      			color: "#000",
      		},
      	],
      	right: [
      		{
      			id: "prev",
      			label: "Prev",
      			style: "outlined,
      			color: "#000",
      		},
      	],
      };
      

Example

You can visit our Chamaileon SDK Playground to see the new interface in action.