1 // ========================================================================== 2 // Project: SproutCore 3 // Copyright: ©2014 7x7 Software, Inc. 4 // License: Licensed under MIT license 5 // ========================================================================== 6 7 8 /** 9 @class 10 11 This view creates a link (i.e. HTML anchor) to a remote resource. You should 12 use this view to create programmatic HTML links, such as links to a PDF or to 13 an external site. 14 15 For example, 16 17 //... 18 19 // A link to download the selected employee's resume. 20 link: SC.LinkView.extend({ 21 22 body: "Current Resume", 23 24 // Compute the fileName on the fly. 25 fileNameBinding: SC.Binding.oneWay('MyApp.employeeController.fullName') 26 .transform(function (fullName) { 27 // ex. "Resume - Tyler Keating.pdf" 28 return "Resume - " + fullName + ".pdf"; 29 }), 30 31 // Compute the href on the fly. 32 hrefBinding: SC.Binding.oneWay('MyApp.employeeController.resumePath'), 33 34 toolTip: "Link to current resume" 35 }), 36 37 //... 38 39 The example above generates something like the following, 40 41 <a href="/users/22/cur-resume.pdf" class="sc-view sc-link-view" download="Resume - Tyler Keating.pdf" title="Link to current resume">Current Resume</a> 42 43 Note that you can localize the `body` and the `toolTip` by setting the `localize` 44 property to true. 45 46 @since SproutCore 1.11 47 */ 48 SC.LinkView = SC.View.extend({ 49 50 /** 51 The WAI-ARIA role for link views. 52 53 @type String 54 @default 'link' 55 @readOnly 56 */ 57 ariaRole: 'link', 58 59 /** 60 The content of the anchor, such as text or an image. 61 62 Note that this will be escaped by default, so any HTML tags will appear 63 as text. To render the body as HTML, set `escapeHTML` to `false` and 64 remember to *NEVER* allow user generated content unescaped in your app. 65 66 If you are using text, you may also want to provide localized versions and 67 should set the `localize` property to true. 68 69 @type String 70 @default "" 71 */ 72 body: "", 73 74 /** 75 The class names for the view. 76 77 Note: this is not an observed display property and as such must be predefined on the 78 view (You can update class names using `classNameBindings`). 79 80 Note: this is a concatenated property and as such all subclasses will inherit 81 the current class names. 82 83 @type Array 84 @default ['sc-view', 'sc-link-view'] 85 */ 86 classNames: ['sc-link-view'], 87 88 /** 89 This is generated by localizing the body property if necessary. 90 91 @readonly 92 @type String 93 @observes 'body' 94 @observes 'localize' 95 */ 96 displayBody: function () { 97 var ret = this.get('body'); 98 99 return (ret && this.get('localize')) ? SC.String.loc(ret) : (ret || ''); 100 }.property('body', 'localize').cacheable(), 101 102 /** 103 The observed properties that will cause the view to be rerendered if they 104 change. 105 106 Note: this is a concatenated property and as such all subclasses will inherit 107 the current display properties. 108 109 @type Array 110 @default ['displayBody', 'displayToolTip', 'fileName', 'href', 'hreflang'] 111 */ 112 displayProperties: ['displayBody', 'displayToolTip', 'fileName', 'href', 'hreflang'], 113 114 /** 115 The default file name to use for the linked resource if it will be 116 downloaded. 117 118 For example, 119 120 //... 121 122 // The linked resource (/students/2013/list-copy.xml) will be downloaded 123 // with the name 'Student List.xml' by default. 124 fileName: 'Student List.xml', 125 126 href: '/students/2013/list-copy.xml' 127 128 //... 129 130 This property is observed, allowing you to programmatically set the download 131 file name. 132 133 For example as a computed property, 134 135 //... 136 137 // The download file name is computed from the linked resource URL. 138 fileName: function () { 139 var href = this.get('href'), 140 linkedYear, 141 ret; 142 143 if (href) { 144 // ex. href == "/reports/2012/annual-report.pdf" 145 linkedYear = href.match(/\/(\d*)\//)[1]; 146 ret = "Annual Report " + linkedYear + '.pdf'; 147 } 148 149 return ret; 150 }.property('href').cacheable(), 151 152 hrefBinding: SC.Binding.oneWay('MyApp.reportController.hardlink'), 153 154 //... 155 156 Note: There are no restrictions on allowed values, but authors are cautioned 157 that most file systems have limitations with regard to what punctuation is 158 supported in file names, and user agents are likely to adjust file names 159 accordingly. Also, support for this attribute varies widely between browsers. 160 161 @see http://caniuse.com/#feat=download 162 @see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#attr-hyperlink-download 163 */ 164 fileName: null, 165 166 /** 167 Whether the body and toolTip will be escaped to avoid HTML injection attacks 168 or not. 169 170 You should only disable this option if you are sure you are displaying 171 non-user generated text. 172 173 Note: this is not an observed display property. If you change it after 174 rendering, you should call `displayDidChange` on the view to update the layer. 175 176 @type Boolean 177 @default true 178 */ 179 escapeHTML: true, 180 181 /** 182 The linked resource URL. 183 184 @type String 185 @default '#' 186 */ 187 href: '#', 188 189 /** 190 The alternate language for the linked resource. 191 192 Set this value to modify the 'hreflang' attribute for the linked resource, 193 which would otherwise be the current locale's language. 194 195 @type String 196 @default null 197 @see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#attr-hyperlink-hreflang 198 */ 199 language: null, 200 201 /** 202 The language attribute of the linked resource. 203 204 This is the current locale's language by default, but may be overridden to 205 a specific other language by setting the `language` property. 206 207 @readonly 208 @type String 209 @observes 'language' 210 @observes 'localize' 211 @default SC.Locale.currentLocale.language 212 @see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#attr-hyperlink-hreflang 213 */ 214 hreflang: function () { 215 var language = this.get('language'), 216 ret; 217 218 ret = language || SC.Locale.currentLocale.language; 219 220 return ret; 221 }.property('language', 'localize').cacheable(), 222 223 /** 224 An array of URLs to ping when the link is clicked. 225 226 For example, this can be used for tracking the use of off-site links without 227 JavaScript or page redirects, 228 229 //... 230 231 // Whenever anyone downloads this resource, we ping our analytics site. 232 ping: ['http://tracker.my-app.com/?action="Downloaded PDF Version"'], 233 234 //... 235 236 Note: this is not an updateable display property. It must be defined before 237 creating the layer. 238 239 @type Array 240 @default null 241 @see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#ping 242 */ 243 ping: null, 244 245 /** 246 A list of space separated non-case sensitive link type tokens. 247 248 For example, 249 250 //... 251 252 // This link is to the author of the article and the result should be loaded in the browser's sidebar if it has one. 253 rel: ['author', 'sidebar'], 254 255 //... 256 257 Note: this is not an updateable display property. It must be defined before 258 creating the layer. 259 260 @type Array 261 @default null 262 @see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#attr-hyperlink-rel 263 @see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#linkTypes 264 */ 265 rel: null, 266 267 /** 268 The tag type to use. 269 270 Note: this is not an updateable display property. It must be defined before 271 creating the layer. 272 273 @type String 274 @default 'a' 275 */ 276 tagName: 'a', 277 278 /** 279 The target for loading the resource. 280 281 The following keywords have special meanings: 282 283 * _self: Load the response into the same HTML4 frame (or HTML5 browsing context) as the current one. 284 * _blank: Load the response into a new unnamed HTML4 window or HTML5 browsing context. 285 * _parent: Load the response into the HTML4 frameset parent of the current frame or HTML5 parent browsing context of the current one. If there is no parent, this option behaves the same way as _self. 286 * _top: In HTML4: Load the response into the full, original window, canceling all other frames. In HTML5: Load the response into the top-level browsing context (that is, the browsing context that is an ancestor of the current one, and has no parent). If there is no parent, this option behaves the same way as _self. 287 288 Note: this is not an updateable display property. It must be defined before 289 creating the layer. 290 291 @type String 292 @default '_blank' 293 @see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#attr-hyperlink-target 294 */ 295 target: '_blank', 296 297 /** 298 The mime type of the link. 299 300 This has little effect, but certain browsers may add display information 301 pertaining to the type, such as a small icon for the linked resource type. 302 303 Note: this is not an updateable display property. It must be defined before 304 creating the layer. 305 306 @type String 307 @default null 308 @see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#attr-hyperlink-type 309 */ 310 type: null, 311 312 // ------------------------------------------------------------------------ 313 // Methods 314 // 315 316 /** @private */ 317 render: function (context) { 318 var displayBody = this.get('displayBody'), 319 displayToolTip = this.get('displayToolTip'), 320 fileName = this.get('fileName'), 321 escapeHTML = this.get('escapeHTML'), 322 href = this.get('href'), 323 hreflang = this.get('hreflang'), 324 ping = this.get('ping'), 325 rel = this.get('rel'), 326 target = this.get('target'), 327 type = this.get('type'); 328 329 // Escape the title of the anchor if needed. This prevents potential XSS attacks. 330 if (displayToolTip && escapeHTML) { 331 displayToolTip = SC.RenderContext.escapeHTML(displayToolTip); 332 } 333 334 // Set attributes 335 context.setAttr({ 336 'download': fileName, 337 'href': href, 338 'hreflang': hreflang, 339 'ping': ping ? ping.join(' ') : null, 340 'rel': rel ? rel.join(' ') : null, 341 'target': target, 342 'title': displayToolTip, 343 'type': type 344 }); 345 346 // Escape the body of the anchor if needed. This prevents potential XSS attacks. 347 if (displayBody && escapeHTML) { 348 displayBody = SC.RenderContext.escapeHTML(displayBody); 349 } 350 351 // Insert the body 352 context.push(displayBody); 353 }, 354 355 /** @private */ 356 mouseDown: function (evt) { 357 evt.allowDefault(); 358 return true; 359 }, 360 361 /** @private */ 362 mouseUp: function (evt) { 363 evt.allowDefault(); 364 return true; 365 }, 366 367 /** @private */ 368 touchStart: function (touch) { 369 touch.allowDefault(); 370 return true; 371 }, 372 373 /** @private */ 374 touchEnd: function (touch) { 375 touch.allowDefault(); 376 return true; 377 }, 378 379 /** @private */ 380 update: function (jqEl) { 381 var displayBody = this.get('displayBody'), 382 displayToolTip = this.get('displayToolTip'), 383 fileName = this.get('fileName'), 384 escapeHTML = this.get('escapeHTML'), 385 href = this.get('href'), 386 hreflang = this.get('hreflang'); 387 388 jqEl.attr('download', fileName); 389 jqEl.attr('href', href); 390 jqEl.attr('hreflang', hreflang); 391 392 // Escape the title of the anchor if needed. This prevents potential XSS attacks. 393 if (displayToolTip && escapeHTML) { 394 displayToolTip = SC.RenderContext.escapeHTML(displayToolTip); 395 } 396 397 jqEl.attr('title', displayToolTip); 398 399 // Escape the body of the anchor if needed. This prevents potential XSS attacks. 400 if (displayBody && escapeHTML) { 401 displayBody = SC.RenderContext.escapeHTML(displayBody); 402 } 403 jqEl.html(displayBody); 404 } 405 406 }); 407