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

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

Added back, forwards and current selection buttons, also various improvements and fixes

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