JavaXT
|
|
Utils Classif(!javaxt) var javaxt={}; if(!javaxt.dhtml) javaxt.dhtml={}; if(!javaxt.dhtml.calendar) javaxt.dhtml.calendar={}; javaxt.dhtml.calendar.Utils = { monthNames : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], dayNames : ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], //************************************************************************** //** getDaysBetween //************************************************************************** /** Returns the number of days between 2 dates. Returns a decimal value. */ getDaysBetween : function(startDate, endDate){ function treatAsUTC(date) { var result = new Date(date); result.setMinutes(result.getMinutes() - result.getTimezoneOffset()); return result; } var millisecondsPerDay = 24 * 60 * 60 * 1000; return (treatAsUTC(endDate) - treatAsUTC(startDate)) / millisecondsPerDay; }, //************************************************************************** //** addColSpan //************************************************************************** /** Used to add a colspan to a given column. */ addColSpan : function(td, newSpan){ if (newSpan<2) return; //Add colspan td.colSpan = newSpan; var deleteCells = function(startCol, numCells){ if (startCol==null) return; var td = startCol; var tr = td.parentNode; var x = 0; var del = []; while (x<numCells){ var colSpan = td.colSpan; if (colSpan==null || colSpan<2) colSpan=1; x = x + colSpan; del.push(td); td = td.nextSibling; if (td==null) break; } for (var i=0; i<del.length; i++){ tr.removeChild(del[i]); } }; //Delete cells that we've spanned deleteCells(td.nextSibling, newSpan-1); }, //************************************************************************** //** getRect //************************************************************************** /** Returns the geometry of a given element. */ getRect : function(el){ if (el.getBoundingClientRect){ return el.getBoundingClientRect(); } else{ var x = 0; var y = 0; var w = el.offsetWidth; var h = el.offsetHeight; function isNumber(n){ return n === parseFloat(n); } var org = el; do{ x += el.offsetLeft - el.scrollLeft; y += el.offsetTop - el.scrollTop; } while ( el = el.offsetParent ); el = org; do{ if (isNumber(el.scrollLeft)) x -= el.scrollLeft; if (isNumber(el.scrollTop)) y -= el.scrollTop; } while ( el = el.parentNode ); return{ left: x, right: x+w, top: y, bottom: y+h, width: w, height: h }; } }, //************************************************************************** //** initDrag //************************************************************************** /** Used to enable/initialize event dragging. Drag events are initiated * after a predefined holdDelay. * @param holdDelay Number of milliseconds to wait before recognizing a hold */ initDrag : function(div, view, holdDelay){ //This timeout, started on mousedown, triggers the beginning of a hold var holdStarter = null; //This flag indicates the user is currently holding the mouse down var holdActive = false; //OnClick //div.onclick = NOTHING!! not using onclick at all - onmousedown and onmouseup take care of everything //MouseDown div.onmousedown = function(e){ //Set the holdStarter and wait for the predetermined delay, and then begin a hold holdStarter = setTimeout(function() { holdStarter = null; holdActive = true; //Initiate drag startDrag(e); //Add event listeners if (document.addEventListener) { // For all major browsers, except IE 8 and earlier document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); } else if (document.attachEvent) { // For IE 8 and earlier versions document.attachEvent("onmousemove", onMouseMove); document.attachEvent("onmouseup", onMouseUp); } }, holdDelay); }; //MouseUp var onMouseUp = function(e){ //Remove javaxt-cal-event-drag class from the event div var innerDiv = getInnerDiv(div); innerDiv.className = innerDiv.className.replace( /(?:^|\s)javaxt-cal-event-drag(?!\S)/g , '' ); //Remove z-index div.style.zIndex = ''; //If the mouse is released immediately (i.e., a click), before the //holdStarter runs, then cancel the holdStarter and do the click if (holdStarter) { clearTimeout(holdStarter); var listener = view.getListener('eventclick'); if (listener!=null){ var callback = listener.callback; var scope = listener.scope; callback.apply(scope, [div.event, div, view, e]); } } //Otherwise, if the mouse was being held, end the hold else if (holdActive) { holdActive = false; //Remove event listeners if (document.removeEventListener) { // For all major browsers, except IE 8 and earlier document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); } else if (document.detachEvent) { // For IE 8 and earlier versions document.detachEvent("onmousemove", onMouseMove); document.detachEvent("onmouseup", onMouseUp); } //Update cursor div.style.cursor = 'pointer'; //Move div moveDiv(div); //Remove the "javaxt-noselect" class var body = document.getElementsByTagName('body')[0]; body.className = body.className.replace( /(?:^|\s)javaxt-noselect(?!\S)/g , '' ); } }; div.onmouseup = onMouseUp; //Touch-specifc variables var x1, x2, y1, y2; var scrollableDiv, scrollOffset; var scroll = function(e){ x2 = e.changedTouches[0].pageX; y2 = e.changedTouches[0].pageY; if (scrollableDiv && holdActive===false){ scrollableDiv.scrollTop = scrollOffset+(y1-y2); } }; var endScroll = function(){ if (document.removeEventListener) { document.removeEventListener("touchmove", scroll); document.removeEventListener("touchend", endScroll); } else if (document.detachEvent) { document.detachEvent("ontouchmove", scroll); document.detachEvent("ontouchend", endScroll); } }; //Start touch (similar to "onmousedown") div.ontouchstart = function(e) { //Prevent default "select" behavior. Also, disables scrolling //if the client starts scrolling over an event div e.preventDefault(); //Get coordinates var touch = e.touches[0]; var x = touch.pageX; var y = touch.pageY; x1 = e.changedTouches[0].pageX; y1 = e.changedTouches[0].pageY; //Add logic to manage scroll events scrollableDiv = view.getScrollDiv(); if (scrollableDiv){ scrollOffset = scrollableDiv.scrollTop; if (document.addEventListener) { document.addEventListener("touchmove", scroll); document.addEventListener("touchend", endScroll); } else if (document.attachEvent) { document.attachEvent("ontouchmove", scroll); document.attachEvent("ontouchend", endScroll); } } //Disable scrolling in the view view.disableTouch(); //Set the holdStarter and wait for the holdDelay before starting the drag holdStarter = setTimeout(function() { holdStarter = null; //Check whether the client's finger has moved more than 10 pixels. //If so the client is probably scrolling. var distance = Math.sqrt( (x2-=x1)*x2 + (y2-=y1)*y2 ); if (distance<0) distance = -distance; if (distance>10){ holdActive = false; return; } holdActive = true; //Initiate drag startDrag({ clientX: x, clientY: y }); //Add "touchmove" event listener if (document.addEventListener) { div.addEventListener("touchmove", onTouchMove); } else if (document.attachEvent) { div.attachEvent("ontouchmove", onTouchMove); } }, holdDelay); }; //End touch (similar to "onmouseup") div.ontouchend = function(e) { //Enable scrolling in the view view.enableTouch(); //If the mouse is released immediately (i.e., a click), before the //holdStarter runs, then cancel the holdStarter and do the click if (holdStarter) { clearTimeout(holdStarter); //run click-only operation here //console.log("Click!"); var listener = view.getListener('eventclick'); if (listener!=null){ var callback = listener.callback; var scope = listener.scope; var touch = e.changedTouches[0]; var x = touch.pageX; var y = touch.pageY; callback.apply(scope, [div.event, div, view, { clientX: x, clientY: y }]); } } //Otherwise, if the mouse was being held, end the hold else if (holdActive) { holdActive = false; moveDiv(div); //Remove javaxt-cal-event-drag class from the event div var innerDiv = getInnerDiv(div); innerDiv.className = innerDiv.className.replace( /(?:^|\s)javaxt-cal-event-drag(?!\S)/g , '' ); //Remove z-index div.style.zIndex = ''; //Remove "touchmove" event listener if (document.removeEventListener) { div.removeEventListener("touchmove", onTouchMove); } else if (document.detachEvent) { div.detachEvent("ontouchmove", onTouchMove); } } }; var onMouseMove = function(e){ var x = e.clientX; var y = e.clientY; div.style.left = (x-div.xOffset) + 'px'; div.style.top = (y-div.yOffset) + 'px'; }; var onTouchMove = function(e) { e.preventDefault(); var touch = e.touches[0]; var x = touch.pageX; var y = touch.pageY; onMouseMove({ clientX: x, clientY: y }); }; var startDrag = function(e){ var x = e.clientX; var y = e.clientY; var rect = _getRect(div); var top = rect.top-parseInt(div.style.marginTop); var xOffset = x-rect.left; var yOffset = view.hasHours() ? (rect.height/2) : y-top; var parentNode = div.parentNode; var orgStyle = { width: div.style.width, left: div.style.left, top: div.style.top, rect: rect }; div.orgStyle = orgStyle; div.orgParent = parentNode; div.xOffset = xOffset; div.yOffset = yOffset; //Disable text selection in the entire document - very important! var body = document.getElementsByTagName('body')[0]; if (!body.className.match(/(?:^|\s)javaxt-noselect(?!\S)/) ){ body.className += (body.className.length==0 ? "" : " ") + "javaxt-noselect"; } //Remove div from the current cell and append it to the body var nextSibling = div.nextSibling; parentNode.removeChild(div); body.appendChild(div); //Add placeholder div as needed (e.g. month view) if (view.hasHours()==false && nextSibling!=null){ var placeHolderDiv = document.createElement("div"); placeHolderDiv.style.width = "100%"; placeHolderDiv.style.height = rect.height + "px"; placeHolderDiv.style.marginTop = div.style.marginTop; parentNode.insertBefore(placeHolderDiv, nextSibling); placeHolderDiv.event = div.event; } div.style.position = "absolute"; div.style.width = rect.width + 'px'; div.style.left = rect.left + 'px'; div.style.top = (y-yOffset) + 'px'; div.style.cursor = 'move'; div.style.zIndex = getNextHighestZindex(); //Add javaxt-cal-event-drag class to the event div var innerDiv = getInnerDiv(div); innerDiv.className += " javaxt-cal-event-drag"; }; /** Used to move an event from cell to cell. */ var moveDiv = function(div){ //Compute geometry of the div var rect = _getRect(div); var minY = rect.top; //Generate list of cells that intersect the four corners of the div var cells = getCells(rect); //If the div doesn't intersect any cells in the view, return the div //to its original location. if (cells.length==0){ returnDiv(div); return; } //Select cell to move to var cell; if (cells.length==1){ cell = cells[0]; } else{ //Find the cell with the largest area of intersection var intersections = {}; var keys = []; for (var i=0; i<cells.length; i++){ var cell = cells[i]; var area = getAreaOfIntersection(rect, cell); keys.push(area); intersections[area] = cell; } keys.sort(function(a,b){return b-a}); cell = intersections[keys[0]]; } //Find the event associated with this div and the original cell ID var event = div.event; var d = event.getStartDate(); var currID = (d.getMonth()+1) + "-" + d.getDate() + "-" + d.getFullYear(); //Move the div if (cell.id==currID){ if (view.hasHours()){ //Compute distance that the div has moved. If the div has only //moved by a few pixels in the y direction, cancel the move. var a = 1; //disregard changes in x var b = minY - div.orgStyle.rect.top; var d = Math.sqrt( a*a + b*b ); if (d<15){ returnDiv(div); return; } //Update start/end time var startDate = event.getStartDate(); var endDate = event.getEndDate(); setTime(getTime(minY+5, div), startDate, endDate); //+5 is for cell padding... moveEvent(event, startDate, endDate); } else{ //Return the div to its original position returnDiv(div); } } else{ //move div to a new cell //Compute new event start/end dates var arr = cell.id.split("-"); var startDate = event.getStartDate(); startDate.setFullYear(parseInt(arr[2])); startDate.setMonth(parseInt(arr[0])-1); startDate.setDate(parseInt(arr[1])); var diffDays = Math.round((startDate.getTime() - event.getStartDate().getTime())/(24*60*60*1000)); var endDate = event.getEndDate(); endDate.setDate(endDate.getDate()+diffDays); //Update start/end time if (view.hasHours()){ setTime(getTime(minY+5, div), startDate, endDate); } moveEvent(event, startDate, endDate); } }; /** Used to update the start/end date of an event and render it in a given cell. */ var moveEvent = function(event, startDate, endDate){ var fn = function(move){ if (move==true){ var parentNode = div.parentNode; parentNode.removeChild(div); //Remove the event from the current cell. Do this before updating //the start/end date. Otherwise, we might end up with holes/gaps //between events in the cell. view.removeEvent(event); //Update the event start/end dates event.setStartDate(startDate); event.setEndDate(endDate); //Add the event to the cell view.addEvent(event); //Call the aftermove callback listener = view.getListener('aftermove'); if (listener!=null) listener.callback.apply(listener.scope, [event, view]); } else{ returnDiv(div); } }; //Call the beforemove callback var listener = view.getListener('beforemove'); if (listener!=null) listener.callback.apply(listener.scope, [event, new Date(startDate), new Date(endDate), fn]); else fn(true); }; /** Used to cancel a div move and return the div to its original position. */ var returnDiv = function(div){ var parentNode = div.parentNode; parentNode.removeChild(div); var orgStyle = div.orgStyle; div.style.left = orgStyle.left; div.style.top = orgStyle.top; div.style.width = orgStyle.width; if (view.hasHours()){ div.orgParent.appendChild(div); } else{ //Make the div relative div.style.position = "relative"; //Find placeholder div var placeholderDiv = null; var event = div.event; for (var i=0; i<div.orgParent.childNodes.length; i++){ if (div.orgParent.childNodes[i].event.equals(event)){ placeholderDiv = div.orgParent.childNodes[i]; break; } } //Return div to its original parent if (placeholderDiv==null){ div.orgParent.appendChild(div); } else{ //Replace placeholder div with the original div var nextDiv = placeholderDiv.nextSibling; if (nextDiv==null){ div.orgParent.removeChild(placeholderDiv); div.orgParent.appendChild(div); } else{ div.orgParent.insertBefore(div, placeholderDiv); div.orgParent.removeChild(placeholderDiv); } } } div.orgParent = null; div.orgStyle = null; }; /** Find the inner div with class="javaxt-cal-event" */ var getInnerDiv = function(div){ var el = div; while (el.childNodes.length>0){ var firstChild = el.childNodes[0]; if (firstChild.className.match(/(?:^|\s)javaxt-cal-event(?!\S)/) ){ return firstChild; } else{ el = firstChild; } } }; /** Returns a list of cells that intersect the four corners of a given rectangle. */ var getCells = function(rect){ var minX = rect.left; var maxX = rect.right; var minY = rect.top; var maxY = rect.bottom; //Generate list of cells that intersect the four corners of the div var cells = []; var addCell = function(cell){ if (cell==null) return; for (var i=0; i<cells.length; i++){ if (cells[i].id==cell.id) return; } cells.push(cell); }; addCell(getCell(minX, minY)); //ul addCell(getCell(minX, maxY)); //ll addCell(getCell(maxX, maxY)); //lr addCell(getCell(maxX, minY)); //ur return cells; }; /** Returns a cell at a given x,y location within the view. */ var getCell = function(x,y){ var cells = view.getCells(); for (var i=0; i<cells.length; i++){ var cell = cells[i]; var rect = cell.rect; var minX = rect.left; var maxX = rect.right; if (x>=minX && x<=maxX){ var minY = rect.top; var maxY = rect.bottom; if (y>=minY && y<=maxY){ return cell; } } } return null; }; /** Returns the total area that a given rectangle intersects a cell. */ var getAreaOfIntersection = function(r1, cell){ var rect = cell.rect; var minX = rect.left; var maxX = rect.right; var minY = rect.top; var maxY = rect.bottom; var left = r1.left; var right = r1.right; var top = r1.top; var bottom = r1.bottom; if (left<minX) left=minX; if (right>maxX) right=maxX; if (top<minY) top=minY; if (bottom>maxY) bottom=maxY; var w = right-left; var h = bottom-top; return w*h; }; /** Returns the time represented by a given y coordinate in the view. * The returned value is a decimal (e.g. 9.5 representing 9:30 AM). */ var getTime = function(y, div){ var rows = []; var parentNode = div.orgParent; for (var i=0; i<parentNode.childNodes.length; i++){ var el = parentNode.childNodes[i]; var tagName = el.tagName.toLowerCase(); if (tagName=="table"){ var tbody = el.childNodes[0]; for (var j=0; j<tbody.childNodes.length; j++){ rows.push(tbody.childNodes[j]); } } } for (var i=0; i<rows.length; i++){ var rect = _getRect(rows[i]); var minY = rect.top; var maxY = rect.bottom; if (y>=minY && y<=maxY){ return (i/2); } } return null; }; /** Used to update a given start and end date with a given time. The time * variable must be a decimal value (e.g. 9.5 representing 9:30 AM). */ var setTime = function(time, startDate, endDate){ var diff = time - (startDate.getHours() + (startDate.getMinutes()/60)); var h = Math.floor(diff); var m = Math.abs((diff % 1)*60); startDate.setHours(startDate.getHours()+h); startDate.setMinutes(startDate.getMinutes()+m); endDate.setHours(endDate.getHours()+h); endDate.setMinutes(endDate.getMinutes()+m); }; var _getRect = javaxt.dhtml.calendar.Utils.getRect; var getNextHighestZindex = function(obj){ var highestIndex = 0; var currentIndex = 0; var elArray = Array(); if(obj){elArray = obj.getElementsByTagName('*');}else{elArray = document.getElementsByTagName('*');} for(var i=0; i < elArray.length; i++){ if (elArray[i].currentStyle){ currentIndex = parseFloat(elArray[i].currentStyle['zIndex']); }else if(window.getComputedStyle){ currentIndex = parseFloat(document.defaultView.getComputedStyle(elArray[i],null).getPropertyValue('z-index')); } if(!isNaN(currentIndex) && currentIndex > highestIndex){highestIndex = currentIndex;} } return(highestIndex+1); }; } }; |