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('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;');
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