Creating Vertivcally/Horizontally Split-Div in Javascript - javascript

For a while now, I have been trying to create dynamic-resizable-split-panes in javascript (something like vs code).
I successfully created the dynamic split panes. It creates the split panes by checking the parent element and its children and decides whether to create a split pane vertically or horizontally.
What I mean is, if the parent already has vertically split panes and the new pane that has to be created is also vertical, it simply created the structure and appends it in the parent. Otherwise, it will wrap the active pane with a new parent and split the pane horizontally. (Same goes for the other way around).
The problem I am facing is, how can I make them resizable like vs code. What I want is, on mouse drag the panes should be resized.
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic Split Panes</title>
<script src="jquery.js"></script>
<link rel="stylesheet" href="main.css">
</head>
<body>
<div id="container">
<div class="ep">
<div class="ec" id="ec"></div>
</div>
</div>
<script src="main.js"></script>
</body>
</html>
CSS:
html,
body{
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#container{
display: flex;
width: 100%;
height: 100%;
background-color: black;
}
.ep{
display: flex;
flex: 1;
}
.ec{
flex: auto;
background-color: white;
}
.gutter-ver{
height: 10px;
width: 100%;
background-color: red;
}
.gutter-hor{
height: 100%;
width: 10px;
background-color: red;
}
.ui-resizable-ghost {
border: 1px dotted gray;
background-color: rgba(128, 128, 128, 0.544);
}
JavaScript:
var ec_active = document.querySelector(".ec");
var ec = null;
function createEC(dir){
if(dir == "ver"){
if(ec_active.parentElement.style.flexDirection == "row"){
// `element` is the element you want to wrap
var parent = ec_active.parentNode;
var wrapper = document.createElement('div');
wrapper.classList.add("ep");
wrapper.style.flexDirection = "column";
// set the wrapper as child (instead of the element)
parent.replaceChild(wrapper, ec_active);
// set element as child of wrapper
wrapper.appendChild(ec_active);
ec = document.createElement("div");
ec.classList.add("ec");
var gv = document.createElement("div");
gv.classList.add("gutter-ver");
ec_active.parentElement.appendChild(gv);
ec_active.parentElement.appendChild(ec);
}else if(ec_active.parentElement.style.flexDirection == "column"){
ec = document.createElement("div");
ec.classList.add("ec");
var gv = document.createElement("div");
gv.classList.add("gutter-ver");
ec_active.parentElement.appendChild(gv);
ec_active.parentElement.appendChild(ec);
}else{
ec = document.createElement("div");
ec.classList.add("ec");
var gv = document.createElement("div");
gv.classList.add("gutter-ver");
ec_active.parentElement.style.flexDirection = "column";
ec_active.parentElement.appendChild(gv);
ec_active.parentElement.appendChild(ec);
}
}else{
if(ec_active.parentElement.style.flexDirection == "column"){
// `element` is the element you want to wrap
var parent = ec_active.parentNode;
var wrapper = document.createElement('div');
wrapper.classList.add("ep");
wrapper.style.flexDirection = "row";
// set the wrapper as child (instead of the element)
parent.replaceChild(wrapper, ec_active);
// set element as child of wrapper
wrapper.appendChild(ec_active);
ec = document.createElement("div");
ec.classList.add("ec");
var gv = document.createElement("div");
gv.classList.add("gutter-hor");
ec_active.parentElement.appendChild(gv);
ec_active.parentElement.appendChild(ec);
}else if(ec_active.parentElement.style.flexDirection == "row"){
ec = document.createElement("div");
ec.classList.add("ec");
var gv = document.createElement("div");
gv.classList.add("gutter-hor");
ec_active.parentElement.appendChild(gv);
ec_active.parentElement.appendChild(ec);
// resizeEC(ec_active, ec);
}else{
ec = document.createElement("div");
ec.classList.add("ec");
var gv = document.createElement("div");
gv.classList.add("gutter-hor");
ec_active.parentElement.style.flexDirection = "row";
ec_active.parentElement.appendChild(gv);
ec_active.parentElement.appendChild(ec);
// resizeEC(ec_active, ec);
}
}
}
document.addEventListener("click", (e)=>{
ec_active = e.target;
});
function refreshGutters(){
var gutters_hor = document.querySelectorAll(".gutter-hor");
gutters_hor.forEach((gutter_hor)=>{
gutter_hor.onmousedown = (e)=>{
var elm_prev_gutter = gutter_hor.previousElementSibling;
var elm_next_gutter = gutter_hor.nextElementSibling;
var elm_prev_gutter_width = $(elm_prev_gutter).width();
var elm_next_gutter_width = $(elm_next_gutter).width();
init_pos = gutter_hor.getBoundingClientRect().x;
init_mouse_pos = e.clientX;
gutter_hor.onmousemove = (e)=>{
if(e.clientX > init_mouse_pos){
gutter_hor.style.left = e.clientX;
}
};
};
});
}
I tried using the split.js library and also jquery-ui but wasn't able to achieve the desired result.

When mousedown on border you enter a certain “resizing” state, where you register mousemove handler on window to capture mouse dx/dy movement, also register on window a mouseup handler to cleanup.
Upon entering this state on mousedown, you capture parent.getBoundingClientRect() so you can calculate dx/dy movement relative to parent dimensions. I’ll use horizontal dx movement as example.
My method is to calc dx over parent width, to get % delta. I also use flex-grow number to layout sibling panes.
Say you have 3 panes, each would have flex-grow: 33.33 all added up to 100 roughly. And you have % delta, now you can distribute this delta among two adjacent sibling panes. E.g. for each 1% delta, one adds 1, the other minuses 1, that’s the idea.
This method is not pixel perfect, but precision is totally acceptable. I like it because it’s simpler considering other case like whole window resize. Of course if you want pixel perfection, you can calculate px width of each pane instead of flex-grow % change, but then you need to address window/parent resize, it’s your call, the basic idea is the same.
When you’re done, by handling mouseup, don’t forget to cleanup all these temporary state variables and event handlers.

Related

Getting javascript to select ALL elements, not just the first one

I have copied a script which does some cool stuff with a floating tooltip (sticks to cursor and flips depending on which quadrant of the viewport it is in, to keep it visible at all times etc).
Everything works fine for the first element found. But it doesn't work on the second element, or any elements after that either.
// I want to select ALL divs with the class 'z-tooltip' but it only seems to select the first one
var tooltip = this.document.getElementsByClassName("z-tooltip")[0];
window.onmousemove = function(ev) {
var leftPixelSpace = ev.clientX;
var rightPixelSpace = this.innerWidth - leftPixelSpace;
var topPixelSpace = ev.clientY;
var bottomPixelSpace = this.innerHeight - topPixelSpace;
var tooltipPosX = leftPixelSpace > rightPixelSpace ? leftPixelSpace - tooltip.offsetWidth : leftPixelSpace;
var tooltipPosY = topPixelSpace > bottomPixelSpace ? topPixelSpace - tooltip.offsetHeight : topPixelSpace;
// I want to apply these styles to ALL divs with the class 'z-tooltip', but again, it only seems to work on the first div...
tooltip.style.left = tooltipPosX+"px";
tooltip.style.top = tooltipPosY+"px";
};
.z-tooltip {
width: 150px;
height: 100px;
border: 1px solid black;
background-color : lightblue;
text-align: center;
position: absolute
}
<div class="z-tooltip">floating tooltip</div>
<div class="z-tooltip">floating tooltip</div>
<div class="z-tooltip">floating tooltip</div>
I did some reading and I have seen that getElementsByClassName("z-tooltip")[0] returns them as an HTML collection, which is not what I want.
Instead I should use querySelectorAll('.z-tooltip) which would select all elements with the class z-tooltip. However when I use this none of the script works any more :(
Furthermore, I want to be able to apply a style (see end of snippet) to ALL elements with the class z-tooltip. I know that to do this I will need to 'loop through' all of them and apply the style. Here's what I've tried so far but it doesn't work:
for (var i=tooltip.length; i--;) {
tooltip.style.left = tooltipPosX+"px";
tooltip.style.top = tooltipPosY+"px";
}
Can anyone point out what I'm doing wrong here?
// I want to select ALL divs with the class 'z-tooltip' but it only seems to select the first one
var tooltip = this.document.getElementsByClassName("z-tooltip");
window.onmousemove = function(ev) {
var leftPixelSpace = ev.clientX;
var rightPixelSpace = this.innerWidth - leftPixelSpace;
var topPixelSpace = ev.clientY;
var bottomPixelSpace = this.innerHeight - topPixelSpace;
// I want to apply these styles to ALL divs with the class 'z-tooltip', but again, it only seems to work on the first div...
[...tooltip].forEach(tooltip => {
let tooltipPosX = leftPixelSpace > rightPixelSpace ? leftPixelSpace - tooltip.offsetWidth : leftPixelSpace;
let tooltipPosY = topPixelSpace > bottomPixelSpace ? topPixelSpace - tooltip.offsetHeight : topPixelSpace;
tooltip.style.left = tooltipPosX+"px";
tooltip.style.top = tooltipPosY+"px";
});
};
.z-tooltip {
width: 150px;
height: 100px;
border: 1px solid black;
background-color : lightblue;
text-align: center;
position: absolute
}
<div class="z-tooltip">floating tooltip</div>
<div class="z-tooltip">floating tooltip</div>
<div class="z-tooltip">floating tooltip</div>
Took the [0] off to get all the tooltips
Change the logic to [...tooltip].forEach to convert the result to an array that could then be looped over

How to create a new element on cursor DOM

How would I be able to create a new element and have it placed right where the mouse/cursor is located?
I have some example code below:
<div id = "adivthing></div>
<script>
var newthing = document.createElement("input");
document.getElementById("adivthing").appendChild(newthing);
</script>
If you use either the position: fixed or position: absolute style properties on the newthing element, you can then use the left and top properties to move the element around the box.
If you get the mouse coordinates from your triggering event (e.g. click), you can add the appropriate left and top to your element.
Example below:
function createInput(event){
var newthing = document.createElement("input");
document.getElementById("adivthing").appendChild(newthing); // Your existing code
// get the coordinates of the mouse
var x = event.clientX; // get the horizontal coordinate
var y = event.clientY; // get the vertical coordinate
// position newthing using the coordinates
newthing.style.position = "fixed"; // fixes el relative to page. Could use absolute.
newthing.style.left = x + "px";
newthing.style.top = y + "px";
}
/* Optional - Making new things more obvious in the pen */
input{
height: 10px;
width: 50px;
background: red;
}
#adivthing{
height: 600px;
width: 600px;
background: blue;
}
<!-- Onclick event added to your existing markup -->
<div id="adivthing" onclick="createInput(event)"></div>

Create a resizable table grid, resizing using mouse, inside a content editable using vanilla javascript

I want to create a regular resizable table grid inside a content editable. The table should be resizable using the mouse, preferebly resizable in both the x-axis and y-axis but this question is more directed towards resizing in the x-axis. I have the following code
index.html
<html>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" type="text/css" href="styles.css">
<script src="javascript.js"></script>
<!--- <script src="jquery-3.4.1.js"></script> -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<title>Resizable table hobby testing</title>
</head>
<body>
<div contenteditable="true" style="width: 500px; height: 500px;"></div>
<button onclick="insert_table(5, 5);>insert table</button>
</body>
</html>
styles.css
table {
border-collapse: collapse;
table-layout: fixed;
}
table, tr, td {
border: 1px solid #AAAAAA;
}
tr, td {
width: 60px;
height: 16px;
overflow: hidden;
padding: 3px;
}
javascript.js
var th_element;
var startOffset;
function insert_table(rows, cols) { // inserts a table into the div
// gets the selected text inside the content editable div and deletes it
var selection = window.getSelection();
var range = selection.getRangeAt(0);
range.deleteContents();
// Create a table element
var ins_table = document.createElement("table");
// set unique id, useful for getting the element later
ins_table.setAttribute("id", "table"); // maybe change to ins_table.id = "table";? (note to self)
// Create the format of rows and columns for the table
for(var r = 0; r < rows; r++) {
ins_table.innerHTML += "<tr></tr>";
ins_table.childNodes[r].innerHTML = "<tr>" + '<td style="min-width: 30px;"> </td>'.repeat(cols) + "</tr>";
}
// general css styling
ins_table.style.width = "200px";
ins_table.style.float = "left";
// insert table into cursor position
range.insertNode(ins_table);
// get the table by finding its id
var the_table = document.getElementById('table');
// get the first row, in order to add a div there that acts as a resize bar grab thingy
var row = table.getElementsByTagName('tr')[0];
var cols = row.childNodes; // the cols is all the child nodes of the first row
for(var i = 0; i < cols.length; i++) { // iterate through all cols and insert the resize grab thingt
var div = create_div(the_table.offsetHeight); // create a div for the selected cell with the height of the table
cols[i].appendChild(div); // append that div to the cell
cols[i].style.position = "relative"; // change position
cols[i].style.overflow = "visible"; // needs to be set as visible to actually be used on the other rows. Without this it only works with the first row
add_listener(div, cols[i]); // add a listener
}
}
function create_div(height){ // create the div the user grabs onto
var div = document.createElement('div'); // create the resize div to grab onto later
// general styling
div.style.top = 0;
div.style.right = 0;
div.style.width = '5px';
div.style.position = 'absolute';
div.style.cursor = 'col-resize';
div.style.userSelect = 'none';
div.style.height = height + 'px';
div.className = 'columnSelector';
div.style.border = "none";
return div; // return the created div, to be inserted into the cell above
}
function add_listener(div, col) {
div.addEventListener('mousedown', function(event) {
th_element = col;
startOffset = col.offsetWidth - event.pageX;
});
document.addEventListener('mousemove', function(event) {
if(th_element) {
th_element.style.width = startOffset + event.pageX + 'px';
}
});
document.addEventListener('mouseup', function() {
th_element = undefined;
});
}
I have tried the code from brainbell but it does not span over multiple cells, nor does it work for me without tweaking the variable calls (e.g. brainbell uses parentElement instead of parentNode, which breaks for me). Dragging a div (the thing you grab onto to resize the table) too far right or left causes werid behaviour with other cells getting resized whilst not being ment to. Among other things.
The code I have taken inspiration from is Resizing tables in vanilla JavaScript without affecting positioning but as you can see it breaks in multiple ways (1) when for example I drag the second div grab resize element back and forth, the first div shakes. (2) resizing using the third or fourth div causes weird resizes. (3) I am inside a content editable, making the text inside the table selected, I think it is because the resizing of the tables does not happen fast enough, thus the mouse is "dragged over" the text in the next, previous, etc cell despite that not being mean to happen (disabling highlighting in a contenteditable during resizing is not possible, I tried doing that via CSS). Among other things.
I am doing a minimal, "classic", vanilla javascript style coding hobby here thus I am not going to use jQuery. Does anybody know how to either (1) fix my code a bit or (2) how to create a resizble table grid (using vanilla JS and the mouse to resize) with editable content inside the cells please post a comment or an answer! Thanks!
Here is also a codepen to SOMEWHAT illustrate my issue, because for some reason the codepen is less broken than mine that I have running on a httpd server, not sure why mine is more broken (or slow?)

Pop up div with absolute position next to click

I am trying to make a right click lead to a div pop up. However, the click will be inside a cesium window, which only lets a div show if it has absolute position. Is it possible to edit the pop up position of a div with absolute position?
Right now, my code looks like this:
var editHandler = newCesium.ScreenSpaceEventHandler(scene.canvas);
editHandlersetInputAction(function(e){
var shapeEditMenu = document.getElementById("shapeEditMenu");
shapeEditMenu.style.display = "block";
shapeEditMenu.style.left = e.clientX;
shapeEditMenu.style.top = e.clientY;
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
#shapeEditMenu {
position: absolute;
display: none:
top: 80px;
height: 50px;
width: 80px;
}
<div id="shapeEditMenu">
Line thickness:
<input type="number"> pt
</div>
It doesn't work here because there isn't cesium, but what happens in my actual code is that the div pops up on right click, but always is at the same position, not at the place where you right click.
As mentioned in the comments, there is some API confusion going on here. clientX is part of browser-native API, not Cesium's API. Since you're using Cesium's event handling system, you get events from that API. In this case, you would use e.position.x and follow that up with a + 'px' to indicate CSS-pixel units for the stylesheet.
Also you have a few typos here:
newCesium. should be new Cesium.
editHandlersetInputAction should be editHandler.setInputAction
display: none: should be display: none;
Here's a live Sandcastle demo that shows a right-click "Testing" menu show up at the mouse location.
var viewer = new Cesium.Viewer('cesiumContainer');
var editHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
editHandler.setInputAction(function(e) {
var shapeEditMenu = document.getElementById("toolbar");
shapeEditMenu.textContent = 'Testing';
shapeEditMenu.style.display = "block";
shapeEditMenu.style.left = e.position.x + 'px';
shapeEditMenu.style.top = e.position.y + 'px';
shapeEditMenu.style.background = 'rgba(42, 42, 42, 0.8)';
shapeEditMenu.style.border = '1px solid #888';
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
editHandler.setInputAction(function(e) {
var shapeEditMenu = document.getElementById("toolbar");
shapeEditMenu.style.display = "none";
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);

Dynamically alter contents of a div and append to another div

So basically I have this div in body
<body>
<div id="main_content"></div>
</body>
Now I am downloading some data from the internet (a set of boolean data) and I have this div template. Let's say the data is (true, false, true). Then for each data I want to alter the template div. For example: first one is true so inside the template div I will change the sub1 div's height to 40 px; if it's false, I'd change sub2 div's height to 40 px; and then I'd append this modified template div to main_content div
Template div:
.child{
width:300px;
height:auto;
}
.sub1{
width:300px;
height:20px;
background-color:#0FF;
}
.sub2{
width:300px;
height:20px;
background-color:#F0F;
}
<div class="child">
<div class="sub1"></div>
<div class="sub2"></div>
</div>
After all this this should be the final output of main_content div
What would be the easiest way of doing this using HTML/CSS/JS.
Thanks
Short answer: Here is a codepen
Long answer:
I would use js to dynamically generate your template div:
function makeTemplateDiv() {
var child = document.createElement('div');
child.className = "child"
var sub1 = document.createElement('div');
sub1.className = "sub1"
var sub2 = document.createElement('div');
sub2.className = "sub2"
child.appendChild(sub1);
child.appendChild(sub2);
return child;
}
Then make a css class for a taller 40 px
.taller {
height: 40px;
}
Then use js to to alter your template based on a passed in value
function alterTemplateDiv(value) {
var template = makeTemplateDiv();
if(value) {
template.getElementsByClassName("sub1")[0].className += " taller";
} else {
template.getElementsByClassName("sub2")[0].className += " taller";
}
return template;
}
Then use js to pass in your array of values, make the divs, and append them
function appendDivs(arrayOfValues) {
var mainDiv = document.getElementById("main_content");
for(var i = 0; i < arrayOfValues.length; i++) {
mainDiv.appendChild(alterTemplateDiv(arrayOfValues[i]));
}
}
This kind of question begs for a million different types of answers, but I think this generally keeps with most best practices for front end coding without the use of a framework:
// Self-invoking function for scoping
// and to protect important global variables from other script changes
// (The variable references can be overwritten)
(function (window, document) {
var templateText,
generatedEl,
topEl,
bitArray;
// Data
bitArray = [true, false, true];
// Get template text
templateText = document.getElementById('my-template').text.trim();
// Loop through your T / F array
for (var i = 0, l = bitArray.length; i < l; i++) {
// Create a DIV and generate HTML within it
generatedEl = document.createElement('div');
generatedEl.innerHTML = templateText;
// Modify the new HTML content
topEl = generatedEl.getElementsByClassName('child')[0];
topEl.className += bitArray[i] ? ' typeA' : ' typeB' ;
// Insert generated HTML (assumes only one top-level element exists)
document.getElementById('my-container').appendChild(generatedEl.childNodes[0]);
}
})(window, document);
.child {
width: 300px;
height: auto;
}
/* For true */
.child.typeA > .sub1 {
width: 300px;
height: 40px;
background-color: #0FF;
}
.child.typeA > .sub2 {
width: 300px;
height: 20px;
background-color: #F0F;
}
/* For false */
.child.typeB > .sub1 {
width: 300px;
height: 20px;
background-color: #0FF;
}
.child.typeB > .sub2 {
width: 300px;
height: 40px;
background-color: #F0F;
}
<!-- Container -->
<div id="my-container">
<!-- HTML Template -->
<script id="my-template" type="text/template">
<div class="child">
<div class="sub1"></div>
<div class="sub2"></div>
</div>
</script>
</div>
Note that the HTML content, JavaScript code and CSS are all kept very separated. This is based on the concepts of "Separation of Concerns" and "Unobtrusive JavaScript". I invite you to read up on them if you haven't already. Also, front end templating can be used for dynamic content like I did here, but I would recommend doing templating on the back end when you can. It works better for SEO purposes.
jQuery makes it easier to manipulate the DOM, so here is another solution for your problem:
var data = [true, false, true];
for (i=0; i<data.length; i++) {
var height1;
var height2;
if (data[i] == true) {
height1 = 40;
height2 = 20;
}
else {
height1 = 20;
height2 = 40;
}
var div1 = document.createElement("div");
$(div1).toggleClass("sub1")
.height(height1)
.appendTo("#main_content");
var div2 = document.createElement("div");
$(div2).toggleClass("sub2")
.height(height2)
.appendTo("#main_content");
}

Categories

Resources