make mixed-xrefs "Related views" collapsible on feature page
[sgn.git] / js / CXGN / DynamicPreviewPane.js
blob4d09e1904fef6023684f8ac64de9012c8f875bc8
1 /*******************************************************************************************************************
3 =head1 NAME
5 DynamicPreviewPane.js: continuously updated preview area with text taken from a specified form field and custom-processed
7 =head1 SYNOPSIS
9 The DynamicPreviewPane object acts as a namespace.
11 Field names starting with underscores are internal.
13 Dependencies: MochiKit.
15 =head1 AUTHOR
17 Evan Herbst
19 Updated 1 / 11 / 07
21 =head1 DESCRIPTION
23 =head2 Functions
25 =over 3
27 =cut
28 *******************************************************************************************************************/
30 //include required modules
31 //tried doing this with an array of strings for module names; no idea why that doesn't go -- Evan
32 if(typeof(JSAN) != 'undefined')
34         JSAN.use("MochiKit.DOM", []);
35         JSAN.use("MochiKit.Signal", []);
37 try //assume that each required module X.Y creates an object named X.Y
39         if(typeof MochiKit.DOM == 'undefined') throw '';
40         if(typeof MochiKit.Signal == 'undefined') throw '';
42 catch(x)
44         throw "DynamicPreviewPane requires the following modules: MochiKit.DOM, MochiKit.Signal";
48 DynamicPreviewPane =
51 //internal constants
52 _styleTagClass: 'dynamic_previewpane_styles',
53 _previewPaneClass: 'dynamic_preview_pane',
55 //internal variables
56 _pane2sourceID: {}, //map preview-pane element IDs to their source IDs
57 _pane2sourceType: {}, //map preview-pane element IDs to the content types of their sources
61 =item _getDocumentHead ()
63 return the HEAD node in the current document
65 =cut
67 _getDocumentHead: function()
69         return MochiKit.DOM.getFirstElementByTagAndClassName('head', null);
74 =item _styleText ()
76 return a string with all the CSS required by this library
78 =cut
80 _styleText: function()
82         return 'div.' + this._previewPaneClass + ' {border: 1px solid #888}';
87 =item _includePreviewPaneStyles ()
89 add a STYLE element with our CSS to the HEAD of the current document,
90 unless there's already a STYLE with the class name this library uses
92 =cut
94 _includePreviewPaneStyles: function()
96         var head = this._getDocumentHead();
97         var ourStyles = MochiKit.DOM.getFirstElementByTagAndClassName('style', this._styleTagClass, head);
98         if(ourStyles == null)
99         {
100                 var styleNode = MochiKit.DOM.createDOM('style', {'type': 'text/css', 'class': this._styleTagClass}, this._styleText());
101                 head.appendChild(styleNode);
102         }
105 /********************************************************************************************
106 * CONVERSION FUNCTIONS FOR VARIOUS SOURCE TYPES
107 ********************************************************************************************/
109 //match info for source types
110 HTML: 'html',
111 DELIMITED_LIST: 'list',
112 _DELIMITED_LIST_REGEXP: null, //allow for specification of list delimiters as 'list(DELIMITERS)'
116 =item _convertHTML ()
118 convert text from an HTML source according to SGN's HTML input rules and
119 return the resulting string
121 Arguments:
122    sourceHTML - input string
124 =cut
126 _convertHTML: function(sourceHTML)
129 [Koni]
130 # Quick, dirty hack method of defenestrating disallowed HTML tags in 
131 # research interest statements: replace allowed tags with a marker string,
132 # then delete all tokens of the form "<[^>]+>", then replace the marker string
133 # with the original tag. 
135 # Using the marker string allows us to "save" the information about the tag
136 # that is allowed -- changing it to a token that won't match the "<[^>]+>"
137 # pattern. Using a random marker string prevents a user learning (through a
138 # website error or something that reveals a static marker string) the marker
139 # and trying to insert illegal tags by typing the marker into their statement
140 # manually. This is further guarded against by only recognizing marker strings
141 # with allowed tags encoded. (Yes, I am paranoid)
143 # The main problem with this is if someone happens to use both a literal "<"
144 # and a matching ">" in their research statement -- if that happens, the
145 # text flanked by these will be deleted -- they'll need to use &lt; and &gt;
146 # instead.
148 Update: removed randomly generated marker string as unnecessary -- Evan
150         sourceHTML = sourceHTML.replace(/<(\/?)([pbi]|br)>/g, "%%$1-$2-MARKER%%"); //just hope nobody actually uses text with this format
151         sourceHTML = sourceHTML.replace(/\<[^>]+\>/g, ""); //remove all disallowed tags
152         sourceHTML = sourceHTML.replace(/%%(\/?)-([pbi]|br)-MARKER%%/g, "<$1$2>");
153         return sourceHTML;
158 =item _convertDelimitedList ()
160 convert text from a list source, inserting newlines instead of delimiters for nice online display
162 Arguments:
163    delimiters - list of delimiting characters
164    sourceText - input string
165         
166 =cut
168 _convertDelimitedList: function(delimiters, sourceText)
170         var regexp = new RegExp("[" + delimiters + "]+", "g");
171         sourceText = sourceText.replace(regexp, "<br />");
172         return sourceText;
177 =item _getConversionFunctorForSourceType ()
179 return a functor whose apply() function takes one argument, the text to be converted
181 Arguments:
182    sourceType - content type to be processed by the returned functor
183         
184 =cut
186 _getConversionFunctorForSourceType: function(sourceType)
188         //DynamicPreviewPane initialization
189         if(this._DELIMITED_LIST_REGEXP == null)
190                 this._DELIMITED_LIST_REGEXP = new RegExp(this.DELIMITED_LIST + '\\((.*)\\)', '');
192         if(sourceType == this.HTML) return this._convertHTML;
193         var matchInfo = sourceType.match(this._DELIMITED_LIST_REGEXP);
194         //if the match was successful, matchInfo is an array with matched subexpressions starting at index 1
195         if(matchInfo != null) return function(sourceText) {return DynamicPreviewPane._convertDelimitedList(matchInfo[1], sourceText);}
196         alert("DynamicPreviewPane can't find conversion function for unknown source type '" + sourceType + "'");
201 =item _updatePreviewPane ()
203 will be called with 'this' having the properties of the preview pane element
204 (as well as possibly some others?)
206 Arguments:
207    event - not sure, but I think that's what gets passed; its type prints as 'object'; anyway, it isn't used
209 update the preview pane to have the same text as its source(s), put through a custom processing function
211 =cut
213 _updatePreviewPane: function(event)
215         var textSource = MochiKit.DOM.getElement(DynamicPreviewPane._pane2sourceID[this.id]);
216         var sourceType = DynamicPreviewPane._pane2sourceType[this.id];
217         this.innerHTML = DynamicPreviewPane._getConversionFunctorForSourceType(sourceType)(textSource.value);
222 =item createPreviewPane ()
224 Arguments:
225    sourceType - a string describing the content type provided by the source; should be one of the DynamicPreviewPane constants
226    textSourceID - an element that can use the 'onchange' event, such as an <input type=text> or a <textarea>
227    paneParentID - the element under which the preview pane (a DIV) will be put
229 include the required CSS if it doesn't seem to be in the document already
231 =cut
233 createPreviewPane: function(sourceType, textSourceID, paneParentID)
235         //include our CSS only if it isn't already
236         this._includePreviewPaneStyles();
237         
238         var paneParent = MochiKit.DOM.getElement(paneParentID);
239         var previewPane = MochiKit.DOM.DIV({'class': this._previewPaneClass, 'id': textSourceID + '_src_preview_pane'}, ''); //give it an empty text node for a child so the child will exist
240         this._pane2sourceID[previewPane.id] = textSourceID;
241         this._pane2sourceType[previewPane.id] = sourceType;
242         paneParent.appendChild(previewPane);
243         MochiKit.Signal.connect(textSourceID, 'onkeyup', previewPane, this._updatePreviewPane);
244         
245         //initialize preview pane
246         this._updatePreviewPane.apply(previewPane);
250 =back
252 =cut
255 }; //end HTMLPreviewPane