1 /*******************************************************************************************************************
5 DynamicPreviewPane.js: continuously updated preview area with text taken from a specified form field and custom-processed
9 The DynamicPreviewPane object acts as a namespace.
11 Field names starting with underscores are internal.
13 Dependencies: MochiKit.
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 '';
44 throw "DynamicPreviewPane requires the following modules: MochiKit.DOM, MochiKit.Signal";
52 _styleTagClass: 'dynamic_previewpane_styles',
53 _previewPaneClass: 'dynamic_preview_pane',
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
67 _getDocumentHead: function()
69 return MochiKit.DOM.getFirstElementByTagAndClassName('head', null);
76 return a string with all the CSS required by this library
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
94 _includePreviewPaneStyles: function()
96 var head = this._getDocumentHead();
97 var ourStyles = MochiKit.DOM.getFirstElementByTagAndClassName('style', this._styleTagClass, head);
100 var styleNode = MochiKit.DOM.createDOM('style', {'type': 'text/css', 'class': this._styleTagClass}, this._styleText());
101 head.appendChild(styleNode);
105 /********************************************************************************************
106 * CONVERSION FUNCTIONS FOR VARIOUS SOURCE TYPES
107 ********************************************************************************************/
109 //match info for source types
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
122 sourceHTML - input string
126 _convertHTML: function(sourceHTML)
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 < and >
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>");
158 =item _convertDelimitedList ()
160 convert text from a list source, inserting newlines instead of delimiters for nice online display
163 delimiters - list of delimiting characters
164 sourceText - input string
168 _convertDelimitedList: function(delimiters, sourceText)
170 var regexp = new RegExp("[" + delimiters + "]+", "g");
171 sourceText = sourceText.replace(regexp, "<br />");
177 =item _getConversionFunctorForSourceType ()
179 return a functor whose apply() function takes one argument, the text to be converted
182 sourceType - content type to be processed by the returned functor
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?)
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
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 ()
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
233 createPreviewPane: function(sourceType, textSourceID, paneParentID)
235 //include our CSS only if it isn't already
236 this._includePreviewPaneStyles();
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);
245 //initialize preview pane
246 this._updatePreviewPane.apply(previewPane);
255 }; //end HTMLPreviewPane