├── Readme.markdown
├── bookmarklet.html
├── build.sh
├── css
├── VisualEvent.css
└── shCore.css
└── js
├── VisualEvent.js
├── VisualEvent_Loader.js
├── jquery.js
├── parsers
├── dom0.js
├── entwine.js
├── eventi.js
├── extjs.js
├── glow.js
├── jQuery.js
├── jQuery1.3.js
├── jsBase.js
├── mootools.js
├── prototype.js
└── yui2.js
└── shCore.js
/Readme.markdown:
--------------------------------------------------------------------------------
1 | # Visual Event - visually inspect Javascript events
2 |
3 | Visual Event is a Javascript bookmarklet which provides debugging information about events that have been attached to DOM elements. Visual Event shows:
4 |
5 | * Which elements have events attached to them
6 | * The type of events attached to an element
7 | * The code that will be run with the event is triggered
8 | * The source file and line number for where the attached function was defined (Webkit browsers and Opera only)
9 |
10 | In addition to being useful for debugging your own code, Visual Event can be used as an educational tool, showing how many web-sites have been authored.
11 |
12 |
13 | ## Make it go!
14 |
15 | As Visual Event is a bookmarklet, installing and running it on any web-page is extremely simple:
16 |
17 | * Open the [Visual Event bookmarklet page](http://sprymedia.co.uk/VisualEvent) and drag the "Visual Event" link to your bookmark bar
18 | * Load a web-page which uses one of the supported Javascript libraries
19 | * Click "Visual Event" in your bookmark bar
20 | * View the event handlers which are attached to the document elements.
21 |
22 | A demo of Visual Event that is automatically loaded is [available](http://sprymedia.co.uk/VisualEvent/demo). This demo uses [DataTables](http://datatables.net) to create a test page with a number of events attached to different elements.
23 |
24 |
25 | ## How it works
26 |
27 | It turns out that there is no standard method provided by the W3C recommended DOM interface to find out what event listeners are attached to a particular element. While this may appear to be an oversight, there was a proposal to include a property called [eventListenerList](http://www.w3.org/TR/2002/WD-DOM-Level-3-Events-20020208/changes.html) to the level 3 DOM specification, but was unfortunately been removed in later drafts. As such we are forced to looked at the individual Javascript libraries, which typically maintain a cache of attached events (so they can later be removed and perform other useful abstractions).
28 |
29 | As such, in order for Visual Event to show events, it must be able to parse the event information out of a Javascript library. Currently the following libraries are supported by Visual Event:
30 |
31 | * DOM 0 events
32 | * jQuery 1.2+
33 | * YUI 2
34 | * MooTools 1.2+
35 | * Prototype 1.6+
36 | * Glow
37 |
38 |
39 | ## How to get involved
40 |
41 | Any help with improvements and suggestions for Visual Event are very welcome indeed! If you hit a specific problem, then please open an issue on GitHub for the problem you are encountering, including a link to the page where the problem occurs. Forks and pull requests are also very much encouraged!
42 |
43 | One area which may be of interest to you is how to add support for additional Javascript libraries. The key thing that is needed is access to the event cache that the library uses, since without that it is impossible to determine what nodes have events and the attached code.
44 |
45 | The VisualEvent class has a static array called `VisualEvent.parsers` which is an array of functions - to add a new parser, just push your function onto this array. The function must return a Javascript array of objects with the following parameters:
46 |
47 | ```javascript
48 | [
49 | {
50 | "node": {element}, // The DOM element that has attached events
51 | "listeners": [ // Array of attached events
52 | {
53 | "type": {string}, // The event type - click, change, keyup etc
54 | "func": {string}, // The code that will handle the event, from Function.toString()
55 | "removed": {boolean}, // If the event has been removed or not (typically will be false, but used if the library doesn't remove the event from its cache)
56 | "source": {string} // Library name and version that attached the event (e.g. "jQuery 1.7")
57 | },
58 | ...
59 | ]
60 | },
61 | ...
62 | ]
63 | ```
64 |
65 | ## Building Visual Event
66 |
67 | In order to run a local copy of Visual Event you must build it, since this process does file concatenation, which brings in the library parsers to the main file. The build process will also build the JSDoc documentation and compress the Javascript files (unless made with debug).
68 |
69 | To build Visual Event, all you need is a system which will run bash scripts and enter the command `./build.sh debug` in your terminal. This will create a new directory in the "builds" directory with the correct loader and the build Visual Event directory (note the timestamp is used to help prevent caching issues for the bookmarklet, both during development and when deployed). The following is the usage for the build script:
70 |
71 | ```text
72 | Visual Event build script - usage:
73 | ./build.sh [loader-dir] [debug]
74 | loader-dir - The web-address of the build files. Note that the build
75 | directory name is automatically appended and \"http:\\\\\" is
76 | automatically prepended. For example:
77 | localhost/VisualEvent/builds - default if no option is provided
78 | sprymedia.co.uk/VisualEvent/builds
79 | debug - Debug indicator. Will not compress the Javascript
80 |
81 | Example deploy build:
82 | ./build.sh sprymedia.co.uk/VisualEvent/builds
83 |
84 | Example debug build:
85 | ./build.sh localhost/VisualEvent/builds debug
86 | ```
87 |
88 | The file `bookmarklet.html` is provided to build your own bookmarklet loader nice and easily. Typically you'll only need to modify the path for the bookmarklet. The link is updated on each keypress and you install it just as you would with any other bookmarklet :-).
89 |
90 |
91 | ## About the author
92 |
93 | Allan Jardine is a freelance web UI developer, broadcasting from Scotland and just loves creating usable and useful developer tools. If you'd like to work with Allan on a project, please do [get in touch](http://sprymedia.co.uk/contact)!
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/bookmarklet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Visual Event javascript booklet
6 |
7 |
24 |
25 |
26 |
47 |
48 |
49 |
50 |
Visual Event bookmarklet generator
51 |
52 | Visual Event is a bookmarklet which will visually show you which elements on an HTML
53 | page have Javascript events assigned to them. This page can be used to generate the
54 | bookmarklet that you want to use for testing or deployment of Visual Event.
55 |
56 |
57 | To generate a bookmarklet, modify the Javascript in the textarea below, the
58 | bookmarklet link will be updated as you type (typically you will only need to
59 | change the URL to load Visual Event) and then click and drag the
60 | Visual Event link to your bookmarklet bar.
61 |
62 |
63 |
64 | Visual Event
65 |
66 |
67 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo ""
4 | if [ "$1" = "-h" ]; then
5 | echo " Visual Event build script - usage:
6 | ./build.sh [loader-dir] [debug]
7 | loader-dir - The web-address of the build files. Note that the build
8 | directory name is automatically appended. For example:
9 | localhost/VisualEvent/builds - default if no option is provided
10 | sprymedia.co.uk/VisualEvent/builds
11 | debug - Debug indicator. Will not compress the Javascript
12 |
13 | Example deploy build:
14 | ./build.sh sprymedia.co.uk/VisualEvent/builds
15 |
16 | Example debug build:
17 | ./build.sh localhost/VisualEvent/builds debug
18 | "
19 | exit
20 | elif [ "$1" = "clean" ]; then
21 | echo "Cleaning Visual Event builds"
22 | rm -Rf $(pwd)/builds/VisualEvent*
23 | exit
24 | fi
25 |
26 | # OPTIONS
27 | SCRIPT_LOC=$1
28 | DEBUG=$2
29 |
30 | if [ "$SCRIPT_LOC" = "debug" ]; then
31 | DEBUG="debug"
32 | SCRIPT_LOC="localhost/VisualEvent/builds"
33 | fi
34 |
35 | if [ -z "$1" ]; then
36 | SCRIPT_LOC="localhost/VisualEvent/builds"
37 | fi
38 |
39 | # DEFAULTS
40 | UGLIFYJS=/Users/allan/node_modules/uglify-js/bin/uglifyjs
41 | JSDOC3=/usr/local/jsdoc/jsdoc
42 |
43 | BUILD_DIR=VisualEvent-$(date +%s)
44 | BUILD_BASE=$(pwd)/builds
45 | BUILD=$(pwd)/builds/${BUILD_DIR}
46 | BUILD_JS=${BUILD}/js
47 | BUILD_CSS=${BUILD}/css
48 | BUILD_DOCS=${BUILD}/docs
49 |
50 | JS=$(pwd)/js
51 | CSS=$(pwd)/css
52 |
53 | echo "Building VisualEvent"
54 | echo " Creating media directory ${BUILD}"
55 |
56 | mkdir -p "$BUILD"
57 |
58 |
59 | # JAVASCRIPT
60 | echo " Javascript"
61 | mkdir "$BUILD_JS"
62 |
63 | echo " Combining Javascript files"
64 | cp "$JS/VisualEvent_Loader.js" "$BUILD_BASE/VisualEvent_Loader.js"
65 | cat "$JS/jquery.js" "$JS/shCore.js" "$JS/VisualEvent.js" "$JS"/parsers/*.js > "$BUILD_JS/VisualEvent-jQuery.js"
66 | cat "$JS/shCore.js" "$JS/VisualEvent.js" "$JS"/parsers/*.js > "$BUILD_JS/VisualEvent.js"
67 |
68 | if [ "$DEBUG" != "debug" -a -e $UGLIFYJS ]; then
69 | echo " Compressing Javascript"
70 | $UGLIFYJS $BUILD_BASE/VisualEvent_Loader.js > $BUILD_BASE/VisualEvent_Loader.min.js
71 | $UGLIFYJS $BUILD_JS/VisualEvent-jQuery.js > $BUILD_JS/VisualEvent-jQuery.min.js
72 | $UGLIFYJS $BUILD_JS/VisualEvent.js > $BUILD_JS/VisualEvent.min.js
73 |
74 | mv "$BUILD_BASE/VisualEvent_Loader.min.js" "$BUILD_BASE/VisualEvent_Loader.js"
75 | mv "$BUILD_JS/VisualEvent-jQuery.min.js" "$BUILD_JS/VisualEvent-jQuery.js"
76 | mv "$BUILD_JS/VisualEvent.min.js" "$BUILD_JS/VisualEvent.js"
77 | fi
78 |
79 | sed "s#__BUILD_URL__#//${SCRIPT_LOC}/${BUILD_DIR}#g" "$BUILD_BASE/VisualEvent_Loader.js" > "$BUILD_BASE/VisualEvent_Loader.tmp.js"
80 | mv "$BUILD_BASE/VisualEvent_Loader.tmp.js" "$BUILD_BASE/VisualEvent_Loader.js"
81 |
82 |
83 | # CSS
84 | echo " Combining CSS files"
85 | mkdir "$BUILD_CSS"
86 | cat "$CSS/VisualEvent.css" "$CSS/shCore.css" > "$BUILD_CSS/VisualEvent.css"
87 |
88 |
89 | # Docs
90 | if [ -e $JSDOC3 -a "$DEBUG" != "debug" ]; then
91 | echo " Documentation"
92 | $JSDOC3 -d $BUILD_DOCS -t JSDoc-DataTables $JS/VisualEvent.js $JS/VisualEvent_Loader.js
93 | fi
94 |
95 | echo "Done :-)"
96 | echo ""
97 |
98 |
--------------------------------------------------------------------------------
/css/VisualEvent.css:
--------------------------------------------------------------------------------
1 | /**
2 | * @summary Visual Event CSS
3 | * @file VisualEvent.css
4 | * @author Allan Jardine (www.sprymedia.co.uk)
5 | * @license GPL v2
6 | * @contact www.sprymedia.co.uk/contact
7 | *
8 | * @copyright Copyright 2007-2011 Allan Jardine.
9 | *
10 | * This source file is free software, under the GPL v2 license:
11 | * http://www.gnu.org/licenses/gpl-2.0.html
12 | */
13 |
14 |
15 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
16 | * Label
17 | */
18 |
19 | #Event_Label {
20 | position: fixed;
21 | bottom: 0;
22 | left: 0;
23 | color: white !important;
24 | padding: 5px 10px 5px 5px;
25 | font-size: 11px;
26 | font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
27 | z-index: 55999;
28 | background-color: #7C94C0;
29 | z-index: 55002;
30 | }
31 |
32 | #Event_Label>span {
33 | color: white !important;
34 | }
35 |
36 | #Event_Label span.Event_LabelClose,
37 | #Event_Label span.Event_LabelHelp {
38 | display: inline-block;
39 | width: 17px;
40 | text-align: center;
41 | border: 1px solid #627ba9;
42 | background-color: #93a8cf;
43 | color: #4b6698;
44 | margin-right: 5px;
45 | cursor: pointer;
46 | *cursor: hand;
47 | }
48 |
49 | #Event_Label span.Event_LabelClose:hover,
50 | #Event_Label span.Event_LabelHelp:hover {
51 | background-color: #aebfdd;
52 | }
53 |
54 | #Event_Label span.Event_LabelHelp {
55 | margin-right: 10px;
56 | }
57 |
58 | #Event_Label span.Event_LabelBy {
59 | display: inline-block;
60 | font-size: 10px;
61 | margin-right: 10px;
62 | }
63 |
64 | #Event_Label span.Event_LabelBy a {
65 | color: #eee;
66 | }
67 |
68 | table.Event_LabelColorInfo td {
69 | text-align: center;
70 | font-size: 12px;
71 | }
72 |
73 | div.Event_LabelColour {
74 | margin: 0 auto;
75 | height: 20px;
76 | width: 20px;
77 | }
78 |
79 |
80 |
81 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
82 | * Help
83 | */
84 |
85 | #Event_Help {
86 | position: fixed;
87 | top: 0;
88 | right: 0;
89 | bottom: 0;
90 | left: 0;
91 | z-index: 55001;
92 | background-color: #ddd;
93 | color: #222;
94 | font-size: 14px;
95 | font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
96 | }
97 |
98 | #Event_Help div.Event_HelpInner {
99 | width: 800px;
100 | margin: 30px auto 0 auto;
101 | }
102 |
103 | #Event_Help h1 {
104 | font-size: 18px;
105 | border-bottom: 1px solid #ccc;
106 | color: #222;
107 | }
108 |
109 | #Event_Help p {
110 | margin: 1em 0;
111 | color: #222;
112 | }
113 |
114 | #Event_Help table {
115 | width: 90%;
116 | margin: 0 auto;
117 | }
118 |
119 | #Event_Help p.Event_HelpClose {
120 | text-align: center;
121 | margin-top: 2em;
122 | }
123 |
124 |
125 |
126 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
127 | * Lightbox
128 | */
129 |
130 | #Event_Lightbox {
131 | position: absolute;
132 | display: none;
133 | color: #111;
134 | width: 660px;
135 | z-index: 55003;
136 | text-align: left;
137 | border-radius: 10px;
138 | -moz-border-radius: 10px;
139 | -webkit-border-radius: 10px;
140 | padding: 5px;
141 | font-size: 12px;
142 | direction : ltr;
143 | background-color: #F8F8F8;
144 | border: 4px solid #7C94C0;
145 | -moz-box-shadow: 3px 3px 5px #111;
146 | -webkit-box-shadow: 3px 3px 5px #111;
147 | box-shadow: 3px 3px 5px #111;
148 | font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
149 | box-sizing: content-box;
150 | }
151 |
152 | #Event_Lightbox * {
153 | box-sizing: content-box !important;
154 | }
155 |
156 | #Event_Lightbox ul {
157 | list-style-type: none;
158 | margin: 0;
159 | padding: 0;
160 | }
161 |
162 | #Event_Lightbox ul li {
163 | display: block;
164 | width: 90px;
165 | padding: 5px;
166 | cursor: pointer;
167 | *cursor: hand;
168 | }
169 |
170 | #Event_Lightbox ul li.Event_EventSelected {
171 | background-color: #eee;
172 | }
173 |
174 | #Event_Lightbox div.Event_NodeRemove {
175 | float: right;
176 | font-size: 10px;
177 | color: #4E6CA3;
178 | cursor: pointer;
179 | *cursor: hand;
180 | }
181 |
182 | #Event_Lightbox div.Event_NodeRemove:hover {
183 | text-decoration: underline;
184 | }
185 |
186 | #Event_Lightbox #Event_Trigger {
187 | color: #4E6CA3;
188 | cursor: pointer;
189 | *cursor: hand;
190 | }
191 |
192 | #Event_Lightbox #Event_Trigger:hover {
193 | text-decoration: underline;
194 | }
195 |
196 | #Event_Lightbox div.Event_Nav {
197 | float: left;
198 | width: 100px;
199 | }
200 |
201 | #Event_Lightbox div.Event_Code {
202 | float: left;
203 | width: 550px;
204 | background-color: #eee;
205 | padding: 5px;
206 | }
207 |
208 | #Event_Lightbox div.Event_Code p {
209 | padding: 0 0 12px 0;
210 | margin: 0;
211 | }
212 |
213 | #Event_Lightbox div.Event_NodeName {
214 | padding: 5px;
215 | }
216 |
217 |
218 |
219 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
220 | * Display
221 | */
222 |
223 | #Event_Display {
224 | position: absolute;
225 | top: 0;
226 | left: 0;
227 | height: 100%;
228 | width: 100%;
229 | z-index: 55001;
230 | background-color: rgba(0, 0, 0, 0.2);
231 | }
232 |
233 |
234 | #Event_Display div.Event_info {
235 | float: left;
236 | width: 15px;
237 | height: 15px;
238 | z-index: 55002;
239 | border: transparent 2px solid;
240 | }
241 |
242 | #Event_Help div.Event_bg_blue,
243 | #Event_Display div.Event_bg_blue {
244 | background-color: rgba(0, 0, 255, 0.3);
245 | border: rgba(0, 0, 255, 0.5) 2px solid;
246 | }
247 |
248 | #Event_Display div.Event_bg_blue:hover {
249 | border: rgba(0, 0, 255, 1) 2px solid;
250 | }
251 |
252 | #Event_Help div.Event_bg_red,
253 | #Event_Display div.Event_bg_red {
254 | background-color: rgba(255, 0, 0, 0.3);
255 | border: rgba(255, 0, 0, 0.5) 2px solid;
256 | }
257 |
258 | #Event_Display div.Event_bg_red:hover {
259 | border: rgba(255, 0, 0, 1) 2px solid;
260 | }
261 |
262 | #Event_Help div.Event_bg_yellow,
263 | #Event_Display div.Event_bg_yellow {
264 | background-color: rgba(255, 204, 51, 0.3);
265 | border: rgba(245, 184, 0, 0.5) 2px solid;
266 | }
267 |
268 | #Event_Display div.Event_bg_yellow:hover {
269 | border: rgba(255, 204, 51, 1) 2px solid;
270 | }
271 |
272 | #Event_Help div.Event_bg_green,
273 | #Event_Display div.Event_bg_green {
274 | background-color: rgba(0, 167, 0, 0.3);
275 | border: rgba(0, 167, 0, 0.5) 2px solid;
276 | }
277 |
278 | #Event_Display div.Event_bg_green:hover {
279 | border: rgba(0, 167, 0, 1) 2px solid;
280 | }
281 |
282 | #Event_Help div.Event_bg_purple,
283 | #Event_Display div.Event_bg_purple {
284 | background-color: rgba(167, 0, 145, 0.3);
285 | border: rgba(167, 0, 145, 0.5) 2px solid;
286 | }
287 |
288 | #Event_Display div.Event_bg_purple:hover {
289 | border: rgba(167, 0, 145, 1) 2px solid;
290 | }
291 |
292 | #Event_Help div.Event_bg_orange,
293 | #Event_Display div.Event_bg_orange {
294 | background-color: rgba(201, 145, 35, 0.3);
295 | border: rgba(201, 145, 35, 0.5) 2px solid;
296 | }
297 |
298 | #Event_Display div.Event_bg_orange:hover {
299 | border: rgba(201, 145, 35, 1) 2px solid;
300 | }
301 |
302 | #Event_Help div.Event_bg_black,
303 | #Event_Display div.Event_bg_black {
304 | background-color: rgba(190, 190, 190, 0.3);
305 | border: rgba(190, 190, 190, 0.5) 2px solid;
306 | }
307 |
308 | #Event_Display div.Event_bg_black:hover {
309 | border: rgba(190, 190, 190, 1) 2px solid;
310 | }
311 |
--------------------------------------------------------------------------------
/css/shCore.css:
--------------------------------------------------------------------------------
1 | /**
2 | * SyntaxHighlighter
3 | * http://alexgorbatchev.com/SyntaxHighlighter
4 | *
5 | * SyntaxHighlighter is donationware. If you are using it, please donate.
6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html
7 | *
8 | * @version
9 | * 3.0.83 (July 02 2010)
10 | *
11 | * @copyright
12 | * Copyright (C) 2004-2010 Alex Gorbatchev.
13 | *
14 | * @license
15 | * Dual licensed under the MIT and GPL licenses.
16 | */
17 | .Event_syntaxHighlighter a,
18 | .Event_syntaxHighlighter div,
19 | .Event_syntaxHighlighter code,
20 | .Event_syntaxHighlighter table,
21 | .Event_syntaxHighlighter table td,
22 | .Event_syntaxHighlighter table tr,
23 | .Event_syntaxHighlighter table tbody,
24 | .Event_syntaxHighlighter table thead,
25 | .Event_syntaxHighlighter table caption,
26 | .Event_syntaxHighlighter textarea {
27 | -moz-border-radius: 0 0 0 0 !important;
28 | -webkit-border-radius: 0 0 0 0 !important;
29 | background: none !important;
30 | border: 0 !important;
31 | bottom: auto !important;
32 | float: none !important;
33 | height: auto !important;
34 | left: auto !important;
35 | line-height: 1.1em !important;
36 | margin: 0 !important;
37 | outline: 0 !important;
38 | overflow: visible !important;
39 | padding: 0 !important;
40 | position: static !important;
41 | right: auto !important;
42 | text-align: left !important;
43 | top: auto !important;
44 | vertical-align: baseline !important;
45 | width: auto !important;
46 | box-sizing: content-box !important;
47 | font-family: "Source Code Pro","Consolas","Monaco","Bitstream Vera Sans Mono","Courier New",Courier,monospace !important;
48 | font-weight: normal !important;
49 | font-style: normal !important;
50 | font-size: 1em !important;
51 | min-height: inherit !important;
52 | min-height: auto !important;
53 | }
54 |
55 | .Event_syntaxHighlighter {
56 | width: 100% !important;
57 | margin: 1em 0 1em 0 !important;
58 | position: relative !important;
59 | overflow: auto !important;
60 | font-size: 1em !important;
61 | }
62 | .Event_syntaxHighlighter.source {
63 | overflow: hidden !important;
64 | }
65 | .Event_syntaxHighlighter .bold {
66 | font-weight: bold !important;
67 | }
68 | .Event_syntaxHighlighter .italic {
69 | font-style: italic !important;
70 | }
71 | .Event_syntaxHighlighter .line {
72 | white-space: pre !important;
73 | }
74 | .Event_syntaxHighlighter table {
75 | width: 100% !important;
76 | }
77 | .Event_syntaxHighlighter table caption {
78 | text-align: left !important;
79 | padding: .5em 0 0.5em 1em !important;
80 | }
81 | .Event_syntaxHighlighter table td.code {
82 | width: 100% !important;
83 | }
84 | .Event_syntaxHighlighter table td.code .container {
85 | position: relative !important;
86 | }
87 | .Event_syntaxHighlighter table td.code .container textarea {
88 | box-sizing: border-box !important;
89 | position: absolute !important;
90 | left: 0 !important;
91 | top: 0 !important;
92 | width: 100% !important;
93 | height: 100% !important;
94 | border: none !important;
95 | background: white !important;
96 | padding-left: 1em !important;
97 | overflow: hidden !important;
98 | white-space: pre !important;
99 | }
100 | .Event_syntaxHighlighter table td.gutter .line {
101 | text-align: right !important;
102 | padding: 2px 0.5em 2px 1em !important;
103 | }
104 | .Event_syntaxHighlighter table td.code .line {
105 | padding: 2px 1em !important;
106 | }
107 | .Event_syntaxHighlighter.nogutter td.code .container textarea, .Event_syntaxHighlighter.nogutter td.code .line {
108 | padding-left: 0em !important;
109 | }
110 | .Event_syntaxHighlighter.show {
111 | display: block !important;
112 | }
113 | .Event_syntaxHighlighter.collapsed table {
114 | display: none !important;
115 | }
116 | .Event_syntaxHighlighter.collapsed .toolbar {
117 | padding: 0.1em 0.8em 0em 0.8em !important;
118 | font-size: 1em !important;
119 | position: static !important;
120 | width: auto !important;
121 | height: auto !important;
122 | }
123 | .Event_syntaxHighlighter.collapsed .toolbar span {
124 | display: inline !important;
125 | margin-right: 1em !important;
126 | }
127 | .Event_syntaxHighlighter.collapsed .toolbar span a {
128 | padding: 0 !important;
129 | display: none !important;
130 | }
131 | .Event_syntaxHighlighter.collapsed .toolbar span a.expandSource {
132 | display: inline !important;
133 | }
134 | .Event_syntaxHighlighter .toolbar {
135 | position: absolute !important;
136 | right: 1px !important;
137 | top: 1px !important;
138 | font-size: 10px !important;
139 | z-index: 10 !important;
140 | }
141 | .Event_syntaxHighlighter .toolbar span.title {
142 | display: inline !important;
143 | }
144 | .Event_syntaxHighlighter .toolbar a {
145 | display: block !important;
146 | text-align: center !important;
147 | text-decoration: none !important;
148 | padding-top: 1px !important;
149 | }
150 | .Event_syntaxHighlighter .toolbar a.expandSource {
151 | display: none !important;
152 | }
153 | .Event_syntaxHighlighter.ie {
154 | font-size: .9em !important;
155 | padding: 1px 0 1px 0 !important;
156 | }
157 | .Event_syntaxHighlighter.ie .toolbar {
158 | line-height: 8px !important;
159 | }
160 | .Event_syntaxHighlighter.ie .toolbar a {
161 | padding-top: 0px !important;
162 | }
163 | .Event_syntaxHighlighter.printing .line.alt1 .content,
164 | .Event_syntaxHighlighter.printing .line.alt2 .content,
165 | .Event_syntaxHighlighter.printing .line.highlighted .number,
166 | .Event_syntaxHighlighter.printing .line.highlighted.alt1 .content,
167 | .Event_syntaxHighlighter.printing .line.highlighted.alt2 .content {
168 | background: none !important;
169 | }
170 | .Event_syntaxHighlighter.printing .line .number {
171 | color: #bbbbbb !important;
172 | }
173 | .Event_syntaxHighlighter.printing .line .content {
174 | color: black !important;
175 | }
176 | .Event_syntaxHighlighter.printing .toolbar {
177 | display: none !important;
178 | }
179 | .Event_syntaxHighlighter.printing a {
180 | text-decoration: none !important;
181 | }
182 | .Event_syntaxHighlighter.printing .plain, .Event_syntaxHighlighter.printing .plain a {
183 | color: black !important;
184 | }
185 | .Event_syntaxHighlighter.printing .comments, .Event_syntaxHighlighter.printing .comments a {
186 | color: #008200 !important;
187 | }
188 | .Event_syntaxHighlighter.printing .string, .Event_syntaxHighlighter.printing .string a {
189 | color: blue !important;
190 | }
191 | .Event_syntaxHighlighter.printing .keyword {
192 | color: #006699 !important;
193 | font-weight: bold !important;
194 | }
195 | .Event_syntaxHighlighter.printing .preprocessor {
196 | color: gray !important;
197 | }
198 | .Event_syntaxHighlighter.printing .variable {
199 | color: #aa7700 !important;
200 | }
201 | .Event_syntaxHighlighter.printing .value {
202 | color: #009900 !important;
203 | }
204 | .Event_syntaxHighlighter.printing .functions {
205 | color: #ff1493 !important;
206 | }
207 | .Event_syntaxHighlighter.printing .constants {
208 | color: #0066cc !important;
209 | }
210 | .Event_syntaxHighlighter.printing .script {
211 | font-weight: bold !important;
212 | }
213 | .Event_syntaxHighlighter.printing .color1, .Event_syntaxHighlighter.printing .color1 a {
214 | color: gray !important;
215 | }
216 | .Event_syntaxHighlighter.printing .color2, .Event_syntaxHighlighter.printing .color2 a {
217 | color: #ff1493 !important;
218 | }
219 | .Event_syntaxHighlighter.printing .color3, .Event_syntaxHighlighter.printing .color3 a {
220 | color: red !important;
221 | }
222 | .Event_syntaxHighlighter.printing .break, .Event_syntaxHighlighter.printing .break a {
223 | color: black !important;
224 | }
225 |
226 |
227 |
228 | /**
229 | * SyntaxHighlighter
230 | * http://alexgorbatchev.com/SyntaxHighlighter
231 | *
232 | * SyntaxHighlighter is donationware. If you are using it, please donate.
233 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html
234 | *
235 | * @version
236 | * 3.0.83 (July 02 2010)
237 | *
238 | * @copyright
239 | * Copyright (C) 2004-2010 Alex Gorbatchev.
240 | *
241 | * @license
242 | * Dual licensed under the MIT and GPL licenses.
243 | */
244 | .Event_syntaxHighlighter {
245 | background-color: white !important;
246 | font-size: 13px !important;
247 | overflow: visible !important;
248 | }
249 | .Event_syntaxHighlighter .line.alt1 {
250 | background-color: white !important;
251 | }
252 | .Event_syntaxHighlighter .line.alt2 {
253 | background-color: #F8F8F8 !important;
254 | }
255 | .Event_syntaxHighlighter .line.highlighted.alt1, .Event_syntaxHighlighter .line.highlighted.alt2 {
256 | background-color: #e0e0e0 !important;
257 | }
258 | .Event_syntaxHighlighter .line.highlighted.number {
259 | color: black !important;
260 | }
261 | .Event_syntaxHighlighter table caption {
262 | color: black !important;
263 | }
264 | .Event_syntaxHighlighter .gutter {
265 | }
266 | .Event_syntaxHighlighter .gutter div {
267 | color: #5C5C5C !important;
268 | }
269 | .Event_syntaxHighlighter .gutter .line.alt1, .Event_syntaxHighlighter .gutter .line.alt2 {
270 | background-color: white !important;
271 | }
272 | .odd .Event_syntaxHighlighter .gutter .line.alt1, .odd .Event_syntaxHighlighter .gutter .line.alt2 {
273 | background-color: #F2F2F2 !important;
274 | }
275 | .Event_syntaxHighlighter .gutter .line {
276 | border-right: 3px solid #4E6CA3 !important;
277 | }
278 | .Event_syntaxHighlighter .gutter .line.highlighted {
279 | background-color: #4E6CA3 !important;
280 | color: white !important;
281 | }
282 | .Event_syntaxHighlighter.printing .line .content {
283 | border: none !important;
284 | }
285 | .Event_syntaxHighlighter.collapsed {
286 | overflow: visible !important;
287 | }
288 | .Event_syntaxHighlighter.collapsed .toolbar {
289 | color: blue !important;
290 | background: white !important;
291 | border: 1px solid #4E6CA3 !important;
292 | }
293 | .Event_syntaxHighlighter.collapsed .toolbar a {
294 | color: blue !important;
295 | }
296 | .Event_syntaxHighlighter.collapsed .toolbar a:hover {
297 | color: red !important;
298 | }
299 | .Event_syntaxHighlighter .toolbar {
300 | color: white !important;
301 | border: none !important;
302 | }
303 | .Event_syntaxHighlighter .toolbar a {
304 | font: 100%/1.45em "Lucida Grande", Verdana, Arial, Helvetica, sans-serif !important;
305 | color: white !important;
306 | background: #4E6CA3 !important;
307 | float: right !important;
308 | padding: 2px 5px !important;
309 | clear: both;
310 | }
311 | .Event_syntaxHighlighter .toolbar a:hover {
312 | color: #b7c5df !important;
313 | background: #39568b !important;
314 | }
315 | .Event_syntaxHighlighter .plain, .Event_syntaxHighlighter .plain a {
316 | color: black !important;
317 | }
318 | .Event_syntaxHighlighter .comments, .Event_syntaxHighlighter .comments a {
319 | color: #008200 !important;
320 | }
321 | .Event_syntaxHighlighter .string, .Event_syntaxHighlighter .string a {
322 | color: blue !important;
323 | }
324 | .Event_syntaxHighlighter .keyword {
325 | color: #006699 !important;
326 | }
327 | .Event_syntaxHighlighter .preprocessor {
328 | color: gray !important;
329 | }
330 | .Event_syntaxHighlighter .variable {
331 | color: #aa7700 !important;
332 | }
333 | .Event_syntaxHighlighter .value {
334 | color: #009900 !important;
335 | }
336 | .Event_syntaxHighlighter .functions {
337 | color: #ff1493 !important;
338 | }
339 | .Event_syntaxHighlighter .constants {
340 | color: #0066cc !important;
341 | }
342 | .Event_syntaxHighlighter .script {
343 | font-weight: bold !important;
344 | color: #006699 !important;
345 | background-color: none !important;
346 | }
347 | .Event_syntaxHighlighter .color1, .Event_syntaxHighlighter .color1 a {
348 | color: gray !important;
349 | }
350 | .Event_syntaxHighlighter .color2, .Event_syntaxHighlighter .color2 a {
351 | color: #ff1493 !important;
352 | }
353 | .Event_syntaxHighlighter .color3, .Event_syntaxHighlighter .color3 a {
354 | color: red !important;
355 | }
356 |
357 | .Event_syntaxHighlighter .keyword {
358 | font-weight: bold !important;
359 | }
360 |
361 | .datatables_ref:hover {
362 | text-decoration: underline;
363 | cursor: pointer;
364 | *cursor: hand;
365 | }
366 |
367 | .Event_syntaxHighlighter .dtapi {
368 | color: #069;
369 | }
370 |
371 | .Event_syntaxHighlighter .dtapi:hover {
372 | text-decoration: underline;
373 | cursor: pointer;
374 | *cursor: hand;
375 | }
376 |
377 | .Event_syntaxHighlighter table {
378 | table-layout: fixed !important;
379 | }
380 |
381 | .Event_syntaxHighlighter table td.gutter {
382 | width: 46px !important; /* enough for three digits */
383 | }
384 |
385 | .Event_syntaxHighlighter table td.code {
386 | width: auto !important;
387 | overflow: auto !important;
388 | }
389 |
390 |
--------------------------------------------------------------------------------
/js/VisualEvent.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @summary Visual Event
3 | * @description Visual Event - show Javascript events which have been attached to objects, and
4 | * the event's associated function code, visually.
5 | * @file VisualEvent_Loader.js
6 | * @author Allan Jardine (www.sprymedia.co.uk)
7 | * @license GPL v2
8 | * @contact www.sprymedia.co.uk/contact
9 | *
10 | * @copyright Copyright 2007-2013 Allan Jardine.
11 | *
12 | * This source file is free software, under the GPL v2 license:
13 | * http://www.gnu.org/licenses/gpl-2.0.html
14 | */
15 |
16 | (function(window, document, $){
17 |
18 | /*global VisualEvent,VisualEvent_Loader,VisualEvents,VisualEventSyntaxHighlighter*/
19 |
20 |
21 | /**
22 | * Visual Event will show, visually, which DOM elements on a web-page have events attached to
23 | * them, information about those events and the code accossiated with each handler for the
24 | * event. It does this by parsing through the cache of Javascript libraries (as there is no DOM
25 | * method to get the information required), thus a major part of Visual Event are the library
26 | * parsers. A result of this is that universal display of events is not possible - there must
27 | * be a parser available.
28 | *
29 | * Visual Event's display is broken into four major areas:
30 | * - Label - The information toolbar at the bottom of the window (fixed) showing Visual Event
31 | * controls (close and help), the name of the program and information about the events that have
32 | * been found on the page.
33 | *
34 | * - Help - The help view is a completely blocking layer which shows information about Visual
35 | * Event and how to use it. A single click will remove the help layer and restore the standard
36 | * Visual Event view.
37 | *
38 | * - Display - A layer which provides a background to Visual Event (thus when Visual Event is
39 | * active is it blocking to the web-page below it) and acts as a container for the boxes (DIVs)
40 | * which serve as a visual indicator that there is an event attached to the element below it
41 | * (sized to match the element with the event attached).
42 | *
43 | * - Lightbox - The event information and code display of attached events.
44 | *
45 | * Note that currently there can only be one instance of Visual Event at a time, due to
46 | * practicality (no point in showing the same thing twice, at the same time) and the use of
47 | * element IDs in the script.
48 | *
49 | * @class VisualEvent
50 | * @constructor
51 | *
52 | * @example
53 | * new VisualEvent();
54 | */
55 | window.VisualEvent = function ()
56 | {
57 | // Sanity check
58 | if ( ! this instanceof VisualEvent ) {
59 | alert( "VisualEvent warning: Must be initialised with the 'new' keyword." );
60 | return;
61 | }
62 |
63 | // Only one instance of VisualEvent at a time, in the current running mode. So if there is a
64 | // current instance, shut it down first
65 | if ( VisualEvent.instance !== null ) {
66 | VisualEvent.instance.close();
67 | }
68 | VisualEvent.instance = this;
69 |
70 |
71 | /**
72 | * Settings object containing customisable information for the class instance
73 | * @namespace
74 | */
75 | this.s = {
76 | /**
77 | * Array of objects containing information about the nodes which have been found to have
78 | * events attached to them. Each object contains the following parameters:
79 | * {element} node The DOM element in question
80 | * {array} listeners Array of objects which with details about each of the events on this node
81 | * {string} func Source of the event handler (from Function.toString())
82 | * {string} source Library name / version
83 | * {string} type Type of event (click, change, keyup etc)
84 | * {boolean} removed Flag to indicate if the event has been removed (for API)
85 | * @type array
86 | * @default null
87 | */
88 | "elements": null,
89 |
90 | /**
91 | * setTimeout reference for delayed hiding of the lightbox layer
92 | * @type int
93 | * @default null
94 | * @private
95 | */
96 | "mouseTimer": null,
97 |
98 | /**
99 | * Counter for the number of events which have been found from a JS library's cache, but
100 | * are not currently available in the document's DOM
101 | * @type int
102 | * @default null
103 | * @private
104 | */
105 | "nonDomEvents": 0,
106 |
107 | /**
108 | * Array of objects holding information about each SCRIPT tag that is found in the DOM. Each
109 | * object contains the parameters:
110 | * {string} src The URL of the script source (or inline string if no src attribute)
111 | * {string} code The code (.text) from the script
112 | * @type array
113 | * @default []
114 | * @private
115 | */
116 | "scripts": []
117 | };
118 |
119 | /**
120 | * DOM elements used by the class instance
121 | * @namespace
122 | */
123 | this.dom = {
124 | /**
125 | * Label layer - for showing that Visual Event is currently running and information and
126 | * controls, about and for this instance
127 | * @type element
128 | * @default See code
129 | */
130 | "label": $(
131 | ''+
132 | '
x'+
133 | '
?'+
134 | 'Visual Event
by Allan Jardine.'+
135 | '
events were found attached to '+
136 | '
nodes. '+
137 | '
events were attached to elements not currently in the document.'+
138 | '
')[0],
139 |
140 | /**
141 | * Display layer - background layer and container for Visual Event visual node indicators
142 | * @type element
143 | * @default See code
144 | */
145 | "display": $('')[0],
146 |
147 | /**
148 | * Lightbox layer - Template for information display about a given node, and the code for
149 | * a given event handler
150 | * @type element
151 | * @default See code
152 | */
153 | "lightbox": $(
154 | ''+
155 | '
Node:
'+
156 | '
Remove from display
'+
157 | '
'+
158 | '
'+
163 | '
'+
164 | '
')[0],
165 |
166 | /**
167 | * Help layer - information about Visual Event and how to use it
168 | * @type element
169 | * @default See code
170 | */
171 | "help": $(
172 | ''+
173 | '
'+
174 | '
Visual Event help
'+
175 | '
Visual Event will show which elements on any given page have Javascript events attached '+
176 | 'to them, what those events are and the code associated with the events. In Webkit '+
177 | 'browsers and Opera, Visual Event will also indicate where the code in question was '+
178 | 'defined.
'+
179 | '
Note that Visual Event is only able to show events for Javascript libraries for which '+
180 | 'it has a parser. This is currently: DOM0 events, Glow, jQuery, MooTools, Prototype and YUI2.
'+
181 | '
Commands:
'+
182 | '
'+
183 | ''+
184 | 'Double click element with event | '+
185 | 'Hide event indicator. Allows access to nodes underneath | '+
186 | '
'+
187 | ''+
188 | 'Key: space | '+
189 | 'Restore all events to be visible | '+
190 | '
'+
191 | ''+
192 | 'Key: esc | '+
193 | 'Quit out of Visual Event | '+
194 | '
'+
195 | ''+
196 | 'Key: h | '+
197 | 'Show / hide this help box | '+
198 | '
'+
199 | ''+
200 | 'Key: r | '+
201 | 'Reload and display events on page | '+
202 | '
'+
203 | '
'+
204 | '
The colour of the elements that have been detected to have an event reflect the type of '+
205 | 'events that are attached to the element:
'+
206 | '
'+
207 | ''+
208 | ' | '+
209 | ' | '+
210 | ' | '+
211 | ' | '+
212 | ' | '+
213 | ' | '+
214 | ' | '+
215 | '
'+
216 | ''+
217 | 'Mouse event | '+
218 | 'UI event | '+
219 | 'HTML event | '+
220 | 'Mouse + HTML | '+
221 | 'Mouse + UI | '+
222 | 'HTML + UI | '+
223 | 'Mouse + HTML + UI | '+
224 | '
'+
225 | '
'+
226 | '
Visual Event is open source software (GPLv2). If you would like to contribute an '+
227 | 'enhancement, please fork the project on '+
228 | 'Github!
'+
229 | '
Click anywhere to close this help box.
'+
230 | '
'+
231 | '
')[0],
232 |
233 |
234 | /**
235 | * Reference to the visual event node indicator - so we have a reference to what node we are
236 | * showing the lightbox information about
237 | * @type element
238 | * @default See code
239 | */
240 | "activeEventNode": null
241 | };
242 |
243 | this._construct();
244 | };
245 |
246 |
247 | VisualEvent.prototype = {
248 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
249 | * API methods
250 | */
251 |
252 | /**
253 | * Shutdown Visual Event and return to the original page
254 | * @param {event} e Event object
255 | */
256 | "close": function ( e )
257 | {
258 | // Remove all events that we've added
259 | $('*').unbind('.VisualEvent');
260 | $(document).unbind( 'keydown.VisualEvent' );
261 |
262 | $(this.dom.display).remove();
263 | $(this.dom.lightbox).remove();
264 | $(this.dom.label).remove();
265 | $(this.dom.help).remove();
266 |
267 | if ( typeof VisualEvent_Loader !== 'undefined' && !VisualEvent_Loader.jQueryPreLoaded ) {
268 | $.noConflict();
269 | }
270 |
271 | VisualEvent.instance = null;
272 | },
273 |
274 |
275 | /**
276 | * Reinitialise Visual Event (i.e. bring it up-to-date with any new events which might have
277 | * been added
278 | */
279 | "reInit": function ()
280 | {
281 | $('*').unbind('.VisualEvent');
282 | $(document).unbind( 'keydown.VisualEvent' );
283 |
284 | $(this.dom.display).empty();
285 | $(this.dom.display).remove();
286 | $(this.dom.lightbox).remove();
287 | $(this.dom.label).remove();
288 | $(this.dom.help).remove();
289 |
290 | this.s.elements.splice(0, this.s.elements.length);
291 | this.s.nonDomEvents = 0;
292 |
293 | this._construct();
294 | },
295 |
296 |
297 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
298 | * Private methods
299 | */
300 |
301 | /**
302 | * Visual Event constructor
303 | * @private
304 | */
305 | "_construct": function ()
306 | {
307 | var that = this;
308 | var i, iLen;
309 | var windowHeight = $(document).height();
310 | var windowWidth = $(document).width();
311 |
312 | /* Prep the DOM */
313 | this.dom.display.style.width = windowWidth+'px';
314 | this.dom.display.style.height = windowHeight+'px';
315 |
316 | document.body.appendChild( this.dom.display );
317 | document.body.appendChild( this.dom.lightbox );
318 | document.body.appendChild( this.dom.label );
319 |
320 | /* Event handlers */
321 | $(this.dom.lightbox).bind('mouseover.VisualEvent', function (e) {
322 | that._timerClear( e );
323 | } ).bind( 'mousemove.VisualEvent', function (e) {
324 | that._timerClear( e );
325 | } ).bind( 'mouseout.VisualEvent', function (e) {
326 | that._lightboxHide();
327 | } );
328 |
329 | $('div.Event_NodeRemove', this.dom.lightbox).bind('click.VisualEvent', function (e) {
330 | that.dom.activeEventNode.style.display = "none";
331 | that.dom.lightbox.style.display = "none";
332 | } );
333 |
334 | $(document).bind( 'keydown.VisualEvent', function( e ) {
335 | if ( e.which === 0 || e.which === 27 ) { // esc
336 | that.close();
337 | }
338 | if ( e.which === 72 ) { // 'h'
339 | if ( $(that.dom.help).filter(':visible').length === 0 ) {
340 | that._help();
341 | }
342 | else {
343 | that._hideHelp();
344 | }
345 | }
346 | else if ( e.which === 32 ) { // space
347 | $('div.EventLabel').css('display', 'block');
348 | e.preventDefault();
349 | }
350 | else if ( e.which === 82 ) { // r
351 | that.reInit();
352 | }
353 | } );
354 |
355 | /* Build the events list and display */
356 | this.s.elements = this._eventsLoad();
357 | for ( i=0, iLen=this.s.elements.length ; i 0 ) {
404 | return;
405 | }
406 |
407 | var loadQueue = [];
408 | var scripts = document.getElementsByTagName('script');
409 | for ( var i=0, iLen=scripts.length ; i'+this._scriptName(srcFiles[0].src)+':'+ srcFiles[0].line + "
";
498 | } else {
499 | origin += srcFiles[0].src+"
";
500 | }
501 | } else {
502 | origin = "Function could originate in multiple locations:
";
503 | for ( i=0, iLen=srcFiles.length ; i'+this._scriptName(srcFiles[i].src)+':'+srcFiles[i].line+'
';
506 | }
507 | }
508 |
509 | return origin;
510 | },
511 |
512 |
513 | /**
514 | * Get the name of a file from a URL (i.e. the last part in a slash seperated string)
515 | * @param {string} src URL to get the file name from
516 | * @returns {string} File name
517 | * @private
518 | */
519 | "_scriptName": function ( src )
520 | {
521 | var a = src.split('/');
522 | return a[ a.length-1 ];
523 | },
524 |
525 |
526 |
527 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
528 | * Display
529 | */
530 |
531 | /**
532 | * Build the list of nodes that have events attached to them by going through all installed
533 | * parsers
534 | * @returns {array} List of nodes with their associated events
535 | * @private
536 | */
537 | "_eventsLoad": function ()
538 | {
539 | var i, iLen;
540 | var elements=[], libraryListeners;
541 |
542 | /* Gather the events from the supported libraries */
543 | for ( i=0, iLen=VisualEvent.parsers.length ; i'+listeners[i].type+'').bind( 'mouseover.VisualEvent',
642 | this._lightboxCode(e, node, listeners[i]) )
643 | );
644 | }
645 |
646 | /* Show the code for the first event in the list */
647 | $('li:eq(0)', this.dom.lightbox).mouseover();
648 |
649 | this._lightboxPosition( this.dom.lightbox, node );
650 | },
651 |
652 |
653 | /**
654 | * Create a function which will build the HTML needed for the display of the code for an
655 | * event handler
656 | * @param {event} e Original mouse event that triggered the lightbox to be shown
657 | * @param {element} node The node with the attached listeners
658 | * @param {object} listener Listener attached to the element
659 | * @returns {function} Function which will display the code for the event when called
660 | * @private
661 | */
662 | "_lightboxCode": function ( e, node, listener )
663 | {
664 | var that = this;
665 |
666 | return function () {
667 | $('li', this.parentNode).removeClass( 'Event_EventSelected' );
668 | $(this).addClass( 'Event_EventSelected' );
669 |
670 | var evt = that._createEvent( e, listener.type, e.target );
671 | that._renderCode( e, listener.func, listener.source, listener.type,
672 | evt===null ? null : function() {
673 | node.dispatchEvent(evt);
674 |
675 | // Might cause stuff to move around by the activation of the event, so re-init
676 | setTimeout( function () {
677 | that.reInit.call(that);
678 | }, 200 );
679 | }
680 | );
681 | };
682 | },
683 |
684 |
685 | /**
686 | * Position the lightbox relative to the element which has an event attached to it
687 | * @param {element} target The lightbox node to move (note there is only one this.dom.lightbox
688 | * but this keeps it nice and generic!)
689 | * @param {element} parent The element with the event attached to it
690 | * @private
691 | */
692 | "_lightboxPosition": function ( target, parent )
693 | {
694 | var offset = $(parent).offset();
695 | var targetT = offset.top + 15; // magic number - height of info button
696 | var targetL = offset.left;
697 | var viewportW = $(window).width() - 25; // use window rather than document, since the target could cause the document to resize
698 | var viewportH = $(document).height();
699 | var targetW = $(target).width();
700 | var targetH = $(target).height();
701 |
702 | // Correct for over-run
703 | if ( targetL + targetW > viewportW ) {
704 | targetL -= (targetL + targetW) - viewportW;
705 | }
706 |
707 | if ( targetT + targetH > viewportH ) {
708 | targetH -= (targetT + targetH) - viewportH;
709 | }
710 |
711 | // Correct for under-run
712 | if ( targetT < 0 ) {
713 | targetT = 0;
714 | }
715 |
716 | if ( targetL < 0 ) {
717 | targetL = 0;
718 | }
719 |
720 | target.style.top = targetT+'px';
721 | target.style.left = targetL+'px';
722 | target.style.display = 'block';
723 | },
724 |
725 |
726 | /**
727 | * Close the lightbox - use a cancellable timer for the hiding of the lightbox, so we can move
728 | * the mouse from element to element without having it flicker.
729 | * @private
730 | */
731 | "_lightboxHide": function ()
732 | {
733 | var that = this;
734 | this.s.mouseTimer = setTimeout( function () {
735 | that.dom.lightbox.style.display = 'none';
736 | },
737 | 200 );
738 | },
739 |
740 |
741 |
742 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
743 | * Rendering methods
744 | */
745 |
746 | /**
747 | * Display a tooltip with event information for a particular event handler
748 | * @param {event} e Target node information
749 | * @param {function} func The function string
750 | * @param {string} type Event type
751 | * @param {function|null} trigger Function to trigger the event
752 | * @private
753 | */
754 | "_renderCode": function( e, func, source, type, trigger )
755 | {
756 | var that = this;
757 | var eventElement = e.target;
758 | var i, iLen;
759 |
760 | this._timerClear( e );
761 |
762 | if ( trigger === null ) {
763 | $('div.Event_Code', this.dom.lightbox).html( ''+type+
764 | ' event subscribed by '+source+'
'+
765 | this._scriptSource( func )+
766 | '
' );
767 | }
768 | else {
769 | $('div.Event_Code', this.dom.lightbox).html( ''+type+
770 | ' event subscribed by '+source+' ('+
771 | 'trigger event)
'+
772 | this._scriptSource( func )+
773 | '
' );
774 | $('#Event_Trigger').bind( 'click.VisualEvent', trigger );
775 | }
776 |
777 | /* Modify the function slightly such that the white space that is found at the start of the
778 | * last line in the function is also put at the start of the first line. This allows
779 | * SyntaxHighlighter to be cunning and remove the block white space - otherwise it is all
780 | * shifted to the left, other than the first line
781 | */
782 | var lines = func.split('\n');
783 | if ( lines.length > 1 ) {
784 | var last = lines[lines.length-1].match(/^(\s*)/g);
785 | lines[0] = last + lines[0];
786 | func = lines.join('\n');
787 | }
788 |
789 | /* Inject the function string here incase it includes a '' string */
790 | $('pre', this.dom.lightbox).html(
791 | func.replace(/&/g, '&').replace(//g, '>')
792 | );
793 |
794 | VisualEventSyntaxHighlighter.highlight( null, document.getElementById('Event_code') );
795 | },
796 |
797 |
798 | /**
799 | * Show information about a particular node - the node name, ID and class (if it has either/both
800 | * of the last two)
801 | * @param {element} node The element to inspect
802 | * @returns {string} Information about the element
803 | * @private
804 | */
805 | "_renderNodeInfo": function ( node )
806 | {
807 | var s = node.nodeName.toLowerCase();
808 |
809 | var id = node.getAttribute('id');
810 | if ( id && id !== '' ) {
811 | s += '#'+id;
812 | }
813 |
814 | var className = node.className;
815 | if ( className !== '' ) {
816 | s += '.'+className;
817 | }
818 |
819 | return s;
820 | },
821 |
822 |
823 | /**
824 | * Display the Visual Event toolbar, writing in the required information and adding the event
825 | * handlers as needed
826 | * @private
827 | */
828 | "_renderLabel": function ()
829 | {
830 | var that = this,
831 | events = 0, i, iLen;
832 |
833 | for (i=0, iLen=this.s.elements.length ; i=' or '>' - logic operation to
1206 | * perform
1207 | * @param {string} v2 Version 2 string
1208 | * @returns {boolean} true if condition is correct, false otherwise
1209 | */
1210 | VisualEvent.versionCompare = function ( v1, operator, v2 ) {
1211 | var a1 = v1.split('.');
1212 | var a2 = v2.split('.');
1213 | var i1, i2;
1214 | var test = 0;
1215 |
1216 | for ( var i=0, iLen=a2.length ; i i2 ) {
1226 | test = 1;
1227 | break;
1228 | }
1229 | }
1230 |
1231 | if ( operator === '<' ) {
1232 | return test === -1;
1233 | }
1234 | else if ( operator === '<=' ) {
1235 | return test === -1 || test === 0;
1236 | }
1237 | else if ( operator === '==' ) {
1238 | return test === 0;
1239 | }
1240 | else if ( operator === '>=' ) {
1241 | return test === 0 || test === 1;
1242 | }
1243 | else if ( operator === '>' ) {
1244 | return test === 1;
1245 | }
1246 | throw 'Unknown operator: '+operator;
1247 | };
1248 |
1249 |
1250 | })(window, document, jQuery);
1251 |
--------------------------------------------------------------------------------
/js/VisualEvent_Loader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @summary VisualEvent_Loader
3 | * @description Loader for VisualEvent - injects the required CSS and Javascript into a page
4 | * @file VisualEvent_Loader.js
5 | * @author Allan Jardine (www.sprymedia.co.uk)
6 | * @license GPL v2
7 | * @contact www.sprymedia.co.uk/contact
8 | *
9 | * @copyright Copyright 2007-2013 Allan Jardine.
10 | *
11 | * This source file is free software, under the GPL v2 license:
12 | * http://www.gnu.org/licenses/gpl-2.0.html
13 | */
14 |
15 | (function(window, document){
16 |
17 | /*global VisualEvent,VisualEvent_Loader*/
18 |
19 | if ( typeof VisualEvent_Loader == 'undefined' ) {
20 |
21 | /**
22 | * VisualEvent_Loader is a class which will provide pre-loading of Javascript and CSS files
23 | * for VisualEvent based on the environment the script is running in (for example if jQuery is
24 | * already available or not).
25 | *
26 | * @class VisualEvent_Loader
27 | * @constructor
28 | *
29 | * @example
30 | * new VisualEvent_Loader();
31 | */
32 | window.VisualEvent_Loader = function ()
33 | {
34 | /* Sanity check */
35 | if ( ! this instanceof VisualEvent_Loader ) {
36 | alert( "VisualEvent loader warning: Must be initialised with the 'new' keyword." );
37 | return;
38 | }
39 |
40 | /**
41 | * Settings object containing the settings information for the instance
42 | * @namespace
43 | */
44 | this.s = {
45 | /**
46 | * Flag to indicate if loading has finished or not. False until the required JS classes are
47 | * found to be available.
48 | * @type boolean
49 | * @default false
50 | */
51 | "loadingComplete": false
52 | };
53 |
54 | /**
55 | * DOM elements used by the instance
56 | * @namespace
57 | */
58 | this.dom = {
59 | /**
60 | * Visual Label to show the end user that Visual Event is being loaded
61 | * @type element
62 | * @default div
63 | */
64 | "loading": document.createElement('div')
65 | };
66 |
67 | this._construct();
68 | };
69 |
70 |
71 | VisualEvent_Loader.prototype = {
72 | /**
73 | * Constrctor - show a loading element to the end user and then load up the various files
74 | * that are needed
75 | * @returns {undefined}
76 | * @private
77 | */
78 | "_construct": function ()
79 | {
80 | var that = this,
81 | loading,
82 | style,
83 | protocol = window.location.protocol === 'file:' ?
84 | 'http:' : '';
85 |
86 | /* Check to see if already loaded */
87 | if ( this.s.loadingComplete === true ) {
88 | return 0;
89 | }
90 |
91 | /* Show a label to the user to let them know that Visual Event is currently loading */
92 | loading = this.dom.loading;
93 | loading.setAttribute( 'id', 'EventLoading' );
94 | loading.appendChild( document.createTextNode( 'Loading Visual Event...' ) );
95 |
96 | style = loading.style;
97 | style.position = 'fixed';
98 | style.bottom = '0';
99 | style.left = '0';
100 | style.color = 'white';
101 | style.padding = '5px 10px';
102 | style.fontSize = '11px';
103 | style.fontFamily = '"Lucida Grande", Verdana, Arial, Helvetica, sans-serif';
104 | style.zIndex = '55999';
105 | style.backgroundColor = '#93a8cf';
106 | document.body.insertBefore( loading, document.body.childNodes[0] );
107 |
108 | /* Store a static flag to let the VisualEvent instance know if jQuery was already available on
109 | * the page or not - used in the "close" method
110 | */
111 | VisualEvent_Loader.jQueryPreLoaded = (typeof jQuery == 'undefined') ? false : true;
112 |
113 | /* Start the polling for ready */
114 | if ( typeof VisualEvent == 'object' ) {
115 | this._pollReady();
116 | return; // Don't need to load any files if its already loaded
117 | }
118 | else {
119 | setTimeout( function () {
120 | that._pollReady();
121 | }, 1000 );
122 | }
123 |
124 | /* Load the required files - note that the token __BUILD_URL__ is replaced by the build
125 | * script with the location of the combined Visual Event file (i.e. with the parsers included
126 | */
127 | this._loadFile( protocol+'__BUILD_URL__/css/VisualEvent.css', 'css' );
128 | if ( typeof jQuery == 'undefined' ) {
129 | this._loadFile( protocol+'__BUILD_URL__/js/VisualEvent-jQuery.js', 'js' );
130 | }
131 | else {
132 | this._loadFile( protocol+'__BUILD_URL__/js/VisualEvent.js', 'js' );
133 | }
134 | },
135 |
136 |
137 | /**
138 | * Load a new file into the DOM, and have it processed based on its type. This can be a
139 | * Javascript file, a CSS file or an image
140 | * @param {string} file URL to the file to load
141 | * @param {string} type The file type. Can be "css", "js" or "image"
142 | * @returns {undefined}
143 | * @private
144 | */
145 | "_loadFile": function ( file, type )
146 | {
147 | var n, img;
148 |
149 | if ( type == 'css' ) {
150 | n = document.createElement('link');
151 | n.type = 'text/css';
152 | n.rel = 'stylesheet';
153 | n.href = file;
154 | n.media = 'screen';
155 | document.getElementsByTagName('head')[0].appendChild( n );
156 | }
157 | else if ( type == 'image' ) {
158 | img = new Image( 1, 1 );
159 | img.src = file;
160 | }
161 | else {
162 | n = document.createElement( 'script' );
163 | n.setAttribute( 'language', 'JavaScript' );
164 | n.setAttribute( 'src', file );
165 | n.setAttribute( 'charset', 'utf8' );
166 | document.body.appendChild( n );
167 | }
168 | },
169 |
170 |
171 | /**
172 | * Check if VisualEvent components (specifically VisualEvent itself and the SyntaxHighlighter)
173 | * have been loaded and are available. If not, wait a little while and try again.
174 | * @returns {undefined}
175 | * @private
176 | */
177 | "_pollReady": function ()
178 | {
179 | var that = this,
180 | tmp;
181 |
182 | if ( typeof VisualEvent == 'function' &&
183 | typeof VisualEventSyntaxHighlighter == 'object' )
184 | {
185 | this._complete();
186 | }
187 | else {
188 | setTimeout( function() {
189 | that._pollReady();
190 | }, 100 );
191 | }
192 | },
193 |
194 |
195 | /**
196 | * Loading is complete, initialise VisualEvent
197 | * @returns {undefined}
198 | * @private
199 | */
200 | "_complete": function ()
201 | {
202 | var that = this;
203 |
204 | this.s.loadingComplete = true;
205 |
206 | tmp = new VisualEvent(); // jsLint need to assign it to a var
207 |
208 | /* Tidy up our display */
209 | document.body.removeChild( this.dom.loading );
210 | }
211 | };
212 |
213 |
214 | VisualEvent_Loader.jQueryPreLoaded = false;
215 |
216 | } /* /typeof VisualEvent_Loader */
217 |
218 |
219 | /*
220 | * If visual event is already defined then we can toggle the display - giving the effect of
221 | * starting it up and shutting it down when using the loader. Note it's preferable to do this in
222 | * the bookmarklet code (and is now - but is it for backwards compatability)
223 | */
224 | var tmp;
225 | if ( typeof VisualEvent != 'undefined' )
226 | {
227 | if ( VisualEvent.instance !== null ) {
228 | VisualEvent.close();
229 | }
230 | else {
231 | tmp = new VisualEvent();
232 | }
233 | }
234 | else {
235 | tmp = new VisualEvent_Loader();
236 | }
237 |
238 |
239 | })(window, document);
240 |
--------------------------------------------------------------------------------
/js/parsers/dom0.js:
--------------------------------------------------------------------------------
1 |
2 | (function(window, document, $, VisualEvent){
3 |
4 | VisualEvent.parsers.push( function () {
5 | var
6 | elements = [], n,
7 | all = document.getElementsByTagName('*'),
8 | types = [ 'click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover',
9 | 'mouseup', 'change', 'focus', 'blur', 'scroll', 'select', 'submit', 'keydown', 'keypress',
10 | 'keyup', 'load', 'unload' ],
11 | i, iLen, j, jLen = types.length;
12 |
13 | for ( i=0, iLen=all.length ; i
4 | */
5 | /* global jQuery, VisualEvent */
6 | "use strict";
7 |
8 | (function(window, document, $, VE) {
9 |
10 | function entwineParser() {
11 | if (!jQuery || !jQuery.fn.entwine) {
12 | return [];
13 | }
14 |
15 | var out = [];
16 |
17 | for(var namespace in jQuery.entwine.namespaces) {
18 | if (!jQuery.entwine.namespaces.hasOwnProperty(namespace)) {
19 | continue;
20 | }
21 |
22 | var store = jQuery.entwine.namespaces[namespace].store;
23 |
24 | for(var key in store) {
25 |
26 | if (!store.hasOwnProperty(key)) {
27 | continue;
28 | }
29 |
30 | // only look for events, entwine allows other functions too.
31 | if (!key.match(/^on/)) {
32 | continue;
33 | }
34 |
35 | var eventName = key.replace(/^on/, '');
36 |
37 | for(var i=0; i < store[key].length; i++) {
38 | var binding = store[key][i];
39 |
40 | if (typeof binding !== 'object' || !binding.selector) {
41 | continue;
42 | }
43 |
44 | var nodes = $(binding.selector.selector);
45 |
46 | for (var j = 0; j < nodes.length; j++) {
47 | out.push({
48 | node: nodes[j],
49 | listeners: [{
50 | type: eventName,
51 | func: binding.func.toString(),
52 | removed: false,
53 | source: "jquery.entwine"
54 | }]
55 | });
56 | } // end node loop
57 | } // end store[key] contents loop
58 | } // end store keys loop
59 | }
60 | return out;
61 | }
62 |
63 | VE.parsers.push(entwineParser);
64 | })(window, document, jQuery, VisualEvent);
65 |
--------------------------------------------------------------------------------
/js/parsers/eventi.js:
--------------------------------------------------------------------------------
1 | (function(window, document, VisualEvent){
2 |
3 | function eventsFor(node) {
4 | var listener = node[Eventi._._key];
5 | if (listener) {
6 | var record = {
7 | node: node,
8 | listeners: []
9 | },
10 | cache = listener.s;
11 | for (var type in cache) {
12 | var handlers = cache[type];
13 | for (var i=0,m=handlers.length; i 0) {
25 | for (var k=0; k 0) {
37 | elements.push( {
38 | "node": cache.el.dom,
39 | "listeners": listeners
40 | } );
41 | }
42 | }
43 | }
44 | }
45 |
46 | return elements;
47 | } );
48 |
49 | })(window, document, jQuery, VisualEvent);
50 |
--------------------------------------------------------------------------------
/js/parsers/glow.js:
--------------------------------------------------------------------------------
1 |
2 | (function(window, document, $, VisualEvent){
3 |
4 | /*global glow*/
5 |
6 | VisualEvent.parsers.push( function () {
7 | if ( typeof glow == 'undefined' || typeof glow.events.listenersByObjId == 'undefined' ) {
8 | return [];
9 | }
10 |
11 | var listeners = glow.events.listenersByObjId;
12 | var globalGlow = "__eventId"+glow.UID;
13 | var elements = [];
14 | var all = document.getElementsByTagName('*');
15 | var i, iLen, j, jLen;
16 | var eventIndex, eventType, typeEvents;
17 |
18 | for ( i=0, iLen=all.length ; i=', '1.7' ) )
12 | {
13 | return [];
14 | }
15 |
16 | var elements = [];
17 | for ( var j in jQuery.cache ) {
18 | jQueryGenericLoop( elements, jQuery.cache[j] );
19 | }
20 |
21 | return elements;
22 | });
23 |
24 |
25 | // jQuery 1.4, 1.7
26 | VE.parsers.push( function () {
27 | if ( ! jQuery ) {
28 | return [];
29 | }
30 |
31 | if (
32 | ( VE.versionCompare( jQuery.fn.jquery, '>=', '1.4' ) && VE.versionCompare( jQuery.fn.jquery, '<', '1.5' ) ) ||
33 | ( VE.versionCompare( jQuery.fn.jquery, '>=', '1.7' ) && VE.versionCompare( jQuery.fn.jquery, '<', '1.8' ) ) )
34 | {
35 | var elements = [];
36 | jQueryGenericLoop( elements, jQuery.cache );
37 | return elements;
38 | }
39 |
40 | return [];
41 | });
42 |
43 |
44 | // jQuery 1.8+
45 | VE.parsers.push( function () {
46 | if ( ! jQuery || VE.versionCompare( jQuery.fn.jquery, '<', '1.8' ) ) {
47 | return [];
48 | }
49 |
50 | var elements = [];
51 |
52 | // Get all 'live' (on) events
53 | $(document).each(function(index1, element) {
54 | jQueryGeneric(elements, element, element);
55 | });
56 |
57 | // Get events on nodes
58 | $('*').each(function(index1, element) {
59 | jQueryGeneric(elements, element, element);
60 | });
61 |
62 | return elements;
63 | });
64 |
65 |
66 | function jQueryGenericLoop (elements, cache) {
67 | $.each( cache, function ( key, val ) {
68 | if ( val.handle ) {
69 | jQueryGeneric(elements, val, val.handle.elem);
70 | }
71 | } );
72 | }
73 |
74 | function jQueryGeneric (elements, eventsObject, node) {
75 | if ( typeof eventsObject == 'object' ) {
76 | var events;
77 |
78 | if (typeof eventsObject.events == 'object') {
79 | events = eventsObject.events;
80 | }
81 |
82 | if ( ! events ) {
83 | events = $._data(eventsObject, 'events');
84 | }
85 |
86 | var func;
87 |
88 | for ( var type in events ) {
89 | if ( Object.hasOwnProperty.bind( events )( type ) ) {
90 | /* Ignore live event object - live events are listed as normal events as well */
91 | if ( type == 'live' ) {
92 | continue;
93 | }
94 |
95 | var oEvents = events[type];
96 |
97 | for ( var j in oEvents ) {
98 | if ( oEvents.hasOwnProperty( j ) ) {
99 | var aNodes = [];
100 | var sjQuery = "jQuery " + jQuery.fn.jquery;
101 |
102 | if ( typeof oEvents[j].selector != 'undefined' && oEvents[j].selector !== null ) {
103 | aNodes = $(oEvents[j].selector, node);
104 | sjQuery += " (live event)";
105 | }
106 | else {
107 | aNodes.push( node );
108 | }
109 |
110 | for ( var k=0, kLen=aNodes.length ; k', '1.3' ) ) {
9 | return [];
10 | }
11 |
12 | var elements = [];
13 | var cache = jQuery.cache;
14 |
15 | for ( var i in cache ) {
16 | if ( typeof cache[i].events == 'object' ) {
17 | var nEventNode = cache[i].handle.elem;
18 |
19 | elements.push( {
20 | "node": nEventNode,
21 | "listeners": []
22 | } );
23 |
24 | for ( var type in cache[i].events )
25 | {
26 | var oEvent = cache[i].events[type];
27 | var iFunctionIndex;
28 | for ( iFunctionIndex in oEvent) {
29 | break;
30 | }
31 |
32 | /* We use jQuery for the Visual Event events... don't really want to display them */
33 | var func = oEvent[ iFunctionIndex ].toString();
34 | if ( !func.match(/VisualEvent/) && !func.match(/EventLoader/) )
35 | {
36 | elements[ elements.length-1 ].listeners.push( {
37 | "type": type,
38 | "func": func,
39 | "removed": false,
40 | "source": 'jQuery'
41 | } );
42 | }
43 | }
44 | }
45 | }
46 |
47 | return elements;
48 | } );
49 |
50 |
51 | // jQuery 1.3 live events
52 | VE.parsers.push( function () {
53 | if ( !jQuery || jQuery.fn.live != 'undefined' ||
54 | typeof jQuery.data == 'undefined' ||
55 | typeof jQuery.data(document, "events") == 'undefined' ||
56 | typeof jQuery.data(document, "events").live == 'undefined' )
57 | {
58 | return [];
59 | }
60 |
61 | var elements = [];
62 | var cache = jQuery.cache;
63 |
64 | jQuery.each( jQuery.data(document, "events").live || [], function(i, fn) {
65 | var event = fn.type.split('.');
66 | event = event[0];
67 | var selector = fn.data;
68 |
69 | $(selector).each( function(i) {
70 | elements.push( {
71 | node: this,
72 | listeners: [],
73 | } );
74 |
75 | elements[elements.length - 1].listeners.push({
76 | type: event,
77 | func: 'Unable to obtain function from live() bound event.',
78 | removed: false,
79 | source: "jQuery 1.3 live"
80 | } );
81 | } );
82 | } );
83 |
84 | return elements;
85 | } );
86 |
87 | })(window, document, jQuery, VisualEvent);
88 |
--------------------------------------------------------------------------------
/js/parsers/jsBase.js:
--------------------------------------------------------------------------------
1 |
2 | (function(window, document, $, VisualEvent){
3 |
4 | /*global jsBase*/
5 |
6 | VisualEvent.parsers.push( function () {
7 | if ( typeof jsBase == 'undefined' ) {
8 | return [];
9 | }
10 |
11 | var elements = [];
12 | var a = jsBase.aEventCache;
13 | var i, iLen;
14 |
15 | for ( i=0, iLen=a.length ; i MIT License
5 | */
6 | var XRegExp;XRegExp=XRegExp||function(n){"use strict";function v(n,i,r){var u;for(u in t.prototype)t.prototype.hasOwnProperty(u)&&(n[u]=t.prototype[u]);return n.xregexp={captureNames:i,isNative:!!r},n}function g(n){return(n.global?"g":"")+(n.ignoreCase?"i":"")+(n.multiline?"m":"")+(n.extended?"x":"")+(n.sticky?"y":"")}function o(n,r,u){if(!t.isRegExp(n))throw new TypeError("type RegExp expected");var f=i.replace.call(g(n)+(r||""),h,"");return u&&(f=i.replace.call(f,new RegExp("["+u+"]+","g"),"")),n=n.xregexp&&!n.xregexp.isNative?v(t(n.source,f),n.xregexp.captureNames?n.xregexp.captureNames.slice(0):null):v(new RegExp(n.source,f),null,!0)}function a(n,t){var i=n.length;if(Array.prototype.lastIndexOf)return n.lastIndexOf(t);while(i--)if(n[i]===t)return i;return-1}function s(n,t){return Object.prototype.toString.call(n).toLowerCase()==="[object "+t+"]"}function d(n){return n=n||{},n==="all"||n.all?n={natives:!0,extensibility:!0}:s(n,"string")&&(n=t.forEach(n,/[^\s,]+/,function(n){this[n]=!0},{})),n}function ut(n,t,i,u){var o=p.length,s=null,e,f;y=!0;try{while(o--)if(f=p[o],(f.scope==="all"||f.scope===i)&&(!f.trigger||f.trigger.call(u))&&(f.pattern.lastIndex=t,e=r.exec.call(f.pattern,n),e&&e.index===t)){s={output:f.handler.call(u,e,i),match:e};break}}catch(h){throw h;}finally{y=!1}return s}function b(n){t.addToken=c[n?"on":"off"],f.extensibility=n}function tt(n){RegExp.prototype.exec=(n?r:i).exec,RegExp.prototype.test=(n?r:i).test,String.prototype.match=(n?r:i).match,String.prototype.replace=(n?r:i).replace,String.prototype.split=(n?r:i).split,f.natives=n}var t,c,u,f={natives:!1,extensibility:!1},i={exec:RegExp.prototype.exec,test:RegExp.prototype.test,match:String.prototype.match,replace:String.prototype.replace,split:String.prototype.split},r={},k={},p=[],e="default",rt="class",it={"default":/^(?:\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\d*|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S])|\(\?[:=!]|[?*+]\?|{\d+(?:,\d*)?}\??)/,"class":/^(?:\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S]))/},et=/\$(?:{([\w$]+)}|(\d\d?|[\s\S]))/g,h=/([\s\S])(?=[\s\S]*\1)/g,nt=/^(?:[?*+]|{\d+(?:,\d*)?})\??/,ft=i.exec.call(/()??/,"")[1]===n,l=RegExp.prototype.sticky!==n,y=!1,w="gim"+(l?"y":"");return t=function(r,u){if(t.isRegExp(r)){if(u!==n)throw new TypeError("can't supply flags when constructing one RegExp from another");return o(r)}if(y)throw new Error("can't call the XRegExp constructor within token definition functions");var l=[],a=e,b={hasNamedCapture:!1,captureNames:[],hasFlag:function(n){return u.indexOf(n)>-1}},f=0,c,s,p;if(r=r===n?"":String(r),u=u===n?"":String(u),i.match.call(u,h))throw new SyntaxError("invalid duplicate regular expression flag");for(r=i.replace.call(r,/^\(\?([\w$]+)\)/,function(n,t){if(i.test.call(/[gy]/,t))throw new SyntaxError("can't use flag g or y in mode modifier");return u=i.replace.call(u+t,h,""),""}),t.forEach(u,/[\s\S]/,function(n){if(w.indexOf(n[0])<0)throw new SyntaxError("invalid regular expression flag "+n[0]);});f"}else if(i)return"\\"+(+i+f);return n},e=[],r,u;if(!(s(n,"array")&&n.length))throw new TypeError("patterns must be a nonempty array");for(u=0;u1&&a(r,"")>-1&&(e=new RegExp(this.source,i.replace.call(g(this),"g","")),i.replace.call(String(t).slice(r.index),e,function(){for(var t=1;tr.index&&(this.lastIndex=r.index)}return this.global||(this.lastIndex=o),r},r.test=function(n){return!!r.exec.call(this,n)},r.match=function(n){if(t.isRegExp(n)){if(n.global){var u=i.match.apply(this,arguments);return n.lastIndex=0,u}}else n=new RegExp(n);return r.exec.call(n,this)},r.replace=function(n,r){var e=t.isRegExp(n),u,f,h,o;return e?(n.xregexp&&(u=n.xregexp.captureNames),n.global||(o=n.lastIndex)):n+="",s(r,"function")?f=i.replace.call(String(this),n,function(){var t=arguments,i;if(u)for(t[0]=new String(t[0]),i=0;in.length-3)throw new SyntaxError("backreference to undefined group "+t);return n[r]||""}throw new SyntaxError("invalid token "+t);})})),e&&(n.lastIndex=n.global?0:o),f},r.split=function(r,u){if(!t.isRegExp(r))return i.split.apply(this,arguments);var e=String(this),h=r.lastIndex,f=[],o=0,s;return u=(u===n?-1:u)>>>0,t.forEach(e,r,function(n){n.index+n[0].length>o&&(f.push(e.slice(o,n.index)),n.length>1&&n.indexu?f.slice(0,u):f},u=c.on,u(/\\([ABCE-RTUVXYZaeg-mopqyz]|c(?![A-Za-z])|u(?![\dA-Fa-f]{4})|x(?![\dA-Fa-f]{2}))/,function(n,t){if(n[1]==="B"&&t===e)return n[0];throw new SyntaxError("invalid escape "+n[0]);},{scope:"all"}),u(/\[(\^?)]/,function(n){return n[1]?"[\\s\\S]":"\\b\\B"}),u(/(?:\(\?#[^)]*\))+/,function(n){return i.test.call(nt,n.input.slice(n.index+n[0].length))?"":"(?:)"}),u(/\\k<([\w$]+)>/,function(n){var t=isNaN(n[1])?a(this.captureNames,n[1])+1:+n[1],i=n.index+n[0].length;if(!t||t>this.captureNames.length)throw new SyntaxError("backreference to undefined group "+n[0]);return"\\"+t+(i===n.input.length||isNaN(n.input.charAt(i))?"":"(?:)")}),u(/(?:\s+|#.*)+/,function(n){return i.test.call(nt,n.input.slice(n.index+n[0].length))?"":"(?:)"},{trigger:function(){return this.hasFlag("x")},customFlags:"x"}),u(/\./,function(){return"[\\s\\S]"},{trigger:function(){return this.hasFlag("s")},customFlags:"s"}),u(/\(\?P?<([\w$]+)>/,function(n){if(!isNaN(n[1]))throw new SyntaxError("can't use integer as capture name "+n[0]);return this.captureNames.push(n[1]),this.hasNamedCapture=!0,"("}),u(/\\(\d+)/,function(n,t){if(!(t===e&&/^[1-9]/.test(n[1])&&+n[1]<=this.captureNames.length)&&n[1]!=="0")throw new SyntaxError("can't use octal escape or backreference to undefined group "+n[0]);return n[0]},{scope:"all"}),u(/\((?!\?)/,function(){return this.hasFlag("n")?"(?:":(this.captureNames.push(null),"(")},{customFlags:"n"}),typeof exports!="undefined"&&(exports.XRegExp=t),t}()
7 |
8 |
9 | /*!
10 | * SyntaxHighlighter by Alex Gorbatchev
11 | * https://github.com/alexgorbatchev/SyntaxHighlighter - MIT license
12 | */
13 |
14 | //
15 | // Begin anonymous function. This is used to contain local scope variables without polutting global scope.
16 | //
17 | if (typeof(VisualEventSyntaxHighlighter) == 'undefined') var VisualEventSyntaxHighlighter = function() {
18 |
19 | // CommonJS
20 | if (typeof(require) != 'undefined' && typeof(XRegExp) == 'undefined')
21 | {
22 | XRegExp = require('xregexp').XRegExp;
23 | }
24 |
25 | // Shortcut object which will be assigned to the SyntaxHighlighter variable.
26 | // This is a shorthand for local reference in order to avoid long namespace
27 | // references to SyntaxHighlighter.whatever...
28 | var sh = {
29 | defaults : {
30 | /** Additional CSS class names to be added to highlighter elements. */
31 | 'class-name' : '',
32 |
33 | /** First line number. */
34 | 'first-line' : 1,
35 |
36 | /**
37 | * Pads line numbers. Possible values are:
38 | *
39 | * false - don't pad line numbers.
40 | * true - automaticaly pad numbers with minimum required number of leading zeroes.
41 | * [int] - length up to which pad line numbers.
42 | */
43 | 'pad-line-numbers' : false,
44 |
45 | /** Lines to highlight. */
46 | 'highlight' : null,
47 |
48 | /** Title to be displayed above the code block. */
49 | 'title' : null,
50 |
51 | /** Enables or disables smart tabs. */
52 | 'smart-tabs' : true,
53 |
54 | /** Gets or sets tab size. */
55 | 'tab-size' : 4,
56 |
57 | /** Enables or disables gutter. */
58 | 'gutter' : true,
59 |
60 | /** Enables or disables toolbar. */
61 | 'toolbar' : true,
62 |
63 | /** Enables quick code copy and paste from double click. */
64 | 'quick-code' : true,
65 |
66 | /** Forces code view to be collapsed. */
67 | 'collapse' : false,
68 |
69 | /** Enables or disables automatic links. */
70 | 'auto-links' : true,
71 |
72 | /** Gets or sets light mode. Equavalent to turning off gutter and toolbar. */
73 | 'light' : false,
74 |
75 | 'unindent' : true,
76 |
77 | 'html-script' : false
78 | },
79 |
80 | config : {
81 | space : ' ',
82 |
83 | /** Enables use of tags. */
84 | useScriptTags : true,
85 |
86 | /** Blogger mode flag. */
87 | bloggerMode : false,
88 |
89 | stripBrs : false,
90 |
91 | /** Name of the tag that SyntaxHighlighter will automatically look for. */
92 | tagName : 'pre',
93 |
94 | strings : {
95 | expandSource : 'expand source',
96 | help : '?',
97 | alert: 'VisualEventSyntaxHighlighter\n\n',
98 | noBrush : 'Can\'t find brush for: ',
99 | brushNotHtmlScript : 'Brush wasn\'t configured for html-script option: ',
100 |
101 | // this is populated by the build script
102 | aboutDialog : '<%- about %>'
103 | }
104 | },
105 |
106 | /** Internal 'global' variables. */
107 | vars : {
108 | discoveredBrushes : null,
109 | highlighters : {}
110 | },
111 |
112 | /** This object is populated by user included external brush files. */
113 | brushes : {},
114 |
115 | /** Common regular expressions. */
116 | regexLib : {
117 | multiLineCComments : XRegExp('/\\*.*?\\*/', 'gs'),
118 | singleLineCComments : /\/\/.*$/gm,
119 | singleLinePerlComments : /#.*$/gm,
120 | doubleQuotedString : /"([^\\"\n]|\\.)*"/g,
121 | singleQuotedString : /'([^\\'\n]|\\.)*'/g,
122 | multiLineDoubleQuotedString : XRegExp('"([^\\\\"]|\\\\.)*"', 'gs'),
123 | multiLineSingleQuotedString : XRegExp("'([^\\\\']|\\\\.)*'", 'gs'),
124 | xmlComments : XRegExp('(<|<)!--.*?--(>|>)', 'gs'),
125 | url : /\w+:\/\/[\w-.\/?%&=:@;#]*/g,
126 | phpScriptTags : { left: /(<|<)\?(?:=|php)?/g, right: /\?(>|>)/g, 'eof' : true },
127 | aspScriptTags : { left: /(<|<)%=?/g, right: /%(>|>)/g },
128 | scriptScriptTags : { left: /(<|<)\s*script.*?(>|>)/gi, right: /(<|<)\/\s*script\s*(>|>)/gi }
129 | },
130 |
131 | toolbar: {
132 | /**
133 | * Generates HTML markup for the toolbar.
134 | * @param {Highlighter} highlighter Highlighter instance.
135 | * @return {String} Returns HTML markup.
136 | */
137 | getHtml: function(highlighter)
138 | {
139 | var html = '',
140 | items = sh.toolbar.items,
141 | list = items.list
142 | ;
143 |
144 | function defaultGetHtml(highlighter, name)
145 | {
146 | return sh.toolbar.getButtonHtml(highlighter, name, sh.config.strings[name]);
147 | }
148 |
149 | for (var i = 0, l = list.length; i < l; i++)
150 | {
151 | html += (items[list[i]].getHtml || defaultGetHtml)(highlighter, list[i]);
152 | }
153 |
154 | html += '
';
155 |
156 | return html;
157 | },
158 |
159 | /**
160 | * Generates HTML markup for a regular button in the toolbar.
161 | * @param {Highlighter} highlighter Highlighter instance.
162 | * @param {String} commandName Command name that would be executed.
163 | * @param {String} label Label text to display.
164 | * @return {String} Returns HTML markup.
165 | */
166 | getButtonHtml: function(highlighter, commandName, label)
167 | {
168 | return '' + label + ''
172 | ;
173 | },
174 |
175 | /**
176 | * Event handler for a toolbar anchor.
177 | */
178 | handler: function(e)
179 | {
180 | var target = e.target,
181 | className = target.className || ''
182 | ;
183 |
184 | function getValue(name)
185 | {
186 | var r = new RegExp(name + '_(\\w+)'),
187 | match = r.exec(className)
188 | ;
189 |
190 | return match ? match[1] : null;
191 | }
192 |
193 | var highlighter = getHighlighterById(findParentElement(target, '.Event_syntaxHighlighter').id),
194 | commandName = getValue('command')
195 | ;
196 |
197 | // execute the toolbar command
198 | if (highlighter && commandName)
199 | sh.toolbar.items[commandName].execute(highlighter);
200 |
201 | // disable default A click behaviour
202 | e.preventDefault();
203 | },
204 |
205 | /** Collection of toolbar items. */
206 | items : {
207 | // Ordered lis of items in the toolbar. Can't expect `for (var n in items)` to be consistent.
208 | list: ['expandSource', 'help'],
209 |
210 | expandSource: {
211 | getHtml: function(highlighter)
212 | {
213 | if (highlighter.getParam('collapse') != true)
214 | return '';
215 |
216 | var title = highlighter.getParam('title');
217 | return sh.toolbar.getButtonHtml(highlighter, 'expandSource', title ? title : sh.config.strings.expandSource);
218 | },
219 |
220 | execute: function(highlighter)
221 | {
222 | var div = getHighlighterDivById(highlighter.id);
223 | removeClass(div, 'collapsed');
224 | }
225 | },
226 |
227 | /** Command to display the about dialog window. */
228 | help: {
229 | execute: function(highlighter)
230 | {
231 | var wnd = popup('', '_blank', 500, 250, 'scrollbars=0'),
232 | doc = wnd.document
233 | ;
234 |
235 | doc.write(sh.config.strings.aboutDialog);
236 | doc.close();
237 | wnd.focus();
238 | }
239 | }
240 | }
241 | },
242 |
243 | /**
244 | * Finds all elements on the page which should be processes by SyntaxHighlighter.
245 | *
246 | * @param {Object} globalParams Optional parameters which override element's
247 | * parameters. Only used if element is specified.
248 | *
249 | * @param {Object} element Optional element to highlight. If none is
250 | * provided, all elements in the current document
251 | * are returned which qualify.
252 | *
253 | * @return {Array} Returns list of { target: DOMElement, params: Object }
objects.
254 | */
255 | findElements: function(globalParams, element)
256 | {
257 | var elements = element ? [element] : toArray(document.getElementsByTagName(sh.config.tagName)),
258 | conf = sh.config,
259 | result = []
260 | ;
261 |
262 | // support for feature
263 | if (conf.useScriptTags)
264 | elements = elements.concat(getSyntaxHighlighterScriptTags());
265 |
266 | if (elements.length === 0)
267 | return result;
268 |
269 | for (var i = 0, l = elements.length; i < l; i++)
270 | {
271 | var item = {
272 | target: elements[i],
273 | // local params take precedence over globals
274 | params: merge(globalParams, parseParams(elements[i].className))
275 | };
276 |
277 | if (item.params['brush'] == null)
278 | continue;
279 |
280 | result.push(item);
281 | }
282 |
283 | return result;
284 | },
285 |
286 | /**
287 | * Shorthand to highlight all elements on the page that are marked as
288 | * SyntaxHighlighter source code.
289 | *
290 | * @param {Object} globalParams Optional parameters which override element's
291 | * parameters. Only used if element is specified.
292 | *
293 | * @param {Object} element Optional element to highlight. If none is
294 | * provided, all elements in the current document
295 | * are highlighted.
296 | */
297 | highlight: function(globalParams, element)
298 | {
299 | var elements = this.findElements(globalParams, element),
300 | propertyName = 'innerHTML',
301 | highlighter = null,
302 | conf = sh.config
303 | ;
304 |
305 | if (elements.length === 0)
306 | return;
307 |
308 | for (var i = 0, l = elements.length; i < l; i++)
309 | {
310 | var element = elements[i],
311 | target = element.target,
312 | params = element.params,
313 | brushName = params.brush,
314 | code
315 | ;
316 |
317 | if (brushName == null)
318 | continue;
319 |
320 | // Instantiate a brush
321 | if (params['html-script'] == 'true' || sh.defaults['html-script'] == true)
322 | {
323 | highlighter = new sh.HtmlScript(brushName);
324 | brushName = 'htmlscript';
325 | }
326 | else
327 | {
328 | var brush = findBrush(brushName);
329 |
330 | if (brush)
331 | highlighter = new brush();
332 | else
333 | continue;
334 | }
335 |
336 | code = target[propertyName];
337 |
338 | // remove CDATA from tags if it's present
339 | if (conf.useScriptTags)
340 | code = stripCData(code);
341 |
342 | // Inject title if the attribute is present
343 | if ((target.title || '') != '')
344 | params.title = target.title;
345 |
346 | params['brush'] = brushName;
347 | highlighter.init(params);
348 | element = highlighter.getDiv(code);
349 |
350 | // carry over ID
351 | if ((target.id || '') != '')
352 | element.id = target.id;
353 |
354 | target.parentNode.replaceChild(element, target);
355 | }
356 | },
357 |
358 | /**
359 | * Main entry point for the SyntaxHighlighter.
360 | * @param {Object} params Optional params to apply to all highlighted elements.
361 | */
362 | all: function(params)
363 | {
364 | attachEvent(
365 | window,
366 | 'load',
367 | function() { sh.highlight(params); }
368 | );
369 | }
370 | }; // end of sh
371 |
372 | /**
373 | * Checks if target DOM elements has specified CSS class.
374 | * @param {DOMElement} target Target DOM element to check.
375 | * @param {String} className Name of the CSS class to check for.
376 | * @return {Boolean} Returns true if class name is present, false otherwise.
377 | */
378 | function hasClass(target, className)
379 | {
380 | return target.className.indexOf(className) != -1;
381 | };
382 |
383 | /**
384 | * Adds CSS class name to the target DOM element.
385 | * @param {DOMElement} target Target DOM element.
386 | * @param {String} className New CSS class to add.
387 | */
388 | function addClass(target, className)
389 | {
390 | if (!hasClass(target, className))
391 | target.className += ' ' + className;
392 | };
393 |
394 | /**
395 | * Removes CSS class name from the target DOM element.
396 | * @param {DOMElement} target Target DOM element.
397 | * @param {String} className CSS class to remove.
398 | */
399 | function removeClass(target, className)
400 | {
401 | target.className = target.className.replace(className, '');
402 | };
403 |
404 | /**
405 | * Converts the source to array object. Mostly used for function arguments and
406 | * lists returned by getElementsByTagName() which aren't Array objects.
407 | * @param {List} source Source list.
408 | * @return {Array} Returns array.
409 | */
410 | function toArray(source)
411 | {
412 | var result = [];
413 |
414 | for (var i = 0, l = source.length; i < l; i++)
415 | result.push(source[i]);
416 |
417 | return result;
418 | };
419 |
420 | /**
421 | * Splits block of text into lines.
422 | * @param {String} block Block of text.
423 | * @return {Array} Returns array of lines.
424 | */
425 | function splitLines(block)
426 | {
427 | return block.split(/\r?\n/);
428 | }
429 |
430 | /**
431 | * Generates HTML ID for the highlighter.
432 | * @param {String} highlighterId Highlighter ID.
433 | * @return {String} Returns HTML ID.
434 | */
435 | function getHighlighterId(id)
436 | {
437 | var prefix = 'highlighter_';
438 | return id.indexOf(prefix) == 0 ? id : prefix + id;
439 | };
440 |
441 | /**
442 | * Finds Highlighter instance by ID.
443 | * @param {String} highlighterId Highlighter ID.
444 | * @return {Highlighter} Returns instance of the highlighter.
445 | */
446 | function getHighlighterById(id)
447 | {
448 | return sh.vars.highlighters[getHighlighterId(id)];
449 | };
450 |
451 | /**
452 | * Finds highlighter's DIV container.
453 | * @param {String} highlighterId Highlighter ID.
454 | * @return {Element} Returns highlighter's DIV element.
455 | */
456 | function getHighlighterDivById(id)
457 | {
458 | return document.getElementById(getHighlighterId(id));
459 | };
460 |
461 | /**
462 | * Stores highlighter so that getHighlighterById() can do its thing. Each
463 | * highlighter must call this method to preserve itself.
464 | * @param {Highilghter} highlighter Highlighter instance.
465 | */
466 | function storeHighlighter(highlighter)
467 | {
468 | sh.vars.highlighters[getHighlighterId(highlighter.id)] = highlighter;
469 | };
470 |
471 | /**
472 | * Looks for a child or parent node which has specified classname.
473 | * Equivalent to jQuery's $(container).find(".className")
474 | * @param {Element} target Target element.
475 | * @param {String} search Class name or node name to look for.
476 | * @param {Boolean} reverse If set to true, will go up the node tree instead of down.
477 | * @return {Element} Returns found child or parent element on null.
478 | */
479 | function findElement(target, search, reverse /* optional */)
480 | {
481 | if (target == null)
482 | return null;
483 |
484 | var nodes = reverse != true ? target.childNodes : [ target.parentNode ],
485 | propertyToFind = { '#' : 'id', '.' : 'className' }[search.substr(0, 1)] || 'nodeName',
486 | expectedValue,
487 | found
488 | ;
489 |
490 | expectedValue = propertyToFind != 'nodeName'
491 | ? search.substr(1)
492 | : search.toUpperCase()
493 | ;
494 |
495 | // main return of the found node
496 | if ((target[propertyToFind] || '').indexOf(expectedValue) != -1)
497 | return target;
498 |
499 | for (var i = 0, l = nodes.length; nodes && i < l && found == null; i++)
500 | found = findElement(nodes[i], search, reverse);
501 |
502 | return found;
503 | };
504 |
505 | /**
506 | * Looks for a parent node which has specified classname.
507 | * This is an alias to findElement(container, className, true)
.
508 | * @param {Element} target Target element.
509 | * @param {String} className Class name to look for.
510 | * @return {Element} Returns found parent element on null.
511 | */
512 | function findParentElement(target, className)
513 | {
514 | return findElement(target, className, true);
515 | };
516 |
517 | /**
518 | * Finds an index of element in the array.
519 | * @ignore
520 | * @param {Object} searchElement
521 | * @param {Number} fromIndex
522 | * @return {Number} Returns index of element if found; -1 otherwise.
523 | */
524 | function indexOf(array, searchElement, fromIndex)
525 | {
526 | fromIndex = Math.max(fromIndex || 0, 0);
527 |
528 | for (var i = fromIndex, l = array.length; i < l; i++)
529 | if(array[i] == searchElement)
530 | return i;
531 |
532 | return -1;
533 | };
534 |
535 | /**
536 | * Generates a unique element ID.
537 | */
538 | function guid(prefix)
539 | {
540 | return (prefix || '') + Math.round(Math.random() * 1000000).toString();
541 | };
542 |
543 | /**
544 | * Merges two objects. Values from obj2 override values in obj1.
545 | * Function is NOT recursive and works only for one dimensional objects.
546 | * @param {Object} obj1 First object.
547 | * @param {Object} obj2 Second object.
548 | * @return {Object} Returns combination of both objects.
549 | */
550 | function merge(obj1, obj2)
551 | {
552 | var result = {}, name;
553 |
554 | for (name in obj1)
555 | result[name] = obj1[name];
556 |
557 | for (name in obj2)
558 | result[name] = obj2[name];
559 |
560 | return result;
561 | };
562 |
563 | /**
564 | * Attempts to convert string to boolean.
565 | * @param {String} value Input string.
566 | * @return {Boolean} Returns true if input was "true", false if input was "false" and value otherwise.
567 | */
568 | function toBoolean(value)
569 | {
570 | var result = { "true" : true, "false" : false }[value];
571 | return result == null ? value : result;
572 | };
573 |
574 | /**
575 | * Opens up a centered popup window.
576 | * @param {String} url URL to open in the window.
577 | * @param {String} name Popup name.
578 | * @param {int} width Popup width.
579 | * @param {int} height Popup height.
580 | * @param {String} options window.open() options.
581 | * @return {Window} Returns window instance.
582 | */
583 | function popup(url, name, width, height, options)
584 | {
585 | var x = (screen.width - width) / 2,
586 | y = (screen.height - height) / 2
587 | ;
588 |
589 | options += ', left=' + x +
590 | ', top=' + y +
591 | ', width=' + width +
592 | ', height=' + height
593 | ;
594 | options = options.replace(/^,/, '');
595 |
596 | var win = window.open(url, name, options);
597 | win.focus();
598 | return win;
599 | };
600 |
601 | /**
602 | * Adds event handler to the target object.
603 | * @param {Object} obj Target object.
604 | * @param {String} type Name of the event.
605 | * @param {Function} func Handling function.
606 | */
607 | function attachEvent(obj, type, func, scope)
608 | {
609 | function handler(e)
610 | {
611 | e = e || window.event;
612 |
613 | if (!e.target)
614 | {
615 | e.target = e.srcElement;
616 | e.preventDefault = function()
617 | {
618 | this.returnValue = false;
619 | };
620 | }
621 |
622 | func.call(scope || window, e);
623 | };
624 |
625 | if (obj.attachEvent)
626 | {
627 | obj.attachEvent('on' + type, handler);
628 | }
629 | else
630 | {
631 | obj.addEventListener(type, handler, false);
632 | }
633 | };
634 |
635 | /**
636 | * Displays an alert.
637 | * @param {String} str String to display.
638 | */
639 | function alert(str)
640 | {
641 | window.alert(sh.config.strings.alert + str);
642 | };
643 |
644 | /**
645 | * Finds a brush by its alias.
646 | *
647 | * @param {String} alias Brush alias.
648 | * @param {Boolean} showAlert Suppresses the alert if false.
649 | * @return {Brush} Returns bursh constructor if found, null otherwise.
650 | */
651 | function findBrush(alias, showAlert)
652 | {
653 | var brushes = sh.vars.discoveredBrushes,
654 | result = null
655 | ;
656 |
657 | if (brushes == null)
658 | {
659 | brushes = {};
660 |
661 | // Find all brushes
662 | for (var brush in sh.brushes)
663 | {
664 | var info = sh.brushes[brush],
665 | aliases = info.aliases
666 | ;
667 |
668 | if (aliases == null)
669 | continue;
670 |
671 | // keep the brush name
672 | info.brushName = brush.toLowerCase();
673 |
674 | for (var i = 0, l = aliases.length; i < l; i++)
675 | brushes[aliases[i]] = brush;
676 | }
677 |
678 | sh.vars.discoveredBrushes = brushes;
679 | }
680 |
681 | result = sh.brushes[brushes[alias]];
682 |
683 | if (result == null && showAlert)
684 | alert(sh.config.strings.noBrush + alias);
685 |
686 | return result;
687 | };
688 |
689 | /**
690 | * Executes a callback on each line and replaces each line with result from the callback.
691 | * @param {Object} str Input string.
692 | * @param {Object} callback Callback function taking one string argument and returning a string.
693 | */
694 | function eachLine(str, callback)
695 | {
696 | var lines = splitLines(str);
697 |
698 | for (var i = 0, l = lines.length; i < l; i++)
699 | lines[i] = callback(lines[i], i);
700 |
701 | // include \r to enable copy-paste on windows (ie8) without getting everything on one line
702 | return lines.join('\r\n');
703 | };
704 |
705 | /**
706 | * This is a special trim which only removes first and last empty lines
707 | * and doesn't affect valid leading space on the first line.
708 | *
709 | * @param {String} str Input string
710 | * @return {String} Returns string without empty first and last lines.
711 | */
712 | function trimFirstAndLastLines(str)
713 | {
714 | return str.replace(/^[ ]*[\n]+|[\n]*[ ]*$/g, '');
715 | };
716 |
717 | /**
718 | * Parses key/value pairs into hash object.
719 | *
720 | * Understands the following formats:
721 | * - name: word;
722 | * - name: [word, word];
723 | * - name: "string";
724 | * - name: 'string';
725 | *
726 | * For example:
727 | * name1: value; name2: [value, value]; name3: 'value'
728 | *
729 | * @param {String} str Input string.
730 | * @return {Object} Returns deserialized object.
731 | */
732 | function parseParams(str)
733 | {
734 | var match,
735 | result = {},
736 | arrayRegex = XRegExp("^\\[(?(.*?))\\]$"),
737 | pos = 0,
738 | regex = XRegExp(
739 | "(?[\\w-]+)" +
740 | "\\s*:\\s*" +
741 | "(?" +
742 | "[\\w%#-]+|" + // word
743 | "\\[.*?\\]|" + // [] array
744 | '".*?"|' + // "" string
745 | "'.*?'" + // '' string
746 | ")\\s*;?",
747 | "g"
748 | )
749 | ;
750 |
751 | while ((match = XRegExp.exec(str, regex, pos)) != null)
752 | {
753 | var value = match.value
754 | .replace(/^['"]|['"]$/g, '') // strip quotes from end of strings
755 | ;
756 |
757 | // try to parse array value
758 | if (value != null && arrayRegex.test(value))
759 | {
760 | var m = XRegExp.exec(value, arrayRegex);
761 | value = m.values.length > 0 ? m.values.split(/\s*,\s*/) : [];
762 | }
763 |
764 | result[match.name] = value;
765 | pos = match.index + match[0].length;
766 | }
767 |
768 | // AJJ - markdown style language option
769 | var a = str.match(/language-(.*)/);
770 | if ( a ) {
771 | result['brush'] = a[1];
772 | }
773 |
774 | return result;
775 | };
776 |
777 | /**
778 | * Wraps each line of the string into
tag with given style applied to it.
779 | *
780 | * @param {String} str Input string.
781 | * @param {String} css Style name to apply to the string.
782 | * @return {String} Returns input string with each line surrounded by tag.
783 | */
784 | function wrapLinesWithCode(str, css)
785 | {
786 | if (str == null || str.length == 0 || str == '\n')
787 | return str;
788 |
789 | str = str.replace(/... to them so that
803 | // leading spaces aren't included.
804 | if (css != null)
805 | str = eachLine(str, function(line)
806 | {
807 | if (line.length == 0)
808 | return '';
809 |
810 | var spaces = '';
811 |
812 | line = line.replace(/^( | )+/, function(s)
813 | {
814 | spaces = s;
815 | return '';
816 | });
817 |
818 | if (line.length == 0)
819 | return spaces;
820 |
821 | return spaces + '' + line + '
';
822 | });
823 |
824 | return str;
825 | };
826 |
827 | /**
828 | * Pads number with zeros until it's length is the same as given length.
829 | *
830 | * @param {Number} number Number to pad.
831 | * @param {Number} length Max string length with.
832 | * @return {String} Returns a string padded with proper amount of '0'.
833 | */
834 | function padNumber(number, length)
835 | {
836 | var result = number.toString();
837 |
838 | while (result.length < length)
839 | result = '0' + result;
840 |
841 | return result;
842 | };
843 |
844 | /**
845 | * Replaces tabs with spaces.
846 | *
847 | * @param {String} code Source code.
848 | * @param {Number} tabSize Size of the tab.
849 | * @return {String} Returns code with all tabs replaces by spaces.
850 | */
851 | function processTabs(code, tabSize)
852 | {
853 | var tab = '';
854 |
855 | for (var i = 0; i < tabSize; i++)
856 | tab += ' ';
857 |
858 | return code.replace(/\t/g, tab);
859 | };
860 |
861 | /**
862 | * Replaces tabs with smart spaces.
863 | *
864 | * @param {String} code Code to fix the tabs in.
865 | * @param {Number} tabSize Number of spaces in a column.
866 | * @return {String} Returns code with all tabs replaces with roper amount of spaces.
867 | */
868 | function processSmartTabs(code, tabSize)
869 | {
870 | var lines = splitLines(code),
871 | tab = '\t',
872 | spaces = ''
873 | ;
874 |
875 | // Create a string with 1000 spaces to copy spaces from...
876 | // It's assumed that there would be no indentation longer than that.
877 | for (var i = 0; i < 50; i++)
878 | spaces += ' '; // 20 spaces * 50
879 |
880 | // This function inserts specified amount of spaces in the string
881 | // where a tab is while removing that given tab.
882 | function insertSpaces(line, pos, count)
883 | {
884 | return line.substr(0, pos)
885 | + spaces.substr(0, count)
886 | + line.substr(pos + 1, line.length) // pos + 1 will get rid of the tab
887 | ;
888 | };
889 |
890 | // Go through all the lines and do the 'smart tabs' magic.
891 | code = eachLine(code, function(line)
892 | {
893 | if (line.indexOf(tab) == -1)
894 | return line;
895 |
896 | var pos = 0;
897 |
898 | while ((pos = line.indexOf(tab)) != -1)
899 | {
900 | // This is pretty much all there is to the 'smart tabs' logic.
901 | // Based on the position within the line and size of a tab,
902 | // calculate the amount of spaces we need to insert.
903 | var spaces = tabSize - pos % tabSize;
904 | line = insertSpaces(line, pos, spaces);
905 | }
906 |
907 | return line;
908 | });
909 |
910 | return code;
911 | };
912 |
913 | /**
914 | * Performs various string fixes based on configuration.
915 | */
916 | function fixInputString(str)
917 | {
918 | var br = /
|<br\s*\/?>/gi;
919 |
920 | if (sh.config.bloggerMode == true)
921 | str = str.replace(br, '\n');
922 |
923 | if (sh.config.stripBrs == true)
924 | str = str.replace(br, '');
925 |
926 | return str;
927 | };
928 |
929 | /**
930 | * Removes all white space at the begining and end of a string.
931 | *
932 | * @param {String} str String to trim.
933 | * @return {String} Returns string without leading and following white space characters.
934 | */
935 | function trim(str)
936 | {
937 | return str.replace(/^\s+|\s+$/g, '');
938 | };
939 |
940 | /**
941 | * Unindents a block of text by the lowest common indent amount.
942 | * @param {String} str Text to unindent.
943 | * @return {String} Returns unindented text block.
944 | */
945 | function unindent(str)
946 | {
947 | var lines = splitLines(fixInputString(str)),
948 | indents = new Array(),
949 | regex = /^\s*/,
950 | min = 1000
951 | ;
952 |
953 | // go through every line and check for common number of indents
954 | for (var i = 0, l = lines.length; i < l && min > 0; i++)
955 | {
956 | var line = lines[i];
957 |
958 | if (trim(line).length == 0)
959 | continue;
960 |
961 | var matches = regex.exec(line);
962 |
963 | // In the event that just one line doesn't have leading white space
964 | // we can't unindent anything, so bail completely.
965 | if (matches == null)
966 | return str;
967 |
968 | min = Math.min(matches[0].length, min);
969 | }
970 |
971 | // trim minimum common number of white space from the begining of every line
972 | if (min > 0)
973 | for (var i = 0, l = lines.length; i < l; i++)
974 | lines[i] = lines[i].substr(min);
975 |
976 | return lines.join('\n');
977 | };
978 |
979 | /**
980 | * Callback method for Array.sort() which sorts matches by
981 | * index position and then by length.
982 | *
983 | * @param {Match} m1 Left object.
984 | * @param {Match} m2 Right object.
985 | * @return {Number} Returns -1, 0 or -1 as a comparison result.
986 | */
987 | function matchesSortCallback(m1, m2)
988 | {
989 | // sort matches by index first
990 | if(m1.index < m2.index)
991 | return -1;
992 | else if(m1.index > m2.index)
993 | return 1;
994 | else
995 | {
996 | // if index is the same, sort by length
997 | if(m1.length < m2.length)
998 | return -1;
999 | else if(m1.length > m2.length)
1000 | return 1;
1001 | }
1002 |
1003 | return 0;
1004 | };
1005 |
1006 | /**
1007 | * Executes given regular expression on provided code and returns all
1008 | * matches that are found.
1009 | *
1010 | * @param {String} code Code to execute regular expression on.
1011 | * @param {Object} regex Regular expression item info from regexList
collection.
1012 | * @return {Array} Returns a list of Match objects.
1013 | */
1014 | function getMatches(code, regexInfo)
1015 | {
1016 | function defaultAdd(match, regexInfo)
1017 | {
1018 | return match[0];
1019 | };
1020 |
1021 | var index = 0,
1022 | match = null,
1023 | matches = [],
1024 | func = regexInfo.func ? regexInfo.func : defaultAdd
1025 | pos = 0
1026 | ;
1027 |
1028 | while((match = XRegExp.exec(code, regexInfo.regex, pos)) != null)
1029 | {
1030 | var resultMatch = func(match, regexInfo);
1031 |
1032 | if (typeof(resultMatch) == 'string')
1033 | resultMatch = [new sh.Match(resultMatch, match.index, regexInfo.css)];
1034 |
1035 | matches = matches.concat(resultMatch);
1036 | pos = match.index + match[0].length;
1037 | }
1038 |
1039 | return matches;
1040 | };
1041 |
1042 | /**
1043 | * Turns all URLs in the code into tags.
1044 | * @param {String} code Input code.
1045 | * @return {String} Returns code with tags.
1046 | */
1047 | function processUrls(code)
1048 | {
1049 | var gt = /(.*)((>|<).*)/;
1050 |
1051 | return code.replace(sh.regexLib.url, function(m)
1052 | {
1053 | var suffix = '',
1054 | match = null
1055 | ;
1056 |
1057 | // We include < and > in the URL for the common cases like
1058 | // The problem is that they get transformed into <http://google.com>
1059 | // Where as > easily looks like part of the URL string.
1060 |
1061 | if (match = gt.exec(m))
1062 | {
1063 | m = match[1];
1064 | suffix = match[2];
1065 | }
1066 |
1067 | return '' + m + '' + suffix;
1068 | });
1069 | };
1070 |
1071 | /**
1072 | * Finds all elementss.
1073 | * @return {Array} Returns array of all found SyntaxHighlighter tags.
1074 | */
1075 | function getSyntaxHighlighterScriptTags()
1076 | {
1077 | var tags = document.getElementsByTagName('script'),
1078 | result = []
1079 | ;
1080 |
1081 | for (var i = 0, l = tags.length; i < l; i++)
1082 | if (tags[i].type == 'Event_syntaxHighlighter')
1083 | result.push(tags[i]);
1084 |
1085 | return result;
1086 | };
1087 |
1088 | /**
1089 | * Strips from content because it should be used
1090 | * there in most cases for XHTML compliance.
1091 | * @param {String} original Input code.
1092 | * @return {String} Returns code without leading tags.
1093 | */
1094 | function stripCData(original)
1095 | {
1096 | var left = '',
1098 | // for some reason IE inserts some leading blanks here
1099 | copy = trim(original),
1100 | changed = false,
1101 | leftLength = left.length,
1102 | rightLength = right.length
1103 | ;
1104 |
1105 | if (copy.indexOf(left) == 0)
1106 | {
1107 | copy = copy.substring(leftLength);
1108 | changed = true;
1109 | }
1110 |
1111 | var copyLength = copy.length;
1112 |
1113 | if (copy.indexOf(right) == copyLength - rightLength)
1114 | {
1115 | copy = copy.substring(0, copyLength - rightLength);
1116 | changed = true;
1117 | }
1118 |
1119 | return changed ? copy : original;
1120 | };
1121 |
1122 |
1123 | /**
1124 | * Quick code mouse double click handler.
1125 | */
1126 | function quickCodeHandler(e)
1127 | {
1128 | var target = e.target,
1129 | highlighterDiv = findParentElement(target, '.Event_syntaxHighlighter'),
1130 | container = findParentElement(target, '.container'),
1131 | textarea = document.createElement('textarea'),
1132 | highlighter
1133 | ;
1134 |
1135 | if (!container || !highlighterDiv || findElement(container, 'textarea'))
1136 | return;
1137 |
1138 | highlighter = getHighlighterById(highlighterDiv.id);
1139 |
1140 | // add source class name
1141 | addClass(highlighterDiv, 'source');
1142 |
1143 | // Have to go over each line and grab it's text, can't just do it on the
1144 | // container because Firefox loses all \n where as Webkit doesn't.
1145 | var lines = container.childNodes,
1146 | code = []
1147 | ;
1148 |
1149 | for (var i = 0, l = lines.length; i < l; i++)
1150 | code.push(lines[i].innerText || lines[i].textContent);
1151 |
1152 | // using \r instead of \r or \r\n makes this work equally well on IE, FF and Webkit
1153 | code = code.join('\r');
1154 |
1155 | // For Webkit browsers, replace nbsp with a breaking space
1156 | code = code.replace(/\u00a0/g, " ");
1157 |
1158 | // inject tag
1159 | textarea.appendChild(document.createTextNode(code));
1160 | container.appendChild(textarea);
1161 |
1162 | // preselect all text
1163 | textarea.focus();
1164 | textarea.select();
1165 |
1166 | // set up handler for lost focus
1167 | attachEvent(textarea, 'blur', function(e)
1168 | {
1169 | textarea.parentNode.removeChild(textarea);
1170 | removeClass(highlighterDiv, 'source');
1171 | });
1172 | };
1173 |
1174 | /**
1175 | * Match object.
1176 | */
1177 | sh.Match = function(value, index, css)
1178 | {
1179 | this.value = value;
1180 | this.index = index;
1181 | this.length = value.length;
1182 | this.css = css;
1183 | this.brushName = null;
1184 | };
1185 |
1186 | sh.Match.prototype.toString = function()
1187 | {
1188 | return this.value;
1189 | };
1190 |
1191 | /**
1192 | * Simulates HTML code with a scripting language embedded.
1193 | *
1194 | * @param {String} scriptBrushName Brush name of the scripting language.
1195 | */
1196 | sh.HtmlScript = function(scriptBrushName)
1197 | {
1198 | var brushClass = findBrush(scriptBrushName),
1199 | scriptBrush,
1200 | xmlBrush = new sh.brushes.Xml(),
1201 | bracketsRegex = null,
1202 | ref = this,
1203 | methodsToExpose = 'getDiv getHtml init'.split(' ')
1204 | ;
1205 |
1206 | if (brushClass == null)
1207 | return;
1208 |
1209 | scriptBrush = new brushClass();
1210 |
1211 | for(var i = 0, l = methodsToExpose.length; i < l; i++)
1212 | // make a closure so we don't lose the name after i changes
1213 | (function() {
1214 | var name = methodsToExpose[i];
1215 |
1216 | ref[name] = function()
1217 | {
1218 | return xmlBrush[name].apply(xmlBrush, arguments);
1219 | };
1220 | })();
1221 |
1222 | if (scriptBrush.htmlScript == null)
1223 | {
1224 | alert(sh.config.strings.brushNotHtmlScript + scriptBrushName);
1225 | return;
1226 | }
1227 |
1228 | xmlBrush.regexList.push(
1229 | { regex: scriptBrush.htmlScript.code, func: process }
1230 | );
1231 |
1232 | function offsetMatches(matches, offset)
1233 | {
1234 | for (var j = 0, l = matches.length; j < l; j++)
1235 | matches[j].index += offset;
1236 | }
1237 |
1238 | function process(match, info)
1239 | {
1240 | var code = match.code,
1241 | matches = [],
1242 | regexList = scriptBrush.regexList,
1243 | offset = match.index + match.left.length,
1244 | htmlScript = scriptBrush.htmlScript,
1245 | result
1246 | ;
1247 |
1248 | // add all matches from the code
1249 | for (var i = 0, l = regexList.length; i < l; i++)
1250 | {
1251 | result = getMatches(code, regexList[i]);
1252 | offsetMatches(result, offset);
1253 | matches = matches.concat(result);
1254 | }
1255 |
1256 | // add left script bracket
1257 | if (htmlScript.left != null && match.left != null)
1258 | {
1259 | result = getMatches(match.left, htmlScript.left);
1260 | offsetMatches(result, match.index);
1261 | matches = matches.concat(result);
1262 | }
1263 |
1264 | // add right script bracket
1265 | if (htmlScript.right != null && match.right != null)
1266 | {
1267 | result = getMatches(match.right, htmlScript.right);
1268 | offsetMatches(result, match.index + match[0].lastIndexOf(match.right));
1269 | matches = matches.concat(result);
1270 | }
1271 |
1272 | for (var j = 0, l = matches.length; j < l; j++)
1273 | matches[j].brushName = brushClass.brushName;
1274 |
1275 | return matches;
1276 | }
1277 | };
1278 |
1279 | /**
1280 | * Main Highlither class.
1281 | * @constructor
1282 | */
1283 | sh.Highlighter = function()
1284 | {
1285 | // not putting any code in here because of the prototype inheritance
1286 | };
1287 |
1288 | sh.Highlighter.prototype = {
1289 | /**
1290 | * Returns value of the parameter passed to the highlighter.
1291 | * @param {String} name Name of the parameter.
1292 | * @param {Object} defaultValue Default value.
1293 | * @return {Object} Returns found value or default value otherwise.
1294 | */
1295 | getParam: function(name, defaultValue)
1296 | {
1297 | var result = this.params[name];
1298 | return toBoolean(result == null ? defaultValue : result);
1299 | },
1300 |
1301 | /**
1302 | * Shortcut to document.createElement().
1303 | * @param {String} name Name of the element to create (DIV, A, etc).
1304 | * @return {HTMLElement} Returns new HTML element.
1305 | */
1306 | create: function(name)
1307 | {
1308 | return document.createElement(name);
1309 | },
1310 |
1311 | /**
1312 | * Applies all regular expression to the code and stores all found
1313 | * matches in the `this.matches` array.
1314 | * @param {Array} regexList List of regular expressions.
1315 | * @param {String} code Source code.
1316 | * @return {Array} Returns list of matches.
1317 | */
1318 | findMatches: function(regexList, code)
1319 | {
1320 | var result = [];
1321 |
1322 | if (regexList != null)
1323 | for (var i = 0, l = regexList.length; i < l; i++)
1324 | // BUG: length returns len+1 for array if methods added to prototype chain (oising@gmail.com)
1325 | if (typeof (regexList[i]) == "object")
1326 | result = result.concat(getMatches(code, regexList[i]));
1327 |
1328 | // sort and remove nested the matches
1329 | return this.removeNestedMatches(result.sort(matchesSortCallback));
1330 | },
1331 |
1332 | /**
1333 | * Checks to see if any of the matches are inside of other matches.
1334 | * This process would get rid of highligted strings inside comments,
1335 | * keywords inside strings and so on.
1336 | */
1337 | removeNestedMatches: function(matches)
1338 | {
1339 | // Optimized by Jose Prado (http://joseprado.com)
1340 | for (var i = 0, l = matches.length; i < l; i++)
1341 | {
1342 | if (matches[i] === null)
1343 | continue;
1344 |
1345 | var itemI = matches[i],
1346 | itemIEndPos = itemI.index + itemI.length
1347 | ;
1348 |
1349 | for (var j = i + 1, l = matches.length; j < l && matches[i] !== null; j++)
1350 | {
1351 | var itemJ = matches[j];
1352 |
1353 | if (itemJ === null)
1354 | continue;
1355 | else if (itemJ.index > itemIEndPos)
1356 | break;
1357 | else if (itemJ.index == itemI.index && itemJ.length > itemI.length)
1358 | matches[i] = null;
1359 | else if (itemJ.index >= itemI.index && itemJ.index < itemIEndPos)
1360 | matches[j] = null;
1361 | }
1362 | }
1363 |
1364 | return matches;
1365 | },
1366 |
1367 | /**
1368 | * Creates an array containing integer line numbers starting from the 'first-line' param.
1369 | * @return {Array} Returns array of integers.
1370 | */
1371 | figureOutLineNumbers: function(code)
1372 | {
1373 | var lines = [],
1374 | firstLine = parseInt(this.getParam('first-line'))
1375 | ;
1376 |
1377 | eachLine(code, function(line, index)
1378 | {
1379 | lines.push(index + firstLine);
1380 | });
1381 |
1382 | return lines;
1383 | },
1384 |
1385 | /**
1386 | * Determines if specified line number is in the highlighted list.
1387 | */
1388 | isLineHighlighted: function(lineNumber)
1389 | {
1390 | var list = this.getParam('highlight', []);
1391 |
1392 | if (typeof(list) != 'object' && list.push == null)
1393 | list = [ list ];
1394 |
1395 | return indexOf(list, lineNumber.toString()) != -1;
1396 | },
1397 |
1398 | /**
1399 | * Generates HTML markup for a single line of code while determining alternating line style.
1400 | * @param {Integer} lineNumber Line number.
1401 | * @param {String} code Line HTML markup.
1402 | * @return {String} Returns HTML markup.
1403 | */
1404 | getLineHtml: function(lineIndex, lineNumber, code)
1405 | {
1406 | var classes = [
1407 | 'line',
1408 | 'number' + lineNumber,
1409 | 'index' + lineIndex,
1410 | 'alt' + (lineNumber % 2 == 0 ? 1 : 2).toString()
1411 | ];
1412 |
1413 | if (this.isLineHighlighted(lineNumber))
1414 | classes.push('highlighted');
1415 |
1416 | if (lineNumber == 0)
1417 | classes.push('break');
1418 |
1419 | return '' + code + '
';
1420 | },
1421 |
1422 | /**
1423 | * Generates HTML markup for line number column.
1424 | * @param {String} code Complete code HTML markup.
1425 | * @param {Array} lineNumbers Calculated line numbers.
1426 | * @return {String} Returns HTML markup.
1427 | */
1428 | getLineNumbersHtml: function(code, lineNumbers)
1429 | {
1430 | var html = '',
1431 | count = splitLines(code).length,
1432 | firstLine = parseInt(this.getParam('first-line')),
1433 | pad = this.getParam('pad-line-numbers')
1434 | ;
1435 |
1436 | if (pad == true)
1437 | pad = (firstLine + count - 1).toString().length;
1438 | else if (isNaN(pad) == true)
1439 | pad = 0;
1440 |
1441 | for (var i = 0; i < count; i++)
1442 | {
1443 | var lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i,
1444 | code = lineNumber == 0 ? sh.config.space : padNumber(lineNumber, pad)
1445 | ;
1446 |
1447 | html += this.getLineHtml(i, lineNumber, code);
1448 | }
1449 |
1450 | return html;
1451 | },
1452 |
1453 | /**
1454 | * Splits block of text into individual DIV lines.
1455 | * @param {String} code Code to highlight.
1456 | * @param {Array} lineNumbers Calculated line numbers.
1457 | * @return {String} Returns highlighted code in HTML form.
1458 | */
1459 | getCodeLinesHtml: function(html, lineNumbers)
1460 | {
1461 | html = trim(html);
1462 |
1463 | var lines = splitLines(html),
1464 | padLength = this.getParam('pad-line-numbers'),
1465 | firstLine = parseInt(this.getParam('first-line')),
1466 | html = '',
1467 | brushName = this.getParam('brush')
1468 | ;
1469 |
1470 | for (var i = 0, l = lines.length; i < l; i++)
1471 | {
1472 | var line = lines[i],
1473 | indent = /^( |\s)+/.exec(line),
1474 | spaces = null,
1475 | lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i;
1476 | ;
1477 |
1478 | if (indent != null)
1479 | {
1480 | spaces = indent[0].toString();
1481 | line = line.substr(spaces.length);
1482 | spaces = spaces.replace(' ', sh.config.space);
1483 | }
1484 |
1485 | line = trim(line);
1486 |
1487 | if (line.length == 0)
1488 | line = sh.config.space;
1489 |
1490 | html += this.getLineHtml(
1491 | i,
1492 | lineNumber,
1493 | (spaces != null ? '' + spaces + '
' : '') + line
1494 | );
1495 | }
1496 |
1497 | return html;
1498 | },
1499 |
1500 | /**
1501 | * Returns HTML for the table title or empty string if title is null.
1502 | */
1503 | getTitleHtml: function(title)
1504 | {
1505 | return title ? '' + title + '' : '';
1506 | },
1507 |
1508 | /**
1509 | * Finds all matches in the source code.
1510 | * @param {String} code Source code to process matches in.
1511 | * @param {Array} matches Discovered regex matches.
1512 | * @return {String} Returns formatted HTML with processed mathes.
1513 | */
1514 | getMatchesHtml: function(code, matches)
1515 | {
1516 | var pos = 0,
1517 | result = '',
1518 | brushName = this.getParam('brush', '')
1519 | ;
1520 |
1521 | function getBrushNameCss(match)
1522 | {
1523 | var result = match ? (match.brushName || brushName) : brushName;
1524 | return result ? result + ' ' : '';
1525 | };
1526 |
1527 | // Finally, go through the final list of matches and pull the all
1528 | // together adding everything in between that isn't a match.
1529 | for (var i = 0, l = matches.length; i < l; i++)
1530 | {
1531 | var match = matches[i],
1532 | matchBrushName
1533 | ;
1534 |
1535 | if (match === null || match.length === 0)
1536 | continue;
1537 |
1538 | matchBrushName = getBrushNameCss(match);
1539 |
1540 | result += wrapLinesWithCode(code.substr(pos, match.index - pos), matchBrushName + 'plain')
1541 | + wrapLinesWithCode(match.value, matchBrushName + match.css)
1542 | ;
1543 |
1544 | pos = match.index + match.length + (match.offset || 0);
1545 | }
1546 |
1547 | // don't forget to add whatever's remaining in the string
1548 | result += wrapLinesWithCode(code.substr(pos), getBrushNameCss() + 'plain');
1549 |
1550 | return result;
1551 | },
1552 |
1553 | /**
1554 | * Generates HTML markup for the whole syntax highlighter.
1555 | * @param {String} code Source code.
1556 | * @return {String} Returns HTML markup.
1557 | */
1558 | getHtml: function(code)
1559 | {
1560 | var html = '',
1561 | classes = [ 'Event_syntaxHighlighter' ],
1562 | tabSize,
1563 | matches,
1564 | lineNumbers
1565 | ;
1566 |
1567 | // process light mode
1568 | if (this.getParam('light') == true)
1569 | this.params.toolbar = this.params.gutter = false;
1570 |
1571 | className = 'Event_syntaxHighlighter';
1572 |
1573 | if (this.getParam('collapse') == true)
1574 | classes.push('collapsed');
1575 |
1576 | if ((gutter = this.getParam('gutter')) == false)
1577 | classes.push('nogutter');
1578 |
1579 | // add custom user style name
1580 | classes.push(this.getParam('class-name'));
1581 |
1582 | // add brush alias to the class name for custom CSS
1583 | classes.push(this.getParam('brush'));
1584 |
1585 | code = trimFirstAndLastLines(code)
1586 | .replace(/\r/g, ' ') // IE lets these buggers through
1587 | ;
1588 |
1589 | tabSize = this.getParam('tab-size');
1590 |
1591 | // replace tabs with spaces
1592 | code = this.getParam('smart-tabs') == true
1593 | ? processSmartTabs(code, tabSize)
1594 | : processTabs(code, tabSize)
1595 | ;
1596 |
1597 | // unindent code by the common indentation
1598 | if (this.getParam('unindent'))
1599 | code = unindent(code);
1600 |
1601 | if (gutter)
1602 | lineNumbers = this.figureOutLineNumbers(code);
1603 |
1604 | // find matches in the code using brushes regex list
1605 | matches = this.findMatches(this.regexList, code);
1606 | // processes found matches into the html
1607 | html = this.getMatchesHtml(code, matches);
1608 | // finally, split all lines so that they wrap well
1609 | html = this.getCodeLinesHtml(html, lineNumbers);
1610 |
1611 | // finally, process the links
1612 | if (this.getParam('auto-links'))
1613 | html = processUrls(html);
1614 |
1615 | if (typeof(navigator) != 'undefined' && navigator.userAgent && navigator.userAgent.match(/MSIE/))
1616 | classes.push('ie');
1617 |
1618 | html =
1619 | ''
1620 | + (this.getParam('toolbar') ? sh.toolbar.getHtml(this) : '')
1621 | + '
'
1622 | + this.getTitleHtml(this.getParam('title'))
1623 | + ''
1624 | + ''
1625 | + (gutter ? '' + this.getLineNumbersHtml(code) + ' | ' : '')
1626 | + ''
1627 | + ' '
1628 | + html
1629 | + ' '
1630 | + ' | '
1631 | + '
'
1632 | + ''
1633 | + '
'
1634 | + '
'
1635 | ;
1636 |
1637 | return html;
1638 | },
1639 |
1640 | /**
1641 | * Highlights the code and returns complete HTML.
1642 | * @param {String} code Code to highlight.
1643 | * @return {Element} Returns container DIV element with all markup.
1644 | */
1645 | getDiv: function(code)
1646 | {
1647 | if (code === null)
1648 | code = '';
1649 |
1650 | this.code = code;
1651 |
1652 | var div = this.create('div');
1653 |
1654 | // create main HTML
1655 | div.innerHTML = this.getHtml(code);
1656 |
1657 | // set up click handlers
1658 | if (this.getParam('toolbar'))
1659 | attachEvent(findElement(div, '.toolbar'), 'click', sh.toolbar.handler);
1660 |
1661 | if (this.getParam('quick-code'))
1662 | attachEvent(findElement(div, '.code'), 'dblclick', quickCodeHandler);
1663 |
1664 | return div;
1665 | },
1666 |
1667 | /**
1668 | * Initializes the highlighter/brush.
1669 | *
1670 | * Constructor isn't used for initialization so that nothing executes during necessary
1671 | * `new SyntaxHighlighter.Highlighter()` call when setting up brush inheritence.
1672 | *
1673 | * @param {Hash} params Highlighter parameters.
1674 | */
1675 | init: function(params)
1676 | {
1677 | this.id = guid();
1678 |
1679 | // register this instance in the highlighters list
1680 | storeHighlighter(this);
1681 |
1682 | // local params take precedence over defaults
1683 | this.params = merge(sh.defaults, params || {})
1684 |
1685 | // process light mode
1686 | if (this.getParam('light') == true)
1687 | this.params.toolbar = this.params.gutter = false;
1688 | },
1689 |
1690 | /**
1691 | * Converts space separated list of keywords into a regular expression string.
1692 | * @param {String} str Space separated keywords.
1693 | * @return {String} Returns regular expression string.
1694 | */
1695 | getKeywords: function(str)
1696 | {
1697 | str = str
1698 | .replace(/^\s+|\s+$/g, '')
1699 | .replace(/\s+/g, '|')
1700 | ;
1701 |
1702 | return '\\b(?:' + str + ')\\b';
1703 | },
1704 |
1705 | /**
1706 | * Makes a brush compatible with the `html-script` functionality.
1707 | * @param {Object} regexGroup Object containing `left` and `right` regular expressions.
1708 | */
1709 | forHtmlScript: function(regexGroup)
1710 | {
1711 | var regex = { 'end' : regexGroup.right.source };
1712 |
1713 | if(regexGroup.eof)
1714 | regex.end = "(?:(?:" + regex.end + ")|$)";
1715 |
1716 | this.htmlScript = {
1717 | left : { regex: regexGroup.left, css: 'script' },
1718 | right : { regex: regexGroup.right, css: 'script' },
1719 | code : XRegExp(
1720 | "(?" + regexGroup.left.source + ")" +
1721 | "(?.*?)" +
1722 | "(?" + regex.end + ")",
1723 | "sgi"
1724 | )
1725 | };
1726 | }
1727 | }; // end of Highlighter
1728 |
1729 | return sh;
1730 | }(); // end of anonymous function
1731 |
1732 | // CommonJS
1733 | typeof(exports) != 'undefined' ? exports.VisualEventSyntaxHighlighter = VisualEventSyntaxHighlighter : null;
1734 |
1735 |
1736 |
1737 | // JS brush
1738 | ;(function()
1739 | {
1740 | // CommonJS
1741 | VisualEventSyntaxHighlighter = VisualEventSyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').VisualEventSyntaxHighlighter : null);
1742 |
1743 | function Brush()
1744 | {
1745 | var keywords = 'break case catch class continue ' +
1746 | 'default delete do else enum export extends false ' +
1747 | 'for function if implements import in instanceof ' +
1748 | 'interface let new null package private protected ' +
1749 | 'static return super switch ' +
1750 | 'this throw true try typeof var while with yield';
1751 |
1752 | var r = VisualEventSyntaxHighlighter.regexLib;
1753 |
1754 | this.regexList = [
1755 | { regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings
1756 | { regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings
1757 | { regex: r.singleLineCComments, css: 'comments' }, // one line comments
1758 | { regex: r.multiLineCComments, css: 'comments' }, // multiline comments
1759 | { regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion
1760 | { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keywords
1761 | ];
1762 |
1763 | this.forHtmlScript(r.scriptScriptTags);
1764 | };
1765 |
1766 | Brush.prototype = new VisualEventSyntaxHighlighter.Highlighter();
1767 | Brush.aliases = ['js', 'jscript', 'javascript', 'json'];
1768 |
1769 | VisualEventSyntaxHighlighter.brushes.JScript = Brush;
1770 |
1771 | // CommonJS
1772 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
1773 | })();
1774 |
1775 | window.VisualEventSyntaxHighlighter = VisualEventSyntaxHighlighter;
1776 |
1777 | })();
1778 |
--------------------------------------------------------------------------------