// Fotonotes DHTML Client (c) 2004-2005 Angus Turnbull http://www.twinhelix.com // Developed under license to FotoNotes LLC // Released under the Open Source License v2.1 or later. // Modification 2005.11.17 - add loading scripts - Greg // See the bottom of this file for configuration. // *** FNCLIENT CONFIGURATION, VARIABLES AND SETUP *** // Address of fotonoter.php on the server (this auto-detect should work): var fnServerPath = "../"; var fnServerFotonotesScript = "fotonotes.php"; var fnServer = fnServerPath + fnServerFotonotesScript; // XMLHTTPRequest object to communicate with server. var fnXMLHTTP = null; if (window.ActiveXObject) { try { fnXMLHTTP = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { } } else if (window.XMLHttpRequest) { fnXMLHTTP = new XMLHttpRequest(); } // Permissions (respect previous settings): // Allowed values are 'allow', 'prompt', 'deny'. if (!window.FN_ADD) var FN_ADD = 'allow'; if (!window.FN_MODIFY) var FN_MODIFY = 'allow'; if (!window.FN_DELETE) var FN_DELETE = 'allow'; // Internationalisation: var FN_CREDITS = 'Fotonotes DHTML Viewer\n\n' + '(c) 2004-2005 Angus Turnbull, http://www.twinhelix.com\n\n' + 'Provided under license to Fotonotes LLC'; var FN_DISALLOWED = 'Sorry, that action is not permitted.\n\n' + 'Please login under a different account.'; var FN_POST_UNSUPPORTED = 'Sorry, your browser does not support editing notes.'; var FN_DELETE_CONFIRM = 'Are you sure you want to delete this note?'; var FN_SAVE_WAIT = 'Loading Fotonotes...'; var FN_SAVE_FAIL = 'An error occurred, and your changes could not be saved.'; var FN_SAVE_FAIL_JPEG_NOT_WRITABLE = "JPEG file is not writable. Please check file permissions on server."; var FN_SAVE_SUCCESS = 'Changes saved!'; // Other global variables: var fnDebugMode = false; // Set to true to show XML sent/received. var fnHideTimer = null; // Hide notes after timeout. var fnActiveNote = null; // Currently visible note. var fnActionVerb = ''; // Control bar's current action. var fnActionTrigger = null; // Control bar's lit item. var fnEditingData = null; // Data store during note editing process. var fnAnnotateAll = false; // Indicate annotation should be applied to all images var fnMinImgWidth = 200; // MinWidth to make to apply to fn-image var fnMinImgHeight = 150; // MinHeight to make to apply to fn-image var imageFileSrc = "src"; // Use 'id' for findImage() to use imgObj.id; use "src" (default) for findImage to use imgOb.src // *** Common API Code *** var aeOL = []; function addEvent(o, n, f, l) { var a = 'addEventListener', h = 'on'+n, b = '', s = ''; if (o[a] && !l) return o[a](n, f, false); o._c |= 0; if (o[h]) { b = '_f' + o._c++; o[b] = o[h]; } s = '_f' + o._c++; o[s] = f; o[h] = function(e) { e = e || window.event; var r = true; if (b) r = o[b](e) != false && r; r = o[s](e) != false && r; return r; }; aeOL[aeOL.length] = { o: o, h: h }; }; addEvent(window, 'unload', function() { for (var i = 0; i < aeOL.length; i++) with (aeOL[i]) { o[h] = null; for (var c = 0; o['_f' + c]; c++) o['_f' + c] = null; } }); function cancelEvent(e, c) { e.returnValue = false; if (e.preventDefault) e.preventDefault(); if (c) { e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); } }; // *** FNCLIENT LOAD DIVS *** // The following functions run after page loaded and retrieve Fotonotes data into the document to show annotations. addLoadEvent(findImage); function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != 'function') { window.onload = func; } else { window.onload = function() { oldonload(); func(); } } } function findImage() { for (i=0;i < document.images.length; i++) { if (fnDebugMode) alert('img '+document.images[i].className); if ( (document.images[i].className == "fn-image") || ( (fnAnnotateAll) && (document.images[i].width >= fnMinImgWidth) && (document.images[i].height >= fnMinImgHeight)) ) { var imgObj = document.images[i]; // get path to image. if (fnDebugMode) alert("imgObj.src: "+imgObj.src); if (imageFileSrc == "id") { var imageFile = imgObj.id; } else { var imageFile = imgObj.src; } if (fnDebugMode) alert('revised imageFile: \n\n' + imageFile); if (imgObj.parentNode.tagName == "A") { var currentLinkNode = imgObj.parentNode; var newNode = document.createElement('div'); //newNode.innerHTML = "replacement newNode"; imgObj.parentNode.parentNode.replaceChild(newNode, imgObj.parentNode); newNode.appendChild(imgObj); newLinkNode = document.createElement('div'); newLinkNode.className = "fn-view-image-link"; currentLinkNode.innerHTML = "View image"; var pathToImage = unescape(currentLinkNode.pathname); var temp = pathToImage.split('blank'); if (window.ActiveXObject) { currentLinkNode.href = temp[1]; //IE quirk } else { currentLinkNode.href = temp[0]; } newLinkNode.appendChild(currentLinkNode); newNode.appendChild(newLinkNode); /* ImgElement.parentNode.parentNode.replaceChild(newNode, ImgElement.parentNode); Y = document.createElement('div'); */ } createFNImage(imgObj, imageFile); } } } function createFNImage(imgObj, imageFile) { getFNDiv(imgObj, imageFile); } function getFNDiv(imgObj, imageFile) { // Sends some XML off to the server to get FNClient for image calls fnGetClientComplete on completion. //imageFile = imgObj.src; // deprecated assigment if (fnDebugMode) alert('Final imageFile: \n\n' + imageFile); if (!imageFile) return alert(FN_SAVE_FAIL); // Compose our post content and send it. var postContent = 'image=' + escape(imageFile) + '&action=' + 'display' + '&width=' + imgObj.width + '&height=' + imgObj.height + '&alt=' + imgObj.alt + '&style=';// + imgObj.style; if (fnDebugMode) alert('TARGET SERVER URL: \n\n' + fnServer); if (fnDebugMode) alert('SENDING TO tlnServer:\n\n' + postContent); /* fnXMLHTTP.open('POST', fnServer, false); // use "false" to stop script from proceeding until data received. fnXMLHTTP.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8'); fnXMLHTTP.setRequestHeader('Content-length', postContent.length); var cookies = document.cookie.split(';'); if (cookies != null) { for (var c = 0; c < cookies.length; c++) { if (cookies[c].length > 0) fnXMLHTTP.setRequestHeader('Cookie', cookies[c]); } } */ // XMLHTTPRequest object to communicate with server. var fnObjXMLHTTP = null; if (window.ActiveXObject) { try { fnObjXMLHTTP = new ActiveXObject('Microsoft.XMLHTTP'); //fnObjXMLHTTP = new ActiveXObject('Msxml2.XMLHTTP.4.0'); // Angus recommend this call, but 'Microsoft.XMLHTTP' seems to be working better. } catch (e) { } } else if (window.XMLHttpRequest) { fnObjXMLHTTP = new XMLHttpRequest(); } fnObjXMLHTTP.open('POST', fnServer, true); fnObjXMLHTTP.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8'); fnObjXMLHTTP.setRequestHeader('Content-length', postContent.length); var cookies2 = document.cookie.split(';'); /* Commented out in December 2005 b/c of problems in some instances of IE for (var c = 0; c < cookies2.length; c++) { fnObjXMLHTTP.setRequestHeader('Cookie', cookies2[c]); } */ fnObjXMLHTTP.onreadystatechange = function() { if (fnObjXMLHTTP.readyState == 4) fnGetClientComplete(true,imgObj,fnObjXMLHTTP.responseText); }; fnObjXMLHTTP.send(postContent); } function fnGetClientComplete(ok2,imgObj,responseText) { // All successful actions: let the user know it's OK, and reset the control bar, // and clear the editing data store. fnModalDialog(FN_SAVE_WAIT); setTimeout('fnModalDialog("")', 500); if (fnDebugMode) alert('RECEIVED FROM FNSERVER:\n\n' + responseText); // Called once the server responds post-Save operation. 'ok' indicates success. // EXTRACT returned HTML text from reply to update document re = /displayHTML##([\w\W\n\r]*)##/; //alert ('re' + re); //alert ('test? ' + re.test(fnXMLHTTP.responseText)); matches = re.exec(responseText); //fnDiv = fnXMLHTTP.responseText; fnDiv = matches[1]; // first matche pattern fnDivElement = document.createElement('div'); fnDivElement.innerHTML = fnDiv; imgObj.parentNode.insertBefore(fnDivElement,imgObj); imgObj.parentNode.removeChild(imgObj); } // *** Drag and Resize Library Code *** // (c) 2005 Angus Turnbull http://www.twinhelix.come function DragResize(myName, config) { var props = { myName: myName, // Name of the object. enabled: true, // Global toggle of drag/resize. handles: ['tl', 'tm', 'tr', 'ml', 'mr', 'bl', 'bm', 'br'], // Array of drag handles: top/mid/. isElement: null, // Function ref to test for an element. isHandle: null, // Function ref to test for move handle. element: null, // The currently selected element. dragging: null, // Active handle reference of the element. minWidth: 10, minHeight: 10, // Minimum pixel size of elements. minLeft: 0, maxRight: 9999, // Bounding box area. minTop: 0, maxBottom: 9999, zIndex: 1, // The highest Z-Index yet allocated. mouseX: 0, mouseY: 0, // Current mouse position, recorded live. lastMouseX: 0, lastMouseY: 0, // Last processed mouse positions. mOffX: 0, mOffY: 0, // A known offset between position & mouse. elmX: 0, elmY: 0, // Element position. elmW: 0, elmH: 0, // Element size. allowBlur: true, // Whether to allow automatic blur onclick. ondragfocus: null, // Event handler functions. ondragstart: null, ondragmove: null, ondragend: null, ondragblur: null }; for (var p in props) { this[p] = (typeof config[p] == 'undefined') ? props[p] : config[p]; } }; DragResize.prototype.apply = function(node) { // Adds object event handlers to the specified DOM node. var obj = this; addEvent(node, 'mousedown', function(e) { obj.mouseDown(e) } ); addEvent(node, 'mousemove', function(e) { obj.mouseMove(e) } ); addEvent(node, 'mouseup', function(e) { obj.mouseUp(e) } ); }; DragResize.prototype.handleSet = function(elm, show) { with (this) { // Either creates, shows or hides the resize handles within an element. // If we're showing them, and no handles have been created, create 4 new ones. if (!elm._handle_tr) { for (var h = 0; h < handles.length; h++) { // Create 4 news divs, assign each a generic + specific class. var hDiv = document.createElement('div'); hDiv.className = myName + ' ' + myName + '-' + handles[h]; elm['_handle_' + handles[h]] = elm.appendChild(hDiv); } } // We now have handles. Find them all and show/hide. for (var h = 0; h < handles.length; h++) { elm['_handle_' + handles[h]].style.visibility = show ? 'inherit' : 'hidden'; } }}; DragResize.prototype.select = function(newElement) { with (this) { // Selects an element for dragging. if (!document.getElementById || !enabled) return; // Activate and record our new dragging element. if (newElement && (newElement != element) && enabled) { element = newElement; // Elevate it and give it resize handles. element.style.zIndex = ++zIndex; handleSet(element, true); // Record element attributes for mouseMove(). elmX = parseInt(element.style.left); elmY = parseInt(element.style.top); elmW = element.offsetWidth; elmH = element.offsetHeight; if (ondragfocus) this.ondragfocus(); } }}; DragResize.prototype.deselect = function(keepHandles) { with (this) { // Immediately stops dragging an element. If 'keepHandles' is false, this // remove the handles from the element and clears the element flag, // completely resetting the . if (!document.getElementById || !enabled) return; if (!keepHandles) { if (ondragblur) this.ondragblur(); handleSet(element, false); element = null; } dragging = null; mOffX = 0; mOffY = 0; }}; DragResize.prototype.mouseDown = function(e) { with (this) { // Suitable elements are selected for drag/resize on mousedown. // We also initialise the resize boxes, and drag parameters like mouse position etc. if (!document.getElementById || !enabled) return true; var elm = e.target || e.srcElement, newElement = null, newHandle = null, hRE = new RegExp(myName + '-([trmbl]{2})', ''); while (elm) { // Loop up the DOM looking for matching elements. Remember one if found. if (elm.className) { if (!newHandle && (hRE.test(elm.className) || isHandle(elm))) newHandle = elm; if (isElement(elm)) { newElement = elm; break } } elm = elm.parentNode; } // If this isn't on the last dragged element, call deselect(false), // which will hide its handles and clear element. if (element && (element != newElement) && allowBlur) deselect(false); // If we have a new matching element, call select(). if (newElement && (!element || (newElement == element))) { // Stop mouse selections. cancelEvent(e); select(newElement, newHandle); dragging = newHandle; if (dragging && ondragstart) this.ondragstart(); } }}; DragResize.prototype.mouseMove = function(e) { with (this) { // This continually offsets the dragged element by the difference between the // last recorded mouse position (mouseX/Y) and the current mouse position. if (!document.getElementById || !enabled) return true; // We always record the current mouse position. mouseX = e.pageX || e.clientX + document.documentElement.scrollLeft; mouseY = e.pageY || e.clientY + document.documentElement.scrollTop; // Record the relative mouse movement, in case we're dragging. // Add any previously stored&ignored offset to the calculations. var diffX = mouseX - lastMouseX + mOffX; var diffY = mouseY - lastMouseY + mOffY; mOffX = mOffY = 0; // Update last processed mouse positions. lastMouseX = mouseX; lastMouseY = mouseY; // That's all we do if we're not dragging anything. if (!dragging) return true; // Establish which handle is being dragged -- retrieve handle name from className. var hClass = dragging && dragging.className && dragging.className.match(new RegExp(myName + '-([tmblr]{2})')) ? RegExp.$1 : ''; // If the hClass is one of the resize handles, resize one or two dimensions. // Bounds checking is the hard bit -- basically for each edge, check that the // element doesn't go under minimum size, and doesn't go beyond its boundary. var rs = 0, dY = diffY, dX = diffX; if (hClass.indexOf('t') >= 0) { rs = 1; if (elmH - dY < minHeight) mOffY = (dY - (diffY = elmH - minHeight)); else if (elmY + dY < minTop) mOffY = (dY - (diffY = minTop - elmY)); elmY += diffY; elmH -= diffY; } if (hClass.indexOf('b') >= 0) { rs = 1; if (elmH + dY < minHeight) mOffY = (dY - (diffY = minHeight - elmH)); else if (elmY + elmH + dY > maxBottom) mOffY = (dY - (diffY = maxBottom - elmY - elmH)); elmH += diffY; } if (hClass.indexOf('l') >= 0) { rs = 1; if (elmW - dX < minWidth) mOffX = (dX - (diffX = elmW - minWidth)); else if (elmX + dX < minLeft) mOffX = (dX - (diffX = minLeft - elmX)); elmX += diffX; elmW -= diffX; } if (hClass.indexOf('r') >= 0) { rs = 1; if (elmW + dX < minWidth) mOffX = (dX - (diffX = minWidth - elmW)); else if (elmX + elmW + dX > maxRight) mOffX = (dX - (diffX = maxRight - elmX - elmW)); elmW += diffX; } // If 'rs' isn't set, we must be dragging the whole element, so move that. if (dragging && !rs) { // Bounds check left-right... if (elmX + dX < minLeft) mOffX = (dX - (diffX = minLeft - elmX)); else if (elmX + elmW + dX > maxRight) mOffX = (dX - (diffX = maxRight - elmX - elmW)); // ...and up-down. if (elmY + dY < minTop) mOffY = (dY - (diffY = minTop - elmY)); else if (elmY + elmH + dY > maxBottom) mOffY = (dY - (diffY = maxBottom - elmY - elmH)); elmX += diffX; elmY += diffY; } // Assign new info back to the element, with minimum dimensions. with (element.style) { left = elmX + 'px'; width = elmW + 'px'; top = elmY + 'px'; height = elmH + 'px'; } // Evil, dirty, hackish Opera select-as-you-drag fix. if (window.opera && document.documentElement) { var oDF = document.getElementById('op-drag-fix'); if (!oDF) { var oDF = document.createElement('input'); oDF.id = 'op-drag-fix'; oDF.style.display = 'none'; document.body.appendChild(oDF); } oDF.focus(); } if (ondragmove) this.ondragmove(); // Stop a normal drag event. cancelEvent(e); }}; DragResize.prototype.mouseUp = function(e) { with (this) { // On mouseup, stop dragging, but don't reset handler visibility. if (!document.getElementById || !enabled) return; if (ondragend) this.ondragend(); deselect(true); }}; // *** FNCLIENT CORE CODE *** var _f_idcount = 1; function fnElementFade(elm, show) { // Fader function that shows/hides an element. var speed = show ? 20 : 10; elm._f_count |= 0; elm._f_timer |= null; clearTimeout(elm._f_timer); if (show && !elm._f_count) elm.style.visibility = 'inherit'; elm._f_count = Math.max(0, Math.min(100, elm._f_count + speed*(show?1:-1))); var f = elm.filters, done = (elm._f_count==100); if (f) { if (!done && elm.style.filter.indexOf("alpha") == -1) elm.style.filter += ' alpha(opacity=' + elm._f_count + ')'; else if (f.length && f.alpha) with (f.alpha) { if (done) enabled = false; else { opacity = elm._f_count; enabled=true } } } else elm.style.opacity = elm.style.MozOpacity = elm._f_count/100.1; if (!show && !elm._f_count) elm.style.visibility = 'hidden'; if (elm._f_count % 100) elm._f_timer = setTimeout(function() { fnElementFade(elm,show) }, 50); }; function fnClassSet(elm, active) { // Utility function that toggles the "-active" and "-inactive" classnames. elm.className = elm.className.replace((active ? (/-inactive/) : (/-active/)), (active ? '-active' : '-inactive')); }; function fnGetContainer(node) { // When passed a DOM node, returns its parent "fn-container". var container = node; while (container) { if ((/fn-container/).test(container.className)) break; container = container.parentNode; } return container; }; function fnGetControlBar(container) { // When passed a container, returns the control bar within that container. var controlBar = null; for (var i = 0; i < container.childNodes.length; i++) { if ((/fn-controlbar/).test(container.childNodes.item(i).className)) { controlBar = container.childNodes.item(i); break; } } return controlBar; }; function fnContainerSet(container, active) { // Sets the "activated" status of a note container area, and changes // the appropriate "toggle" item in its control bar. var controlBar = fnGetControlBar(container); for (var i = 0; i < controlBar.childNodes.length; i++) { if ((/fn-controlbar-toggle/).test(controlBar.childNodes.item(i).className)) { fnClassSet(controlBar.childNodes.item(i), !active); break; } } fnClassSet(container, active); }; function fnAction(action, trigger) { // Called on click of control buttons to highlight/dim them. // Control the state of the trigger buttons, and set the global fnActionVerb variable. if (fnActionVerb != action) { // Set a new action, dim the old button. if (fnActionTrigger && fnActionVerb) fnClassSet(fnActionTrigger, false); fnActionVerb = action; fnActionTrigger = trigger; if (trigger) fnClassSet(trigger, true); } else { // Deactivate a trigger that is clicked twice. fnActionVerb = ''; if (trigger) fnClassSet(trigger, false); } }; function fnMouseOverOutHandler(evt, isOver) { // Called on document.onmouseover & onmouseout, manages tip visibility. var node = evt.target || evt.srcElement; if (node.nodeType != 1) node = node.parentNode; while (node && !((node.className||'').indexOf('fn-container') > -1)) { // If the node has an CLASS of "fotonote-area", process it. // No mouseovers if fnActionVerb is set (i.e. editing/deleting/adding/etc). if (node && ((node.className||'').indexOf('fn-area') > -1) && !fnActionVerb) { var area = node; // Find the first child element, which will be the note in question. var note = area.firstChild; while (note && note.nodeType != 1) note = note.nextSibling; if (!note) return; // Clear any hide timeout, and either show the note, or set a timeout for its hide. // We record the currently active note for the hide timer to work, and also elevate // its parent area above any previously active area (which is lowered). clearTimeout(fnHideTimer); if (isOver) { if (fnActiveNote && (note != fnActiveNote)) fnElementFade(fnActiveNote, false); fnElementFade(note, true); if (fnActiveNote) fnActiveNote.parentNode.style.zIndex = 1; note.parentNode.style.zIndex = 2; fnActiveNote = note; } else { fnHideTimer = setTimeout('if (fnActiveNote) { ' + 'fnElementFade(fnActiveNote, false); fnActiveNote = null }', 200); } } // Loop up the DOM. node = node.parentNode; } }; function fnClickHandler(evt) { // Processes clicks on the document, performs the correct action. var node = evt.target || evt.srcElement; if (node.nodeType != 1) node = node.parentNode; while (node && !((node.className||'').indexOf('fn-container') > -1)) { // Check buttons within the Edit bar. if ((/fn-editbar-ok/).test(node.className)) return fnEditButtonHandler(true); if ((/fn-editbar-cancel/).test(node.className)) return fnEditButtonHandler(false); // Perform no other if we're currently editing a note. if (fnEditingData) return; // If an existing area with a CLASS of the form "fn-area" // has been clicked, check if we're editing/deleting it. if ((/fn-area/).test(node.className)) { var area = node; if (fnActionVerb == 'del') fnDelNote(area); if (fnActionVerb == 'edit') { var note = area.firstChild; while (note && note.nodeType != 1) note = note.nextSibling; if (note) fnEditNote(note); } return; } // Buttons on/within the Control bar. if ((/fn-controlbar-logo/).test(node.className)) { // Logo click toggles control bar, if we're not editing a note. var isActive = ((/fn-controlbar-active/).test(node.parentNode.className)); fnClassSet(node.parentNode, !isActive); return; } if ((/fn-controlbar-credits/).test(node.className)) { alert(FN_CREDITS); return; } if ((/fn-controlbar-del/).test(node.className)) { if (!fnXMLHTTP) return alert(FN_POST_UNSUPPORTED); if (FN_DELETE == 'deny') return alert(FN_DISALLOWED); return fnAction('del', node); } if ((/fn-controlbar-edit/).test(node.className)) { if (!fnXMLHTTP) return alert(FN_POST_UNSUPPORTED); if (FN_MODIFY == 'deny') return alert(FN_DISALLOWED); return fnAction('edit', node); } if ((/fn-controlbar-add/).test(node.className)) { if (!fnXMLHTTP) return alert(FN_POST_UNSUPPORTED); if (FN_ADD == 'deny') return alert(FN_DISALLOWED); return fnAddNote(node); } if ((/fn-controlbar-toggle/).test(node.className)) { // Find the parent container, and toggle its classname to show/hide notes. var container = fnGetContainer(node); if (container) { var isActive = ((/fn-container-active/).test(container.className)); fnContainerSet(container, !isActive); } } // Otherwise, loop up the hierarchy. node = node.parentNode; } }; function fnEditUISet(show) { // Either shows or hides the editing UI. if (!fnEditingData) return; with (fnEditingData) { // Start or stop dragging the selected area. if (show) dragresize.select(area, area); else dragresize.deselect(); // Set area className so its remains visible if editing, or reset it back otherwise. area.className = show ? 'fn-area-editing' : 'fn-area'; // Fade the editing UI in/out, and toggle its classname so it stays that way. fnElementFade(form, show); fnClassSet(form, show); // Toggle the container class and control bar (for other notes' visibility) fnContainerSet(container, !show); fnClassSet(fnGetControlBar(container), !show); } }; function fnAddNote(node) { // Adds a new note when the specified button is clicked. // Find the parent container of this node. var container = fnGetContainer(node); if (!container) return; // Highlight the "Add" button. fnAction('add', node); // Create a new area in which the note will reside. var newArea = document.createElement('div'); newArea.className = 'fn-area'; newArea.style.left = (container.offsetWidth/2 - 25) + 'px'; newArea.style.top = (container.offsetHeight/2 - 25) + 'px'; newArea.style.width = '50px'; newArea.style.height = '50px'; newArea.id = 'fn-area-new'; var newNote = document.createElement('div'); newNote.className = 'fn-note'; newArea.appendChild(newNote); // Create note elements. var newTitle = document.createElement('span'); newTitle.className = 'fn-note-title'; newNote.appendChild(newTitle); var newContent = document.createElement('span'); newContent.className = 'fn-note-content'; newNote.appendChild(newContent); var newAuthor = document.createElement('span'); newAuthor.className = 'fn-note-author'; newNote.appendChild(newAuthor); var newUserid = document.createElement('span'); newUserid.className = 'fn-note-userid'; newNote.appendChild(newUserid); var newID = document.createElement('span'); newID.className = 'fn-note-id'; newID.title = ''; newArea.appendChild(newID); // add in innerborders var newInnerBorder = document.createElement('div'); newInnerBorder.className = 'fn-area-innerborder-right'; newArea.appendChild(newInnerBorder); var newInnerBorder = document.createElement('div'); newInnerBorder.className = 'fn-area-innerborder-left'; newArea.appendChild(newInnerBorder); var newInnerBorder = document.createElement('div'); newInnerBorder.className = 'fn-area-innerborder-top'; newArea.appendChild(newInnerBorder); var newInnerBorder = document.createElement('div'); newInnerBorder.className = 'fn-area-innerborder-bottom'; newArea.appendChild(newInnerBorder); // Add newArea to document container.appendChild(newArea); // Record this note as editing, and set the "add" action flag. fnEditingData = { area: newArea, note: newNote }; // Hand over to the editing function. fnEditNote(); }; function fnEditNote(note) { // Edits a passed note reference. var area = null; if (note) { // If we're editing an existing note, setup the data store. area = note.parentNode; fnEditingData = { area: area, note: note }; } else { // New notes: pull the note and area out of the stored data. area = fnEditingData.area; note = fnEditingData.note; } // Find our container and form references. var container = fnGetContainer(area); if (!container) return; var form = container.getElementsByTagName('form'); if (!form) return; form = form.item(0); // Pick up existing values for content from the note. var oldTitle = '', oldAuthor = '', oldContent = '', noteID = ''; var fields = area.getElementsByTagName('span'); for (var n = 0; n < fields.length; n++) { var field = fields.item(n); if (field.className == 'fn-note-id') noteID = field.getAttribute('title'); if (field.className == 'fn-note-title') oldTitle = field.innerHTML; if (field.className == 'fn-note-author') oldAuthor = field.innerHTML; if (field.className == 'fn-note-content') oldContent = field.innerHTML; } // Backup the original content, refs and position in our datastore. // It already has the .note and .area properties. // And yes, I know innerHTML isn't standard, but it's SO MUCH EASIER here! fnEditingData.container = container; fnEditingData.form = form; fnEditingData.noteID = noteID; fnEditingData.oldTitle = oldTitle; fnEditingData.oldAuthor = oldAuthor; fnEditingData.oldContent = oldContent; fnEditingData.oldLeft = parseInt(area.style.left); fnEditingData.oldTop = parseInt(area.style.top); fnEditingData.oldWidth = area.offsetWidth; fnEditingData.oldHeight = area.offsetHeight; // Some values for the post-editing callback handler to populate. fnEditingData.newTitle = fnEditingData.newAuthor = fnEditingData.newContent = ''; fnEditingData.newLeft = fnEditingData.newTop = 0; fnEditingData.newWidth = fnEditingData.newHeight = 0; // Populate the editing UI with its current content. var inputs = form.getElementsByTagName('input'); for (var i = 0; i < inputs.length; i++) { if ((/title/).test(inputs[i].className)) inputs[i].value = oldTitle; if ((/author/).test(inputs[i].className)) inputs[i].value = oldAuthor; } var textarea = form.getElementsByTagName('textarea'); if (textarea && (/content/).test(textarea.item(0).className)) textarea.item(0).value = oldContent; // Finally, show the editing UI for the recorded area. fnEditUISet(true); }; function fnEscapeHTML(html) { // Returns a properly escaped HTML string. return html.replace('&', '&').replace('<', '<').replace('>', '>'); }; function fnEditButtonHandler(ok) { // Button click handler from the editing UI. // Pass a boolean value indicating if the OK button was clicked (so save should proceed). if (!fnEditingData) return; with (fnEditingData) { if (ok) { // Populate fnEditingData.new* from the edit form fields and area attributes. // SET default value for all params. newTitle = newAuthor = newUserid = newEntryid = newContent = newBorderColor = ''; var inputs = form.getElementsByTagName('input'); for (var i = 0; i < inputs.length; i++) { if ((/title/).test(inputs[i].className)) {newTitle = inputs[i].value;} //else {newTitle = '';} if ((/author/).test(inputs[i].className)) {newAuthor = inputs[i].value;} //else {newAuthor = '';} if ((/userid/).test(inputs[i].className)) {newUserid = inputs[i].value;} //else {newUserid = '';} if ((/entry_id/).test(inputs[i].className)) {newEntryid = inputs[i].value;} //else {newEntryid = '';} if ((/border_color/).test(inputs[i].className)) {newBorderColor = inputs[i].value;} //else {newEntryid = '';} } var textarea = form.getElementsByTagName('textarea'); if (textarea && (/content/).test(textarea.item(0).className)) {newContent = textarea.item(0).value}; newLeft = parseInt(area.style.left); newTop = parseInt(area.style.top); newWidth = area.offsetWidth; newHeight = area.offsetHeight; if (fnDebugMode) alert('Begin server save operation ' + 'newBorderColor: ' + newBorderColor); // Get the scalefactor from a hidden SPAN in the container. var sFact = 1; for (var n = 0; n < container.childNodes.length; n++) { if ((/fn-scalefactor/).test(container.childNodes.item(n).className)) sFact = parseFloat(container.childNodes.item(n).getAttribute('title')); } // Begin server save operation. /* Bordercolor UI elements have been removed fn div elements. See fnclient-0.4.0.bordercolor for elements.*/ fnPostXML( '' + '' + (fnActionVerb == 'edit' ? '' + noteID + '' : '') + '' + parseInt(newLeft/sFact) + ',' + parseInt(newTop/sFact) + ',' + parseInt((newLeft+newWidth)/sFact) + ',' + parseInt((newTop+newHeight)/sFact) + '' + '' + fnEscapeHTML(newTitle) + '' + '' + fnEscapeHTML(newAuthor) + '' + fnEscapeHTML(newUserid) + '' + '' + fnEscapeHTML(newContent) + '' + '' + fnEscapeHTML(newEntryid) + '' + '' ); } else { // For "cancel" clicks: if (fnActionVerb == 'add') { // Just delete new notes. area.parentNode.removeChild(area); } else { // Restore original note area position/size for edited notes. area.style.left = oldLeft + 'px'; area.style.top = oldTop + 'px'; area.style.width = oldWidth + 'px'; area.style.height = oldHeight + 'px'; } // Hide the editing UI, reset the control bar, clear the data store. fnEditUISet(false); fnAction('', null); fnEditingData = null; } } }; function fnDelNote(area) { // Deletes a note area -- passed a whole area reference. // Find the ID of this note. var noteID = '', fields = area.getElementsByTagName('span'); for (var n = 0; n < fields.length; n++) if (fields.item(n).className == 'fn-note-id') noteID = fields.item(n).getAttribute('title'); if (!noteID) alert(FN_SAVE_FAIL); if (noteID && confirm(FN_DELETE_CONFIRM)) { // Set up our data store to delete this area, and post to the server. fnEditingData = { area: area, note: null, container: fnGetContainer(area) }; fnPostXML( '' + '' + '' + noteID + '' + '' ); } else { // Reset control bar if cancelled. fnAction('', null); } }; function fnModalDialog(message) { // Shows or hides the browser-wide modal dialog. // Pass a message to show, or an empty string to hide the dialog. var dialog = document.getElementById('fn-modaldialog'); if (!dialog) { dialog = document.createElement('div'); dialog.setAttribute('id', 'fn-modaldialog'); document.body.appendChild(dialog); } /* // Different approach for IE/Windows, since it doesn't support position: fixed. dialog.style.position = (window.ActiveXObject ? 'absolute' : 'fixed'); dialog.style.zIndex = '100000'; dialog.style.top = (window.activeXObject ? document.documentElement.scrollTop+(document.documentElement.clientHeight/2) + 'px' : '0'); dialog.style.left = '0'; dialog.style.width = '100%'; dialog.style.height = (window.ActiveXObject ? document.documentElement.scrollHeight : '100%'); dialog._setupDone = true; */ dialog.innerHTML = '' + message + ''; dialog.style.visibility = message ? 'visible' : 'hidden'; }; function fnPostXML(xml) { // Sends some XML off to the server and calls fnEditComplete on completion. // Hopefully my auto-detect-fu powers are strong. I'll use the Crouching Regex Style. var image = fnEditingData.container.getElementsByTagName('img').item(0); var imageFile = image.getAttribute('src'); if (!imageFile) return alert(FN_SAVE_FAIL); // Figure out if we need to prompt the user for a password. var password = '', password_req = false; switch (fnActionVerb) { case 'add': { if (FN_ADD == 'prompt') password_req = true; break }; case 'edit': { if (FN_MODIFY == 'prompt') password_req = true; break }; case 'del': { if (FN_DELETE == 'prompt') password_req = true; break }; } if (password_req) { // TODO: Secure input here. password = prompt('Please enter your password', ''); } // Compose our post content and send it. var actVerbs = { add: 'add', edit: 'modify', del: 'delete' }; var postContent = 'image=' + escape(imageFile) + '&action=' + actVerbs[fnActionVerb] + (password ? '&password=' + escape(password) : '') + '&xml=' + escape(xml); if (fnDebugMode) alert('SENDING TO FNSERVER:\n\n' + postContent); fnXMLHTTP.open('POST', fnServer, true); fnXMLHTTP.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8'); fnXMLHTTP.setRequestHeader('Content-length', postContent.length); var cookies = document.cookie.split(';'); /* for (var c = 0; c < cookies.length; c++) { fnXMLHTTP.setRequestHeader('Cookie', cookies[c]); } */ fnXMLHTTP.onreadystatechange = function() { if (fnXMLHTTP.readyState == 4) fnEditComplete(true); }; // Show "please wait" modal dialog, which prevents document clicks, and send. fnModalDialog(FN_SAVE_WAIT); fnXMLHTTP.send(postContent); }; function fnEditComplete(ok) { // Called once the server responds post-Save operation. 'ok' indicates success. if (fnDebugMode) alert('RECEIVED FROM FNSERVER:\n\n' + fnXMLHTTP.responseText); if (!ok || !fnXMLHTTP.responseText.match('success=ok')) { // In the case of a communication error, hide the modal dialog and alert the user. fnModalDialog(''); if (fnXMLHTTP.responseText.match('success=501')) { // File is not writable alert(FN_SAVE_FAIL_JPEG_NOT_WRITABLE); } else { // Some other error occurred. alert(FN_SAVE_FAIL); } // Failed deletes: reset the control bar and clear the data store. // (Failed edits/adds: UI and data persist). if (fnActionVerb == 'del') { fnEditingData = null; fnAction('', null); } } else with (fnEditingData) { // Depending on our action, commit the changes to the document. if (fnActionVerb == 'add' || fnActionVerb == 'edit') { // Place new values in the note. It's already in the right position. for (var n = 0; n < note.childNodes.length; n++) { var field = note.childNodes.item(n); if (field.className == 'fn-note-title') field.innerHTML = newTitle; if (field.className == 'fn-note-author') field.innerHTML = newAuthor; if (field.className == 'fn-note-content') field.innerHTML = newContent; } // Hide the editing UI. fnEditUISet(false); } else { // Deleting notes? Just remove the area from the document. area.parentNode.removeChild(area); } // All successful actions: let the user know it's OK, and reset the control bar, // and clear the editing data store. fnModalDialog(FN_SAVE_SUCCESS); setTimeout('fnModalDialog("")', 500); fnAction('', null); fnEditingData = null; } // Reload the page - Added temporarily // window.location.reload(); }; // INITIALISATION CODE: if (document.getElementById) { // Create a new DragResize() object, and set it up. // We apply to the whole document to interoperate with blinds. var dragresize = new DragResize('dragresize', { allowBlur: false }); dragresize.isElement = function(elm) { if (!(/(add|edit)/).test(fnActionVerb)) return false; if ((/fn-area-editing/).test(elm.className)) { var container = fnGetContainer(elm); this.maxRight = container.offsetWidth - 2; this.maxBottom = container.offsetHeight - 2; return true; } }; dragresize.isHandle = function(elm) { if (!(/(add|edit)/).test(fnActionVerb)) return false; if ((/fn-area-editing/).test(elm.className)) return true; }; dragresize.ondragfocus = function() { this.element.style.cursor = 'move'; }; dragresize.ondragblur = function() { this.element.style.cursor = 'default'; }; dragresize.apply(document); // *** Global event handler setup *** // These are global, rather than assigned to individual notes, to work with the "blind" code. // Note show/hide events. addEvent(document, 'mouseover', new Function('e', 'fnMouseOverOutHandler(e, 1)')); addEvent(document, 'mouseout', new Function('e', 'fnMouseOverOutHandler(e, 0)')); // Creation/editing/deletion events. if (document.createElement && document.documentElement) { //addEvent(document, 'mousedown', fnMouseDownHandler); //addEvent(document, 'mouseup', fnMouseUpHandler); addEvent(document, 'click', fnClickHandler); } }