// 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);
}
}