blob: 3001c5c299d54306f228509c449d2f71985fb49c [file] [log] [blame]
// Copyright 2009 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 Provides the base goog.ui.Control and goog.ui.ControlRenderer
* for media types, as well as a media model consistent with the Yahoo Media RSS
* specification {@link http://search.yahoo.com/mrss/}.
*
* The goog.ui.media.* package is basically a set of goog.ui.ControlRenderers
* subclasses (such as goog.ui.media.Youtube, goog.ui.media.Picasa, etc) that
* should all work with the same goog.ui.Control (goog.ui.media.Media) logic.
*
* This design guarantees that all different types of medias will behave alike
* (in a base level) but will look different.
*
* In MVC terms, {@link goog.ui.media.Media} is the Controller,
* {@link goog.ui.media.MediaRenderer} + CSS definitions are the View and
* {@code goog.ui.media.MediaModel} is the data Model. Typically,
* MediaRenderer will be subclassed to provide media specific renderers.
* MediaRenderer subclasses are also responsible for defining the data model.
*
* This design is strongly patterned after:
* http://go/closure_control_subclassing
*
* goog.ui.media.MediaRenderer handles the basic common ways to display media,
* such as displaying tooltips, frames, minimize/maximize buttons, play buttons,
* etc. Its subclasses are responsible for rendering media specific DOM
* structures, like youtube flash players, picasa albums, etc.
*
* goog.ui.media.Media handles the Control of Medias, by listening to events
* and firing the appropriate actions. It knows about the existence of captions,
* minimize/maximize buttons, and takes all the actions needed to change states,
* including delegating the UI actions to MediaRenderers.
*
* Although MediaRenderer is a base class designed to be subclassed, it can
* be used by itself:
*
* <pre>
* var renderer = new goog.ui.media.MediaRenderer();
* var control = new goog.ui.media.Media('hello world', renderer);
* var control.render(goog.dom.getElement('mediaHolder'));
* </pre>
*
* It requires a few CSS rules to be defined, which you should use to control
* how the component is displayed. {@link goog.ui.ControlRenderer}s is very CSS
* intensive, which separates the UI structure (the HTML DOM elements, which is
* created by the {@code goog.ui.media.MediaRenderer}) from the UI view (which
* nodes are visible, which aren't, where they are positioned. These are defined
* on the CSS rules for each state). A few examples of CSS selectors that needs
* to be defined are:
*
* <ul>
* <li>.goog-ui-media
* <li>.goog-ui-media-hover
* <li>.goog-ui-media-selected
* </ul>
*
* If you want to have different custom renderers CSS namespaces (eg. you may
* want to show a small thumbnail, or you may want to hide the caption, etc),
* you can do so by using:
*
* <pre>
* var renderer = goog.ui.ControlRenderer.getCustomRenderer(
* goog.ui.media.MediaRenderer, 'my-custom-namespace');
* var media = new goog.ui.media.Media('', renderer);
* media.render(goog.dom.getElement('parent'));
* </pre>
*
* Which will allow you to set your own .my-custom-namespace-hover,
* .my-custom-namespace-selected CSS selectors.
*
* NOTE(user): it seems like an overkill to subclass goog.ui.Control instead of
* using a factory, but we wanted to make sure we had more control over the
* events for future media implementations. Since we intent to use it in many
* different places, it makes sense to have a more flexible design that lets us
* control the inner workings of goog.ui.Control.
*
* TODO(user): implement, as needed, the Media specific state changes UI, such
* as minimize/maximize buttons, expand/close buttons, etc.
*
*/
goog.provide('goog.ui.media.Media');
goog.provide('goog.ui.media.MediaRenderer');
goog.require('goog.asserts');
goog.require('goog.style');
goog.require('goog.ui.Component');
goog.require('goog.ui.Control');
goog.require('goog.ui.ControlRenderer');
/**
* Provides the control mechanism of media types.
*
* @param {goog.ui.media.MediaModel} dataModel The data model to be used by the
* renderer.
* @param {goog.ui.ControlRenderer=} opt_renderer Renderer used to render or
* decorate the component; defaults to {@link goog.ui.ControlRenderer}.
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
* document interaction.
* @constructor
* @extends {goog.ui.Control}
* @final
*/
goog.ui.media.Media = function(dataModel, opt_renderer, opt_domHelper) {
goog.ui.Control.call(this, null, opt_renderer, opt_domHelper);
// Sets up the data model.
this.setDataModel(dataModel);
this.setSupportedState(goog.ui.Component.State.OPENED, true);
this.setSupportedState(goog.ui.Component.State.SELECTED, true);
// TODO(user): had to do this to for mouseDownHandler not to
// e.preventDefault(), because it was not allowing the event to reach the
// flash player. figure out a better way to not e.preventDefault().
this.setAllowTextSelection(true);
// Media items don't use RTL styles, so avoid accessing computed styles to
// figure out if the control is RTL.
this.setRightToLeft(false);
};
goog.inherits(goog.ui.media.Media, goog.ui.Control);
/**
* The media data model used on the renderer.
*
* @type {goog.ui.media.MediaModel}
* @private
*/
goog.ui.media.Media.prototype.dataModel_;
/**
* Sets the media model to be used on the renderer.
* @param {goog.ui.media.MediaModel} dataModel The media model the renderer
* should use.
*/
goog.ui.media.Media.prototype.setDataModel = function(dataModel) {
this.dataModel_ = dataModel;
};
/**
* Gets the media model renderer is using.
* @return {goog.ui.media.MediaModel} The media model being used.
*/
goog.ui.media.Media.prototype.getDataModel = function() {
return this.dataModel_;
};
/**
* Base class of all media renderers. Provides the common renderer functionality
* of medias.
*
* The current common functionality shared by Medias is to have an outer frame
* that gets highlighted on mouse hover.
*
* TODO(user): implement more common UI behavior, as needed.
*
* NOTE(user): I am not enjoying how the subclasses are changing their state
* through setState() ... maybe provide abstract methods like
* goog.ui.media.MediaRenderer.prototype.preview = goog.abstractMethod;
* goog.ui.media.MediaRenderer.prototype.play = goog.abstractMethod;
* goog.ui.media.MediaRenderer.prototype.minimize = goog.abstractMethod;
* goog.ui.media.MediaRenderer.prototype.maximize = goog.abstractMethod;
* and call them on this parent class setState ?
*
* @constructor
* @extends {goog.ui.ControlRenderer}
*/
goog.ui.media.MediaRenderer = function() {
goog.ui.ControlRenderer.call(this);
};
goog.inherits(goog.ui.media.MediaRenderer, goog.ui.ControlRenderer);
/**
* Builds the common DOM structure of medias. Builds an outer div, and appends
* a child div with the {@code goog.ui.Control.getContent} content. Marks the
* caption with a {@code this.getClassClass()} + '-caption' css flag, so that
* specific renderers can hide/show the caption as desired.
*
* @param {goog.ui.Control} control The control instance.
* @return {!Element} The DOM structure that represents control.
* @override
*/
goog.ui.media.MediaRenderer.prototype.createDom = function(control) {
goog.asserts.assertInstanceof(control, goog.ui.media.Media);
var domHelper = control.getDomHelper();
var div = domHelper.createElement('div');
div.className = this.getClassNames(control).join(' ');
var dataModel = control.getDataModel();
// Only creates DOMs if the data is available.
if (dataModel.getCaption()) {
var caption = domHelper.createElement('div');
caption.className = goog.getCssName(this.getCssClass(), 'caption');
caption.appendChild(domHelper.createDom(
'p', goog.getCssName(this.getCssClass(), 'caption-text'),
dataModel.getCaption()));
domHelper.appendChild(div, caption);
}
if (dataModel.getDescription()) {
var description = domHelper.createElement('div');
description.className = goog.getCssName(this.getCssClass(), 'description');
description.appendChild(domHelper.createDom(
'p', goog.getCssName(this.getCssClass(), 'description-text'),
dataModel.getDescription()));
domHelper.appendChild(div, description);
}
// Creates thumbnails of the media.
var thumbnails = dataModel.getThumbnails() || [];
for (var index = 0; index < thumbnails.length; index++) {
var thumbnail = thumbnails[index];
var thumbnailElement = domHelper.createElement('img');
thumbnailElement.src = thumbnail.getUrl();
thumbnailElement.className = this.getThumbnailCssName(index);
// Check that the size is defined and that the size's height and width
// are defined. Undefined height and width is deprecated but still
// seems to exist in some cases.
var size = thumbnail.getSize();
if (size && goog.isDefAndNotNull(size.height) &&
goog.isDefAndNotNull(size.width)) {
goog.style.setSize(thumbnailElement, size);
}
domHelper.appendChild(div, thumbnailElement);
}
if (dataModel.getPlayer()) {
// if medias have players, allow UI for a play button.
var playButton = domHelper.createElement('div');
playButton.className = goog.getCssName(this.getCssClass(), 'playbutton');
domHelper.appendChild(div, playButton);
}
control.setElementInternal(div);
this.setState(
control,
/** @type {goog.ui.Component.State} */ (control.getState()),
true);
return div;
};
/**
* Returns a renamable CSS class name for a numbered thumbnail. The default
* implementation generates the class names goog-ui-media-thumbnail0,
* goog-ui-media-thumbnail1, and the generic goog-ui-media-thumbnailn.
* Subclasses can override this method when their media requires additional
* specific class names (Applications are supposed to know how many thumbnails
* media will have).
*
* @param {number} index The thumbnail index.
* @return {string} CSS class name.
* @protected
*/
goog.ui.media.MediaRenderer.prototype.getThumbnailCssName = function(index) {
switch (index) {
case 0: return goog.getCssName(this.getCssClass(), 'thumbnail0');
case 1: return goog.getCssName(this.getCssClass(), 'thumbnail1');
case 2: return goog.getCssName(this.getCssClass(), 'thumbnail2');
case 3: return goog.getCssName(this.getCssClass(), 'thumbnail3');
case 4: return goog.getCssName(this.getCssClass(), 'thumbnail4');
default: return goog.getCssName(this.getCssClass(), 'thumbnailn');
}
};