emojipicker.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. // Copyright 2007 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Emoji Picker implementation. This provides a UI widget for
  16. * choosing an emoji from a grid of possible choices.
  17. *
  18. * @see ../demos/popupemojipicker.html for an example of how to instantiate
  19. * an emoji picker.
  20. *
  21. * Based on goog.ui.ColorPicker (colorpicker.js).
  22. *
  23. * @see ../../demos/popupemojipicker.html
  24. */
  25. goog.provide('goog.ui.emoji.EmojiPicker');
  26. goog.require('goog.dom.TagName');
  27. goog.require('goog.style');
  28. goog.require('goog.ui.Component');
  29. goog.require('goog.ui.TabPane');
  30. goog.require('goog.ui.emoji.Emoji');
  31. goog.require('goog.ui.emoji.EmojiPalette');
  32. goog.require('goog.ui.emoji.EmojiPaletteRenderer');
  33. goog.require('goog.ui.emoji.ProgressiveEmojiPaletteRenderer');
  34. /**
  35. * Creates a new, empty emoji picker. An emoji picker is a grid of emoji, each
  36. * cell of the grid containing a single emoji. The picker may contain multiple
  37. * pages of emoji.
  38. *
  39. * When a user selects an emoji, by either clicking or pressing enter, the
  40. * picker fires a goog.ui.Component.EventType.ACTION event with the id. The
  41. * client listens on this event and in the handler can retrieve the id of the
  42. * selected emoji and do something with it, for instance, inserting an image
  43. * tag into a rich text control. An emoji picker does not maintain state. That
  44. * is, once an emoji is selected, the emoji picker does not remember which emoji
  45. * was selected.
  46. *
  47. * The emoji picker is implemented as a tabpane with each tabpage being a table.
  48. * Each of the tables are the same size to prevent jittering when switching
  49. * between pages.
  50. *
  51. * @param {string} defaultImgUrl Url of the img that should be used to fill up
  52. * the cells in the emoji table, to prevent jittering. Should be the same
  53. * size as the emoji.
  54. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
  55. * @extends {goog.ui.Component}
  56. * @constructor
  57. */
  58. goog.ui.emoji.EmojiPicker = function(defaultImgUrl, opt_domHelper) {
  59. goog.ui.Component.call(this, opt_domHelper);
  60. this.defaultImgUrl_ = defaultImgUrl;
  61. /**
  62. * Emoji that this picker displays.
  63. *
  64. * @type {Array<Object>}
  65. * @private
  66. */
  67. this.emoji_ = [];
  68. /**
  69. * Pages of this emoji picker.
  70. *
  71. * @type {Array<goog.ui.emoji.EmojiPalette>}
  72. * @private
  73. */
  74. this.pages_ = [];
  75. /**
  76. * Keeps track of which pages in the picker have been loaded. Used for delayed
  77. * loading of tabs.
  78. *
  79. * @type {Array<boolean>}
  80. * @private
  81. */
  82. this.pageLoadStatus_ = [];
  83. /**
  84. * Tabpane to hold the pages of this emojipicker.
  85. *
  86. * @type {goog.ui.TabPane}
  87. * @private
  88. */
  89. this.tabPane_ = null;
  90. this.getHandler().listen(
  91. this, goog.ui.Component.EventType.ACTION, this.onEmojiPaletteAction_);
  92. };
  93. goog.inherits(goog.ui.emoji.EmojiPicker, goog.ui.Component);
  94. /**
  95. * Default number of rows per grid of emoji.
  96. *
  97. * @type {number}
  98. */
  99. goog.ui.emoji.EmojiPicker.DEFAULT_NUM_ROWS = 5;
  100. /**
  101. * Default number of columns per grid of emoji.
  102. *
  103. * @type {number}
  104. */
  105. goog.ui.emoji.EmojiPicker.DEFAULT_NUM_COLS = 10;
  106. /**
  107. * Default location of the tabs in relation to the emoji grids.
  108. *
  109. * @type {goog.ui.TabPane.TabLocation}
  110. */
  111. goog.ui.emoji.EmojiPicker.DEFAULT_TAB_LOCATION =
  112. goog.ui.TabPane.TabLocation.TOP;
  113. /** @private {goog.ui.emoji.Emoji} */
  114. goog.ui.emoji.EmojiPicker.prototype.selectedEmoji_;
  115. /** @private {goog.ui.emoji.EmojiPaletteRenderer} */
  116. goog.ui.emoji.EmojiPicker.prototype.renderer_;
  117. /**
  118. * Number of rows per grid of emoji.
  119. *
  120. * @type {number}
  121. * @private
  122. */
  123. goog.ui.emoji.EmojiPicker.prototype.numRows_ =
  124. goog.ui.emoji.EmojiPicker.DEFAULT_NUM_ROWS;
  125. /**
  126. * Number of columns per grid of emoji.
  127. *
  128. * @type {number}
  129. * @private
  130. */
  131. goog.ui.emoji.EmojiPicker.prototype.numCols_ =
  132. goog.ui.emoji.EmojiPicker.DEFAULT_NUM_COLS;
  133. /**
  134. * Whether the number of rows in the picker should be automatically determined
  135. * by the specified number of columns so as to minimize/eliminate jitter when
  136. * switching between tabs.
  137. *
  138. * @type {boolean}
  139. * @private
  140. */
  141. goog.ui.emoji.EmojiPicker.prototype.autoSizeByColumnCount_ = true;
  142. /**
  143. * Location of the tabs for the picker tabpane.
  144. *
  145. * @type {goog.ui.TabPane.TabLocation}
  146. * @private
  147. */
  148. goog.ui.emoji.EmojiPicker.prototype.tabLocation_ =
  149. goog.ui.emoji.EmojiPicker.DEFAULT_TAB_LOCATION;
  150. /**
  151. * Whether the component is focusable.
  152. * @type {boolean}
  153. * @private
  154. */
  155. goog.ui.emoji.EmojiPicker.prototype.focusable_ = true;
  156. /**
  157. * Url of the img that should be used for cells in the emoji picker that are
  158. * not filled with emoji, i.e., after all the emoji have already been placed
  159. * on a page.
  160. *
  161. * @type {string}
  162. * @private
  163. */
  164. goog.ui.emoji.EmojiPicker.prototype.defaultImgUrl_;
  165. /**
  166. * If present, indicates a prefix that should be prepended to all URLs
  167. * of images in this emojipicker. This provides an optimization if the URLs
  168. * are long, so that the client does not have to send a long string for each
  169. * emoji.
  170. *
  171. * @type {string|undefined}
  172. * @private
  173. */
  174. goog.ui.emoji.EmojiPicker.prototype.urlPrefix_;
  175. /**
  176. * If true, delay loading the images for the emojipalettes until after
  177. * construction. This gives a better user experience before the images are in
  178. * the cache, since other widgets waiting for construction of the emojipalettes
  179. * won't have to wait for all the images (which may be a substantial amount) to
  180. * load.
  181. *
  182. * @type {boolean}
  183. * @private
  184. */
  185. goog.ui.emoji.EmojiPicker.prototype.delayedLoad_ = false;
  186. /**
  187. * Whether to use progressive rendering in the emojipicker's palette, if using
  188. * sprited imgs. If true, then uses img tags, which most browsers render
  189. * progressively (i.e., as the data comes in). If false, then uses div tags
  190. * with the background-image, which some newer browsers render progressively
  191. * but older ones do not.
  192. *
  193. * @type {boolean}
  194. * @private
  195. */
  196. goog.ui.emoji.EmojiPicker.prototype.progressiveRender_ = false;
  197. /**
  198. * Whether to require the caller to manually specify when to start loading
  199. * animated emoji. This is primarily for unittests to be able to test the
  200. * structure of the emojipicker palettes before and after the animated emoji
  201. * have been loaded.
  202. *
  203. * @type {boolean}
  204. * @private
  205. */
  206. goog.ui.emoji.EmojiPicker.prototype.manualLoadOfAnimatedEmoji_ = false;
  207. /**
  208. * Index of the active page in the picker.
  209. *
  210. * @type {number}
  211. * @private
  212. */
  213. goog.ui.emoji.EmojiPicker.prototype.activePage_ = -1;
  214. /**
  215. * Adds a group of emoji to the picker.
  216. *
  217. * @param {string|Element} title Title for the group.
  218. * @param {Array<Array<string>>} emojiGroup A new group of emoji to be added
  219. * Each internal array contains [emojiUrl, emojiId].
  220. */
  221. goog.ui.emoji.EmojiPicker.prototype.addEmojiGroup = function(
  222. title, emojiGroup) {
  223. this.emoji_.push({title: title, emoji: emojiGroup});
  224. };
  225. /**
  226. * Gets the number of rows per grid in the emoji picker.
  227. *
  228. * @return {number} number of rows per grid.
  229. */
  230. goog.ui.emoji.EmojiPicker.prototype.getNumRows = function() {
  231. return this.numRows_;
  232. };
  233. /**
  234. * Gets the number of columns per grid in the emoji picker.
  235. *
  236. * @return {number} number of columns per grid.
  237. */
  238. goog.ui.emoji.EmojiPicker.prototype.getNumColumns = function() {
  239. return this.numCols_;
  240. };
  241. /**
  242. * Sets the number of rows per grid in the emoji picker. This should only be
  243. * called before the picker has been rendered.
  244. *
  245. * @param {number} numRows Number of rows per grid.
  246. */
  247. goog.ui.emoji.EmojiPicker.prototype.setNumRows = function(numRows) {
  248. this.numRows_ = numRows;
  249. };
  250. /**
  251. * Sets the number of columns per grid in the emoji picker. This should only be
  252. * called before the picker has been rendered.
  253. *
  254. * @param {number} numCols Number of columns per grid.
  255. */
  256. goog.ui.emoji.EmojiPicker.prototype.setNumColumns = function(numCols) {
  257. this.numCols_ = numCols;
  258. };
  259. /**
  260. * Sets whether to automatically size the emojipicker based on the number of
  261. * columns and the number of emoji in each group, so as to reduce jitter.
  262. *
  263. * @param {boolean} autoSize Whether to automatically size the picker.
  264. */
  265. goog.ui.emoji.EmojiPicker.prototype.setAutoSizeByColumnCount = function(
  266. autoSize) {
  267. this.autoSizeByColumnCount_ = autoSize;
  268. };
  269. /**
  270. * Sets the location of the tabs in relation to the emoji grids. This should
  271. * only be called before the picker has been rendered.
  272. *
  273. * @param {goog.ui.TabPane.TabLocation} tabLocation The location of the tabs.
  274. */
  275. goog.ui.emoji.EmojiPicker.prototype.setTabLocation = function(tabLocation) {
  276. this.tabLocation_ = tabLocation;
  277. };
  278. /**
  279. * Sets whether loading of images should be delayed until after dom creation.
  280. * Thus, this function must be called before {@link #createDom}. If set to true,
  281. * the client must call {@link #loadImages} when they wish the images to be
  282. * loaded.
  283. *
  284. * @param {boolean} shouldDelay Whether to delay loading the images.
  285. */
  286. goog.ui.emoji.EmojiPicker.prototype.setDelayedLoad = function(shouldDelay) {
  287. this.delayedLoad_ = shouldDelay;
  288. };
  289. /**
  290. * Sets whether to require the caller to manually specify when to start loading
  291. * animated emoji. This is primarily for unittests to be able to test the
  292. * structure of the emojipicker palettes before and after the animated emoji
  293. * have been loaded. This only affects sprited emojipickers with sprite data
  294. * for animated emoji.
  295. *
  296. * @param {boolean} manual Whether to load animated emoji manually.
  297. */
  298. goog.ui.emoji.EmojiPicker.prototype.setManualLoadOfAnimatedEmoji = function(
  299. manual) {
  300. this.manualLoadOfAnimatedEmoji_ = manual;
  301. };
  302. /**
  303. * Returns true if the component is focusable, false otherwise. The default
  304. * is true. Focusable components always have a tab index and allocate a key
  305. * handler to handle keyboard events while focused.
  306. * @return {boolean} Whether the component is focusable.
  307. */
  308. goog.ui.emoji.EmojiPicker.prototype.isFocusable = function() {
  309. return this.focusable_;
  310. };
  311. /**
  312. * Sets whether the component is focusable. The default is true.
  313. * Focusable components always have a tab index and allocate a key handler to
  314. * handle keyboard events while focused.
  315. * @param {boolean} focusable Whether the component is focusable.
  316. */
  317. goog.ui.emoji.EmojiPicker.prototype.setFocusable = function(focusable) {
  318. this.focusable_ = focusable;
  319. for (var i = 0; i < this.pages_.length; i++) {
  320. if (this.pages_[i]) {
  321. this.pages_[i].setSupportedState(
  322. goog.ui.Component.State.FOCUSED, focusable);
  323. }
  324. }
  325. };
  326. /**
  327. * Sets the URL prefix for the emoji URLs.
  328. *
  329. * @param {string} urlPrefix Prefix that should be prepended to all URLs.
  330. */
  331. goog.ui.emoji.EmojiPicker.prototype.setUrlPrefix = function(urlPrefix) {
  332. this.urlPrefix_ = urlPrefix;
  333. };
  334. /**
  335. * Sets the progressive rendering aspect of this emojipicker. Must be called
  336. * before createDom to have an effect.
  337. *
  338. * @param {boolean} progressive Whether this picker should render progressively.
  339. */
  340. goog.ui.emoji.EmojiPicker.prototype.setProgressiveRender = function(
  341. progressive) {
  342. this.progressiveRender_ = progressive;
  343. };
  344. /**
  345. * Adjusts the number of rows to be the maximum row count out of all the emoji
  346. * groups, in order to prevent jitter in switching among the tabs.
  347. *
  348. * @private
  349. */
  350. goog.ui.emoji.EmojiPicker.prototype.adjustNumRowsIfNecessary_ = function() {
  351. var currentMax = 0;
  352. for (var i = 0; i < this.emoji_.length; i++) {
  353. var numEmoji = this.emoji_[i].emoji.length;
  354. var rowsNeeded = Math.ceil(numEmoji / this.numCols_);
  355. if (rowsNeeded > currentMax) {
  356. currentMax = rowsNeeded;
  357. }
  358. }
  359. this.setNumRows(currentMax);
  360. };
  361. /**
  362. * Causes the emoji imgs to be loaded into the picker. Used for delayed loading.
  363. * No-op if delayed loading is not set.
  364. */
  365. goog.ui.emoji.EmojiPicker.prototype.loadImages = function() {
  366. if (!this.delayedLoad_) {
  367. return;
  368. }
  369. // Load the first page only
  370. this.loadPage_(0);
  371. this.activePage_ = 0;
  372. };
  373. /**
  374. * @override
  375. * @suppress {deprecated} Using deprecated goog.ui.TabPane.
  376. */
  377. goog.ui.emoji.EmojiPicker.prototype.createDom = function() {
  378. this.setElementInternal(this.getDomHelper().createDom(goog.dom.TagName.DIV));
  379. if (this.autoSizeByColumnCount_) {
  380. this.adjustNumRowsIfNecessary_();
  381. }
  382. if (this.emoji_.length == 0) {
  383. throw Error('Must add some emoji to the picker');
  384. }
  385. // If there is more than one group of emoji, we construct a tabpane
  386. if (this.emoji_.length > 1) {
  387. // Give the tabpane a div to use as its content element, since tabpane
  388. // overwrites the CSS class of the element it's passed
  389. var div = this.getDomHelper().createDom(goog.dom.TagName.DIV);
  390. this.getElement().appendChild(div);
  391. this.tabPane_ = new goog.ui.TabPane(
  392. div, this.tabLocation_, this.getDomHelper(), true /* use MOUSEDOWN */);
  393. }
  394. this.renderer_ = this.progressiveRender_ ?
  395. new goog.ui.emoji.ProgressiveEmojiPaletteRenderer(this.defaultImgUrl_) :
  396. new goog.ui.emoji.EmojiPaletteRenderer(this.defaultImgUrl_);
  397. for (var i = 0; i < this.emoji_.length; i++) {
  398. var emoji = this.emoji_[i].emoji;
  399. var page = this.delayedLoad_ ? this.createPlaceholderEmojiPage_(emoji) :
  400. this.createEmojiPage_(emoji, i);
  401. this.pages_.push(page);
  402. }
  403. this.activePage_ = 0;
  404. this.getElement().tabIndex = 0;
  405. };
  406. /**
  407. * Used by unittests to manually load the animated emoji for this picker.
  408. */
  409. goog.ui.emoji.EmojiPicker.prototype.manuallyLoadAnimatedEmoji = function() {
  410. for (var i = 0; i < this.pages_.length; i++) {
  411. this.pages_[i].loadAnimatedEmoji();
  412. }
  413. };
  414. /**
  415. * Creates a page if it has not already been loaded. This has the side effects
  416. * of setting the load status of the page to true.
  417. *
  418. * @param {Array<Array<string>>} emoji Emoji for this page. See
  419. * {@link addEmojiGroup} for more details.
  420. * @param {number} index Index of the page in the emojipicker.
  421. * @return {goog.ui.emoji.EmojiPalette} the emoji page.
  422. * @private
  423. */
  424. goog.ui.emoji.EmojiPicker.prototype.createEmojiPage_ = function(emoji, index) {
  425. // Safeguard against trying to create the same page twice
  426. if (this.pageLoadStatus_[index]) {
  427. return null;
  428. }
  429. var palette = new goog.ui.emoji.EmojiPalette(
  430. emoji, this.urlPrefix_, this.renderer_, this.getDomHelper());
  431. if (!this.manualLoadOfAnimatedEmoji_) {
  432. palette.loadAnimatedEmoji();
  433. }
  434. palette.setSize(this.numCols_, this.numRows_);
  435. palette.setSupportedState(goog.ui.Component.State.FOCUSED, this.focusable_);
  436. palette.createDom();
  437. palette.setParent(this);
  438. this.pageLoadStatus_[index] = true;
  439. return palette;
  440. };
  441. /**
  442. * Returns an array of emoji whose real URLs have been replaced with the
  443. * default img URL. Used for delayed loading.
  444. *
  445. * @param {Array<Array<string>>} emoji Original emoji array.
  446. * @return {!Array<!Array<string>>} emoji array with all emoji pointing to the
  447. * default img.
  448. * @private
  449. */
  450. goog.ui.emoji.EmojiPicker.prototype.getPlaceholderEmoji_ = function(emoji) {
  451. var placeholderEmoji = [];
  452. for (var i = 0; i < emoji.length; i++) {
  453. placeholderEmoji.push([this.defaultImgUrl_, emoji[i][1]]);
  454. }
  455. return placeholderEmoji;
  456. };
  457. /**
  458. * Creates an emoji page using placeholder emoji pointing to the default
  459. * img instead of the real emoji. Used for delayed loading.
  460. *
  461. * @param {Array<Array<string>>} emoji Emoji for this page. See
  462. * {@link addEmojiGroup} for more details.
  463. * @return {!goog.ui.emoji.EmojiPalette} the emoji page.
  464. * @private
  465. */
  466. goog.ui.emoji.EmojiPicker.prototype.createPlaceholderEmojiPage_ = function(
  467. emoji) {
  468. var placeholderEmoji = this.getPlaceholderEmoji_(emoji);
  469. var palette = new goog.ui.emoji.EmojiPalette(
  470. placeholderEmoji,
  471. null, // no url prefix
  472. this.renderer_, this.getDomHelper());
  473. palette.setSize(this.numCols_, this.numRows_);
  474. palette.setSupportedState(goog.ui.Component.State.FOCUSED, this.focusable_);
  475. palette.createDom();
  476. palette.setParent(this);
  477. return palette;
  478. };
  479. /**
  480. * EmojiPickers cannot be used to decorate pre-existing html, since the
  481. * structure they build is fairly complicated.
  482. * @param {Element} element Element to decorate.
  483. * @return {boolean} Returns always false.
  484. * @override
  485. */
  486. goog.ui.emoji.EmojiPicker.prototype.canDecorate = function(element) {
  487. return false;
  488. };
  489. /**
  490. * @override
  491. * @suppress {deprecated} Using deprecated goog.ui.TabPane.
  492. */
  493. goog.ui.emoji.EmojiPicker.prototype.enterDocument = function() {
  494. goog.ui.emoji.EmojiPicker.superClass_.enterDocument.call(this);
  495. for (var i = 0; i < this.pages_.length; i++) {
  496. this.pages_[i].enterDocument();
  497. var pageElement = this.pages_[i].getElement();
  498. // Add a new tab to the tabpane if there's more than one group of emoji.
  499. // If there is just one group of emoji, then we simply use the single
  500. // page's element as the content for the picker
  501. if (this.pages_.length > 1) {
  502. // Create a simple default title containg the page number if the title
  503. // was not provided in the emoji group params
  504. var title = this.emoji_[i].title || (i + 1);
  505. this.tabPane_.addPage(
  506. new goog.ui.TabPane.TabPage(pageElement, title, this.getDomHelper()));
  507. } else {
  508. this.getElement().appendChild(pageElement);
  509. }
  510. }
  511. // Initialize listeners. Note that we need to initialize this listener
  512. // after createDom, because addPage causes the goog.ui.TabPane.Events.CHANGE
  513. // event to fire, but we only want the handler (which loads delayed images)
  514. // to run after the picker has been constructed.
  515. if (this.tabPane_) {
  516. this.getHandler().listen(
  517. this.tabPane_, goog.ui.TabPane.Events.CHANGE, this.onPageChanged_);
  518. // Make the tabpane unselectable so that changing tabs doesn't disturb the
  519. // cursor
  520. goog.style.setUnselectable(this.tabPane_.getElement(), true);
  521. }
  522. this.getElement().unselectable = 'on';
  523. };
  524. /** @override */
  525. goog.ui.emoji.EmojiPicker.prototype.exitDocument = function() {
  526. goog.ui.emoji.EmojiPicker.superClass_.exitDocument.call(this);
  527. for (var i = 0; i < this.pages_.length; i++) {
  528. this.pages_[i].exitDocument();
  529. }
  530. };
  531. /** @override */
  532. goog.ui.emoji.EmojiPicker.prototype.disposeInternal = function() {
  533. goog.ui.emoji.EmojiPicker.superClass_.disposeInternal.call(this);
  534. if (this.tabPane_) {
  535. this.tabPane_.dispose();
  536. this.tabPane_ = null;
  537. }
  538. for (var i = 0; i < this.pages_.length; i++) {
  539. this.pages_[i].dispose();
  540. }
  541. this.pages_.length = 0;
  542. };
  543. /**
  544. * @return {string} CSS class for the root element of EmojiPicker.
  545. */
  546. goog.ui.emoji.EmojiPicker.prototype.getCssClass = function() {
  547. return goog.getCssName('goog-ui-emojipicker');
  548. };
  549. /**
  550. * Returns the currently selected emoji from this picker. If the picker is
  551. * using the URL prefix optimization, allocates a new emoji object with the
  552. * full URL. This method is meant to be used by clients of the emojipicker,
  553. * e.g., in a listener on goog.ui.component.EventType.ACTION that wants to use
  554. * the just-selected emoji.
  555. *
  556. * @return {goog.ui.emoji.Emoji} The currently selected emoji from this picker.
  557. */
  558. goog.ui.emoji.EmojiPicker.prototype.getSelectedEmoji = function() {
  559. return this.urlPrefix_ ?
  560. new goog.ui.emoji.Emoji(
  561. this.urlPrefix_ + this.selectedEmoji_.getId(),
  562. this.selectedEmoji_.getId()) :
  563. this.selectedEmoji_;
  564. };
  565. /**
  566. * Returns the number of emoji groups in this picker.
  567. *
  568. * @return {number} The number of emoji groups in this picker.
  569. */
  570. goog.ui.emoji.EmojiPicker.prototype.getNumEmojiGroups = function() {
  571. return this.emoji_.length;
  572. };
  573. /**
  574. * Returns a page from the picker. This should be considered protected, and is
  575. * ONLY FOR TESTING.
  576. *
  577. * @param {number} index Index of the page to return.
  578. * @return {goog.ui.emoji.EmojiPalette?} the page at the specified index or null
  579. * if none exists.
  580. */
  581. goog.ui.emoji.EmojiPicker.prototype.getPage = function(index) {
  582. return this.pages_[index];
  583. };
  584. /**
  585. * Returns all the pages from the picker. This should be considered protected,
  586. * and is ONLY FOR TESTING.
  587. *
  588. * @return {Array<goog.ui.emoji.EmojiPalette>?} the pages in the picker or
  589. * null if none exist.
  590. */
  591. goog.ui.emoji.EmojiPicker.prototype.getPages = function() {
  592. return this.pages_;
  593. };
  594. /**
  595. * Returns the tabpane if this is a multipage picker. This should be considered
  596. * protected, and is ONLY FOR TESTING.
  597. *
  598. * @return {goog.ui.TabPane} the tabpane if it is a multipage picker,
  599. * or null if it does not exist or is a single page picker.
  600. */
  601. goog.ui.emoji.EmojiPicker.prototype.getTabPane = function() {
  602. return this.tabPane_;
  603. };
  604. /**
  605. * @return {goog.ui.emoji.EmojiPalette} The active page of the emoji picker.
  606. * @private
  607. */
  608. goog.ui.emoji.EmojiPicker.prototype.getActivePage_ = function() {
  609. return this.pages_[this.activePage_];
  610. };
  611. /**
  612. * Handles actions from the EmojiPalettes that this picker contains.
  613. *
  614. * @param {goog.ui.Component.EventType} e The event object.
  615. * @private
  616. */
  617. goog.ui.emoji.EmojiPicker.prototype.onEmojiPaletteAction_ = function(e) {
  618. this.selectedEmoji_ = this.getActivePage_().getSelectedEmoji();
  619. };
  620. /**
  621. * Handles changes in the active page in the tabpane.
  622. *
  623. * @param {goog.ui.TabPaneEvent} e The event object.
  624. * @private
  625. */
  626. goog.ui.emoji.EmojiPicker.prototype.onPageChanged_ = function(e) {
  627. var index = /** @type {number} */ (e.page.getIndex());
  628. this.loadPage_(index);
  629. this.activePage_ = index;
  630. };
  631. /**
  632. * Loads a page into the picker if it has not yet been loaded.
  633. *
  634. * @param {number} index Index of the page to load.
  635. * @private
  636. * @suppress {deprecated} Using deprecated goog.ui.TabPane.
  637. */
  638. goog.ui.emoji.EmojiPicker.prototype.loadPage_ = function(index) {
  639. if (index < 0 || index > this.pages_.length) {
  640. throw Error('Index out of bounds');
  641. }
  642. if (!this.pageLoadStatus_[index]) {
  643. var oldPage = this.pages_[index];
  644. this.pages_[index] = this.createEmojiPage_(this.emoji_[index].emoji, index);
  645. this.pages_[index].enterDocument();
  646. var pageElement = this.pages_[index].getElement();
  647. if (this.pages_.length > 1) {
  648. this.tabPane_.removePage(index);
  649. var title = this.emoji_[index].title || (index + 1);
  650. this.tabPane_.addPage(
  651. new goog.ui.TabPane.TabPage(pageElement, title, this.getDomHelper()),
  652. index);
  653. this.tabPane_.setSelectedIndex(index);
  654. } else {
  655. var el = this.getElement();
  656. el.appendChild(pageElement);
  657. }
  658. if (oldPage) {
  659. oldPage.dispose();
  660. }
  661. }
  662. };