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

Last change on this file since 37480 was 37480, checked in by davidb, 13 months ago

Changes to get Greenbug back up and running. Theis commit targets two areas: we now need to explicitly specify 'o=xml' in our calls to general action with 'sa' as things like 'XSLTGetTemplateListFromFile'; we now also need to set the AJAX calls explicitly so they return string-type as their response (even if fundamentally the message is XML for example), which is done with dataType: 'text' in the jquery-based AJAX call. We are now using jquery v3.x and it looks like the newer versions of the library auto-sense the return time, where as the older jquery we were using did not to this, hence the reason for the change. In the long run it would be better to keep the returned response in XML, and use browser API to access XML to get to the data we want, however the decision for now was to explicitly set things so it is handled as a string, thereby allowing all our currently written code to operate as it used to.

File size: 39.8 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 + "?o=xml&a=g&rt=r&s=GetTemplateListFromFile&s1.locationName=interface&s1.interfaceName=" + gs.xsltParams.interface_name + "&s1.fileName=gslib.xsl";
291
292 $.ajax(url, { dataType: "text" })
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.error("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 + "?o=xml&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, { dataType: "text" }).done(successFunction).fail(errorFunction);
1178 }
1179
1180 $.ajax(url, { dataType: "text" }).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.