JQM - How to detect if item is shown on screen - jquery-mobile

I'm using JQuery mobile and I would like to raise an event when the listview is scrolled and a specific item is shown on screen.
Is there an event for this?! what are my options?!
Thanks.

AFAIK there are no events in jQM to do this, but you could combine ideas from some other solutions on StackOverflow to do this.
This question provides some code to determine if an item is visible, i.e.:
function isScrolledIntoView(elem)
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom)
&& (elemBottom <= docViewBottom) && (elemTop >= docViewTop) );
}
You can combine this with the scrollstop event. Say you are monitoring a <li> item defined as
<li id="myiditem">
I'm being watched!!
</li>
Then in $(document).ready you can do
var watchitem;
$(document).ready(function(){
watchitem = document.getElementById ('myiditem');
/* Bind to scroll event */
$(window).bind('scrollstop', function () {
if (isScrolledIntoView( watchitem )) {
alert('monitored li item was scrolled into view');
}
});
});
Here is a jsFiddle example.

Related

How to catch the deselection event in Add-on SDK in Firefox

I use the Add-on Builder & SDK to develop Firefox add-on. I catch the event when users select a piece of text by the snippet:
var selection = require("sdk/selection");
selection.on('select', function () {
//Doing something
});
However, what I want also is to do other things when users does not select that text anymore, but I can not figure it out how to do it. Anyone can help me with this? Thank you very much.
I am not aware if there are events on Deselection of a text.
However, you could register for Mouse click event on body or div containing that text and then in the callback function of the event, you could check if last selected texts is currently selected or not.
var selection = require("sdk/selection");
var lastText;
function addSelection(){
var selection = false;
var body = document.body;
if(!selection.text){
//deselection
//remove DOM listeners
body.removeListener('click', addSelection);
}
if (!selection.isContiguous) {
for (var subselection in selection) {
if(subselection.text == lastText){
selection = true;
}
}
} else if(selection.text && selection.text == lastText){
selection = true;
}
if(!selection){
//deselected
//remove DOM listeners
}
}
selection.on('select', function () {
var body;
if(lastText !== selection.text){
//deselected
//remove listeners
};
lastText = selection.text;
body = document.body;
//add DOM listeners like click - that can potentially remove selection
body.addEventListener('click', addSelection);
});

Scroll multiple drop containers with jQuery UI draggable/droppable?

Using jQuery UI draggable/droppable, how can force multiple drop containers to scroll when the draggable is dragged over them?
For example, how can I make these "drop target" lists scroll when dragging the "drag me" square over them?
Fiddle of above: http://jsfiddle.net/AdrkG/
Note: the draggable({ scroll: true }) option will not work here, as the draggable isn't a child of either drop container.
And some code examples to satisfy StackOverflow (otherwise it complains that I'm only linking to JSFiddle):
<div class="draggable">drag me</div>
<div class="dropcontainer">
<div class="droppable">drop target 0</div>
<div class="droppable">drop target 1</div>
…
</div>
<div class="dropcontainer">
<div class="droppable">drop target 0</div>
<div class="droppable">drop target 1</div>
…
</div>
<script>
$(".draggable").draggable()
$(".doppable").droppable()
</script>
<style>
.dropcontainer {
overflow: auto;
width: 150px;
height: 100px;
}
</style>
You may use the drag event.
Here is an exemple: http://jsfiddle.net/AdrkG/8/
I have almost th the same problem now. Thanks #Bali Balo for the direction and great example.
I just want to show 2-dimentional scroll variant of his code if somebody else needs:
var dropContainers = $(".dropContainer");
drag: function (event, ui) {
dropContainers.each(function () {
var $this = $(this);
var cOffset = $this.offset();
var bottomPos = cOffset.top + $this.height();
var rightPos = cOffset.left + $this.width();
clearInterval($this.data('timerScroll'));
$this.data('timerScroll', false);
if (event.pageX >= cOffset.left && event.pageX <= cOffset.left + $this.width()) {
if (event.pageY >= bottomPos - triggerZone && event.pageY <= bottomPos) {
var moveDown = function () {
$this.scrollTop($this.scrollTop() + scrollSpeed);
};
$this.data('timerScroll', setInterval(moveDown, 10));
moveDown();
}
if (event.pageY >= cOffset.top && event.pageY <= cOffset.top + triggerZone) {
var moveUp = function () {
$this.scrollTop($this.scrollTop() - scrollSpeed);
};
$this.data('timerScroll', setInterval(moveUp, 10));
moveUp();
}
}
if (event.pageY >= cOffset.top && event.pageY <= cOffset.top + $this.height()) {
if (event.pageX >= rightPos - triggerZone && event.pageX <= rightPos) {
var moveRight = function () {
$this.scrollLeft($this.scrollLeft() + scrollSpeed);
};
$this.data('timerScroll', setInterval(moveRight, 10));
moveRight();
}
if (event.pageX >= cOffset.left && event.pageX <= cOffset.left + triggerZone) {
var moveLeft = function () {
$this.scrollLeft($this.scrollLeft() - scrollSpeed);
};
$this.data('timerScroll', setInterval(moveLeft, 10));
moveLeft();
}
}
});
},
I added optimization not to search the droppable areas on every drag event - I pre-calculated it before initializing draggable widget. That substantially increased performance and responsiveness of dragging.
One more minor change is that it looks like moveUp and moveDown function names were interchanged ocasionally (I renamed them vise versa).

Latency issue with Primefaces overlayPanel - loads to lazy

I am using Primefaces 3.2 with jsf 2 and glassfish 3.1.2.
I have a p:dataTable of users containing avatars of the user. Whenever the user moves the mouse over the avatar a p:overlayPanel appears with more information (lazy loaded) on the user, and disappears when the user moves the cursor away - like this:
<p:overlayPanel for="avatar" dynamic="true" showEvent="mouseover" hideEvent="mouseout" ...>
This works very well - as long as the user is "slowhanded". Whenever an user moves the cursor fast above many avatars many of the overlayPanels stay visible.
For example when the user has the cursor over the position where user avatars are displayed and uses the scroll wheel of his mouse to scroll the usertable down or up.
I believe that the overlaypanel starts to load the information dynamically (dynamic="true") from the server when showEvent="mouseover" is dispatched and displays the overlaypanel after the response from the server arrives.
This way it is not possible to detect whether the cursor is already away when the overlaypanel becomes visible - so the hideEvent="mouseout" is never dispatched.
Is there a way to make the primefaces overlaypanel appear directly on mousover, showing a loading gif and update the content into the overlaypanel when the response comes from the server.
Is this a good appraoch or does anyone know any other way to solve this nasty problem?
Thanks Pete
As my first answer is already very long and contains valid information, I decided to open a new answer presenting my final approach.
Im now using Primefaces inheritance pattern making the code alot cleaner. Also I noticed that replacing/overwriting the whole bindEvents function isnt necessary, as we can remove the old event handlers. Finally this code fixs the latest issue experienced: A hide event before ajax arrival.
PrimeFaces.widget.OverlayPanel = PrimeFaces.widget.OverlayPanel
.extend({
bindEvents : function() {
this._super();
var showEvent = this.cfg.showEvent + '.ui-overlay', hideEvent = this.cfg.hideEvent
+ '.ui-overlay';
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(
showEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.timer = setTimeout(function() {
_self.hidden = false;
_self.show();
}, 300);
}).on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.hidden = true;
_self.hide();
});
},
_show : function() {
if (!this.cfg.dynamic || !this.hidden) {
this._super();
}
}
});
Im sorry for the poor formatting: Eclipses fault ;)
Wow, finally after a long debuging session and testing various approaches i recognized that the problem isnt the ajax request but the event handlers itself:
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
if(_self.isVisible()) {
_self.hide();
}
});
As you can see, the widget is just hidden if its visible before. If your moving your mouse out too fast, now two things can happen:
The widget isnt visible at all
The animation is still going on
In this case the event is discarded and the panel stays visible. As animations are queued, one simply has to remove the if statement to fix the issue. I did this by replacing the whole bindEvents method:
PrimeFaces.widget.OverlayPanel.prototype.bindEvents = function() {
//mark target and descandants of target as a trigger for a primefaces overlay
this.target.data('primefaces-overlay-target', this.id).find('*').data('primefaces-overlay-target', this.id);
//show and hide events for target
if(this.cfg.showEvent == this.cfg.hideEvent) {
var event = this.cfg.showEvent;
$(document).off(event, this.targetId).on(event, this.targetId, this, function(e) {
e.data.toggle();
});
}
else {
var showEvent = this.cfg.showEvent + '.ui-overlay',
hideEvent = this.cfg.hideEvent + '.ui-overlay';
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(showEvent, this.targetId, this, function(e) {
var _self = e.data;
if(!_self.isVisible()) {
_self.show();
}
})
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
_self.hide();
});
}
//enter key support for mousedown event
this.bindKeyEvents();
var _self = this;
//hide overlay when mousedown is at outside of overlay
$(document.body).bind('mousedown.ui-overlay', function (e) {
if(_self.jq.hasClass('ui-overlay-hidden')) {
return;
}
//do nothing on target mousedown
var target = $(e.target);
if(_self.target.is(target)||_self.target.has(target).length > 0) {
return;
}
//hide overlay if mousedown is on outside
var offset = _self.jq.offset();
if(e.pageX < offset.left ||
e.pageX > offset.left + _self.jq.outerWidth() ||
e.pageY < offset.top ||
e.pageY > offset.top + _self.jq.outerHeight()) {
_self.hide();
}
});
//Hide overlay on resize
var resizeNS = 'resize.' + this.id;
$(window).unbind(resizeNS).bind(resizeNS, function() {
if(_self.jq.hasClass('ui-overlay-visible')) {
_self.hide();
}
});
};
Execute this code on load and the issue should be gone.
As your replacing the js code nevertheless, you can use this oppurtunity to implement quite a nice feature. By using timeouts in the event handlers one can easily implement a little delay not just improving usability (no more thousands of popups appear) but also reducing network traffic:
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(showEvent, this.targetId, this, function(e) {
var _self = e.data;
_self.timer = setTimeout( function(){
if(!_self.isVisible()) {
_self.show();
}
}, 300);
})
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.hide();
});
Ofcourse you can use a global variable to control the delay time. If you want a more flexible approach youll have to overwrite the encodeScript method in the OverlayPanelRender to transmit an additional property. You could access it then with _self.cfg.delay. Notice though that youll have to replace the component model OverlayPanel too providing it with an extra attribute.
At the same time I thank you for this brilliant solution I take the opportunity to update it for Primefaces 5.2. In our application the code broke after that upgrade.
Follows the updated code for Primefaces 5.2:
PrimeFaces.widget.OverlayPanel.prototype.bindTargetEvents = function() {
var $this = this;
//mark target and descandants of target as a trigger for a primefaces overlay
this.target.data('primefaces-overlay-target', this.id).find('*').data('primefaces-overlay-target', this.id);
//show and hide events for target
if(this.cfg.showEvent === this.cfg.hideEvent) {
var event = this.cfg.showEvent;
this.target.on(event, function(e) {
$this.toggle();
});
}
else {
var showEvent = this.cfg.showEvent + '.ui-overlaypanel',
hideEvent = this.cfg.hideEvent + '.ui-overlaypanel';
this.target
.off(showEvent + ' ' + hideEvent)
.on(showEvent, function(e) {
clearTimeout($this.timer);
$this.timer = setTimeout(function() {
$('.ui-overlaypanel').hide();
$this.hidden = false;
$this.show();
}, 500);
})
.on(hideEvent, function(e) {
clearTimeout($this.timer);
$this.timer = setTimeout(function() {
// don't hide if hovering overlay
if(! $this.jq.is(":hover")) {
$this.hide();
}
}, 100);
});
}
$this.target.off('keydown.ui-overlaypanel keyup.ui-overlaypanel').on('keydown.ui-overlaypanel', function(e) {
var keyCode = $.ui.keyCode, key = e.which;
if(key === keyCode.ENTER||key === keyCode.NUMPAD_ENTER) {
e.preventDefault();
}
})
.on('keyup.ui-overlaypanel', function(e) {
var keyCode = $.ui.keyCode, key = e.which;
if(key === keyCode.ENTER||key === keyCode.NUMPAD_ENTER) {
$this.toggle();
e.preventDefault();
}
});
};
I also added an extra feature which allows the user to move the mouse over the overlay without hiding it. It should hide when you move the mouse out of it then which I accomplished through:
<p:overlayPanel .... onShow="onShowOverlayPanel(this)" ...>
function onShowOverlayPanel(ovr) {
ovr.jq.on("mouseleave", function(e) {
ovr.jq.hide();
});
}
Hope you enjoy!
It's been a long time, but in case anyone bumps into this problem, a showDelay attribute was added to the overlayPanel to solve this problem starting from Primefaces 6.2. However, it is not in the official documentation for some reason.

jQuery Mobile + backbone.js: navbar issue

I load pages created from templates dynamically with a function from the Router (as seen on some tutorials):
changePage: function(page) { // page is a View object
$(page.el).attr('data-role', 'page');
page.render();
$('body').append($(page.el));
var transition = $.mobile.defaultPageTransition;
if (this.firstPage) {
transition = 'none';
this.firstPage = false;
}
$.mobile.changePage($(page.el), {changeHash:false, transition: transition});
}
The thing is when pages contain a JQ Mobile navbar, the active item is not highlighted. Actually it is, like 1 ms, then it's not, I feel like it's because the navbar is "reloaded".
When I click 2 times on the same item, it works the second time.
Is there anybody who is able to have working navbars with jQuery Mobile and backbone.js?
I ended up doing that:
var activeTab = null;
$('[data-role=page]').live('pageshow', function (event, ui) {
$.each($('[data-role=navbar] ul li').children(), function (i, val) {
if (typeof activeTab !== "undefined" && activeTab != null && $(val).attr('id') == 'navTab' + activeTab)
$(val).addClass($.mobile.activeBtnClass);
else
$(val).removeClass($.mobile.activeBtnClass);
});
activeTab = null;
});
And for each route that requires an active tab I just do for instance:
r_search: function() { // Search page (form)
activeTab = "Search";
this.changePage(new SearchView());
},

Changing the color of a selected link that is embedded in a table

I'm trying to use class names to change the color of a link after it has been selected, so that It will remain the new color, but only until another link is selected, and then it will change back.
I'm using this code that was posted by Martin Kool in this question:
<html>
<head>
<script>
document.onclick = function(evt) {
var el = window.event? event.srcElement : evt.target;
if (el && el.className == "unselected") {
el.className = "selected";
var siblings = el.parentNode.childNodes;
for (var i = 0, l = siblings.length; i < l; i++) {
var sib = siblings[i];
if (sib != el && sib.className == "selected")
sib.className = "unselected";
}
}
}
</script>
<style>
.selected { background: #f00; }
</style>
</head>
<body>
One
Two
Three
</body>
It works fine until I try to out the links in a table. Why is this? Be easy, I'm a beginner.
There is no error, the links are changing to the "selected" class, but when another link is selected, the old links are keeping the "selected" class instead of changing to "unselected". Basically, as far as I can tell, it's functioning like a vlink attribute, which is not what I'm going for.
And yes, the links are all in different cells, how would you suggest I change the code so that it works correctly?
OK, actually, I spoke too soon.
document.onclick = function(evt)
{
var el = window.event? event.srcElement : evt.target;
if (el && el.className == 'unselected')
{
var links = document.getElementsByTagName('a');
for (var i = links.length - 1; i >= 0; i--)
{
if (links[i].className == 'selected')
links[i].className = 'unselected';
}
el.className = 'selected';
}
return false;
}
This code you gave me works great, visually, it does exactly what I want it to do. However, It makes my links stop working... They change color, but dont link to anything, and then when I remove the script, they work fine. What am I doing wrong/what do I have to change to make this work?
Also, I want to do the same thing somewhere else in my website, where the links are all in one <div> tag, separated by <p> tags. How can I make this work?
You're looping through the siblings. If the links are in separate <td>'s then they're no longer siblings.
You can loop through all the links like this:
document.onclick = function(evt)
{
var el = window.event? event.srcElement : evt.target;
if (el && el.className == 'unselected')
{
var links = document.getElementsByTagName('a');
for (var i = links.length - 1; i >= 0; i--)
{
if (links[i].className == 'selected')
links[i].className = 'unselected';
}
el.className = 'selected';
}
return false;
}
I've also added a return false; at the end of the function to stop you going to '#'
Is there an error or is there just nothing happening? A good first step if you are a javascript beginner is to use a tool like Firebug so you see detailed error messages, and you can add in console.log statements to see what's going on while you run your code.
By ‘in tables’ do you mean putting each link in its own cell? Because that would make this line:
var siblings = el.parentNode.childNodes;
fail to select other links outside of the cell. You'd have to find another way to signal which element is the link container.

Resources