Files
mrbs-equbao-2026/web/js/resizable.js.php
T
人事系统开发 48092cab42
Docker image / push (push) Canceled after 0s
MRBS 1.12.2 等保2.0二级整改完整提交
包含:登录失败锁定、90天密码有效期、30分钟会话超时、
强制改密、登录审计日志、屏幕水印、企业背景图、
备案信息固定底部、favicon、登录页JS修复等全部改动
2026-09-08 21:19:47 +08:00

1812 lines
62 KiB
PHP

<?php
declare(strict_types=1);
namespace MRBS;
require "../defaultincludes.inc";
http_headers(array("Content-type: application/x-javascript"),
60*30); // 30 minute expiry
?>
'use strict';
<?php // function to reverse a collection of jQuery objects ?>
$.fn.reverse = [].reverse;
<?php
// Get the sides, optionally including the border, of the rectangle represented by
// the jQuery object obj.The result object is indexed by 'n', 'w', 's' and 'e' as
// well as 'top', 'left', 'bottom' and 'right'.
//
// [Note: this depends on the object having box-sizing of content-box, as
// jQuery has problems getting the correct dimensions when using border-box
// and the browser zoom level is not 100%. If you need to use border-box,
// then JavaScript's getBoundingClientRect() works, but remember that it
// returns positions relative to the viewport and not the document.]
?>
function getSides(obj, includeBorder)
{
var result = {};
if (includeBorder)
{
result.n = obj.offset().top;
result.w = obj.offset().left;
result.s = result.n + obj.outerHeight();
result.e = result.w + obj.outerWidth();
}
else
{
<?php
// We need to use parseFloat instead of parseInt because the CSS width may be
// a float, especially if the browser zoom level is not 100%.
?>
result.n = obj.offset().top + parseFloat(obj.css('border-top-width'));
result.w = obj.offset().left + parseFloat(obj.css('border-left-width'));
result.s = result.n + obj.innerHeight();
result.e = result.w + obj.innerWidth();
}
result.top = result.n;
result.left = result.w;
result.bottom = result.s;
result.right = result.e;
return result;
}
<?php // Checks to see whether two rectangles occupy the same space ?>
function rectanglesIdentical(r1, r2)
{
<?php // Allow some tolerance for fractional pixels ?>
return ((Math.floor(Math.abs(r1.n - r2.n)) <= 1) &&
(Math.floor(Math.abs(r1.s - r2.s)) <= 1) &&
(Math.floor(Math.abs(r1.e - r2.e)) <= 1) &&
(Math.floor(Math.abs(r1.w - r2.w)) <= 1));
}
<?php // Checks whether two rectangles overlap ?>
function rectanglesOverlap(r1, r2)
{
<?php
// If none of the sides of r1 is clear of the opposite side of r2, then the two
// rectangles overlap. We allow the sides to be on top of each other (ie we use
// < and > instead of <= and >=).
?>
return ((Math.round(r1.n - r2.s) < 0) &&
(Math.round(r1.s - r2.n) > 0) &&
(Math.round(r1.w - r2.e) < 0) &&
(Math.round(r1.e - r2.w) > 0));
}
<?php
// Gets the side-most side of the array of rectangles.
// side can be 'n', 's', 'e' or 'w'
?>
function getClosestSide(rectangles, side)
{
var result = null;
rectangles.forEach(function(rectangle) {
if (result === null)
{
result = rectangle[side];
}
else if ((side === 'e') || (side === 's'))
{
result = Math.max(result, rectangle[side]);
}
else
{
result = Math.min(result, rectangle[side]);
}
});
return result;
}
<?php
// Get the name of the data attribute in this jQuery object.
?>
function getDataName(jqObject)
{
var possibleNames = ['room', 'date', 'seconds'];
for (var i=0; i<possibleNames.length; i++)
{
if (jqObject.data(possibleNames[i]) !== undefined)
{
return possibleNames[i];
}
}
return false;
}
<?php
// The object ui.element is only altered when the resize stops, so we can only
// get its current position and size using ui.position and ui.size. This function
// creates an invisible element in the DOM with the same position and size as
// ui.element would have when resizing stops. This clone is useful for passing to
// functions that expect a jQuery object looking like the eventual ui.element.
?>
function uiDummyClone(ui)
{
return $('<div></div>')
.css('position', 'absolute')
.appendTo($('body'))
.offset(ui.position)
.width(ui.size.width)
.height(ui.size.height);
}
var Table = {
selector: ".dwm_main",
borderLeftWidth: undefined,
borderTopWidth: undefined,
bookedMap: [],
grid: {},
<?php
// Return the parameters for the booking represented by el
// The result is an object with property of the data name (eg
// 'seconds', 'time', 'room') and each property is an array of
// the values for that booking (for example an array of room ids)
?>
getBookingParams: function(el) {
var rtl = ($(Table.selector).css('direction').toLowerCase() === 'rtl'),
params = {},
data,
tolerance = 2, <?php //px ?>
cell = {x: {}, y: {}},
i,
axis;
cell.x.start = el.offset().left;
cell.y.start = el.offset().top;
cell.x.end = cell.x.start + el.outerWidth();
cell.y.end = cell.y.start + el.outerHeight();
for (axis in cell)
{
if (cell.hasOwnProperty(axis))
{
data = Table.grid[axis].data;
if (params[Table.grid[axis].key] === undefined)
{
params[Table.grid[axis].key] = [];
}
if (rtl && (axis==='x'))
{
for (i = data.length - 1; i >= 0; i--)
{
if ((data[i].coord + tolerance) < cell[axis].start)
{
<?php
// 'seconds' behaves slightly differently to the other parameters:
// we need to know the end time for the new slot. Also it's possible
// for us to have a zero element, eg when selecting a new booking, and if
// so we need to make sure there's something returned
?>
if ((Table.grid[axis].key === 'seconds') ||
(params[Table.grid[axis].key].length === 0))
{
params[Table.grid[axis].key].push(data[i].value);
}
break;
}
if ((data[i].coord + tolerance) < cell[axis].end)
{
params[Table.grid[axis].key].push(data[i].value);
}
}
}
else
{
for (i=0; i<data.length; i++)
{
if ((data[i].coord + tolerance) > cell[axis].end)
{
<?php
// 'seconds' behaves slightly differently to the other parameters:
// we need to know the end time for the new slot. Also it's possible
// for us to have a zero element, eg when selecting a new booking, and if
// so we need to make sure there's something returned
?>
if ((Table.grid[axis].key === 'seconds') ||
(params[Table.grid[axis].key].length === 0))
{
params[Table.grid[axis].key].push(data[i].value);
}
break;
}
if ((data[i].coord + tolerance) > cell[axis].start)
{
params[Table.grid[axis].key].push(data[i].value);
}
} <?php // for ?>
}
}
} <?php // for (axis in cell) ?>
return params;
}, <?php // getBookingParams() ?>
getRowNumber: function(y) {
for (var i=0; i<Table.grid.y.data.length - 1; i++)
{
if ((y >= Table.grid.y.data[i].coord) &&
(y < Table.grid.y.data[i+1].coord))
{
return i;
}
}
return null;
}, <?php // getRowNumber ?>
<?php // Remove any highlighting that has been applied to the row labels ?>
clearRowLabels: function() {
if (Table.highlightRowLabels.rows !== undefined)
{
for (var i=0; i < Table.highlightRowLabels.rows.length; i++)
{
Table.highlightRowLabels.rows[i].removeClass('selected');
}
}
}, <?php // clearRowLabels ?>
<?php
// function to highlight the row labels in the table that are level
// with the element el
?>
highlightRowLabels: function (el) {
if (Table.highlightRowLabels.rows === undefined)
{
<?php // Cache the row label cells in an array ?>
Table.highlightRowLabels.rows = [];
$(Table.selector).find('tbody tr').each(function() {
Table.highlightRowLabels.rows.push($(this).find('th'));
});
}
var elStartRow = Table.getRowNumber(el.offset().top);
var elEndRow = Table.getRowNumber(el.offset().top + el.outerHeight());
for (var i=0; i<Table.highlightRowLabels.rows.length ; i++)
{
if (((elStartRow === null) || (elStartRow <= i)) &&
((elEndRow === null) || (i < elEndRow)))
{
Table.highlightRowLabels.rows[i].addClass('selected');
}
else
{
Table.highlightRowLabels.rows[i].removeClass('selected');
}
}
}, <?php // highlightRowLabels ?>
init: function() {
var table = $(Table.selector);
var container = table.parent();
<?php
// TODO: rename bookedMap to something more descriptive.
// Initialise the bookedMap, which is an array of booked slots and slots that are not
// allowed to be booked (eg if booking is not allowed at weekends). Each member of the array
// is an object with four properties (n, s, e, w) representing the coordinates (x or y)
// of the side. We will use this array to test whether a proposed
// booking overlaps an existing booking. Select just the visible cells because there
// could be hidden days.
?>
Table.bookedMap = [];
table.find('td.booked:visible').add(table.find('.not_allowed').parent()).each(function() {
Table.bookedMap.push(getSides($(this)));
});
<?php // Size the table ?>
Table.size();
Table.sizeTbodyViewport();
},
<?php
// Tests whether the point p with coordinates x and y is outside the table
?>
outside: function(p) {
var headBottoms = $(Table.selector).find('thead').map(function() {
return $(this).offset().top + $(this).outerHeight();
}).get();
<?php
// We might have floating headers in operation, in which case
// it doesn't make sense to drag in the part of the table behind
// the floating header because you can't see what's happening.
// So test to see if the cursor is above the bottom of the lowest
// table header.
?>
if (p.y < (Math.max.apply(null, headBottoms)))
{
return true;
}
return ((p.x < Table.grid.x.data[0].coord) ||
(p.y < Table.grid.y.data[0].coord) ||
(p.x > Table.grid.x.data[Table.grid.x.data.length - 1].coord) ||
(p.y > Table.grid.y.data[Table.grid.y.data.length - 1].coord) );
}, <?php // outside() ?>
<?php
// Check whether the rectangle (with sides n,s,e,w) overlaps any
// of the booked slots in the table. Returns an array of overlapped
// bookings.
// stopAtFirst (optional) If true then only the first overlap found will
// be returned. Default false.
// ignoreRectangle (optional). A rectangle that is to be ignored when checking
// for overlaps.
?>
overlapsBooked: function overlapsBooked(rectangle, stopAtFirst, ignoreRectangle)
{
var result = [];
for (var i=0; i<Table.bookedMap.length; i++)
{
if (!(ignoreRectangle && rectanglesIdentical(ignoreRectangle, Table.bookedMap[i])))
{
if (rectanglesOverlap(rectangle, Table.bookedMap[i]))
{
result.push(Table.bookedMap[i]);
if (stopAtFirst)
{
break;
}
}
}
}
return result;
},
<?php // Clip the ui.helper so that it doesn't protrude outside the table body. ?>
setClipPath: function(ui) {
const top = Table.tbodyViewport.top - ui.position.top;
const right = ui.position.left + ui.size.width - Table.tbodyViewport.right;
const bottom = ui.position.top + ui.size.height - Table.tbodyViewport.bottom;
const left = Table.tbodyViewport.left - ui.position.left;
let path = 'none';
if ((top > 0) || (right > 0) || (bottom > 0) || (left > 0))
{
<?php // Set the top, right, bottom and left offsets ?>
let offsets = [];
offsets.push((top > 0) ? top + 'px' : '0');
offsets.push((right > 0) ? right + 'px' : '0');
offsets.push((bottom > 0) ? bottom + 'px' : '0');
offsets.push((left > 0) ? left + 'px' : '0');
path = 'inset(' + offsets.join(' ') + ')';
}
ui.helper.css('clip-path', path);
},
scrollContainerBy: function(xCoord, yCoord) {
var container = $(Table.selector).parent();
<?php
// If we use 'smooth' behavior then the code has to be more complicated
// in order to prevent another mousemove set of actions being triggered
// before the scrolling has completed.
?>
container[0].scrollBy({
top: yCoord,
left: xCoord,
behavior: 'instant'
});
},
<?php
// Calculate the amount by which the table container should be scrolled given an event 'e'.
// Returns an object with 'x' and 'y' properties.
?>
scrollDelta: function(e) {
<?php
// Set the distance from the edge of the visible tbody at which we should start scrolling.
// It should not be more than half the height/width of the visible tbody
?>
const scrollGapX = Math.min(30, Math.floor((Table.tbodyViewport.right - Table.tbodyViewport.left)/2));
const scrollGapY = Math.min(30, Math.floor((Table.tbodyViewport.bottom - Table.tbodyViewport.top)/2));
const table = $(Table.selector);
const tableContainer = table.parent();
let result = {x: 0, y: 0};
<?php // First check whether we are approaching the top. ?>
if ((e.pageY - Table.tbodyViewport.top) < scrollGapY)
{
<?php // Don't go beyond the top ?>
result.y = -Math.min(scrollGapY, tableContainer.scrollTop());
}
<?php // Then whether we are approaching the bottom. ?>
else if ((Table.tbodyViewport.bottom - e.pageY) < scrollGapY)
{
<?php // Don't go beyond the bottom ?>
result.y = Math.min(scrollGapY, table.outerHeight() - tableContainer.outerHeight() - tableContainer.scrollTop());
result.y = Math.max(result.y, 0);
<?php
// In Chrome, when the browser is zoomed the pixel numbers can be floating, so round down anything less than 1.
// See https://stackoverflow.com/questions/5828275/how-to-check-if-a-div-is-scrolled-all-the-way-to-the-bottom-with-jquery
?>
if (result.y < 1)
{
result.y = 0;
}
}
<?php // Then the left hand side ?>
if ((e.pageX - Table.tbodyViewport.left) < scrollGapX)
{
<?php // Don't go beyond the left hand side ?>
result.x = -Math.min(scrollGapX, tableContainer.scrollLeft());
}
<?php // And finally the right ?>
else if ((Table.tbodyViewport.right - e.pageX) < scrollGapX)
{
<?php // Don't go beyond the bottom ?>
result.x = Math.min(scrollGapX, table.outerWidth() - tableContainer.outerWidth() - tableContainer.scrollLeft());
result.x = Math.max(result.x, 0);
<?php
// In Chrome, when the browser is zoomed the pixel numbers can be floating, so round down anything less than 1.
// See https://stackoverflow.com/questions/5828275/how-to-check-if-a-div-is-scrolled-all-the-way-to-the-bottom-with-jquery
?>
if (result.x < 1)
{
result.x = 0;
}
}
return result;
},
size: function() {
<?php // Don't do anything if this is the year view or all-rooms week view ?>
if ((args.view === 'year') || ((args.view === 'week') && args.view_all))
{
return;
}
<?php
// Get the width of the top and left borders of the first proper slot cell
// in the main table (ie ignore the row labels cell). This won't be the
// same as the value in the CSS if the browsers zoom level is not 100%.
?>
var table = $(Table.selector);
var td = table.find('tbody tr:first-child td:first-of-type');
Table.borderLeftWidth = parseFloat(td.css('border-left-width'));
Table.borderTopWidth = parseFloat(td.css('border-top-width'));
<?php
// Build an object holding all the data we need about the table, which is
// the coordinates of the cell boundaries and the names and values of the
// data attributes. The object has two properties, x and y, which in turn
// are objects containing the data for the x and y axes. Each of these
// objects has a key property which holds the name of the data attribute and a
// data object, which is an array of objects holding the coordinate and data
// value at each cell boundary.
//
// Note that jQuery.offset() measures to the top left hand corner of the content
// and does not take into account padding. So we need to make sure that the padding-top
// and padding-left is the same for all elements that we are going to measure so
// that we can compare them properly. It is simplest to use zero and put any
// padding required on the contained element.
?>
var rtl = ((table.css('direction') !== undefined) &&
(table.css('direction').toLowerCase() === 'rtl'));
var resolution = table.data('resolution');
Table.grid.x = {};
Table.grid.x.data = [];
<?php
// We need :visible because there might be hidden days
?>
var columns = table.find('thead tr:first-child th:visible').not('.first_last');
<?php
// If the table has direction rtl, as it may do if you're using a RTL language
// such as Hebrew, then the columns will have been presented in the order right
// to left and we'll need to reverse the columns.
?>
if (rtl)
{
columns.reverse();
}
columns.each(function() {
if (Table.grid.x.key === undefined)
{
Table.grid.x.key = getDataName($(this));
}
Table.grid.x.data.push({coord: $(this).offset().left,
value: $(this).data(Table.grid.x.key)});
});
<?php
// and also get the right hand edge (and also the left hand edge if the
// direction is RTL, as in Hebrew). If we're dealing with seconds
// we need to know what the end time of the slot would be
?>
if (rtl)
{
columns.filter(':first').each(function() {
var value = null;
if (Table.grid.x.key === 'seconds')
{
value = Table.grid.x.data[0].value + resolution;
}
var edge = $(this).offset().left;
Table.grid.x.data.unshift({coord: edge, value: value});
});
}
columns.filter(':last').each(function() {
var value = null;
if (Table.grid.x.key === 'seconds')
{
value = Table.grid.x.data[Table.grid.x.data.length - 1].value + resolution;
}
var edge = $(this).offset().left + $(this).outerWidth();
Table.grid.x.data.push({coord: edge, value: value});
});
Table.grid.y = {};
Table.grid.y.data = [];
var rows = table.find('tbody th:first-child');
rows.each(function() {
if (Table.grid.y.key === undefined)
{
Table.grid.y.key = getDataName($(this));
}
Table.grid.y.data.push({coord: $(this).offset().top,
value: $(this).data(Table.grid.y.key)});
});
<?php // and also get the bottom edge ?>
rows.filter(':last').each(function() {
var value = null;
if (Table.grid.y.key === 'seconds')
{
value = Table.grid.y.data[Table.grid.y.data.length - 1].value + resolution;
}
Table.grid.y.data.push({coord: $(this).offset().top + $(this).outerHeight(),
value: value});
});
}, <?php // size() ?>
<?php // Get the boundaries of the visible part of the tbody. ?>
sizeTbodyViewport: function() {
const table = $(Table.selector);
const tableContainer = table.parent();
const thead = table.find('thead');
const tfoot = table.find('tfoot');
const tfootHeight = (tfoot.length) ? tfoot.outerHeight() : 0;
const tbodyFirstRowTh = table.find('tbody tr:first th');
const tbodyRightTh = tbodyFirstRowTh.eq(2);
const tbodyRightThWidth = (tbodyRightTh.length) ? tbodyRightTh.outerWidth() : 0;
Table.tbodyViewport = {
top: tableContainer.offset().top + thead.outerHeight(),
left: tableContainer.offset().left + tbodyFirstRowTh.first().outerWidth(),
bottom: tableContainer.offset().top + tableContainer.outerHeight() - tfootHeight,
right: tableContainer.offset().left + tableContainer.outerWidth() - tbodyRightThWidth
};
}, <?php // sizeTbodyViewport() ?>
<?php
// Given an object 'obj', calculate the changes that must be made to its position,
// width and height to snap the side 'side' to the grid. ('side' can be 'left',
// 'right', 'top' or 'bottom'.) If 'force' is true, then the side is snapped regardless
// of where it is.
//
// Returns an object with properties 'top', 'left', 'width' and 'height'.
?>
snapDelta: function (obj, side, force) {
<?php // Check the side argument is valid ?>
if (!['top', 'left', 'bottom', 'right'].includes(side))
{
throw new Error("Invalid argument '" + side + "' for parameter side.")
}
<?php // Initialise the result object. By default, no change. ?>
let result = {
top: 0,
left: 0,
width: 0,
height: 0
};
const snapGap = 35, <?php // px ?>
tolerance = 2, <?php // px ?>
isLR = (side === 'left') || (side === 'right'),
data = (isLR) ? Table.grid.x.data : Table.grid.y.data;
let topLeft, bottomRight, gap, gapTopLeft, gapBottomRight;
let rectangle = obj.offset();
rectangle.bottom = rectangle.top + obj.innerHeight();
rectangle.right = rectangle.left + obj.innerWidth();
const outerWidth = rectangle.right - rectangle.left,
outerHeight = rectangle.bottom - rectangle.top,
thisCoord = rectangle[side];
for (let i=0; i<(data.length -1); i++)
{
topLeft = data[i].coord;
bottomRight = data[i+1].coord;
<?php
// Allow for the borders: .offset() includes borders.
?>
if (side === 'left')
{
topLeft += Table.borderLeftWidth;
bottomRight += Table.borderLeftWidth;
}
else if (side === 'top')
{
topLeft += Table.borderTopWidth;
bottomRight += Table.borderTopWidth;
}
gapTopLeft = thisCoord - topLeft;
gapBottomRight = bottomRight - thisCoord;
if (((gapTopLeft > 0) && (gapBottomRight > 0)) ||
<?php // containment tests ?>
((i === 0) && (gapTopLeft < 0)) ||
((i === (data.length-2)) && (gapBottomRight < 0)) )
{
gap = bottomRight - topLeft;
<?php
// If we're forcing to the top or left side, or else the gap
// to the top or left side is within snapping distance ...
?>
if ((force && ((side === 'top') || (side === 'left'))) ||
(!force && (gapTopLeft <= gap/2) && (gapTopLeft < snapGap)))
{
switch (side)
{
case 'top':
result.top = topLeft - rectangle.top;
result.height = gapTopLeft;
break;
case 'left':
result.left = topLeft - rectangle.left;
result.width = gapTopLeft;
break;
case 'bottom':
<?php // Don't let the height become zero. ?>
if ((outerHeight - gapTopLeft) < tolerance)
{
result.height = gapBottomRight;
}
else
{
result.height = -gapTopLeft;
}
break;
case 'right':
<?php // Don't let the width become zero. ?>
if ((outerWidth - gapTopLeft) < tolerance)
{
result.width = gapBottomRight;
}
else
{
result.width = -gapTopLeft;
}
break;
}
return result;
}
<?php
// If we're forcing to the bottom or right side, or else the gap
// to the bottom or right side is within snapping distance ...
?>
if ((force && ((side === 'bottom') || (side === 'right'))) ||
(!force && (gapBottomRight <= gap/2) && (gapBottomRight < snapGap)))
{
switch (side)
{
case 'top':
<?php // Don't let the height become zero. ?>
if ((outerHeight - gapBottomRight) < tolerance)
{
result.top = topLeft - rectangle.top;
result.height = gapTopLeft;
}
else
{
result.top = bottomRight - rectangle.top;
result.height = -gapBottomRight;
}
break;
case 'left':
<?php // Don't let the width become zero. ?>
if ((outerWidth - gapBottomRight) < tolerance)
{
result.left = topLeft - rectangle.left;
result.width = gapTopLeft;
}
else
{
result.left = bottomRight - rectangle.left;
result.width = -gapBottomRight;
}
break;
case 'bottom':
result.height = gapBottomRight;
break;
case 'right':
result.width = gapBottomRight;
break;
}
return result;
}
}
} <?php // for ?>
return result;
}, <?php // snapDelta() ?>
<?php
// Given the jQuery object 'obj', snap the side specified (can be 'left', 'right', 'top'
// or 'bottom') to the nearest grid line, if the side is within the snapping range.
// If force is true, then the side is snapped regardless of where it is.
?>
snapToGrid: function (obj, side, force) {
<?php
// Get the changes that must be made to the object and apply them as necessary.
?>
const delta = this.snapDelta(obj, side, force);
if ((delta.top !== 0) || (delta.left !==0))
{
const offset = obj.offset();
obj.offset({top: offset.top + delta.top, left: offset.left + delta.left});
}
if (delta.width !== 0)
{
obj.outerWidth(obj.outerWidth() + delta.width);
}
if (delta.height !== 0)
{
obj.outerHeight(obj.outerHeight() + delta.height);
}
}, <?php // snapToGrid() ?>
<?php
// Snap a jQuery UI object to the grid. Snap the side specified (can be 'left', 'right',
// 'top' or 'bottom') to the nearest grid line, if the side is within the snapping range.
// If force is true, then the side is snapped regardless of where it is.
//
// We have to provide our own snapUiToGrid function instead of using the grid
// option in the jQuery UI resize widget because our table may not have uniform
// row heights and column widths - either because overflow: hidden isn't being
// used, or just because of the way the browser lays out the table - so we can't
// specify a grid in terms of a simple array as required by the resize widget.
?>
snapUiToGrid: function (ui, side, force) {
<?php
// Get the changes that must be made to the UI element and apply them by updating
// ui.position and ui.size. The Helper element will automatically follow the
// new position and size.
?>
const obj = uiDummyClone(ui);
const delta = this.snapDelta(obj, side, force);
obj.remove(); <?php // Remove the object so it doesn't clutter the DOM. ?>
ui.position.top += delta.top;
ui.position.left += delta.left;
ui.size.width += delta.width;
ui.size.height += delta.height;
}
};
<?php
// Add scroll positions, if any, of a jQuery object to the location.
// This enables the scroll position to be preserved after a booking
// has been made. The originalScroll parameter is an object with left
// and top properties giving the original scroll positions of object.
// The scroll positions that are added are the minima of the current and
// original positions. We do this so that the top left of the booking,
// and thus the brief description is in view when the booking has been
// saved and MRBS returns to the calendar (index) page.
?>
function addScrollPosition(location, object, originalScroll)
{
if (object.isHScrollable())
{
location += '&left=' + encodeURIComponent(Math.min(object.scrollLeft(), originalScroll.left));
}
if (object.isVScrollable())
{
location += '&top=' + encodeURIComponent(Math.min(object.scrollTop(), originalScroll.top));
}
return location;
}
<?php
// Extend the jQuery UI resizable widget so that we can scroll the container. When the
// container is scrolled we need to be able to change some of the ui variables which we
// don't have access to from the standard widget.
// The extension offers two additional options:
//
// scrollableContainer A jQuery object representing the element that can be scrolled.
// Default: null
// scrollDelta A function that returns the amount by which the container must
// be scrolled on a given event. Returns an object with x and y
// properties. Default: null
?>
$.widget('ui.resizable', $.ui.resizable, {
options: {
scrollableContainer: null,
scrollDelta: null
},
_mouseDrag: function(event) {
if (this.options.scrollableContainer && this.options.scrollDelta)
{
<?php // Calculate the amount to scroll by. ?>
const delta = this.options.scrollDelta.call(this, event);
if (delta.x || delta.y)
{
<?php // Scroll the container. ?>
this.options.scrollableContainer[0].scrollBy({
top: delta.y,
left: delta.x,
behavior: 'instant'
});
<?php
// Adjust the original mouse position and the original, current and previous positions.
?>
['originalMousePosition', 'originalPosition', 'position', 'prevPosition'].forEach((property) => {
if (delta.x) {
this[property].left -= delta.x;
}
if (delta.y) {
this[property].top -= delta.y;
}
});
<?php // Adjust the position of the helper. ?>
const helperOffset = this.helper.offset();
this.helper.css({
top: (helperOffset.top - delta.y) + 'px',
left: (helperOffset.left - delta.x) + 'px'
});
<?php // Resize the table to adjust the position of existing bookings and grid lines. ?>
Table.size();
}
}
// Invoke the parent widget's _mouseDrag().
return this._super(event);
}
});
$(document).on('page_ready', function() {
<?php // Don't do anything if we're in kiosk mode ?>
if (args.kiosk)
{
return;
}
<?php
// Resizable bookings work by creating an element which is a clone of the real booking
// element and making it resizable. We can't make the real element resizable
// because it is bound by the table cell walls (THIS ISN'T TRUE ANYMORE!). So we give
// the clone an absolute position and a positive z-index. We work out what
// new booking the user is requesting by comparing the coordinates of the clone
// with the table grid. We also put the booking parameters (eg room id) as HTML5
// data attributes in the cells of the header row and the column labels, so that we
// can then get a set of parameters to send to edit_entry_handler as an Ajax request.
// The result is a JSON object containing a success/failure boolean and the new table
// HTML if successful or the reasons for failure if not.
// We set up the resizable bookings on a table load rather than a window load event
// because when we have the refresh timer going we just want to reload the table. Reloading
// the window causes other things to be re-initialised, which we don't want. For example
// if we have the datepicker open we don't want that to be reset.
?>
$(Table.selector).on('tableload', function() {
var table = $(this);
var tableContainer = table.parent();
<?php
// Don't do anything if this is an empty table, or the all-rooms week view,
// or the month or year view
?>
if ((args.view === 'year') ||
(args.view === 'month') ||
((args.view === 'week') && args.view_all) ||
table.find('tbody').data('empty'))
{
return;
}
Table.init();
var mouseDown = false;
var downHandler = function(e) {
<?php // Ignore the click if the user is not allowed to make a boooking in this slot. ?>
if ($(e.target).hasClass('not_allowed'))
{
return;
}
mouseDown = true;
<?php // Save the original scroll position ?>
downHandler.originalScroll = {
left: tableContainer.scrollLeft(),
top: tableContainer.scrollTop()
};
<?php
// Apply a class so that we know that resizing is in progress, eg to turn off
// highlighting
?>
table.addClass('resizing');
var jqTarget = $(e.target);
<?php // If we've landed on the + symbol we want the parent ?>
if (e.target.nodeName.toLowerCase() === "img")
{
jqTarget = jqTarget.parent();
}
downHandler.origin = jqTarget.offset();
downHandler.firstPosition = {x: e.pageX, y: e.pageY};
<?php
// Get the original link in case we need it later. We can't be sure whether
// the target was the <a> or the <td> so play safe and get all possibilities
?>
downHandler.originalLink = jqTarget.find('a').addBack('a').attr('href');
downHandler.box = $('<div class="div_select">');
if (!args.isBookAdmin)
{
<?php
// If we're not an admin and we're not allowed to book repeats (in
// the week view) or select multiple rooms (in the day view) then
// constrain the box to fit in the current slot width/height
?>
if (((args.view === 'week') && <?php echo ($auth['only_admin_can_book_repeat']) ? 'true' : 'false'?>) ||
((args.view === 'day') && <?php echo ($auth['only_admin_can_select_multiroom']) ? 'true' : 'false'?>))
{
<?php
if ($times_along_top)
{
?>
var slotHeight = jqTarget.outerHeight();
downHandler.maxHeightSet = true;
downHandler.box.css('max-height', slotHeight + 'px');
<?php
}
else
{
?>
var slotWidth = jqTarget.outerWidth();
downHandler.maxWidthSet = true;
downHandler.box.css('max-width', slotWidth + 'px');
<?php
}
?>
}
}
<?php // Attach the element to the table container before setting the offset ?>
tableContainer.append(downHandler.box);
downHandler.box.offset(downHandler.origin);
};
var moveHandler = function(e) {
<?php
// Don't do anything if the mouse button isn't considered to be down, eg
// if the user wasn't allowed to make a booking in the origin slot.
?>
if (!mouseDown)
{
return;
}
<?php
// Check to see if we're only allowed to go one slot wide/high
// and have gone over that limit. If so, do nothing and return
?>
if ((downHandler.maxWidthSet && (e.pageX < downHandler.origin.left)) ||
(downHandler.maxHeightSet && (e.pageY < downHandler.origin.top)))
{
return;
}
const box = downHandler.box;
const oldBoxOffset = box.offset();
const oldBoxWidth = box.outerWidth();
const oldBoxHeight = box.outerHeight();
const delta = Table.scrollDelta(e);
<?php // Scroll the table if necessary ?>
if (delta.x || delta.y)
{
Table.scrollContainerBy(delta.x, delta.y);
<?php
// Need to resize the table after a scroll because the coordinates
// of the grid lines and booked cells will have changed.
// TODO: Optimise performance by just recording the cumulative x- and
// TODO: y-deltas and then use those in snapToGrid() etc.?
?>
Table.size();
<?php
// Because we've scrolled we need to correct the positions of
// downHandler.firstPosition and oldBoxOffset.
?>
downHandler.firstPosition.x -= delta.x;
downHandler.firstPosition.y -= delta.y;
oldBoxOffset.left -= delta.x;
oldBoxOffset.top -= delta.y;
}
<?php // Otherwise redraw the box ?>
if (e.pageX < downHandler.firstPosition.x)
{
if (e.pageY < downHandler.firstPosition.y)
{
box.offset({top: e.pageY, left: e.pageX});
}
else
{
box.offset({top: downHandler.firstPosition.y, left: e.pageX});
}
}
else if (e.pageY < downHandler.firstPosition.y)
{
box.offset({top: e.pageY, left: downHandler.firstPosition.x});
}
else
{
box.offset({top: downHandler.firstPosition.y, left: downHandler.firstPosition.x});
}
box.width(Math.abs(e.pageX - downHandler.firstPosition.x));
box.height(Math.abs(e.pageY - downHandler.firstPosition.y));
<?php
// Snap the box to grid boundaries if it's close, and even if it's not
// if you're dragging away from that edge.
?>
const draggingDown = (e.pageY > downHandler.firstPosition.y);
const draggingRight = (e.pageX > downHandler.firstPosition.x);
Table.snapToGrid(box, 'top', draggingDown);
Table.snapToGrid(box, 'left', draggingRight);
Table.snapToGrid(box, 'bottom', !draggingDown);
Table.snapToGrid(box, 'right', !draggingRight);
<?php
// If the new box overlaps a booked cell, then undo the changes
// We set stopAtFirst=true because we just want to know if there is
// *any* overlap.
?>
if (Table.overlapsBooked(getSides(box), true).length)
{
box.offset(oldBoxOffset)
.width(oldBoxWidth)
.height(oldBoxHeight);
}
<?php
// Check to see if we've moved outside the table and if we have
// then give some visual feedback. If we've moved back into the box
// remove the feedback.
?>
if (Table.outside({x: e.pageX, y: e.pageY}))
{
if (!moveHandler.outside)
{
box.addClass('outside');
moveHandler.outside = true;
Table.clearRowLabels();
}
}
else if (moveHandler.outside)
{
box.removeClass('outside');
moveHandler.outside = false;
}
<?php
// Highlight the corresponding row label cells (provided we are
// inside the table)
?>
if (!moveHandler.outside)
{
Table.highlightRowLabels(box);
}
};
var upHandler = function(e) {
<?php
// Don't do anything if the mouse button isn't considered to be down, eg
// if the user wasn't allowed to make a booking in the origin slot.
?>
if (!mouseDown)
{
return;
}
mouseDown = false;
e.preventDefault();
var tolerance = 2; <?php // px ?>
var box = downHandler.box;
var params = Table.getBookingParams(box);
$(document).off('mousemove',moveHandler);
$(document).off('mouseup', upHandler);
<?php
// If the user has released the button while outside the table it means
// they want to cancel, so just return.
?>
if (Table.outside({x: e.pageX, y: e.pageY}))
{
box.remove();
$(Table.selector).removeClass('resizing');
return;
}
<?php
// If the user has hardly moved the mouse then just treat this as a
// traditional click and follow the original link. This will mean
// that things such as the default duration are used.
?>
else if ((Math.abs(e.pageX - downHandler.firstPosition.x) <= tolerance) &&
(Math.abs(e.pageY - downHandler.firstPosition.y) <= tolerance))
{
if (downHandler.originalLink !== undefined)
{
window.location = addScrollPosition(downHandler.originalLink, tableContainer, downHandler.originalScroll);
}
else
{
box.remove();
$(Table.selector).removeClass('resizing');
}
return;
}
<?php
// Otherwise get the selected parameters and go to the edit_entry page
?>
var queryString = 'drag=1'; <?php // Says that we've come from a drag select ?>
queryString += '&area=' + args.area;
queryString += '&start_seconds=' + params.seconds[0];
queryString += '&end_seconds=' + params.seconds[params.seconds.length - 1];
if (args.view === 'day')
{
for (var i=0; i<params.room.length; i++)
{
queryString += '&rooms[]=' + params.room[i];
}
queryString += '&start_date=' + args.pageDate;
}
else <?php // it's a week ?>
{
queryString += '&rooms[]=' + args.room;
queryString += '&start_date=' + params.date[0];
queryString += '&end_date=' + params.date[params.date.length - 1];
}
if (args.site)
{
queryString += '&site=' + encodeURIComponent(args.site);
}
window.location = addScrollPosition('edit_entry.php?' + queryString, tableContainer, downHandler.originalScroll);
};
<?php
// resize event callback function
?>
var resize = function(event, ui)
{
var closest,
rectangle = {},
sides = {n: false, s: false, e: false, w: false};
<?php
// Get the sides of the desired rectangle and also the direction(s) of
// resize. Note that the desired rectangle may be being moved in two
// directions at once (eg nw) if a corner has been grabbed. Use Math.round
// to avoid problems with floats.
?>
if (Math.round(ui.position.top - ui.originalPosition.top) === 0)
{
rectangle.n = ui.position.top;
}
else
{
rectangle.n = event.pageY;
sides.n = true;
}
if (Math.round(ui.position.left - ui.originalPosition.left) === 0)
{
rectangle.w = ui.position.left;
}
else
{
rectangle.w = event.pageX;
sides.w = true;
}
if (Math.round((ui.position.top + ui.size.height) -
(ui.originalPosition.top + ui.originalSize.height)) === 0)
{
rectangle.s = ui.position.top + ui.size.height;
}
else
{
rectangle.s = event.pageY;
sides.s = true;
}
if (Math.round((ui.position.left + ui.size.width) -
(ui.originalPosition.left + ui.originalSize.width)) === 0)
{
rectangle.e = ui.position.left + ui.size.width;
}
else
{
rectangle.e = event.pageX;
sides.e = true;
}
<?php
// Get all the bookings that the desired rectangle would overlap. Note
// that it could overlap more than one other booking, so we need to find them
// all and then find the closest one.
// Recalculate the original rectangle which will have move if we have scrolled.
?>
const originalRectangle = {
n: ui.originalPosition.top,
s: ui.originalPosition.top + ui.originalSize.height,
w: ui.originalPosition.left,
e: ui.originalPosition.left + ui.originalSize.width
};
var overlappedElements = Table.overlapsBooked(rectangle, false, originalRectangle);
if (!overlappedElements.length)
{
<?php // No overlaps: remove any constraints ?>
ui.element.resizable('option', {maxHeight: null,
maxWidth: null});
}
else
{
<?php
// There is at least overlap, so for each direction that the booking is being
// resized, get the closest booking in that direction. If there's an overlap
// then constrain the desired rectangle not to overlap.
?>
if (sides.n)
{
closest = getClosestSide(overlappedElements, 's');
if (event.pageY <= closest)
{
ui.position.top = closest + Table.borderTopWidth;
ui.element.resizable('option', 'maxHeight', ui.originalSize.height + ui.originalPosition.top - ui.position.top);
}
else
{
ui.element.resizable('option', 'maxHeight', null);
}
}
if (sides.w)
{
closest = getClosestSide(overlappedElements, 'e');
if (event.pageX <= closest)
{
ui.position.left = closest + Table.borderLeftWidth;
ui.element.resizable('option', 'maxWidth', ui.originalSize.width + ui.originalPosition.left - ui.position.left);
}
else
{
ui.element.resizable('option', 'maxWidth', null);
}
}
if (sides.s)
{
closest = getClosestSide(overlappedElements, 'n');
if (event.pageY >= closest)
{
ui.element.resizable('option', 'maxHeight', closest - ui.originalPosition.top);
}
else
{
ui.element.resizable('option', 'maxHeight', null);
}
}
if (sides.e)
{
closest = getClosestSide(overlappedElements, 'w');
if (event.pageX >= closest)
{
ui.element.resizable('option', 'maxWidth', closest - ui.originalPosition.left);
}
else
{
ui.element.resizable('option', 'maxWidth', null);
}
}
}
<?php
// Check to see if any of the four sides of the div have moved since the last time
// and if so, see if they've got close enough to the next boundary that we can snap
// them to the grid. (Note: using the condition sides.w etc. doesn't seem to work
// properly for some reason - it will pull the other edge off the grid slightly).
?>
<?php // left edge ?>
if (sides.w)
{
Table.snapUiToGrid(ui, 'left');
}
<?php // right edge ?>
if (sides.e)
{
Table.snapUiToGrid(ui, 'right');
}
<?php // top edge ?>
if (sides.n)
{
Table.snapUiToGrid(ui, 'top');
}
<?php // bottom edge ?>
if (sides.s)
{
Table.snapUiToGrid(ui, 'bottom');
}
const obj = uiDummyClone(ui);
Table.highlightRowLabels(obj);
obj.remove();
Table.setClipPath(ui);
}; <?php // resize ?>
<?php
// callback function called when the resize starts
?>
var resizeStart = function(event, ui)
{
resizeStart.originalOffset = ui.element.offset();
resizeStart.oldParams = Table.getBookingParams(ui.originalElement.find('a'));
<?php
// Add a class so that we can disable the highlighting when we are
// resizing (the flickering is a bit annoying)
?>
table.addClass('resizing');
}; <?php // resizeStart ?>
<?php
// callback function called when the resize stops
?>
var resizeStop = function(event, ui)
{
<?php
// Snap the edges of both the helper and the element being resized to the grid,
// regardless of where they are.
?>
['left', 'right', 'top', 'bottom'].forEach(function(side) {
Table.snapUiToGrid(ui, side, true);
});
<?php // Recalculate the original rectangle which will have move if we have scrolled. ?>
const originalRectangle = {
n: ui.originalPosition.top,
s: ui.originalPosition.top + ui.originalSize.height,
w: ui.originalPosition.left,
e: ui.originalPosition.left + ui.originalSize.width
};
if (rectanglesIdentical(originalRectangle, getSides(ui.helper)))
{
<?php
// Restore the proper height and width so that if the browser zoom
// level is changed then the booking fills the slot properly. (The
// resizing will have set actual pixel values instead of percentages).
?>
ui.element.css({width: '100%', height: '100%'});
$(Table.selector).removeClass('resizing');
return;
}
<?php
// We've got a change to the booking, so we need to send an Ajax
// request to the server to make the new booking
?>
var data = {csrf_token: getCSRFToken(),
commit: 1},
booking = ui.element.find('a');
<?php // get the booking id and type ?>
data.id = booking.data('id');
data.type = booking.data('type');
<?php // get the other parameters ?>
var oldParams = resizeStart.oldParams;
var newParams = Table.getBookingParams(booking);
if (newParams.seconds !== undefined)
{
<?php
// We only send through the time parameters that have changed.
// This is so that edit_entry_handler.php knows whether to use
// the original booking parameters. We need to do this so that
// we can properly handle multi-day bookings in the week view.
?>
if (newParams.seconds[0] !== oldParams.seconds[0])
{
data.start_seconds = newParams.seconds[0];
}
if (newParams.seconds[newParams.seconds.length - 1] !==
oldParams.seconds[oldParams.seconds.length - 1])
{
data.end_seconds = newParams.seconds[newParams.seconds.length - 1];
<?php
if ($enable_periods)
{
// When we're dealing with periods the end time is defined as
// the start of the last period (as opposed to the start of the
// next slot in times mode)
?>
data.end_seconds -= 60;
<?php
}
?>
}
}
data.view = args.view;
data.view_all = args.view_all;
if (args.view === 'day')
{
data.start_date = args.pageDate;
}
else <?php // it's 'week' ?>
{
data.start_date = newParams.date[0];
var onlyAdminCanBookRepeat = <?php echo ($auth['only_admin_can_book_repeat']) ? 'true' : 'false';?>;
if (args.isBookAdmin || !onlyAdminCanBookRepeat)
{
if (newParams.date.length > 1)
{
data.rep_type = <?php echo RepeatRule::DAILY ?>;
data.rep_interval = 1;
data.rep_end_date = newParams.date[newParams.date.length - 1];
}
}
}
data.end_date = data.start_date;
data.rooms = (typeof newParams.room === 'undefined') ? args.room : newParams.room;
<?php
if (isset($timetohighlight))
{
?>
data.timetohighlight = <?php echo $timetohighlight ?>;
<?php
}
// Give some visual feedback that the change is being saved. Note that the span
// is inserted after the elemement rather than appended, because if it's a child
// then any opacity rule that is applied to the parent will also apply to the child.
?>
booking.addClass('saving')
.after('<span class="saving"><?php echo get_js_vocab('saving'); ?></span>');
if(args.site)
{
data.site = args.site;
}
$.post('edit_entry_handler.php',
data,
function(result) {
<?php
// Load the new HTML. (1) Empty the existing
// table in order to get rid of events and data and
// prevent memory leaks (2) insert the updated table HTML
// and then (3) trigger a table load event so that the
// resizable bookings are re-created
?>
table.empty()
.html(result.table_innerhtml)
.trigger('tableload');
<?php
// If the booking failed then show an alert explaining why.
?>
if (!result.valid_booking)
{
var alertMessage = '';
if (result.conflicts.length > 0)
{
alertMessage += '<?php echo escape_js(html_entity_decode(get_vocab("conflict"))) ?>' + ": \n\n";
var conflictsList = getErrorList(result.conflicts);
alertMessage += conflictsList.text;
}
if (result.violations.errors.length > 0)
{
if (result.conflicts.length > 0)
{
alertMessage += "\n\n";
}
alertMessage += '<?php echo escape_js(html_entity_decode(get_vocab("rules_broken"))) ?>' + ": \n\n";
var rulesList = getErrorList(result.violations.errors);
alertMessage += rulesList.text;
}
window.alert(alertMessage);
}
},
'json')
.fail(function() {
<?php
// The Ajax request failed for some reason, so remove the "Saving ..." message
// and restore the element to its original position and size.
?>
booking.removeClass('saving')
.next().remove();
ui.element.offset(resizeStart.originalOffset)
.width(ui.originalSize.width)
.height(ui.originalSize.height);
<?php // Re-initialise the table ?>
$('table.dwm_main').trigger('tableload');
<?php // Allow some time for the changes above to complete and then alert the user ?>
setTimeout(function() {
alert("<?php echo get_js_vocab('resize_error')?>");
}, 250
);
});
}; <?php // resizeStop ?>
<?php
// Turn all the empty cells where a new multi-cell selection
// can be created by dragging the mouse
?>
table.find('td.new').each(function() {
$(this).find('a').on('click', function(event) {
event.preventDefault();
});
$(this).on('mousedown', function(event) {
event.preventDefault();
downHandler(event);
$(document).on('mousemove', moveHandler);
$(document).on('mouseup', upHandler);
});
});
<?php
// Make all the writable bookings resizable
?>
table.find('.writable')
.each(function() {
<?php
// Get the set of directions in which we are allowed to drag the
// box. At this stage we will do it by reference to the two axes,
// the times axis and the other axis, which will be days or rooms.
// Then later we will turn it into n/s/e/w handles (this will depend
// on $times_along_top)
?>
var directions = {times: {plus: true, minus: true},
other: {plus: true, minus: true}};
if ($(this).hasClass('series'))
{
<?php
// We only only members of a series to have their duration changed.
// It would be a bit confusing to have an individual member of a
// series dragged across days to make it a new daily series, or rooms
// to create new bookings in other rooms.
?>
directions.other = {plus: false, minus: false};
}
if (!args.isBookAdmin)
{
if (((args.view === 'week') && <?php echo ($auth['only_admin_can_book_repeat']) ? 'true' : 'false'?>) ||
((args.view === 'day') && <?php echo ($auth['only_admin_can_select_multiroom']) ? 'true' : 'false'?>))
{
<?php
// If we're in the week view then if non-admins aren't allowed to
// make repeat bookings, or else if we're in the day view and they
// aren't allowed to select multiple rooms, then we want to restrict
// the handles we offer them so that they can't get that far.
?>
directions.other = {plus: false, minus: false};
}
<?php
if ($auth['only_admin_can_book_multiday'])
{
// Don't let non-admins drag multi-day bookings if
// $auth['only_admin_can_book_multiday'] is set
?>
if ($(this).hasClass('multiday_start') ||
$(this).hasClass('multiday_end'))
{
directions.times = {plus: false, minus: false};
}
<?php
}
?>
}
<?php
// Don't allow multiday bookings to be moved at the end
// which is joined to another day nor along the other axis
?>
if ($(this).hasClass('multiday_start'))
{
directions.times.minus = false;
directions.other = {plus: false, minus: false};
}
if ($(this).hasClass('multiday_end'))
{
directions.times.plus = false;
directions.other = {plus: false, minus: false};
}
<?php
// Now turn the directions in which we are allowed to move the
// boxes into an array of n/s/e/w handles, depending on the
// setting of $times_along_top
?>
var aHandles = [];
if (directions.times.plus)
{
aHandles.push('<?php echo ($times_along_top) ? "e" : "s" ?>');
}
if (directions.times.minus)
{
aHandles.push('<?php echo ($times_along_top) ? "w" : "n" ?>');
}
if (directions.other.plus)
{
aHandles.push('<?php echo ($times_along_top) ? "s" : "e" ?>');
}
if (directions.other.minus)
{
aHandles.push('<?php echo ($times_along_top) ? "n" : "w" ?>');
}
<?php
// Test each corner. If we've got both the side handles then
// add in the corner handle
?>
['nw', 'ne', 'se', 'sw'].forEach(function(corner) {
if ((aHandles.indexOf(corner[0]) >= 0) &&
(aHandles.indexOf(corner[1]) >= 0))
{
aHandles.push(corner);
}
});
var handles = aHandles.join(',');
if (handles)
{
$(this).resizable({
handles: handles,
helper: 'resizable-helper',
start: resizeStart,
resize: resize,
stop: resizeStop,
scrollableContainer: tableContainer,
scrollDelta: Table.scrollDelta
});
}
$(this).css('background-color', 'transparent');
});
<?php
// We want to disable page refresh if the user is hovering over
// the resizable handles. We trigger a mouseenter event on page
// load so we can work out whether the mouse is over the handle
// on page load (but we only need to trigger one event).
//
// mouseDown will also be set by the event handlers for drag selection
// of new bookings, so that we don't turn on page refresh while in the
// middle of a drag selection when we pass over a resizable handle
?>
$('.ui-resizable-handle')
.on('mouseenter', function(e) {
if (!mouseDown)
{
if ($(this).is(':hover'))
{
table.addClass('resizing');
}
else
{
table.removeClass('resizing');
}
}
})
.on('mouseleave', function() {
if (!mouseDown)
{
table.removeClass('resizing');
}
})
.on('mousedown', function() {
mouseDown = true;
if ($(this).is(':hover'))
{
table.addClass('resizing');
}
})
.on('mouseup', function() {
mouseDown = false;
if (!$(this).is(':hover'))
{
table.removeClass('resizing');
}
})
.first().trigger('mouseenter');
}).trigger('tableload');
$(window).on('resize', throttle(function(event) {
if (event.target === this) <?php // don't want the ui-resizable event bubbling up ?>
{
<?php
// The table dimensions have changed, so we need to re-map the table
?>
Table.init();
}
}, 50));
<?php
// We need to re-initialise when the table container is scrolled because all the coordinates
// are relative to the document rather than the container. (The jQuery UI widget gives
// positions this way).
?>
$(Table.selector).parent().on('scroll', throttle(function() {
Table.init();
}));
});