Logic behind hiding elements - javascript

I am having a dilemma in the logic of this particular issue. Forgive me if this is quite newbie question but I'd rather have a solid bg on it.
There are a lot of examples of this all around the web where you click on an element to display another element. such case may be a menu that when you hover your mouse on it (or click on it) its get displayed. Later the element gets hidden either on mouse out, OR CLICKING ON ANY OTHER ELEMENT.. so, how is this achieved? I am sure the solution is not to bind a "hideElem" function on all the elements.
regards,

I haven't done it in a while, but an easy solution is to add a click event to the top of the DOM tree that will close the open element. Here's an example in psuedo-javascript:
document.body.onclick = function() {
element.style.display = "none";
}
If you need complex behaviors inside the "shown" element, make sure your preventing the necessary events from propagating up the DOM tree.
element.onclick = function(e) {
e.stopPropagation()
}

In general, the logic is the other way around (at least with menus) i.e. the element in question is hidden until a state-event unhides it, then hidden again as dictated. The point being that the hiding/unhiding logic is usually tied to the element itself, not everything else on the page.
As to how it's done, methods vary. There are lots of Javascript solutions, mostly along the lines of those already outlined, but menus can also be done purely with CSS - typically utilising the display: none; property, though you can also do stuff like setting/unsetting a negative margin so that the element is moved 'off and on the page'.
To use some of my own work by way of example:
Drop-down menu with Javascript
Drop-down menu with jQuery
Drop-down menu with CSS

$('#target').bind('click', function(event)
{
var $element = $('#element');
$element.show();
$(document).one('click', function()
{
$element.hide();
});
// If you don't stop the event, it will bubble up to the document
// and trigger the click event we just bound.
// This will hide the element right now just after showing it,
// we don't want that.
event.stopPropagation();
}
You have to keep in mind that a Javascript event goes up and down the whole tree when begin fired. So you can bind event listeners to any parent when you want to listen for an event on many elements.
This is called event delegation.

A cheap way to do it potentially is to bind an event handler to the "(on)blur" event of the clickable item and/or it's target. If your design allows.

That is one way to do it.
You could also write a method that traps (hooks into) all 'click' events regardless of the element, and hide your menu from there.
JQuery would make this task easier for you.

step 1- use a javascript library so you can have the code be as cross browser as possible - otherwise you have to cater to two different event models between internet explorer and gecko/webkit based browsers. JQuery, Mootools, YUI - all will handle this for you - there are more but those 3 are my favorite and are well documented.
step 2 - you prob would want to implement a clickshield for this - essentially a block-level dom element that is absolutely positioned over your entire page with a higher z-index than the rest of the page. attach a click event to that, and you can perform your logic for hiding elements on the page. The clickshield could easily have javascript code expand it to the width -height of your page post DOM rendering using the methods of any of the aforementioned javascript libraries.

Related

Is using a universal document.addEventListener("click",f) listener slower or weaker form than a specific use of it?

My code is this:
document.addEventListener('click', function(ev){
if(ev.path[0].className == 'linkTogether'){//do something}
if(ev.path[0].id == "createNewPage"){//do something}
});
Which has actually been working well for dynamically created buttons and nodes, but something just feels off about it. So I'm wondering if this is best practice or if there is a better way to add event listeners to dynamically created elements.
Thanks,
Jack
In specific cases where you have huge amount of objects that behave in the same way you can use this technique (adding event listener to their parent) to improve the performance of your script.
In a general page however you just have to many different objects and iterating trough all of them to check which one you've clicked is not faster.
Here is an article for the technique you are referring to - event delegation.
This is the proper pattern for creating event listeners that will work for dynamically-added elements. It's essentially the same approach as used by jQuery's event delegation methods (e.g. .on).
However, it does have performance implications. Every time you click anywhere in the document, the code will run, and have to go through the entire list of event bindings that you need to check. You can improve this by adding your event listener to a more specific element. If the dynamic elements are always added inside a specific DIV, add your listener to that DIV rather than document.
This also avoids another pitfall of event delegation. Event delegation depends on the event bubbling up from an inner element to all its containers. But if there are any handlers along the way that call event.stopPropagation, the event won't make it out to document. If you add the listener to a lower element, you're less likely to have a conflict like that.
And I would like to add, if you create elements dynamically, better you include the listeners inline and filter in the function what you want to do.
<div class="form_ID1" onclick="myfunction(this, event);">
... children bubbling
... Use if statements in caught js myFunction function.
In myfunction function you will capture the child element by event.target and the element that contains the listener by this.className.
There are scenarios however that you need a universal ( document ) click event. e.g You need to close a pop up when you click outside of a pop up !! It is like: e.g.
if (clicked.className != popup.className) popup.remove()
Even in this case there is a workaround by inserting an onblur="myfunction(this); in the parent DIV of the popup.

jQuery on() method - which way to use is better for performance?

Is it better to attach the on() event to the document or a closer parent?
Note: Initially this question had another aspect and a different topic. It became obsolete really quickly (typo in the source code)
The best key for performance using jQuery is to use an id as the initial identifier. For example:
$('#my_id').on('click', 'tag.my_class', function () {
...
});
This allows jQuery to go straight to the container, and then begin trawling from there.
if you bind the "on" event to the closest parent will produce exactly what are you looking for, click function will works fine even if it is appended to document, but in future if you append any elements with class "clickable" will also get binded. so its always good practice to append the "on" event to closest parent rather than whole document.
if you want more specific you can use
$("ul.media-grid").on('click', 'li.clickable', function () {
alert("works")
});
as it will get the ul with the class "media-grid" and appends the event to the li's with class "clickable"

jQuery: How to listen for DOM changes?

Is it possible and how can I listen for changes through the entire DOM tree with jQuery?
My specific issue: I have a 'tooltip' function that displays the contents of the title attribute in a stylish way when you do a hover on any html element. When you do a hover, however, by standard the browser renders the title in its own box. I would like to supress that. So what I've thought of is to move the contents of the title attribute to a custom (HTML5) data-title attribute the first time the page is loaded, and then my tooltip function will work with data-title.
The problem is that later on I might add / remove / change the HTML dynamically, so I need to 'rebind' those elements - change those title attrs again. It would be nice if there was an event listener that would listen for such changes for me and rebind the elements automatically.
My best guess is that you want to listen to DOM mutation events.
You can do that by DOM mutation event as any normal javascript event such as a mouse click.
Refer to this : W3 MutationEvent
Example:
$("element-root").bind("DOMSubtreeModified", "CustomHandler");
[edited in reply to research by member Tony]
So, without additional code, this is a bit of a blind shot, but it seems to me there are two things to think about here: 1. the default browser tooltip behaviour; 2. a potentially updated DOM and the ability for your custom tooltips to continue functioning.
Regarding #1: when you bind your custom event to the element, you can use event.preventDefault() so that the tooltips don't appear. This doesn't work properly. So, the workaround to keep using the "title" attribute is to grab the value, push it into the data object (the $.data() function), and then null the title with an empty string (removeAttr is inconsistent). Then on mouseleave, you grab the value out of the data object and push it back into the title. This idea comes from here: How to disable tooltip in the browser with jQuery?
Regarding #2: instead of re-binding on DOM change, you just need to bind once to a listener element that is never expected to be destroyed. Usually this is a container element of some sort, but it can even be document (approximating .live() which is now deprecated) if you really need an all-encompassing container. Here's a sample that uses some fake markup of my own devising:
var container = $('.section');
container.on('mouseenter', 'a', function() {
var $this = $(this);
var theTitle = $this.attr('title');
$this.attr('title', '');
$('#notatooltip').html(theTitle);
$.data(this, 'title', theTitle);
});
container.on('mouseleave', 'a', function() {
$('#notatooltip').html('');
var $this = $(this);
var storedTitle = $.data(this, 'title');
$this.attr('title', storedTitle);
});
My unrealistic markup (just for this example) is here:
<div class="section">
Hover this foo!
<div id="notatooltip"></div>
</div>
And a fiddle is here: http://jsfiddle.net/GVDqn/
Or with some sanity checks: http://jsfiddle.net/GVDqn/1/
There's probably a more optimal way to do this (I honestly didn't research if you could bind two separate functions for two separate events with one selector) but it'll do the trick.
You shouldn't need to re-bind based on DOM change, the delegated listener will automatically handle it. And you should be able to prevent default tooltip functionality just by preventing it.
You need to look at this here: http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-mutationevents
As noted by Greg Pettit, you should be using the on() function on the element.
What this does is allows you to bind a selector to an event, then jQuery will add this event handler when the objects returned by the selector are available.
If you wanted a function to fire on a mouse over event and you wanted it to fire on all elements with the class of *field_title* you would do this:
$('.field_title').bind('mouseenter', function() { doSomething(); });
This will trigger on the over mouse over event on any objects that have the class of *field_title* and execute the function doSomething().
Hope that makes sense :)

Easy way to bind dynamic elements without having to recall functions jquery

I currently have a page in which users can drag content around, resize it, etc.
The basic structure looks like the following:
$('.element').on().resizable().draggable();
When called at the beginning of the document load it adds bind events, resizable and draggable. Now the problem I have is that a user can add in new elements dynamically. These new elements are no longer covered by the above code. What I ended up doing is the following:
function bindEvents(){
$('.element').on().resizable().draggable();
}
bindEvents();
And then when elements are added I turn off the current implementation and then turn it on again so that it will include the new elements:
$('.element').off().resizable("destroy").draggable("destroy");
bindEvents();
No I know this is incredibly inefficient and most likely uses way more memory than needed. My question is, is there a better way to do this? Is there a way to add the one particular element to the already established group of bound elements?
Thanks!
If you don't want to waste work on the old items then brand them with something and avoid them later, or vice versa.
function bindEvents(){
$('.element').not('.initialized').addClass('initialized')
.on().resizable().draggable();
}
Why not use the delegation functionality of on() and bind to a container of the elements?
$('#container').on('mouseover', '.element', function(e) {
$(this).resizable().draggable();
});
You'd want to add the styling for the draggable classes to be there by default, but this is easily accomplished by adding the ui-resizable class to the elements:
<div class="element ui-resizable">Foo</div>
Any elements that match the filter ('.element') will automatically get the functionality when they are added to #container

Simple Horizontal Javascript Navigation with Prototype

I'm trying to implement a simple horizontal navigation menu that just shows a single div for each link. It is kinda like a dropdown menu but instead of a mouseover triggering a dropdown, an onclick event will trigger the showing of a div. I want to make sure I am taking the right approach before going too much further, any help is appreciated. This is what I have so far:
<ul id="settings_nav">
<li>
<a>Theme</a>
<div id="settings_block"><%= render :partial => 'email_password' %></div>
</li>
<li>
Lists
<div id="settings_block"><%= render :partial => 'lists' %></div>
</li>
</ul>
window.onload = function(){
settingsMenuInit('settings_nav')
}
function settingsMenuInit(settings_nav){
$(settings_nav).childElements().each(
function(node){
node.onclick= function(){ this.next.show() };
})
}
Something like that, but I am unsure how to get the div that is currently shown and hide it. I could iterate through all the childElements and hide each div and then show the one that is being clicked, but maybe there's a better way?
Some notes FW(T)W:
With Prototype and similar libraries, you don't want to hook up event handlers by assigning functions to the element's onclick and similar properties; that style has several disadvantages (not least that there can only be one handler for the event on the element). Instead, use Prototype's observe function:
someElement.observe('click', functionRefHere);
// or
Element.observe(someElementOrID, 'click', functionRefHere);
This also lets Prototype work around some IE memory loss bugs for you.
You might look at is Prototype's dom:loaded event, which happens sooner than window.onload (which won't happen until all of your images and other external resources have loaded, which can be a second or two after the page is displayed):
document.observe('dom:loaded', initFunctionRefHere);
You can use event delegation and just watch your settings_nav element, rather than each child node individually.
$(settings_nav).observe('click', handleNavClick);
function handleNavClick(event) {
var elm = event.findElement("some CSS selector here");
if (elm) {
event.stop();
// Handle it
}
}
As you can see, Event#findElement accepts a CSS selector. It starts with the actual element that was clicked and tries to match that with the selector; if it matches, it returns the element, otherwise it goes to the parent to see if it matches; etc. So with your HTML you might look for a li (event.findElement('li')) or the link (event.findElement('a')).
But if you want to watch each one individually, they can share a function (as they do in your example):
$(settings_nav).childElements().invoke('observe', 'click', handleNavClick);
function handleNavClick(event) {
// Prototype makes `this` reference the element being observed, so
// `this` will be the `li` element in this example.
}
Whether you watch each element individually or use event delegation depends on what you're doing (and personal preference). Whenever anything is likely to change (adding and removing navigation li elements, for instance) or when there are lots of things to watch, look to event delegation -- it's much easier simpler to deal with changing sets of elements using event delegation and just watching the parent. When dealing with a stable structure of just a few things (as in your example), it may be simpler to just watch the elements individually.
Once inside your handler, you can use Element#down to find child elements (so from the li, you might use li.down('div') to find the div), or Element#next to get to the next sibling element (e.g., going from the link to the div). Either way, once you have a reference to the div, you can use Element#show and Element#hide (or Element#toggle).
I recommend using named functions instead of anonymous ones (see my example above). Named functions help your tools (debuggers, browsers showing errors, etc.) help you. Just be sure not to declare a named function and use it as an expression (e.g., don't immediately assign it to something):
// Don't do this because of browser implementation bugs:
someElement.observe('click', function elementClickHandler(event) {
// ...
});
// Do this instead:
someElement.observe('click', elementClickHandler);
function elementClickHandler(event) {
// ...
}
...because although you should be able to do that according to the spec, in reality various bugs in various browsers make it not work reliably (article).

Categories

Resources