If condition not executes while mouse move fastly - javascript

CSS
.selected
{
background-color: Red;
color: #ffffff;
}
jQuery
$(document).on('mousemove', '#mytable tr', function (e)
{
var currentColoumn = $(e.target).closest('td').index();
if ($(this).find("td").eq(currentColoumn).hasClass('selected') == true) {
e.preventDefault();
return false;
}
});
HTML
<table border="1" id="mytable ">
<tr>
<td>
9:30 AM
</td>
<td>
30
</td>
<td>
</td>
<td class="selected">
SELECTED
</td>
<td>
</td>
</tr>
</table>
I have to check a condition in tr mouse move where if td has class selected then mouse move stops.
If condition not executes while mouse move fastly
Demo

The way you're using on here, is attaching the event listener to the document, and apply the callback to any mousemove events on rows in the #mytable element. That means internally, the call to on is translated into a call to delegate.
Normally, I'm all for event delegation, for obvious reasons (performance being the most obvious of all). However, when you delegate mouse events, your handler is being called every time the event is fired. When doesn't a mouse move somewhere in the document? When the client is reading, the window is minimized or not in focus.
Though jQuery hides this, your code actually does this:
document.body.addEventListener('mousemove',function(e)
{
var parent, target = (e = e || window.event).target || e.srcElement;
if (target.tagName.toLowerCase() === 'tr')
{//this reverse DOM lookup is probably cached, but anyway
parent = target;
while(parent = parent.parentNode)
{//or something
if (parent.getAttribute('id') === 'myTable')
{
break;
}
if (parent === document.body)
{//not child of #myTable
return e;
}
}
if (yourCallbackFunction.apply(target, [e]) === false)
{//remember: return false in jQ == preventDefault + stopPropagation:
e.preventDefault();
e.stopPropagation();
}
}
}, false);
Your callback is only called when the element is a child of a #myTable, but each time the mouse moves, an event handler is being called!
When delegating events like this, it'd be wiser to do this:
$('#myTable').on('mousemove', 'tr', function()
{
//your code here
});
This way, an event handler will only be called when the mousemove event occurs inside the #myTable element. Since that's all you're interested in, that's what you should do.
The fiddle you linked to is also packed with live calls. live has been deprecated for years now, one of the reasons being: it was slooow.
Another thing to look into if speed is an issue is: avoid excessive DOM calls, by using closures for example. DOM calls are slow!
If speed is still an issue, don't use jQuery: use VanillaJs, if done right... it'll always be faster than a lib

I think the approach just needs some tweaking. The constraints can be calculated at the time a cell is clicked; as the mouse moves you know when to stop colouring the cells.
Here's a breakdown:
jQuery(function($) {
document.onselectstart = function () { return false; }
var $table = $('#contentPlaceHolderMain_tableAppointment'),
columns = [],
dragInfo = null;
$table.find('td').each(function() {
var i = $(this).index();
(columns[i] = columns[i] || []).push(this);
});
This creates a pivoted array so that you can reference each column more easily. This comes in handy later.
$table.on('mouseup', function() {
// reset selection
dragInfo = null;
});
This is the same as your previous code, except you may have noticed two differences:
I'm setting up the click event handler on $table instead of document; this prevents some overheads when clicks outside the table are received.
Because .live() is deprecated, .on() should be used instead.
Let's move on to the mousedown handler.
$table.on('mousedown', 'td', function() {
var $this = $(this),
columnIndex = $this.index();
if ($this.is('.selected') || columnIndex < 2) {
return;
}
var thisRow = $this.parent().index(),
selectionRowAbove = -Infinity,
selectionRowBelow = Infinity;
$.each(columns[columnIndex], function(rowIndex) {
if ($(this).is('.selected')) {
if (rowIndex < thisRow && rowIndex > selectionRowAbove) {
selectionRowAbove = rowIndex;
} else if (rowIndex > thisRow && rowIndex < selectionRowBelow) {
selectionRowBelow = rowIndex;
}
}
});
By adding a listener on td instead of tr you don't have to find the nearest cell; it gets calculated for you. This part of the function works out the red cells above and below the current cell.
// unmark cells
$table.find(".csstdhighlight").removeClass("csstdhighlight");
dragInfo = {
column: columnIndex,
enclosure: {
above: selectionRowAbove,
below: selectionRowBelow
},
min: thisRow,
max: thisRow
};
$this.addClass('csstdhighlight');
});
The last part of the function saves all the drag information that you will need later on.
$table.on('mousemove', 'td', function() {
if (!dragInfo) {
return;
}
var $this = $(this),
columnIndex = $this.index(),
rowIndex = $this.parent().index();
if (columnIndex != dragInfo.column || rowIndex == 0 || rowIndex <= dragInfo.enclosure.above || rowIndex >= dragInfo.enclosure.below) {
dragInfo = null;
return;
}
These conditions make sure the selection stays within the bounds that we have determined earlier.
// mark cells
var cells = [columns[columnIndex][rowIndex]];
while (dragInfo.min > rowIndex) {
cells.push(columns[columnIndex][dragInfo.min--]);
}
while (dragInfo.max < rowIndex) {
cells.push(columns[columnIndex][dragInfo.max++]);
}
$(cells).addClass('csstdhighlight');
});
});
The last part of the function marks the selection; it does so by calculating the differences between the last invocation, so that you don't have to mark cells again.
Demo

There is one semi-solution, however, that could be done.
Use CSS on the body to hide the actual mouse cursor, then display a fake mouse pointer (just a regular graphic) at the mouse location.
If the mouse pointer is out of bounds, stop the fake cursor at the bounding box.
I don't know what your use-case is, but it would be MUCH better if you don't hide the real mouse pointer, but display some kind of oversized mouse pointer in the bounding box. Mostly the same effect and much more user friendly.

Consider a diferent approach to deciding on wether a mousemove should stop highlighting a cell. Instead of "stoping when it goes over a cell with the class selected",
Try stoping if between the initial cell and the current cell exists a cell with the class selected. This ensures that if a fast movement prevents the mousemove event from being delegated to your "selected" cells it will still be able to detect that you have passed over one.

I think that the main problem here is that the mouse does not stop on every single cell. The mouse usually looks like it moves continuously, but when I move it quickly, it jumps around, skipping large amounts of space in between.
I think you've already seen this affect. Hence, you are highlighting a range of cells, not just the one you are pointing to (current cell). However, you are only checking if the current cell is selected, not if any of the in between cells are selected.
The solution is to check all the in between cells to make sure that they are not selected. jQuery has a filter function that does that nicely. I've refactored your code to use filter:
$("#contentPlaceHolderMain_tableAppointment tr").live('mousemove', function (e) {
// Compares with the last and next row index.
var currentRow = $(this).closest("tr")[0].rowIndex;
var currentColoumn = $(e.target).closest('td').index();
var allRows = $('#contentPlaceHolderMain_tableAppointment tr');
var previousRowIndex = parseInt(lastRow, 10);
var currentRowIndex = parseInt(currentRow, 10);
var traversedRows;
if (previousRowIndex < currentRowIndex)
traversedRows = allRows.slice(previousRowIndex, parseInt(currentRowIndex + 1));
else {
traversedRows = allRows.slice(currentRowIndex, previousRowIndex)
}
var affectedCells = traversedRows.children(':nth-child(' + parseInt(currentColoumn + 1) + ')');
if ($(this).find("td").eq(currentColoumn).hasClass('selected') == true ||
affectedCells.filter('.selected').length > 0) {
if (flag != false) {
flag = false;
e.preventDefault();
return false;
}
}
if (currentRow == 0) {
flag = false;
return false;
}
//cross cell selection.
if (colIndex != currentColoumn) {
flag = false;
return;
}
if (flag) {
affectedCells.addClass('csstdhighlight');
e.preventDefault();
return false;
}
});
The fiddle is at: http://jsfiddle.net/Jg58G/7/
There still exists an issue where some cells are supposed to be highlighted, but they are not. Now that you have the row of the selected cell, you can redo your slice logic to slice at the right place. I'll leave that part to you.

Note sure if this is precisely what you're aiming for, but it should be easy to adapt. Using mouseover cuts down on the calls, should have better performance.
Fiddle at: http://jsfiddle.net/VJG4F/
$(document).ready(function () {
// setup
var doc = this;
doc.checkOver = false;
doc.activeCell = {
x: -1,
y: -1
};
doc.lastCell = {
x: -1,
y: -1
};
doc.highlightClass = 'csstdhighlight';
doc.selectionClass = 'selected';
doc.selectionText = 'SELECTED';
// start checking on mousedown
$('#contentPlaceHolderMain_tableAppointment td').on('mousedown', function (e) {
doc.checkOver = true;
// set active and last cell for reference
doc.lastCell.x = doc.activeCell.x = $(this).closest('td').index();
doc.lastCell.y = doc.activeCell.y = $(this).closest("tr").index();
// highlight selected cells
var cellSelector = '#contentPlaceHolderMain_tableAppointment tr:eq(' + doc.activeCell.y + ') td:eq(' + doc.activeCell.x + ')';
$(cellSelector).addClass(doc.highlightClass);
// check for movement
$('#contentPlaceHolderMain_tableAppointment td').on('mouseover', function (e) {
if (!(doc.checkOver)) return;
// get current cell for reference
var currCell = {
x: $(e.target).closest('td').index(),
y: $(e.target).closest('tr').index()
};
// verify mouse is over a different cell
if ( !((currCell.y != doc.lastCell.y && currCell.y != -1) || (currCell.x != doc.lastCell.x && currCell.x != -1)) ) return false;
// make sure other cells are not highlighted
$('#contentPlaceHolderMain_tableAppointment td').removeClass(doc.highlightClass);
// highlight selected cells
var topLeft = {
x: Math.min(currCell.x, doc.activeCell.x),
y: Math.min(currCell.y, doc.activeCell.y)
};
var botRight = {
x: Math.max(currCell.x, doc.activeCell.x),
y: Math.max(currCell.y, doc.activeCell.y)
};
for (var x=topLeft.x;x<=botRight.x;x++) {
for (var y=topLeft.y;y<=botRight.y;y++) {
var cellSelector = '#contentPlaceHolderMain_tableAppointment tr:eq(' + y + ') td:eq(' + x + ')';
$(cellSelector).addClass(doc.highlightClass);
}
}
// update last cell
doc.lastCell.y = currCell.y;
doc.lastCell.x = currCell.x;
return false;
});
// check for mouseup
$('#contentPlaceHolderMain_tableAppointment td').on('mouseup', function (e) {
// stop checking for movement
$('#contentPlaceHolderMain_tableAppointment td').off('mouseup');
$('#contentPlaceHolderMain_tableAppointment td').off('mouseout');
// get current cell for reference
var currCell = {
x: $(this).closest("td").index(),
y: $(this).closest('tr').index()
};
// make sure cells are not highlighted
$('#contentPlaceHolderMain_tableAppointment td').removeClass(doc.highlightClass);
// select cells
var topLeft = {
x: Math.min(currCell.x, doc.activeCell.x),
y: Math.min(currCell.y, doc.activeCell.y)
};
var botRight = {
x: Math.max(currCell.x, doc.activeCell.x),
y: Math.max(currCell.y, doc.activeCell.y)
};
// single cell - toggle
if( topLeft.x == botRight.x && topLeft.y == botRight.y ) {
var cellSelector = '#contentPlaceHolderMain_tableAppointment tr:eq(' + topLeft.y + ') td:eq(' + topLeft.x + ')';
$(cellSelector).toggleClass(doc.selectionClass);
if( $(cellSelector).text() == doc.selectionText ) $(cellSelector).text('');
else $(cellSelector).text(doc.selectionText);
// multi-cell, select all
} else {
for (var x=topLeft.x;x<=botRight.x;x++) {
for (var y=topLeft.y;y<=botRight.y;y++) {
var cellSelector = '#contentPlaceHolderMain_tableAppointment tr:eq(' + y + ') td:eq(' + x + ')';
$(cellSelector).addClass(doc.selectionClass);
$(cellSelector).text(doc.selectionText);
}
}
}
// reset
doc.checkOver = false;
doc.activeCell.y = -1;
doc.activeCell.x = -1;
doc.lastCell.y = -1;
doc.lastCell.x = -1;
return false;
});
return false; // prevent default
});
});

Related

Focus an input field when clicking on an element, except if some text within the container was highlighted

An input field #chatInput needs to be be focused when clicking on a container element #text EXCEPT if text inside that element was (highlighted via either double click or mouse selection)
// what I got so far which is incomplete
$('#text').on('click', function (e) {
$('#chatInput').focus();
});
Fiddle: https://jsfiddle.net/xhykmtwy/4/
You may want to consider the solution below, which checks if some text is selected in the text element after the click event:
$('#text').click(function () {
var container = this;
setTimeout(function () {
var selectedElement = null;
var selectedText = null;
if (window.getSelection) {
// For modern browsers
var selection = window.getSelection();
if (selection) {
selectedText = selection.toString();
if (selection.anchorNode) {
selectedElement = selection.anchorNode.parentNode;
}
}
}
else if (document.selection && document.selection.type === "Text") {
// For IE < 9
var selection = document.selection;
selectedText = selection.createRange().text;
}
if (!(selectedText && selectedText.length > 0) || (selectedElement !== container && !$(container).has(selectedElement))) {
setTimeout(function () { $('#chatInput').focus(); }, 0);
}
}, 0);
});
According to my tests, it works in IE (including IE7), Firefox and Chrome. The only exception is the double-click in IE, which does not select the text. You can see the result in this jsfiddle.
The calls to setTimeout ensures that all the selection processing has been done, especially when clicking on the selected text to deselect it.
Credits:
I used the method proposed by Eineki in How can I get the element in which highlighted text is in? to check if the text element contains the selected text.
The code for processing the selection in IE < 9 was found in Tim Down's answer to the post Get the Highlighted/Selected text.
A bit longer than I initially thought a solution could be but here's what I got:
var mouseDownStart = 0,
lastKeyupTime = 0;
function processKeyDown() {
if (!mouseDownStart) {
mouseDownStart = Date.now();
}
}
function processKeyUp() {
var now = Date.now(),
isDoubleClick = lastKeyupTime && now - lastKeyupTime < 500;
isHighliting = now - mouseDownStart > 150
lastKeyupTime = now;
mouseDownStart = 0;
return {
isDoubleClick: isDoubleClick,
isHighliting: isHighliting
}
}
$('#text').on('mousedown', function (e) {
processKeyDown();
});
$('#text').on('mouseup', function (e) {
var data = processKeyUp();
if (data.isDoubleClick || data.isHighliting) return;
$('#chatInput').focus();
});
Updated fiddle: https://jsfiddle.net/xhykmtwy/1/

Draggable code not working with hammer.js

I've successfully implemented jQueryUI draggable, but as soon as I add hammer.js code, the draggable code no longer works.
It is not as soon as I include hammer.js, but as soon as I use the script.
Why is this? How can I get them both to work?
Both the draggable and hammer are applied to .dataCard and #main
The draggable code works fine here ( with hammer implementation commented out ): http://goo.gl/MO5Pde
Here is an example of the draggable code:
$('#main').draggable({
axis:'y',
revert:true,
start: function(event, ui){
topValue = ui.position.top;
},
drag: function(event, ui){
if(pastBreakpoint === false){
$('#searchInput').blur();
if(topValue > ui.position.top) return false;
if(ui.position.top >= 161){
if(pastBreakpoint === false){
pastBreakpoint = true;
if($('.loadingRefresh').length === 0) $('#main').before('<div class="loadingRefresh"></div>');
else{
$('.loadingRefresh').remove();
$('#main').before('<div class="loadingRefresh"></div>');
}
$('.loadingRefresh').fadeIn();
$('#main').mouseup();
setTimeout(function(){
location.reload();
}, 1000);
}
}
}
}
});
Here is the hammer code uncommented and the draggable code not working: http://goo.gl/994pxF
Here is the hammer code:
var hammertime = Hammer(document.getElementById('main'), {
transform_always_block: true,
transform_min_scale: 0
});
var posX = 0,
posY = 0,
lastPosX = 0,
lastPosY = 0,
bufferX = 0,
bufferY = 0,
scale = 1,
last_scale = 1;
hammertime.on('touch transform transformend', function(ev) {
if ((" " + ev.target.className + " ").indexOf(" dataCard ") < 0) return;
else manageMultitouch(ev, ev.target); });
function manageMultitouch(ev, element) {
switch (ev.type) {
case 'touch':
last_scale = scale;
return;
case 'transform':
scale = Math.min(last_scale * ev.gesture.scale, 10);
break;
}
if(scale <= 0.5) $(element).hide('clip');
if(scale > 1.0) $(element).addClass('focused');
var transform = "translate(" + 0 + "px," + 0 + "px) " + "scale(" + 1 + "," + scale + ")";
var style = element.style;
style.transform = transform;
style.oTransform = transform;
style.msTransform = transform;
style.mozTransform = transform;
style.webkitTransform = transform;
}
I had the same problem in my app, even with touch punch included. I had to do a good research to find what was the problem stopping the jquery ui drag.
The problem occurring is a preventDefault set at the event ( only when hammer is included ) changing the result of a trigger method from jquery ui.
Well, lets get back a little bit:
The first method you should see is the _mouseMove(), which is connected with the mousemove event.
The drag will be trigged only when the condition (this._mouseStart(this._mouseDownEvent, event) !== false) be true.
_mouseMove: function (event) {
// IE mouseup check - mouseup happened when mouse was out of window
if ($.ui.ie && (!document.documentMode || document.documentMode < 9) && !event.button) {
return this._mouseUp(event);
}
if (this._mouseStarted) {
this._mouseDrag(event);
return event.preventDefault();
}
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
this._mouseStarted =
(this._mouseStart(this._mouseDownEvent, event) !== false);
(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
}
return !this._mouseStarted;
}
The next method will create the helper ( element's clone ), set some css in the element and return true ( value we expect ), unless this._trigger("start", event) returns false.
_mouseStart: function(event) {
var o = this.options;
//Create and append the visible helper
this.helper = this._createHelper(event);
this.helper.addClass("ui-draggable-dragging");
//Cache the helper size
this._cacheHelperProportions();
//If ddmanager is used for droppables, set the global draggable
if($.ui.ddmanager) {
$.ui.ddmanager.current = this;
}
/*
* - Position generation -
* This block generates everything position related - it's the core of draggables.
*/
//Cache the margins of the original element
this._cacheMargins();
//Store the helper's css position
this.cssPosition = this.helper.css( "position" );
this.scrollParent = this.helper.scrollParent();
this.offsetParent = this.helper.offsetParent();
this.offsetParentCssPosition = this.offsetParent.css( "position" );
//The element's absolute position on the page minus margins
this.offset = this.positionAbs = this.element.offset();
this.offset = {
top: this.offset.top - this.margins.top,
left: this.offset.left - this.margins.left
};
//Reset scroll cache
this.offset.scroll = false;
$.extend(this.offset, {
click: { //Where the click happened, relative to the element
left: event.pageX - this.offset.left,
top: event.pageY - this.offset.top
},
parent: this._getParentOffset(),
relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
});
//Generate the original position
this.originalPosition = this.position = this._generatePosition(event);
this.originalPageX = event.pageX;
this.originalPageY = event.pageY;
//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
//Set a containment if given in the options
this._setContainment();
//Trigger event + callbacks
if(this._trigger("start", event) === false) {
this._clear();
return false;
}
//Recache the helper size
this._cacheHelperProportions();
//Prepare the droppable offsets
if ($.ui.ddmanager && !o.dropBehaviour) {
$.ui.ddmanager.prepareOffsets(this, event);
}
this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
if ( $.ui.ddmanager ) {
$.ui.ddmanager.dragStart(this, event);
}
return true;
}
Below is the first _trigger called, its from drag widget.
_trigger: function (type, event, ui) {
ui = ui || this._uiHash();
$.ui.plugin.call(this, type, [event, ui]);
//The absolute position has to be recalculated after plugins
if(type === "drag") {
this.positionAbs = this._convertPositionTo("absolute");
}
return $.Widget.prototype._trigger.call(this, type, event, ui);
}
At this point the result will call another trigger method (this time from the $.Widget) and that's the point where we have the problem.
_trigger: function (type, event, data) {
var prop, orig,
callback = this.options[type];
data = data || {};
event = $.Event(event);
event.type = (type === this.widgetEventPrefix ?
type :
this.widgetEventPrefix + type).toLowerCase();
// the original event may come from any element
// so we need to reset the target on the new event
event.target = this.element[0];
// copy original event properties over to the new event
orig = event.originalEvent;
if (orig) {
for (prop in orig) {
if (!(prop in event)) {
event[prop] = orig[prop];
}
}
}
return !($.isFunction(callback) && callback.apply(this.element[0], [event].concat(data)) === false || event.isDefaultPrevented());
}
return !($.isFunction(callback) && callback.apply(this.element[0], [event].concat(data)) === false || event.isDefaultPrevented());
Our problem is exactly at this line. More specific the || before event.isDefaultPrevented().
When hammer is included the method event.isDefaultPrevented() is resulting true, once the value is denied before return, the final value would be false.
(Without the hammer included the event.isDefaultPrevented() returns false as expected.)
Backing in our _moseMouve(), instead of calling the _mouseDrag() method it'll invoke _mouseUp().
U can see it will unbind the events and call _mouseStop().
_mouseUp: function (event) {
$(document)
.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
if (this._mouseStarted) {
this._mouseStarted = false;
if (event.target === this._mouseDownEvent.target) {
$.data(event.target, this.widgetName + ".preventClickEvent", true);
}
this._mouseStop(event);
}
return false;
}
If you change the OR (||) operator by an AND (&&) it'll work fine. Off course it's not a little change, I had been testing it and until this moment I haven't find any problem at all. The line would be like this:
return !($.isFunction(callback) && callback.apply(this.element[0], [event].concat(data)) === false && event.isDefaultPrevented());
As I said, its not 100% secure, but until now I didn't find a reason to keep || instead of &&. I'll keep testing it for a few days.
Besides I've already sent an email to the lead developer from jquery ui asking about it.
Similar problem for me using it with isomorphic smartclient.
I fixed it by handling the start event and resetting isDefaultPrevented to false
$(element).draggable({
start: function (event, ui) {
event.isDefaultPrevented = function () { return false; }
}
});
I had the same problem and found this issue at github.
the shown solution using event delegation worked fine for me:
$(document).hammer().on('touch transform transformend', '#main', function() {...});

Jquery slideshow starts with an image on the left of a row but I want it to start at the right end

I'm using this javascript and the slide show slides right to left with the images in this order and positon:
start postion > 1 | 2 | 3 | 4 | 5 | 6 etc etc
but I want to swap them so they run in this position
6 | 5 | 4 | 3 | 2 | 1 < start position
Kind of like reading a book back to front, but keeping it in the right order
I've been told I need to modify the lines labelled below: //MODIFY ME
I hope someone can help! Thank you
Here's my code
(function($) {
$.fn.slideshow = function(method) {
if ( this[0][method] ) {
return this[0][ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return this.each(function() {
var ANIMATION_DURATION = .6; // The duration to flick the content. In seconds.
var MOVE_THRESHOLD = 10; // Since touch points can move slightly when initiating a click this is the
// amount to move before allowing the element to dispatch a click event.
var itemWidth;
var horizontalGap;
var $this = $(this);
var collection;
var viewItems = [];
var touchStartTransformX; // The start transformX when the user taps.
var touchStartX; // The start x coord when the user taps.
var interval; // Interval used for measuring the drag speed.
var wasContentDragged; // Flag for whether or not the content was dragged. Takes into account MOVE_THRESHOLD.
var targetTransformX; // The target transform X when a user flicks the content.
var touchDragCoords = []; // Used to keep track of the touch coordinates when dragging to measure speed.
var touchstartTarget; // The element which triggered the touchstart.
var selectedIndex = 0; // The current visible page.
var viewPortWidth; // The width of the div that holds the horizontal content.
var isAnimating;
var pageChangedLeft;
// The x coord when the items are reset.
var resetX;
var delayTimeout;
init(method);
function init(options) {
collection = options.data;
renderer = options.renderer;
itemWidth = options.itemWidth;
horizontalGap = options.horizontalGap;
initLayout();
$this[0].addEventListener("touchstart", touchstartHandler);
$this[0].addEventListener("mousedown", touchstartHandler);
viewPortWidth = $this.width();
$this.on("webkitTransitionEnd", transitionEndHandler);
collection.on("add", addItem);
}
// MODIFY ME
function initLayout() {
// Layout five items. The one in the middle is always the selected one.
for (var i = 0; i < 5; i++) {
var viewItem;
if (i > 1 && collection.at(i - 2)) // Start at the one in the middle. Subtract 2 so data index starts at 0.
viewItem = new renderer({model: collection.at(i - 2)});
else
viewItem = new renderer();
viewItem.render().$el.appendTo($this);
viewItem.$el.css("left", itemWidth * i + horizontalGap * i);
viewItem.setState(i != 2 ? "off" : "on");
viewItems.push(viewItem);
}
// Center the first viewItem
resetX = itemWidth * 2 - ($this.width() - itemWidth - horizontalGap * 4) / 2;
setTransformX(-resetX);
}
function getCssLeft($el) {
var left = $el.css("left");
return Number(left.split("px")[0]);
}
// MODIFY ME
function transitionEndHandler() {
if (pageChangedLeft != undefined) {
var viewItem;
if (pageChangedLeft) {
// Move the first item to the end.
viewItem = viewItems.shift();
viewItems.push(viewItem);
viewItem.model = collection.at(selectedIndex + 2);
viewItem.$el.css("left", getCssLeft(viewItems[3].$el) + itemWidth + horizontalGap);
} else {
// Move the last item to the beginning.
viewItem = viewItems.pop();
viewItems.splice(0, 0, viewItem);
viewItem.model = collection.at(selectedIndex - 2);
viewItem.$el.css("left", getCssLeft(viewItems[1].$el) - itemWidth - horizontalGap);
}
viewItem.render();
// Reset the layout of the items.
for (var i = 0; i < 5; i++) {
var viewItem = viewItems[i];
viewItem.$el.css("left", itemWidth * i + horizontalGap * i);
viewItem.setState(i != 2 ? "off" : "on");
}
// Reset the transformX so we don't run into any rendering limits. Can't find a definitive answer for what the limits are.
$this.css("-webkit-transition", "none");
setTransformX(-resetX);
pageChangedLeft = undefined;
}
}
function touchstartHandler(e) {
clearInterval(interval);
wasContentDragged = false;
transitionEndHandler();
// Prevent the default so the window doesn't scroll and links don't open immediately.
e.preventDefault();
// Get a reference to the element which triggered the touchstart.
touchstartTarget = e.target;
// Check for device. If not then testing on desktop.
touchStartX = window.Touch ? e.touches[0].clientX : e.clientX;
// Get the current transformX before the transition is removed.
touchStartTransformX = getTransformX();
// Set the transformX before the animation is stopped otherwise the animation will go to the end coord
// instead of stopping at its current location which is where the drag should begin from.
setTransformX(touchStartTransformX);
// Remove the transition so the content doesn't tween to the spot being dragged. This also moves the animation to the end.
$this.css("-webkit-transition", "none");
// Create an interval to monitor how fast the user is dragging.
interval = setInterval(measureDragSpeed, 20);
document.addEventListener("touchmove", touchmoveHandler);
document.addEventListener("touchend", touchendHandler);
document.addEventListener("mousemove", touchmoveHandler);
document.addEventListener("mouseup", touchendHandler);
}
function measureDragSpeed() {
touchDragCoords.push(getTransformX());
}
function touchmoveHandler(e) {
var deltaX = (window.Touch ? e.touches[0].clientX : e.clientX) - touchStartX;
if (wasContentDragged || Math.abs(deltaX) > MOVE_THRESHOLD) { // Keep track of whether or not the user dragged.
wasContentDragged = true;
setTransformX(touchStartTransformX + deltaX);
}
}
function touchendHandler(e) {
document.removeEventListener("touchmove", touchmoveHandler);
document.removeEventListener("touchend", touchendHandler);
document.removeEventListener("mousemove", touchmoveHandler);
document.removeEventListener("mouseup", touchendHandler);
clearInterval(interval);
e.preventDefault();
if (wasContentDragged) { // User dragged more than MOVE_THRESHOLD so transition the content.
var previousX = getTransformX();
var bSwitchPages;
// Compare the last 5 coordinates
for (var i = touchDragCoords.length - 1; i > Math.max(touchDragCoords.length - 5, 0); i--) {
if (touchDragCoords[i] != previousX) {
bSwitchPages = true;
break;
}
}
// User dragged more than halfway across the screen.
if (!bSwitchPages && Math.abs(touchStartTransformX - getTransformX()) > (viewPortWidth / 2))
bSwitchPages = true;
if (bSwitchPages) {
if (previousX > touchStartTransformX) { // User dragged to the right. go to previous page.
if (selectedIndex > 0) { // Make sure user is not on the first page otherwise stay on the same page.
selectedIndex--;
tweenTo(touchStartTransformX + itemWidth + horizontalGap);
pageChangedLeft = false;
} else {
tweenTo(touchStartTransformX);
pageChangedLeft = undefined;
}
} else { // User dragged to the left. go to next page.
if (selectedIndex + 1 < collection.length) {// Make sure user is not on the last page otherwise stay on the same page.
selectedIndex++;
tweenTo(touchStartTransformX - itemWidth - horizontalGap);
pageChangedLeft = true;
} else {
tweenTo(touchStartTransformX);
pageChangedLeft = undefined;
}
}
} else {
tweenTo(touchStartTransformX);
pageChangedLeft = undefined;
}
} else { // User dragged less than MOVE_THRESHOLD trigger a click event.
var event = document.createEvent("MouseEvents");
event.initEvent("click", true, true);
touchstartTarget.dispatchEvent(event);
}
}
// Returns the x of the transform matrix.
function getTransformX() {
var transformArray = $this.css("-webkit-transform").split(","); // matrix(1, 0, 0, 1, 0, 0)
var transformElement = $.trim(transformArray[4]); // remove the leading whitespace.
return transformX = Number(transformElement); // Remove the ).
}
// Sets the x of the transform matrix.
function setTransformX(value) {
$this.css("-webkit-transform", "translateX("+ Math.round(value) + "px)");
}
function tweenTo(value) {
isAnimating = true;
targetTransformX = value;
// Set the style for the transition.
$this.css("-webkit-transition", "-webkit-transform " + ANIMATION_DURATION + "s");
// Need to set the timing function each time -webkit-transition is set.
// The transition is set to ease-out.
$this.css("-webkit-transition-timing-function", "cubic-bezier(0, 0, 0, 1)");
setTransformX(targetTransformX);
}
// MODIFY ME
function addItem(folio) {
clearTimeout(delayTimeout);
// Create a timeout in case multiple items are added in the same frame.
// When the timeout completes all of the view items will have their model
// updated. The renderer should check to make sure the model is different
// before making any changes.
delayTimeout = setTimeout(function(folio) {
var index = collection.models.indexOf(folio);
var dataIndex = index;
var firstIndex = selectedIndex - 2;
var dataIndex = firstIndex;
var viewItem;
for (var i = 0; i < viewItems.length; i++) {
viewItem = viewItems[i];
if (dataIndex >= 0 && dataIndex < collection.length) {
viewItem.model = collection.at(dataIndex);
viewItem.render();
}
viewItem.setState(i != 2 ? "off" : "on");
dataIndex += 1;
}
}, 200);
}
// Called when the data source has changed. Resets the view with the new data source.
this.setData = function(data) {
$this.empty();
viewItems = [];
collection = data;
selectedIndex = 0;
initLayout();
}
});
} else {
$.error( 'Method ' + method + ' does not exist on Slideshow' );
}
}
})(jQuery);
From what I can make out, you need to simply "flip" the loops that create the sides in the slideshow so that it makes the last slide where it was making the first. It seems to do this in two places.
Then, you will need to amend the code which adds a slide to make it add it before the other slides instead of after.
This sounds an awful lot like homework - it's always best to attempt an answer before asking on here. An example on a site like JSFiddle is also generally appreciated.

How do I in vanilla javascript: selectors,events and the need of $(this)

I have 3 pictures cropped by span.main{overflow:hidden}. User can pan the span with touch events and explore the hidden parts of the picture.
Code so far:
document.addEventListener('DOMContentLoaded', function() {
var box = document.querySelector('.main');
box.addEventListener("touchstart", onStart, false);
box.addEventListener("touchmove", onMove, false);
box.addEventListener("touchend", onEnd, false);
});
var startOffsetX, startOffsetY;
var moving = false;
function getPos(ev) {
return {
x: ev.touches ? ev.touches[0].clientX : ev.clientX,
y: ev.touches ? ev.touches[0].clientY : ev.clientY
};
}
function onStart(ev) {
moving = true;
var box = document.querySelector('.main');// I need something like $(this)
var pos = getPos(ev);
startOffsetX = pos.x + box.scrollLeft;
startOffsetY = pos.y + box.scrollTop;
if (ev.preventDefault)
ev.preventDefault();
else
ev.returnValue = false;
}
function onMove(ev) {
if (moving) {
var pos = getPos(ev);
var x = startOffsetX - pos.x;
var y = startOffsetY - pos.y;
var box = document.querySelector('.main'); // I need something like $(this)
box.scrollLeft = x;
box.scrollTop = y;
if (ev.preventDefault)
ev.preventDefault();
else
ev.returnValue = false;
}
}
function onEnd(ev) {
if (moving) {
moving = false;
}
}
The problem is that only the first thumbnail works as expected. I've tried:
-querySelector only returns the first element so if I add ID's and querySelector('#box1,#box2,#box3') should work. Nein. I thing I have a 'this' problem on the functions...
-Place events (as Apple suggests) inline <div class="box" onStart="ontouchstartCallback( ev);" ontouchend="onEnd( ev );"ontouchmove="onMove( ev );" > <img></div> looked like a solution yet...My guess, because of 'this' again...
You want to use the querySelectorAll method instead. It returns all matched elements in the subtree instead of only the first one (which is what querySelector does). Then loop through them using a for loop.
var elements = document.querySelectorAll('.main');
for (var i = 0, ii = elements.length; i < ii; ++i) {
var element = elements[i];
element.ontouchstart = onStart;
// ...
}
The other approach you can take (and it is probably a better one) is to use event delegation and set the event listeners on a parent element and decide which of the pictures is being manipulated by checking the target property of each event.
<div id="pictures">
<span class="main"><img /></span>
<span class="main"><img /></span>
<span class="main"><img /></span>
</div>
var parent = document.getElementById('pictures');
parent.ontouchstart = function (e) {
var box = e.target.parentNode; // parentNode because e.target is an Image
if (box.className !== 'main') return;
onStart(e, box);
};

How do I listen for triple clicks in JavaScript?

If this is for a double-click:
window.addEventListener("dblclick", function(event) { }, false);
How can I capture a triple-click? This is for a pinned tab in Google Chrome.
You need to write your own triple-click implementation because no native event exists to capture 3 clicks in a row. Fortunately, modern browsers have event.detail, which the MDN documentation describes as:
A count of consecutive clicks that happened in a short amount of time, incremented by one.
This means you can simply check the value of this property and see if it is 3:
window.addEventListener('click', function (evt) {
if (evt.detail === 3) {
alert('triple click!');
}
});
Working demo: http://jsfiddle.net/L6d0p4jo/
If you need support for IE 8, the best approach is to capture a double-click, followed by a triple-click — something like this, for example:
var timer, // timer required to reset
timeout = 200; // timer reset in ms
window.addEventListener("dblclick", function (evt) {
timer = setTimeout(function () {
timer = null;
}, timeout);
});
window.addEventListener("click", function (evt) {
if (timer) {
clearTimeout(timer);
timer = null;
executeTripleClickFunction();
}
});
Working demo: http://jsfiddle.net/YDFLV/
The reason for this is that old IE browsers will not fire two consecutive click events for a double click. Don't forget to use attachEvent in place of addEventListener for IE 8.
Since DOM Level 2 you could use mouse click handler and check the detail parameter of event which should be interpreted as:
The detail attribute inherited from UIEvent indicates the number of times a mouse button has been pressed and released over the same screen location during a user action. The attribute value is 1 when the user begins this action and increments by 1 for each full sequence of pressing and releasing. If the user moves the mouse between the mousedown and mouseup the value will be set to 0, indicating that no click is occurring.
So the value of detail === 3 will give you the triple-click event.
More information in specification at http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-MouseEvent.
Thanks to #Nayuki https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail - a DOM3 extension which is WIP https://w3c.github.io/uievents/
Here is the real Triple click event, which triggers only when all of three clicks fired with equal interval.
// Default settings
var minClickInterval = 100,
maxClickInterval = 500,
minPercentThird = 85.0,
maxPercentThird = 130.0;
// Runtime
var hasOne = false,
hasTwo = false,
time = [0, 0, 0],
diff = [0, 0];
$('#btn').on('click', function() {
var now = Date.now();
// Clear runtime after timeout fot the 2nd click
if (time[1] && now - time[1] >= maxClickInterval) {
clearRuntime();
}
// Clear runtime after timeout fot the 3rd click
if (time[0] && time[1] && now - time[0] >= maxClickInterval) {
clearRuntime();
}
// Catch the third click
if (hasTwo) {
time[2] = Date.now();
diff[1] = time[2] - time[1];
var deltaPercent = 100.0 * (diff[1] / diff[0]);
if (deltaPercent >= minPercentThird && deltaPercent <= maxPercentThird) {
alert("Triple Click!");
}
clearRuntime();
}
// Catch the first click
else if (!hasOne) {
hasOne = true;
time[0] = Date.now();
}
// Catch the second click
else if (hasOne) {
time[1] = Date.now();
diff[0] = time[1] - time[0];
(diff[0] >= minClickInterval && diff[0] <= maxClickInterval) ?
hasTwo = true : clearRuntime();
}
});
var clearRuntime = function() {
hasOne = false;
hasTwo = false;
time[0] = 0;
time[1] = 0;
time[2] = 0;
diff[0] = 0;
diff[1] = 0;
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Click button three times with equal interval
<button id="btn">Click me</button>
Also, I wrote jquery plugin TrplClick, which enables 'trplclick' event
it's very simple if you do it right, and you can even catch single, double, triple, ... clicks as you like. plain javascript, customizable click delay (timeout):
var clicks = 0;
var timer, timeout = 350;
var doubleClick = function(e) {
console.log('doubleClick');
}
var tripleClick = function(e) {
console.log('tripleClick');
}
// click timer
yourcontainer.addEventListener('click', function(e) {
clearTimeout(timer);
clicks++;
var evt = e;
timer = setTimeout(function() {
if(clicks==2) doubleClick(evt);
if(clicks==3) tripleClick(evt);
clicks = 0;
}, timeout);
});
pseudo-code:
var clicks = 0
onclick:
clicks++;
setTimer(resetClicksToZero);
if clicks == 3: tripleclickdetected(); clicks = 0;
I am working on a javascript code editor and I had to listen for triple click and here is the solution that will work for most browsers:
// Function to get mouse position
var getMousePosition = function (mouseEvent) {
var currentObject = container;
var currentLeft = 0;
var currentTop = 0;
do {
currentLeft += currentObject.offsetLeft;
currentTop += currentObject.offsetTop;
currentObject = currentObject.offsetParent;
} while (currentObject != document.body);
return {
x: mouseEvent.pageX - currentLeft,
y: mouseEvent.pageY - currentTop
}
}
// We will need a counter, the old position and a timer
var clickCounter = 0;
var clickPosition = {
x: null,
y: null
};
var clickTimer;
// The listener (container may be any HTML element)
container.addEventListener('click', function (event) {
// Get the current mouse position
var mousePosition = getMousePosition(event);
// Function to reset the data
var resetClick = function () {
clickCounter = 0;
var clickPosition = {
x: null,
y: null
};
}
// Function to wait for the next click
var conserveClick = function () {
clickPosition = mousePosition;
clearTimeout(clickTimer);
clickTimer = setTimeout(resetClick, 250);
}
// If position has not changed
if (clickCounter && clickPosition.x == mousePosition.x && clickPosition.y == mousePosition.y) {
clickCounter++;
if (clickCounter == 2) {
// Do something on double click
} else {
// Do something on triple click
resetClick();
}
conserveClick();
} else {
// Do something on single click
conserveClick();
}
});
Tested on Firefox 12, Google Chrome 19, Opera 11.64, Internet Explorer 9
This approach checks if the user has not changed cursor's position, you still can do something when you have single click or double click. Hope this solution will help everybody who will need to implement a triple click event listener :)
Configurable n-clicks event detector factory
const nClicks = (minClickStreak, maxClickInterval = 500, resetImmediately = true) => {
let timerId = 0
let clickCount = 0
let lastTarget = null
const reset = () => {
timerId = 0
clickCount = 0
lastTarget = null
}
return (originalEventHandler) => (e) => {
if (lastTarget == null || lastTarget == e.target) { // 2. unless we clicked same target
clickCount++ // 3. then increment click count
clearTimeout(timerId)
}
lastTarget = e.target
timerId = setTimeout(reset, maxClickInterval) // 1. reset state within set time
if (clickCount >= minClickStreak) {
originalEventHandler(e)
if (resetImmediately) {
clickCount = 0
}
}
}
}
Usage
table.addEventListener('click', nClicks(2)(e => { // double click
selectCell(e.target)
}))
table.addEventListener('click', nClicks(3)(e => { // triple click
selectRow(e.target)
}))

Categories