How would I make this code smaller? Maybe a toggle, but people were saying this was easily done in jQuery. But the problem is that I am not a fan of using jQuery for just one thing in my code.
function open() {
document.getElementById('message').style.display='block';
document.getElementById('fade').style.display='block';
}
function close() {
document.getElementById('message').style.display='none';
document.getElementById('fade').style.display='none';
}
DRY it up.
var b='block',h='none',m='message',f='fade';
function s(i,d){document.getElementById(i).style.display=d}
function open(){s(m,b);s(f,b)}
function close(){s(m,h);s(f,h)}
With the whitespace and proper variable names (to be passed to a minifier), this looks like:
var show = 'block', hide = 'none', message = 'message', fade = 'fade';
function setStyle(id, display) {
document.getElementById(id).style.display=display;
}
function open() {
setStyle(message, show);
setStyle(fade, show);
}
function close() {
setStyle(message, hide);
setStyle(fade, hide);
}
There are some best-practices which don't relate to the question but are worth considering if your project grows beyond this trivial situation:
Use a minifier. My favorite is uglifyjs. This allows you to use meaningful variable names in your unminified code (like the second example). The minifier will output code more like (but probably even better than) the first example. Even with a minifier, keep thinking about what it can and cannot do - creating a private shortcut to a long public API like document.getElementById can aid the minification if you use that API frequently. Look at the minified code to make sure there isn't something you can do to optimize it.
Separate your javascript into .js modules that are loaded separate from the page and asychrounously, if possible.
Manage all your static assets (like the .js modules) so they have a long cache timeout - use the Expires: http header. Then change their URLs when they actually change. This way, clients can cache them indefinitely until you change them & then the client will immediately fetch a new version.
Put discrete modules inside function wrappers, so that your variables don't conflict with other pieces of code - either your own or 3rd party modules. If you want to make a variable public, do it explicitly: window.pubvar =
var message = document.getElementById('message'),
fade = document.getElementById('fade');
function open() {
message.style.display = fade.style.display = 'block';
}
function close() {
message.style.display = fade.style.display = 'none';
}
Or:
function toggle() {
var message = document.getElementById('message'),
fade = document.getElementById('fade'),
currentdisplay = getComputedStyle(message, null)['display'];
if(currentdisplay == 'block' || currentdisplay == 'inline')
message.style.display = fade.style.display = 'none';
else
message.style.display = fade.style.display = 'block'; /* or inline */
}
Or:
function toggle() {
var currentdisplay = getComputedStyle(arguments[1], null)['display'],
i,
newdisplay;
if(currentdisplay == 'block' || currentdisplay == 'inline')
newdisplay = 'none';
else
newdisplay = 'block';
for(i = 0; i < arguments.length; i++)
arguments[i].style.display = newdisplay;
}
var message = document.getElementById('message'),
fade = document.getElementById('fade');
toggle(message, fade); /* hide */
toggle(message, fade); /* show */
document.body.onclick = function(){
toggle(message, fade);
}
Toggle Example:
http://jsfiddle.net/djHTq/
var toggle = function(doc){
var $ = doc.getElementById, message = $('message'), fade = $('fade'), open = true;
return function(){
var display = open ? 'none' : 'block';
message.style.display = display;
fade.style.display = display;
open = !open;
}
}(document);
toggle(); // Hide both elements
toggle(); // Show both elements. Rinse and repeat.
Avoids polluting global scope:
(function() {
var msgstl = document.getElementById('message').style,
fdestl = document.getElementById('fade').style;
window.open = function() {msgstl.display = fdestl.display = "block";};
window.close = function() {msgstl.display = fdestl.display = "none";};
})();
One thing is you can create a helper function for setting styles on elements. This would be useful in cases where you need to set many different elements.
function setStyle(element, style, value) {
document.getElementById(element).style[style] = value;
}
function open() {
setStyle('message', 'display', 'block');
setStyle('fade', 'display', 'block');
}
function close() {
setStyle('message', 'display', 'none');
setStyle('fade', 'display', 'none');
}
You may also want to set the elements to variables if you work with the elements often enough.
var message = document.getElementById('message'), fade = ...
Related
I am using LiveChat on my website and trying to show a div if no agents are available, using their guide here
Javascript
LC_API.on_after_load = function() {
if (LC_API.agents_are_available()) {
$("#ChatLink2").hide();
} else {
$("#ChatLink2").show();
}
};
HTML
<div class='ChatLink2' id='ChatLink2'>Currently Unavailable</div>
However, the div ChatLink2 is not showing when no agents are available
It appears to be race conditions with both LiveChat and JQuery. Below checks for LC availability and uses vanilla JS instead of jQuery.
Also included state change callback if agents go offline after init.
var waitForLC = setInterval(function () {
if (window.LC_API === undefined) {
return;
}
clearInterval(waitForLC);
var showUnavailableStatus = function(show){
var statusDiv = document.getElementById("ChatLink2");
var style = show ? "block" : "none";
statusDiv.style.display = style;
}
LC_API.on_after_load = function() {
if (LC_API.agents_are_available()) {
showUnavailableStatus(false);
} else {
showUnavailableStatus(true);
}
};
LC_API.on_chat_state_changed = function(data) {
showUnavailableStatus(data.state==="offline");
};
}, 100);
I'm using the Microsoft Translation Widget, which I'd like to use to automatically translate a webpage without user interaction.
The problem is, I can't get rid of the widget that keeps popping up or hide it on document.ready because the CSS and JS get loaded from Microsoft's own script in the widget!
Does anyone know a way around this? I've looked everywhere and cannot find a solutuion for this.
Whoa, after some time playing around with that, I've finally achieved what you want.
It's kindda ugly, because of some needed workarounds, but it works, take a look at the fiddle.
The steps were:
Firstly, we must override the default addEventListener behavior:
var addEvent = EventTarget.prototype.addEventListener;
var events = [];
EventTarget.prototype.addEventListener = function(type, listener) {
addEvent.apply(this, [].slice.call(arguments));
events.push({
element: this,
type: type,
listener: listener
});
}
Then, we create a helper function removeEvents. It removes all the event listeners of an element.
var removeEvents = function(el, type) {
var elEvents = events.filter(function(ev) {
return ev.element === el && (type ? ev.type === type : true);
});
for (var i = 0; i < elEvents.length; i++) {
el.removeEventListener(elEvents[i].type, elEvents[i].listener);
}
}
When creating the script tag, in the way Microsoft says:
var s = d.createElement('script');
s.type = 'text/javascript';
s.charset = 'UTF-8';
s.src = ((location && location.href && location.href.indexOf('https') == 0) ? 'https://ssl.microsofttranslator.com' : 'http://www.microsofttranslator.com') + '/ajax/v3/WidgetV3.ashx?siteData=ueOIGRSKkd965FeEGM5JtQ**&ctf=True&ui=true&settings=Manual&from=';
var p = d.getElementsByTagName('head')[0] || d.dElement;
p.insertBefore(s, p.firstChild);
We must add a load event listener to that script, and the code below is fully commented:
s.addEventListener('load', function() {
// when someone changes the translation, the plugin calls the method TranslateArray
// then, we save the original method in a variable, and we override it
var translate = Microsoft.Translator.TranslateArray;
Microsoft.Translator.TranslateArray = function() {
// we call the original method
translate.apply(this, [].slice.call(arguments));
// since the translation is not immediately available
// and we don't have control when it will be
// I've created a helper function to wait for it
waitForTranslation(function() {
// as soon as it is available
// we get all the elements with an attribute lang
[].forEach.call(d.querySelectorAll('[lang]'), function(item, i) {
// and we remove all the mouseover event listeners of them
removeEvents(item, 'mouseover');
});
});
}
// this is the helper function which waits for the translation
function waitForTranslation(cb) {
// since we don't have control over the translation callback
// the workaround was to see if the Translating label is visible
// we keep calling the function, until it's hidden again
// and then we call our callback
var visible = d.getElementById('FloaterProgressBar').style.visibility;
if (visible === 'visible') {
setTimeout(function() {
waitForTranslation(cb);
}, 0);
return;
}
cb();
}
});
Update 1
After re-reading your question, it seems you want to hide all the widgets at all.
So, you must add the following code as soon as the translation is got:
waitForTranslation(function() {
document.getElementById('MicrosoftTranslatorWidget').style.display = 'none';
document.getElementById('WidgetLauncher').style.display = 'none';
document.getElementById('LauncherTranslatePhrase').style.display = 'none';
document.getElementById('TranslateSpan').style.display = 'none';
document.getElementById('LauncherLogo').style.display = 'none';
document.getElementById('WidgetFloaterPanels').style.display = 'none';
// rest of the code
});
I've created another fiddle for you, showing that new behavior.
Update 2
You can prevent the widget showing at all by adding the following CSS code:
#MicrosoftTranslatorWidget, #WidgetLauncher, #LauncherTranslatePhrase, #TranslateSpan, #LauncherLogo, #WidgetFloaterPanels {
opacity: 0!important;
}
And you can even prevent the before-translated text being showed, by hiding the document.body by default, and then showing it when the page is fully translated:
(function(w, d) {
document.body.style.display = 'none';
/* (...) */
s.addEventListener('load', function() {
var translate = Microsoft.Translator.TranslateArray;
Microsoft.Translator.TranslateArray = function() {
translate.apply(this, [].slice.call(arguments));
waitForTranslation(function() {
/* (...) */
document.body.style.display = 'block';
});
}
});
});
Take a look at the final fiddle I've created.
For me, this was the solution:
on your < style > section add this class
.LTRStyle { display: none !important }
Also, if you are invoking the translation widget this way:
Microsoft.Translator.Widget.Translate('en', lang, null, null, TranslationDone, null, 3000);
then add this to your callback (in this example is TranslationDone) function:
function TranslationDone() {
Microsoft.Translator.Widget.domTranslator.showHighlight = false;
Microsoft.Translator.Widget.domTranslator.showTooltips = false;
document.getElementById('WidgetFloaterPanels').style.display = 'none';
};
I've a task of building a modal prompt, that's been simple so far describing its methods like "show", "hide" when it comes down just to DOM manupulation.
Now comes the hardship for me... Imagine we have a page on which there are several immediate calls to construct and show several modals on one page
//on page load:
$("browser-deprecated-modal").modal();
$("change-your-city-modal").modal();
$("promotion-modal").modal();
By default my Modal (and other libraries i tried) construct all of these modals at once and show them overlapping each other in reverse order -
i.e $(promotion-modal) is on the top, while the
$("browser-deprecated-modal") will be below all of them. that's not what i want, let alone overlapping overlays.
I need each modal to show up only when the previous one (if there'are any) has been closed. So, first we should see "browser-deprecated-modal" (no other modals underneath), upon closing it there must pop up the second one and so on.
I've been trying to work it out with this:
$.fn.modal = function(options) {
return this.each(function() {
if (Modal.running) {
Modal.toInstantiateLater.push({this,options});
} else {
var md = new Modal(this, options);
}
});
}
destroy :function () {
....
if (Modal.toInstantiateLater.length)
new Modal (Modal.toInstantiateLater[0][0],Modal.toInstantiateLater[0][1]);
}
keeping a track of all calls to construct a Modal in a array and in the "destroy" method make a check of this array is not empty.
but it seems awkward and buggy me thinks.
i need a robust and clear solution. I've been thinking about $.Callbacks or $.Deferred,
kinda set up a Callback queue
if (Modal.running) { //if one Modal is already running
var cb = $.Callbacks();
cb.add(function(){
new Modal(this, options);
});
} else { //the road is clear
var md = new Modal(this, options);
}
and to trigger firing cb in the destroy method, but i'm new to this stuff and stuck and cannot progress, whether it's right or not, or other approach is more suitable.
Besides, I read that callbacks fire all the functions at once (if we had more than one extra modal in a queue), which is not right, because I need to fire Modal creation one by one and clear the Callback queue one by one.
Please help me in this mess.
My code jsfiddle
I got rid of the counter variable, as you can use toInstantiateLater to keep track of where you are, and only had to make a few changes. Give this a try...
Javscript
function Modal(el, opts){
this.el = $(el);
this.opts = opts;
this.overlay = $("<div class='overlay' id='overlay"+Modal.counter+"'></div>");
this.wrap = $("<div class='wrap' id='wrap"+Modal.counter+"'></div>");
this.replace = $("<div class='replace' id='replace"+Modal.counter+"'></div>");
this.close = $("<span class='close' id='close"+Modal.counter+"'></span>")
if (Modal.running) {
Modal.toInstantiateLater.push(this);
}
else {
Modal.running = true;
this.show();
}
}
Modal.destroyAll = function() {
Modal.prototype.destroyAll();
};
Modal.prototype = {
show: function() {
var s = this;
s.wrap.append(s.close);
s.el.before(s.replace).appendTo(s.wrap).show();
$('body').append(s.overlay).append(s.wrap);
s.bindEvents();
Modal.currentModal = s;
},
bindEvents: function() {
var s = this;
s.close.on("click.modal",function(e){
s.destroy.call(s,e);
});
},
destroy: function(e) {
var s = this;
s.replace.replaceWith(s.el.hide());
s.wrap.remove();
s.overlay.remove();
if (Modal.toInstantiateLater.length > 0) {
Modal.toInstantiateLater.shift().show();
}
else {
Modal.running = false;
}
},
destroyAll: function(e) {
Modal.toInstantiateLater = [];
Modal.currentModal.destroy();
}
}
Modal.running = false;
Modal.toInstantiateLater = [];
Modal.currentModal = {};
$.fn.modal = function(options) {
return this.each(function() {
var md = new Modal(this, options);
});
}
$("document").ready(function(){
$("#browser-deprecated-modal").modal();
$("#change-your-city-modal").modal();
$("#promotion-modal").modal();
$("#destroy-all").on("click", function() {
Modal.destroyAll();
});
});
jsfiddle example
http://jsfiddle.net/zz9ccbLn/4/
This code is being used on a Chrome Extension.
When I call the "showOrHideYT()" function, I get a
"Uncaught ReferenceError: showOrHideYT is not defined | (anonymous
function) | onclick"
This code will search for youtube links in a page, and it will add a button (it's really a div with an event) next to the link to show the iframe with the embedded video, pretty much like Reddit Enhancement Suite. Consider the code, per se, incomplete. I just want to know what am i missing when i call the "showOrHideYT(frameZES12345)" function.
if needed, i can provide manifest.json.
Thanks
function showOrHideYT(id)
{
var YTvidWidth = 420;
var YTvidHeight = 315;
frameYT=getElementById(id);
console.log(frameYT.style.visibility);
if (frameYT.style.visibility == "hidden")
{
frameYT.style.width = YTvidWidth+"px";
frameYT.style.height = YTvidHeight+"px";
frameYT.style.visibility = "visible";
}
if (frameYT.style.visibility == "visible")
{
frameYT.style.width = "0px";
frameYT.style.height = "0px";
frameYT.style.visibility = "hidden";
}
};
// DOM utility functions
function insertAfter( referenceNode, newNode ) {
if ((typeof(referenceNode) == 'undefined') || (referenceNode == null)) {
console.log(arguments.callee.caller);
} else if ((typeof(referenceNode.parentNode) != 'undefined') && (typeof(referenceNode.nextSibling) != 'undefined')) {
if (referenceNode.parentNode == null) {
console.log(arguments.callee.caller);
} else {
referenceNode.parentNode.insertBefore( newNode, referenceNode.nextSibling );
}
}
};
function createElementWithID(elementType, id, classname) {
obj = document.createElement(elementType);
if (id != null) {
obj.setAttribute('id', id);
}
if ((typeof(classname) != 'undefined') && (classname != '')) {
obj.setAttribute('class', classname);
}
return obj;
};
///////////////////////////////////////
$(document).ready(function() {
var vidWidth = 420;
var vidHeight = 315;
var linksSemID = document.getElementsByTagName("a") ;
for (var i = 0; i < linksSemID.length; i++){
if (/id=$/.test(linksSemID[i].href)) links[i].href += "1";
}
i=0;
var youTubeRegExp = /(?:v=)([\w\-]+)/g;
var forEach = Array.prototype.forEach;
var linkArray = document.getElementsByTagName('a');
forEach.call(linkArray, function(link){
linkArray.id="zes" + i++;
var linkTarget = link.getAttribute('href');
if (linkTarget!=null)
{
if (linkTarget.search(youTubeRegExp) !=-1)
{
console.log (linkTarget);
idVideo=linkTarget.match(/(?:v=)([\w\-]+)/g);
//idVideo = idVideo.replace("v=", "");
//add buton
botaoMais = document.createElement('DIV');
botaoMais.setAttribute('class','expando-button collapsed video');
botaoMais.setAttribute('onclick','showOrHideYT(frameZES'+ i +')');
insertAfter(link, botaoMais);
//add iframe
ifrm = document.createElement('IFRAME');
ifrm.setAttribute('src', 'http://www.youtube.com/embed/'+ idVideo);
ifrm.style.width = '0px';
ifrm.style.height = '0px';
ifrm.style.frameborder='0px';
ifrm.style.visibility = 'hidden';
ifrm.setAttribute('id', 'frameZES' + i);
insertAfter(link, ifrm);
}
}
});
});
When you use setAttribute with a string, the event will be executed in the context of the page. The functions which are defined in a Content script are executed in a sandboxed scope. So, you have to pass a function reference, instead of a string:
Replace:
botaoMais.setAttribute('onclick','showOrHideYT(frameZES'+ i +')');
With:
botaoMais.addEventListener('click', (function(i) {
return function() {
showOrHideYT("frameZES"+ i);
};
})(i));
Explanation of code:
(function(i) { ..})(i) is used to preserve the value of i for each event.
Inside this self-invoking function, another function is returned, used as an event listener to click.
I see that you are using jQuery in your code. I personally think if we are using a library like jQuery, then we should not mix the native javascript code and jQuery code.
You can use jQuery bind to bind your the functions you need to call on dom ready.
Read below to know more.
suppose you want to call a javascript function on a button click, Here is the HTML for the same.
<div id="clickme">
<input id= "clickmebutton" type="button" value = "clickme" />
</div>
suppose "test" is the function you need to call, here is the code for test function.
function test() {
alert("hello");
}
you now need to bind the test function on the button click.
$(document).ready(function() {
$("#clickmebutton").bind("click", function(){
// do what ever you want to do here
test();
});
});
In the following code, if Control (the element that trigers Toggle's first OL) is not Visible it should be set Visible and all other Controls (Controls[i]) so be Hidden.
.js
function Toggle(Control){
var Controls=document.getElementsByTagName("ol",document.getElementById("Quote_App"));
var Control=Control.getElementsByTagName("ol")[0];
if(Control.style.visibility!="visible"){
for(var i=0;i<Controls.length;i++){
if(Controls[i]!=Control){
Reveal("hide",20,0.3,Controls[i]);
}else{
Reveal("show",20,0.3,Control);
};
};
}else{
Reveal("hide",20,0.3,Control);
};
};
Although the function [Toggle] works fine, it is actually setting Controls[i] to Hidden even if it is already.
This is easily rectified by adding an If statement as in the code below, surely there is a more elegant solution, maybe a complex If condition?
.js
function Toggle(Control){
var Controls=document.getElementsByTagName("ol",document.getElementById("Quote_App"));
var Control=Control.getElementsByTagName("ol")[0];
if(Control.style.visibility!="visible"){
for(var i=0;i<Controls.length;i++){
if(Controls[i]!=Control){
if(Controls[i].style.visibility=="visible"){
Reveal("hide",20,0.3,Controls[i]);
};
}else{
Reveal("show",20,0.3,Control);
};
};
}else{
Reveal("hide",20,0.3,Control);
};
};
Your help is appreciated always.
In the ugly pure javascript code world, your solution is fine. But only because you said "elegant", my answer is use jQuery.
I'll write it probably closer to what it really would be, using behaviour-based code rather than event-based, so this won't EXACTLY match your code.. but, it would look something like:
$('#Quote_app ol').click(function() {
if ($(this).is(':visible')) {
$(this).fadeOut();
} else {
$(this).fadeIn();
$('ol', $(this).parent()).not(this).fadeOut();
}
});
That attaches a click event to every ol element underneath something with ID=Quote_app, and if it's currently visible, hides it, and otherwise, shows it, and hides all other ol elements.
if(Controls[i]!=Control && Controls[i].style.visibility=="visible") {
Reveal("hide",20,0.3,Controls[i]);
}
Not sure about what means what in your code. Stratagy is to do default action for all items first, and then do specifica action for selected item. Something like this:
for(var i=0;i<Controls.length;i++){
if(Controls[i].style.visibility=="visible"){
Reveal("hide",20,0.3,Controls[i]);
};
}
Reveal("show",20,0.3,Control);
if( Controls[i] != Control ) {
if( Controls[i].style.visibility == "visible" ){
Reveal( "hide", 20, 0.3, Controls[i] );
};
} else {
Reveal( "show", 20, 0.3, Control );
};
could be rewritten as:
if ( Controls[i] == Control ) {
Reveal( "show", 20, 0.3, Control );
} else if ( Controls[i].style.visibility == "visible" ) {
Reveal( "hide", 20, 0.3, Controls[i] );
}
To follow on from the jQuery suggestion -
jQuery often has a toggle function which beomes even more attractive in this situation as it reduces your code to a couple of lines. There currently isnt a toggleFade function but it can be easily added, to quote Karl Swedberg:
You can write a custom animation like this:
jQuery.fn.fadeToggle = function(speed, easing, callback) {
return this.animate({opacity: 'toggle'}, speed, easing, callback);
};
Then, you can do this:
$(".bio-toggler").click(function () {
$("#bio-form").fadeToggle();
})
;
This will work without you having to use getComputedStyle, assuming your Reveal("hide", ...) function sets visibility to hidden.
if(Controls [i] !== Control && Controls[i].style.visibility !== "hidden") {
Reveal("hide", 20, 0.3, Controls[i]);
}
With a little monkey-patching you could make this a lot cleaner without using any external framework. I have also taken the liberty to reshuffle the logic based on the assumption that the ordering of animations (if any) is unimportant.
if Control is hidden
loop through Controls as C
hide if C != Control
show if C = Control
else
hide Control
Another way to interpret this algorithm is - as long as Controls contains at least one element (doesn't matter which), the visibility of Control will be toggled. And all (Controls minus Control) will be hidden. So I'm again taking the liberty to assume that there will always be one control in Controls, and that Control will always be toggled.
Here's the monkey-patch++ code for it (also on jsfiddle). This eliminates all ifs and elses from the function.
The Toggle function now looks like this:
function Toggle(Control) {
var Controls = document.getElementsByTagName("ol" ..
var Control = Control.getElementsByTagName("ol")[0];
Control.toggle();
Controls.filter(function(c) {
return c != Control && c.isVisible();
}).hide();
};
Here is the code-behind. NodeList and Array that apply a property on a list of elements:
NodeList.prototype.forEach = function(f) {
for(var i = 0; i < this.length; i++) {
f.apply(null, [this[i]]);
}
};
Array.prototype.forEach = NodeList.prototype.forEach;
NodeList.prototype.filter = function(f) {
var results = [];
for(var i = 0; i < this.length; i++) {
if(f.apply(null, [this[i]])) {
results.push(this[i]);
}
}
return results;
};
Array.prototype.filter = NodeList.prototype.filter;
NodeList.prototype.hide = function() {
this.forEach(function(e) {
e.hide();
});
};
Array.prototype.hide = NodeList.prototype.hide;
NodeList.prototype.show = function() {
this.forEach(function(e) {
e.show();
});
};
Array.prototype.show = NodeList.prototype.show;
These methods apply a property on an individual element:
Element.prototype.isVisible = function() {
return this.style.visibility == 'visible' || this.style.visibility == '';
};
Element.prototype.show = function() {
this.style.visibility = 'visible';
};
Element.prototype.hide = function() {
this.style.visibility = 'hidden';
};
Element.prototype.toggle = function() {
this.isVisible() ? this.hide() : this.show();
};