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

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

Some usability changes as well as a few bug fixes

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