Hello I do have data which needs to be refreshed every 5 seconds but I can't retrieve the value after the refresh. I've tried this:
First, I've used the refresh function:
$(document).ready(function(){
$('#div_refresh').load("userdetail.html");
setInterval(function(){
$('#div_refresh').load("userdetail.html")
}, 5000);
});
This it is loading + refreshing my DIV, as per the HTML (userdetail.html)
<html>
<p>Username: <span id="Name"></span></br>
Routing Profile: <span id="routingProfile"></span></br>
Agent State: <span id="State"></span></br>
State TimeStamp: <span id="TimeStamp"></span></br>
State Duration: <span id="Duration"></span></p>
</html>
This is working, DIV it is refreshing, however, after the first refresh all data that it is being populated through the script:
/* agent.js */
connect.agent(function(agent) {
document.getElementById("Name").innerHTML = agent.getName();
document.getElementById("routingProfile").innerHTML = agent.getRoutingProfile().name;
document.getElementById("State").innerHTML= agent.getState().name;
document.getElementById("TimeStamp").innerHTML = agent.getState().startTimestamp;
document.getElementById("Duration").innerHTML = agent.getStateDuration();
});
No longer appears, it just returns the paragraph within the names, but the 'span' value doesn't.
On my main HTML (index.html) this is how the setup it is set:
<div id="div_refresh"></div>
<script id="agent" type="text/javascript" src="./agent.js"></script>
<script src="./reload.js"></script>
This is how it looks like before the refresh and the span data disapear
https://iili.io/c6p8pS.png
Any ideas?
A different approach to this is instead of reloading the div every x seconds is to trigger a function on the agent.onRefresh event in streams. For example...
connect.agent(async function (agent) {
agent.onRefresh(handleAgentRefresh);
});
function handleAgentRefresh(agent) {
var changedRoutingProfileName = agent.getRoutingProfile().name;
if (changedRoutingProfileName != document.getElementById('routingProfile').innerHTML ) {
document.getElementById('routingProfile').innerHTML = changedRoutingProfileName ;
}
}
This way the routing profile name will update automatically when streams reports that it has changed. and you don't have to reload at all.
This is totally untested and will not actually run here. Uing a promise we can get the results, paste them in, then once that is done to the "agent" thing, then after 5 seconds, trigger it to start over.
FWIW, I change the id since "#divxxx" making the id match the tag is not a good practice. I also used some CSS and remove your invalid </br> and used CSS to style it instead - but that is not part of your question, more me formatting it with CSS.
$(function() {
function fetchMe(url) {
return jQuery.ajax(url, {
type: 'GET',
dataType: 'html'
});
}
$('#refreshed-content').on("go-get-another", function() {
$.when(fetchMe('userdetail.html'))
.then(function(results) { // Resolve
$('#refreshed-content').html(results.data);
}, function() { // Reject!
console.log('Something broke!');
}).then(function() {
// you decide if this is the right place to go get what was pasted in: I have less experience with this area.
connect.agent(function(agent) {
document.getElementById("Name").innerHTML = agent.getName();
document.getElementById("routingProfile").innerHTML = agent.getRoutingProfile().name;
document.getElementById("State").innerHTML = agent.getState().name;
document.getElementById("TimeStamp").innerHTML = agent.getState().startTimestamp;
document.getElementById("Duration").innerHTML = agent.getStateDuration();
});
})
// set a 5 second delay then trigger next load
.then(value =>
new Promise(resolve =>
setTimeout(() => resolve(value), 5000)
)
).then(function() {
$('#refreshed-content').trigger('go-get-another');
});
})
// start the ball rolling:
.trigger('go-get-another');
});
.content-block {
padding-top: 1rem;
padding-bottom: 1rem;
display: flex;
flex-direction: coumn;
}
.content-row {
display: block;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="refreshed-content" class="content-block">
<div class="content-row user-name">Username:<span id="Name"></span></div>
<div class="content-row"> Routing Profile:<span id="routingProfile"></span></div>
<span class="content-row"> Agent State:<span id="State"></span></span>
<p class="content-row"> State TimeStamp:<span id="TimeStamp"></span></p>
<div class="content-row"> State Duration:<span id="Duration"></span></div>
</div>
Related
I am writing a chrome extension extension for an auto repair POS (ShopWare: www.shop-ware.com). My extension takes the workflow page (see images below) and turns it into a 'dispatch board' that is displayed on a big screen out in a shop for auto technicians to view incoming repair orders.
My extension removes unnecessary elements, enlarges the technician names, and causes incoming repair orders to blink until the technician 'accepts' the order on his/her personal device.
Problem:
I was unable to enlarge the text of the technician names with CSS injection. I had to handle it programmatically:
const addStyleProp = () => {
const className = "section-title";
const styleObj = { "font-size": "40px" };
elms = document.getElementsByClassName(className);
for (let elm in elms) {
let curr = elms[elm];
if (typeof curr === "object") {
try {
Object.assign(curr.style, styleObj);
} catch (error) {
console.log(
"Error attempting to change styling on the current node.",
elm
);
}
}
}
};
addStyleProp();
The issue I am having is, whenever a repair order is transferred to the technician, it triggers a soft reload, that is, it reloads the components, including the text that I have modified with my script. I am now trying to add an event listener to catch that, but I can't figure out what the event is. I've tried:
document.getElementById('workorders-index').addEventListener('change',addStyleProp);
//add a DOM listener to handle changes to the DOM
document.addEventListener("DOMContentLoaded", addStyleProp);
Here is the page source before my insertion:
<h4 class="section-title">
<i class="icon-staff blue"></i>
George
<span class="assigned-work-orders">
<i class="icon-car-fa6"></i>
17
</span>
</h4>
Here is the POS source after my extension:
<h4 class="section-title" style="font-size: 40px;">
<i class="icon-staff blue"></i>
George
<span class="assigned-work-orders">
<i class="icon-car-fa6"></i>
17
</span>
</h4>
Whenever an order is transferred, the element reverts to the original source. When I try CSS injection, there is no change to the text; however, the CSS that I injected to cause the elements to blink work just fine. I feel that injecting the style sheet would be the best way to go here, but I can't figure out how to make it work. So, how can I either
A.) change via CSS injection
B.) catch the event when the text reverts?
Here is a snippet of my background.js and blink.css. Note: the CSS setting font-size does NOT WORK, hence my 'enlarge-text.js script.
Hope I haven't been too convoluted! I'd appreciate any thoughts on how I can do this better.
background.js:
const scriptFiles = ["scripts/removeElements.js", "./scripts/enlarge-text.js"];
const cssFiles = ["./css/blink.css"];
chrome.runtime.onInstalled.addListener(() => {
chrome.action.setBadgeText({
text: "",
});
});
const swRegex = new RegExp(".shop-ware.com");
chrome.action.onClicked.addListener(async (tab) => {
//Check to see if we're on shopware. If not, bail.
if (!(tab.url.search(swRegex) > 0)) {
return;
}
const state = await chrome.action.getBadgeText({ tabId: tab.id });
//Already on. Notihng to do here!
if (state === "ON") {
return;
}
//set action badge to state
await chrome.action.setBadgeText({
tabId: tab.id,
text: "ON",
});
await chrome.scripting.executeScript({
files: scriptFiles,
target: { tabId: tab.id },
});
/**Blink the in-transition class and enlarge text*/
await chrome.scripting.insertCSS({
files: cssFiles,
target: { tabId: tab.id },
});
});
blink.css:
.in-transition{
animation: blink-animation 1s steps(5, start) none infinite;
}
#keyframes blink-animation {
to{
visibility: hidden;
}
}
h4.section-title {
font-size: 40px;
}
i.muted {
font-size: 40px;
}
This is the view before I activate my service:
View after my service:
Titus posted the following answer that solved the issue without having to add an event listener. It's much cleaner than my approach to handling it programmatically!
Titus answer:
Try adding font-size: 40px !important; to the injected CSS file (blink.css). If that doesn't work, take a look at MutationObserver –
Titus
first of all, I apologize that I don't speak English, I am trying to improve in this language.
I have a problem that I don't know how to solve it, I have a web in WordPress and it creates buttons dynamically with some dynamic classes, I need to take 1 of those classes to do the following:
Let's imagine that we have 5 buttons, when I clicked on 1 of those 5 that the other 4 buttons are hidden and this is saved in the local storage, ie everything is kept in the browser.
I have this code:
$('.dce-button').click(function(event){
$(this).attr('data-id' , 'Next');
localStorage.setItem("class", "noDisplay");
localStorage.setItem("noDisplay", "true");
$('.dce-button').not(this).each(function(){
$(this).toggleClass("noDisplay");
});
})
I use it to select the button I need and WORKS, it disappears the other 4 but I don't know how to keep it with the local storage, Is someone can help me?
Something like this would work and address Peter's comment
(working JS fiddle since localStorage isnt accessible in snippets on SO https://jsfiddle.net/f7804xwc/1/)
// on page load, get the stored value if there is one and set the buttons accordingly
let selectedBtn = localStorage.getItem('selectedBtn');
if (selectedBtn) {
$('.dce-button').hide();
$('#'+selectedBtn).show()
}
$('.dce-button').click(function(event) {
let selectedBtn = localStorage.getItem('selectedBtn');
console.log(selectedBtn);
if (selectedBtn === this.id) {
// user clicked the selected button again, lets un-select it and re-show all the buttons
localStorage.removeItem('selectedBtn');
$('.dce-button').show();
} else {
// user clicked a button, store selection and hide the others
localStorage.setItem("selectedBtn", this.id);
$('.dce-button').not(this).each(function() {
$(this).hide();
});
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="dce-button" id="btn1">Button 1</button><br>
<button class="dce-button" id="btn2">Button 2</button><br>
<button class="dce-button" id="btn3">Button 3</button><br>
<button class="dce-button" id="btn4">Button 4</button><br>
<button class="dce-button" id="btn5">Button 5</button><br>
From the above comment ...
"Once the OP has managed to solve writing to and reading from the local storage the proper button state there will be never a chance of any other button visible than the very one which was selected at the very first time (unless one resets/erases the local storage's state(s))."
... and all code of the next provided example which relates to initStorageRelatedUIHandling (the last 3 buttons which are for logging, clearing and rendering from the local storage's current state) is going to prove the above said/commented.
Now as for a possible solution, I personally would chose an approach which stores the state(s) of any DCE related button as a JSON.stringify 'ed array. Thus one does not rely on any specific button identification but stays generic from the beginning based on just a list of queried (jquery or selector api ... it doesn't matter) DOM nodes.
One would write 2 functions writeDceState and readDceState which both take care of the storage and JSON handling.
Another function like updateDceState exclusively takes care of generating the current array representation of any buttons state based on the currently targeted/selected button. It queries a list of all DCE buttons and does map each visibility state as false except for the currently chosen. It finally forwards the updated state to writeDceState.
The render part is covered exclusively by renderDceStateFromStorage. It reads the most current state from storage and updates each button's visibility from the parsed array.
Then there is a sole handler function ... handleDceStateChange ... which on click invokes both functions one after the other ... updateDceState and renderDceStateFromStorage.
function writeDceState(state = null) {
// `mock` is a specifc namespace which carries
// `localStorage` behavior for SO environments.
// - Thus the next line can be omitted later.
const { localStorage } = mock;
localStorage
.setItem('dce', JSON.stringify(state)
);
}
function readDceState() {
// `mock` is a specifc namespace which carries
// `localStorage` behavior for SO environments.
// - Thus the next line can be omitted later.
const { localStorage } = mock;
return JSON
.parse(
localStorage.getItem('dce') || null
);
}
function updateDceState(dceTarget) {
const state = Array
.from(
document
.querySelectorAll('.dce-button')
)
.map(elm => (elm === dceTarget));
writeDceState(state);
}
function renderDceStateFromStorage() {
const state = readDceState();
document
.querySelectorAll('.dce-button')
.forEach((elm, idx) =>
elm.disabled = !!state && !state[idx]
);
}
function handleDceStateChange({ currentTarget }) {
updateDceState(currentTarget);
renderDceStateFromStorage();
}
function initDceHandling() {
document
.querySelectorAll('.dce-button')
.forEach(elm =>
elm.addEventListener('click', handleDceStateChange)
);
}
function initStorageRelatedUIHandling() {
document
.querySelector('[data-state-display]')
.addEventListener('click', () =>
console.log( readDceState() )
);
document
.querySelector('[data-state-clear]')
.addEventListener('click', mock.localStorage.clear);
document
.querySelector('[data-state-rerender]')
.addEventListener('click', renderDceStateFromStorage);
}
initDceHandling();
initStorageRelatedUIHandling();
body { margin: 0; }
ul { list-style: none; padding: 0; margin: 0; }
li:nth-child(6) { margin-top: 20px; }
li:nth-child(8) { margin-top: 10px; }
.as-console-wrapper { left: auto!important; width: 50%; min-height: 100%; }
<ul>
<li>
<button class="dce-button">Foo</button>
</li>
<li>
<button class="dce-button">Bar</button>
</li>
<li>
<button class="dce-button">Baz</button>
</li>
<li>
<button class="dce-button">Biz</button>
</li>
<li>
<button class="dce-button">Buzz</button>
</li>
<li>
<button data-state-clear>clear storage state</button>
</li>
<li>
<button data-state-display>display storage state</button>
</li>
<li>
<button data-state-rerender>rerender from storage</button>
</li>
</ul>
<script>
// mock for the SO specific stack snippet
// due to the policies and environment are
// not allowing an original storage access.
const mock = {
localStorage: (function () {
// https://developer.mozilla.org/en-US/docs/Web/API/Storage
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
const storage = new Map;
function key(int) {
return [
...storage.keys()
][parseInt(int, 10)];
}
function setItem(key, value) {
return storage.set(String(key), String(value));
}
function getItem(key) {
return storage.get(String(key));
}
function removeItem(key) {
return storage.delete(String(key));
}
function clear() {
return storage.clear();
}
return {
get length() {
return storage.size;
},
key,
getItem,
setItem,
removeItem,
clear,
};
}()),
}
</script>
I would like to be able to embed the content that gets created via this snippet anywhere I like, using one line of code - same way that you get any kind of snippet somewhere, so that it renders an iframe or similar in return. I am not sure where to start with that, and if this what I already have is usable/ready to "convert" to an embeddable snippet. I went through a few tutorials but it was quite confusing since it was using some backend stuff I don't really understand...
Obviously, it should be hosted somewhere, but the part where I need to "call" the snippet is not really clear to me. Now, it just appears everywhere on my website since it's just a normal JS file which gets included, same as all other files.
It would be great if I could just pack it somehow and call it like this let's say:
<script src="link-to-my-snippet.js"></script>
If someone could direct me a bit that would be great.
const data = [
{
name: "John Doe",
age: 40
},
{
name: "Jane Doe",
age: 50
}
];
window.addEventListener("DOMContentLoaded", function() {
function Item(configurationObject) {
apiCall(data);
}
function apiCall(data) {
// Do some API call and get back the data
// With the Unique ID that wass passed in via the
// configuration Object
// "data" is faked just for the demonstration
createHTML(data);
}
function createHTML(data) {
const mainDiv = document.createElement("div");
document.body.appendChild(mainDiv);
let html = '';
data.forEach((user) => {
html += `
<div class="test">
<p>${user.name}</p>
<p>${user.age}</p>
</div>
`;
});
mainDiv.innerHTML = html;
createStylesheet();
}
function createStylesheet() {
const style = document.createElement("style");
style.innerHTML = `
.test {
background-color: lightgreen;
color: white;
}
`;
document.head.appendChild(style);
}
let configurationObject = {
uniqueID: 1234
}
let initialize = new Item(configurationObject);
});
There are two ways:
Using modern javascript - ES6, Webpack, SCSS, and then bundle all in a single file using NPM
Follow: https://blog.jenyay.com/building-javascript-widget/
Pure JavaScript - Custom Created.
You can create a self-executable anonymous function like this and write your complete widget code - including your HTML, CSS, etc inside this. It will be executed once your page is loaded with this script.
(function() {
// The following code will be enclosed within an anonymous function
var foo = "Hello World!";
document.write("<p>Inside our anonymous function foo means '" + foo + '".</p>');
})(); // We call our anonymous function immediately
For the second type solution you can also follow following article:
https://gomakethings.com/the-anatomy-of-a-vanilla-javascript-plugin/
I'm assuming that you have some local static HTML/CSS page.
First off, you don't need to render the generated HTML by the javascript in an iframe, almost any element will do. The purpose of JS is to create, manipulate and read DOM-elements, so don't feel limited.
Secondly, some of that code is useless for your purpose, unless you plan on doing stuff with an API (which I assume not), and have an actual need for a unique ID. In that code, that unique Id isn't unique anyway and isn't used for anything.
There is so many ways to implement this script on any page of your choice, but here's one:
You have a HTML-file, in that one, put:
<div id="users-list"></div>
wherever you want the list to appear.
Create a file called whatever you want, but for example users-list.js. Check the demo in my answer for the JS code to put in that file.
In any HTML file where you have added an element with the ID of 'users-list', simply also add the script in that same HTML file. Preferably before the ending tag.
<script src="/path/to/users-list.js"></script>
Of course, you make this in so many ways, and expand on it infinitely. For example, something like this could be cool to have:
<div id="some-div-id"></div>
...
<script src="/path/users-list.js">
getUsers({
element: 'some-div-id'
theme: 'dark',
layout: 'boxes' // or 'rows'
});
</script>
Then you could invoke the script on different pages with different generated HTML, but of course, that would require some modification of your JS code, to actually print out different css content, and getting the right element to place the data in.
In any case, it wouldn't be hard to do.
But back on topic, working demo:
const users = [
{
name: "John Doe",
age: 40
},
{
name: "Jane Doe",
age: 50
}
];
const styles = `
.user {
background-color: lightgreen;
color: white;
}
.age { font-weight: bold; }
`;
function setStyles() {
const styleElement = document.createElement('style');
styleElement.innerHTML = styles;
document.head.appendChild(styleElement);
}
function setUsers(users) {
let element = document.getElementById('users-list')
let usersHtml = '';
users.forEach(user => {
usersHtml += `
<div class="user">
<p class="name">${user.name}</p>
<p class="age">${user.age}</p>
</div>
`;
})
if (element) element.innerHTML = usersHtml;
}
window.addEventListener("DOMContentLoaded", function () {
setUsers(users);
setStyles(styles);
});
<div id="users-list"></div>
Here is an example of a self invoking recursive IIFE checking for the document readyState, better than the accepted answers solution
const myPlugin = () => {
// stuff
}
/**
* Checks the document readyState until it's ready
*/
(ready = (delay) => {
// this is always 'complete' if everything on the page is loaded,
// if you want to check for a state when all html/js is loaded but not all assets, check for 'interactive'
if (document.readyState == 'complete') {
myPlugin() // your stuff being invoked when doc is ready
} else {
console.log('Retrying!')
setTimeout(() => { ready(delay) }, delay)
}
})(50)
I am still fairly new to JavaScript and am trying to deepen my understanding of it through mini projects.
In this counter project, I have managed to get the result what I want:
After clicking the "add" button, the counter increment would increase and change color to green.
After clicking the "subtract" button, the counter increment would decrease and change color to red.
Below would be my JavaScript code:
//create variables to hold the HTML selectors
var counterDisplay = document.querySelector('.counter-display');
var counterMinus = document.querySelector('.counter-minus');
var counterPlus = document.querySelector('.counter-plus');
//create variable for the counter
//what we'll use to add and subtract from
var count = 0;
//create a function to avoid code redundancy
function updateDisplay(){
counterDisplay.innerHTML = count;
};
function toGreen(){
document.querySelector('.counter-display').style.color = "green";
};
function toRed(){
document.querySelector('.counter-display').style.color = "red";
};
/*-------------------------------------------*/
updateDisplay();
//EventListeners
counterPlus.addEventListener("click", () =>{
count++;
updateDisplay();
toGreen();
});
counterMinus.addEventListener("click", () =>{
count--;
updateDisplay();
toRed();
});
I separated the color functions but I feel like there's a cleaner way to write this code i.e conditionals like if statements, however, as I'm still learning I don't fully know how to implement this.
**As mentioned, I'm still learning so a thorough explanation/laymen's terms is greatly appreciated!
Please let me know for any clarifications or if more info is needed!
Thank you in advance!
EDIT:
Thank you those who have taken the time to help me in sharing their solutions!
The code you wrote is quite long but it does the job
I'm not sure what do you want exactly, but here are few notes :
Use HTML onclick Event instead:
Instead of adding event listener from javascript you can add it in the HTML code like so: <button onclick="myFunction()">Click me</button>, and whenever the button is clicked myFunction() will be called.
You can also pass the button as a parameter, for example
function myFunction(element) {
element.style.backgroundColor = "red";
}
<button onclick="myFunction(this)">Click me</button>
Use let instead of var:
Variables declared by var keyword are scoped to the immediate function body (hence the function scope) while let variables are scoped to the immediate enclosing block denoted by { } (hence the block scope).
find more info here: What's the difference between using “let” and “var”?
You need to make the following few changes:
Make use of let and const. If it doesn't change its value in the future then use const else let.
You can create an array of buttons (in the below example buttons) so that you can loop over and add event listener on all buttons. So that you don't need to add an event listener on each one of the buttons.
You can use data attribute to recognize which type of button it is.
Get rid of multiple functions toRed or toGreen, instead make a single function that will change the color of text with only one function changeTextColor.
If you just need to change the text of the HTML element then better to use textContent in updateDisplay function.
see innerText vs innerHTML vs label vs text vs textContent vs outerText
//create variables to hold the HTML selectors
const counterDisplay = document.querySelector(".counter-display");
const counterMinus = document.querySelector(".counter-minus");
const counterPlus = document.querySelector(".counter-plus");
//create variable for the counter
//what we'll use to add and subtract from
let count = 0;
//create a function to avoid code redundancy
function updateDisplay() {
counterDisplay.textContent = count;
}
function changeTextColor(color) {
counterDisplay.style.color = color;
}
/*-------------------------------------------*/
//EventListeners
const buttons = [counterMinus, counterPlus];
buttons.forEach((btn) => {
btn.addEventListener("click", (e) => {
const btnType = e.target.dataset.type;
if (btnType === "sub") {
count--;
changeTextColor("red");
} else {
count++;
changeTextColor("green");
}
updateDisplay();
});
});
.counter-display {
background-color: black;
padding: 1rem;
border-radius: 4px;
margin-bottom: 1rem;
font-size: 2rem;
font-weight: 700;
text-align: center;
color: whitesmoke;
}
.button-container {
display: flex;
gap: 1rem;
justify-content: center;
}
.button-container>* {
padding: .75rem 3rem;
font-size: 2rem;
border: none;
border-radius: 4px;
}
.counter-minus {
background-color: red;
}
.counter-plus {
background-color: green;
}
<div class="counter-display"> 0 </div>
<div class="button-container">
<button class="counter-minus" data-type="sub">-</button>
<button class="counter-plus" data-type="add">+</button>
</div>
How about something more app based? You can break your whole thing down into a couple key components with a little glue get something that you can easily build on. Its not very different than what you already have, it just sprinkles in some tried and true patterns.
const widget = (element) => {
const $minus = element.querySelector('.counter-minus');
const $plus = element.querySelector('.counter-plus');
const $display = element.querySelector('.counter-display');
let state = {
color: 'black',
count: 0
};
const update = (next) => {
state = next;
render();
};
const render = () => {
$display.innerText = state.count;
$display.style.color = state.color;
};
const onMinusClick = () => {
update({ color: 'red', count: state.count - 1 });
};
const onPlusClick = () => {
update({ color: 'green', count: state.count + 1 });
};
$minus.addEventListener('click', onMinusClick);
$plus.addEventListener('click', onPlusClick);
render();
return () => {
$minus.removeEventListener('click', onMinusClick);
$plus.removeEventListener('click', onPlusClick);
};
};
widget(document.querySelector('#app1'));
widget(document.querySelector('#app2'));
widget(document.querySelector('#app3'));
div {
margin-bottom: .5rem;
}
<div id="app1">
<button class="counter-minus">-</button>
<span class="counter-display"></span>
<button class="counter-plus">+</button>
</div>
<div id="app2">
<button class="counter-minus">-</button>
<span class="counter-display"></span>
<button class="counter-plus">+</button>
</div>
<div id="app3">
<button class="counter-minus">-</button>
<span class="counter-display"></span>
<button class="counter-plus">+</button>
</div>
First thing you probably notice is that i have multiple instance running of my app. I can do this because I dont rely on global state. You can see that when you call widget, it holds its own variables.
I have state as an object and the only thing that can write to state is the update function. You had something very similar except i combined my state into a single variable and have 1 function in charge of writing to state, and then reacting to it i.e. rendering.
Then I have some event handlers being attached that are just calling update with how they want the state to look. This of course triggers a render to keep the ui consistent with the state. This 1 way data flow is very important to keep code clear. The update function can be more sophistacted and do things like except partial state values and spread state = { ...state, ...next}. it can get pretty crazy.
Lasty i return a function. You can return anything in here, you might want to return an api for a user to interact with you widget with. For example you have a calendar and you want to be able to set the date outside of the widget. You might return { setDate: (date) => update({ date }) } or something. However i return a function that will clean up after the widgets by removing event listeners so garbage collection can reclaim any memory i was using.
I have tried googling and searching entirety of stack overflow for this question but I think it boils down to the keywords I'm using to search.
Basically my problem boils down to the following: when the cursor leaves an element, wait 500 milliseconds before closing the element. Before close the element, check if the cursor is back in the element, and if its not, do not hide it.
I'm using vuejs to do this but I boiled down the problem to being in setTimeout function. The part where I have the code is fairly complex to post it here, therefore I created a simple POC to demonstrate the problem:
<template>
<div id="app">
<ul v-for="x in 2000" :key="x">
<li #mouseenter="handleMouseEnter(x)" #mouseleave="handleMouseLeave(x)" style="height: 50px;">
Hello
<span style="background-color: red" v-show="showBox[x]">BOX</span>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "App",
components: {},
methods: {
handleMouseEnter(index) {
setTimeout(() => {
let showBox = [...this.showBox];
showBox[index] = true;
this.showBox = showBox;
}, 500);
},
handleMouseLeave(index) {
let showBox = [...this.showBox];
showBox[index] = false;
this.showBox = showBox;
}
},
data() {
return {
showBox: []
};
},
created() {
for (let i = 0; i <= 2000; i++) {
this.showBox[i] = false;
}
}
};
</script>
You can checkout the sandbox here: https://codesandbox.io/s/cold-river-ruz7b
If you hover over from top to bottom in a moderate speed you will realize that even after leaving the li element the red box stays.
I guess the problem lays in the fact that handleMouseEnter is being called with a setTimeout and the handleMouseLeave is not. Therefore, making handleMouseEnter be executed after handleMouseLeave therefore showing the box.
Any light would be highly appreciated here and if a short explanation could be given on why the problem is happening it would be great
Your example seems to operate the opposite way round to the original problem description (the timer is on showing not hiding) but I think I get what you mean.
As you suggest, the problem is that the timer callback is being called after the mouseleave event fires. So the red box does get hidden but shortly thereafter the timer fires and brings it back.
In the example below I have simply cancelled the timer using clearTimeout. In general it might be necessary to store an array of such timers, one for each element, but in this specific example I believe it only makes sense to have one timer active at once so I can get away without an array.
I also moved the initial population of showBox into data. There seemed no reason to use a created hook here.
There's no need to copy the whole array each time, you can just use $set to set the value in the existing array. I haven't changed that in my example.
I would also note that for this particular example you don't need an array to hold all the showBox values. Only one red box can be visible at once so you only need a single property to hold the index of the currently visible box. I haven't changed this in my example as I suspect your real use case is not as straight forward as this.
new Vue({
el: '#app',
methods: {
handleMouseEnter(index) {
this.currentTimer = setTimeout(() => {
let showBox = [...this.showBox];
showBox[index] = true;
this.showBox = showBox;
}, 500);
},
handleMouseLeave(index) {
clearTimeout(this.currentTimer)
let showBox = [...this.showBox];
showBox[index] = false;
this.showBox = showBox;
}
},
data() {
const showBox = [];
for (let i = 0; i <= 2000; i++) {
showBox[i] = false;
}
return {
showBox
};
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<ul v-for="x in 2000" :key="x">
<li #mouseenter="handleMouseEnter(x)" #mouseleave="handleMouseLeave(x)" style="height: 50px;">
Hello
<span style="background-color: red" v-show="showBox[x]">BOX</span>
</li>
</ul>
</div>