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

Last change on this file since 36027 was 36027, checked in by cstephen, 2 years ago

Migrate to using jQuery3 and jQuery-UI-1.13.2; and integrate cookie consent manager

File size: 39.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 //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 .done(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 .fail(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.on("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.on("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.on("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.on("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.on("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.on("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 var visitCalledTemplate = function()
1151 {
1152 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");
1153
1154 var successFunction = function(response)
1155 {
1156 var startIndex = response.indexOf("<requestedTemplate>") + ("<requestedTemplate>").length;
1157 var endIndex = response.indexOf("</requestedTemplate>");
1158
1159 if(endIndex != -1)
1160 {
1161 var templateFileName = response.substring(startIndex, endIndex);
1162 var file = _greenbug.fileNameToLocationAndName(templateFileName);
1163
1164 var fileTemplateName = file.filename.replace(/\\/g, "/") + " (" + file.location + ")";
1165 $("#veFileSelector").find("option").filter(function(){return $(this).text() == fileTemplateName}).prop("selected", true);
1166 _greenbug.populateTemplateSelectorFromFile(file.filename, file.location);
1167 _greenbug.changeCurrentTemplate(file.location, file.filename, "template", "xsl", _xmlNode.getAttribute("name"), null);
1168 }
1169 else
1170 {
1171 _greenbug.changeCurrentTemplate("interface", "gslib.xsl", "template", "xsl", _xmlNode.getAttribute("name"), null);
1172 }
1173 }
1174
1175 var errorFunction = function()
1176 {
1177 $.ajax(url).done(successFunction).fail(errorFunction);
1178 }
1179
1180 $.ajax(url).done(successFunction).fail(errorFunction);
1181 }
1182
1183 //Fill the information area with details about this element
1184 this.populateInformationDiv = function()
1185 {
1186 _thisEditor.savePendingEdits();
1187 _infoDiv.empty();
1188
1189 if(_xmlNode.nodeType == 1)
1190 {
1191 var nameElementTitle = $("<div>", {"class":"ui-state-default ui-corner-all veInfoDivTitle"}).text("Element name:");
1192 _infoDiv.append(nameElementTitle);
1193 _infoDiv.append($("<p>").text(_xmlNode.nodeName));
1194 }
1195 else
1196 {
1197 var textElementTitle = $("<div>", {"class":"ui-state-default ui-corner-all veInfoDivTitle"}).text("Text node:");
1198 _infoDiv.append(textElementTitle);
1199 }
1200
1201 if(_xmlNode.nodeType == 1)
1202 {
1203 var attributeTableTitle = $("<div>", {"class":"ui-state-default ui-corner-all veInfoDivTitle"});
1204 attributeTableTitle.text("Attributes:");
1205 var attributeTable = $("<table>");
1206 attributeTable.addClass("veAttributeTableContainer");
1207
1208 attributeTable.append($("<tr>").html("<td class=\"veNameCell\">Name</td><td class=\"veValueCell\">Value</td>"));
1209
1210 $(_xmlNode.attributes).each(function()
1211 {
1212 var veAttribute = new VEAttribute(this, _xmlNode);
1213 attributeTable.append(veAttribute.getAsTableRow());
1214 });
1215
1216 _infoDiv.append(attributeTableTitle);
1217 _infoDiv.append(attributeTable);
1218
1219 var addDiv = $("<div>", {"class":"veInfoDivTitle"});
1220 var addSelect = $("<select>");
1221 addSelect.append("<option>[blank]</option>", {value:"[blank]"});
1222 var fullName = _xmlNode.tagName;
1223 var namespace;
1224 var nodeName;
1225 if(fullName.indexOf(":") == -1)
1226 {
1227 namespace = "no namespace";
1228 nodeName = fullName;
1229 }
1230 else
1231 {
1232 namespace = fullName.substring(0, fullName.indexOf(":"));
1233 nodeName = fullName.substring(fullName.indexOf(":") + 1);
1234 }
1235 var nameList = _validAttrList[namespace];
1236 if(nameList)
1237 {
1238 var nameList = _validAttrList[namespace];
1239 var attrList = nameList[nodeName];
1240 if(attrList)
1241 {
1242 for(var i = 0; i < attrList.length; i++)
1243 {
1244 addSelect.append($("<option>" + attrList[i] + "</option>", {value:attrList[i]}));
1245 }
1246 }
1247 }
1248
1249 var addButton = $("<button>Add attribute</button>");
1250 addButton.on("click", function()
1251 {
1252 var newAtt;
1253 var editModeValue;
1254 if(addSelect.find(":selected").val() == "[blank]")
1255 {
1256 newAtt = new VEAttribute(null, _xmlNode, "", "");
1257 editModeValue = false;
1258 }
1259 else
1260 {
1261 newAtt = new VEAttribute(null, _xmlNode, addSelect.find(":selected").val(), "");
1262 editModeValue = true;
1263 }
1264
1265 var row = newAtt.getAsTableRow();
1266 attributeTable.append(row);
1267 newAtt.editMode(editModeValue);
1268 _transactions.push({type:"addAttr", row:row})
1269 });
1270 addDiv.append(addSelect);
1271 addDiv.append(addButton);
1272 _infoDiv.append(addDiv);
1273 addButton.button();
1274
1275 if(_xmlNode.tagName == "xsl:call-template" && _xmlNode.getAttribute("name").length > 0)
1276 {
1277 var extraOptionsTitle = $("<div>", {"class":"ui-state-default ui-corner-all veInfoDivTitle"}).text("Additional options:");
1278 var visitTemplateOption = $("<button>View called template</button>");
1279
1280 _infoDiv.append(extraOptionsTitle);
1281 _infoDiv.append(visitTemplateOption);
1282
1283 visitTemplateOption.button();
1284 visitTemplateOption.click(visitCalledTemplate);
1285 }
1286 }
1287
1288 if(_xmlNode.nodeType == 3)
1289 {
1290 var textNode = new VEText(_xmlNode);
1291 _infoDiv.append(textNode.getDiv());
1292 }
1293 }
1294
1295 //Add mouseover/out and click events to this element
1296 var addMouseEvents = function()
1297 {
1298 _div.mouseover(function(event)
1299 {
1300 event.stopPropagation();
1301 if(!_selectedElement || _id != _selectedElement.data("parentVEElement").getID())
1302 {
1303 _div.css("border", "1px solid orange");
1304 }
1305
1306 var titleString = " ";
1307 if(_xmlNode.nodeType == 1)
1308 {
1309 for(var i = 0; i < _xmlNode.attributes.length; i++)
1310 {
1311 var current = _xmlNode.attributes[i];
1312 var name = current.name;
1313 var value = current.value;
1314
1315 titleString += name + "=\"" + value + "\" ";
1316 }
1317 }
1318 else if(_xmlNode.nodeType == 3)
1319 {
1320 titleString = _xmlNode.nodeValue;
1321 }
1322 _div.attr("title", titleString);
1323 });
1324 _div.mouseout(function(event)
1325 {
1326 if(!_selectedElement || _id != _selectedElement.data("parentVEElement").getID())
1327 {
1328 _div.css("border", "1px solid black");
1329 }
1330 event.stopPropagation();
1331 });
1332 _div.on("click", function(event)
1333 {
1334 event.stopPropagation();
1335 if(_selectedElement)
1336 {
1337 _selectedElement.css("border", _selectedElement.prevBorder);
1338 if(_selectedElement.children("a").length)
1339 {
1340 _selectedElement.children("a").replaceWith(_selectedElement.children("a").children(".veTitleElement"));
1341 }
1342 }
1343 _selectedElement = _div;
1344 _div.prevBorder = _div.css("border");
1345 _div.css("border", "red solid 1px");
1346
1347 _div.data("parentVEElement").focus();
1348 _div.data("parentVEElement").populateInformationDiv();
1349
1350 if(_xmlNode.tagName == "xsl:call-template" && _xmlNode.getAttribute("name").length > 0)
1351 {
1352 var link = $("<a>");
1353 link.attr("href", "javascript:;");
1354 link.click(visitCalledTemplate);
1355 link.append(_div.children(".veTitleElement"));
1356 _div.append(link);
1357 }
1358 });
1359 }
1360
1361 //Check if we need to expand an element before we do
1362 var checkResizeNecessary = function()
1363 {
1364 var elemsToCheck = _div.find(".veTitleElement");
1365 for(var i = 0; i < elemsToCheck.length; i++)
1366 {
1367 var titleElem = elemsToCheck.eq(i);
1368 titleElem.html("<span>" + titleElem.html() + "</span>");
1369 var titleSpan = titleElem.children("span");
1370
1371 var resizeNecessary = false;
1372 if(titleSpan.width() >= titleElem.parent().width())
1373 {
1374 resizeNecessary = true;
1375 }
1376 titleElem.html(titleSpan.text());
1377
1378 if(resizeNecessary)
1379 {
1380 return true;
1381 }
1382 }
1383 return false;
1384 }
1385
1386 //Remove this from the editor
1387 this.remove = function()
1388 {
1389 var divParent = _div.parents(".veElement");
1390 _transactions.push({type:"remMvElem", vElemParent:_div.parent(), vElemPos:_div.index(), vElem:_div});
1391 _div.data("expanded", "normal");
1392 $(_xmlNode).remove();
1393 _div.detach();
1394 _infoDiv.empty();
1395
1396 if(divParent.length)
1397 {
1398 divParent.first().trigger("click");
1399 }
1400
1401 $("#veTrash").children("img").attr("src", gs.imageURLs.trashFull);
1402 }
1403
1404 //Expend this element horizontally
1405 this.expand = function()
1406 {
1407 var siblings = _div.siblings(".veElement");
1408 if(!(_div.data("expanded") == "expanded") && siblings.length && siblings.length > 0)
1409 {
1410 var sibWidth = 20 / siblings.length;
1411 siblings.each(function()
1412 {
1413 $(this).animate({width:sibWidth + "%"}, 900);
1414 $(this).data("expanded", "small");
1415 });
1416
1417 _div.animate({width:"80%"}, 1000);
1418 _div.data("expanded", "expanded");
1419 }
1420 }
1421
1422 //Evenly distribute the children of this node evenly
1423 this.evenlyDistributeChildren = function()
1424 {
1425 var children = _div.find(".veElement")
1426 .each(function()
1427 {
1428 $(this).data("expanded", "normal");
1429 var length = $(this).siblings(".veElement").filter(function(){return !($(this).data("helper"))}).length + 1;
1430 $(this).css("width", (100 / length) + "%");//$(this).animate({"width":(100 / length) + "%"}, 900);
1431 });
1432 }
1433
1434 //Expand this node and any parents and evenly distribute its children
1435 this.focus = function()
1436 {
1437 if(checkResizeNecessary())
1438 {
1439 _div.data("parentVEElement").expand();
1440
1441 var parents = _div.parents(".veElement");
1442 parents.each(function()
1443 {
1444 $(this).data("parentVEElement").expand();
1445 });
1446 }
1447
1448 _div.data("parentVEElement").evenlyDistributeChildren();
1449 }
1450
1451 //Set whether to use the short name for this element (i.e. without the namespace)
1452 this.setShortName = function(short)
1453 {
1454 if(short && _xmlNode.nodeType == 1 && _xmlNode.tagName.indexOf(":") != -1)
1455 {
1456 _div.children(".veTitleElement").text(_xmlNode.tagName.substring(_xmlNode.tagName.indexOf(":") + 1));
1457 }
1458 else if(!short)
1459 {
1460 _div.children(".veTitleElement").text(_xmlNode.tagName);
1461 }
1462 }
1463
1464 //Set the width of this element
1465 this.setWidth = function(width)
1466 {
1467 _div.css("width", width + "%");
1468 }
1469
1470 //Visual Editor Element constructor
1471 var initVEE = function()
1472 {
1473 _div.addClass("veElement");
1474 _div.addClass("ui-corner-all");
1475 makeDraggable();
1476
1477 var titleText;
1478 if(_xmlNode.nodeType == 3 && _xmlNode.nodeValue.search(/\S/) != -1)
1479 {
1480 _div.addClass("veTextElement");
1481 titleText = "[text]";
1482 }
1483 else if (_xmlNode.nodeType == 1)
1484 {
1485 if(_xmlNode.tagName.search(/^xsl/) != -1)
1486 {
1487 _div.addClass("veXSLElement");
1488 }
1489 else if(_xmlNode.tagName.search(/^gsf/) != -1)
1490 {
1491 _div.addClass("veGSFElement");
1492 }
1493 else if(_xmlNode.tagName.search(/^gslib/) != -1)
1494 {
1495 _div.addClass("veGSLIBElement");
1496 }
1497 else
1498 {
1499 _div.addClass("veHTMLElement");
1500 }
1501 titleText = _xmlNode.tagName;
1502 }
1503
1504 addMouseEvents();
1505
1506 _div.append("<div class=\"veTitleElement\">" + titleText + "</div>");
1507 }
1508
1509 initVEE();
1510 }
1511
1512 //Call the constructor
1513 initVXE();
1514}
Note: See TracBrowser for help on using the repository browser.