I'm trying to make a custom "nickname-highlighter" just for me, on a distant and javascript based webchat. Clearly, I want that each time my nickname appears in the chat, a sound is played.
In this chat there is a #TheChatDiv in which a new .NewChatText is added each time a user write something new.
So far I've tried this :
$('#TheChatDiv').bind("DOMSubtreeModified",function(){
// Do things, like play the song of your people.
});
Which actually works well, except that i'm unable to extract the text of the chat posted. The thing is that I also need to read the text posted, to detect my nickname in it and make my code behave like it needs to.
Any idea how I could do that ?
You should generally create an event or fire a function whenever a message is inserted into the chat element and then search the added message for whatever you're looking for, however if that's not an option you'd generally be better of using mutation observers instead of the deprecated DOMSubtreeModified event, something like this
var observer = new MutationObserver(function(mutations) {
$.each(mutations, function(_, mutation) {
$.each(mutation.addedNodes, function(_, node) {
if ( $(node).text().indexOf('Cyc') != -1) {
// Play sound for Cyc
}
});
});
});
observer.observe(
$('#TheChatDiv').get(0),
{ attributes: true, childList: true, characterData: true }
);
FIDDLE
As for me the better way is to inject highlighting logic directly in your receiveMessage function where NewChatText is being created and appended to the TheChatDiv.
I assume you have something like:
function receiveMessage(message) {
var chat = $('#TheChatDiv');
$('<div>')
.text(message)
.addClass('NewChatText')
.appendTo(chat);
if (message.indexOf('YourNickName') {
// Play sound, highlight etc.
}
}
Try this:
var nickname = $('.NewChatText .nickname').text();
if (nickname == 'YOURNAME') {
// Insert your code here, the autor of .NewChatText is 'YOURNAME'
}
If this isn't the response you are searching for, you should describe your problem more in detail.
Related
I'm writing a userscript for a website where occasionally a coin drop will appear on-screen and only a limited number of people on the site can claim it. My script detects when a new coin drop appears based on the length of the page element "coindrop-status", and when a new drop is detected it auto-clicks the prompt to open the initial drop splash screen, then auto-clicks the actual grab button within that splash screen.
The problem is that because the first auto-click is within a for-loop, it continuously spam-clicks to open the splash screen until the drop has been fully claimed and the loop breaks, preventing stage 2 of the auto-click function from clicking the actual button to grab the drop within the splash screen.
I've tried to solve this problem many times now but because coin drops are so infrequent, it's a massive pain to debug - how can I change my script so that when a drop is detected, the splash screen is only clicked once (so that it stays open) before clicking the grab button within it repeatedly?
var newDrop = false;
function dropCheck() {
clearInterval(scanFreq);
var coinLength = document.getElementsByClassName("coindrop-status").length - 1;
for(var i = coinLength; i >= 0; i--) {
if(document.getElementsByClassName("coindrop-status")[i].innerText == "Grab") {
newDrop = true;
document.getElementsByClassName("coindrop-status")[i].click();
setTimeout(function() {document.elementFromPoint(1250, 840).click()},1000);
setTimeout(function() {document.elementFromPoint(1250, 840).click()},1000);
}
}
if(newDrop) {
newDrop = false;
setTimeout(dropCheck,800);
} else {
setTimeout(dropCheck,100);
}
}
var scanFreq = setInterval(dropCheck,800);
Admittedly, clicking the grab button multiple times is probably overkill, but I figure at least it guarantees that the coin drop actually gets grabbed.
Forgive any bad coding practice I may have integrated into this script; I'm still learning to program. I'm sure there are much, much more elegant ways to accomplish the goal of this userscript, so if you have any other suggestions please feel free to give some constructive criticism as well.
First, you don't need to loop like this. Like at all. There is a class called MutationObserver which will call a callback only when elements in the DOM changed. My approach when waiting for a specific element to be added is basically this:
/**
* Waits for a node to start to appear in DOM. Requires a CSS query to fetch it.
* #param {string} query CSS query
* #returns {Promise<HTMLElement>}
*/
function awaitElement(query) {
return new Promise((resolve, reject)=>{
const targetNode = document.body;
const testNow = targetNode.querySelector(query);
if(testNow != null) {
resolve(testNow);
return;
}
const observer = new MutationObserver((mutationList, observer) => {
const result = targetNode.querySelector(query);
if(result != null) {
observer.disconnect();
resolve(result)
}
});
observer.observe(targetNode, { attributes: false, childList: true, subtree: true });
});
}
Used as:
(async () => {
while(true) {
let myElm = await awaitElement(".coindrop-status");
// handle the drop here. Note that you must remove the element somehow, otherwise you will get an endless loop
}
})();
You will probably need to adjust my function a little, so that you can make it ignore coin drops you already handled. One way to handle this without any modification is add a custom class name to the handled coin divs, and then use the not selector when searching for more.
I also do not think it's really wise to use element from point. Doesn't the popup for claiming the coin have some selector as well?
As an exercise I have to do a little online bike reservation app. This app begins with a header which explains how to use the service. I wanted this tutorial be optional so I wrote a welcome message in HTML and if the user doesn't have a var in his cookies saying he doesn't want to see the tutorial again, the welcome message is replaced by a slider that displays the information.
To achieve that is fetch a JSON file with all the elements I need to build the slider (three divs : the left one with an arrow image inside, the central one where the explanations occur and the right one with another arrow). Furthermore I want to put "click" events on the arrows to display next or previous slide. However, when I do so, only the right arrow event works. I thought of a closure problem since it is the last element to be added to the DOM that keeps its event but tried many things without success. I also tried to add another event to the div that works ("keypress") but only the click seems to work. Can you look at my code give me an hint on what is going on?
Here is the init function of my controller:
init: function() {
var load = this.getCookie();
if(load[0] === ""){
viewHeader.clearCode();
var diapoData = ServiceModule.loadDiapoData("http://localhost/javascript-web-srv/data/diaporama.json");
diapoData.then(
(data) => {
// JSON conversion
modelDiapo.init(data);
// translation into html
controller.initElementHeader(modelDiapo.diapoElt[0]);
controller.hideTuto();
}, (error) => {
console.log('Promise rejected.');
console.log(error);
});
} else {
viewHeader.hideButton();
controller.relaunchTuto();
}
}
There is a closer look at my function translating the JSON elements into HTML and adding events if needed :
initElementHeader: function(data){
data.forEach(element => {
// Creation of the new html element
let newElement = new modelHeader(element);
// render the DOM
viewHeader.init(newElement);
});
}
NewElement is a class creating all I need to insert the HTML, viewHeader.init() updates the DOM with those elements and add events to them if needed.
init: function(objetElt){
// call the render
this.render(objetElt.parentElt, objetElt.element);
// add events
this.addEvent(objetElt);
},
Finally the addEvent function:
addEvent: function(objet){
if(objet.id === "image_fleche_gauche"){
let domEventElt = document.getElementById(objet.id);
domEventElt.addEventListener("click", function(){
// do things
});
}
else if(objet.id === "image_fleche_droite"){
let domEventElt = document.getElementById(objet.id);
domEventElt.addEventListener("click", function(){
// do stuff
});
};
},
I hope being clear enough about my problem. Thank You.
Ok, I found the problem, even if the element was actually created, it somehow stayed in the virtual DOM for some time, when the "getElementById" in "addEvent" was looking for it in the real DOM it didn't find the target and couldn't add the event. This problem didn't occur for the last element since there was nothing else buffering in the virtual DOM.
On conclusion I took out the function adding events out of the forEach loop and created another one after the DOM is updated to add my events.
Well, Hi everyone for my first post here :)
I will try to be as clear as possible, because the problem encountered is just...
(English is not my native language, sorry if it's not perfect)
I'm working on a website, after a while of user's inactivity, a Chatbox appears. It's not present in the DOM at beginning and doesn't appear with a simple CSS class. It creates a new block div in the DOM with the ID "mychatcontainer", so to catch it, I'm using a mutation observer, below my code (it's not perfect, I'm not done with it, but it do the work I need for the moment :))
var target = document.getElementById('page13621');
var observer = new MutationObserver(function(mutations){
mutations.forEach(function(mutation){
if (mutation.addedNodes[0].id === "mychatcontainer") {
console.log(mutation.type);
console.log(mutation.addedNodes);
console.log(mutation.addedNodes[0]);
console.log(document.getElementById("mychatcontainer"));
console.log(this.document.getElementById("mychatcontainer"));
}
});
});
var config = { attributes: false, childList: true, characterData: true, subtree: true };
observer.observe(target, config);
Here is the result of console.log
As you can see, it only shows
<div id="mychatcontainer"></div>
without the possibilty to access to children and so the part which is interresting me.
Whereas if I do it manually with my browser console I get all the information and the access to children !!
Manual result
So first question here, am I missing a point ?!
The second thing on which I want to catch your attention is :
When yesterday I tried my code on my personnal Network, console.log were having an access to children but only on first try, then when I relaod the page, the same problem occurs, No access to children
So second question, what is that ? So crazy...
Thanks for your attention, hope someone could help me :)
Ok guys, I found a solution even if I don't like it, I guess that in my case I don't have the choice..
As you can see below I add a setTiemout function with 1.2 seconds, I can't reduce that time unfortunatly, but at the end I have access to children's elements.
var target = document.getElementById('page13621');
var observer = new MutationObserver(function(mutations){
mutations.forEach(function(mutation){
if (mutation.addedNodes[0].id === "#ID_of_created_block") {
//add a setTiemout function
myFunction();
}
});
});
var config = { attributes: false, childList: true, characterData: true, subtree: true };
observer.observe(target, config);
var myFunction = function() {
setTimeout(function(){
// Here add your code, in my exemple it was my console.log
}, 1200);
}
Can someone confirm me that it exist a time of synchronization between the existing DOM et the created one ? Which is explaining this 1.2 seconds delay.
A tip for devs, don't create new elements in the DOM during user experience. Create all at the beginning then hide or show it. Like that the access to children is without delay.
Thanks for your attention, everything to improve my solution is welcome :)
I'm making a chrome extension to remove certain posts from Facebook feeds. I'm using jQuery, and I've got something like this:
$(document).ready(function() {
var post = $('.fbTimelineUnit');
post.remove();
}
);
This removes the first posts that load, but others just autoload and occupy the space the removed posts did. Say I want to remove all posts as they come in. How do I do this?
Thanks!
You could use a MutationObserver to listen for changes (i.e. child additions) in the posts' parent element. (I don't know much about FB, so you have to figure out the details yourself.)
Somethin like this:
var postsContainer = ...
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
// ...if it's a new post delete/remove/hide it somehow
});
});
observer.observe(postsContainer, {childList: true, subtree: true });
// When done:
observer.disconnect();
To get you started, here are some (simple to not so simple) examples of using a MutationObserver in an extension:
Example 1
Example 2
Example 3
I'm trying to create an algorithm for an event based editor like in StarCraft 2 Editor that can support:
Create UI
Play sounds
Handle keyboard/mouse inputs
Display messages
Button(or some referenced UI object) is pressed etc.
Pretty much the same thing as in StarCraft 2 Editor (of course not the 3D stuff too)
So far I'm thinking to use JSON , add every event in an object and then loop through them and create an event using the addEventListener() method.
The JSON Events Object(of course it will be created by the user in the editor with no programming):
var Events={
//your event's names here
onReady:{ //on page ready to manipulate
displayMessage:{//just a simple popup
text:"Hello user!",
title:"Welcome!",
type:"normal",
},
createButton:{ //creates a buton on the screen
text:"Click me!",
id:"myButton"
}
},
onClick:{
id:"myButton" ,//the id of the button we just created
actions:{ //the actions applied after we click the button
displayMessage:{//just a simple popup
text:"You pressed me!",
title:"Button",
type:"error",//show the message as an error
}
}
}
}
I found some softwares (GameMaker,Construct 2,GameDevelop) that have an event based editor if you would like to get an idea about what I'm talking about (if you don't already know about StarCraft 2 Editor)
My question is:
What is the best algorithm that I can use to achieve this?
Sounds like a job for jQuery UI.
When the user creates a custom area in your editor all it's attributes are stored inside an object (that you can save as JSON) that would then be applied to a div as param when loading the map (using html-attributes.
function create_areas(areas){
var map = $('#map_area');
for(var i=0;i<areas.length;i++){
map.append($('<div>', area[i].params));
}
}
whereas params would look something like this:
params = {
width: 100,
height: 200,
....
mousedown: function(){ play_music('hello'); },
keydown: function(e){ alert('you pressed ' + e.keyCode; }
}
also the jQuery UI tools like draggable and resizeable should ease up building your editor.
I'd model this more after backbone's event system:
events: {
'click selector': handler,
'mouseover selector': handler2,
...
}
Handlers can be any javascript function, this would allow you to create a bunch of pre-defined functions like displayMessage.
Then you could curry your own handlers, which would allow your users to specify configuration if they need it.
Example:
var events = {
'click element': displayMessage({
text:"Hello user!",
title:"Welcome!",
type:"normal",
}),
'mouseover pizza': createButton({...})
}
function displayMessage(options) {
var options = options;
return function() {
//display message logic
}
}
Then you can supply a compose function among other helpers (look up promises perhaps?) to combine your functions together:
var events = {
'click element': compose(
displayMessage({
text:"Hello user!",
title:"Welcome!",
type:"normal",
}),
createButton({})
),
'mouseover pizza': createButton({...})
}
This could work out?
Caveat: it might be better if events was an array that contained objects. That way you can have multiple click handlers on some selector without collisions.
The way I see this there are really severall choices you need to make. I would, although I prefer JSON as a data construct not limit myself to this subset of an actuall programming language. And engener this the other way around.
You have events, handlers and options. Where a option, or better a option list is the user inputed data, the handlers are the actual action, and the events are triggers to set some action off.
If you read this carefully you will notice this is the exact description of the basic structure of most jQuery-Scripts or Event-Driven Software in generall. Only the users options in jQuery are (since it is a DOM Framework) most often the context of a single DOM-Element. So, here we are and I would suggest to simply borrow the theorie behind this and make use of promisses wich make a very clear and great way to generate code!
So my call to any event chain would look like this.
...when(chainObject['event'])
.then(function(event) {
//call handler
handlers[chainObject[selectedHandler]].call(event.context, chainObject['options']);
//apply next element(s) in chain, this is the current promise
appendNextElement(chainObject['followingHandlers'], this);
})...
Notice how apply makes it easy for you to change the environement and in turn behaviour of any hanlder based on what the user and event did. And promisses make error handling very easy!
This of course applies to only one node in your chain. So what should a data structure look like to let you generate this kind of code?
One node in your structure would look like this:
{
event: 'click',
selectedHandler: 'sohwText',
options: {
'text': 'helloWorld'
},
followingChain: {...OTHER HANDLERS....}
}
The important thing to notice is that like a good structured functional programm you are looking at a tree and not at a simple list of events. So every actual DOM Element holds many of these
var eventTree = {
'.someButton': [..Handlers of this button...],'
'.someOtherButton': [..Handlers of the other button...],
}
And there we go. You have a context (the button), a event, user input and a handler.
The resulting app should not only work, but will be styled for any experienced JavaScript-Programmer to expand or mod.