How to trigger ngModelChange event from Custom Video Formats
Hi there
I have added a custom video formatter which has the logic to resize the video element which is working as expected, but only issue here is that change in the size of video element is not triggering the editor content change event but after changing the size if i type in some text in the editor than the updated video element size is reflected in editor Change event.
So is there way to trigger the editor content change event withing the Video BlobFormatter ?
Below is the code on how I am registering the Custom video Formatter
Quill.register({ 'modules/blotFormatter': BlotFormatter, 'formats/video': QuillCustomVideo, 'formats/image': QuillCustomImage, });
An below is how the resize feature looks on UI
Thank You
it depends how your resize is implemented.
ngx-quill per default only track real "user" changes. if you check the quilljs documentation you see each "update" api accepts a last "source" parameter, which can be "user", "api", "silent". So per default ngx-quill only recognized "user" changes as real changes. if not each formControl/ngModel change from outside would retrigger the change-outputs.
you have two options -> in your custom resize module trigger the changes as "user" or set the trackChanges input of the ngx-quill editor component to all.
it depends how your resize is implemented.
ngx-quill per default only track real "user" changes. if you check the quilljs documentation you see each "update" api accepts a last "source" parameter, which can be "user", "api", "silent". So per default ngx-quill only recognized "user" changes as real changes. if not each formControl/ngModel change from outside would retrigger the change-outputs.
you have two options -> in your custom resize module trigger the changes as "user" or set the
trackChangesinput of the ngx-quill editor component toall.
Can i trigger changes from above CustomVideoBlob?
do not know... since i can not see all your code..
Sorry for that below is how i am adding the video format blot and a resize logic on it
import Quill from 'quill';
const Parchment = Quill.import('parchment');
const Video = Quill.import('formats/video');
interface VideoAttributes {
[key: string]: string | undefined;
width?: string;
height?: string;
style?: string;
}
const ATTRIBUTES = ['width', 'height', 'style'];
interface NubStyles {
[key: string]: {
top?: string;
right?: string;
bottom?: string;
left?: string;
};
}
const nubStyles: NubStyles = {
tLeft: {
top: '-5px',
left: '-5px',
},
tRight: {
top: '-5px',
right: '-5px',
},
bLeft: {
bottom: '-5px',
left: '-5px',
},
bRight: {
bottom: '-5px',
right: '-5px',
},
};
const getClosest = (el: any, sel: any) => {
while ((el = el.parentElement) && !(el.matches || el.matchesSelector).call(el, sel));
return el;
};
const createSpacer = () => {
let spacer = document.createElement('div');
spacer.appendChild(document.createElement('br'));
return spacer;
};
class VideoBuilder {
selectedElement: any;
parentElement: any;
node: any;
videoElement: any;
boxes: HTMLSpanElement[] = [];
dragHandeler: any;
mouseUp: any;
mouseDown: any;
dragBox: any;
dragStartX: any;
dragStartY: any;
preDragWidth: any;
preDragHeight: any;
handelDeselect: any;
buildVideoElement(src: any, node: any, videoHeight: number, videoWidth: number) {
let videoElement = document.createElement('video');
videoElement.setAttribute('frameborder', '0');
videoElement.setAttribute('allowfullscreen', 'true');
videoElement.className = 'td-quill-video-editing';
videoElement.setAttribute('width', videoWidth.toString());
videoElement.setAttribute('height', videoHeight.toString());
videoElement.setAttribute('src', src);
return videoElement;
}
buildNode(node: any, wrapper: any, videoHeight: number, videoWidth: number) {
node.appendChild(wrapper);
setTimeout(() => {
let videoElement = node.getElementsByTagName('video')[0];
videoElement.setAttribute('width', videoWidth.toString() || 300);
videoElement.setAttribute('height', videoHeight.toString() || 150);
node.setAttribute('contenteditable', 'false');
node.parentElement && node.parentElement.insertBefore(createSpacer(), node);
node.parentElement && node.parentElement.appendChild(createSpacer());
}, 0);
return node;
}
buildOverlay() {
let overlay = document.createElement('div');
overlay.setAttribute('class', 'td-quill-video-overlay');
overlay.setAttribute('contenteditable', 'false');
overlay.addEventListener('click', (event) => {
let rootElement = getClosest(overlay, '.ql-editor');
if (rootElement && rootElement.__blot) {
let node = Parchment.find(overlay?.parentElement?.parentElement);
if (node instanceof Video) {
node.domNode.builder.select(overlay, node);
}
}
});
return overlay;
}
select(overlay: anynode: any) {
this.selectedElement = overlay;
if (this.selectedElement.className.indexOf('active') === -1) {
this.parentElement = this.selectedElement.parentElement;
this.node = node;
this.videoElement = this.parentElement.getElementsByTagName('video')[0];
this.selectedElement.setAttribute('class', 'td-quill-video-overlay active');
this.buildResize();
this.handelDeselect = this.deselect.bind(this);
this.node.parent.domNode.addEventListener('click', this.handelDeselect, false);
}
}
deselect(event: any) {
if (event.target !== this.selectedElement) {
this.selectedElement.setAttribute('class', 'td-quill-video-overlay');
this.clearNubEvents(true);
while (this.selectedElement.firstChild) {
this.selectedElement.removeChild(this.selectedElement.firstChild);
}
this.selectedElement = null;
this.node.parent.domNode.removeEventListener('click', this.handelDeselect, false);
}
}
buildResize() {
this.boxes = [];
this.dragHandeler = this.handleDrag.bind(this);
this.mouseUp = this.handelMouseUp.bind(this);
this.mouseDown = this.handleMousedown.bind(this);
for (let key in nubStyles) {
let nub = this.buildNub(key);
this.boxes.push(nub);
this.selectedElement.appendChild(nub);
}
return this.selectedElement;
}
buildNub(pos: any) {
let nub = document.createElement('span');
nub.className = 'td-quill-resize-nub';
Object.assign(nub.style, nubStyles[pos]);
nub.addEventListener('mousedown', this.mouseDown, false);
return nub;
}
handleMousedown(event: any) {
this.dragBox = event.target;
this.dragStartX = event.clientX;
this.dragStartY = event.clientY;
this.preDragWidth = parseInt(this.videoElement.width, 10) || 300;
this.preDragHeight = parseInt(this.videoElement.height, 10) || 150;
document.addEventListener('mousemove', this.dragHandeler, false);
document.addEventListener('mouseup', this.mouseUp, false);
}
handleDrag(event: any) {
if (!this.videoElement) {
return;
}
const deltaX = event.clientX - this.dragStartX;
const deltaY = event.clientY - this.dragStartY;
if (this.dragBox === this.boxes[0] || this.dragBox === this.boxes[2]) {
this.videoElement.width = Math.round(this.preDragWidth - deltaX);
this.videoElement.height = Math.round(this.preDragHeight + deltaY);
} else {
this.videoElement.width = Math.round(this.preDragWidth + deltaX);
this.videoElement.height = Math.round(this.preDragHeight + deltaY);
}
this.node.domNode.style.width = this.videoElement.width;
this.node.domNode.style.height = this.videoElement.height;
}
handelMouseUp(event: any) {
this.clearNubEvents(false);
}
clearNubEvents(include_nub: boolean) {
for (let nub in this.boxes) {
document.removeEventListener('mousemove', this.dragHandeler, false);
document.removeEventListener('mouseup', this.mouseUp, false);
if (include_nub) {
this.boxes[nub].removeEventListener('mousedown', this.handleMousedown, false);
}
}
this.node.parent.update('user');
}
}
export default class QuillCustomVideo extends Video {
static videoHeight: number = 150;
static videoWidth: number = 300;
static formats(domNode: HTMLElement): VideoAttributes {
return ATTRIBUTES.reduce((formats, attribute) => {
if (domNode.hasAttribute(attribute)) {
formats[attribute] = domNode.getAttribute(attribute) || '';
}
return formats;
}, {} as VideoAttributes);
}
format(name: string, value: any) {
if (ATTRIBUTES.indexOf(name) > -1) {
if (value) {
this['domNode'].setAttribute(name, value);
} else {
this['domNode'].removeAttribute(name);
}
} else {
super.format(name, value);
}
}
static create(url: any) {
let node = super.create();
node.builder = new VideoBuilder();
let wrapper = document.createElement('div');
wrapper.className = 'td-quill-video-wrapper';
let videoElement = node.builder.buildVideoElement(url, node, this.videoHeight, this.videoWidth);
let overlay = node.builder.buildOverlay();
wrapper.appendChild(videoElement);
wrapper.appendChild(overlay);
return node.builder.buildNode(node, wrapper, this.videoHeight, this.videoWidth);
}
static value(node: HTMLElement) {
debugger;
if (node.innerHTML) {
let firstChild = node.firstChild;
if (firstChild) {
let secondChild = firstChild.firstChild;
if (secondChild && secondChild instanceof HTMLVideoElement) {
this.videoHeight = secondChild.clientHeight;
this.videoWidth = secondChild.clientWidth;
} else {
console.log('No video element found or the element is not a video.');
}
}
}
return node.getElementsByTagName('video')[0].getAttribute('src');
}
}
QuillCustomVideo.blotName = 'video';
QuillCustomVideo.tagName = 'div';
Quill.register(QuillCustomVideo, true);
you can try to use https://quilljs.com/docs/api/#update. Call it with 'user' whenever the "user" is changing something you want to be triggered as a real change
you can try to use https://quilljs.com/docs/api/#update. Call it with 'user' whenever the "user" is changing something you want to be triggered as a real change
Ya sure ill try this and check
Thanks for your quick response
@Gourishmesta any news on that one?
@Gourishmesta any news on that one?
Nope i tried calling update with user as parameter still didn't work
another thing would be to check how the editor triggers changes for other formats/modules like images, links and so on.
@KillerCodeMonkey will give it a try Thank You
any updates @Gourishmesta
closing