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