How to keep selection but also press button? - javascript

I have this text. On select, a div will appear that has multiple colors. On click of the blue div or the first color div, the text should highlight the text that is currently highlighted. This only works if I remove the conditional if statement for the #blue_box. I think the click element is removing the selection of the text before the program can retrieve the text. How can I keep the click element, but also track the selection?
$("#actual_verse").mouseup(function() {
var text = "";
if (window.getSelection) {
text = window.getSelection().toString();
} else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().text;
}
if (/\S/.test(text)) {
$("#blue_box").click(function() {
var range = document.getSelection().getRangeAt(0);
var contents = range.extractContents();
var node = document.createElement('span');
node.style.backgroundColor = "yellow";
node.appendChild(contents);
range.insertNode(node);
});
// Tool Tip
var ele = document.getElementById('tooltip');
var sel = window.getSelection();
var rel1 = document.createRange();
rel1.selectNode(document.getElementById('cal1'));
var rel2 = document.createRange();
rel2.selectNode(document.getElementById('cal2'));
window.addEventListener('mouseup', function() {
if (!sel.isCollapsed) {
var r = sel.getRangeAt(0).getBoundingClientRect();
var rb1 = rel1.getBoundingClientRect();
var rb2 = rel2.getBoundingClientRect();
//this will place ele below the selection
ele.style.top = (r.bottom - rb2.top) * 100 / (rb1.top - rb2.top) + 'px';
//this will align the right edges together
ele.style.left = (r.left - rb2.left) * 100 / (rb1.left - rb2.left) + 'px';
//code to set content
ele.style.display = 'block';
}
});
// End of Tool Tip
}
});
/* Tool Kit */
#tooltip {
position: absolute;
display: none;
border: grey solid 1px;
background: #373737;
padding: 5px;
border-radius: 3px;
}
#cal1 {
position: absolute;
height: 0px;
width: 0px;
top: 100px;
left: 100px;
overflow: none;
z-index: -100;
}
#cal2 {
position: absolute;
height: 0px;
width: 0px;
top: 0;
left: 0;
overflow: none;
z-index: -100;
}
.boxes {
width: 15px;
height: 15px;
cursor: pointer;
display: inline-block;
margin-right: 2px;
position: relative;
top: 3px;
}
#blue_box {
background: #AAF6FF;
}
#green_box {
background: #D6FFAA;
}
#orange_box {
background: #FFBF98;
}
#purple_box {
background: #D7D5FC;
}
#red_box {
background: #FF9B9F;
}
#yellow_box {
background: #FFF8AA;
}
.highlight {
background: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span id='actual_verse' class='context'> Hello There! </span>
<div id='cal1'> </div>
<div id='cal2'> </div>
<div id='tooltip'>
<div id='blue_box' class='boxes' title='Blue'></div>
<div id='green_box' class='boxes' title='Green'></div>
<div id='orange_box' class='boxes' title='Orange'></div>
<div id='purple_box' class='boxes' title='Purple'></div>
<div id='red_box' class='boxes' title='Red'></div>
</div>
<br>
<br>

I believe the following changes accomplish what you desire.
Changes:
Moved $("#blue_box").click(function(){} out of your #actual_verse mouseup event handler. You were adding yet another click handler for the blue box every time there was a mouseup event on #actual_verse. It should only be added once. Alternately, you can define the function in the global scope and name it. Then you can add it and remove it multiple times.
Add a mousedown handler on the #tooltip which just calls event.preventDefalut(). The mousedown event is the one that clears the selection. This happens before the click event, so by the time you were getting to your click handler there was no selection.
Add selection.removeAllRanges(); to the end of the blue_box click handler to clear the selection to show the highlight. I'm assuming this is desirable. For me as a user, this is what I expect to happen.
Create hideTooltip() and add it to the end of the blue_box event handler. As a user, I expect the tooltip to disappear once I have made that click.
Remove the window.addEventListener('mouseup', but leave the code which it was executing. I'm not sure why that code was a window mouseup handler. Where it was being added as an event listener resulted in yet another copy of the handler added every time the #actual_verse mouseup event handler executed, just like was happening with the blue_box click handler.
$("#tooltip").mousedown(function(event) {
event.preventDefault();
});
//Only add the listener once, not another listener each mouseup
$("#blue_box").click(function() {
var selection = document.getSelection();
var range = selection.getRangeAt(0);
var contents = range.extractContents();
var node = document.createElement('span');
node.style.backgroundColor = "yellow";
node.appendChild(contents);
range.insertNode(node);
selection.removeAllRanges(); //Clear the selection, showing highlight
hideTooltip();
});
function hideTooltip() {
document.getElementById('tooltip').style.display = ''; //hide the tooltip
}
$("#actual_verse").mouseup(function() {
var text = "";
if (window.getSelection) {
text = window.getSelection().toString();
} else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().text;
}
if (/\S/.test(text)) {
// Tool Tip
var ele = document.getElementById('tooltip');
var sel = window.getSelection();
var rel1 = document.createRange();
rel1.selectNode(document.getElementById('cal1'));
var rel2 = document.createRange();
rel2.selectNode(document.getElementById('cal2'));
if (!sel.isCollapsed) {
var r = sel.getRangeAt(0).getBoundingClientRect();
var rb1 = rel1.getBoundingClientRect();
var rb2 = rel2.getBoundingClientRect();
//this will place ele below the selection
ele.style.top = (r.bottom - rb2.top) * 100 / (rb1.top - rb2.top) + 'px';
//this will align the right edges together
ele.style.left = (r.left - rb2.left) * 100 / (rb1.left - rb2.left) + 'px';
//code to set content
ele.style.display = 'block';
}
// End of Tool Tip
}
});
/* Tool Kit */
#tooltip {
position: absolute;
display: none;
border: grey solid 1px;
background: #373737;
padding: 5px;
border-radius: 3px;
}
#cal1 {
position: absolute;
height: 0px;
width: 0px;
top: 100px;
left: 100px;
overflow: none;
z-index: -100;
}
#cal2 {
position: absolute;
height: 0px;
width: 0px;
top: 0;
left: 0;
overflow: none;
z-index: -100;
}
.boxes {
width: 15px;
height: 15px;
cursor: pointer;
display: inline-block;
margin-right: 2px;
position: relative;
top: 3px;
}
#blue_box {
background: #AAF6FF;
}
#green_box {
background: #D6FFAA;
}
#orange_box {
background: #FFBF98;
}
#purple_box {
background: #D7D5FC;
}
#red_box {
background: #FF9B9F;
}
#yellow_box {
background: #FFF8AA;
}
.highlight {
background: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span id='actual_verse' class='context'> Hello There! </span>
<div id='cal1'> </div>
<div id='cal2'> </div>
<div id='tooltip'>
<div id='blue_box' class='boxes' title='Blue'></div>
<div id='green_box' class='boxes' title='Green'></div>
<div id='orange_box' class='boxes' title='Orange'></div>
<div id='purple_box' class='boxes' title='Purple'></div>
<div id='red_box' class='boxes' title='Red'></div>
</div>
<br>
<br>

Related

Check if DOM elements are present inside a DIV then run functions assigned to those elements in order

i'm trying to develop a game using html, css and js. At the moment I'm focusing on manipulating DOM elements without using the canvas tag. My idea is to create a pseudo graphical programming language, similar to the Blockly environment. So far I have inserted 3 clickable elements inside #toolbox that create their copies in #workspace.
Now, I am trying to assign functions to the elements present in #workspace, which once pressed the Run button are executed in order of appearance, so as to create a queue of commands that is able to move the pink square inside #output_section.
Therefore I cannot understand how to write the function that is able to verify the presence of the elements and then be able to perform the different functions assigned to these elements.
Any ideas? :D
I'm using Jquery 3.3.1
function addRed() {
var redWorkspace = document.createElement("DIV");
redWorkspace.className = "remove-block block red";
document.getElementById("workspace").appendChild(redWorkspace);
};
function addBlue() {
var blueWorkspace = document.createElement("DIV");
blueWorkspace.className = "remove-block block blue";
document.getElementById("workspace").appendChild(blueWorkspace);
};
function addGreen() {
var greenWorkspace = document.createElement("DIV");
greenWorkspace.className = "remove-block block green";
document.getElementById("workspace").appendChild(greenWorkspace);
};
$("#clear_workspace").click(function () {
$("#workspace").empty();
});
$(document).on("click", ".remove-block", function () {
$(this).closest("div").remove();
});
html,
body {
margin: 0;
padding: 0;
}
#workspace {
display: flex;
height: 100px;
padding: 10px;
background: black;
}
#toolbox {
display: flex;
padding: 10px;
width: 300px;
}
#output_section {
height: 500px;
width: 500px;
border: solid black;
margin: 10px;
position: relative;
}
#moving_square {
position: absolute;
bottom: 0;
right: 0;
width: 100px;
height: 100px;
background: pink;
}
.block {
height: 100px;
width: 100px;
}
.red {
background: red;
}
.blue {
background: cyan;
}
.green {
background: green;
}
.grey {
background: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>
<body>
<div id="workspace"></div>
<div id="workspace-menu">
<button id="run_workspace">Run</button>
<button id="clear_workspace">Clear</button>
</div>
<div id="toolbox" class="grey">
<div onclick="addRed()" class="block red">Left</div>
<div onclick="addBlue()" class="block blue">Up</div>
<div onclick="addGreen()" class="block green">Right</div>
</div>
<div id="output_section">
<div id="moving_square"></div>
</div>
</body>
</html>
Completely untested but run button does something along the lines of:
$("#run_workspace").click(function() {
$("#workspace .block").each(function(elem) {
if (elem.hasClass("red")) {
moveObjectLeft();
} else if (elem.hasClass("green")) {
moveObjectRight();
} else if (elem.hasClass("blue")) {
moveObjectUp();
}
});
});
Commonly, it's a good idea to store all required information in arrays and objects, and use HTML only to display your data.
Also, if you are already using jQuery - use it for all 100%)
Made some improvements:
let mobs = {
pinky: {
node: $('#moving_square'),
coors: { top: 400, left: 400 },
step: 30,
moveQueue: [],
// moveTimeout ???
},
}; // storing here all created objects, that must move.
/* Each [moveQueue] array will store the chain of moves, like ["up", "up", "left"]
You can take each "key-word" of move, and get required function buy that key,
from the 'move' object */
let move = { // Think about how to simlify this object and functions. It's possible!)
left: function (obj) {
let left = obj.coors.left = (obj.coors.left - obj.step);
obj.node.css('left', left + 'px');
},
up: function (obj) {
let top = obj.coors.top = (obj.coors.top - obj.step);
obj.node.css('top', top + 'px');
},
right: function (obj) {
let left = obj.coors.left = (obj.coors.left + obj.step);
obj.node.css('left', left + 'px');
}
};
let stepTimeout = 1000;
let running = false;
let timeouts = {}; // store all running timeouts here,
// and clear everything with for( key in obj ) loop, if required
$('#toolbox .block').on('click', function () {
let color = $(this).attr('data-color');
let workBlock = '<div class="remove-block block ' + color + '"></div>';
$('#workspace').append(workBlock);
mobs.pinky.moveQueue.push( $(this).text().toLowerCase() ); // .attr('data-direction');
// instead of pinky - any other currently selected object
// $(this).text().toLowerCase() — must be "left", "up", "right"
});
$('#run_workspace').on('click', function () {
running = true;
runCode();
function runCode() {
for (let obj in mobs) { // mobile objects may be multiple
// Inside the loop, obj == mobs each key name. Here it's == "pinky"
let i = 0;
let pinky = mobs[obj];
localRun();
function localRun() {
let direction = pinky.moveQueue[i]; // getting direction key by array index.
move[direction](pinky); // calling the required function from storage.
if (pinky.moveQueue[++i] && running ) {
// self-calling again, if moveQueue has next element.
// At the same time increasing i by +1 ( ++i )
timeouts[obj] = setTimeout(localRun, stepTimeout);
}
}
}
}
});
$("#clear_workspace").click(function () {
$("#workspace").empty();
});
$('#workspace').on("click", ".remove-block", function () {
$(this).closest("div").remove();
});
html,
body {
margin: 0;
padding: 0;
}
#workspace {
display: flex;
height: 100px;
padding: 10px;
background: black;
}
#toolbox {
display: flex;
padding: 10px;
width: 300px;
}
#output_section {
height: 500px;
width: 500px;
border: solid black;
margin: 10px;
position: relative;
}
#moving_square {
position: absolute;
top: 400px;
left: 400px;
width: 100px;
height: 100px;
background: pink;
}
.block {
height: 100px;
width: 100px;
}
.red {
background: red;
}
.blue {
background: cyan;
}
.green {
background: green;
}
.grey {
background: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="workspace"></div>
<div id="workspace-menu">
<button id="run_workspace">Run</button>
<button id="clear_workspace">Clear</button>
</div>
<div id="toolbox" class="grey">
<div data-color="red" class="block red">Left</div>
<div data-color="blue" class="block blue">Up</div>
<div data-color="green" class="block green">Right</div>
</div>
<div id="output_section">
<div id="moving_square"></div>
</div>
But... jQuery was used only for clicks... Translation to JS:
let mobs = {
pinky: {
node: document.getElementById('moving_square'),
coors: { top: 400, left: 400 },
step: 30,
moveQueue: [],
},
};
let move = {
left: function (obj) {
let left = obj.coors.left = (obj.coors.left - obj.step);
obj.node.style.left = left + 'px';
},
up: function (obj) {
let top = obj.coors.top = (obj.coors.top - obj.step);
obj.node.style.top = top + 'px';
},
right: function (obj) {
let left = obj.coors.left = (obj.coors.left + obj.step);
obj.node.style.left = left + 'px';
}
};
let stepTimeout = 1000;
let running = false;
let timeouts = {};
let blocks = document.querySelectorAll('#toolbox .block');
let workSpace = document.getElementById('workspace');
blocks.forEach(function(block){
block.addEventListener('click', function(){
let color = this.dataset.color;
let workBlock = '<div class="remove-block block ' + color + '"></div>';
workSpace.insertAdjacentHTML('beforeend', workBlock);
mobs.pinky.moveQueue.push( this.textContent.toLowerCase() );
});
});
document.getElementById('run_workspace').addEventListener('click', function () {
running = true;
runCode();
function runCode() {
for (let obj in mobs) { // mobile objects may be multiple
// Inside the loop, obj == mobs each key name. Here it's == "pinky"
let i = 0;
let pinky = mobs[obj];
localRun();
function localRun() {
let direction = pinky.moveQueue[i]; // getting direction key by array index.
move[direction](pinky); // calling the required function from storage.
if (pinky.moveQueue[++i] && running ) {
// self-calling again, if moveQueue has next element.
// At the same time increasing i by +1 ( ++i )
timeouts[obj] = setTimeout(localRun, stepTimeout);
}
}
}
}
});
document.getElementById("clear_workspace").addEventListener('click', function () {
workSpace.textContent = "";
});
workSpace.addEventListener('click', function (e) {
if( e.target.classList.contains('remove-block') ){
e.target.remove();
}
});
html,
body {
margin: 0;
padding: 0;
}
#workspace {
display: flex;
height: 100px;
padding: 10px;
background: black;
}
#toolbox {
display: flex;
padding: 10px;
width: 300px;
}
#output_section {
height: 500px;
width: 500px;
border: solid black;
margin: 10px;
position: relative;
}
#moving_square {
position: absolute;
top: 400px;
left: 400px;
width: 100px;
height: 100px;
background: pink;
}
.block {
height: 100px;
width: 100px;
}
.red {
background: red;
}
.blue {
background: cyan;
}
.green {
background: green;
}
.grey {
background: #ccc;
}
<div id="workspace"></div>
<div id="workspace-menu">
<button id="run_workspace">Run</button>
<button id="clear_workspace">Clear</button>
</div>
<div id="toolbox" class="grey">
<div data-color="red" class="block red">Left</div>
<div data-color="blue" class="block blue">Up</div>
<div data-color="green" class="block green">Right</div>
</div>
<div id="output_section">
<div id="moving_square"></div>
</div>

Vertical dragBar for resizing two divs

I wanted a vertical dragBar for resizing two divs. I have created an example for the same but I am facing an issue.
Actual : As and when I resize the the upper div and move the slider down, the area of parent div increases and hence a scroll bar is given.
Expected: When Resizing, if the slider is moved down, it should only show the data contained in the upper div and when slider is moved up, it should show the content of lower div and should not increase the over all length of the parent div.
var handler = document.querySelector('.handler');
var wrapper = handler.closest('.wrapper');
var boxA = wrapper.querySelector('.box1');
var boxB = wrapper.querySelector('.box2');
var isHandlerDragging = false;
document.addEventListener('mousedown', function(e) {
// If mousedown event is fired from .handler, toggle flag to true
if (e.target === handler) {
isHandlerDragging = true;
}
});
document.addEventListener('mousemove', function(e) {
// Don't do anything if dragging flag is false
if (!isHandlerDragging) {
return false;
}
// Get offset
var containerOffsetTop= wrapper.offsetTop;
var containerOffsetBottom= wrapper.offsetBottom;
// Get x-coordinate of pointer relative to container
var pointerRelativeXpos = e.clientY - containerOffsetTop;
var pointerRelativeXpos2 = e.clientY - e.offsetTop + e.offsetHeight;
var boxAminWidth = 30;
boxA.style.height = (Math.max(boxAminWidth, pointerRelativeXpos - 2)) + 'px';
boxA.style.flexGrow = 0;
boxB.style.height = (Math.max(boxAminWidth, pointerRelativeXpos2 - 8)) + 'px';
boxB.style.flexGrow = 0;
});
document.addEventListener('mouseup', function(e) {
// Turn off dragging flag when user mouse is up
isHandlerDragging = false;
});
body {
margin: 40px;
}
.wrapper {
background-color: #fff;
color: #444;
/* Use flexbox */
}
.box1, .box2 {
background-color: #444;
color: #fff;
border-radius: 5px;
padding: 20px;
font-size: 150%;
margin-top:2%;
/* Use box-sizing so that element's outerwidth will match width property */
box-sizing: border-box;
/* Allow box to grow and shrink, and ensure they are all equally sized */
}
.handler {
width: 20px;
height:7px;
padding: 0;
cursor: ns-resize;
}
.handler::before {
content: '';
display: block;
width: 100px;
height: 100%;
background: red;
margin: 0 auto;
}
<div class="wrapper">
<div class="box1">A</div>
<div class="handler"></div>
<div class="box2">B</div>
</div>
Hope I was clear in explaining the issue I am facing in my project. Any help is appreciated.
It looks like your on the right track. You just need to make the wrapper a flexbox with the flex direction column and assign it a height. Also box 2 needs to have a flex of 1 so it can grow and shrink as needed. Finally I needed to remove the code that set the flex grow to 0 in the JavaScript. Here is the result.
var handler = document.querySelector('.handler');
var wrapper = handler.closest('.wrapper');
var boxA = wrapper.querySelector('.box1');
var boxB = wrapper.querySelector('.box2');
var isHandlerDragging = false;
document.addEventListener('mousedown', function(e) {
// If mousedown event is fired from .handler, toggle flag to true
if (e.target === handler) {
isHandlerDragging = true;
}
});
document.addEventListener('mousemove', function(e) {
// Don't do anything if dragging flag is false
if (!isHandlerDragging) {
return false;
}
e.preventDefault();
// Get offset
var containerOffsetTop= wrapper.offsetTop;
var containerOffsetBottom= wrapper.offsetBottom;
// Get x-coordinate of pointer relative to container
var pointerRelativeXpos = e.clientY - containerOffsetTop;
var pointerRelativeXpos2 = e.clientY - e.offsetTop + e.offsetHeight;
var boxAminWidth = 30;
boxA.style.height = (Math.max(boxAminWidth, pointerRelativeXpos - 2)) + 'px';
boxB.style.height = (Math.max(boxAminWidth, pointerRelativeXpos2 - 8)) + 'px';
});
document.addEventListener('mouseup', function(e) {
// Turn off dragging flag when user mouse is up
isHandlerDragging = false;
});
body {
margin: 40px;
}
.wrapper {
background-color: #fff;
color: #444;
/* Use flexbox */
display: flex;
flex-direction: column;
height: 200px;
}
.box1, .box2 {
background-color: #444;
color: #fff;
border-radius: 5px;
padding: 20px;
font-size: 150%;
margin-top:2%;
/* Use box-sizing so that element's outerwidth will match width property */
box-sizing: border-box;
/* Allow box to grow and shrink, and ensure they are all equally sized */
}
.box2 {
flex: 1;
}
.handler {
width: 20px;
height:7px;
padding: 0;
cursor: ns-resize;
}
.handler::before {
content: '';
display: block;
width: 100px;
height: 100%;
background: red;
margin: 0 auto;
}
<div class="wrapper">
<div class="box1">A</div>
<div class="handler"></div>
<div class="box2">B</div>
</div>

How to change class and text of one tag by clicking on another tag?

I don't know how to describe this without making it more complicated.
So look at the result of the code and click on the first link with "Show", then the second one and third one.
When the second link is clicked, first one closes but text remains "Hide" and i want it to change to "Show".
So, when clicking a link, detect if any other link has text "Hide" and change it to "Show".
And please no jQuery...
document.getElementsByClassName("show")[0].onclick = function() {
var x = document.getElementsByClassName("hide")[0];
var y = document.getElementsByClassName("show")[0];
if (x.classList.contains("visible")) {
x.classList.remove("visible");
y.textContent = "Show";
} else {
closeOther();
x.classList.add("visible");
y.textContent = "Hide";
}
};
document.getElementsByClassName("show")[1].onclick = function() {
var x = document.getElementsByClassName("hide")[1];
var y = document.getElementsByClassName("show")[1];
if (x.classList.contains("visible")) {
x.classList.remove("visible");
y.textContent = "Show";
} else {
closeOther();
x.classList.add("visible");
y.textContent = "Hide";
}
};
document.getElementsByClassName("show")[2].onclick = function() {
var x = document.getElementsByClassName("hide")[2];
var y = document.getElementsByClassName("show")[2];
if (x.classList.contains("visible")) {
x.classList.remove("visible");
y.textContent = "Show";
} else {
closeOther();
x.classList.add("visible");
y.textContent = "Hide";
}
};
function closeOther() {
var visible = document.querySelectorAll(".visible"),
i, l = visible.length;
for (i = 0; i < l; ++i) {
visible[i].classList.remove("visible");
}
}
.style {
background-color: yellow;
width: 200px;
height: 200px;
display: inline-block;
}
.hide {
background-color: red;
width: 50px;
height: 50px;
display: none;
position: relative;
top: 50px;
left: 50px;
}
.hide.visible {
display: block;
}
<div class="style">
Show
<div class="hide">
</div>
</div>
<div class="style">
Show
<div class="hide">
</div>
</div>
<div class="style">
Show
<div class="hide">
</div>
</div>
I tried to write a solution which didn't use any javascript at all and worked using CSS alone. I couldn't get it to work though - CSS can identify focus but it can't identify blur (ie. when focus has just been removed).
So here is a solution which uses javascript and the classList API, instead:
var divs = document.getElementsByTagName('div');
function toggleFocus() {
for (var i = 0; i < divs.length; i++) {
if (divs[i] === this) continue;
divs[i].classList.add('show');
divs[i].classList.remove('hide');
}
this.classList.toggle('show');
this.classList.toggle('hide');
}
for (let i = 0; i < divs.length; i++) {
divs[i].addEventListener('click', toggleFocus, false);
}
div {
display: inline-block;
position: relative;
width: 140px;
height: 140px;
background-color: rgb(255,255,0);
}
.show::before {
content: 'show';
}
.hide::before {
content: 'hide';
}
div::before {
color: rgb(0,0,255);
text-decoration: underline;
cursor: pointer;
}
.hide::after {
content: '';
position: absolute;
top: 40px;
left: 40px;
width: 50px;
height: 50px;
background-color: rgb(255,0,0);
}
<div class="show"></div>
<div class="show"></div>
<div class="show"></div>
Like this?
Just added following to closeOther():
visible = document.querySelectorAll(".show"),
i, l = visible.length;
for (i = 0; i < l; ++i) {
visible[i].textContent="Show";
}

Semi-fixed text in a scrolling container

I've got a bunch of horizontal boxes containing text. The boxes are all in a horizontally scrolling container:
// generate some random data
var model = {
leftEdge: ko.observable(0)
};
model.rows = populateArray(10 + randInt(20), randRow);
ko.applyBindings(model);
$(function() {
$('.slide').on('scroll', function() {
model.leftEdge(this.scrollLeft);
})
})
function randRow() {
var events = populateArray(50 + randInt(100), randEvent);
var left = randInt(1000);
events.forEach(function(event) {
event.left = left;
left += 10 + event.width + randInt(1000);
});
return {
events: events
}
}
function randEvent() {
var word = randWord()
var width = 50 + Math.max(8 * word.length, randInt(200));
var event = {
left: 0,
width: width,
label: word
};
event.offset = ko.computed(function() {
// reposition the text to stay
// * within its container
// * fully on-screen (if possible)
var leftEdge = model.leftEdge();
return Math.max(0, Math.min(
leftEdge - event.left,
event.width - 8 * event.label.length
));
});
return event;
}
function randWord() {
var n = 2 + randInt(5);
var ret = "";
while (n-- > 0) {
ret += randElt("rmhntsk");
ret += randElt("aeiou");
}
return ret;
}
function randElt(arr) {
return arr[randInt(arr.length)];
}
function populateArray(n, populate) {
var arr = new Array(n);
for (var i = 0; i < n; i++) {
arr[i] = populate();
}
return arr;
}
function randInt(n) {
return Math.floor(Math.random() * n);
}
.slide {
max-width: 100%;
overflow: auto;
border: 5px solid black;
}
.row {
position: relative;
height: 25px;
}
.event {
position: absolute;
top: 2.5px;
border: 1px solid black;
padding: 2px;
background: #cdffff;
font-size: 14px;
font-family: monospace;
}
.event > span {
position: relative;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="slide" data-bind="foreach: rows">
<div class="row" data-bind="foreach: events">
<div class="event" data-bind="style: { left: left+'px', width: width+'px' }"><span data-bind="text:label, style: { left: offset() + 'px' }"></div>
</div>
</div>
What I'd like to do is as the user scrolls from left-to-right, reposition the text within each box that partially overlaps the left border of the visible window to keep the text as visible as possible.
Currently I'm doing this by manually repositioning each item of text.
Is there a cleaner way to do this using CSS?
A friend helped me come up with this solution.
In English, the idea is to add an overlay to each row that is positioned relatively to the frame of the scrolling box, rather than the contents.
Then we can place a label for any box that overlaps the left edge in this overlay and it will appear to smoothly move as the box underneath it scrolls.
// generate some random data
var model = {
leftEdge: ko.observable(0),
};
model.rows = populateArray(10 + randInt(20), randRow);
model.width = Math.max.apply(Math, $.map(model.rows, function(row) {
return row.width
}));
ko.applyBindings(model);
$(function() {
$('.slide').on('scroll', function() {
model.leftEdge(this.scrollLeft);
})
})
function randRow() {
var events = populateArray(50 + randInt(100), randEvent);
var left = randInt(1000);
events.forEach(function(event) {
event.left = left;
left += 10 + event.width + randInt(1000);
});
return {
events: events,
width: left
}
}
function randEvent() {
var word = randWord()
var width = 50 + Math.max(8 * word.length, randInt(200));
var event = {
width: width,
label: word,
};
event.tense = ko.computed(function() {
// reposition the text to stay#
// * within its container
// * fully on-screen (if possible)
var leftEdge = model.leftEdge();
return ['future', 'present', 'past'][
(leftEdge >= event.left) +
(leftEdge > event.left + event.width - 8 * event.label.length)
];
});
return event;
}
function randWord() {
var n = 2 + randInt(5);
var ret = "";
while (n-- > 0) {
ret += randElt("rmhntsk");
ret += randElt("aeiou");
}
return ret;
}
function randElt(arr) {
return arr[randInt(arr.length)];
}
function populateArray(n, populate) {
var arr = new Array(n);
for (var i = 0; i < n; i++) {
arr[i] = populate();
}
return arr;
}
function randInt(n) {
return Math.floor(Math.random() * n);
}
.wrapper {
position: relative;
border: 5px solid black;
font-size: 14px;
font-family: monospace;
}
.slide {
max-width: 100%;
overflow: auto;
}
.slide > * {
height: 25px;
}
.overlay {
position: absolute;
width: 100%;
left: 0;
}
.overlay .past {
display: none
}
.overlay .present {
position: absolute;
z-index: 1;
top: 5.5px;
left 0;
}
.overlay .future {
display: none
}
.row {
position: relative;
}
.event {
position: absolute;
top: 2.5px;
border: 1px solid black;
padding: 2px;
background: #cdffff;
height: 14px;
}
.event .past {
float: right;
}
.event .present {
display: none;
}
.event .future {
float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="wrapper">
<div class="slide" data-bind="foreach: rows, style: { width: width + 'px' }">
<div class="overlay" data-bind="foreach: events">
<span data-bind="text:label, css: tense"></span>
</div>
<div class="row" data-bind="foreach: events">
<div class="event" data-bind="style: { left: left+'px', width: width+'px' }"><span data-bind="text:label, css: tense"></div>
</div>
</div></div>
This doesn't result in less javascript, but it does result in more efficient javascript, as class changes happen much less often than offset changes, so fewer updates to DOM elements are required.
You can avoid processing every "event" (in the above example) by doing some pre-partitioning of the horizontal space, and only updating events in the relevant partition.

Div containing input disapears on mouseout

I would like to keep a div visible while the mouse is in the bounds of the div, the code works until it hovers over the input. I would like a sign up form appear onmouseover and when the sign in is complete and the mouse moves off the div is no longer visible. jsFiddle Demo
HTML
<div class="members">
Members
<div id="sign-up-form" class="sign-up-form">
<input type="text" name="firstName">
</div>
</div>
JS
var signUp = document.getElementById('signUp');
var signUpForm = document.getElementById('sign-up-form');
signUp.onmouseover = function(){
signUpForm.style.display = 'block';
}
signUpForm.onmouseout = function(){
signUpForm.style.display = 'none';
}
CSS
#signUp{
position: relative;
background-color: red;
color: white;
padding: 6px;
}
#sign-up-form{
display: none;
position:absolute;
top: 32px;
left: 8px;
background-color: rgba(0,83,159,0.6);
padding: 15px;
}
I would do it this only with CSS:
#sign-up-form {
display: none;
}
.members:hover #sign-up-form {
display: block;
}
This example uses mouseover and mouseout event listeners and a function that will listen to all children elements before changing the display of the signUpForm element.
function outVis(event) {
var e = event.toElement || event.relatedTarget;
if (e.parentNode == this || e == this) {
return;
}
signUpForm.style.display = 'none';
}
http://jsfiddle.net/9xhb532v/1/

Categories

Resources