-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathhtml-utils.js
More file actions
245 lines (233 loc) · 9.54 KB
/
html-utils.js
File metadata and controls
245 lines (233 loc) · 9.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/**
* Useful functions for dealing with HTML.
*
* In particular, these functions default to being safe against
* Cross Site Scripting (XSS) attacks. You can read more about
* the best practices for handling proper escaping in the Open edX
* platform with
* [Preventing Cross Site Scripting Vulnerabilities][1].
* [1]: http://edx.readthedocs.org/projects/edx-developer-guide/en/latest/conventions/safe_templates.html
*
* @module HtmlUtils
*/
(function(define) {
'use strict';
define(['underscore', 'jquery', 'edx-ui-toolkit/js/utils/string-utils'], function(_, $, StringUtils) {
var HtmlUtils, ensureHtml, interpolateHtml, joinHtml, HTML, template, setHtml, append, prepend;
/**
* Creates an HTML snippet.
*
* The intention of an HTML snippet is to communicate that the string
* it represents contains HTML that has been safely escaped as necessary.
* As an example, this allows `HtmlUtils.interpolate` to understand that
* it does not need to further escape this HTML.
*
* @param {string} htmlString The string of HTML.
*/
function HtmlSnippet(htmlString) {
this.text = htmlString;
}
HtmlSnippet.prototype.valueOf = function() {
return this.text;
};
HtmlSnippet.prototype.toString = function() {
return this.text;
};
/**
* Helper function to create an HTML snippet from a string.
*
* The intention of an HTML snippet is to communicate that the string
* it represents contains HTML that has been safely escaped as necessary.
*
* @param {string} htmlString The string of HTML.
* @returns {HtmlSnippet} An HTML snippet that can be safely rendered.
*/
HTML = function(htmlString) {
return new HtmlSnippet(htmlString);
};
/**
* Ensures that the provided text is properly HTML-escaped.
*
* If a plain text string is provided, then it will be HTML-escaped and
* returned as an HtmlSnippet. If the parameter is an HTML snippet
* then it will be returned directly so as not to double escape it.
*
* @param {(string|HtmlSnippet)} html Either a plain text string
* or an HTML snippet.
* @returns {HtmlSnippet} A safely escaped HTML snippet.
*/
ensureHtml = function(html) {
if (html instanceof HtmlSnippet) {
return html;
} else {
return HTML(_.escape(html));
}
};
/**
* Returns an HTML snippet by interpolating the provided parameters.
*
* The text is provided as a tokenized format string where parameters
* are indicated via curly braces, e.g. `'Hello {name}'`. These tokens are
* replaced by the parameter value of the same name.
*
* Parameter values will be rendered using their toString methods and then
* HTML-escaped. The only exception is that instances of the class HTML
* are rendered without escaping as their contract declares that they are
* already valid HTML.
*
* Example:
*
*~~~ javascript
* HtmlUtils.interpolateHtml(
* 'You are enrolling in {spanStart}{courseName}{spanEnd}',
* {
* courseName: 'Rock & Roll 101',
* spanStart: HtmlUtils.HTML('<span class="course-title">'),
* spanEnd: HtmlUtils.HTML('</span>')
* }
* );
*~~~
*
* returns:
*
*~~~ javascript
* 'You are enrolling in <span class="course-title">Rock & Roll 101</span>'
*~~~
*
* Note: typically the formatString will need to be internationalized, in which
* case it will be wrapped with a call to an i18n lookup function. If using
* the Django i18n library this would look like:
*
*~~~ javascript
* HtmlUtils.interpolateHtml(
* gettext('You are enrolling in {spanStart}{courseName}{spanEnd}'),
* ...
* );
*~~~
*
* @param {string} formatString The string to be interpolated.
* @param {Object} parameters An optional set of parameters for interpolation.
* @returns {HtmlSnippet} The resulting safely escaped HTML snippet.
*/
interpolateHtml = function(formatString, parameters) {
var result = StringUtils.interpolate(
ensureHtml(formatString).toString(),
_.mapObject(parameters, ensureHtml)
);
return HTML(result);
};
/**
* Joins multiple strings and/or HTML snippets together to produce
* a single safely escaped HTML snippet.
*
* For each item, if it is provided as an HTML snippet then it is joined
* directly. If the item is a string then it is assumed to be unescaped and
* so it is first escaped before being joined.
*
* @param {...(string|HtmlSnippet)} items The strings and/or HTML snippets
* to be joined together.
* @returns {HtmlSnippet} The resulting safely escaped HTML snippet.
*/
joinHtml = function() {
var html = '',
argumentCount = arguments.length,
i;
for (i = 0; i < argumentCount; i += 1) {
html += ensureHtml(arguments[i]);
}
return HTML(html);
};
/**
* Returns a function that renders an Underscore template as an HTML snippet.
*
* Note: This helper function makes the following context parameters
* available to the template in addition to those passed in:
*
* - `HtmlUtils`: the `HtmlUtils` helper class
* - `StringUtils`: the `StringUtils` helper class
*
* @param {string} text
* @param {object} settings
* @returns {function} A function that returns a rendered HTML snippet.
*/
template = function(text, settings) {
return function(data) {
var augmentedData = _.extend(
{
HtmlUtils: HtmlUtils,
StringUtils: StringUtils
},
data || {}
);
return HTML(_.template(text, settings)(augmentedData));
};
};
/**
* A wrapper for `$.html` that safely escapes the provided HTML.
*
* If the HTML is provided as an HTML snippet then it is used directly.
* If the value is a string then it is assumed to be unescaped and
* so it is first escaped before being used.
*
* @param {element} element The element or elements to be updated.
* @param {(string|HtmlSnippet)} html The desired HTML, either as a
* plain string or as an HTML snippet.
* @returns {JQuery} The JQuery object representing the element or elements.
*/
setHtml = function(element, html) {
return $(element).html(ensureHtml(html).toString());
};
/**
* A wrapper for `$.append` that safely escapes the provided HTML.
*
* If the HTML is provided as an HTML snippet then it is used directly.
* If the value is a string then it is assumed to be unescaped and
* so it is first escaped before being used.
*
* @param {element} element The element or elements to be updated.
* @param {(string|HtmlSnippet)} html The desired HTML, either as a
* plain string or as an HTML snippet.
* @returns {JQuery} The JQuery object representing the element or elements.
*/
append = function(element, html) {
return $(element).append(ensureHtml(html).toString());
};
/**
* A wrapper for `$.prepend` that safely escapes the provided HTML.
*
* If the HTML is provided as an HTML snippet then it is used directly.
* If the value is a string then it is assumed to be unescaped and
* so it is first escaped before being used.
*
* @param {element} element The element or elements to be updated.
* @param {(string|HtmlSnippet)} html The desired HTML, either as a
* plain string or as an HTML snippet.
* @returns {JQuery} The JQuery object representing the element or elements.
*/
prepend = function(element, html) {
return $(element).prepend(ensureHtml(html).toString());
};
HtmlUtils = {
append: append,
ensureHtml: ensureHtml,
HTML: HTML,
HtmlSnippet: HtmlSnippet,
interpolateHtml: interpolateHtml,
joinHtml: joinHtml,
prepend: prepend,
setHtml: setHtml,
template: template
};
return HtmlUtils;
});
}).call(
this,
// Pick a define function as follows:
// 1. Use the default 'define' function if it is available
// 2. If not, use 'RequireJS.define' if that is available
// 3. else use the GlobalLoader to install the class into the edx namespace
// eslint-disable-next-line no-nested-ternary
typeof define === 'function' && define.amd ? define :
(typeof RequireJS !== 'undefined' ? RequireJS.define :
edx.GlobalLoader.defineAs('HtmlUtils', 'edx-ui-toolkit/js/utils/html-utils'))
);