Targeting In and Out Animations on Elements - javascript

I'm prototyping a web app dealing with lots of views that are off screen until activated by an element currently on screen. Example:
<div class='current' data-view='home'>
<a href='#' data-target='menu'>View Menu</a>
</div>
<div data-view='menu'>
<a href='#' data-target='home'>Go back home</a>
</div>
Right now I've got the jQuery rigged to find the matching value of "data-target" to "data-view". When it finds the match, it toggles the class "current" between the two views.
Anyways! I'm hoping someone could help me figure out a good way to apply my enter and exit animations to the toggled elements. Here's what I tried:
$('[data-target]').on('click', function () {
var parentView = $(this).parents('[data-view]'),
currentView = $('.current');
function finishedAnimation() {
currentView.one('webkitAnimationEnd oanimationend msAnimationEnd animationend',
function() { currentView.removeClass(); });
};
if (parentView.data('view', 'home')) {
targetView.addClass('moveFromTop');
currentView.addClass('moveToBottom');
finishedAnimation();
}
else if (parentView.data('view', 'menu')) {
targetView.addClass('moveFromBottom');
currentView.addClass('moveToTop');
finishedAnimation();
}
$(this).parents('body').find('[data-view=' + $(this).data('target') + ']').addClass('current');
});
It works on the first click, but on the subsequent click to return home it fails to perform the animation correctly.
I've been digging around and switch cases look like a viable option (?). If anyone has guidance on a better approach it would be much appreciated.

Not exactly sure how your setup works, but I prefer to use an object ( as an interface ) for something like this:
function handleView( view ) {
views = {
home : function(){ /* Do stuff with view here */ },
menu : function(){}
}
views[view]()
}
$('[data-target]').on('click', function (e) {
e.preventDefault()
view = $(this).parent().data('view') /* This should return 'view name' */
handleView( view );
});
Remember that if you're adding a class that has a transition associated with it, you'll need to remove it too.
so targetView.addClass('moveFromTop'); needs targetView.removeClass('moveFromTop'); in order to properly toggle.

Related

How to close a dropdown with JS with a second click handler

I've got the functionality that allows you to open and close the hidden dropdown that you can see in the link but I want to also be able to close the already open dropdown if you click the same image again.
At the moment I can only close the ones on the outside of the clicked element.
Main fragment of code (wired up on ready), complete working sample - JsFiddle:
function close() {
$('.slide').removeClass("active");
$('.slide').css("padding-bottom", 0 + "px");
}
function open() {
$(this).addClass("active");
$(this).css("padding-bottom", height);
}
$('.slide').on('touchstart click', function() {
close();
if ( $(this).hasClass("active") ) {
close();
}
if ( !$(this).hasClass("active") ) {
$(this).addClass("active");
$(this).css("padding-bottom", height);
}
});
HTML:
<div class="slide"> title
<div class="js-slide">content of slide hidden via CSS when
"active" class is not present</div>
</div>
This change to your code seems to achieve the desired effect:
$('.slide').on('touchstart click', function() {
if ( $(this).hasClass("active") ) {
close();
} else {
close();
$(this).addClass("active");
$(this).css("padding-bottom", height);
}
});
Because your close() function removes the active class from the element, you need to have it in a place where it won't mess with the conditions your checking around the active class. Moving it inside the if blocks is an easy and quick change to fix the issue. Some people might be bothered by the way close() is repeated in both blocks. If you're one of those people, refactor appropriately. Just don't do the obvious-seeming move close() outside the if/then because that will cause this issue to re-appear.
Fiddle: https://jsfiddle.net/960cm7ux/1/

How do I detect when a div has lost focus?

Given the following markup, I want to detect when an editor has lost focus:
<div class="editor">
<input type="text"/>
<input type="text"/>
</div>
<div class="editor">
<input type="text"/>
<input type="text"/>
</div>
<button>GO</button>
EDIT: As the user tabs through the input elements and as each editor div loses focus (meaning they tabbed outside the div) add the loading class to the div that lost focus.
This bit of jquery is what I expected to work, but it does nothing:
$(".editor")
.blur(function(){
$(this).addClass("loading");
});
This seems to work, until you add the console log and realize it is triggering on every focusout of the inputs.
$('div.editor input').focus( function() {
$(this).parent()
.addClass("focused")
.focusout(function() {
console.log('focusout');
$(this).removeClass("focused")
.addClass("loading");
});
});
Here is a jsfiddle of my test case that I have been working on. I know I am missing something fundamental here. Can some one enlighten me?
EDIT: After some of the comments below, I have this almost working the way I want it. The problem now is detecting when focus changes to somewhere outside an editor div. Here is my current implementation:
function loadData() {
console.log('loading data for editor ' + $(this).attr('id'));
var $editor = $(this).removeClass('loaded')
.addClass('loading');
$.post('/echo/json/', {
delay: 2
})
.done(function () {
$editor.removeClass('loading')
.addClass('loaded');
});
}
$('div.editor input').on('focusin', function () {
console.log('focus changed');
$editor = $(this).closest('.editor');
console.log('current editor is ' + $editor.attr('id'));
if (!$editor.hasClass('focused')) {
console.log('switched editors');
$('.editor.focused')
.removeClass('focused')
.each(loadData);
$editor.addClass('focused');
}
})
A bit more complicated, and using classes for state. I have also added in the next bit of complexity which is to make an async call out when an editor loses focus. Here a my jsfiddle of my current work.
If you wish to treat entry and exit of the pairs of inputs as if they were combined into a single control, you need to see if the element gaining focus is in the same editor. You can do this be delaying the check by one cycle using a setTimeout of 0 (which waits until all current tasks have completed).
$('div.editor input').focusout(function () {
var $editor = $(this).closest('.editor');
// wait for the new element to be focused
setTimeout(function () {
// See if the new focused element is in the editor
if ($.contains($editor[0], document.activeElement)) {
$editor.addClass("focused").removeClass("loading");
}
else
{
$editor.removeClass("focused").addClass("loading");
}
}, 1);
});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/8s8ayv52/18/
To complete the puzzle (get your initial green state) you will also need to also catch the focusin event and see if it is coming from the same editor or not (save the previous focused element in a global etc).
Side note: I recently had to write a jQuery plugin that did all this for groups of elements. It generates custom groupfocus and groupblur events to make the rest of the code easier to work with.
Update 1: http://jsfiddle.net/TrueBlueAussie/0y2dvxpf/4/
Based on your new example, you can catch the focusin repeatedly without damage, so tracking the previous focus is not necessary after all. Using my previous setTimeout example resolves the problem you have with clicking outside the divs.
$('div.editor input').focusin(function(){
var $editor = $(this).closest('.editor');
$editor.addClass("focused").removeClass("loading");
}).focusout(function () {
var $editor = $(this).closest('.editor');
// wait for the new element to be focused
setTimeout(function () {
// See if the new focused element is in the editor
if (!$.contains($editor[0], document.activeElement)) {
$editor.removeClass("focused").each(loadData);
}
}, 0);
});
Here's what worked for me:
$(".editor").on("focusout", function() {
var $this = $(this);
setTimeout(function() {
$this.toggleClass("loading", !($this.find(":focus").length));
}, 0);
});
Example:
http://jsfiddle.net/Meligy/Lxm6720k/
I think you can do this. this is an exemple I did. Check it out:
http://jsfiddle.net/igoralves1/j9soL21x/
$( "#divTest" ).focusout(function() {
alert("focusout");
});

Backbone Click Anchor Events

I am starting a Web Application from scratch and I am using Backbone. So to start I was trying to have my whole app associated with a View, binding some anchors ( with internal links ) to a click event which gets the href and navigate to that, like this:
var App = new ( Backbone.View.extend( {
...
events : {
'click a[data-internal]' : function( event ) {
event.preventDefault();
Backbone.history.navigate( event.target.pathname, { trigger : true } );
}
},
start : function() {
Backbone.history.start( { pushState : true } );
}
}))({ el : document.body });
$( function() { App.start(); });
Then i have the following html:
<li>
<img src="assets/img/mail.png" alt="Mensagens" title="Mensagens">
</li>
My problem is, when the event is fired the event.target is always associated to the img child and not to the anchor. It doesnt seem to be something about bubbling. I have already tryed to use e.stopPropagation(). With no inner img it works well.
I don't understand why this happens. Can someone explain it to me?
Thanks a lot ;)
EDIT:
I solved the problem using event.currentTarget as ho.s suggested.

Prototype event handler

I've defined the following HTML elements
<span class="toggle-arrow">▼</span>
<span class="toggle-arrow" style="display:none;">▶</span>
When I click on one of the elements the visibility of both should be toggled. I tried the following Prototype code:
$$('.toggle-arrow').each(function(element) {
element.observe('click', function() {
$(element).toggle();
});
});
but it doesn't work. I know everything would be much simpler if I used jQuery, but unfortunately this is not an option:
Instead of iterating through all arrows in the collection, you can use the invoke method, to bind the event handlers, as well as toggling them. Here's an example:
var arrows = $$('.toggle-arrow');
arrows.invoke("observe", "click", function () {
arrows.invoke("toggle");
});
DEMO: http://jsfiddle.net/ddMn4/
I realize this is not quite what you're asking for, but consider something like this:
<div class="toggle-arrow-container">
<span class="toggle-arrow" style="color: pink;">▶</span>
<span class="toggle-arrow" style="display:none; color: orange;">▶</span>
</div>
document.on('click', '.toggle-arrow-container .toggle-arrow', function(event, el) {
var buddies = el.up('.toggle-arrow-container').select('.toggle-arrow');
buddies.invoke('toggle');
});
This will allow you to have multiple "toggle sets" on the page. Check out the fiddle: http://jsfiddle.net/nDppd/
Hope this helps on your Prototype adventure.
Off the cuff:
function toggleArrows(e) {
e.stop();
// first discover clicked arow
var clickedArrow = e.findElement();
// second hide all arrows
$$('.toggle-arrow').invoke('hide');
// third find arrow that wasn't clicked
var arw = $$('.toggle-arrow').find(function(a) {
return a.identify() != clickedArrow.identify();
});
// fourth complete the toggle
if(arw)
arw.show();
}
Wire the toggle arrow function in document loaded event like this
document.on('click','.toggle-arrow', toggleArrows.bindAsEventListener());
That's it, however you would have more success if you took advantage of two css classes of: arrow and arrow-selected. Then you could easily write your selector using these class names to invoke your hide/show "toggle" with something like:
function toggleArrows(e) {
e.stop();
$$('.toggle-arrow').invoke('hide');
var arw = $$('.toggle-arrow').reject(function(r) {
r.hasClassName('arrow-selected'); });
$$('.arrow-selected').invoke('removeClassName', 'arrow-selected');
arw.show();
arw.addClassName('arrow-selected');
}

How to execute .click() code when previous click binding finishes

I am trying to use a Twitter Bootstrap button group with data-toggle="buttons-radio" in my site. Bootstrap markup as follows.
<div class="btn-group program-status" data-toggle="buttons-radio">
<button class="btn">All</button>
<button class="btn">Active</button>
<button class="btn">Planning</button>
<button class="btn">End of Life</button>
<button class="btn">Cancelled</button>
</div>
I need to redirect to the same page with query depending on the pressed button. I tried to use following jQuery code to achieve this.
<script>
var sParamStr = '';
function addToParamStr(str) {
sParamStr += str;
}
function redirectToUpdatedLocation() {
$('.program-status > .btn.active').each(function () {
addToParamStr( '?status=' + $(this).text());
});
console.log(sParamStr);
window.location.href = "program" + sParamStr;
}
$document.ready(function () {
$('.program-status > .btn').on('click', function (e) {
redirectToUpdatedLocation();
});
});
</script>
But the browser always redirects to {site}/program without the query string. By commenting out window.location.href = "program" + sParamStr; line, I managed to observe that second click onwards, sParamStr getting appended properly.
It seems that, my code tries to read the text of the pressed button before, .button('toggle') method form bootstrap.js finished. Code worked as intended when I changed function as follows.
$document.ready(function () {
$( '.program-status > .btn').on('click', function (e) {
$(this).addClass('active');
redirectToUpdatedLocation();
});
});
While this method works for me right now, I would like to know the proper way to achieve this. i.e How to execute my code after previous click binding finishes?
UPDATE:
I found this link in the Twitter Bootstrap forum. Seems it is a known issue.
https://github.com/twitter/bootstrap/issues/2380
I'm not sure what Bootstrap's .toggle is doing exactly, but it seems like it does some sort of animation that completes with the setting of the active class. You can try enqueing your code instead:
$( '.program-status > .btn').on('click', function (e){
$(this).queue(function (next) {
redirectToUpdatedLocation();
next();
});
});
For example, click the div as it is being toggled: http://jsfiddle.net/9HwYy/
It also seems a bit silly to me to update every href instead of just the one you clicked on since you are changing the window location anyway.
try
$('.program-status > .btn.active').each(function(i,v){
v = $(v);
addToParamStr( '?status=' + v.text());
});
since im not sure "this" is working in your case.

Categories