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

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

Enabled the functionality to go to the template that is called by xsl:call-template

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