UrlWidget.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2014, 2019
  5. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  6. */
  7. define(['jquery', 'doT', './StaticWidget', 'text!./NewWidget.html'], function ($, dot, StaticWidget, NewWidget) {
  8. var NewWidgetTemplate = dot.template(NewWidget);
  9. /**
  10. * The UrlWidget is a generic class for widgets which load some markup based on a URL resource
  11. */
  12. var F12 = 123;
  13. var ESCAPE = 27;
  14. var UrlWidget = null;
  15. UrlWidget = StaticWidget.extend({
  16. init: function init() {
  17. UrlWidget.inherited('init', this, arguments);
  18. },
  19. initialize: function initialize() {
  20. return Promise.resolve();
  21. },
  22. getId: function getId() {
  23. return this.id;
  24. },
  25. onContainerReady: function onContainerReady() {
  26. UrlWidget.inherited('onContainerReady', this, arguments);
  27. if (this.isInlineEditMode()) {
  28. this.renderInlineEditUi();
  29. }
  30. this.updateModeMarkup();
  31. },
  32. /**
  33. * Virtual method
  34. * @return boolean true iff inline edit mode should be shown (i.e. not the actual widget payload rendering)
  35. */
  36. isInlineEditMode: function isInlineEditMode() {
  37. return false;
  38. },
  39. onConsumeMode: function onConsumeMode() {
  40. UrlWidget.inherited('onConsumeMode', this, arguments);
  41. var $container = this.$el.find('.staticContent');
  42. $container.removeClass('authoring');
  43. $container.addClass('consume');
  44. },
  45. onAuthoringMode: function onAuthoringMode() {
  46. UrlWidget.inherited('onAuthoringMode', this, arguments);
  47. var $container = this.$el.find('.staticContent');
  48. $container.removeClass('consume');
  49. $container.addClass('authoring');
  50. },
  51. /**
  52. * Virtual method
  53. * @return The caption for the inline edit textbox
  54. */
  55. getInlineEditCaption: function getInlineEditCaption() {
  56. return null;
  57. },
  58. /**
  59. * Virtual method
  60. * @return The text to show in consume mode when no URL has been specified
  61. */
  62. getMissingUrlText: function getMissingUrlText() {
  63. return null;
  64. },
  65. /**
  66. * Virtual method
  67. * @return The text for aria label
  68. */
  69. getAriaLabelText: function getAriaLabelText() {
  70. return null;
  71. },
  72. /**
  73. * Virtual method
  74. * @return The content class, used to hook into default images for pinning
  75. */
  76. getContentClass: function getContentClass() {
  77. return null;
  78. },
  79. renderInlineEditUi: function renderInlineEditUi() {
  80. if (!this.$el.find('.inlineEditInput').length) {
  81. this.$el.find('.staticContent').replaceWith(NewWidgetTemplate({
  82. title: this.getInlineEditCaption(),
  83. missingUrlText: this.getMissingUrlText(),
  84. mediaAriaLabel: this.getAriaLabelText(),
  85. contentClass: this.getContentClass(),
  86. includeAuthoringMode: true
  87. }));
  88. var $input = this.$el.find('.staticContent input');
  89. $input.on('click touchend', function () {
  90. // give focus to the text input
  91. $input.focus();
  92. }.bind(this));
  93. $(this.el).on('keydown', '.inlineEditInput', this._inlineKeyDown.bind(this));
  94. $(this.el).on('keyup', '.inlineEditInput', this._inlineKeyUp.bind(this));
  95. $(this.el).on('input', '.inlineEditInput', this._inlineInput.bind(this));
  96. //Might be called multiple time so we need to cleanup
  97. $(this.el).off('blur.urlWidget');
  98. // re-register
  99. $(this.el).on('blur.urlWidget', '.inlineEditInput', this._onBlur.bind(this));
  100. }
  101. },
  102. _onBlur: function _onBlur() {
  103. if (this.$el.find('.inlineEditInput').val()) {
  104. this._uiSetUrl();
  105. }
  106. },
  107. getLabel: function getLabel() {
  108. // To be implemented by concrete class
  109. },
  110. _inlineKeyDown: function _inlineKeyDown(evt) {
  111. if (evt.keyCode === 13) {
  112. this._uiSetUrl();
  113. }
  114. evt.stopPropagation();
  115. },
  116. _inlineKeyUp: function _inlineKeyUp(evt) {
  117. evt.stopPropagation();
  118. if (evt.keyCode === ESCAPE || evt.keyCode === F12 && evt.shiftKey) {
  119. // esc
  120. this.$el.focus();
  121. }
  122. },
  123. _inlineInput: function _inlineInput(evt) {
  124. var $inlineEdit = this.$el.find('.inlineEdit');
  125. if ($(evt.target).val() === '' && $inlineEdit.hasClass('inputError')) {
  126. $inlineEdit.removeClass('inputError');
  127. }
  128. },
  129. _uiSetUrl: function _uiSetUrl() {
  130. var url = this.$el.find('.inlineEditInput').val();
  131. if (url !== this._lastSetURL) {
  132. //Suppress double executions (Enter setting the url, triggering a blur)
  133. this._lastSetURL = url;
  134. var errorData = {};
  135. var urlSetSuccessfully = this.setUrl(url, errorData);
  136. var $inlineEdit = this.$el.find('.inlineEdit');
  137. if (urlSetSuccessfully) {
  138. $inlineEdit.removeClass('inputError');
  139. } else {
  140. $inlineEdit.addClass('inputError');
  141. $inlineEdit.find('.errorMessage').html(errorData.message);
  142. }
  143. this.refreshPropertiesPane();
  144. }
  145. },
  146. /**
  147. * Virtual method
  148. * @param The URL entered by the user
  149. * @return true iff the URL is successfully set
  150. */
  151. setUrl: function setUrl() /*url, error data object*/{
  152. return false;
  153. },
  154. /**
  155. * Applies any mode-specific changes to the markup
  156. * (e.g. authoring vs consume mode).
  157. */
  158. updateModeMarkup: function updateModeMarkup() {
  159. if (this.isAuthoringMode) {
  160. this.onAuthoringMode();
  161. } else {
  162. this.onConsumeMode();
  163. }
  164. this.updateDescription(this.getLabel());
  165. },
  166. /**
  167. * Updates markup, derived class may do more work
  168. */
  169. updateMarkup: function updateMarkup() {
  170. this.updateModeMarkup();
  171. },
  172. onChromeSelected: function onChromeSelected() {
  173. UrlWidget.inherited('onChromeSelected', this, arguments);
  174. this.onFocus();
  175. setTimeout(function () {
  176. $(this.el).find('.inlineEditInput').focus();
  177. }.bind(this), 10);
  178. },
  179. registerEventGroup: function registerEventGroup() {
  180. // URL widgets are not added to event groups
  181. },
  182. encodeURL: function encodeURL(URL) {
  183. //Run a decode/encode to block XSS attacks while not double-encoding already encoded URLs
  184. //As different parts of the URI encode different characters, we need to parse the URI to encode different parts using different functions.
  185. var URLComponents = URL.split('?');
  186. var urlPath = encodeURI(decodeURI(URLComponents[0])),
  187. searchPath = URLComponents[1];
  188. URLComponents = [];
  189. URLComponents.push(urlPath);
  190. if (searchPath) {
  191. var searchPathComponents = searchPath.split('&'),
  192. encodedSearchPathComponents = [];
  193. for (var i = 0; i < searchPathComponents.length; i++) {
  194. var paramComponents = searchPathComponents[i].split('='),
  195. encodedParamComponents = [];
  196. for (var j = 0; j < paramComponents.length; j++) {
  197. encodedParamComponents.push(encodeURIComponent(decodeURIComponent(paramComponents[j].replace(/\+/g, ' '))));
  198. }
  199. encodedSearchPathComponents.push(encodedParamComponents.join('='));
  200. }
  201. URLComponents.push(encodedSearchPathComponents.join('&'));
  202. }
  203. return URLComponents.join('?');
  204. }
  205. });
  206. /**
  207. * Checks whether the browser will render this URL as an embedded object
  208. * (e.g. iframe, video, audio, embed, object, etc.)
  209. */
  210. UrlWidget._isAllowedProtocol = function (url) {
  211. if (!url) {
  212. return false;
  213. }
  214. url = url.toLowerCase();
  215. // https:// is always allowed - i.e. both in pages served from HTTP or
  216. // HTTPS
  217. // // is always allowed as it will copy whichever protocol the
  218. // containing page is using
  219. // http:// is only allowed if the containing page is using HTTP as well
  220. if (url.indexOf('https://') === 0 || url.indexOf('//') === 0) {
  221. return true;
  222. }
  223. return false;
  224. };
  225. /**
  226. * Checks whether the browser will render this URL as an embedded object
  227. * (e.g. iframe, video, audio, embed, object, etc.)
  228. */
  229. UrlWidget.isAllowedProtocol = function (url) {
  230. return UrlWidget._isAllowedProtocol(url);
  231. };
  232. UrlWidget.getDefaultConsumeMarkup = function (missingText) {
  233. return NewWidgetTemplate({
  234. missingUrlText: missingText
  235. });
  236. };
  237. return UrlWidget;
  238. });
  239. //# sourceMappingURL=UrlWidget.js.map