How can I make this function watch for changes? - javascript

https://jsfiddle.net/1vm0259x/ I want to have it so when the contents of #b changes it immediately makes the div display. Is that possible? I'm having to get a little hacky because of the limitations of a CMS plugin. I don't know jQuery very well.
Markup
<div id="a">
<span id="b">0 items</span>
</div>
jQuery
$(document).ready(function(){
if($('#b:contains("0 items")')) {
$('#a').css("display", "none");
}
if($('#b:not(:contains("0 items"))')) {
$('#a').css("display", "block");
}
});

The best way to monitor for text changes like this are to find a way to hook into some related and existing event in the browser and then see if the text has changed to what you want.
In the newer browsers (such as IE11+, recent versions of Chrome, Firefox and Safari), you can use a DOM MutationObserver to directly watch to see if the text nodes change.
The mutation callback is called anytime the children of the specified element are changed (children include the text nodes).
Here's some runnable code that watches for a text change in a div in this code snippet:
document.getElementById("go").addEventListener("click", function(e) {
var t = document.getElementById("test");
t.innerHTML = parseInt(t.innerHTML, 10) + 1;
});
var m = new MutationObserver(function(mRecords, obj) {
log("Current text value: " + document.getElementById("test").innerHTML);
}).observe(document.getElementById("test"), {childList: true, characterData: true, subtree: true});
function log(x) {
var d = document.createElement("div");
d.innerHTML = x;
document.body.appendChild(d);
}
<button id="go">Click Me to Change the Text</button><br><br>
<div id="test">1</div><br><br>
If you need support for older version of IE, then the next best thing would be to figure out what existing events in the browser precede the text change and monitor those events. When one of those events occurs, you can then check the text. For example, if the action that triggers the text change always comes after an Ajax call, you can monitor/hook Ajax calls on a system wide basis and then check your text after each Ajax call completes. Since this only ever does anything when other things are already happening in the web page, it's very efficient. Or, if the text only changes after a particular button is clicked or some text field is changed, you can monitor those DOM elements with event listeners.
To suggest how to do that more specifically, we'd need to see the details of your actual circumstance and would have to understand what events in the page lead to the changing text. Such short duration timers can also negatively affect the performance of things like animations running in your page or in other tabs.
It is NOT recommended to use a short duration timer to poll the DOM because this kills mobile battery life and, in fact, mobile browsers will attempt to delay or slow down any long running interval timers you use in order to try to preserve battery life.

On top of MutationObserver, eloquently put by #jfriend00, there is an older API available at our disposal as well by the name DOMSubtreeModified. Combine that with onpropertychange that of Internet Explorer and I believe you get a nice backward compatible change event. Take a look at the snippet below, not thoroughly tested though:
Snippet:
var myDIV=document.getElementById('a');
var mySpan=document.getElementById('b');
var myButton=document.getElementById('button');
var myResult=document.getElementById('result');
if(window.addEventListener){
myButton.addEventListener('click',onButtonClicked,false);
mySpan.addEventListener('DOMSubtreeModified',onSpanModified,false);
}else{
myButton.attachEvent('onclick',onButtonClicked);
mySpan.attachEvent('onpropertychange',onSpanModified);
}
function onButtonClicked(){
//mySpan.innerText=Math.random();
mySpan.innerHTML=Math.random();
}
function onSpanModified(){
myResult.innerHTML=mySpan.innerHTML;
}
<div id="a">
<span id="b">0</span>
</div>
<input id="button" type="button" value="Click Me" />
<span id="result"></span>
Hope this helps in some way though. Apologies if this was not what you were looking for and if I misunderstood your problem completely.

You can't really "watch" something in JavaScript since it's an event-driven language (there are a few exceptions to this rule when it comes to object properties, but it is not something that is commonly useful).
Instead you should set an interval that updates regularly. I used 50 millisecond intervals, you can choose to use whatever interval you like:
function update() {
if($('#b:contains("0 items")').length) {
$('#a').css("display", "none");
}
if($('#b:not(:contains("0 items"))').length) {
$('#a').css("display", "block");
}
}
$(document).ready(function () {
setInterval(update, 50);
});

Related

Replay jQuery function every 5 seconds

I want to replay my jquery function ChangeStats() every 5 seconds, it's currently doing sod all.
function ChangeStats() {
$('body').find('.admin-stats-big-figures-hidden').fadeIn(500);
setTimeout(function() {
$('body').find('.admin-stats-big-figures').fadeOut(500);
}, 500);
}
$(document).ready(function(){
setInterval(ChangeStats, 5000);
})();
Yes I have got the right class names.
No I haven't used underscores in my HTML.
I think it's something to do with my use of "find()", once the DOM has loaded and the function is set is it meant to traverse up the DOM tree instead of down?
EDIT:
Updated code, still not working.
HTML:
<span class="admin-stats-big-figures">%productCount%</span>
<span class="admin-stats-big-figures-hidden">hey</span>
Ok, I am going to go out on a limb and make several assumptions here; one is that you wish to cycle between two elements repeatedly, another is that you are using $(this) in the context of the window rather than a containing element. If either of these are incorrect then the following solution may not be suitable. However, let's give this a shot, eh?
1) You need to use setInterval rather than setTimeout to create a repeating call. You can of course "chain" your timeouts (ie: call the succeeding timeout from the code of the current timeout). This has some benefits in certain situations, but for now let's just assume you will use intervals rather than timeouts.
2) You call the find() jQuery method every time, which is a little unnecessary, especially if you will be repeating the actions so one idea would be to cache the lookup. If you are going to do that a custom object would be more suitable than separate global variables.
3) Some flexibility in terms of starting and stopping the animation could be provided. If we use a custom object as mentioned in (2) then that can easily be added.
4) You are using fadeIn and fadeOut, however if you wish the items to cycle then fadeToggle may be your best solution as it will simply allow you to do exactly that, toggle, without needing to check the current opacity state of the element.
5) Finally in my example I have provided a little extra "padding HTML" in order for the example to look good when run. Fading in jQuery will actually set the faded item to a CSS display of "none" which results in the content "jumping about" in this demo, so I have used some div's and a couple of HTML entity spaces to keep the formatting.
Ok, after all that here is the code..
// your custom animation object
var myAnim = {
// these will be cached variables used in the animation
elements : null,
interval : null,
// default values for fading and anim delays are set to allow them to be optional
delay : { fade: 500, anim: 200 },
// call the init() function in order to set the variables and trigger the animation
init : function(classNameOne, classNameTwo, fadeDelay, animDelay) {
this.elements = [$("."+classNameOne),$("."+classNameTwo)];
// if no fade and animation delays are provided (or if they are 0) the default ones are used
if (animDelay) this.delay.anim = animDelay;
if (fadeDelay) this.delay.fade= fadeDelay;
this.elements[0].fadeOut(function(){myAnim.start()});
},
// this is where the actual toggling happens, it uses the fadeToggle callback function to fade in/out one element once the previous fade has completed
update : function() {
this.elements[0].fadeToggle(this.delay.anim,function(el,delay){el.fadeToggle(delay)}(this.elements[1],this.delay.anim));
},
// the start() method allows you to (re)start the animation
start : function() {
if (this.interval) return; // do nothing if the animation is currently running
this.interval = setInterval(function(){myAnim.update()},this.delay.fade);
},
// and as you would expect the stop() stops it.
stop : function () {
if (!this.interval) return; // do nothing if the animation had already stopped
clearInterval(this.interval);
this.interval = null;
}
}
// this is the jQuery hook in order to run the animation the moment the document is ready
$(document).ready(
function(){
// the first two parameters are the two classnames of the elements
// the last two parameters are the delay between the animation repeating and the time taken for each animation (fade) to happen. The first one should always be bigger
myAnim.init("admin-stats-big-figures","admin-stats-big-figures-hidden",500,200);
}
);
OK, so now we need the HTML to compliment this (as I say I have added a little formatting):
<div><span class="admin-stats-big-figures">One</span> </div>
<div><span class="admin-stats-big-figures-hidden">Two</span> </div>
<hr/>
<input type="button" value="Start" onclick="myAnim.start()"/> | <input type="button" value="Stop" onclick="myAnim.stop()"/>
I have also provided buttons to stop/start the animation. You can see a working example at this JSFiddle - although the stop/start buttons are not working (presumably something specific to JSFiddle) they do work when in context though.
Here im gonna just replace your $(this). and maybe it'll work then.. also using callback.
function ChangeStats() {
$('body').find('.admin-stats-big-figures-hidden').fadeIn(500, function() {
$('body').find('.admin-stats-big-figures').fadeOut(500);
});
}
$(document).ready(function(){
setTimeout('ChangeStats()', 5000);
});

Run javascript on text change

What I am looking for is slightly subjective, but I am sure there is a better way to do this.
I am looking for a better way to perform javascript while a user is typing content into either a textarea or input box on a website. For instance, sites such as Google Docs are capable of saving changes to documents almost instantly without noticeable performance degradation. Many sites however use a bit of jQuery that might look like the following:
$("#element").on("keyup", function() { /* Do something */ });
This works fine for simple things like autocomplete in search boxes, but performance becomes a nightmare once you have any sizable corpus for it to have to deal with (or if a user types fast, yikes).
In trying to find a better way to analyze/save/what-have-you text as the user is typing, I started to do something like this:
var changed = false;
$("#element").on("keyup", function() { changed = true });
setInterval(function() { if(changed) { /* Do something */ changed = false; } }, 1000);
It seems to alleviate laggy or delayed text input, but to me it seems like a less than elegant solution.
So back to my question, is there a better way to have javascript execute when a corpus has been changed? Is there a solution outside of using intervals?
Thanks.
There is a jQuery plugin that does pretty much what you did.
Your example will be transformed into
$("#element").on("keyup", $.debounce(1000, function() { /* Do something */ }));
The code will execute after a user is not pressing any keys for 1000ms.
I have found a very good solution for this. This code will check whether the content has been changed and based on that it will save it otherwise the save functionality will not be executed !
Check out this demo JSFIDDLE
Here is the code :
HTML :
Content:<br>
<br>
(type some text into the textarea and it will get saved automatically)
<textarea rows="5" cols="25" id="content"></textarea>
<br>
<span id="sp_msg_saved" style="background-color:yellow; display:none">Content is saved as draft !</span>
JS:
var old_content = "";
function save_content()
{
var current_content = $('#content').val();
//check if content has been updated or not
if(current_content != old_content)
{
alert('content is updated ! Save via ajax');
old_content = current_content;
$('#sp_msg_saved').show(100);
$('#sp_msg_saved').fadeOut(3000);
}
}
setInterval(save_content,3000);
You can increase or decrease the amount of time for the save function to call by altering the values in setInterval function. Put the code for saving the content via ajax, that will save the current user content into your DB, I haven't included that one...
You can make your own little delay by using the window.setTimeout-Function:
var IntervalId = null;
function saveEdits(){
//Doing your savings...
}
$('input').keyup(function(){
if (IntervalId){
window.clearTimeout(IntervalId);
IntervalId = null;
}
IntervalId = window.setTimeout(function(){
saveEdits();
}, 3000);
});

Using classes to observe a page

I am writing a script that needs to detect elements added to a Web page, for example events rendered in a calendar (div tags). I don't care about elements that are removed. There should be at most 20-30 such elements on the page.
My idea - short and easy code - is to use a specific class ("myName") to brand elements already in the page. At regular intervals I would poll the page:
// Get all divs in the calendar:
var allDivsCount=myCalendar.querySelectorAll("div").length;
// Get already branded divs
var oldDivsCount=myCalendar.querySelectorAll("div.myName").length;
if (allDivsCount > oldDivsCount) {
// brand the new divs and do stuff
}
Is this a good practice, or is there a better way to do it? Are there libraries that already have such logic implemented?
I am trying to avoid DOMNodeInserted as some browsers don't support it and it is deprecated (due to performance issues, from what I've read).
I know that you're against DOMNodeInserted but I'll added it anyways for options (in case you're not supporting older version of IE).
I blogged about this a while back but it seems like the same solution still applies today (but like I said, depending on the browsers you currently support).
Example:
$(document.body).on('DOMNodeInserted', function (e) {
if (e.currentTarget.toString() === 'HTMLBodyElement') {
console.log(e);
}
});
This triggers any changes within the body not just the body itself.
$('#context').append($('<div />')); // triggers the event above
If you can intercept a DOM change (an AJAX call for instance) and create a global callback function, that would be ideal instead of the setInterval option.
var globalCallback = function() {
/** Do something when the DOM changes */
};
/** Global AJAX event to watch for all AJAX complete within the body */
$('body').ajaxComplete(globalCallback);
This is just one example (AJAX callbacks) of course.

Overriding yellow autofill background

After a long struggle, I've finally found the only way to clear autofill styling in every browser:
$('input').each(function() {
var $this = $(this);
$this.after($this.clone()).remove();
});
However, I can’t just run this in the window load event; autofill applies sometime after that. Right now I’m using a 100ms delay as a workaround:
// Kill autofill styles
$(window).on({
load: function() {
setTimeout(function() {
$('.text').each(function() {
var $this = $(this);
$this.after($this.clone()).remove();
});
}, 100);
}
});
and that seems safe on even the slowest of systems, but it’s really not elegant. Is there some kind of reliable event or check I can make to see if the autofill is complete, or a cross-browser way to fully override its styles?
If you're using Chrome or Safari, you can use the input:-webkit-autofill CSS selector to get the autofilled fields.
Example detection code:
setInterval(function() {
var autofilled = document.querySelectorAll('input:-webkit-autofill');
// do something with the elements...
}, 500);
There's a bug open over at http://code.google.com/p/chromium/issues/detail?id=46543#c22 relating to this, it looks like it might (should) eventually be possible to just write over the default styling with an !important selector, which would be the most elegant solution. The code would be something like:
input {
background-color: #FFF !important;
}
For now though the bug is still open and it seems like your hackish solution is the only solution for Chrome, however a) the solution for Chrome doesn't need setTimeout and b) it seems like Firefox might respect the !important flag or some sort of CSS selector with high priority as described in Override browser form-filling and input highlighting with HTML/CSS. Does this help?
I propose you avoiding the autofill in first place, instead of trying to trick the browser
<form autocomplete="off">
More information: http://www.whatwg.org/specs/web-forms/current-work/#the-autocomplete
If you want to keep the autofill behaviour but change the styling, maybe you can do something like this (jQuery):
$(document).ready(function() {
$("input[type='text']").css('background-color', 'white');
});
$(window).load(function()
{
if ($('input:-webkit-autofill'))
{
$('input:-webkit-autofill').each(function()
{
$(this).replaceWith($(this).clone(true,true));
});
// RE-INITIALIZE VARIABLES HERE IF YOU SET JQUERY OBJECT'S TO VAR FOR FASTER PROCESSING
}
});
I noticed that the jQuery solution you posted does not copy attached events. The method I have posted works for jQuery 1.5+ and should be the preferred solution as it retains the attached events for each object. If you have a solution to loop through all initialized variables and re-initialize them then a full 100% working jQuery solution would be available, otherwise you have to re-initialize set variables as needed.
for example you do: var foo = $('#foo');
then you would have to call: foo=$('#foo');
because the original element was removed and a clone now exists in its place.

How do update a page element while inside a long running event?

I've got a long running method -- and I want to indicate to the user that an operation is underway. This is NOT and ajax call, so I can't use the common pattern I've used in the past to display, say a spinner, before the ajax event, then hiding it on success for example.
In my case -- I'm not making an ajax call, I'm instead doing some very heavy DOM manipulation.
I had a test example on jsFiddle.net -- would love to learn how to capture the event. At the moment, my "wait-message" div updates at the same exact time when my operation completes which is much too late :(
Complete sample code is here: http://jsfiddle.net/rsturim/97hrs/6/
Javascript (jQuery)
$(document).ready(function() {
$("#link-action").click(function(e) {
$("#wait-message").text("starting ...");
var count = longRunningMethod(1000000000);
$("#result").text(count);
$("#wait-message").text("completed.");
});
var longRunningMethod = function(countUpTo) {
var i = 0;
while (i <= countUpTo) {
i++;
}
return i;
};
});
HTML:
<div id="wait-message">
push button please
</div>
<hr />
<button id="link-action">Run Operation</button>
<hr />
<h1>Results:</h1>
<div id="result"> </div>
Here is a solution. I'm not sure if it works in all browsers, you may want to test it out in several, but I think it does:
$(document).ready(function() {
$("#link-action").click(function(e) {
$("#wait-message").text("starting ...");
// Stuff to do after a render
setTimeout(function(){
var count = longRunningMethod(1000000000);
$("#result").text(count);
$("#wait-message").text("completed.");
}, 0);
});
var longRunningMethod = function(countUpTo) {
var i = 0;
while (i <= countUpTo) {
i++;
}
return i;
};
});
Basically, the browser won't render any changes until a script finishes executing. That allows you to do things like:
Hide all divs with a certain class
Show one of those divs
In a row and the browser will never render the div that is being shown as hidden, so you won't get weird flickers or things moving around on the page.
Using setTimeout like I did, the anonymous click handler will finish executing, the browser will re-render, the the anonymous function in the setTimeout will run (immediately after the render since there is no actual delay).
Use setTimeout or setInterval instead of your while loop; a sub-second delay like 15ms should be enough to prevent your window freezing / UI locking.

Categories

Resources