Hi I'm working on a drag and drop component website editor.
So I added a button when you click it it shown an input element and your input becomes the text of an paragraph element that's pushed to an array everytime you do it
My problem is that I want to make it so that when you click with the mouse the mouse x,y position becomes the left,top property of the paragraph element
But it sends me an error that says: Cannot read properties of undefined (reading 'left')
I already have the code that gets the mouse position so don't worry about it
Here's the code
//imports
import {mouse} from '/mouse.js'
//variables
let text_data_holder = document.getElementById('text-field')
let text_data = ''
//Creating text
let text_array = []
text_button.addEventListener('click', function() {
text_data_holder.style.display = 'block'
})
text_data_holder.addEventListener('change', function(e) {
e.preventDefault()
text_data += e.target.value
for(let i = 0; i < 1; i++){
text_array[i] = document.createElement('p')
.innerText = text_data
text_data_holder.after(text_array[i])
console.log(text_data)
text_array[i].left = mouse.x
}
})
Related
I have a series of horizontal lists within one vertical list. Each list item has an ID 'cr-id-x_y' where x is the vertical list item index and y is the horizontal list item index. I have created a for loop within a for loop to determine which list item has been targeted. The problem I'm having is the button presses seem to be firing twice (i.e. I'm seeing "Hello world" printed to the console twice on a button press). Please can someone tell me where I'm going wrong?
document.getElementById("parent-list").addEventListener("click", function (e) {
for (let x = 0; x < 100; x++) {
for (let i = 0; i < 30; i++) {
if (
e.target.closest(`#cr-id-${x}_${i}`) &&
(e.target.nodeName == "BUTTON" ||
e.target.parentElement.nodeName == "BUTTON")
) {
console.log("Hello world");
}
}
}
});
From the code you posted, it is not possible to guess why it is firing twice, but actually there is no need to loop at all. When you delegate like you do, you should be able to interrogate the clicked target - for example like this
document.getElementById("parent-list").addEventListener("click", function(e) {
const tgt = e.target; // or if nested e.target.closest('button'), then use tgt in the next if
if (tgt.tagName==="BUTTON") {
const someParent = tgt.closest('someElementThatMightHaveCRId');
if(someParent.id && someParent.id.startsWith('cr-id-')) {
console.log("Hello world",someParent.id);
}
}
})
I've got a fixed height div with a list of clickable list items. In the middle of the div, I have an absolute positioned line that is meant to signify a selected item. Right now, it's just a static line.
Is there a way to add an active class to the list item as it is "selected" by the line?
http://dev.chrislamdesign.com/shortwave/
One of the solutions is to use document.elementFromPoint(x, y) method. Something like this:
let lineCoords, lineTop, lineCenter;
// try to remove these two lines, leave just the scroll event listener
// document.getElementById('scrollUp1').addEventListener('click', setActive);
// document.getElementById('scrollDown1').addEventListener('click', setActive);
// 2nd edition: added these event listeners
window.addEventListener('scroll', getLineCoords);
window.addEventListener('load', getLineCoords);
// added this line
document.getElementById('wrap-scroll-1').addEventListener('scroll', setActive);
function setActive() {
const li = document.elementFromPoint(lineCenter, lineTop + lineCoords.height);
clearActive();
li.classList.add('active');
}
function clearActive() {
const ul = document.getElementById('ul-scroll-1');
const activeLi = ul.querySelector('li.active');
if (activeLi) {
activeLi.classList.remove('active');
}
}
// 2nd edition: added this function
function getLineCoords() {
lineCoords = document.querySelector('.orange-line').getBoundingClientRect();
lineTop = lineCoords.top;
lineCenter = lineCoords.left + (lineCoords.width / 2);
}
You can see this in action here: JsBin. These up and down buttons are assumed to scroll the list, but I don't have this functionality, because that's not a point here - just scroll it youself. The point here is that the element under the orange line will get active class each time you click one of these buttons.
So, take this code and edit it as you want.
Edited: I added an scroll event listener to the #wrap-scroll-1 container, because I guess the scroll event occurs right on it. If not - you can change it. Look at this in action: JsBin
2nd edition: Added event listeners to reassign the orange line coordinates every time when the page scrolled, and also when the page is loaded. Take a look at the result here: JsBin
You could compare the rects of the line and each option to find which is selected:
const line = document.querySelector('#emotional .orange-line');
const options = document.querySelector('#emotional .selection-options').children;
const lineY = line.getBoundingClientRect().y;
const optionAfterSelected = [...options].find((option) => {
return option.getBoundingClientRect().y > lineY;
});
const selected = optionAfterSelected.previousSibling;
selected.classList.add('selected');
The selected option is the one with the largest y value without exceeding the y value of the orange line. To make things simple, the loop just returns the first option with a y value greater than the line, then grabs its previous sibling.
Update
To get this code to run whenever the user scrolls, you can wrap it in a function and attach it as an eventListener:
function updateSelection(menuId) {
const line = document.querySelector(menuId + ' .orange-line');
const options = document.querySelector(menuId + ' .selection-options').children;
const lineY = line.getBoundingClientRect().y;
const optionAfterSelected = [...options].find((option) => {
return option.getBoundingClientRect().y > lineY;
});
const selected = optionAfterSelected.previousSibling;
selected.classList.add('selected');
}
document.querySelector('#emotional .wrap-container').addEventListener('wheel', () => {
updateSelection('#emotional');
});
document.querySelector('#genre .wrap-container').addEventListener('wheel', () => {
updateSelection('#genre');
});
document.querySelector('#cinematic .wrap-container').addEventListener('wheel', () => {
updateSelection('#cinematic');
});
I have a list of news and I can modify the news. When I modify one she stay at her inital position (if she was at place 5 she stay here). But when I click "modify this new" a form pop to the bottom of the page and then when I submit the modifications I want to scroll to this modified new. To do that I would use something like
find position where label.text() == titleModified
then I could do
window.scrollTo(0,result of the line above);
For the moment I tried to do document.getElementById but it always bring me to the top of the page...
Thank you for helping me
PS: there is a link on Plunker to see the structure of news : https://plnkr.co/edit/mLCxPYaBR56KkEOLNF8F?p=preview
and this is my JS for the modification:
'submit .modifyArticle'(event) {
event.preventDefault();
const target = event.target;
const textModif = target.textModif.value;
const titreModif = target.titreModif.value;
const photoModif = target.photoModif.value;
const idModif = Session.get('idTemp');
//test if values from the from are not empty or whitespaced
if ((/\S/.test(textModif))||(/\S/.test(titreModif))) {
console.log("2ème étape: dans body.js -> submit .modifyArticle");
Meteor.call('articles.modify',idModif,textModif,titreModif,photoModif);
Session.set('wantModif',false);
//here my new is modified so I want to scroll to her
//var titreModified actually contains the title after the modification but only for the first new...
setTimeout(function(){
var titreModified = document.getElementById("titreArticle");
var position = titreModified.offsetTop;
console.log("Y: " + position);
console.log("var titreArticleModif: "+ titreModified.textContent);
}, 50)
[...]
EDIT (i'll put the solution here but the real hero is #alexr101): First I had to add a class to my label <label class="titreArticle">{{titre}}</label>
then this is the JS:
`setTimeout(function(){
$('.titreArticle').each(function(i, obj) {
if(obj.textContent.includes(titreModif)){
alert("le titre devrait être: " + obj.textContent);
var position = obj.offsetTop;
window.scrollTo(0,position);
return false;
}
});
}, 20)`
the timeout is here because it's not 100% real-time and I had to wait until the new title was put in the DOM.
Try getting the y position of the element like so:
//get modified element
var titleModified = document.getElementById("modifiedElement");
//get y position of element through offsetTop function
var yPosition = titleModified.offsetTop;
//Set scroll amount
window.scrollTo(0, yPosition );
Would that work?
I'm dynamically generating a div using JavaScript. When mouse is over certain elements in the page the new div appears. On mouseout it disappears. All that is working fine. But I want the div to be top-positioned according to the element the mouse was over of. So I record the position of the element with getBoundingClientReach:
function lopen(AbstId) { //called OnMouseOver
var rect = document.getElementById(AbstId).getBoundingClientRect();
var st3="px";
divtop = rect.top+st3 ;
alert ("Hello :" + divtop); //for checking purposes only
if (this.element == null) {
this.element = document.createElement('div');
this.element.id = "myPopup";
this.element.className = "myPopupBody";
this.element.onmouseover = 'prevent()'; //if mouse over new div, do not close
this.element.style["top"] = divtop; //HERE IS MY PROBLEM
}
document.body.appendChild(this.element);
popUpDetails();
}
function lclose () { //called OnMouseOut
document.getElementById("myPopup").innerHTML = " ";
document.body.removeChild(this.element);
}
The piece of code
this.element.style["top"] = divtop;
is getting the correct top value on first event, but do not actualize for further onmouseover events, even though divtop is actually refreshing (I check with the alert of 5th line).
Does anyone spot the problem?
Again, just for completeness. The problem was an incorrect assignment of style: top. Below the right code
function lopen(AbstId) { //called OnMouseOver
var rect = document.getElementById(AbstId).getBoundingClientRect();
var st3="px";
divtop = rect.top+st3 ;
//alert ("Hello :" + divtop); //for checking purposes only
if (this.element == null) {
this.element = document.createElement('div');
this.element.id = "myPopup";
this.element.className = "myPopupBody";
this.element.onmouseover = 'prevent()'; //if mouse over new div, do not close
//this.element.style["top"] = divtop; //here WAS my problem -deleted line
}
document.body.appendChild(this.element);
document.getElementById("myPopup").style["top"] = divtop; //here right code
popUpDetails();
}
I have a HTML5 canvas, which is displaying a number of images and a paragraph of text on the page underneath the canvas. I want the text in the paragraph to be updated to display a different element from a JS array depending on which image the user clicks on.
Currently, I have a 'mousedown' function that looks like this:
_mousedown: function(evt) {
this._setUserPosition(evt);
var obj = this.getIntersection(this.getUserPosition());
if(obj && obj.shape) {
var shape = obj.shape;
this.clickStart = true;
shape._handleEvent('mousedown', evt);
isClickOnImage(evt);
var id = shape.id;
selectTip(id);
}
//init stage drag and drop
if(Kinetic.DD && this.attrs.draggable) {
this._initDrag();
}
}
I tried using the line var id = shape.id to update the ID that's being passed to the function, so that it will get the correct element from my 'tips' array, but for some reason, when I view the page in the browser, and click on an image, the text beneath the canvas is not updated. It seems that this function is not updating the 'id' variable to the ID of whichever image has been clicked.
After looking into this, it seems to me that I will want to use a loop inside the 'mousedown' function, that will take the 'id' of the image on which the click has been detected, and loop through my 'sources' array (which is where all of the images have been loaded from the HTML into the JS), checking at each position whether the image stored at that location has the same ID as that of the image that has been clicked on. If it does, the loop should set the text to the text stored at that position of the array, and if not, it should continue looking through the array until it find it. Would this make sense? I tried adding the following code to the 'mousedown' function, but it doesn't change the text as I expected:
var imageCheckArray = 0;
while(imageCheckArray < sources.length){
if(shape.id == sources[imageCheckArray]){
selectTip(imageCheckArray);
} else {
imageCheckArray++;
}
}
Is there something I'm missing from the loop?
The code for the whole function currently looks like this:
_mousedown: function(evt) {
this._setUserPosition(evt);
var obj = this.getIntersection(this.getUserPosition());
if(obj && obj.shape) {
var shape = obj.shape;
this.clickStart = true;
shape._handleEvent('mousedown', evt);
isClickOnImage(evt);
/*This line needs to get the element of the sources array that has been selected,
and then select the element at the same position from the tips array.*/
//var id = null;
var imageCheckArray = 0;
while(imageCheckArray < sources.length){
if(shape.id == sources[imageCheckArray]){
selectTip(imageCheckArray);
} else {
imageCheckArray++;
}
}
//var id =
//selectTip(id);
}
//init stage drag and drop
if(Kinetic.DD && this.attrs.draggable) {
this._initDrag();
}
}
Edit 11/01/2013 # 16:10
The code for selectTip is:
function selectTip(id){
$("#tipsParagraph").text(tips[id]);
}
and I've put a jsFiddle up here: http://jsfiddle.net/cd8G7/ although the 'result' panel is not showing what I actually see when I view the page in my browser- I get the canvas with all of the images displayed, and the paragraph underneath the canvas shows the text from the first element of my 'tips' array.
Edit 23/01/2013 # 13:50
Here's my isClickOnImage function:
function isClickOnImage(event){
var clickX = event.clientX;
var clickY = event.clientY;
//var imageCheckIteration = 0;
while(imageCheckIteration < sources.length){
if((clickX > sources[imageCheckIteration].x && clickX < sources[imageCheckIteration].x + imageWidth) &&
(clickY > sources[imageCheckIteration].y && clickY < sources[imageCheckIteration].y + imageHeight)){
/*This is where I need to print the variable that holds the text I want to display, but I need to display its contents
outside the canvas, in the <p></p> tags below. */
console.log("Click on image detected");
document.getElementById("tipsParagraph").innerHTML = sources[imageCheckIteration].data-tip /*tips[imageCheckIteration]*/;
} else {
document.getElementById("tipsParagraph").innerHTML = "";
}
}
}
What I intended that this function do is, capture the X & Y coordinates of any click on the canvas, and store them in the variables "clickX" and "clickY". Then, I have a variable called "imageCheckIteration" that has been initialised to 0, and while this variable is less than the length of my "sources" array (which is the array where all of the images have been stored), the function should check whether the click was on an area of the canvas that is covered by one of the images in the array.
If it was, then a console log should display the message "click on image detected", and the line
document.getElementById("tipsParagraph").innerHTML = sources[imageCheckIteration].data-tip;
should set the value of the "tipsParagraph" to be the value of the 'data-tip' attribute of whichever image is at the 'imageCheckIteration' position of the 'sources' array. If the click was detected on an area of the canvas that does not have an image displayed, then the value of the "tipsParagraph" should be set to hold nothing.
However, for some reason, when I view the page in the browser, the 'tipsParagraph' displays the text "This is where the text will be displayed", which is its default value, so that's fine. But, when I click on an image, or click anywhere else on the canvas, the text displayed in the 'tipsParagraph' is not updated.
I can't figure out why this is- can someone point me in the right direction? Does it mean that my isClickOnImage(event) function is never being called?
I simplified the way you are getting a reference to an image through the canvas. The trick here is to swap the z-index of the canvas and the image container and grab the reference to the image on the mouse up event. I don't know of a clean way to get elements behind a canvas, hence the workaround.
$('canvas').bind('mousedown', function(e) {
$('section').css('z-index', 4);
});
$('img').bind('mouseup', function(e) {
$('#tipsParagraph').text($(this).attr('id') + ":" + $(this).attr('alt'));
$('section').css('z-index', 2);
});
The second portion here is grabbing some attributes from the image itself and updating the text inside your div.
You can see more of the solution here.