Dragging a file to a folder.The user has left-clicked and held the mouse button down on the index1.html item; the user then dragged the mouse to the left, carrying the item with it.. Whe
Trang 1li.className = 'draggable';
var s = '';
for (var j = 0;
j < fileNodes[i].firstChild.nodeValue.length; j += 5) {
s += fileNodes[i].firstChild.nodeValue.substr(j, 5);
s += '<wbr>';
}
li.setAttribute('path', path + '/' +
fileNodes[i].firstChild.nodeValue);
li.innerHTML = s;
ul.appendChild(li);
}
files.appendChild(ul);
setTimeout(fM.setUpDraggables, 100);
},
receiveFilenames receives the XML returned by the server in the form of a Sarissa DomDocument object (xmlhttp.responseXML in loadFiles), and constructs from it a slice of HTML—a document fragment Here’s an example of that frag-ment, based on two retrieved files: file1.html and longfilename.html:
<ul>
<li class="draggable"
path="www.example.com/html/file1.html">file1<wbr>.html</li> <li class="draggable"
path="www.example.com/html/file1.html"
>longf<wbr>ilena<wbr>me.htm<wbr>l</li>
</ul>
Essentially, receiveFilenames creates an unordered list of filenames, and adds
to each list item a class of draggable (ensuring that our drag script, later, will know that this is a draggable item) It also adds a custom path attribute with the full path of the file (to make life easier on the drag script) Finally, it breaks the filename into five-character chunks and inserts a <wbr> tag after each chunk The
<wbr> tag indicates a point at which a word may be broken for wrapping at the end of a line This is used to ensure that the filename can be word-wrapped, so that it doesn’t break the layout.3
3 <wbr> is a nonstandard tag, the use of which may well engender some guilty feelings However, there is no cross-browser way to say, “break up this word wherever you need to in order to get it to fit into a box properly.” The other possibilities are ­ , the soft hyphen, which is unsupported
by Mozilla, and the official solution: zero-width space ​ , which has patchy support MSIE also has the nonstandard CSS word-wrap property, but there is no cross-browser equivalent.
Trang 2If you work through the method slowly, you’ll see that three document hierarchies are at work: the page itself, the XML document fragment returned from the server, and the document fragment being built up for insertion into the page
The method clears the contents of the document element that has the ID files (which is a container div that will be used to display the file list), and puts the newly-created list structure into it
Finally, it calls fM.setUpDraggables, which we’ll look at later, to make the new filename elements draggable
Server Control Commands
Now that we’ve got a list of server files to work with, we’ll need to be able to manipulate them: to tell the server what to do with the files Control instructions will pass from browser to server In this application, we have only one control instruction: “move file A to directory B.”
Sending a command to the server can be achieved using XMLHttpRequest in ex-actly the same way as we’d use it to retrieve data The mechanics of sending a communication to the server are the same, it’s just that the focus has changed Before, we were conceptually sending a request for data, and getting back some data; now, we send a command and retrieve a success or failure message Here, the browser tells the server what to do, rather than asking the server for inform-ation
The server code should achieve the following:
1 It should accept two query string parameters: path and file The file para-meter is the full path of the file that we want to move (again, relative to the root); the path parameter is the relative path to the directory to which the file should be moved
2 It should be paranoid, and check that:
❑ the directory is under the root path
❑ the file is under the root path
❑ the directory is a directory and the file is a file
3 It should move the file into the directory
Trang 3Here’s the PHP server-side code:
File: moveFiles.php
<?php
$ROOT = realpath($_SERVER['DOCUMENT_ROOT'] '/test');
$path = isset($_GET['path']) ? $_GET['path'] : '/';
$rp = realpath($ROOT $path);
// Be paranoid; check that this is a subdir of ROOT
if (strpos($rp, $ROOT) === 0) {
$fname = isset($_GET['file']) ? $_GET['file'] : '';
$fn = realpath($ROOT $fname);
if (strpos($fn, $ROOT) === 0) {
if (is_dir($rp) && file_exists($fn)) {
$fileonly = basename($fn);
rename($fn, $rp '/' $fileonly)
or die('Moving file failed');
echo 'OK';
} else {
echo 'File or directory bad';
}
} else {
echo 'Bad filename';
}
} else {
echo 'Bad directory';
}
?>
As with getFiles.php, this script is paranoid: it does not allow the user to exploit
it in order to move files around outside the $ROOT directory Since we know that our designed client-side code will only pass legitimate parameters to the server code, any non-legitimate parameters that are detected must have been sent by someone who’s trying to exploit the script Therefore, the error messages are in-tentionally not particularly helpful (but at least there are error messages; the script itself does not throw an error)
The client code that uses this server move script is the moveFileHere method
It is passed the element that was dragged (which will be an element describing a file, with a path attribute) It also has access to the folder that’s the drag-n-drop target in the variable this (the current object)
File: fileman.js (excerpt)
moveFileHere: function(dragged) {
var file = dragged.getAttribute('path');
Trang 4var path = this.getAttribute('path');
var xmlhttp = new XMLHttpRequest();
var qs = '?path=' + escape(path) + '&file=' + escape(file);
var url = 'moveFiles.php' + qs;
xmlhttp.open('POST', url, true);
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) { fM.receiveMoveDetails(xmlhttp.responseText, dragged);
} };
xmlhttp.send(null);
}, This code extracts the source and destination locations in its first two lines, then tells the server what to do Although we still use the URL query string to pass instructions to the server, we use a POST request, rather than a GET request, to indicate that we wish to perform some kind of action on the server—not just re-trieve information Again, the server response is sent to an anonymous callback function, which calls receiveMoveDetails:
File: fileman.js (excerpt)
receiveMoveDetails: function(data, dragged) {
if (data == 'OK') { dragged.parentNode.removeChild(dragged);
} else { alert('There was an error moving the file:\n' + data);
} }, This method deletes the dragged element from the HTML, so that it appears that the drag target beneath the dragged item has “swallowed” the dragged item Now, let’s see how the dragging is achieved
Implementing Drag-and-Drop
We’ve now done all the required client-server interaction, but this time, unlike Chapter 8, the server requests are hidden under a thick layer of user interface: the collapsible menu, and the drag-n-drop system Let’s look at the latter of those two interface elements Figure 9.2 shows the user interface halfway through a drag action
Trang 5Figure 9.2 Dragging a file to a folder.
The user has left-clicked and held the mouse button down on the index1.html item; the user then dragged the mouse to the left, carrying the item with it Since the item’s not on top of any of the listed directories, none of them is highlighted
as the current drag target
Basic Drag-and-Drop with DOM-Drag
Making elements draggable is, in concept, a pretty simple thing to implement via DHTML It works like this:
1 When the user holds the mouse button down over a draggable element, set variable dragMode to true and record which element fired the mousedown event
2 When the user releases the mouse button, set dragMode to false
3 If the mouse moves, and dragMode is true, change the position of the recor-ded element to the position of the mouse
That’s it, in concept However, it can be a fiddly thing to get right Fortunately, other people have already done the heavy lifting on this; unobtrusive JavaScript libraries are available that make the implementation of draggable elements easy One of the best is Aaron Boodman’s DOM-Drag4.5
Usage of DOM-Drag is pretty simple, First, we include the library:
4 http://www.youngpup.net/2001/domdrag
5 Aaron says “DOM-Drag was written for you Because I care about you.” That’s the spirit!
Trang 6File: fileman.html (excerpt)
<script type="text/javascript" src="dom-drag.js"></script>
<script type="text/javascript" src="sarissa.js"></script>
<script type="text/javascript" src="fileman.js"></script>
Then, add initialization calls for each draggable element:
Drag.init(element);
Additionally, any element that you want to drag must use absolute or relative positioning in the style sheet:
File: fileman.css (excerpt)
.draggable { position: relative;
} Once you’ve loaded the DOM-Drag library, you’ve succeeded in added basic dragging functionality to an element We’ll use this method in our script to make elements draggable
That explains how we’ll do the dragging, but what about the dropping?
Simple Drag Target Tactics
DOM-Drag provides no facility for knowing whether the user is currently dragging one element over another, so we’ll need to build this ourselves At first examina-tion, it sounds simple:
1 Attach mouseover and mouseout listeners to all potential drop target elements
A drop target is an element on which the user can drop a dragged element.
If a dragged element is dropped anywhere other than a drop target, it should
“snap back” to its original position
2 The mouseover listener on a drop target element must check if dragMode is true (i.e if a dragging operation is in progress) If it is, then set a class hover
on this target element (so that it can be highlighted with CSS)
3 The mouseout listener on a drop target element should remove the hover class
4 When a dragged element is dropped (step 2 in the simple description of dragging above), check if any target has class hover If one has, then the
Trang 7dragged element must have been dropped on that target element, so call moveFileHere, from above, for that target
This approach is fine in theory, but sadly, it’s not quite as simple as that
Smarter Drag Target Tactics
When an element is being dragged, mouseovers on any dragged-over drop target elements will not fire in Mozilla-based browsers The reason for this is that the cursor isn’t over the target element; it’s over the dragged element Figure 9.3 il-lustrates this point
As shown in Figure 9.3, the cursor is on a plane of its own, on top of the dragged element The cursor and the dragged element move together on top of the drop target element The cursor is never over the drop target element itself, because the dragged element is in between the two This means that the target’s mouseover event never fires
Figure 9.3 Mouse, element, and target layers.
Trang 8One way to solve this problem is with a proxy element Imagine that every drop
target element is actually two elements: the drop target itself, and an invisible a element that’s the same size and position as the drop target, and exactly on top
of it This structural alteration would have no effect on the page’s appearance With careful manipulation of the z-index of each element, we can create a situ-ation where the invisible proxy element lies on top of the dragged element To
do this, leave the drop target’s z-index unset (so it defaults to zero), set the dragged element’s z-index to 999, and set the invisible proxy’s z-index to 1000 The elements will then stack up as shown in Figure 9.4
Now, the cursor is immediately on top of the invisible proxy element That means the proxy element will receive mouse events The dragged element, when moved,
slides underneath the proxy (but you can’t tell, because the proxy is invisible) and,
hence, does not receive events The proxy never moves This use of proxy elements isn’t restricted to DHTML; elsewhere in user interface development it’s sometimes
called a hotspot.
Figure 9.4 The transparent proxy element layer.
A better procedure for dragging an element, including the new proxy elements, might be:
Trang 91 When the user holds the mouse button down over a draggable element:
❑ Set a variable dragMode to true
❑ Record which element fired the mousedown event
❑ Create invisible proxy elements for each target element in the document (note that this is done every time a drag starts, not just once at document creation)
❑ Each proxy element should have a mouseover and mouseout event listener; the mouseover listener must apply the hover class to the real element corresponding to this proxy (not the proxy itself)
❑ The mouseout listener should remove the hover class from the real element corresponding to this proxy
2 When the user releases the mouse button, set dragMode to false Remove the transparent proxy targets If a target is of class hover, call the moveFileHere method for that element
3 If the cursor moves, and dragMode is true, change the position of the recorded element to reflect the position of the cursor
That’s the right recipe for highlighting drag targets
Creating Proxy Drag Targets
The creation of proxy targets will be triggered whenever the user starts to drag a draggable element We’ll see how this is set up in a moment, but for now, let’s look at the process itself, which is completed by the createProxyTargets method:
File: fileman.js (excerpt)
createProxyTargets: function() {
fM.PROXY_TARGETS = [];
var targets = document.getElementsByTagName('*');
for (var i = 0; i < targets.length; i++) {
var t = targets[i];
if (t.className.search(/\btarget\b/) != -1) {
var proxyTarget = document.createElement('a');
proxyTarget.className = 'proxyTarget';
proxyTarget.style.left = fM.findPosX(t) + 'px';
Trang 10proxyTarget.style.top = fM.findPosY(t) + 'px';
proxyTarget.style.width = t.offsetWidth + 'px';
proxyTarget.style.height = t.offsetHeight + 'px';
proxyTarget.href = '#';
proxyTarget.realElement = t;
fM.PROXY_TARGETS[fM.PROXY_TARGETS.length] = proxyTarget;
document.body.appendChild(proxyTarget);
fM.addEvent(proxyTarget, 'mouseover', fM.targetOver, false);
fM.addEvent(proxyTarget, 'mouseout', fM.targetOut, false); }
} }, This method iterates through each drop target element t, and dynamically creates
a new a element with a CSS class of proxyTarget We use this in our style sheet
to style the proxy element as required:
File: fileman.css (excerpt)
.proxyTarget { cursor: crosshair;
position: absolute;
background-color: white;
z-index: 1000;
opacity: 0;
filter: alpha(opacity=0);
}
In addition to changing the mouse cursor, our proxy objects are given a back-ground color so that they occupy the entire rectangular area of the drop target The z-index of 1000 ensures that they will float over the draggable elements Finally, we render the proxy objects invisible by setting an opacity of zero (the filter property is required to do this in Internet Explorer)
The createProxyTargets method calculates the drop targets’ sizes and positions, and copies them to this proxy element A reference to the proxy’s associated real target element is stored in proxyTarget.realElement, so that it can be retrieved later if the proxy is moused over The proxy element is then added to fM.PROXY_TARGETS (a list of all proxy elements), as well as the document Finally, the proxy gets its own listeners that will respond to the mouseover and mouseout events They’re discussed in the next section