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