123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- // Copyright 2006 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview A base ratings widget that allows the user to select a rating,
- * like "star video" in Google Video. This fires a "change" event when the user
- * selects a rating.
- *
- * Keyboard:
- * ESC = Clear (if supported)
- * Home = 1 star
- * End = Full rating
- * Left arrow = Decrease rating
- * Right arrow = Increase rating
- * 0 = Clear (if supported)
- * 1 - 9 = nth star
- *
- * @see ../demos/ratings.html
- */
- goog.provide('goog.ui.Ratings');
- goog.provide('goog.ui.Ratings.EventType');
- goog.require('goog.a11y.aria');
- goog.require('goog.a11y.aria.Role');
- goog.require('goog.a11y.aria.State');
- goog.require('goog.asserts');
- goog.require('goog.dom');
- goog.require('goog.dom.TagName');
- goog.require('goog.dom.classlist');
- goog.require('goog.events.EventType');
- goog.require('goog.ui.Component');
- /**
- * A UI Control used for rating things, i.e. videos on Google Video.
- * @param {Array<string>=} opt_ratings Ratings. Default: [1,2,3,4,5].
- * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
- * @constructor
- * @extends {goog.ui.Component}
- */
- goog.ui.Ratings = function(opt_ratings, opt_domHelper) {
- goog.ui.Component.call(this, opt_domHelper);
- /**
- * Ordered ratings that can be picked, Default: [1,2,3,4,5]
- * @type {Array<string>}
- * @private
- */
- this.ratings_ = opt_ratings || ['1', '2', '3', '4', '5'];
- /**
- * Array containing references to the star elements
- * @type {Array<Element>}
- * @private
- */
- this.stars_ = [];
- // Awkward name because the obvious name is taken by subclasses already.
- /**
- * Whether the control is enabled.
- * @type {boolean}
- * @private
- */
- this.isEnabled_ = true;
- /**
- * The last index to be highlighted
- * @type {number}
- * @private
- */
- this.highlightedIndex_ = -1;
- /**
- * The currently selected index
- * @type {number}
- * @private
- */
- this.selectedIndex_ = -1;
- /**
- * An attached form field to set the value to
- * @type {HTMLInputElement|HTMLSelectElement|null}
- * @private
- */
- this.attachedFormField_ = null;
- };
- goog.inherits(goog.ui.Ratings, goog.ui.Component);
- goog.tagUnsealableClass(goog.ui.Ratings);
- /**
- * Default CSS class to be applied to the root element of components rendered
- * by this renderer.
- * @type {string}
- */
- goog.ui.Ratings.CSS_CLASS = goog.getCssName('goog-ratings');
- /**
- * Enums for Ratings event type.
- * @enum {string}
- */
- goog.ui.Ratings.EventType = {
- CHANGE: 'change',
- HIGHLIGHT_CHANGE: 'highlightchange',
- HIGHLIGHT: 'highlight',
- UNHIGHLIGHT: 'unhighlight'
- };
- /**
- * Decorate a HTML structure already in the document. Expects the structure:
- * <pre>
- * - div
- * - select
- * - option 1 #text = 1 star
- * - option 2 #text = 2 stars
- * - option 3 #text = 3 stars
- * - option N (where N is max number of ratings)
- * </pre>
- *
- * The div can contain other elements for graceful degredation, but they will be
- * hidden when the decoration occurs.
- *
- * @param {Element} el Div element to decorate.
- * @override
- */
- goog.ui.Ratings.prototype.decorateInternal = function(el) {
- var select = goog.dom.getElementsByTagName(
- goog.dom.TagName.SELECT, goog.asserts.assert(el))[0];
- if (!select) {
- throw Error(
- 'Can not decorate ' + el + ', with Ratings. Must ' +
- 'contain select box');
- }
- this.ratings_.length = 0;
- for (var i = 0, n = select.options.length; i < n; i++) {
- var option = select.options[i];
- this.ratings_.push(option.text);
- }
- this.setSelectedIndex(select.selectedIndex);
- select.style.display = 'none';
- this.attachedFormField_ = /** @type {HTMLSelectElement} */ (select);
- this.createDom();
- el.insertBefore(this.getElement(), select);
- };
- /**
- * Render the rating widget inside the provided element. This will override the
- * current content of the element.
- * @override
- */
- goog.ui.Ratings.prototype.enterDocument = function() {
- var el = this.getElement();
- goog.asserts.assert(el, 'The DOM element for ratings cannot be null.');
- goog.ui.Ratings.base(this, 'enterDocument');
- el.tabIndex = 0;
- goog.dom.classlist.add(el, this.getCssClass());
- goog.a11y.aria.setRole(el, goog.a11y.aria.Role.SLIDER);
- goog.a11y.aria.setState(el, goog.a11y.aria.State.VALUEMIN, 0);
- var max = this.ratings_.length - 1;
- goog.a11y.aria.setState(el, goog.a11y.aria.State.VALUEMAX, max);
- var handler = this.getHandler();
- handler.listen(el, 'keydown', this.onKeyDown_);
- // Create the elements for the stars
- for (var i = 0; i < this.ratings_.length; i++) {
- var star = this.getDomHelper().createDom(goog.dom.TagName.SPAN, {
- 'title': this.ratings_[i],
- 'class': this.getClassName_(i, false),
- 'index': i
- });
- this.stars_.push(star);
- el.appendChild(star);
- }
- handler.listen(el, goog.events.EventType.CLICK, this.onClick_);
- handler.listen(el, goog.events.EventType.MOUSEOUT, this.onMouseOut_);
- handler.listen(el, goog.events.EventType.MOUSEOVER, this.onMouseOver_);
- this.highlightIndex_(this.selectedIndex_);
- };
- /**
- * Should be called when the widget is removed from the document but may be
- * reused. This removes all the listeners the widget has attached and destroys
- * the DOM nodes it uses.
- * @override
- */
- goog.ui.Ratings.prototype.exitDocument = function() {
- goog.ui.Ratings.superClass_.exitDocument.call(this);
- for (var i = 0; i < this.stars_.length; i++) {
- this.getDomHelper().removeNode(this.stars_[i]);
- }
- this.stars_.length = 0;
- };
- /** @override */
- goog.ui.Ratings.prototype.disposeInternal = function() {
- goog.ui.Ratings.superClass_.disposeInternal.call(this);
- this.ratings_.length = 0;
- };
- /**
- * Returns the base CSS class used by subcomponents of this component.
- * @return {string} Component-specific CSS class.
- */
- goog.ui.Ratings.prototype.getCssClass = function() {
- return goog.ui.Ratings.CSS_CLASS;
- };
- /**
- * Sets the selected index. If the provided index is greater than the number of
- * ratings then the max is set. 0 is the first item, -1 is no selection.
- * @param {number} index The index of the rating to select.
- */
- goog.ui.Ratings.prototype.setSelectedIndex = function(index) {
- index = Math.max(-1, Math.min(index, this.ratings_.length - 1));
- if (index != this.selectedIndex_) {
- this.selectedIndex_ = index;
- this.highlightIndex_(this.selectedIndex_);
- if (this.attachedFormField_) {
- if (this.attachedFormField_.tagName == goog.dom.TagName.SELECT) {
- this.attachedFormField_.selectedIndex = index;
- } else {
- this.attachedFormField_.value =
- /** @type {string} */ (this.getValue());
- }
- var ratingsElement = this.getElement();
- goog.asserts.assert(
- ratingsElement, 'The DOM ratings element cannot be null.');
- goog.a11y.aria.setState(
- ratingsElement, goog.a11y.aria.State.VALUENOW, this.ratings_[index]);
- }
- this.dispatchEvent(goog.ui.Ratings.EventType.CHANGE);
- }
- };
- /**
- * @return {number} The index of the currently selected rating.
- */
- goog.ui.Ratings.prototype.getSelectedIndex = function() {
- return this.selectedIndex_;
- };
- /**
- * Returns the rating value of the currently selected rating
- * @return {?string} The value of the currently selected rating (or null).
- */
- goog.ui.Ratings.prototype.getValue = function() {
- return this.selectedIndex_ == -1 ? null : this.ratings_[this.selectedIndex_];
- };
- /**
- * Returns the index of the currently highlighted rating, -1 if the mouse isn't
- * currently over the widget
- * @return {number} The index of the currently highlighted rating.
- */
- goog.ui.Ratings.prototype.getHighlightedIndex = function() {
- return this.highlightedIndex_;
- };
- /**
- * Returns the value of the currently highlighted rating, null if the mouse
- * isn't currently over the widget
- * @return {?string} The value of the currently highlighted rating, or null.
- */
- goog.ui.Ratings.prototype.getHighlightedValue = function() {
- return this.highlightedIndex_ == -1 ? null :
- this.ratings_[this.highlightedIndex_];
- };
- /**
- * Sets the array of ratings that the comonent
- * @param {Array<string>} ratings Array of value to use as ratings.
- */
- goog.ui.Ratings.prototype.setRatings = function(ratings) {
- this.ratings_ = ratings;
- // TODO(user): If rendered update stars
- };
- /**
- * Gets the array of ratings that the component
- * @return {Array<string>} Array of ratings.
- */
- goog.ui.Ratings.prototype.getRatings = function() {
- return this.ratings_;
- };
- /**
- * Attaches an input or select element to the ratings widget. The value or
- * index of the field will be updated along with the ratings widget.
- * @param {HTMLSelectElement|HTMLInputElement} field The field to attach to.
- */
- goog.ui.Ratings.prototype.setAttachedFormField = function(field) {
- this.attachedFormField_ = field;
- };
- /**
- * Returns the attached input or select element to the ratings widget.
- * @return {HTMLSelectElement|HTMLInputElement|null} The attached form field.
- */
- goog.ui.Ratings.prototype.getAttachedFormField = function() {
- return this.attachedFormField_;
- };
- /**
- * Enables or disables the ratings control.
- * @param {boolean} enable Whether to enable or disable the control.
- */
- goog.ui.Ratings.prototype.setEnabled = function(enable) {
- this.isEnabled_ = enable;
- if (!enable) {
- // Undo any highlighting done during mouseover when disabling the control
- // and highlight the last selected rating.
- this.resetHighlights_();
- }
- };
- /**
- * @return {boolean} Whether the ratings control is enabled.
- */
- goog.ui.Ratings.prototype.isEnabled = function() {
- return this.isEnabled_;
- };
- /**
- * Handle the mouse moving over a star.
- * @param {goog.events.BrowserEvent} e The browser event.
- * @private
- */
- goog.ui.Ratings.prototype.onMouseOver_ = function(e) {
- if (!this.isEnabled()) {
- return;
- }
- if (goog.isDef(e.target.index)) {
- var n = e.target.index;
- if (this.highlightedIndex_ != n) {
- this.highlightIndex_(n);
- this.highlightedIndex_ = n;
- this.dispatchEvent(goog.ui.Ratings.EventType.HIGHLIGHT_CHANGE);
- this.dispatchEvent(goog.ui.Ratings.EventType.HIGHLIGHT);
- }
- }
- };
- /**
- * Handle the mouse moving over a star.
- * @param {goog.events.BrowserEvent} e The browser event.
- * @private
- */
- goog.ui.Ratings.prototype.onMouseOut_ = function(e) {
- // Only remove the highlight if the mouse is not moving to another star
- if (e.relatedTarget && !goog.isDef(e.relatedTarget.index)) {
- this.resetHighlights_();
- }
- };
- /**
- * Handle the mouse moving over a star.
- * @param {goog.events.BrowserEvent} e The browser event.
- * @private
- */
- goog.ui.Ratings.prototype.onClick_ = function(e) {
- if (!this.isEnabled()) {
- return;
- }
- if (goog.isDef(e.target.index)) {
- this.setSelectedIndex(e.target.index);
- }
- };
- /**
- * Handle the key down event. 0 = unselected in this case, 1 = the first rating
- * @param {goog.events.BrowserEvent} e The browser event.
- * @private
- */
- goog.ui.Ratings.prototype.onKeyDown_ = function(e) {
- if (!this.isEnabled()) {
- return;
- }
- switch (e.keyCode) {
- case 27: // esc
- this.setSelectedIndex(-1);
- break;
- case 36: // home
- this.setSelectedIndex(0);
- break;
- case 35: // end
- this.setSelectedIndex(this.ratings_.length);
- break;
- case 37: // left arrow
- this.setSelectedIndex(this.getSelectedIndex() - 1);
- break;
- case 39: // right arrow
- this.setSelectedIndex(this.getSelectedIndex() + 1);
- break;
- default:
- // Detected a numeric key stroke, such as 0 - 9. 0 clears, 1 is first
- // star, 9 is 9th star or last if there are less than 9 stars.
- var num = parseInt(String.fromCharCode(e.keyCode), 10);
- if (!isNaN(num)) {
- this.setSelectedIndex(num - 1);
- }
- }
- };
- /**
- * Resets the highlights to the selected rating to undo highlights due to hover
- * effects.
- * @private
- */
- goog.ui.Ratings.prototype.resetHighlights_ = function() {
- this.highlightIndex_(this.selectedIndex_);
- this.highlightedIndex_ = -1;
- this.dispatchEvent(goog.ui.Ratings.EventType.HIGHLIGHT_CHANGE);
- this.dispatchEvent(goog.ui.Ratings.EventType.UNHIGHLIGHT);
- };
- /**
- * Highlights the ratings up to a specific index
- * @param {number} n Index to highlight.
- * @private
- */
- goog.ui.Ratings.prototype.highlightIndex_ = function(n) {
- for (var i = 0, star; star = this.stars_[i]; i++) {
- goog.dom.classlist.set(star, this.getClassName_(i, i <= n));
- }
- };
- /**
- * Get the class name for a given rating. All stars have the class:
- * goog-ratings-star.
- * Other possible classnames dependent on position and state are:
- * goog-ratings-firststar-on
- * goog-ratings-firststar-off
- * goog-ratings-midstar-on
- * goog-ratings-midstar-off
- * goog-ratings-laststar-on
- * goog-ratings-laststar-off
- * @param {number} i Index to get class name for.
- * @param {boolean} on Whether it should be on.
- * @return {string} The class name.
- * @private
- */
- goog.ui.Ratings.prototype.getClassName_ = function(i, on) {
- var className;
- var enabledClassName;
- var baseClass = this.getCssClass();
- if (i === 0) {
- className = goog.getCssName(baseClass, 'firststar');
- } else if (i == this.ratings_.length - 1) {
- className = goog.getCssName(baseClass, 'laststar');
- } else {
- className = goog.getCssName(baseClass, 'midstar');
- }
- if (on) {
- className = goog.getCssName(className, 'on');
- } else {
- className = goog.getCssName(className, 'off');
- }
- if (this.isEnabled_) {
- enabledClassName = goog.getCssName(baseClass, 'enabled');
- } else {
- enabledClassName = goog.getCssName(baseClass, 'disabled');
- }
- return goog.getCssName(baseClass, 'star') + ' ' + className + ' ' +
- enabledClassName;
- };
|