Monaco editor dynamically resizable - javascript

I have been searching for a discussion about if it's possible to mimic the html tag textarea's resizing when using Monaco Editor's field all over the Internet but I couldn't find one answering my question.
I'm using the monaco-editor npm package in a React application. Do you have any idea if this is easy to implement?
Thank you in advance!
SOLUTION
With pure css I selected the target html element and just added these properties:
div {
resize: vertical;
overflow: auto;
}

TL;DR: add automaticLayout: true to your editor's configuration.
NL;PR:
Monaco has a built-in auto resize to parent container functionality:
createEditorWithAutoResize(){
this.editor = monaco.editor.create(
this.editorDiv.current, {
value: "var x = 0;",
language: 'javascript',
automaticLayout: true // <<== the important part
}
);
}
componentDidMount(){this.createEditorWithAutoResize();}
constructor(props){super(props); this.editorDiv = React.createRef();}
render(){return <div ref={this.editorDiv} className="editor" ></div>}
And the CSS for the editor (it avoids rendering the editor for the first time with like 10px height):
.editor{
height: 100%;
}
First tested: v0.10.1, Last tested: v0.32.1
Note:
< v0.20.0: The mechanism does not listen to its container size changes, it polls them.
#nrayburn-tech (Monaco Editor's contributor): Version 0.20 uses MutationObserver for all browsers. Version 0.21 and later uses ResizeObserver on supported browsers, otherwise, it uses polling as a fallback.

if you have a reference to the editor you can just call
editor.layout()
on some resize event.
For example, on window resize:
window.onresize = function (){
editor.layout();
};

For anyone coming here having this issue in a basic web app (html, css, javascript) I've found a solution for the resizing issue I'm experiencing.
I have the monaco editor in a resizable flex container. It will only grow the width, not shrink it, and vertical resizing doesn't seem to work out of the box.
If you use the monaco config "automaticLayout: true" and the following CSS it seems to resize as expected:
.monaco-editor { position: absolute !important; }
I tried the max-width 99% trick but it causes a laggy delayed effect when increasing the width near edge of page.

For posterity, the solution I arrived on was to set automaticLayout: false so that I could perform all the layout in a resize event listener.
const placeholder = document.getElementById('placeholder')
const editor = monaco.editor.create(placeholder, {
value: '// hello world',
language: 'javascript',
automaticLayout: false // or remove, it defaults to false
})
// we need the parent of the editor
const parent = placeholder.parentElement
window.addEventListener('resize', () => {
// make editor as small as possible
editor.layout({ width: 0, height: 0 })
// wait for next frame to ensure last layout finished
window.requestAnimationFrame(() => {
// get the parent dimensions and re-layout the editor
const rect = parent.getBoundingClientRect()
editor.layout({ width: rect.width, height: rect.height })
})
})
By first reducing the editor layout to 0 we can safely query the dimensions of the parent element without the child (editor) contributing to its size. We can then match the editor to the new parent dimensions. Since this takes place over a single frame, there should be no flickering or lag.

this is old question but get the problem to and solved it with react-resize-detector
based on ResizeObserver it feet perfectly to the need (check browser compatibility)
Exemple of component :
import React, { Component } from 'react';
import ReactResizeDetector from 'react-resize-detector';
import * as monaco from 'monaco-editor';
class Editor extends Component {
constructor(props) {
super(props)
this.state = {
width: 0,
height: 0,
}
this.editor_div = React.createRef()
this.handle_rezise = this.handle_rezise.bind(this);
}
componentDidMount() {
const editor_model = monaco.editor.createModel('', 'sql');
this.monaco_editor = monaco.editor.create(this.editor_div.current, this.props.editorOptions);
this.monaco_editor.setModel(editor_model);
}
componentWillUnmount() {
this.monaco_editor && this.monaco_editor.dispose();
}
handle_rezise(width, height) {
this.monaco_editor.layout({ height, width });
}
render() {
return(
<div
className="editor-container"
style={{ height: '100%' }}>
<ReactResizeDetector
handleWidth
handleHeight
onResize={ this.handle_rezise }
refreshMode="debounce"
refreshRate={100} />
<div
className="editor"
ref={ this.editor_div }
style={{ height: '100%' }} />
</div>
)
}
}
export default Editor;
Hope it's help

In my case I'm using that exact CSS but although automaticLayout: true works, I found out overkill (seems to pooling the DOM 100ms interval and I have several editors opened in the document. SO I ended up implementing it manually :
just in case , my needs are different: I want the user to resize it the container - in a standard way and cheap (both on code and performance) on libraries and performance. This is what I did:
css container : resize: vertical; overflow: auto
and this js :
function installResizeWatcher(el, fn, interval){
let offset = {width: el.offsetWidth, height: el.offsetHeight}
setInterval(()=>{
let newOffset = {width: el.offsetWidth, height: el.offsetHeight}
if(offset.height!=newOffset.height||offset.width!=newOffset.width){
offset = newOffset
fn()
}
}, interval)
}
const typeScriptCodeContainer = document.getElementById('typeScriptCodeContainer')
typeScriptCodeEditor = monaco.editor.create(typeScriptCodeContainer, Object.assign(editorOptions, {value: example.codeValue}))
installResizeWatcher(typeScriptCodeContainer, typeScriptCodeEditor.layout.bind(typeScriptCodeEditor), 2000)
yes, 2 seconds interval and make sure it registers only once. I see there is / was a resize interval on 100ms for the automatic relayout in monaco - IMHO that's too much.
See it in action: https://typescript-api-playground.glitch.me/?example=2

Related

How to use IntersectionObserver API to make navBar sticky

I am making the clone of a webpage which is made in JS but I am developing it by HTML, CSS, JS. Its navBar looks like this . Here is the link if you want to experience yourself link.
So, I have tried to implement this using IntersectionObserver API as well as by using window.addEventListener(). I don't want to implement this by using scroll event Listener because it is too heavy for end user.
const intersectionCB = ([entry]) => {
const elem = entry.target;
if (!entry.isIntersecting) {
elem.classList.add('nav__2-sticky');
// observer.unobserve(navBar);
} else {
elem.classList.remove('nav__2-sticky');
}
};
const observer = new IntersectionObserver(intersectionCB, {
root: null,
threshold: 0
});
observer.observe(navBar);
In HTML file
<div class="nav__2">
<div class="row nav__2--content">
<div class="logo-container">
<img src="img/logo-black.png" alt="" class="logo" />
</div>
........
In SCSS file
.nav {
&__2 {
top: 8rem;
position: absolute;
left: 0;
width: 100%;
&-sticky {
position: fixed;
top: 0;
}
}
}
You might understand what is happening. When navBar gets out of the view, (navBar is positioned at 8rem from top!). I append nav__2-sticky class (which is positioned fixed at 0 from top) to appear on the screen. Due to which entry.isIntersecting becomes true and elem.classList.remove('nav__2-sticky'); is executed. As a result navBar again gets out of the view and again elem.classList.add('nav__2-sticky') is executed. This cycle of adding and removing classes due to entry.isIntersecting becoming True and False is creating a problem for me. This happens in such speed that it shows abnormal behaviour.
So, is there any proper solution for this? I would also like to hear other solutions that might work.
I used scroll event after all. Here is the code, I think I don't need to explain. You will get more detailed explanation here link
const initialCords = navBar.getBoundingClientRect();
document.addEventListener('scroll', () => {
if (window.scrollY > initialCords.top) {
navBar.classList.add('nav__2-sticky');
} else {
navBar.classList.remove('nav__2-sticky');
}
});
Another angle could be to run the intersection observer on an element that is out of view (below the bottom of the screen) and not only the navbar itself

How to optimize many dom reflow operations caused by getComputedStyle?

I'm building solution for business PDF like HTML elements splitting based on their content height which can be used for example - with puppeter/wkhtmltopdf to render suitable layout as much similar with my HTML/JS solution as possible. My goal is also to create unlimited to margins/padding/box-sizing way to styling it in CSS.
For better explanation, imagine div container with constant height, 1000px for example, but content is unlimited, and everytime its sum will take more than thats div specified height, we have to clone that page with appriopriate layout and content which overflows its boundary.
One of use cases is to use it in React because it has very high possibility to make it fancy with his reactive functionalities (Live pdf preview). But building it with react was such a pain, so I decided to create it in plain javascript.
I'm watching for DOM update using MutationObserver
private _initOnDomUpdateEvents(observer: Element, callback?: () => any) {
const mutation = new MutationObserver(() => {
callback();
});
mutation.observe(observer, {
attributes: true,
subtree: true,
childList: true
})
this.watchers.push(mutation);
return mutation;
}
And everytime the DOM has ben updated in observer scope (for example in .css selector named .sheet), I'm creating tree of its children which contains details about their relative height, offsetY, margins, paddings, borders, box-sizing, overflow and display - It has to be very accurate, pixel perfect almost. But to archive this, I've used very expansive solution, and optimisation gives me headache everytime I think of it.
To imagine my problem, look at this.
class Tree {
public getElementTree(element: Element): TreeElement {
const main = Tree._getChildrenBoundaryDetails(element, element);
main.setChildren(Tree._buildDimensionsTree(main, element));
return main;
}
private static _buildDimensionsTree(parent: TreeElement, element: Element) {
return Array.from(element.children).map(e => {
const dimensions = Tree._getChildrenBoundaryDetails(parent.getDomElement(), e)
dimensions.setChildren(Array.from(e.children).map(e => Tree._getChildrenBoundaryDetails(parent.getDomElement(), e)))
return dimensions;
})
}
private static _getChildrenBoundaryDetails(parent: Element, element: Element): TreeElement {
const style = getComputedStyle(element);
const offsetHeightTop = element.getBoundingClientRect().top
- parent.getBoundingClientRect().top;
return new TreeElement({
borderBox: style.boxSizing === 'border-box',
element: element,
offsetY: {
from: offsetHeightTop,
to: offsetHeightTop + parseFloat(style.height)
},
margin: {
top: parseFloat(style.marginTop),
bottom: parseFloat(style.marginBottom)
},
padding: {
top: parseFloat(style.paddingTop),
bottom: parseFloat(style.paddingBottom),
},
border: {
top: parseFloat(style.borderTopWidth),
bottom: parseFloat(style.borderBottomWidth)
}
})
}
}
Do you see that recursion in Tree._buildDimensionsTree? It's terrifying if you imagine, how many children may exists in real world examples, and for each, you have to call window.getComputedStyle. So I'm comming with question.
How to create more CPU friendly solution which don't requires as many DOM Reflows as in my example.
Thank you for help.

Can't change element's height

I'm trying to resize element by dragging (like so). I wrote a simple directive (will handle ghostbar later):
#Directive({ selector: '[drag-resize]' })
export class DragResizeDirective {
private dragging: boolean;
constructor(el: ElementRef, renderer: Renderer2) {
renderer.listen('window', 'mouseup', (e) => {
if (this.dragging) {
el.nativeElement.style.backgroundColor = 'red'; // Works fine.
el.nativeElement.style.height = `${e.pageY + 2}px`; // Not so much.
this.dragging = false;
}
});
}
#HostListener('mousedown') onMouseDown() {
this.dragging = true;
}
}
The problem is I can't change height of the element. Other styles work fine. I'm using Angular 4.0.3.
Computed attributes:
display: block;
height: 244.781px;
left: 0px;
overflow-x: hidden;
overflow-y: scroll;
position: absolute;
top: 655.422px;
width: 1793px;
*renderer.setStyle() doesn't work either.
** Element I'm trying to resize is a grid tile (md-grid-tile from Angular Material).
*** Directive works on other elements.
Edit 2:
I've dug into the md-grid-list implementation. The row height is recalculated everytime that ngAfterContentChecked is triggered (code). This happens after every mouse event and is probably the reason why setting the height has no effect.
From the md-grid-list documentation I can see that you can also pass a rowHeight (e.g. 200px) input parameter to the `md-grid-list. This seems to be cleanest way to set the row height, but will scale all rows to the same size.
If this is not the effect you want to achieve, you can try setting the height in the ngAfterViewChecked lifecycle hook.
Edit:
In case your code is trying to resize a display: inline element, you first have to apply a e.g. display: inline-block to it. Otherwise it will ignore the height value.
The style.height attribute expects numeric values to have a unit (e.g. %, px, rem, em, vh).
This should work:
el.nativeElement.style.height = `${e.pageX + 2}px`;

Mithril: render one DOM element bases on another

I have a fixed height div (body) containing two children, the header and the content. The header's height is changing on click of a button, and the content's height should be auto adjusting to fill the rest of the body. Now the problem is, the new header's height is calculated after the header and content divs got rendered, so the content div's height won't be updated upon the button click. Here's the shortened code:
return m('.body', {
style: {
height: '312px'
}
}, [
m('.header', /* header contents */),
m('.content', {
style: {
height: (312 - this._viewModel._headerHeight()) + 'px'
}
}, /* some contents */)
])
The headerHeight function calculates the header's height and applies changes to it. However the new height is calculated after it's rendered thus won't be applied immediately to the calculation of content's height - there's always a lag.
Any idea to fix it?
This is a regular problem when dealing with dynamic DOM layouts in which some writable DOM properties are derived from other readable DOM properties. This is especially tough to reason about in declarative virtual DOM idioms like Mithril because they're based on the premise that every view function should be self-complete snapshots of UI state — which in this case isn't possible.
You have 3 options: you can either break out of the virtual DOM idiom to achieve this functionality by directly manipulating the DOM outside of Mithril's view, or you can model your component to operate on a '2 pass draw', whereby each potential change to the header element results in 1 draw to update the header and a second draw to update the content accordingly. Alternatively, you might be able to get away with a pure CSS solution.
Because you only need to update one property, you're almost certainly better off going for the first option. By using the config function, you can write custom functionality that executes after the view on every draw.
return m('.body', {
style: {
height: '312px'
},
config : function( el ){
el.lastChild.style.height = ( 312 - el.firstChild.offsetHeight ) + 'px'
}
}, [
m('.header', /* header contents */),
m('.content', /* some contents */)
])
The second option is more idiomatic in terms of virtual DOM philosophy because it avoids direct DOM manipulation and keeps all stateful data in a model read and applied by the view. This approach becomes more useful when you have a multitude of dynamic DOM-related properties, because you can inspect the entire view model as the view is rendered — but it's also a lot more complicated and inefficient, especially for your scenario:
controller : function(){
this.headerHeight = 0
},
view : function( ctrl ){
return m('.body', {
style: {
height: '312px'
}
}, [
m('.header', {
config : function( el ){
if( el.offsetHeight != ctrl.headerHeight ){
ctrl.headerHeight = el.offsetHeight
window.requestAnimationFrame( m.redraw )
}
}, /* header contents */),
m('.content', {
style : {
height : ( 312 - ctrl.headerHeight ) + 'px'
}
}, /* some contents */)
])
}
A third option — depending on which browsers you need to support — would be to use the CSS flexbox module.
return m('.body', {
style: {
height: '312px',
display: 'flex',
flexDirection: 'column'
}
}, [
m('.header', {
style : {
flexGrow: 1,
flexShrink: 0
}
}, /* header contents */),
m('.content', {
style : {
flexGrow: 0,
flexShrink: 1
}
}, /* some contents */)
])
This way, you can simply state that the container is a flexbox, that the header should grow to fit its content and never shrink, and that the contents should shrink but never grow.

Change tinyMce editor's height dynamically

I am using tinymce editor in my page. What I want to do is to change the height of the editor dynamically. I have created a function:
function setComposeTextareaHeight()
{
$("#compose").height(200);
}
but that is not working.
My textarea is
<textarea id="compose" cols="80" name="composeMailContent" style="width: 100%; height: 100%">
I have tried all sorts of methods for changing the height but could not come to a resolution. Is there any thing that i am missing?
You can resize tinymce with the resizeTo theme method:
editorinstance.theme.resizeTo (width, height);
The width and height set the new size of the editing area - I have not found a way to deduce the extra size of the editor instance, so you might want to do something like this:
editorinstance.theme.resizeTo (new_width - 2, new_height - 32);
Try:
tinyMCE.init({
mode : "exact",
elements : "elm1",
....
To change size dynamically in your javascript code:
var resizeHeight = 350;
var resizeWidth = 450;
tinyMCE.DOM.setStyle(tinyMCE.DOM.get("elm1" + '_ifr'), 'height', resizeHeight + 'px');
tinyMCE.DOM.setStyle(tinyMCE.DOM.get("elm1" + '_ifr'), 'width', resizeWidth + 'px');
The following comes in from this other SO answer I posted:
None of the above were working for me in TinyMCE v4, so my solution was to calculate the height based on the toolbars/menu bar/status bar, and then set the height of the editor, taking those heights into consideration.
function resizeEditor(myHeight) {
window.console.log('resizeEditor');
myEditor = getEditor();
if (myEditor) {
try {
if (!myHeight) {
var targetHeight = window.innerHeight; // Change this to the height of your wrapper element
var mce_bars_height = 0;
$('.mce-toolbar, .mce-statusbar, .mce-menubar').each(function(){
mce_bars_height += $(this).height();
});
window.console.log('mce bars height total: '+mce_bars_height);
myHeight = targetHeight - mce_bars_height - 8; // the extra 8 is for margin added between the toolbars
}
window.console.log('resizeEditor: ', myHeight);
myEditor.theme.resizeTo('100%', myHeight); // sets the dimensions of the editable area
}
catch (err) {
}
}
}
In my case, I wanted the editor window to match the width and height of the actual window, since the editor would come up in a popup. To detect changes and resize, I set this to a callback:
window.onresize = function() {
resizeEditor();
}
It's a bit late but for Googler like me, check the autoresize plugin
tinymce.init({
plugins: "autoresize"
});
Options
autoresize_min_height : Min height value of the editor when it auto resizes.
autoresize_max_height : Max height value of the editor when it auto resizes.
I'm using tinymce 4.8.3.
I display the editor in a resizable modal dialog box.
I solved this using flexbox, shown here in SASS/SCSS:
// TinyMCE editor is inside a container something like this
.html-container {
height: 100%;
overflow: hidden;
}
.mce-tinymce {
// This prevents the bottom border being clipped
// May work with just 100%, I may have interference with other styles
height: calc(100% - 2px);
& > .mce-container-body {
height: 100%;
display: flex;
flex-direction: column;
& > .mce-edit-area {
flex: 1;
// This somehow prevents minimum height of iframe in Chrome
// If we resize too small the iframe stops shrinking.
height: 1px;
}
}
}
When the editor is initialized we have to tell it to put 100% height on the IFRAME. In my case I also have to subtract 2px else the right border is clipped off:
tinymce.init({
...
height: "100%",
width: "calc(100% - 2px)"
});
What ManseUK stated is almost correct.
The correct solution is:
$('#compose_ifr').height(200);
or in your case
$('#composeMailContent_ifr').height(200);
Update: maybe this is more what you are looking for:
// resizes editoriframe
resizeIframe: function(frameid) {
var frameid = frameid ? frameid : this.editor.id+'_ifr';
var currentfr=document.getElementById(frameid);
if (currentfr && !window.opera){
currentfr.style.display="block";
if (currentfr.contentDocument && currentfr.contentDocument.body.offsetHeight) { //ns6 syntax
currentfr.height = 200 + 26;
}
else if (currentfr.Document && currentfr.Document.body.scrollHeight) { //ie5+ syntax
currentfr.height = 200;
}
styles = currentfr.getAttribute('style').split(';');
for (var i=0; i<styles.length; i++) {
if ( styles[i].search('height:') ==1 ){
styles.splice(i,1);
break;
}
};
currentfr.setAttribute('style', styles.join(';'));
}
},
In case someone finds this and also wants to change the height of the source code editor plugin.
You need to edit the following file:
\tiny_mce\plugins\code\plugin.min.js
Look out for the attribute called minHeigh and adjust it to your needs. The height you define there is not the height of the entire box, but it is not the height of the textarea either. It is something inbetween.
You can set it according to your height
tinymce.init({
height: "500px"
});
I test this solution on version 5 of TinyMCE.
For change the height of TinyMCE after page loaded, all you need is: your element id
$(function(){
var selectedElement = tinymce.get('Element id without sharp(#)');
selectedElement.settings.height = 700;
selectedElement.settings.max_height = 400;
selectedElement.settings.min_height = 1000;
});
Also you can use autoresize plugin for get better experience:
tinymce.init({
plugins: 'wordcount autoresize',
})
$(window).load(function () {
$('#YourID_ifr').css('height', '550px');
});

Categories

Resources