/** * @filename * public.js * * @author * Julian Turner, Ripley, Derbyshire, England * http://www.baconbutty.com * * @copyright * (c) Julian Turner 2007 * * @version * 1 (developed 2007) * * @fileoverview * public.js contains a number of functions used by * all of the web pages on http://www.baconbutty.com, * where additional JavaScript functionality is offered, * such as the CodePlay, HTMLPlay and PagePlay features. * * @dependencies * public.js is not dependent on any other js file. * * @licence * Your only permission is to download and run * this code in your browser as part of the HTML * page you are viewing at http://www.baconbutty.com. * All other rights are reserved. */
var DOMAIN = "http://127.0.0.1:8080/baconbutty/";
var DOMAIN = "http://www.baconbutty.com/";
function CodePlay_Edit( elementId /*: String*/ ) /*: void*/ { var element /*: HTMLElement*/ = document.getElementById(elementId); if (!element) { return; } if (element.getAttribute("activated") == "true") { return; } else { element.setAttribute("activated", "true"); } var code /*: String*/ = HTMLElement_InnerText(element); var height /*: int*/ = element.offsetHeight - 2; height = 200; var div1 /*: HTMLDivElement*/ = document.createElement("div"); div1.className = "edit-div-1"; var div2 /*: HTMLDivElement*/ = document.createElement("div"); div2.className = "edit-div-2"; var div3 /*: HTMLDivElement*/ = document.createElement("div"); div3.className = "edit-div-3"; var textarea /*: HTMLTextAreaElement*/ = document.createElement("textarea"); textarea.className = "edit-area"; textarea.id = element.id; textarea.value = code; textarea.style.height = height + "px"; HTMLElement_AttachEvent(textarea, "onkeydown", HTMLTextAreaElement_InsertTab); div1.appendChild(div2); div2.appendChild(div3); div3.appendChild(textarea); element.parentNode.insertBefore(div1, element); element.parentNode.removeChild(element); }
Replaces an HTMLElement
with an HTMLTextAreaElement
, and puts the element's textContent
into the HTMLTextAreaElement
.
elementId : String
The id
of the target HTMLElement
.
void
HTMLElement_InnerText
HTMLElement_AttachEvent
HTMLTextAreaElement_InsertTab
None.
None.
function CodePlay_Play( elementId /*: String*/ ) /*: void*/ { var element /*: HTMLElement*/ = document.getElementById(elementId); if (!element) { return; } var code /*: String*/ = HTMLElement_InnerText(element); code = CodePlay_ConvertHeredoc(code); var data /*: Object*/ = CodePlay_GetDataRegions(code); eval(code); }
Runs the JavaScript code in a code play region.
elementId : String
The id
of the HTMLElement
whose content will be run.
void
CodePlay_GetDataRegions
CodePlay_ConvertHeredoc
HTMLElement_InnerText
The JavaScript code is pre-processed using CodePlay_GetDataRegions
and CodePlay_ConvertHeredoc
to convert embedded data into valid JavaScript string literals.
None
function CodePlay_GetDataRegions( code /*: String*/ ) /*: Object*/ { var data /*: Object*/ = {}; var dataRegionStartPattern /*: RegExp*/ = /\/\*(\w+)/gi; var dataRegionName /*: String*/ = ""; var dataRegionEndMarker /*: String*/ = ""; var dataRegionStart /*: int*/ = 0; var dataRegionInnerStart /*: int*/ = 0; var dataRegionContents /*: String*/ = ""; var dataRegionInnerEnd /*: int*/ = 0; var dataRegionEnd /*: int*/ = 0; var execResult /*: Array*/ = []; var overflow /*: int*/ = 100; while(overflow--) { dataRegionStartPattern.lastIndex = dataRegionEnd; execResult = dataRegionStartPattern.exec(code); if (!execResult) { break; } dataRegionName = execResult[1]; dataRegionEndMarker = dataRegionName + "*/"; dataRegionStart = execResult.index; dataRegionInnerStart = dataRegionStartPattern.lastIndex; dataRegionInnerEnd = code.indexOf(dataRegionEndMarker, dataRegionInnerStart); if (dataRegionInnerEnd == -1) { dataRegionEnd = dataRegionInnerStart; continue; } dataRegionEnd = dataRegionInnerEnd + dataRegionEndMarker.length; dataRegionContents = code.substring(dataRegionInnerStart, dataRegionInnerEnd); dataRegionContents = dataRegionContents.replace(/^\s\s*/g,"").replace(/\s*\s$/g,""); data[dataRegionName] = dataRegionContents; } return data; }
The purpose is to enable you to use strings in your code without escaping \t
,\r
and \n
.
The function extracts from the code any strings stored within a multi-line comment /* */
(data regions) within the code, and returns them in an Object
.
The idea being that you then eval
the code in a scope which contains that Object
. In effect you run this like a macro before you eval
the code.
code : String
The code to be evaluated, which contains data regions.
Object
None
The function is used as follows.
The data regions are in the format:-
/*yourIdentifer
Some Data
Some Data
Some Data
yourIdentifier*/
The string is extracted, trimmed, and then made a property of the returned Object
using the yourIdentifier
name.
To use the Object
in your evaluated code, you would do the following:-
var data /*: Object*/ = CodePlay_GetDataRegions(code);
eval(code);
The data
variable would be a variable local to the scope in which the code is eval'd
and the inner scopes of any functions within your code.
Your eval code would then look like this:-
alert(data.stringValue);
/*stringValue
Some Data
Some Data
Some Data
stringValue*/
Try it out:-
alert(data.stringValue);
/*stringValue
Some Data
Some Data
Some Data
stringValue*/
function CodePlay_ConvertHeredoc( code /*: String*/ ) /*: String*/ { var convertedCode /*: Array*/ = []; var heredocStartPattern /*: RegExp*/ = /<<<(\w+)/gi; var heredocEndPattern /*: RegExp*/ = null; var heredocIdentifier /*: String*/ = ""; var heredocStart /*: int*/ = 0; var heredocInnerStart /*: int*/ = 0; var heredocContents /*: String*/ = ""; var heredocInnerEnd /*: int*/ = 0; var heredocEnd /*: int*/ = 0; var execResult /*: Array*/ = []; var overflow /*: int*/ = 100; while(overflow--) { heredocStartPattern.lastIndex = heredocEnd; execResult = heredocStartPattern.exec(code); if (!execResult) { convertedCode[convertedCode.length] = code.substring(heredocEnd, code.length); break; } heredocIdentifier = execResult[1]; heredocStart = execResult.index; heredocInnerStart = heredocStartPattern.lastIndex; convertedCode[convertedCode.length] = code.substring(heredocEnd, heredocStart); heredocEndPattern = new RegExp("(\\r|\\n)" + heredocIdentifier + ";", "g"); heredocEndPattern.lastIndex = heredocInnerStart; execResult = heredocEndPattern.exec(code); if (!execResult) { alert("Could not find end of identifier " + heredocIdentifier); return ""; break; } heredocInnerEnd = execResult.index; heredocEnd = heredocEndPattern.lastIndex; heredocContents = code.substring(heredocInnerStart, heredocInnerEnd); heredocContents = heredocContents.replace(/^\s\s*/g,"").replace(/\s*\s$/g,""); heredocContents = String_ToStringLiteral(heredocContents); convertedCode[convertedCode.length] = "\"" + heredocContents + "\";\r\n"; } return convertedCode.join(""); }
The purpose is to enable you to use strings in your code without escaping \t
,\r
and \n
.
The function replaces within the code any strings stored within a Heredoc
format (as used in PHP
) with a JavaScript string literal using the same identifier.
In effect you run this like a macro before you eval
the code.
code : String
The code which contains Heredoc regions to be converted to string literals.
String
The code after conversion of the Heredoc areas.
String_ToStringLiteral
The function is used as follows.
var myString /*: String*/ = <<<RANDOM
Some Data
Some Data
Some Data
RANDOM;
alert(myString);
The RANDOM
can be any characters you want, and must not appear within the data itself.
The closing RANDOM
must appear on its own line, and not be indented.
The code is transformed to:-
var myString /*: String*/ = "Some Data\r\n\tSome Data\r\n\t\tSome Data"
alert(myString);
Try it out:-
var myString /*: String*/ = <<<RANDOM
Some Data
Some Data
Some Data
RANDOM;
alert(myString);
function HTMLPlay_Edit( elementId /*: String*/ ) { var element /*: HTMLElement*/ = document.getElementById(elementId); if (!element) { return; } if (element.getAttribute("activated") == "true") { return; } else { element.setAttribute("activated", "true"); } var html /*: String*/ = HTMLElement_InnerText(element); var height /*: String*/ = element.offsetHeight - 2; height = 200; var div1 /*: HTMLDivElement*/ = document.createElement("div"); div1.className = "edit-div-1"; var div2 /*: HTMLDivElement*/ = document.createElement("div"); div2.className = "edit-div-2"; var div3 /*: HTMLDivElement*/ = document.createElement("div"); div3.className = "edit-div-3"; var textarea /*: HTMLTextAreaElement*/ = document.createElement("textarea"); textarea.className = "edit-area"; textarea.id = element.id; textarea.value = html; textarea.style.height = height + "px"; HTMLElement_AttachEvent(textarea, "onkeydown", HTMLTextAreaElement_InsertTab); div1.appendChild(div2); div2.appendChild(div3); div3.appendChild(textarea); element.parentNode.insertBefore(div1, element); element.parentNode.removeChild(element); }
Replaces an HTMLElement
with an HTMLTextAreaElement
, and puts the element's textContent
into the HTMLTextAreaElement
.
elementId : String
The id
of the target HTMLElement
.
void
HTMLElement_InnerText
HTMLElement_AttachEvent
HTMLTextAreaElement_InsertTab
None.
None.
function HTMLPlay_Play( elementId /*: String*/ ) { var element /*: HTMLElement*/ = document.getElementById(elementId); if (!element) { return; } var html /*: String*/ = HTMLElement_InnerText(element); var display /*: HTMLElement*/ = document.getElementById(elementId + "DISPLAY"); if (!display) { return; } display.style.display = "block"; display.innerHTML = html; }
Inserts the HTML
mark-up into a revealed div
.
elementId : String
The id
of the HTMLElement
whose content will be run.
void
HTMLElement_InnerText
None
None
function PagePlay_Edit( elementId /*: String*/ ) { var element /*: HTMLElement*/ = document.getElementById(elementId); if (!element) { return; } if (element.getAttribute("activated") == "true") { return; } else { element.setAttribute("activated", "true"); } var code /*: String*/ = HTMLElement_InnerText(element); var height /*: String*/ = element.offsetHeight - 2; height = 200; var div1 /*: HTMLDivElement*/ = document.createElement("div"); div1.className = "edit-div-1"; var div2 /*: HTMLDivElement*/ = document.createElement("div"); div2.className = "edit-div-2"; var div3 /*: HTMLDivElement*/ = document.createElement("div"); div3.className = "edit-div-3"; var textarea /*: HTMLTextAreaElement*/ = document.createElement("textarea"); textarea.className = "edit-area"; textarea.id = element.id; textarea.value = code; textarea.style.height = height + "px"; HTMLElement_AttachEvent(textarea, "onkeydown", HTMLTextAreaElement_InsertTab); div1.appendChild(div2); div2.appendChild(div3); div3.appendChild(textarea); element.parentNode.insertBefore(div1, element); element.parentNode.removeChild(element); }
Replaces an HTMLElement
with an HTMLTextAreaElement
, and puts the element's textContent
into the HTMLTextAreaElement
.
elementId : String
The id
of the target HTMLElement
.
void
HTMLElement_InnerText
HTMLElement_AttachEvent
HTMLTextAreaElement_InsertTab
None.
None.
function PagePlay_Play_NewWindow( elementId /*: String*/ ) { var element /*: HTMLElement*/ = document.getElementById(elementId); if (!element) { return; } var code /*: String*/ = HTMLElement_InnerText(element); code = CodePlay_ConvertHeredoc(code); var screenWidth /*: int*/ = screen.availWidth; var screenHeight /*: int*/ = screen.availHeight; if (screenWidth) { var border /*: int*/ = 200; var height /*: int*/ = screenHeight - (border * 2); var width /*: int*/ = screenWidth - (border * 2); var left /*: int*/ = border; var top /*: int*/ = border; } else { var height /*: int*/ = 300; var width /*: int*/ = 500; var left /*: int*/ = 0; var top /*: int*/ = 0; } var win /*: Window*/ = window.open("", "", "status=yes,height=" + height + ",width=" + width + ",left=" + left + ",top=" + top + ",resizable=yes,scrollbars=yes"); if (win.document) { var doc /* HTMLDocument*/ = win.document; doc.open(); doc.write(code); doc.close(); } else { var count /*: int*/ = 10; var interval /*: HostObject*/ = window.setInterval(function() { if (!count--) { window.clearInterval(interval); return; } if (!win.document) { return; } var doc /* HTMLDocument*/ = win.document; doc.open(); doc.write(code); doc.close(); window.clearInterval(interval); },200); } }
Opens the html
document in the PagePlay
region in a new browser window.
elementId : String
The id
of the PagePlay
region.
void
CodePlay_ConvertHeredoc
HTMLElement_InnerText
The html
mark-up is pre-processed using CodePlay_ConvertHeredoc
.
The window
is written to using the classic:-
var doc /* HTMLDocument*/ = win.document;
doc.open();
doc.write(code);
doc.close();
The window starts off centered.
None
function PagePlay_Play_IFrame( elementId /*: String*/ ) { var element /*: HTMLElement*/ = document.getElementById(elementId); if (!element) { return; } var html /*: String*/ = HTMLElement_InnerText(element); html = CodePlay_ConvertHeredoc(html); var div /*: HTMLDivElement*/ = document.getElementById(elementId + "DISPLAY"); div.style.display = "block"; var iframe /*: HTMLIFrameElement*/ = document.getElementById(elementId + "IFRAME"); if (!iframe) { return; } var win /*: Window*/ = iframe.contentWindow || document.frames[elementId + "IFRAME"]; var doc /*: HTMLDocument*/ = win.document; doc.open(); doc.write(html); doc.close(); }
Opens the html
document in the PagePlay
region in a revealed iframe
beneath.
elementId : String
The id
of the PagePlay
region.
void
CodePlay_ConvertHeredoc
HTMLElement_InnerText
The html
mark-up is pre-processed using CodePlay_ConvertHeredoc
.
The iframe
is written to using the classic:-
var doc /* HTMLDocument*/ = win.document;
doc.open();
doc.write(code);
doc.close();
None
function HTMLElement_InnerText( element /*: HTMLElement*/ ) /*: String*/ { if (!element) { return ""; } if (element.nodeName.toLowerCase() == "textarea" || element.nodeName.toLowerCase() == "input") { return element.value; } var innerText /*: String*/ = ""; var childNodes /*: NodeList*/ = element.childNodes; var node /*: Node*/ = null; for (var i /*: int*/ = 0; i < childNodes.length; i++) { node = childNodes[i]; if (node.nodeType == 1) { if (node.nodeName.toLowerCase() == "textarea" || node.nodeName.toLowerCase() == "input") { innerText += node.value; } else { innerText += HTMLElement_InnerText(node); } } if (node.nodeType == 3) { innerText += node.nodeValue; } if (node.nodeType == 4) { innerText += node.nodeValue; } } return innerText; }
Gets the text content of an HTMLElement
.
element : HTMLElement
The HTMLElement
whose text content you are getting.
String
The text content of the HTMLElement
.
None
The function works by recursion; it applies itself again to each Element
it finds.
The text content is the unescaped content of any of the following which is found as a descendent Node
:-
Text
HTMLInputElement
HTMLTextAreaElement
None
function HTMLElement_AttachEvent( element /*: HTMLElement*/, eventType /*: String*/, handler /*: Function*/, capturePhase /*: Boolean*/ ) /*: void*/ { if (typeof handler != "function") { return; } capturePhase = capturePhase || false; if (typeof element.addEventListener !== "undefined") { eventType = eventType.replace(/^on/, ""); element.addEventListener(eventType, handler, capturePhase); } else if (typeof element.attachEvent !== "undefined") { if (!/^on/.test(eventType)) { eventType = "on" + eventType; } element.attachEvent(eventType, handler); } }
Attaches an event handler (or listener) to an HTMLElement
.
element : HTMLElement
The HTMLElement
to attach the event handler to.
eventType : String
The event's name such as:-
onclick
onmouseover
The on
part is optional.
handler : Function
A function to handle the event.
Remember in non-IE browsers the function is passed the Event
object as its first argument.
[capturePhase : Boolean]
For non-IE browsers, the event is triggered in the capture phase (on the way down the tree to the target element) rather than the bubble phase (on the way back up the tree from the target element).
void
None
If you are planning to detach the Function
later, then you need to keep a reference to it, because you need to supply the exact same Function
to the detach method. If you wrap the Function
in another function, then you need to keep a reference to the wrapper function.
None
function HTMLTextAreaElement_InsertTab( event /*: Event*/ ) /*: void*/ { event = event || window.event; var textarea /*: HTMLTextAreaElement*/ = event.srcElement || event.target; if (event.keyCode == 9) { if (typeof textarea.selectionStart !== "undefined") { event.stopPropagation(); event.preventDefault(); /* Collapse Insertion Point */ //textarea.setSelectionRange(textarea.selectionEnd, textarea.selectionEnd); //textarea.focus(); /* Insert Tab */ var selStart /*: int*/ = textarea.selectionStart; var selEnd /*: int*/ = textarea.selectionEnd; var currentScroll /*: int*/ = textarea.scrollTop; var contents /*: String*/ = textarea.value; textarea.value = contents.substring(0, selStart) + "\t" + contents.substring(selEnd, contents.length); textarea.scrollTop = currentScroll; textarea.setSelectionRange(selEnd + 1, selEnd + 1); textarea.focus(); //window.setTimeout(function(){ // textarea.focus(); //}, 0); return false; } else if (document.selection) { var range /*: HostObject(TextRange)*/ = document.selection.createRange(); range.collapse(); range.text = String.fromCharCode(9); range.collapse(); range.select(); event.returnValue = false; } } }
This is attached as an event handler to an HTMLTextAreaElement
.
It tries to insert a TAB
character, and overrides the default behaviour for the TAB
key. This is not recommended without warning the user.
[event : Event]
For non-IE browsers the Event
object is received as the first argument.
void
Nothing.
Internet Explorer uses its proprietary TextRange
object.
Other browsers implement simple selection properties on the HTMLTextAreaElement
. In this case it is important to remember the scroll position of the element and reset this after inserting the TAB
.
Opera inserts the TAB
but does not let you override the default behaviour of jumping to the next form
element. If that next element is off the page, then Opera scrolls to that element as well, causing the page to jump.
None
function String_ToStringLiteral( s /*: String*/ ) /*: String*/ { var escapedCharacters /*: RegExp*/ = /(\f|\n|\r|\t|'|"|\\|[\x00-\x1F]|[\x7F-\x9F])/gi; var overflow /*: int*/ = 10000; var execResult /*: Array*/; var stringLiteralBuffer /*: Array.<String>*/ = []; var previousIndex /*: int*/ = 0; var escaped /*: String*/ = ""; var charCode /*: int*/; escapedCharacters.lastIndex = 0; while(overflow-- ) { execResult /*: Array*/ = escapedCharacters.exec(s); if (!execResult) { break; } stringLiteralBuffer[stringLiteralBuffer.length] = s.substring(previousIndex, escapedCharacters.lastIndex - 1); escaped = ""; switch(execResult[0]) { case "\f": /*FORM FEED(12)*/ escaped = "\\f"; break; case "\n": /*LINE FEED(10)*/ escaped = "\\n"; break; case "\r": /*CR(13)*/ escaped = "\\r"; break; case "\t": escaped = "\\t"; break; case "\'": escaped = "\\\'"; break; case "\"": escaped = "\\\""; break; case "\\": escaped = "\\\\"; break; default: charCode = execResult[0].charCodeAt(0); if (charCode < 256) { escaped = "\\x" + LZ(charCode.toString(16), 2); } else { escaped = "\\u" + LZ(charCode.toString(16), 4); } break; } stringLiteralBuffer[stringLiteralBuffer.length] = escaped; previousIndex = escapedCharacters.lastIndex; } stringLiteralBuffer[stringLiteralBuffer.length] = s.substring(previousIndex, s.length); return stringLiteralBuffer.join(""); function LZ( s /*: String*/, overflow /*: int*/ ) /*: String*/ { var zeros /*: String*/ = "00000000000000000000"; if (s.length < overflow) { return zeros.substr(0, overflow - s.length) + s; } else { return s; } } }
Converts a String
into a literal value, escaping TAB
to \t
etc.
s : String
String to be converted.
String
The literal version.
Does not work for \b
(BACKSPACE) or \v
(VERTICAL_TAB).
Does not escape unicode ranges \u0100-\uFFFF
, because Opera does not recognise these.
Basic test:-
var s = "start \f\t\n\r\t\'\"\\\0\x81\u0101 end";
var es = String_ToStringLiteral(s);
alert(es);