1// Fotonotes DHTML Client (c) 2004-2005 Angus Turnbull http://www.twinhelix.com 2// Developed under license to FotoNotes LLC 3// Released under the Open Source License v2.1 or later. 4 5// Modification 2005.11.17 - add loading scripts - Greg 6 7// See the bottom of this file for configuration. 8 9 10// *** FNCLIENT CONFIGURATION, VARIABLES AND SETUP *** 11 12 13// Address of fotonoter.php on the server (this auto-detect should work): 14var fnServerPath = "../"; 15var fnServerFotonotesScript = "fotonotes.php"; 16var fnServer = fnServerPath + fnServerFotonotesScript; 17 18// XMLHTTPRequest object to communicate with server. 19var fnXMLHTTP = null; 20if (window.ActiveXObject) 21{ 22 try 23 { 24 fnXMLHTTP = new ActiveXObject('Microsoft.XMLHTTP'); 25 } 26 catch (e) { } 27} 28else if (window.XMLHttpRequest) 29{ 30 fnXMLHTTP = new XMLHttpRequest(); 31} 32 33// Permissions (respect previous settings): 34// Allowed values are 'allow', 'prompt', 'deny'. 35if (!window.FN_ADD) var FN_ADD = 'allow'; 36if (!window.FN_MODIFY) var FN_MODIFY = 'allow'; 37if (!window.FN_DELETE) var FN_DELETE = 'allow'; 38 39// Internationalisation: 40var FN_CREDITS = 'Fotonotes DHTML Viewer\n\n' + 41 '(c) 2004-2005 Angus Turnbull, http://www.twinhelix.com\n\n' + 42 'Provided under license to Fotonotes LLC'; 43var FN_DISALLOWED = 'Sorry, that action is not permitted.\n\n' + 44 'Please login under a different account.'; 45var FN_POST_UNSUPPORTED = 'Sorry, your browser does not support editing notes.'; 46var FN_DELETE_CONFIRM = 'Are you sure you want to delete this note?'; 47var FN_SAVE_WAIT = 'Loading Fotonotes...'; 48var FN_SAVE_FAIL = 'An error occurred, and your changes could not be saved.'; 49var FN_SAVE_FAIL_JPEG_NOT_WRITABLE = "JPEG file is not writable. Please check file permissions on server."; 50var FN_SAVE_SUCCESS = 'Changes saved!'; 51 52// Other global variables: 53var fnDebugMode = false; // Set to true to show XML sent/received. 54var fnHideTimer = null; // Hide notes after timeout. 55var fnActiveNote = null; // Currently visible note. 56var fnActionVerb = ''; // Control bar's current action. 57var fnActionTrigger = null; // Control bar's lit item. 58var fnEditingData = null; // Data store during note editing process. 59var fnAnnotateAll = false; // Indicate annotation should be applied to all images 60var fnMinImgWidth = 200; // MinWidth to make to apply to fn-image 61var fnMinImgHeight = 150; // MinHeight to make to apply to fn-image 62var imageFileSrc = "src"; // Use 'id' for findImage() to use imgObj.id; use "src" (default) for findImage to use imgOb.src 63 64 65// *** Common API Code *** 66 67var aeOL = []; 68function addEvent(o, n, f, l) 69{ 70 var a = 'addEventListener', h = 'on'+n, b = '', s = ''; 71 if (o[a] && !l) return o[a](n, f, false); 72 o._c |= 0; 73 if (o[h]) 74 { 75 b = '_f' + o._c++; 76 o[b] = o[h]; 77 } 78 s = '_f' + o._c++; 79 o[s] = f; 80 o[h] = function(e) 81 { 82 e = e || window.event; 83 var r = true; 84 if (b) r = o[b](e) != false && r; 85 r = o[s](e) != false && r; 86 return r; 87 }; 88 aeOL[aeOL.length] = { o: o, h: h }; 89}; 90addEvent(window, 'unload', function() { 91 for (var i = 0; i < aeOL.length; i++) with (aeOL[i]) 92 { 93 o[h] = null; 94 for (var c = 0; o['_f' + c]; c++) o['_f' + c] = null; 95 } 96}); 97 98function cancelEvent(e, c) 99{ 100 e.returnValue = false; 101 if (e.preventDefault) e.preventDefault(); 102 if (c) 103 { 104 e.cancelBubble = true; 105 if (e.stopPropagation) e.stopPropagation(); 106 } 107}; 108 109 110// *** FNCLIENT LOAD DIVS *** 111// The following functions run after page loaded and retrieve Fotonotes data into the document to show annotations. 112 113addLoadEvent(findImage); 114 115function addLoadEvent(func) { 116 var oldonload = window.onload; 117 if (typeof window.onload != 'function') { 118 window.onload = func; 119 } else { 120 window.onload = function() { 121 oldonload(); 122 func(); 123 } 124 } 125} 126 127 128 129function findImage() { 130 for (i=0;i < document.images.length; i++) { 131 if (fnDebugMode) alert('img '+document.images[i].className); 132 if ( (document.images[i].className == "fn-image") || ( (fnAnnotateAll) && (document.images[i].width >= fnMinImgWidth) && (document.images[i].height >= fnMinImgHeight)) ) { 133 var imgObj = document.images[i]; 134 135 // get path to image. 136 if (fnDebugMode) alert("imgObj.src: "+imgObj.src); 137 if (imageFileSrc == "id") { 138 var imageFile = imgObj.id; 139 } else { 140 var imageFile = imgObj.src; 141 } 142 if (fnDebugMode) alert('revised imageFile: \n\n' + imageFile); 143 144 if (imgObj.parentNode.tagName == "A") { 145 146 var currentLinkNode = imgObj.parentNode; 147 var newNode = document.createElement('div'); 148 //newNode.innerHTML = "replacement newNode"; 149 imgObj.parentNode.parentNode.replaceChild(newNode, imgObj.parentNode); 150 newNode.appendChild(imgObj); 151 152 newLinkNode = document.createElement('div'); 153 newLinkNode.className = "fn-view-image-link"; 154 currentLinkNode.innerHTML = "View image"; 155 var pathToImage = unescape(currentLinkNode.pathname); 156 var temp = pathToImage.split('blank'); 157 if (window.ActiveXObject) { 158 currentLinkNode.href = temp[1]; //IE quirk 159 } else { 160 currentLinkNode.href = temp[0]; 161 } 162 newLinkNode.appendChild(currentLinkNode); 163 newNode.appendChild(newLinkNode); 164 165 /* 166 ImgElement.parentNode.parentNode.replaceChild(newNode, ImgElement.parentNode); 167 Y = document.createElement('div'); 168 */ 169 } 170 171 createFNImage(imgObj, imageFile); 172 } 173 } 174} 175 176function createFNImage(imgObj, imageFile) { 177 getFNDiv(imgObj, imageFile); 178} 179 180function getFNDiv(imgObj, imageFile) { 181 // Sends some XML off to the server to get FNClient for image calls fnGetClientComplete on completion. 182 //imageFile = imgObj.src; // deprecated assigment 183 if (fnDebugMode) alert('Final imageFile: \n\n' + imageFile); 184 if (!imageFile) return alert(FN_SAVE_FAIL); 185 // Compose our post content and send it. 186 var postContent = 'image=' + escape(imageFile) + '&action=' + 'display' + '&width=' + imgObj.width + '&height=' + imgObj.height + '&alt=' + imgObj.alt + '&style=';// + imgObj.style; 187 if (fnDebugMode) alert('TARGET SERVER URL: \n\n' + fnServer); 188 if (fnDebugMode) alert('SENDING TO tlnServer:\n\n' + postContent); 189/* fnXMLHTTP.open('POST', fnServer, false); // use "false" to stop script from proceeding until data received. 190 191 fnXMLHTTP.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8'); 192 fnXMLHTTP.setRequestHeader('Content-length', postContent.length); 193 var cookies = document.cookie.split(';'); 194 195 if (cookies != null) { 196 for (var c = 0; c < cookies.length; c++) 197 { 198 if (cookies[c].length > 0) fnXMLHTTP.setRequestHeader('Cookie', cookies[c]); 199 } 200 } 201*/ 202 203// XMLHTTPRequest object to communicate with server. 204var fnObjXMLHTTP = null; 205if (window.ActiveXObject) 206{ 207 try 208 { 209 fnObjXMLHTTP = new ActiveXObject('Microsoft.XMLHTTP'); 210 //fnObjXMLHTTP = new ActiveXObject('Msxml2.XMLHTTP.4.0'); // Angus recommend this call, but 'Microsoft.XMLHTTP' seems to be working better. 211 } 212 catch (e) { } 213} 214else if (window.XMLHttpRequest) 215{ 216 fnObjXMLHTTP = new XMLHttpRequest(); 217} 218 219 fnObjXMLHTTP.open('POST', fnServer, true); 220 fnObjXMLHTTP.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8'); 221 fnObjXMLHTTP.setRequestHeader('Content-length', postContent.length); 222 var cookies2 = document.cookie.split(';'); 223 /* Commented out in December 2005 b/c of problems in some instances of IE 224 for (var c = 0; c < cookies2.length; c++) 225 { 226 fnObjXMLHTTP.setRequestHeader('Cookie', cookies2[c]); 227 } 228 */ 229 fnObjXMLHTTP.onreadystatechange = function() 230 { 231 if (fnObjXMLHTTP.readyState == 4) fnGetClientComplete(true,imgObj,fnObjXMLHTTP.responseText); 232 }; 233 234 fnObjXMLHTTP.send(postContent); 235 236} 237 238function fnGetClientComplete(ok2,imgObj,responseText) 239{ 240 // All successful actions: let the user know it's OK, and reset the control bar, 241 // and clear the editing data store. 242 fnModalDialog(FN_SAVE_WAIT); 243 244 setTimeout('fnModalDialog("")', 500); 245 if (fnDebugMode) alert('RECEIVED FROM FNSERVER:\n\n' + responseText); 246// Called once the server responds post-Save operation. 'ok' indicates success. 247 248 // EXTRACT returned HTML text from reply to update document 249 re = /displayHTML##([\w\W\n\r]*)##/; 250 //alert ('re' + re); 251 //alert ('test? ' + re.test(fnXMLHTTP.responseText)); 252 matches = re.exec(responseText); 253 //fnDiv = fnXMLHTTP.responseText; 254 fnDiv = matches[1]; // first matche pattern 255 fnDivElement = document.createElement('div'); 256 fnDivElement.innerHTML = fnDiv; 257 imgObj.parentNode.insertBefore(fnDivElement,imgObj); 258 imgObj.parentNode.removeChild(imgObj); 259} 260 261 262// *** Drag and Resize Library Code *** 263// (c) 2005 Angus Turnbull http://www.twinhelix.come 264 265 266function DragResize(myName, config) 267{ 268 var props = { 269 myName: myName, // Name of the object. 270 enabled: true, // Global toggle of drag/resize. 271 handles: ['tl', 'tm', 'tr', 272 'ml', 'mr', 'bl', 'bm', 'br'], // Array of drag handles: top/mid/. 273 isElement: null, // Function ref to test for an element. 274 isHandle: null, // Function ref to test for move handle. 275 element: null, // The currently selected element. 276 dragging: null, // Active handle reference of the element. 277 minWidth: 10, minHeight: 10, // Minimum pixel size of elements. 278 minLeft: 0, maxRight: 9999, // Bounding box area. 279 minTop: 0, maxBottom: 9999, 280 zIndex: 1, // The highest Z-Index yet allocated. 281 mouseX: 0, mouseY: 0, // Current mouse position, recorded live. 282 lastMouseX: 0, lastMouseY: 0, // Last processed mouse positions. 283 mOffX: 0, mOffY: 0, // A known offset between position & mouse. 284 elmX: 0, elmY: 0, // Element position. 285 elmW: 0, elmH: 0, // Element size. 286 allowBlur: true, // Whether to allow automatic blur onclick. 287 ondragfocus: null, // Event handler functions. 288 ondragstart: null, 289 ondragmove: null, 290 ondragend: null, 291 ondragblur: null 292 }; 293 294 for (var p in props) 295 { 296 this[p] = (typeof config[p] == 'undefined') ? props[p] : config[p]; 297 } 298}; 299 300 301DragResize.prototype.apply = function(node) 302{ 303 // Adds object event handlers to the specified DOM node. 304 305 var obj = this; 306 addEvent(node, 'mousedown', function(e) { obj.mouseDown(e) } ); 307 addEvent(node, 'mousemove', function(e) { obj.mouseMove(e) } ); 308 addEvent(node, 'mouseup', function(e) { obj.mouseUp(e) } ); 309}; 310 311 312DragResize.prototype.handleSet = function(elm, show) { with (this) 313{ 314 // Either creates, shows or hides the resize handles within an element. 315 316 // If we're showing them, and no handles have been created, create 4 new ones. 317 if (!elm._handle_tr) 318 { 319 for (var h = 0; h < handles.length; h++) 320 { 321 // Create 4 news divs, assign each a generic + specific class. 322 var hDiv = document.createElement('div'); 323 hDiv.className = myName + ' ' + myName + '-' + handles[h]; 324 elm['_handle_' + handles[h]] = elm.appendChild(hDiv); 325 } 326 } 327 328 // We now have handles. Find them all and show/hide. 329 for (var h = 0; h < handles.length; h++) 330 { 331 elm['_handle_' + handles[h]].style.visibility = show ? 'inherit' : 'hidden'; 332 } 333}}; 334 335 336DragResize.prototype.select = function(newElement) { with (this) 337{ 338 // Selects an element for dragging. 339 340 if (!document.getElementById || !enabled) return; 341 342 // Activate and record our new dragging element. 343 if (newElement && (newElement != element) && enabled) 344 { 345 element = newElement; 346 // Elevate it and give it resize handles. 347 element.style.zIndex = ++zIndex; 348 handleSet(element, true); 349 // Record element attributes for mouseMove(). 350 elmX = parseInt(element.style.left); 351 elmY = parseInt(element.style.top); 352 elmW = element.offsetWidth; 353 elmH = element.offsetHeight; 354 if (ondragfocus) this.ondragfocus(); 355 } 356}}; 357 358 359DragResize.prototype.deselect = function(keepHandles) { with (this) 360{ 361 // Immediately stops dragging an element. If 'keepHandles' is false, this 362 // remove the handles from the element and clears the element flag, 363 // completely resetting the . 364 365 if (!document.getElementById || !enabled) return; 366 367 if (!keepHandles) 368 { 369 if (ondragblur) this.ondragblur(); 370 handleSet(element, false); 371 element = null; 372 } 373 374 dragging = null; 375 mOffX = 0; 376 mOffY = 0; 377}}; 378 379 380DragResize.prototype.mouseDown = function(e) { with (this) 381{ 382 // Suitable elements are selected for drag/resize on mousedown. 383 // We also initialise the resize boxes, and drag parameters like mouse position etc. 384 if (!document.getElementById || !enabled) return true; 385 386 var elm = e.target || e.srcElement, 387 newElement = null, 388 newHandle = null, 389 hRE = new RegExp(myName + '-([trmbl]{2})', ''); 390 391 while (elm) 392 { 393 // Loop up the DOM looking for matching elements. Remember one if found. 394 if (elm.className) 395 { 396 if (!newHandle && (hRE.test(elm.className) || isHandle(elm))) newHandle = elm; 397 if (isElement(elm)) { newElement = elm; break } 398 } 399 elm = elm.parentNode; 400 } 401 402 // If this isn't on the last dragged element, call deselect(false), 403 // which will hide its handles and clear element. 404 if (element && (element != newElement) && allowBlur) deselect(false); 405 406 // If we have a new matching element, call select(). 407 if (newElement && (!element || (newElement == element))) 408 { 409 // Stop mouse selections. 410 cancelEvent(e); 411 select(newElement, newHandle); 412 dragging = newHandle; 413 if (dragging && ondragstart) this.ondragstart(); 414 } 415}}; 416 417 418DragResize.prototype.mouseMove = function(e) { with (this) 419{ 420 // This continually offsets the dragged element by the difference between the 421 // last recorded mouse position (mouseX/Y) and the current mouse position. 422 if (!document.getElementById || !enabled) return true; 423 424 // We always record the current mouse position. 425 mouseX = e.pageX || e.clientX + document.documentElement.scrollLeft; 426 mouseY = e.pageY || e.clientY + document.documentElement.scrollTop; 427 // Record the relative mouse movement, in case we're dragging. 428 // Add any previously stored&ignored offset to the calculations. 429 var diffX = mouseX - lastMouseX + mOffX; 430 var diffY = mouseY - lastMouseY + mOffY; 431 mOffX = mOffY = 0; 432 // Update last processed mouse positions. 433 lastMouseX = mouseX; 434 lastMouseY = mouseY; 435 436 // That's all we do if we're not dragging anything. 437 if (!dragging) return true; 438 439 // Establish which handle is being dragged -- retrieve handle name from className. 440 var hClass = dragging && dragging.className && 441 dragging.className.match(new RegExp(myName + '-([tmblr]{2})')) ? RegExp.$1 : ''; 442 443 // If the hClass is one of the resize handles, resize one or two dimensions. 444 // Bounds checking is the hard bit -- basically for each edge, check that the 445 // element doesn't go under minimum size, and doesn't go beyond its boundary. 446 var rs = 0, dY = diffY, dX = diffX; 447 if (hClass.indexOf('t') >= 0) 448 { 449 rs = 1; 450 if (elmH - dY < minHeight) mOffY = (dY - (diffY = elmH - minHeight)); 451 else if (elmY + dY < minTop) mOffY = (dY - (diffY = minTop - elmY)); 452 elmY += diffY; 453 elmH -= diffY; 454 } 455 if (hClass.indexOf('b') >= 0) 456 { 457 rs = 1; 458 if (elmH + dY < minHeight) mOffY = (dY - (diffY = minHeight - elmH)); 459 else if (elmY + elmH + dY > maxBottom) mOffY = (dY - (diffY = maxBottom - elmY - elmH)); 460 elmH += diffY; 461 } 462 if (hClass.indexOf('l') >= 0) 463 { 464 rs = 1; 465 if (elmW - dX < minWidth) mOffX = (dX - (diffX = elmW - minWidth)); 466 else if (elmX + dX < minLeft) mOffX = (dX - (diffX = minLeft - elmX)); 467 elmX += diffX; 468 elmW -= diffX; 469 } 470 if (hClass.indexOf('r') >= 0) 471 { 472 rs = 1; 473 if (elmW + dX < minWidth) mOffX = (dX - (diffX = minWidth - elmW)); 474 else if (elmX + elmW + dX > maxRight) mOffX = (dX - (diffX = maxRight - elmX - elmW)); 475 elmW += diffX; 476 } 477 // If 'rs' isn't set, we must be dragging the whole element, so move that. 478 if (dragging && !rs) 479 { 480 // Bounds check left-right... 481 if (elmX + dX < minLeft) mOffX = (dX - (diffX = minLeft - elmX)); 482 else if (elmX + elmW + dX > maxRight) mOffX = (dX - (diffX = maxRight - elmX - elmW)); 483 // ...and up-down. 484 if (elmY + dY < minTop) mOffY = (dY - (diffY = minTop - elmY)); 485 else if (elmY + elmH + dY > maxBottom) mOffY = (dY - (diffY = maxBottom - elmY - elmH)); 486 elmX += diffX; 487 elmY += diffY; 488 } 489 490 // Assign new info back to the element, with minimum dimensions. 491 with (element.style) 492 { 493 left = elmX + 'px'; 494 width = elmW + 'px'; 495 top = elmY + 'px'; 496 height = elmH + 'px'; 497 } 498 499 // Evil, dirty, hackish Opera select-as-you-drag fix. 500 if (window.opera && document.documentElement) 501 { 502 var oDF = document.getElementById('op-drag-fix'); 503 if (!oDF) 504 { 505 var oDF = document.createElement('input'); 506 oDF.id = 'op-drag-fix'; 507 oDF.style.display = 'none'; 508 document.body.appendChild(oDF); 509 } 510 oDF.focus(); 511 } 512 513 if (ondragmove) this.ondragmove(); 514 515 // Stop a normal drag event. 516 cancelEvent(e); 517}}; 518 519 520DragResize.prototype.mouseUp = function(e) { with (this) 521{ 522 // On mouseup, stop dragging, but don't reset handler visibility. 523 if (!document.getElementById || !enabled) return; 524 525 if (ondragend) this.ondragend(); 526 deselect(true); 527}}; 528 529 530 531 532 533// *** FNCLIENT CORE CODE *** 534 535var _f_idcount = 1; 536function fnElementFade(elm, show) 537{ 538 // Fader function that shows/hides an element. 539 var speed = show ? 20 : 10; 540 elm._f_count |= 0; 541 elm._f_timer |= null; 542 clearTimeout(elm._f_timer); 543 544 if (show && !elm._f_count) elm.style.visibility = 'inherit'; 545 546 elm._f_count = Math.max(0, Math.min(100, elm._f_count + speed*(show?1:-1))); 547 548 var f = elm.filters, done = (elm._f_count==100); 549 if (f) 550 { 551 if (!done && elm.style.filter.indexOf("alpha") == -1) 552 elm.style.filter += ' alpha(opacity=' + elm._f_count + ')'; 553 else if (f.length && f.alpha) with (f.alpha) 554 { 555 if (done) enabled = false; 556 else { opacity = elm._f_count; enabled=true } 557 } 558 } 559 else elm.style.opacity = elm.style.MozOpacity = elm._f_count/100.1; 560 561 if (!show && !elm._f_count) elm.style.visibility = 'hidden'; 562 563 if (elm._f_count % 100) 564 elm._f_timer = setTimeout(function() { fnElementFade(elm,show) }, 50); 565}; 566 567 568 569 570function fnClassSet(elm, active) 571{ 572 // Utility function that toggles the "-active" and "-inactive" classnames. 573 574 elm.className = elm.className.replace((active ? (/-inactive/) : (/-active/)), 575 (active ? '-active' : '-inactive')); 576}; 577 578 579 580 581function fnGetContainer(node) 582{ 583 // When passed a DOM node, returns its parent "fn-container". 584 585 var container = node; 586 while (container) 587 { 588 if ((/fn-container/).test(container.className)) break; 589 container = container.parentNode; 590 } 591 return container; 592}; 593 594 595 596function fnGetControlBar(container) 597{ 598 // When passed a container, returns the control bar within that container. 599 600 var controlBar = null; 601 for (var i = 0; i < container.childNodes.length; i++) 602 { 603 if ((/fn-controlbar/).test(container.childNodes.item(i).className)) 604 { 605 controlBar = container.childNodes.item(i); 606 break; 607 } 608 } 609 return controlBar; 610}; 611 612 613 614 615function fnContainerSet(container, active) 616{ 617 // Sets the "activated" status of a note container area, and changes 618 // the appropriate "toggle" item in its control bar. 619 620 var controlBar = fnGetControlBar(container); 621 for (var i = 0; i < controlBar.childNodes.length; i++) 622 { 623 if ((/fn-controlbar-toggle/).test(controlBar.childNodes.item(i).className)) 624 { 625 fnClassSet(controlBar.childNodes.item(i), !active); 626 break; 627 } 628 } 629 630 fnClassSet(container, active); 631}; 632 633 634 635 636function fnAction(action, trigger) 637{ 638 // Called on click of control buttons to highlight/dim them. 639 640 // Control the state of the trigger buttons, and set the global fnActionVerb variable. 641 if (fnActionVerb != action) 642 { 643 // Set a new action, dim the old button. 644 if (fnActionTrigger && fnActionVerb) fnClassSet(fnActionTrigger, false); 645 fnActionVerb = action; 646 fnActionTrigger = trigger; 647 if (trigger) fnClassSet(trigger, true); 648 } 649 else 650 { 651 // Deactivate a trigger that is clicked twice. 652 fnActionVerb = ''; 653 if (trigger) fnClassSet(trigger, false); 654 } 655}; 656 657 658 659 660function fnMouseOverOutHandler(evt, isOver) 661{ 662 // Called on document.onmouseover & onmouseout, manages tip visibility. 663 664 var node = evt.target || evt.srcElement; 665 if (node.nodeType != 1) node = node.parentNode; 666 667 while (node && !((node.className||'').indexOf('fn-container') > -1)) 668 { 669 // If the node has an CLASS of "fotonote-area", process it. 670 // No mouseovers if fnActionVerb is set (i.e. editing/deleting/adding/etc). 671 if (node && ((node.className||'').indexOf('fn-area') > -1) && !fnActionVerb) 672 { 673 var area = node; 674 // Find the first child element, which will be the note in question. 675 var note = area.firstChild; 676 while (note && note.nodeType != 1) note = note.nextSibling; 677 if (!note) return; 678 679 // Clear any hide timeout, and either show the note, or set a timeout for its hide. 680 // We record the currently active note for the hide timer to work, and also elevate 681 // its parent area above any previously active area (which is lowered). 682 clearTimeout(fnHideTimer); 683 if (isOver) 684 { 685 if (fnActiveNote && (note != fnActiveNote)) fnElementFade(fnActiveNote, false); 686 fnElementFade(note, true); 687 if (fnActiveNote) fnActiveNote.parentNode.style.zIndex = 1; 688 note.parentNode.style.zIndex = 2; 689 fnActiveNote = note; 690 } 691 else 692 { 693 fnHideTimer = setTimeout('if (fnActiveNote) { ' + 694 'fnElementFade(fnActiveNote, false); fnActiveNote = null }', 200); 695 } 696 } 697 698 // Loop up the DOM. 699 node = node.parentNode; 700 } 701}; 702 703 704 705 706function fnClickHandler(evt) 707{ 708 // Processes clicks on the document, performs the correct action. 709 var node = evt.target || evt.srcElement; 710 if (node.nodeType != 1) node = node.parentNode; 711 while (node && !((node.className||'').indexOf('fn-container') > -1)) 712 { 713 714 // Check buttons within the Edit bar. 715 if ((/fn-editbar-ok/).test(node.className)) return fnEditButtonHandler(true); 716 if ((/fn-editbar-cancel/).test(node.className)) return fnEditButtonHandler(false); 717 718 // Perform no other if we're currently editing a note. 719 if (fnEditingData) return; 720 721 // If an existing area with a CLASS of the form "fn-area" 722 // has been clicked, check if we're editing/deleting it. 723 if ((/fn-area/).test(node.className)) 724 { 725 var area = node; 726 if (fnActionVerb == 'del') fnDelNote(area); 727 if (fnActionVerb == 'edit') 728 { 729 var note = area.firstChild; 730 while (note && note.nodeType != 1) note = note.nextSibling; 731 if (note) fnEditNote(note); 732 } 733 return; 734 } 735 736 // Buttons on/within the Control bar. 737 if ((/fn-controlbar-logo/).test(node.className)) 738 { 739 // Logo click toggles control bar, if we're not editing a note. 740 var isActive = ((/fn-controlbar-active/).test(node.parentNode.className)); 741 fnClassSet(node.parentNode, !isActive); 742 return; 743 } 744 if ((/fn-controlbar-credits/).test(node.className)) 745 { 746 alert(FN_CREDITS); 747 return; 748 } 749 if ((/fn-controlbar-del/).test(node.className)) 750 { 751 if (!fnXMLHTTP) return alert(FN_POST_UNSUPPORTED); 752 if (FN_DELETE == 'deny') return alert(FN_DISALLOWED); 753 return fnAction('del', node); 754 } 755 if ((/fn-controlbar-edit/).test(node.className)) 756 { 757 if (!fnXMLHTTP) return alert(FN_POST_UNSUPPORTED); 758 if (FN_MODIFY == 'deny') return alert(FN_DISALLOWED); 759 return fnAction('edit', node); 760 } 761 if ((/fn-controlbar-add/).test(node.className)) 762 { 763 if (!fnXMLHTTP) return alert(FN_POST_UNSUPPORTED); 764 if (FN_ADD == 'deny') return alert(FN_DISALLOWED); 765 return fnAddNote(node); 766 } 767 if ((/fn-controlbar-toggle/).test(node.className)) 768 { 769 // Find the parent container, and toggle its classname to show/hide notes. 770 var container = fnGetContainer(node); 771 if (container) 772 { 773 var isActive = ((/fn-container-active/).test(container.className)); 774 fnContainerSet(container, !isActive); 775 } 776 } 777 778 // Otherwise, loop up the hierarchy. 779 node = node.parentNode; 780 } 781}; 782 783 784 785 786function fnEditUISet(show) 787{ 788 // Either shows or hides the editing UI. 789 790 if (!fnEditingData) return; 791 with (fnEditingData) 792 { 793 // Start or stop dragging the selected area. 794 if (show) dragresize.select(area, area); 795 else dragresize.deselect(); 796 // Set area className so its remains visible if editing, or reset it back otherwise. 797 area.className = show ? 'fn-area-editing' : 'fn-area'; 798 // Fade the editing UI in/out, and toggle its classname so it stays that way. 799 fnElementFade(form, show); 800 fnClassSet(form, show); 801 // Toggle the container class and control bar (for other notes' visibility) 802 fnContainerSet(container, !show); 803 fnClassSet(fnGetControlBar(container), !show); 804 } 805}; 806 807 808 809 810function fnAddNote(node) 811{ 812 // Adds a new note when the specified button is clicked. 813 814 // Find the parent container of this node. 815 var container = fnGetContainer(node); 816 if (!container) return; 817 818 // Highlight the "Add" button. 819 fnAction('add', node); 820 821 // Create a new area in which the note will reside. 822 var newArea = document.createElement('div'); 823 newArea.className = 'fn-area'; 824 newArea.style.left = (container.offsetWidth/2 - 25) + 'px'; 825 newArea.style.top = (container.offsetHeight/2 - 25) + 'px'; 826 newArea.style.width = '50px'; 827 newArea.style.height = '50px'; 828 newArea.id = 'fn-area-new'; 829 830 var newNote = document.createElement('div'); 831 newNote.className = 'fn-note'; 832 newArea.appendChild(newNote); 833 834 // Create note elements. 835 var newTitle = document.createElement('span'); 836 newTitle.className = 'fn-note-title'; 837 newNote.appendChild(newTitle); 838 839 var newContent = document.createElement('span'); 840 newContent.className = 'fn-note-content'; 841 newNote.appendChild(newContent); 842 843 var newAuthor = document.createElement('span'); 844 newAuthor.className = 'fn-note-author'; 845 newNote.appendChild(newAuthor); 846 847 var newUserid = document.createElement('span'); 848 newUserid.className = 'fn-note-userid'; 849 newNote.appendChild(newUserid); 850 851 var newID = document.createElement('span'); 852 newID.className = 'fn-note-id'; 853 newID.title = ''; 854 newArea.appendChild(newID); 855 856 // add in innerborders 857 var newInnerBorder = document.createElement('div'); 858 newInnerBorder.className = 'fn-area-innerborder-right'; 859 newArea.appendChild(newInnerBorder); 860 861 var newInnerBorder = document.createElement('div'); 862 newInnerBorder.className = 'fn-area-innerborder-left'; 863 newArea.appendChild(newInnerBorder); 864 865 var newInnerBorder = document.createElement('div'); 866 newInnerBorder.className = 'fn-area-innerborder-top'; 867 newArea.appendChild(newInnerBorder); 868 869 var newInnerBorder = document.createElement('div'); 870 newInnerBorder.className = 'fn-area-innerborder-bottom'; 871 newArea.appendChild(newInnerBorder); 872 873 // Add newArea to document 874 container.appendChild(newArea); 875 876 // Record this note as editing, and set the "add" action flag. 877 fnEditingData = { 878 area: newArea, 879 note: newNote 880 }; 881 882 // Hand over to the editing function. 883 fnEditNote(); 884}; 885 886 887 888 889function fnEditNote(note) 890{ 891 // Edits a passed note reference. 892 893 var area = null; 894 if (note) 895 { 896 // If we're editing an existing note, setup the data store. 897 area = note.parentNode; 898 fnEditingData = { 899 area: area, 900 note: note 901 }; 902 } 903 else 904 { 905 // New notes: pull the note and area out of the stored data. 906 area = fnEditingData.area; 907 note = fnEditingData.note; 908 } 909 910 // Find our container and form references. 911 var container = fnGetContainer(area); 912 if (!container) return; 913 var form = container.getElementsByTagName('form'); 914 if (!form) return; 915 form = form.item(0); 916 917 // Pick up existing values for content from the note. 918 var oldTitle = '', oldAuthor = '', oldContent = '', noteID = ''; 919 var fields = area.getElementsByTagName('span'); 920 for (var n = 0; n < fields.length; n++) 921 { 922 var field = fields.item(n); 923 if (field.className == 'fn-note-id') noteID = field.getAttribute('title'); 924 if (field.className == 'fn-note-title') oldTitle = field.innerHTML; 925 if (field.className == 'fn-note-author') oldAuthor = field.innerHTML; 926 if (field.className == 'fn-note-content') oldContent = field.innerHTML; 927 } 928 929 // Backup the original content, refs and position in our datastore. 930 // It already has the .note and .area properties. 931 // And yes, I know innerHTML isn't standard, but it's SO MUCH EASIER here! 932 fnEditingData.container = container; 933 fnEditingData.form = form; 934 fnEditingData.noteID = noteID; 935 fnEditingData.oldTitle = oldTitle; 936 fnEditingData.oldAuthor = oldAuthor; 937 fnEditingData.oldContent = oldContent; 938 fnEditingData.oldLeft = parseInt(area.style.left); 939 fnEditingData.oldTop = parseInt(area.style.top); 940 fnEditingData.oldWidth = area.offsetWidth; 941 fnEditingData.oldHeight = area.offsetHeight; 942 // Some values for the post-editing callback handler to populate. 943 fnEditingData.newTitle = fnEditingData.newAuthor = fnEditingData.newContent = ''; 944 fnEditingData.newLeft = fnEditingData.newTop = 0; 945 fnEditingData.newWidth = fnEditingData.newHeight = 0; 946 947 // Populate the editing UI with its current content. 948 var inputs = form.getElementsByTagName('input'); 949 for (var i = 0; i < inputs.length; i++) 950 { 951 if ((/title/).test(inputs[i].className)) inputs[i].value = oldTitle; 952 if ((/author/).test(inputs[i].className)) inputs[i].value = oldAuthor; 953 } 954 var textarea = form.getElementsByTagName('textarea'); 955 if (textarea && (/content/).test(textarea.item(0).className)) 956 textarea.item(0).value = oldContent; 957 958 // Finally, show the editing UI for the recorded area. 959 fnEditUISet(true); 960}; 961 962 963 964 965function fnEscapeHTML(html) 966{ 967 // Returns a properly escaped HTML string. 968 969 return html.replace('&', '&').replace('<', '<').replace('>', '>'); 970}; 971 972 973 974 975function fnEditButtonHandler(ok) 976{ 977 // Button click handler from the editing UI. 978 // Pass a boolean value indicating if the OK button was clicked (so save should proceed). 979 980 if (!fnEditingData) return; 981 with (fnEditingData) 982 { 983 if (ok) 984 { 985 // Populate fnEditingData.new* from the edit form fields and area attributes. 986 // SET default value for all params. 987 newTitle = newAuthor = newUserid = newEntryid = newContent = newBorderColor = ''; 988 var inputs = form.getElementsByTagName('input'); 989 for (var i = 0; i < inputs.length; i++) 990 { 991 if ((/title/).test(inputs[i].className)) {newTitle = inputs[i].value;} //else {newTitle = '';} 992 if ((/author/).test(inputs[i].className)) {newAuthor = inputs[i].value;} //else {newAuthor = '';} 993 if ((/userid/).test(inputs[i].className)) {newUserid = inputs[i].value;} //else {newUserid = '';} 994 if ((/entry_id/).test(inputs[i].className)) {newEntryid = inputs[i].value;} //else {newEntryid = '';} 995 if ((/border_color/).test(inputs[i].className)) {newBorderColor = inputs[i].value;} //else {newEntryid = '';} 996 } 997 var textarea = form.getElementsByTagName('textarea'); 998 if (textarea && (/content/).test(textarea.item(0).className)) {newContent = textarea.item(0).value}; 999 newLeft = parseInt(area.style.left); 1000 newTop = parseInt(area.style.top); 1001 newWidth = area.offsetWidth; 1002 newHeight = area.offsetHeight; 1003 1004 if (fnDebugMode) alert('Begin server save operation ' + 'newBorderColor: ' + newBorderColor); 1005 1006 // Get the scalefactor from a hidden SPAN in the container. 1007 var sFact = 1; 1008 for (var n = 0; n < container.childNodes.length; n++) 1009 { 1010 if ((/fn-scalefactor/).test(container.childNodes.item(n).className)) 1011 sFact = parseFloat(container.childNodes.item(n).getAttribute('title')); 1012 } 1013 1014 // Begin server save operation. 1015 /* Bordercolor UI elements have been removed fn div elements. See fnclient-0.4.0.bordercolor for elements.*/ 1016 fnPostXML( 1017 '<?xml version="1.0" encoding="UTF-8"?>' + 1018 '<feed><entry>' + 1019 (fnActionVerb == 'edit' ? '<id>' + noteID + '</id>' : '') + 1020 '<fn:selection><fn:boundingBox>' + 1021 parseInt(newLeft/sFact) + ',' + parseInt(newTop/sFact) + ',' + 1022 parseInt((newLeft+newWidth)/sFact) + ',' + parseInt((newTop+newHeight)/sFact) + 1023 '</fn:boundingBox></fn:selection>' + 1024 '<title>' + fnEscapeHTML(newTitle) + '</title>' + 1025 '<author><name>' + fnEscapeHTML(newAuthor) + '</name><userid>' + fnEscapeHTML(newUserid) + '</userid></author>' + 1026 '<content>' + fnEscapeHTML(newContent) + '</content>' + '<entry_id>' + fnEscapeHTML(newEntryid) + '</entry_id>' + 1027 '</entry></feed>' 1028 ); 1029 1030 } 1031 else 1032 { 1033 // For "cancel" clicks: 1034 1035 if (fnActionVerb == 'add') 1036 { 1037 // Just delete new notes. 1038 area.parentNode.removeChild(area); 1039 } 1040 else 1041 { 1042 // Restore original note area position/size for edited notes. 1043 area.style.left = oldLeft + 'px'; 1044 area.style.top = oldTop + 'px'; 1045 area.style.width = oldWidth + 'px'; 1046 area.style.height = oldHeight + 'px'; 1047 } 1048 1049 // Hide the editing UI, reset the control bar, clear the data store. 1050 fnEditUISet(false); 1051 fnAction('', null); 1052 fnEditingData = null; 1053 } 1054 } 1055 1056}; 1057 1058 1059 1060 1061function fnDelNote(area) 1062{ 1063 // Deletes a note area -- passed a whole area reference. 1064 1065 // Find the ID of this note. 1066 var noteID = '', fields = area.getElementsByTagName('span'); 1067 for (var n = 0; n < fields.length; n++) 1068 if (fields.item(n).className == 'fn-note-id') 1069 noteID = fields.item(n).getAttribute('title'); 1070 if (!noteID) alert(FN_SAVE_FAIL); 1071 1072 if (noteID && confirm(FN_DELETE_CONFIRM)) 1073 { 1074 // Set up our data store to delete this area, and post to the server. 1075 fnEditingData = { 1076 area: area, 1077 note: null, 1078 container: fnGetContainer(area) 1079 }; 1080 fnPostXML( 1081 '<?xml version="1.0" encoding="UTF-8"?>' + 1082 '<feed><entry>' + 1083 '<id>' + noteID + '</id>' + 1084 '</entry></feed>' 1085 ); 1086 } 1087 else 1088 { 1089 // Reset control bar if cancelled. 1090 fnAction('', null); 1091 } 1092 1093}; 1094 1095 1096 1097 1098function fnModalDialog(message) 1099{ 1100 // Shows or hides the browser-wide modal dialog. 1101 // Pass a message to show, or an empty string to hide the dialog. 1102 1103 var dialog = document.getElementById('fn-modaldialog'); 1104 if (!dialog) 1105 { 1106 dialog = document.createElement('div'); 1107 dialog.setAttribute('id', 'fn-modaldialog'); 1108 document.body.appendChild(dialog); 1109 } 1110 1111 /* 1112 // Different approach for IE/Windows, since it doesn't support position: fixed. 1113 dialog.style.position = (window.ActiveXObject ? 'absolute' : 'fixed'); 1114 dialog.style.zIndex = '100000'; 1115 dialog.style.top = (window.activeXObject ? 1116 document.documentElement.scrollTop+(document.documentElement.clientHeight/2) + 'px' : '0'); 1117 dialog.style.left = '0'; 1118 dialog.style.width = '100%'; 1119 dialog.style.height = (window.ActiveXObject ? 1120 document.documentElement.scrollHeight : '100%'); 1121 dialog._setupDone = true; 1122*/ 1123 dialog.innerHTML = '<span>' + message + '</span>'; 1124 dialog.style.visibility = message ? 'visible' : 'hidden'; 1125}; 1126 1127 1128 1129 1130function fnPostXML(xml) 1131{ 1132 // Sends some XML off to the server and calls fnEditComplete on completion. 1133 1134 // Hopefully my auto-detect-fu powers are strong. I'll use the Crouching Regex Style. 1135 var image = fnEditingData.container.getElementsByTagName('img').item(0); 1136 var imageFile = image.getAttribute('src'); 1137 if (!imageFile) return alert(FN_SAVE_FAIL); 1138 1139 // Figure out if we need to prompt the user for a password. 1140 var password = '', password_req = false; 1141 switch (fnActionVerb) 1142 { 1143 case 'add': { if (FN_ADD == 'prompt') password_req = true; break }; 1144 case 'edit': { if (FN_MODIFY == 'prompt') password_req = true; break }; 1145 case 'del': { if (FN_DELETE == 'prompt') password_req = true; break }; 1146 } 1147 if (password_req) 1148 { 1149 // TODO: Secure input here. 1150 password = prompt('Please enter your password', ''); 1151 } 1152 1153 // Compose our post content and send it. 1154 var actVerbs = { add: 'add', edit: 'modify', del: 'delete' }; 1155 var postContent = 'image=' + escape(imageFile) + '&action=' + actVerbs[fnActionVerb] + 1156 (password ? '&password=' + escape(password) : '') + 1157 '&xml=' + escape(xml); 1158 1159 if (fnDebugMode) alert('SENDING TO FNSERVER:\n\n' + postContent); 1160 1161 fnXMLHTTP.open('POST', fnServer, true); 1162 fnXMLHTTP.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8'); 1163 fnXMLHTTP.setRequestHeader('Content-length', postContent.length); 1164 var cookies = document.cookie.split(';'); 1165 /* 1166 for (var c = 0; c < cookies.length; c++) 1167 { 1168 fnXMLHTTP.setRequestHeader('Cookie', cookies[c]); 1169 } 1170 */ 1171 fnXMLHTTP.onreadystatechange = function() 1172 { 1173 if (fnXMLHTTP.readyState == 4) fnEditComplete(true); 1174 }; 1175 1176 // Show "please wait" modal dialog, which prevents document clicks, and send. 1177 fnModalDialog(FN_SAVE_WAIT); 1178 fnXMLHTTP.send(postContent); 1179}; 1180 1181 1182 1183 1184function fnEditComplete(ok) 1185{ 1186 // Called once the server responds post-Save operation. 'ok' indicates success. 1187 1188 if (fnDebugMode) alert('RECEIVED FROM FNSERVER:\n\n' + fnXMLHTTP.responseText); 1189 1190 if (!ok || !fnXMLHTTP.responseText.match('success=ok')) 1191 { 1192 // In the case of a communication error, hide the modal dialog and alert the user. 1193 fnModalDialog(''); 1194 if (fnXMLHTTP.responseText.match('success=501')) { 1195 // File is not writable 1196 alert(FN_SAVE_FAIL_JPEG_NOT_WRITABLE); 1197 } else { 1198 // Some other error occurred. 1199 alert(FN_SAVE_FAIL); 1200 } 1201 // Failed deletes: reset the control bar and clear the data store. 1202 // (Failed edits/adds: UI and data persist). 1203 if (fnActionVerb == 'del') 1204 { 1205 fnEditingData = null; 1206 fnAction('', null); 1207 } 1208 } 1209 else with (fnEditingData) 1210 { 1211 // Depending on our action, commit the changes to the document. 1212 if (fnActionVerb == 'add' || fnActionVerb == 'edit') 1213 { 1214 // Place new values in the note. It's already in the right position. 1215 for (var n = 0; n < note.childNodes.length; n++) 1216 { 1217 var field = note.childNodes.item(n); 1218 if (field.className == 'fn-note-title') field.innerHTML = newTitle; 1219 if (field.className == 'fn-note-author') field.innerHTML = newAuthor; 1220 if (field.className == 'fn-note-content') field.innerHTML = newContent; 1221 } 1222 // Hide the editing UI. 1223 fnEditUISet(false); 1224 } 1225 else 1226 { 1227 // Deleting notes? Just remove the area from the document. 1228 area.parentNode.removeChild(area); 1229 } 1230 1231 // All successful actions: let the user know it's OK, and reset the control bar, 1232 // and clear the editing data store. 1233 fnModalDialog(FN_SAVE_SUCCESS); 1234 setTimeout('fnModalDialog("")', 500); 1235 fnAction('', null); 1236 fnEditingData = null; 1237 } 1238 // Reload the page - Added temporarily 1239 // window.location.reload(); 1240}; 1241 1242 1243 1244 1245 1246 1247// INITIALISATION CODE: 1248if (document.getElementById) 1249{ 1250 // Create a new DragResize() object, and set it up. 1251 // We apply to the whole document to interoperate with blinds. 1252 var dragresize = new DragResize('dragresize', { allowBlur: false }); 1253 dragresize.isElement = function(elm) 1254 { 1255 if (!(/(add|edit)/).test(fnActionVerb)) return false; 1256 if ((/fn-area-editing/).test(elm.className)) 1257 { 1258 var container = fnGetContainer(elm); 1259 this.maxRight = container.offsetWidth - 2; 1260 this.maxBottom = container.offsetHeight - 2; 1261 return true; 1262 } 1263 }; 1264 dragresize.isHandle = function(elm) 1265 { 1266 if (!(/(add|edit)/).test(fnActionVerb)) return false; 1267 if ((/fn-area-editing/).test(elm.className)) return true; 1268 }; 1269 dragresize.ondragfocus = function() 1270 { 1271 this.element.style.cursor = 'move'; 1272 }; 1273 dragresize.ondragblur = function() 1274 { 1275 this.element.style.cursor = 'default'; 1276 }; 1277 dragresize.apply(document); 1278 1279 1280 // *** Global event handler setup *** 1281 // These are global, rather than assigned to individual notes, to work with the "blind" code. 1282 1283 // Note show/hide events. 1284 addEvent(document, 'mouseover', new Function('e', 'fnMouseOverOutHandler(e, 1)')); 1285 addEvent(document, 'mouseout', new Function('e', 'fnMouseOverOutHandler(e, 0)')); 1286 // Creation/editing/deletion events. 1287 if (document.createElement && document.documentElement) 1288 { 1289 //addEvent(document, 'mousedown', fnMouseDownHandler); 1290 //addEvent(document, 'mouseup', fnMouseUpHandler); 1291 addEvent(document, 'click', fnClickHandler); 1292 } 1293} 1294 1295 1296 1297 1298