source: main/trunk/greenstone3/web/interfaces/default/js/visual-xml-editor.js@ 27130

Last change on this file since 27130 was 27130, checked in by sjm84, 11 years ago

Some code tidying and well as adding more documentation

File size: 38.5 KB
Line 
1// ********************************************************************** //
2// Visual XML Editor //
3// This class represents an editor that allows you to modify XML visually //
4// ********************************************************************** //
5function visualXMLEditor(xmlString)
6{
7 //Variables that store the visual editor and a link to the DebugWidget
8 var _thisEditor = this;
9 var _greenbug;
10
11 //Stores what id we are up to (used to compare VEElements)
12 var _globalID = 0;
13
14 //Stores the current state of the XML
15 var _xml;
16
17 //Elements of the editor
18 var _mainDiv = $("<div>", {"id":"veMainDiv"});
19 var _toolboxDiv = $("<div>", {"id":"veToolboxDiv"});
20 var _editorContainer = $("<div>", {"id":"veEditorContainer"});
21 var _editorDiv = $("<div>", {"id":"veEditorDiv"});
22 var _infoDiv = $("<div>", {"id":"veInfoDiv"});
23
24 //State-keeping variables
25 var _rootElement;
26 var _selectedElement;
27 var _overTrash = false;
28 var _validDropSpot = false;
29 var _validDropType;
30 var _validDropElem;
31 var _origDDParent;
32 var _origDDPosition;
33
34 //Keep track of what is currently being edited
35 var _editingNodes = new Array();
36
37 //Stores what elements we are currently over while dragging (necessary to find the deepest element)
38 var _overList = new Array();
39 _overList.freeSpaces = new Array();
40
41 //Keep a list of what has been changed so that it can be undone
42 var _transactions = new Array();
43
44 //A list of "ready-made" attributes for certain elements
45 var _validAttrList =
46 {
47 gsf:
48 {
49 "cgi-param":["name"],
50 "collectionText":["args", "name"],
51 "displayItem":["name"],
52 "displayText":["name"],
53 "equivlinkgs3":["name"],
54 "foreach-metadata":["name", "separator"],
55 "icon":["type"],
56 "if-metadata-exists":["name"],
57 "image":["type"],
58 "interfaceText":["name"],
59 "link":["OID", "OIDmetadata", "name", "nodeID", "target", "title", "titlekey", "type"],
60 "metadata":["format", "hidden", "name", "pos", "prefix", "separator", "suffix", "type"],
61 "script":["src"],
62 "style":["src"],
63 "switch":["preprocess", "test", "test-value"]
64 }
65 }
66
67 //The list of elements that show up in the toolbox (gslib is added dynamically later)
68 var _elemList =
69 {
70 html:["a", "br", "div", "li", "link", "p", "script", "span", "table", "td", "tr", "ul"],
71 xsl:
72 [
73 "apply-imports", "apply-templates", "attribute", "attribute-set", "call-template",
74 "choose", "copy", "copy-of", "decimal-format", "element",
75 "fallback", "for-each", "if", "import", "include",
76 "key", "message", "namespace-alias", "number", "otherwise",
77 "output", "param", "preserve-space", "processing-instruction", "sort",
78 "strip-space", "stylesheet", "template", "text", "transform",
79 "value-of", "variable", "when", "with-param"
80 ],
81 gsf:
82 [
83 "cgi-param", "choose-metadata", "collectionText", "displayItem", "displayText",
84 "equivlinkgs3", "foreach-metadata", "icon", "if-metadata-exists", "image",
85 "interfaceText", "link", "meta-value", "metadata", "script",
86 "style", "switch", "template", "text", "variable"
87 ]
88 };
89
90 //Restricts what elements can be added to a given element
91 var _childRestrictions =
92 {
93 gsf:
94 {
95 "choose-metadata":["gsf:metadata", "gsf:default"],
96 "metadata":[]
97 }
98 };
99
100 //Get a connection to the DebugWidget
101 this.setGreenbug = function(gb)
102 {
103 _greenbug = gb;
104 }
105
106 //Get the XML in its current state
107 this.getXML = function()
108 {
109 return _xml;
110 }
111
112 //Undo a transation
113 this.undo = function()
114 {
115 if(_transactions.length > 0)
116 {
117 var t = _transactions.pop();
118 //Undo an added element
119 if(t.type == "addElem")
120 {
121 $(t.vElem.data("parentVEElement").getXMLNode()).remove();
122 t.vElem.remove();
123 resizeAll();
124 }
125 //Undo a removed or moved element
126 else if(t.type == "remMvElem")
127 {
128 var parent = t.vElemParent;
129 var pos = t.vElemPos;
130 var elem = t.vElem;
131
132 elem.detach();
133 if(pos == 0)
134 {
135 parent.prepend(elem);
136 $(parent.parent().data("parentVEElement").getXMLNode()).prepend(elem.data("parentVEElement").getXMLNode());
137 }
138 else if(pos == parent.children(".veElement").length)
139 {
140 $(parent.children(".veElement").eq(pos - 1).data("parentVEElement").getXMLNode()).after(elem.data("parentVEElement").getXMLNode());
141 parent.children(".veElement").eq(pos - 1).after(elem);
142 }
143 else
144 {
145 $(parent.children(".veElement").eq(pos).data("parentVEElement").getXMLNode()).before(elem.data("parentVEElement").getXMLNode());
146 parent.children(".veElement").eq(pos).before(elem);
147 }
148 resizeAll();
149
150 //Check if we need to change the recycle bin icon
151 var found = false;
152 for(var i = 0; i < _transactions.length; i++)
153 {
154 if(_transactions[i].type == "remMvElem"){found = true; break;}
155 }
156
157 if(!found)
158 {
159 $("#veTrash").children("img").attr("src", gs.imageURLs.trashEmpty);
160 }
161 }
162 //Undo an added attribute
163 else if(t.type == "addAttr")
164 {
165 if(t.row)
166 {
167 t.row.remove();
168 }
169 }
170 //Undo a removed or edited attribute
171 else if(t.type == "editAttr")
172 {
173 t.elem.removeAttribute(t.newName);
174 t.elem.setAttribute(t.name, t.value);
175 if(t.row)
176 {
177 t.row.children("td").eq(0).text(t.name);
178 t.row.children("td").eq(1).text(t.value);
179 }
180 }
181 //Undo a removed or edited attribute
182 else if(t.type == "remAttr")
183 {
184 t.elem.setAttribute(t.name, t.value);
185 if(t.rowParent)
186 {
187 t.rowParent.append(t.row);
188 }
189 }
190 //Undo edited text
191 else if(t.type == "editText")
192 {
193 t.elem.nodeValue = t.value;
194 if(t.vElem)
195 {
196 t.vElem.text(t.value);
197 }
198 }
199 }
200 }
201
202 //Check if an element is allowed as a child to another element
203 var checkRestricted = function(child, parent)
204 {
205 var pFullNodename = parent.tagName;
206 var cFullNodename = child.tagName;
207 var pNamespace;
208 var pNodeName;
209 if(pFullNodename.indexOf(":") == -1)
210 {
211 pNamespace = "no namespace";
212 pNodeName = pFullNodename;
213 }
214 else
215 {
216 pNamespace = pFullNodename.substring(0, pFullNodename.indexOf(":"));
217 pNodeName = pFullNodename.substring(pFullNodename.indexOf(":") + 1);
218 }
219
220 var namespaceList = _childRestrictions[pNamespace];
221 if(namespaceList)
222 {
223 var childList = namespaceList[pNodeName];
224 if(childList)
225 {
226 for(var i = 0; i < childList.length; i++)
227 {
228 if(childList[i] == cFullNodename)
229 {
230 return true;
231 }
232 }
233 return false;
234 }
235 }
236
237 return true;
238 }
239
240 //Add the trash bin to the editor
241 var placeTrashBin = function()
242 {
243 var binImage = $("<img src=\"" + gs.imageURLs.trashEmpty + "\"/>");
244 var bin = $("<div id=\"veTrash\">");
245 bin.append(binImage);
246 bin.addClass("ui-state-default");
247 bin.addClass("ui-corner-all");
248 bin.droppable(
249 {
250 "over":function()
251 {
252 _overTrash = true;
253 },
254 "out":function()
255 {
256 _overTrash = false;
257 }
258 });
259 _editorContainer.append(bin);
260 }
261
262 //Dynamically retrieve the gslib elements from the gslib.xsl file to put into the toolbox
263 var retrieveGSLIBTemplates = function(callback)
264 {
265 var url = gs.xsltParams.library_name + "?a=g&rt=r&s=GetTemplateListFromFile&s1.locationName=interface&s1.interfaceName=" + gs.xsltParams.interface_name + "&s1.fileName=gslib.xsl";
266
267 $.ajax(url)
268 .success(function(response)
269 {
270 startIndex = response.search("<templateList>") + "<templateList>".length;
271 endIndex = response.search("</templateList>");
272
273 var listString = response.substring(startIndex, endIndex);
274 var list = eval(listString.replace(/&quot;/g, "\""));
275 var modifiedList = new Array();
276
277 for(var i = 0; i < list.length; i++)
278 {
279 var current = list[i];
280 if(current.name)
281 {
282 modifiedList.push(current.name);
283 }
284 }
285
286 _elemList["gslib"] = modifiedList;
287
288 if(callback)
289 {
290 callback();
291 }
292 })
293 .error(function()
294 {
295 console.log("Error retrieving GSLIB templates");
296 });
297 }
298
299 //Create the toolbar
300 var populateToolbar = function()
301 {
302 var tabHolder = $("<ul>");
303 _toolboxDiv.append(tabHolder);
304
305 for(var key in _elemList)
306 {
307 var currentList = _elemList[key];
308
309 var tab = $("<li>");
310 var tabLink = $("<a>", {"href":"#ve" + key});
311 tabLink.css({"font-size":"0.9em", "padding":"5px"});
312 tabLink.text(key);
313 tab.append(tabLink);
314 tabHolder.append(tab);
315
316 var tabDiv = $("<div>", {"id":"ve" + key});
317 for(var j = 0; j < currentList.length; j++)
318 {
319 var elemName = currentList[j];
320
321 var ns = (key == "html") ? "" : (key + ":");
322 var newElem = _xml.createElement(ns + elemName);
323 var veElement = new VEElement(newElem);
324 veElement.setShortName(true);
325 var veDiv = veElement.getDiv();
326 veDiv.css("float", "none");
327 veDiv.data("toolbar", true);
328 tabDiv.append(veDiv);
329 }
330
331 _toolboxDiv.append(tabDiv);
332 }
333
334 var otherTab = $("<li>");
335 var otherTabLink = $("<a>", {"href":"#veother"});
336 otherTabLink.css({"font-size":"0.9em", "padding":"5px"});
337 otherTabLink.text("other");
338 otherTab.append(otherTabLink);
339 tabHolder.append(otherTab);
340
341 var otherTabDiv = $("<div>", {"id":"veother"});
342 var textNode = _xml.createTextNode("text");
343 var textVEElement = new VEElement(textNode);
344 var textDiv = textVEElement.getDiv();
345 textDiv.css("float", "none");
346 textDiv.data("toolbar", true);
347 otherTabDiv.append(textDiv);
348
349 var customInput = $("<input type=\"text\">");
350 var customElemHolder = $("<div>");
351 var customCreateButton = $("<button>Create element</button>");
352 customCreateButton.click(function()
353 {
354 var elemName = customInput.val();
355 if(elemName.length)
356 {
357 var elem = _xml.createElement(elemName);
358 var veElement = new VEElement(elem);
359 var customElemDiv = veElement.getDiv();
360 customElemDiv.css("float", "none");
361 customElemDiv.data("toolbar", true);
362 customElemHolder.empty();
363 customElemHolder.append(customElemDiv);
364 }
365 });
366 otherTabDiv.append(customInput);
367 otherTabDiv.append(customCreateButton);
368 otherTabDiv.append(customElemHolder);
369
370 _toolboxDiv.append(otherTabDiv);
371
372 _toolboxDiv.tabs();
373
374 customCreateButton.button();
375 }
376
377 //Turns the given XML into the nice visual structure recursively
378 var constructDivsRecursive = function(currentDiv, currentParent, level)
379 {
380 if(!level)
381 {
382 level = 1;
383 }
384
385 var container = $("<div>");
386 container.addClass("veContainerElement");
387 currentDiv.append(container);
388
389 var allowedList = new Array();
390 var counter = currentParent.firstChild;
391 while(counter)
392 {
393 if(counter.nodeType == 1)
394 {
395 allowedList.push(counter)
396 }
397 else if(counter.nodeType == 3 && counter.nodeValue.search(/\S/) != -1)
398 {
399 allowedList.push(counter)
400 }
401 counter = counter.nextSibling;
402 }
403
404 var width = 100 / allowedList.length;
405 for(var i = 0; i < allowedList.length; i++)
406 {
407 var currentElement = allowedList[i];
408
409 var veElement = new VEElement(currentElement);
410 var elementDiv = veElement.getDiv();
411 veElement.setWidth(width);
412
413 if(!_rootElement)
414 {
415 _rootElement = elementDiv;
416 }
417
418 container.append(elementDiv);
419 if(currentElement.firstChild)
420 {
421 constructDivsRecursive(elementDiv, currentElement, level + 1);
422 }
423
424 currentElement = currentElement.nextSibling;
425 }
426
427 container.append($("<div>", {"style":"clear:both;"}));
428 }
429
430 //Fake a click on the root element
431 this.selectRootElement = function()
432 {
433 var height = _editorDiv.height() + 10;
434 if(height < 300){height = 300;}
435
436 _editorContainer.css("height", height + "px");
437 _infoDiv.css("height", height + "px");
438 _rootElement.trigger("click");
439 }
440
441 //Return the main visual editor div
442 this.getMainDiv = function()
443 {
444 return _mainDiv;
445 }
446
447 //Save any unfinished edits
448 this.savePendingEdits = function()
449 {
450 while(_editingNodes && _editingNodes.length > 0)
451 {
452 var attr = _editingNodes.pop();
453 attr.saveEdits();
454 }
455 }
456
457 //Add the given VEElement to the list of VEElements we are currently dragging over
458 var addToOverList = function(veElement)
459 {
460 if(veElement.getDiv().data("toolbar"))
461 {
462 return;
463 }
464
465 for(var i = 0; i < _overList.length; i++)
466 {
467 if(!_overList[i])
468 {
469 continue;
470 }
471
472 if(_overList[i].getID() == veElement.getID())
473 {
474 return false;
475 }
476 }
477
478 if(_overList.freeSpaces.length > 0)
479 {
480 _overList[_overList.freeSpaces.pop()] = veElement;
481 }
482 else
483 {
484 _overList.push(veElement);
485 }
486 }
487
488 //Remove the given VEElement from the list of VElements we are currently dragging over
489 var removeFromOverList = function(veElement)
490 {
491 for(var i = 0; i < _overList.length; i++)
492 {
493 if(!_overList[i])
494 {
495 continue;
496 }
497
498 if(_overList[i].getID() == veElement.getID())
499 {
500 delete _overList[i];
501 _overList.freeSpaces.push(i);
502 }
503 }
504 }
505
506 //Get the deepest VEElement we are currently dragging over
507 var getDeepestOverElement = function()
508 {
509 if(_overList.length == 0)
510 {
511 return null;
512 }
513
514 var deepestVal = 0;
515 var deepestElem = _overList[0];
516
517 for(var i = 0; i < _overList.length; i++)
518 {
519 if(!_overList[i])
520 {
521 continue;
522 }
523
524 var depth = _overList[i].getDiv().parents(".veElement").length;
525 depth = (depth) ? depth : 0;
526
527 if (depth > deepestVal)
528 {
529 deepestVal = depth;
530 deepestElem = _overList[i];
531 }
532 }
533
534 return deepestElem;
535 }
536
537 //Resize all the VEElements
538 var resizeAll = function()
539 {
540 var filterFunction = function()
541 {
542 var toolbarStatus = $(this).data("toolbar");
543 var beingDraggedStatus = $(this).data("dragging");
544
545 if(beingDraggedStatus || !toolbarStatus)
546 {
547 return true;
548 }
549
550 return false;
551 }
552
553 var allElems = $(".veElement").filter(filterFunction).each(function()
554 {
555 if($(this).data("helper")){return;}
556
557 var size = $(this).data("expanded");
558 if(size == "small")
559 {
560 var width = (20 / ($(this).siblings(".veElement").filter(function(){return !($(this).data("helper"))}).length - 1));
561 $(this).css("width", width + "%");
562 }
563 else if(size == "normal")
564 {
565 var width = (100 / ($(this).siblings(".veElement").filter(function(){return !($(this).data("helper"))}).length + 1));
566 $(this).css("width", width + "%");
567 }
568 else if(size == "expanded")
569 {
570 $(this).css("width", "80%");
571 }
572 });
573 }
574
575 //Initialise the visual editor
576 var initVXE = function()
577 {
578 try
579 {
580 _xml = $.parseXML('<testContainer xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:java="http://xml.apache.org/xslt/java" xmlns:util="xalan://org.greenstone.gsdl3.util.XSLTUtil" xmlns:gslib="http://www.greenstone.org/skinning" xmlns:gsf="http://www.greenstone.org/greenstone3/schema/ConfigFormat">' + xmlString + "</testContainer>");
581 constructDivsRecursive(_editorDiv, _xml.firstChild);
582 }
583 catch(error)
584 {
585 console.log(error);
586 return null;
587 }
588
589 retrieveGSLIBTemplates(function(){populateToolbar();});
590 placeTrashBin();
591
592 _editorContainer.append(_editorDiv);
593 _mainDiv.append(_toolboxDiv);
594 _mainDiv.append(_editorContainer);
595 _mainDiv.append($("<div>", {"id":"veSpacerDiv"}));
596 _mainDiv.append(_infoDiv);
597 _mainDiv.append($("<div>", {"style":"clear:both;"}));
598 }
599
600 // *********************************************************************** //
601 // Visual Editor Text //
602 // This inner class represents a single xml text node in the visual editor //
603 // *********************************************************************** //
604 var VEText = function(node)
605 {
606 //Constructor code
607 var _thisNode = this;
608 var _xmlNode = node;
609
610 var _textEditor = $("<div>");
611 var _nodeText = $("<div>");
612 _nodeText.text(_xmlNode.nodeValue);
613
614 _textEditor.append(_nodeText);
615
616 var _editButton = $("<button>Edit text</button>");
617 _editButton.click(function()
618 {
619 if(_editButton.button("option", "label") == "Edit text")
620 {
621 _thisNode.editMode();
622 }
623 else
624 {
625 _thisNode.saveEdits();
626 }
627 });
628 _textEditor.append(_editButton);
629
630 //Enable editing of this text node
631 this.editMode = function()
632 {
633 _editingNodes.push(_thisNode);
634 _nodeText.data("prevTextValue", _nodeText.text());
635 var textArea = $("<textarea>");
636 textArea.val(_nodeText.text());
637 _nodeText.text("");
638 _nodeText.append(textArea);
639 _editButton.button("option", "label", "Done");
640 }
641
642 //Save edits to this text node
643 this.saveEdits = function()
644 {
645 for(var i = 0; i < _editingNodes.length; i++)
646 {
647 if(_editingNodes[i] == _thisNode)
648 {
649 _editingNodes.splice(i, 1);
650 break;
651 }
652 }
653
654 _transactions.push({type:"editText", elem:_xmlNode, vElem: _nodeText, value:_nodeText.data("prevTextValue")});
655 var textArea = _nodeText.find("textarea");
656 var newValue = textArea.val();
657 _xmlNode.nodeValue = newValue;
658 _nodeText.empty();
659 _nodeText.text(newValue);
660 _editButton.button("option", "label", "Edit text");
661 }
662
663 //Create a text node editor
664 this.getDiv = function()
665 {
666 //A hack to make sure the edit text button is styled correctly
667 setTimeout(function(){_editButton.button()}, 1);
668 return _textEditor;
669 }
670 }
671
672 // *********************************************************************** //
673 // Visual Editor Attribute //
674 // This inner class represents a single xml attribute in the visual editor //
675 // *********************************************************************** //
676 var VEAttribute = function(attrElem, xmlElem, name, value)
677 {
678 //Constructor code
679 var _name;
680 if(name)
681 {
682 _name = name;
683 }
684 else if(attrElem && attrElem.name)
685 {
686 _name = attrElem.name;
687 }
688
689 var _value;
690 if(value)
691 {
692 _value = value;
693 }
694 else if(attrElem && attrElem.value)
695 {
696 _value = attrElem.value;
697 }
698 var _xmlElem = xmlElem;
699 var _row;
700
701 var _thisAttr = this;
702
703 //Get the attribute name
704 this.getName = function()
705 {
706 return _name;
707 }
708
709 //Get the attribute value
710 this.getValue = function()
711 {
712 return _value;
713 }
714
715 //Get the name cell of the attribute table
716 var createNameCell = function()
717 {
718 var cell = $("<td>", {"class":"veNameCell"});
719 cell.text(_name);
720 return cell;
721 }
722
723 //Get the value cell of the attribute table
724 var createValueCell = function()
725 {
726 var cell = $("<td>", {"class":"veValueCell"});
727 cell.text(_value);
728 return cell;
729 }
730
731 //Get the edit cell of the attribute table
732 var createEditCell = function()
733 {
734 var cell = $("<td>", {"class":"veEditCell"});
735 var link = $("<a href=\"javascript:;\">edit</a>");
736
737 link.click(function()
738 {
739 _thisAttr.editMode();
740 });
741 cell.append(link);
742 return cell;
743 }
744
745 //Get the delete cell of the attribute table
746 var createDeleteCell = function()
747 {
748 var cell = $("<td>", {"class":"veDeleteCell"});
749 var link = $("<a href=\"javascript:;\">delete</a>");
750 link.click(function()
751 {
752 _transactions.push({type:"remAttr", elem:_xmlElem, row:_row, rowParent:_row.parent(), name:_name, value:_value});
753 _xmlElem.removeAttribute(_name);
754 _row.detach();
755 });
756 cell.append(link);
757 return cell;
758 }
759
760 //Create a table row from this attribute
761 this.getAsTableRow = function()
762 {
763 var tableRow = $("<tr>");
764
765 var attributeName = createNameCell();
766 tableRow.append(attributeName);
767
768 var attributeValue = createValueCell();
769 tableRow.append(attributeValue);
770
771 var editCell = createEditCell()
772 tableRow.append(editCell);
773
774 var deleteCell = createDeleteCell();
775 tableRow.append(deleteCell);
776
777 _row = tableRow;
778
779 return tableRow;
780 }
781
782 //Enable editing of this attribute
783 this.editMode = function(editValue)
784 {
785 _editingNodes.push(_thisAttr);
786
787 var nameCell = _row.children("td").eq(0);
788 var valueCell = _row.children("td").eq(1);
789 var editLink = _row.children("td").eq(2).find("a");
790
791 editLink.text("done");
792 editLink.off("click");
793 editLink.click(function()
794 {
795 _thisAttr.saveEdits();
796 });
797
798 var nameInput = $("<input type=\"text\">");
799 nameInput.width(nameCell.width() - 5);
800 nameInput.val(_name);
801
802 var valueInput = $("<input type=\"text\">");
803 valueInput.width(valueCell.width() - 5);
804 valueInput.val(_value);
805
806 nameCell.text("");
807 valueCell.text("");
808
809 nameCell.append(nameInput);
810 valueCell.append(valueInput);
811
812 if(editValue)
813 {
814 valueInput.focus();
815 }
816 else
817 {
818 nameInput.focus();
819 }
820 }
821
822 //Save edits to this attribute
823 this.saveEdits = function()
824 {
825 for(var i = 0; i < _editingNodes.length; i++)
826 {
827 if(_editingNodes[i] == _thisAttr)
828 {
829 _editingNodes.splice(i, 1);
830 break;
831 }
832 }
833
834 var nameCell = _row.children("td").eq(0);
835 var valueCell = _row.children("td").eq(1);
836 var editLink = _row.children("td").eq(2).find("a");
837
838 editLink.text("edit");
839 editLink.off("click");
840 editLink.click(function()
841 {
842 _thisAttr.editMode();
843 });
844
845 var nameInput = nameCell.children("input");
846 var valueInput = valueCell.children("input");
847
848 nameInput.blur();
849 valueInput.blur();
850
851 var name = nameInput.val();
852 var value = valueInput.val();
853
854 if(name.length == 0 || name.search(/\w/g) == -1)
855 {
856 _row.remove();
857 return;
858 }
859
860 nameCell.empty();
861 nameCell.text(name);
862
863 valueCell.empty();
864 valueCell.text(value);
865
866 if(nameCell.data("prevName") != "")
867 {
868 _xmlElem.removeAttribute(_name);
869 }
870 _xmlElem.setAttribute(name, value);
871
872 _transactions.push({type:"editAttr", elem:_xmlElem, row:_row, newName:name, name:_name, value:_value});
873
874 _name = name;
875 _value = value;
876 }
877 }
878
879 // ********************************************************************************** //
880 // Visual Editor Element //
881 // This inner class represents a single xml element or text node in the visual editor //
882 // ********************************************************************************** //
883 var VEElement = function(xml)
884 {
885 var _div = $("<div>");
886 var _xmlNode = xml;
887 var _id = _globalID++;
888
889 _div.data("parentVEElement", this);
890 _div.data("expanded", "normal");
891
892 //Add the necessary functions to make this VEElement draggable
893 var makeDraggable = function()
894 {
895 _div.draggable(
896 {
897 "distance":"20",
898 "revert":"true",
899 "helper":function()
900 {
901 //Make sure the cursor is put in the centre of the div
902 var height = _div.children(".veTitleElement").height();
903 _div.draggable("option", "cursorAt", {top:(height / 2), left:(_div.width() / 2)});
904
905 var tempVEE = new VEElement(_xmlNode);
906 var tempDiv = tempVEE.getDiv();
907 tempDiv.css("border", "1px solid orangered");
908 tempDiv.css("background", "orange");
909 tempDiv.width(_div.width());
910 tempDiv.data("helper", true);
911 return tempDiv;
912 },
913 "cursor":"move",
914 "appendTo":_mainDiv,
915 "start":function(event, ui)
916 {
917 _overTrash = false;
918 _origDDParent = _div.parent();
919 _origDDPosition = _div.index();
920
921 _div.siblings(".veElement").filter(function(){return !($(this).data("helper"))}).data("expanded", "normal");
922 _div.css("border", "1px solid orangered");
923 _div.data("prevBackground", _div.css("background"));
924 _div.css("background", "orange");
925 _div.css("float", "left");
926
927 _div.data("dragging", true);
928 if(_div.data("toolbar"))
929 {
930 var cloneElem = new VEElement(_xmlNode.cloneNode(true));
931 cloneElem.setShortName(true);
932 var cloneDiv = cloneElem.getDiv();
933 cloneDiv.css("float", "none");
934 cloneDiv.data("toolbar", true);
935 _div.before(cloneDiv);
936 }
937 _div.detach();
938
939 resizeAll();
940 },
941 "drag":function(event, ui)
942 {
943 var foundDefined = false;
944 for(var i = 0; i < _overList.length; i++)
945 {
946 if(!(_overList[i] === "undefined"))
947 {
948 foundDefined = true;
949 }
950 }
951
952 _validDropSpot = false;
953 if(foundDefined)
954 {
955 var overElement = getDeepestOverElement();
956 if(overElement && overElement.getXMLNode().nodeType != 3 && checkRestricted(_xmlNode, overElement.getXMLNode()))
957 {
958 _validDropSpot = true;
959 var overDiv = overElement.getDiv();
960
961 var overLeft = overDiv.offset().left;
962 var helperLeft = ui.helper.offset().left;
963 var helperMiddle = helperLeft + (ui.helper.width() / 2);
964
965 var overContainers = overDiv.children(".veContainerElement");
966 if(!overContainers.length)
967 {
968 overDiv.append($("<div>", {"class":"veContainerElement"}));
969 overContainers = overDiv.children(".veContainerElement");
970 }
971 var overChildren = overContainers.children(".veElement").filter(function(){return !($(this).data("helper")) && !(_div.data("parentVEElement").getID() == $(this).data("parentVEElement").getID())});
972 var overChildrenLength = overChildren.length + 1;
973
974 if(!overChildren.length)
975 {
976 _validDropElem = overDiv;
977 _validDropType = "into";
978 overContainers.append(_div);
979 }
980 else
981 {
982 var posPercent = (helperMiddle - overLeft) / overDiv.width();
983 if(posPercent < 0)
984 {
985 posPercent = 0;
986 }
987 else if(posPercent > 1)
988 {
989 posPercent = 1;
990 }
991 var pos = Math.floor(overChildrenLength * posPercent);
992
993 if(pos < overChildrenLength - 1)
994 {
995 _validDropElem = overChildren.eq(pos);
996 _validDropType = "before";
997 overChildren.eq(pos).before(_div);
998 }
999 else
1000 {
1001 _validDropElem = overChildren.eq(pos - 1);
1002 //Necessary to fix a rare bug that causes pos to be off by one
1003 if(!_validDropElem.length)
1004 {
1005 _validDropElem = overChildren.eq(pos - 2);
1006 }
1007 _validDropType = "after";
1008 overChildren.eq(pos - 1).after(_div);
1009 }
1010 }
1011
1012 overChildren.data("expanded", "normal");
1013 _div.data("expanded", "normal");
1014
1015 resizeAll();
1016 }
1017 }
1018 },
1019 "stop":function(event)
1020 {
1021 var transactionType = (_div.data("toolbar")) ? "addElem" : "remMvElem";
1022
1023 if(_div.data("toolbar"))
1024 {
1025 _div.data("parentVEElement").setShortName(false);
1026 }
1027
1028 _div.css("border", "1px solid black");
1029 _div.css("background", _div.data("prevBackground"));
1030
1031 //If the element was not dropped in a valid place then put it back
1032 if(!_validDropSpot && !_div.data("toolbar"))
1033 {
1034 _div.detach();
1035 if(_origDDPosition == 0)
1036 {
1037 _origDDParent.prepend(_div);
1038 }
1039 else if(_origDDPosition == _origDDParent.children(".veElement").length)
1040 {
1041 _origDDParent.children(".veElement").eq(_origDDPosition - 1).after(_div);
1042 }
1043 else
1044 {
1045 _origDDParent.children(".veElement").eq(_origDDPosition).before(_div);
1046 }
1047
1048 if(_overTrash)
1049 {
1050 _div.data("parentVEElement").remove();
1051 return;
1052 }
1053
1054 resizeAll();
1055 }
1056 //Otherwise modify the XML
1057 else
1058 {
1059 var xmlNode = _validDropElem.data("parentVEElement").getXMLNode();
1060 if(_validDropType == "before")
1061 {
1062 $(xmlNode).before(_xmlNode);
1063 }
1064 else if (_validDropType == "after")
1065 {
1066 $(xmlNode).after(_xmlNode);
1067 }
1068 else if (_validDropType == "into")
1069 {
1070 $(xmlNode).append(_xmlNode);
1071 }
1072 _transactions.push({type:transactionType, vElemParent:_origDDParent, vElemPos:_origDDPosition, vElem:_div});
1073 }
1074
1075 _div.data("dragging", false);
1076 _div.data("toolbar", false);
1077
1078 _overList = new Array();
1079 _overList.freeSpaces = new Array();
1080 }
1081 });
1082
1083 //Also make this element a drop-zone for other elements
1084 _div.droppable(
1085 {
1086 "over":function(event, ui)
1087 {
1088 addToOverList($(this).data("parentVEElement"));
1089 event.stopPropagation();
1090 },
1091 "out":function(event)
1092 {
1093 removeFromOverList($(this).data("parentVEElement"));
1094 event.stopPropagation();
1095 }
1096 });
1097 }
1098
1099 //Get the underlying div
1100 this.getDiv = function()
1101 {
1102 return _div;
1103 }
1104
1105 //Get the XML for this element
1106 this.getXMLNode = function()
1107 {
1108 return _xmlNode;
1109 }
1110
1111 //Get the unique ID of this VEElement
1112 this.getID = function()
1113 {
1114 return _id;
1115 }
1116
1117 //Fill the information area with details about this element
1118 this.populateInformationDiv = function()
1119 {
1120 _thisEditor.savePendingEdits();
1121 _infoDiv.empty();
1122
1123 if(_xmlNode.nodeType == 1)
1124 {
1125 var nameElementTitle = $("<div>", {"class":"ui-state-default ui-corner-all veInfoDivTitle"}).text("Element name:");
1126 _infoDiv.append(nameElementTitle);
1127 _infoDiv.append($("<p>").text(_xmlNode.nodeName));
1128 }
1129 else
1130 {
1131 var textElementTitle = $("<div>", {"class":"ui-state-default ui-corner-all veInfoDivTitle"}).text("Text node:");
1132 _infoDiv.append(textElementTitle);
1133 }
1134
1135 if(_xmlNode.nodeType == 1)
1136 {
1137 var attributeTableTitle = $("<div>", {"class":"ui-state-default ui-corner-all veInfoDivTitle"});
1138 attributeTableTitle.text("Attributes:");
1139 var attributeTable = $("<table>");
1140 attributeTable.addClass("veAttributeTableContainer");
1141
1142 attributeTable.append($("<tr>").html("<td class=\"veNameCell\">Name</td><td class=\"veValueCell\">Value</td>"));
1143
1144 $(_xmlNode.attributes).each(function()
1145 {
1146 var veAttribute = new VEAttribute(this, _xmlNode);
1147 attributeTable.append(veAttribute.getAsTableRow());
1148 });
1149
1150 _infoDiv.append(attributeTableTitle);
1151 _infoDiv.append(attributeTable);
1152
1153 var addDiv = $("<div>", {"class":"veInfoDivTitle"});
1154 var addSelect = $("<select>");
1155 addSelect.append("<option>[blank]</option>", {value:"[blank]"});
1156 var fullName = _xmlNode.tagName;
1157 var namespace;
1158 var nodeName;
1159 if(fullName.indexOf(":") == -1)
1160 {
1161 namespace = "no namespace";
1162 nodeName = fullName;
1163 }
1164 else
1165 {
1166 namespace = fullName.substring(0, fullName.indexOf(":"));
1167 nodeName = fullName.substring(fullName.indexOf(":") + 1);
1168 }
1169 var nameList = _validAttrList[namespace];
1170 if(nameList)
1171 {
1172 var nameList = _validAttrList[namespace];
1173 var attrList = nameList[nodeName];
1174 if(attrList)
1175 {
1176 for(var i = 0; i < attrList.length; i++)
1177 {
1178 addSelect.append($("<option>" + attrList[i] + "</option>", {value:attrList[i]}));
1179 }
1180 }
1181 }
1182
1183 var addButton = $("<button>Add attribute</button>");
1184 addButton.click(function()
1185 {
1186 var newAtt;
1187 var editModeValue;
1188 if(addSelect.find(":selected").val() == "[blank]")
1189 {
1190 newAtt = new VEAttribute(null, _xmlNode, "", "");
1191 editModeValue = false;
1192 }
1193 else
1194 {
1195 newAtt = new VEAttribute(null, _xmlNode, addSelect.find(":selected").val(), "");
1196 editModeValue = true;
1197 }
1198
1199 var row = newAtt.getAsTableRow();
1200 attributeTable.append(row);
1201 newAtt.editMode(editModeValue);
1202 _transactions.push({type:"addAttr", row:row})
1203 });
1204 addDiv.append(addSelect);
1205 addDiv.append(addButton);
1206 _infoDiv.append(addDiv);
1207 addButton.button();
1208
1209 /*
1210 if(_xmlNode.tagName == "xsl:call-template" && _xmlNode.getAttribute("name").length > 0)
1211 {
1212 var extraOptionsTitle = $("<div>", {"class":"ui-state-default ui-corner-all veInfoDivTitle"}).text("Additional options:");
1213 var visitTemplateOption = $("<button>View called template</button>");
1214
1215 _infoDiv.append(extraOptionsTitle);
1216 _infoDiv.append(visitTemplateOption);
1217
1218 visitTemplateOption.button();
1219 visitTemplateOption.click(function()
1220 {
1221 var template = "";
1222 template += '<xsl:template match="/">';
1223 template += '<calledTemplate>';
1224 var params = $(_xmlNode).children("xsl\\:with-param");
1225 if(!params.length)
1226 {
1227 template += '<xsl:call-template name="' + _xmlNode.getAttribute("name") + '"/>';
1228 }
1229 else
1230 {
1231 template += '<xsl:call-template name="' + _xmlNode.getAttribute("name") + '">';
1232 for(var i = 0; i < params.length; i++)
1233 {
1234 var param = params.eq(i);
1235 template += "<xsl:with-param";
1236 for(var j = 0; j < param[0].attributes.length; j++)
1237 {
1238 var attr = param[0].attributes[j];
1239 template += " " + attr.name + "=\"" + attr.value + "\"";
1240 }
1241 template += "/>";
1242 }
1243 template += '</xsl:call-template>';
1244 }
1245 template += '</calledTemplate>';
1246 template += '</xsl:template>';
1247 template = template.replace(" ", "%20");
1248
1249 var url = document.URL;
1250 if(url.indexOf("?") == url.length - 1)
1251 {
1252 url += "ilt=" + template;
1253 }
1254 else if(url.indexOf("?") != -1)
1255 {
1256 url += "&ilt=" + template;
1257 }
1258 else
1259 {
1260 url += "?ilt=" + template;
1261 }
1262
1263 $.ajax(url)
1264 .success(function(response)
1265 {
1266 var xml = $.parseXML(response);
1267 var debug = $(xml).find("calledTemplate debug");
1268
1269 _greenbug.changeCurrentTemplate(debug[0].getAttribute("filename"), "template", "xsl", debug[0].getAttribute("name"), null);
1270 $(".gbTemplateContainer").css("border", "1px dashed #AAAAAA");
1271 });
1272 });
1273 }
1274 */
1275 }
1276
1277 if(_xmlNode.nodeType == 3)
1278 {
1279 var textNode = new VEText(_xmlNode);
1280 _infoDiv.append(textNode.getDiv());
1281 }
1282 }
1283
1284 //Add mouseover/out and click events to this element
1285 var addMouseEvents = function()
1286 {
1287 _div.mouseover(function(event)
1288 {
1289 event.stopPropagation();
1290 _div.css("border", "1px solid orange");
1291 var titleString = " ";
1292 if(_xmlNode.nodeType == 1)
1293 {
1294 for(var i = 0; i < _xmlNode.attributes.length; i++)
1295 {
1296 var current = _xmlNode.attributes[i];
1297 var name = current.name;
1298 var value = current.value;
1299
1300 titleString += name + "=\"" + value + "\" ";
1301 }
1302 }
1303 else if(_xmlNode.nodeType == 3)
1304 {
1305 titleString = _xmlNode.nodeValue;
1306 }
1307 _div.attr("title", titleString);
1308 });
1309 _div.mouseout(function(event)
1310 {
1311 _div.css("border", "1px solid black");
1312 event.stopPropagation();
1313 });
1314 _div.click(function(event)
1315 {
1316 if(_selectedElement)
1317 {
1318 _selectedElement.css("border", _selectedElement.prevBorder);
1319 }
1320 _selectedElement = _div;
1321 _div.prevBorder = _div.css("border");
1322 _div.css("border", "red solid 1px");
1323
1324 _div.data("parentVEElement").focus();
1325 _div.data("parentVEElement").populateInformationDiv();
1326
1327 event.stopPropagation();
1328 });
1329 }
1330
1331 //Check if we need to expand an element before we do
1332 var checkResizeNecessary = function()
1333 {
1334 var elemsToCheck = _div.find(".veTitleElement");
1335 for(var i = 0; i < elemsToCheck.length; i++)
1336 {
1337 var titleElem = elemsToCheck.eq(i);
1338 titleElem.html("<span>" + titleElem.html() + "</span>");
1339 var titleSpan = titleElem.children("span");
1340
1341 var resizeNecessary = false;
1342 if(titleSpan.width() >= titleElem.parent().width())
1343 {
1344 resizeNecessary = true;
1345 }
1346 titleElem.html(titleSpan.text());
1347
1348 if(resizeNecessary)
1349 {
1350 return true;
1351 }
1352 }
1353 return false;
1354 }
1355
1356 //Remove this from the editor
1357 this.remove = function()
1358 {
1359 var divParent = _div.parents(".veElement");
1360 _transactions.push({type:"remMvElem", vElemParent:_div.parent(), vElemPos:_div.index(), vElem:_div});
1361 _div.data("expanded", "normal");
1362 $(_xmlNode).remove();
1363 _div.detach();
1364 _infoDiv.empty();
1365
1366 if(divParent.length)
1367 {
1368 divParent.first().trigger("click");
1369 }
1370
1371 $("#veTrash").children("img").attr("src", gs.imageURLs.trashFull);
1372 }
1373
1374 //Expend this element horizontally
1375 this.expand = function()
1376 {
1377 var siblings = _div.siblings(".veElement");
1378 if(!(_div.data("expanded") == "expanded") && siblings.length && siblings.length > 0)
1379 {
1380 var sibWidth = 20 / siblings.length;
1381 siblings.each(function()
1382 {
1383 $(this).animate({width:sibWidth + "%"}, 900);
1384 $(this).data("expanded", "small");
1385 });
1386
1387 _div.animate({width:"80%"}, 1000);
1388 _div.data("expanded", "expanded");
1389 }
1390 }
1391
1392 //Evenly distribute the children of this node evenly
1393 this.evenlyDistributeChildren = function()
1394 {
1395 var children = _div.find(".veElement")
1396 .each(function()
1397 {
1398 $(this).data("expanded", "normal");
1399 var length = $(this).siblings(".veElement").filter(function(){return !($(this).data("helper"))}).length + 1;
1400 $(this).css("width", (100 / length) + "%");//$(this).animate({"width":(100 / length) + "%"}, 900);
1401 });
1402 }
1403
1404 //Expand this node and any parents and evenly distribute its children
1405 this.focus = function()
1406 {
1407 if(checkResizeNecessary())
1408 {
1409 _div.data("parentVEElement").expand();
1410
1411 var parents = _div.parents(".veElement");
1412 parents.each(function()
1413 {
1414 $(this).data("parentVEElement").expand();
1415 });
1416 }
1417
1418 _div.data("parentVEElement").evenlyDistributeChildren();
1419 }
1420
1421 //Set whether to use the short name for this element (i.e. without the namespace)
1422 this.setShortName = function(short)
1423 {
1424 if(short && _xmlNode.nodeType == 1 && _xmlNode.tagName.indexOf(":") != -1)
1425 {
1426 _div.children(".veTitleElement").text(_xmlNode.tagName.substring(_xmlNode.tagName.indexOf(":") + 1));
1427 }
1428 else if(!short)
1429 {
1430 _div.children(".veTitleElement").text(_xmlNode.tagName);
1431 }
1432 }
1433
1434 //Set the width of this element
1435 this.setWidth = function(width)
1436 {
1437 _div.css("width", width + "%");
1438 }
1439
1440 //Visual Editor Element constructor
1441 var initVEE = function()
1442 {
1443 _div.addClass("veElement");
1444 _div.addClass("ui-corner-all");
1445 makeDraggable();
1446
1447 var titleText;
1448 if(_xmlNode.nodeType == 3 && _xmlNode.nodeValue.search(/\S/) != -1)
1449 {
1450 _div.addClass("veTextElement");
1451 titleText = "[text]";
1452 }
1453 else if (_xmlNode.nodeType == 1)
1454 {
1455 if(_xmlNode.tagName.search(/^xsl/) != -1)
1456 {
1457 _div.addClass("veXSLElement");
1458 }
1459 else if(_xmlNode.tagName.search(/^gsf/) != -1)
1460 {
1461 _div.addClass("veGSFElement");
1462 }
1463 else if(_xmlNode.tagName.search(/^gslib/) != -1)
1464 {
1465 _div.addClass("veGSLIBElement");
1466 }
1467 else
1468 {
1469 _div.addClass("veHTMLElement");
1470 }
1471 titleText = _xmlNode.tagName;
1472 }
1473
1474 addMouseEvents();
1475
1476 _div.append("<div class=\"veTitleElement\">" + titleText + "</div>");
1477 }
1478
1479 initVEE();
1480 }
1481
1482 //Call the constructor
1483 initVXE();
1484}
Note: See TracBrowser for help on using the repository browser.