[QUESTION]: How to update the model for a custom component when a trait is changed.
Question
Hi! I have read and re-read the GrapesJS Docs and API Reference on how to create a custom component and I just can't seem to figure it out. I initially started trying to extend the existing video component but then decided to make my own custom video component. I'm using the existing video component as a guide. My component can be dragged onto the canvas and the component settings (traits) can be toggled but nothing updates in the canvas. My component tagName is a div because I need a wrapper to make the inner iframe responsive via CSS. That in itself could be an issue in how I'm adding the iframe but I can't get far enough with this to tell for sure.
I really appreciate any insight that you can provide!
Code snippet update and moved to closing comment
Answers (1)
I have finally figured this out on my own and want to share the result for anyone else that is struggling with this the way I did. This code snipped creates a custom, responsive video component that supports YouTube, Vimeo, MyVRSpot, and a custom iframe src. I was able to get the model to update in the canvas when a component setting (trait) is changed. I hope someone else finds this helpful.
(function() {
var PageBuilder = {
// ...
"BuildGrapesEditor": function() {
// INITIALLY LOAD THE SAVED APP CONTENT INTO THE EDITOR
var savedComponents = JSON.parse($("#grapes-components").val());
// DEFINE CUSTOM COMPONENTS *BEFORE* GRAPES INITIALIZATION (VERY IMPORTANT) - https://grapesjs.com/docs/modules/Components.html#define-custom-component-type
// ALSO ADD PLUGIN NAMES IN THIS METHODS RETURNED ARRAY
var plugins = this.GatherCustomComponents();
// INITIALIZE THE GRAPES EDITOR
this.GrapesEditor = grapesjs.init({
container: "#cs-grapes-editor",
height: "700px",
width: "auto",
showOffsets: true,
allowScripts: true,
components: savedComponents,
style: $("#grapes-css").val(),
storageManager: {
type: null,
autosave: false,
},
avoidInlineStyle: true,
plugins: plugins
});
this.GrapesCommands = this.GrapesEditor.Commands;
this.GrapesPanels = this.GrapesEditor.Panels;
this.GrapesBlocks = this.GrapesEditor.BlockManager;
},
"GatherCustomComponents": function() {
var _this = this;
// CUSTOM VIDEO COMPONENT
var customVideo = function(editor) {
editor.DomComponents.addType("csVideo", {
// FRAMEWORK METHOD - https://grapesjs.com/docs/modules/Components.html#model
"model": {
defaults: {
// FRAMEWORK DEFAULTS
tagName: "div",
draggable: true,
droppable: false,
classes: ["cs-gjs-video"],
content: '<iframe class="gjs-no-pointer" src="https://www.youtube.com/embed/mxD7QEt85ms" frameborder="0" allow="autoplay; fullscreen" scrolling="no"></iframe>' +
'<style>' +
'.cs-gjs-video {' +
'position: relative;' +
'margin: 5px 0px;' +
'}' +
'.cs-gjs-video:before {' +
'content: "";' +
'display: block;' +
'padding-top: 56.25%;' +
'}' +
'.cs-gjs-video iframe {' +
'width: 100%;' +
'height: 100%;' +
'position: absolute;' +
'top: 0px;' +
'left: 0px;' +
'}' +
'</style>',
editable: true,
// CUSTOM DEFAULTS
provider: "youtube",
videoId: "mxD7QEt85ms",
videoWidth: 560,
videoHeight: 315,
youtubeUrl: "https://www.youtube.com/embed/",
youtubeDefaultId: "mxD7QEt85ms",
vimeoUrl: "https://player.vimeo.com/video/",
vimeoDefaultId: "22439234",
myvrspotUrl: "https://live.myvrspot.com/iframe",
myvrspotDefaultId: "vff46db9afa6d",
iframeDefaultId: 'https://www.youtube.com/embed/mxD7QEt85ms',
loop: 0,
autoplay: 0,
controls: 1,
color: "",
rel: 1,
modestbranding: 0,
title: 1,
portrait: 1,
byline: 1
},
// FRAMEWORK METHOD
"init": function(properties, options) {
// SET TRAITS
var traits = this.getTraits();
this.set("traits", traits);
// MAKE COMPONENTS PANEL ACTIVE
if (_this.GrapesEditor.Panels !== undefined) {
_this.GrapesEditor.Panels.getButton("views", "open-tm").set("active", true);
}
// EVENT LISTENER FOR TRAIT CHANGES
this.listenTo(this, "change:provider", this.updateTraits);
this.listenTo(this, "change:videoId change:provider change:videoWidth change:videoHeight change:autoplay change:loop change:controls change:rel change:modestbranding change:color change:portrait change:title change:byline", this.updateModel);
},
// CUSTOM METHOD TO UPDATE THE COMPONENT MODEL - https://grapesjs.com/docs/modules/Components.html#define-custom-component-type
"updateModel": function() {
this.updateSrc();
var attributes = this.attributes;
var modelComponent = _this.GrapesEditor.getSelected();
modelComponent.addAttributes({
"data-cs-gjs-id": modelComponent.ccid
});
modelComponent.set("content", '' +
'<iframe class="gjs-no-pointer" src="' + attributes.src + '" frameborder="0" allow="autoplay; fullscreen" scrolling="no"></iframe>' +
'<style>' +
'[data-cs-gjs-id="' + modelComponent.ccid + '"] {' +
'position: relative;' +
'margin: 5px 0px;' +
'}' +
'[data-cs-gjs-id="' + modelComponent.ccid + '"]:before {' +
'content: "";' +
'display: block;' +
'padding-top: ' + ((attributes.videoHeight / attributes.videoWidth) * 100) + '%;' +
'}' +
'[data-cs-gjs-id="' + modelComponent.ccid + '"] iframe {' +
'width: 100%;' +
'height: 100%;' +
'position: absolute;' +
'top: 0px;' +
'left: 0px;' +
'}' +
'</style>' +
'');
},
// CUSTOM METHOD TO RETURN PROVIDER DROPDOWN TRAIT
"getProviderTrait": function() {
return {
type: "select",
label: "Provider",
name: "provider",
changeProp: 1,
options: [{
value: "youtube",
name: "YouTube"
},
{
value: "vimeo",
name: "Vimeo"
},
{
value: "myvrspot",
name: "MyVRSpot"
},
{
value: "iframe",
name: "Custom iframe"
}
],
};
},
// CUSTOM METHOD TO RETURN YOUTUBE TRAITS
"getYouTubeTraits": function() {
return [
this.getProviderTrait(),
{
type: "text",
label: "Video ID",
name: "videoId",
placeholder: "eg. mxD7QEt85ms",
changeProp: 1
},
{
type: "text",
label: "Video Width",
name: "videoWidth",
placeholder: "eg. 560",
changeProp: 1
},
{
type: "text",
label: "Video Height",
name: "videoHeight",
placeholder: "eg. 315",
changeProp: 1
},
{
type: "checkbox",
label: "Autoplay",
name: "autoplay",
changeProp: 1
},
{
type: "checkbox",
label: "Loop",
name: "loop",
changeProp: 1
},
{
type: "checkbox",
label: "Controls",
name: "controls",
changeProp: 1
},
{
type: "checkbox",
label: "Related",
name: "rel",
changeProp: 1
},
{
type: "checkbox",
label: "Modest Branding",
name: "modestbranding",
changeProp: 1
}
];
},
// CUSTOM METHOD TO RETURN VIMEO TRAITS
"getVimeoTraits": function() {
return [
this.getProviderTrait(),
{
type: "text",
label: "Video ID",
name: "videoId",
placeholder: "eg. 22439234",
changeProp: 1
},
{
type: "text",
label: "Video Width",
name: "videoWidth",
placeholder: "eg. 560",
changeProp: 1
},
{
type: "text",
label: "Video Height",
name: "videoHeight",
placeholder: "eg. 315",
changeProp: 1
},
{
type: "text",
label: "Color",
name: "color",
placeholder: "eg. #00adef",
changeProp: 1
},
{
type: "checkbox",
label: "Autoplay",
name: "autoplay",
changeProp: 1
},
{
type: "checkbox",
label: "Loop",
name: "loop",
changeProp: 1
},
{
type: "checkbox",
label: "Portrait",
name: "portrait",
changeProp: 1
},
{
type: "checkbox",
label: "Title",
name: "title",
changeProp: 1
},
{
type: "checkbox",
label: "Byline",
name: "byline",
changeProp: 1
}
];
},
// CUSTOM METHOD TO RETURN MYVRSPOT TRAITS
"getMyVRSpotTraits": function() {
return [
this.getProviderTrait(),
{
type: "text",
label: "Video ID",
name: "videoId",
placeholder: "eg. vff46db9afa6d",
changeProp: 1
},
{
type: "text",
label: "Video Width",
name: "videoWidth",
placeholder: "eg. 560",
changeProp: 1
},
{
type: "text",
label: "Video Height",
name: "videoHeight",
placeholder: "eg. 315",
changeProp: 1
}
];
},
// CUSTOM METHOD TO RETURN CUSTOM IFRAME TRAITS
"getIframeTraits": function() {
return [
this.getProviderTrait(),
{
type: "text",
label: "iframe Src Url",
name: "videoId",
placeholder: 'eg. https://player.vimeo.com/video/22439234?autoplay=1&loop=1&title=0&byline=0&portrait=0',
changeProp: 1
},
{
type: "text",
label: "Video Width",
name: "videoWidth",
placeholder: "eg. 560",
changeProp: 1
},
{
type: "text",
label: "Video Height",
name: "videoHeight",
placeholder: "eg. 315",
changeProp: 1
}
];
},
// CUSTOM HELPER METHOD TO GET THE TRAITS FOR THE SELECTED PROVIDER
"getTraits": function() {
var provider = this.get("provider");
var traits = "";
switch (provider) {
case "youtube":
traits = this.getYouTubeTraits();
break;
case "vimeo":
traits = this.getVimeoTraits();
break;
case "myvrspot":
traits = this.getMyVRSpotTraits();
break;
case "iframe":
traits = this.getIframeTraits();
break;
default:
traits = this.getYouTubeTraits();
break;
}
return traits;
},
// CUSTOM METHOD TO UPDATE TRAITS ON PROVIDER DROPDOWN CHANGE
"updateTraits": function() {
this.loadTraits(this.getTraits());
var provider = this.get("provider");
switch (provider) {
case "youtube":
traits = this.getYouTubeTraits();
break;
case "vimeo":
traits = this.getVimeoTraits();
break;
case "myvrspot":
traits = this.getMyVRSpotTraits();
break;
case "iframe":
traits = this.getIframeTraits();
break;
default:
traits = this.getYouTubeTraits();
break;
}
return traits;
},
// CUSTOM HELPER METHOD TO UPDATE THE IFRAME SRC WHEN TRAITS CHANGE
"updateSrc": function() {
var provider = this.get("provider");
switch (provider) {
case "youtube":
this.set("src", this.getYouTubeSrc());
break;
case "vimeo":
this.set("src", this.getVimeoSrc());
break;
case "myvrspot":
this.set("src", this.getMyVRSpotSrc());
break;
case "iframe":
this.set("src", this.getIframeSrc());
break;
}
},
// CUSTOM METHOD TO BUILD YOUTUBE IFRAME SRC
"getYouTubeSrc": function() {
var videoId = this.get("videoId");
var url = this.get("youtubeUrl");
url += videoId + "?";
url += this.get("autoplay") ? "&autoplay=1" : "";
url += this.get("controls") ? "" : "&controls=0&showinfo=0";
url += this.get("loop") ? "&loop=1&playlist=" + videoId : "";
url += this.get("rel") ? "" : "&rel=0";
url += this.get("modestbranding") ? "&modestbranding=1" : "";
return url;
},
// CUSTOM METHOD TO BUILD VIMEO IFRAME SRC
"getVimeoSrc": function() {
var videoId = this.get("videoId");
var url = this.get("vimeoUrl");
url += videoId + "?";
url += this.get("autoplay") ? "&autoplay=1" : "";
url += this.get("loop") ? "&loop=1" : "";
url += this.get("portrait") ? "&portrait=0" : "";
url += this.get("title") ? "&title=0" : "";
url += this.get("byline") ? "" : "&byline=0";
url += this.get("color") ? "&color=" + this.get("color") : "";
return url;
},
// CUSTOM METHOD TO BUILD MYVRSPOT IFRAME SRC
"getMyVRSpotSrc": function() {
var videoId = this.get("videoId");
var url = this.get("myvrspotUrl");
url += "?v=" + videoId;
return url;
},
// CUSTOM METHOD TO BUILD CUSTOM IFRAME SRC
"getIframeSrc": function() {
var videoId = this.get("videoId");
return videoId;
}
}
});
}
// RETURN AN ARRAY OF DEFINED COMPONENTS/PLUGINS
return [customVideo];
},
// ...
"ExtendGrapesBlocks": function() {
var _this = this;
// VIDEO BLOCK
this.GrapesBlocks.add("csVideo", {
label: 'Video',
content: {
type: "csVideo",
name: "Video",
attributes: {
"data-element-type": "csVideo"
}
},
category: 'Multimedia',
attributes: {
"class": "fa fa-youtube-play"
},
select: true
});
},
// ...
};
})();
Related Questions and Answers
Continue research with similar issue discussions.
Issue #2741
How to wrap component with text nodes based on trait?
Hello @artf , I'm not sure if this goes here but I can't figure out from the documentation how to update the component model based on a tra...
Issue #2777
[QUESTION] How to update styles in Style Manager
Hello. I created custom component where I set backgraound-image via custom asset manager Custom block code: Command which I run to set new...
Issue #358
Calling updateScript from the script section of a pluging
Hi guys, I've been stuck for a few hours trying to figure out how to call updateScript from the script section of a custom plugin. So far I...
Issue #2696
[QUESTION]: How can I create block with background image with behavior like built-in "image"?
Hello, I'm trying to create block with background image (similar to Grapedrop Image Box). I added new component like this: And also I added...
Paid Plugins That Match This Issue
Curated by issue keywords and label relevance to help you ship faster.
Loading paid plugin recommendations...
Browse Plugin Categories
Jump directly to plugin category pages on the marketplace.