Drag and drop with javascript - javascript

I just tried a walkthrough for creating drag and drop-enabled features. I followed the guide at http://www.webreference.com/programming/javascript/mk/column2/index.html
Even though the guide is well written I cant get this to function, any obvious mistakes here? All of my code is pasted below and the guide authors comments are there as well to clarify. When I run the page displays all the items but nothing happens when I try moving them.
<%# Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<script type="text/javascript">
// iMouseDown represents the current mouse button state: up or down
/*
lMouseState represents the previous mouse button state so that we can
check for button clicks and button releases:
if(iMouseDown && !lMouseState) // button just clicked!
if(!iMouseDown && lMouseState) // button just released!
*/
var mouseOffset = null;
var iMouseDown = false;
var lMouseState = false;
var dragObject = null;
// Demo 0 variables
var DragDrops = [];
var curTarget = null;
var lastTarget = null;
var dragHelper = null;
var tempDiv = null;
var rootParent = null;
var rootSibling = null;
Number.prototype.NaN0 = function () { return isNaN(this) ? 0 : this; }
function CreateDragContainer() {
/*
Create a new "Container Instance" so that items from one "Set" can not
be dragged into items from another "Set"
*/
var cDrag = DragDrops.length;
DragDrops[cDrag] = [];
/*
Each item passed to this function should be a "container". Store each
of these items in our current container
*/
for (var i = 0; i < arguments.length; i++) {
var cObj = arguments[i];
DragDrops[cDrag].push(cObj);
cObj.setAttribute('DropObj', cDrag);
/*
Every top level item in these containers should be draggable. Do this
by setting the DragObj attribute on each item and then later checking
this attribute in the mouseMove function
*/
for (var j = 0; j < cObj.childNodes.length; j++) {
// Firefox puts in lots of #text nodes...skip these
if (cObj.childNodes[j].nodeName == '#text') continue;
cObj.childNodes[j].setAttribute('DragObj', cDrag);
}
}
}
function mouseMove(ev) {
ev = ev || window.event;
/*
We are setting target to whatever item the mouse is currently on
Firefox uses event.target here, MSIE uses event.srcElement
*/
var target = ev.target || ev.srcElement;
var mousePos = mouseCoords(ev);
// mouseOut event - fires if the item the mouse is on has changed
if (lastTarget && (target !== lastTarget)) {
// reset the classname for the target element
var origClass = lastTarget.getAttribute('origClass');
if (origClass) lastTarget.className = origClass;
}
/*
dragObj is the grouping our item is in (set from the createDragContainer function).
if the item is not in a grouping we ignore it since it can't be dragged with this
script.
*/
var dragObj = target.getAttribute('DragObj');
// if the mouse was moved over an element that is draggable
if (dragObj != null) {
// mouseOver event - Change the item's class if necessary
if (target != lastTarget) {
var oClass = target.getAttribute('overClass');
if (oClass) {
target.setAttribute('origClass', target.className);
target.className = oClass;
}
}
// if the user is just starting to drag the element
if (iMouseDown && !lMouseState) {
// mouseDown target
curTarget = target;
// Record the mouse x and y offset for the element
rootParent = curTarget.parentNode;
rootSibling = curTarget.nextSibling;
mouseOffset = getMouseOffset(target, ev);
// We remove anything that is in our dragHelper DIV so we can put a new item in it.
for (var i = 0; i < dragHelper.childNodes.length; i++) dragHelper.removeChild(dragHelper.childNodes[i]);
// Make a copy of the current item and put it in our drag helper.
dragHelper.appendChild(curTarget.cloneNode(true));
dragHelper.style.display = 'block';
// set the class on our helper DIV if necessary
var dragClass = curTarget.getAttribute('dragClass');
if (dragClass) {
dragHelper.firstChild.className = dragClass;
}
// disable dragging from our helper DIV (it's already being dragged)
dragHelper.firstChild.removeAttribute('DragObj');
/*
Record the current position of all drag/drop targets related
to the element. We do this here so that we do not have to do
it on the general mouse move event which fires when the mouse
moves even 1 pixel. If we don't do this here the script
would run much slower.
*/
var dragConts = DragDrops[dragObj];
/*
first record the width/height of our drag item. Then hide it since
it is going to (potentially) be moved out of its parent.
*/
curTarget.setAttribute('startWidth', parseInt(curTarget.offsetWidth));
curTarget.setAttribute('startHeight', parseInt(curTarget.offsetHeight));
curTarget.style.display = 'none';
// loop through each possible drop container
for (var i = 0; i < dragConts.length; i++) {
with (dragConts[i]) {
var pos = getPosition(dragConts[i]);
/*
save the width, height and position of each container.
Even though we are saving the width and height of each
container back to the container this is much faster because
we are saving the number and do not have to run through
any calculations again. Also, offsetHeight and offsetWidth
are both fairly slow. You would never normally notice any
performance hit from these two functions but our code is
going to be running hundreds of times each second so every
little bit helps!
Note that the biggest performance gain here, by far, comes
from not having to run through the getPosition function
hundreds of times.
*/
setAttribute('startWidth', parseInt(offsetWidth));
setAttribute('startHeight', parseInt(offsetHeight));
setAttribute('startLeft', pos.x);
setAttribute('startTop', pos.y);
}
// loop through each child element of each container
for (var j = 0; j < dragConts[i].childNodes.length; j++) {
with (dragConts[i].childNodes[j]) {
if ((nodeName == '#text') || (dragConts[i].childNodes[j] == curTarget)) continue;
var pos = getPosition(dragConts[i].childNodes[j]);
// save the width, height and position of each element
setAttribute('startWidth', parseInt(offsetWidth));
setAttribute('startHeight', parseInt(offsetHeight));
setAttribute('startLeft', pos.x);
setAttribute('startTop', pos.y);
}
}
}
}
}
// If we get in here we are dragging something
if (curTarget) {
// move our helper div to wherever the mouse is (adjusted by mouseOffset)
dragHelper.style.top = mousePos.y - mouseOffset.y;
dragHelper.style.left = mousePos.x - mouseOffset.x;
var dragConts = DragDrops[curTarget.getAttribute('DragObj')];
var activeCont = null;
var xPos = mousePos.x - mouseOffset.x + (parseInt(curTarget.getAttribute('startWidth')) / 2);
var yPos = mousePos.y - mouseOffset.y + (parseInt(curTarget.getAttribute('startHeight')) / 2);
// check each drop container to see if our target object is "inside" the container
for (var i = 0; i < dragConts.length; i++) {
with (dragConts[i]) {
if (((getAttribute('startLeft')) < xPos) &&
((getAttribute('startTop')) < yPos) &&
((getAttribute('startLeft') + getAttribute('startWidth')) > xPos) &&
((getAttribute('startTop') + getAttribute('startHeight')) > yPos)) {
/*
our target is inside of our container so save the container into
the activeCont variable and then exit the loop since we no longer
need to check the rest of the containers
*/
activeCont = dragConts[i];
// exit the for loop
break;
}
}
}
// Our target object is in one of our containers. Check to see where our div belongs
if (activeCont) {
// beforeNode will hold the first node AFTER where our div belongs
var beforeNode = null;
// loop through each child node (skipping text nodes).
for (var i = activeCont.childNodes.length - 1; i >= 0; i--) {
with (activeCont.childNodes[i]) {
if (nodeName == '#text') continue;
// if the current item is "After" the item being dragged
if (
curTarget != activeCont.childNodes[i] &&
((getAttribute('startLeft') + getAttribute('startWidth')) > xPos) &&
((getAttribute('startTop') + getAttribute('startHeight')) > yPos)) {
beforeNode = activeCont.childNodes[i];
}
}
}
// the item being dragged belongs before another item
if (beforeNode) {
if (beforeNode != curTarget.nextSibling) {
activeCont.insertBefore(curTarget, beforeNode);
}
// the item being dragged belongs at the end of the current container
} else {
if ((curTarget.nextSibling) || (curTarget.parentNode != activeCont)) {
activeCont.appendChild(curTarget);
}
}
// make our drag item visible
if (curTarget.style.display != '') {
curTarget.style.display = '';
}
} else {
// our drag item is not in a container, so hide it.
if (curTarget.style.display != 'none') {
curTarget.style.display = 'none';
}
}
}
// track the current mouse state so we can compare against it next time
lMouseState = iMouseDown;
// mouseMove target
lastTarget = target;
// track the current mouse state so we can compare against it next time
lMouseState = iMouseDown;
// this helps prevent items on the page from being highlighted while dragging
return false;
}
function mouseUp(ev) {
if (curTarget) {
// hide our helper object - it is no longer needed
dragHelper.style.display = 'none';
// if the drag item is invisible put it back where it was before moving it
if (curTarget.style.display == 'none') {
if (rootSibling) {
rootParent.insertBefore(curTarget, rootSibling);
} else {
rootParent.appendChild(curTarget);
}
}
// make sure the drag item is visible
curTarget.style.display = '';
}
curTarget = null;
iMouseDown = false;
}
function mouseDown() {
iMouseDown = true;
if (lastTarget) {
return false;
}
}
document.onmousemove = mouseMove;
document.onmousedown = mouseDown;
document.onmouseup = mouseUp;
window.onload = function () {
// Create our helper object that will show the item while dragging
dragHelper = document.createElement('DIV');
dragHelper.style.cssText = 'position:absolute;display:none;';
CreateDragContainer(
document.getElementById('DragContainer1'),
document.getElementById('DragContainer2'),
document.getElementById('DragContainer3')
);
document.body.appendChild(dragHelper);
}
function mouseCoords(ev) {
if (ev.pageX || ev.pageY) {
return { x: ev.pageX, y: ev.pageY };
}
return {
x: ev.clientX + document.body.scrollLeft - document.body.clientLeft,
y: ev.clientY + document.body.scrollTop - document.body.clientTop
};
}
</script>
<title>Drag and drop test</title>
</head>
<body>
<form id="form1" runat="server">
<!--the mouse over and dragging class are defined on each item-->
<div class="DragContainer" id="DragContainer1">
<div class="DragBox" id="Item1" overClass="OverDragBox" dragClass="DragDragBox">Item #1</div>
<div class="DragBox" id="Item2" overClass="OverDragBox" dragClass="DragDragBox">Item #2</div>
<div class="DragBox" id="Item3" overClass="OverDragBox" dragClass="DragDragBox">Item #3</div>
<div class="DragBox" id="Item4" overClass="OverDragBox" dragClass="DragDragBox">Item #4</div>
</div>
<div class="DragContainer" id="DragContainer2">
<div class="DragBox" id="Item5" overClass="OverDragBox" dragClass="DragDragBox">Item #5</div>
<div class="DragBox" id="Item6" overClass="OverDragBox" dragClass="DragDragBox">Item #6</div>
<div class="DragBox" id="Item7" overClass="OverDragBox" dragClass="DragDragBox">Item #7</div>
<div class="DragBox" id="Item8" overClass="OverDragBox" dragClass="DragDragBox">Item #8</div>
</div>
<div class="DragContainer" id="DragContainer3">
<div class="DragBox" id="Item9" overClass="OverDragBox" dragClass="DragDragBox">Item #9</div>
<div class="DragBox" id="Item10" overClass="OverDragBox" dragClass="DragDragBox">Item #10</div>
<div class="DragBox" id="Item11" overClass="OverDragBox" dragClass="DragDragBox">Item #11</div>
<div class="DragBox" id="Item12" overClass="OverDragBox" dragClass="DragDragBox">Item #12</div>
</div>
</form>
</body>
</html>

Did you miss some javascript references? Chrome is screaming that it does not find a function getMouseOffset (line 95)

Related

Javascript run 2 functions on button click with multiple buttons and same class

I have a portfolio grid that has a screenshot of past work in each grid item.
Currently on button click, it calls a function to scroll the screenshot and stop once it reaches the bottom of the image.
I need to reverse the scroll once the button is clicked again. The scroll is created by a setInterval. I have a class of "down" on the button which is removed on click.
I have an if statement that does not work to check if the class of "down" is present and run a scrollUp function.
This is a PHP loop so there are multiple buttons with same class.
I cannot use jQuery.
Thanks for any help!
HTML:
<ul>
<li>
<div class="image-container overflow-hidden height-500">
<img class="item absolute pin-t w-full h-auto pin-l"
src="/image.jpg"/>
</div>
<button class="portScroll down">Scroll Down</button>
</li>
<li class="web-design-portfolio">
<div class="image-container overflow-hidden height-500">
<img class="item absolute pin-t w-full h-auto pin-l"
src="/image.jpg"/>
</div>
<button class="portScroll down">Scroll Down</button>
</li>
</ul>
JS:
function scrollDown() {
var portImg = this.parentNode.parentNode.querySelector('img.item');
var height = portImg.clientHeight;
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == height - 500) {
clearInterval(id);
} else {
pos++;
portImg.style.top = - + pos + 'px';
}
}
for (const button of document.querySelectorAll('button.portScroll')) {
button.classList.remove('down');
}
}
for (const button of document.querySelectorAll('button.portScroll')) {
if (button.classList.contains("down")) {
button.addEventListener("click", scrollDown);
} else {
button.addEventListener("click", scrollUp);
}
}
Here is the working Codepen for scroll down:
https://codepen.io/completewebco/pen/bZeVoz
I created a single function that does what is needed. I hope that is OK.
function scrollUpOrDown(_this, state) {
var portImg = _this.parentNode.parentNode.querySelector('img.item');
var height = portImg.clientHeight;
if(state.id > -1) {
clearInterval(state.id);
state.dir *= -1;
}
if(state.pos < 0) {
state.pos = 1;
}
state.id = setInterval(frame, 5);
function frame() {
if ((state.pos == height - 500 && state.dir > 0) || (state.pos == 0 && state.dir < 0)) {
clearInterval(state.id);
} else {
state.pos += state.dir;
portImg.style.top = -+state.pos + "px";
}
}
}
for (const button of document.querySelectorAll("button.portScroll")) {
let scollingState = {
pos: -1,
id: -1,
dir: 1
};
if (button.classList.contains("down")) {
button.addEventListener("click", function(){
scrollUpOrDown(this,scollingState);
});
}
}
https://codepen.io/prtjohanson/pen/QoEyQK
If you are wondering why/how it works, look at the output of this code
var array = [0,1,2,3,4,5];
for(const i in array) {
setTimeout(function(){console.log(i)}, Math.random()*1000)
}
and read https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Scoping_rules_2

Getting a position of an element in a 2D array

So I'm building a a turn based board game which needs to contain 2 player that can move across the map. I'm stuck at getting the position of the player(which is just a simple div element) inside of that 2D array. I've tried using indexOf, but even tho it's placed inside an onclick function, always returns 0.
The html code contains just of few div's with col classes:
And here is the JavaScript code (btw it contains some unnecessary stuff that I've just added for test purposes) :
let row = document.querySelector('.row');
let fields = document.getElementsByClassName('col-md-2')
let fieldsArr = Array.from(fields);
let header = document.getElementById("clicked");
let cols = header.getElementsByClassName("col-md-2");
let player = document.getElementById('player');
let player2 = document.getElementById('player2');
let blockedField = document.getElementsByClassName('blocked');
fieldsArr.sort(function() {
return 0.5 - Math.random();
}).forEach(function(el) {
row.appendChild(el);
});
// ADD AN EVENT LISTENER AND LISTEN FOR COLS ID
function replyClick(e) {
e = e || window.event;
e = e.target || e.srcElement;
if (e.nodeName === 'DIV') {
let changable = e.id;
//console.log(changable);
}
}
// CREATE A 2D ARRAY (MAP)
var map = [];
while (fieldsArr.length) map.push(fieldsArr.splice(0, 6));
// ON CLICK ADD A CLASS OF ACTIVE
for (var i = 0; i < cols.length; i++) {
cols[i].addEventListener("click", function() {
var current = document.getElementsByClassName("active");
current[0].className = current[0].className.replace(" active", "");
this.className += " active";
});
}
// MOVE PLAYER ONE ACROSS THE MAP
function movePlayer(multWidth, multHeight) {
$(".active").append(player);
if ((row).click > multWidth) {
alert(1)
}
}
// MOVE PLAYER 2 ACROSS THE MAP
function movePlayer2() {
$(".active").append(player2);
}
// MAKE GRAYED OUT FIELD UNAVALIABLE AND SHOW AN ALERT
$(blockedField).css("pointer-events", "none");
// APPEND PLAYER1(2) TO THE FIRST(LAST) FIELD ON THE MAP
map[0][0].appendChild(player);
map[5][5].appendChild(player2);
// GET PLAYERS CURRENT POSITION
$(row).click(function() {
let current = player.offsetTop;
});
const widthAllowed = 3 * 156;
const heightAllowed = 3 * 146;
// LIMIT PLAYER MOVES
let player1Moves = 3;
player2Moves = 3;
$(row).click(function() {
movePlayer();
let remainingMoves = player1Moves -= 1;
if (remainingMoves === 0) {
alert("You don't have any more moves. Player's 2 turn.");
$(player).css("pointer-events", "none");
$(row).click(movePlayer2);
}
})
for (var x = 0; x < map.length; x++) {
for (var y = 0; y < map[x].length; y++) {
console.log(x, y);
}
}
console.log(map);
console.log(map[2][5]);
console.log(map[5][0]);
You should take some beginner jquery/javascript course, since your question is very simple and you will find the whole programming thing way easier with a few basic concepts (like selectors, events and callbacks)
That said, here is a basic example of how to return the div element that contains the player element and how to use event attachment instead of inline events.
let row = $('.row');
row.on('click', replyClick);
function replyClick(e) {
var targetRow = $(e.target);
$('.row > div.active').removeClass('active');
targetRow.addClass('active');
var player = $('.row div.player');
alert(player.parent().attr('id'));
};
.player {
width: 20px;
height: 20px;
background: red;
}
.row > div {
padding: 10px;
width: 20px;
height: 20px;
border: 1px solid red;
}
.row > div.active {
background: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="row">
<div id="col1" class="col-md-2">
<div class="player"></div>
</div>
<div id="col2" class="col-md-2 blocked"></div>
<div id="col3" class="col-md-2 active"></div>
<div id="col4" class="col-md-2"></div>
</div>
</div>

JS function that scrolls an element into view taking into account possible scrollable and positioned parent

I was looking for a function that would scroll a given element into view with some smart behavior:
if an element is descendant of a scrollable element - that ancestor is scrolled rather than body.
if an element is descendant of a positioned element - body won't be scrolled.
I didn't find any suitable function, so I made one and wanted some expert opinion on it. Please check the plunkr http://plnkr.co/edit/DNGWLh5cH1Cr1coZbwpa?p=preview . There are problems with animated scroll in FF, so please use Chrome to check the logic.
To illustrate, what I'm looking for - here is the first update that came to mind - if we reached an element that can scroll, lets call it SC (Scroll Parent), we should not only scroll SC to make the target visible inside it, but also recursively scroll SC itself into view, since it may outside of the currently visible are of the page. Here is the update plunkr http://plnkr.co/edit/DNGWLh5cH1Cr1coZbwpa?p=preview (also applied fix for FF scrolling problem).
And here is the code of the function
function scrollTo(target){
//Position delta is used for scrollable elements other than BODY
var combinedPositionDelta = 0;
var previousParent = $(target);
var parent = $(target).parent();
while(parent){
combinedPositionDelta += previousParent.position().top - parent.position().top;
//If we reached body
if(parent.prop("tagName").toUpperCase() == "BODY"){
scrollBody(target.offset().top);
break;
}
//if we reached an element that can scroll
if(parent[0].scrollHeight > parent.outerHeight()){
scrollElementByDelta(parent,combinedPositionDelta);
//Recursively scroll parent into view, since it itself might not be visible
scrollTo(parent);
break;
}
//if we reached a apositioned element - break
if(parent.css('position').toUpperCase() != 'STATIC'){
console.log("Stopping due to positioned parent " + parent[0].outerHTML);
break;
}
previousParent = parent;
parent = parent.parent();
}
}
var offsetSkin = 20;
function scrollElementByDelta(element,offsetDelta){
$(element).animate({
scrollTop: element.scrollTop() + (offsetDelta - offsetSkin)
}, 1000);
}
function scrollBody(offset){
$('body,html').animate({
scrollTop: offset - offsetSkin
}, 1000);
}
Well I'm Using this one which works very well for me:
function scrollIntoView (element, alignTop) {
var document = element.ownerDocument;
var origin = element, originRect = origin.getBoundingClientRect();
var hasScroll = false;
var documentScroll = this.getDocumentScrollElement(document);
while (element) {
if (element == document.body) {
element = documentScroll;
} else {
element = element.parentNode;
}
if (element) {
var hasScrollbar = (!element.clientHeight) ? false : element.scrollHeight > element.clientHeight;
if (!hasScrollbar) {
if (element == documentScroll) {
element = null;
}
continue;
}
var rects;
if (element == documentScroll) {
rects = {
left : 0,
top : 0
};
} else {
rects = element.getBoundingClientRect();
}
// check that elementRect is in rects
var deltaLeft = originRect.left - (rects.left + (parseInt(element.style.borderLeftWidth, 10) | 0));
var deltaRight = originRect.right
- (rects.left + element.clientWidth + (parseInt(element.style.borderLeftWidth, 10) | 0));
var deltaTop = originRect.top - (rects.top + (parseInt(element.style.borderTopWidth, 10) | 0));
var deltaBottom = originRect.bottom
- (rects.top + element.clientHeight + (parseInt(element.style.borderTopWidth, 10) | 0));
// adjust display depending on deltas
if (deltaLeft < 0) {
element.scrollLeft += deltaLeft;
} else if (deltaRight > 0) {
element.scrollLeft += deltaRight;
}
if (alignTop === true && !hasScroll) {
element.scrollTop += deltaTop;
} else if (alignTop === false && !hasScroll) {
element.scrollTop += deltaBottom;
} else {
if (deltaTop < 0) {
element.scrollTop += deltaTop;
} else if (deltaBottom > 0) {
element.scrollTop += deltaBottom;
}
}
if (element == documentScroll) {
element = null;
} else {
// readjust element position after scrolls, and check if vertical scroll has changed.
// this is required to perform only one alignment
var nextRect = origin.getBoundingClientRect();
if (nextRect.top != originRect.top) {
hasScroll = true;
}
originRect = nextRect;
}
}
}
}
I hope this helps.
If you do not mind venturing into jQuery, the scrollTo plugin is the best bet. It handles most needs and gives a very refined smooth trasition.
Hope it helps.

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);
};

Categories