blob: 4320d901a5748f4b4108ac0c0a5e8bdb461225c1 [file] [log] [blame]
// Copyright 2010 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 Behavior for combining two controls.
*
* @see ../demos/split.html
*/
goog.provide('goog.ui.SplitBehavior');
goog.provide('goog.ui.SplitBehavior.DefaultHandlers');
goog.require('goog.Disposable');
goog.require('goog.asserts');
goog.require('goog.dispose');
goog.require('goog.dom');
goog.require('goog.dom.NodeType');
goog.require('goog.dom.classlist');
goog.require('goog.events.EventHandler');
goog.require('goog.ui.ButtonSide');
goog.require('goog.ui.Component');
goog.require('goog.ui.decorate');
goog.require('goog.ui.registry');
/**
* Creates a behavior for combining two controls. The behavior is triggered
* by a given event type which applies the behavior handler.
* Can be used to also render or decorate the controls.
* For a usage example see {@link goog.ui.ColorSplitBehavior}
*
* @param {goog.ui.Control} first A ui control.
* @param {goog.ui.Control} second A ui control.
* @param {function(goog.ui.Control,Event)=} opt_behaviorHandler A handler
* to apply for the behavior.
* @param {string=} opt_eventType The event type triggering the
* handler.
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
* document interaction.
* @constructor
* @extends {goog.Disposable}
*/
goog.ui.SplitBehavior = function(first, second, opt_behaviorHandler,
opt_eventType, opt_domHelper) {
goog.Disposable.call(this);
/**
* @type {goog.ui.Control}
* @private
*/
this.first_ = first;
/**
* @type {goog.ui.Control}
* @private
*/
this.second_ = second;
/**
* Handler for this behavior.
* @type {function(goog.ui.Control,Event)}
* @private
*/
this.behaviorHandler_ = opt_behaviorHandler ||
goog.ui.SplitBehavior.DefaultHandlers.CAPTION;
/**
* Event type triggering the behavior.
* @type {string}
* @private
*/
this.eventType_ = opt_eventType || goog.ui.Component.EventType.ACTION;
/**
* True iff the behavior is active.
* @type {boolean}
* @private
*/
this.isActive_ = false;
/**
* Event handler.
* @type {goog.events.EventHandler}
* @private
*/
this.eventHandler_ = new goog.events.EventHandler();
/**
* Whether to dispose the first control when dispose is called.
* @type {boolean}
* @private
*/
this.disposeFirst_ = true;
/**
* Whether to dispose the second control when dispose is called.
* @type {boolean}
* @private
*/
this.disposeSecond_ = true;
};
goog.inherits(goog.ui.SplitBehavior, goog.Disposable);
goog.tagUnsealableClass(goog.ui.SplitBehavior);
/**
* Css class for elements rendered by this behavior.
* @type {string}
*/
goog.ui.SplitBehavior.CSS_CLASS = goog.getCssName('goog-split-behavior');
/**
* An emum of split behavior handlers.
* @enum {function(goog.ui.Control,Event)}
*/
goog.ui.SplitBehavior.DefaultHandlers = {
NONE: goog.nullFunction,
CAPTION: function(targetControl, e) {
var item = /** @type {goog.ui.MenuItem} */ (e.target);
var value = (/** @type {string} */((item && item.getValue()) || ''));
var button = /** @type {goog.ui.Button} */ (targetControl);
button.setCaption && button.setCaption(value);
button.setValue && button.setValue(value);
},
VALUE: function(targetControl, e) {
var item = /** @type {goog.ui.MenuItem} */ (e.target);
var value = (/** @type {string} */(item && item.getValue()) || '');
var button = /** @type {goog.ui.Button} */ (targetControl);
button.setValue && button.setValue(value);
}
};
/**
* The element containing the controls.
* @type {Element}
* @private
*/
goog.ui.SplitBehavior.prototype.element_ = null;
/**
* @return {Element} The element containing the controls.
*/
goog.ui.SplitBehavior.prototype.getElement = function() {
return this.element_;
};
/**
* @return {function(goog.ui.Control,Event)} The behavior handler.
*/
goog.ui.SplitBehavior.prototype.getBehaviorHandler = function() {
return this.behaviorHandler_;
};
/**
* @return {string} The behavior event type.
*/
goog.ui.SplitBehavior.prototype.getEventType = function() {
return this.eventType_;
};
/**
* Sets the disposeControls flags.
* @param {boolean} disposeFirst Whether to dispose the first control
* when dispose is called.
* @param {boolean} disposeSecond Whether to dispose the second control
* when dispose is called.
*/
goog.ui.SplitBehavior.prototype.setDisposeControls = function(disposeFirst,
disposeSecond) {
this.disposeFirst_ = !!disposeFirst;
this.disposeSecond_ = !!disposeSecond;
};
/**
* Sets the behavior handler.
* @param {function(goog.ui.Control,Event)} behaviorHandler The behavior
* handler.
*/
goog.ui.SplitBehavior.prototype.setHandler = function(behaviorHandler) {
this.behaviorHandler_ = behaviorHandler;
if (this.isActive_) {
this.setActive(false);
this.setActive(true);
}
};
/**
* Sets the behavior event type.
* @param {string} eventType The behavior event type.
*/
goog.ui.SplitBehavior.prototype.setEventType = function(eventType) {
this.eventType_ = eventType;
if (this.isActive_) {
this.setActive(false);
this.setActive(true);
}
};
/**
* Decorates an element and returns the behavior.
* @param {Element} element An element to decorate.
* @param {boolean=} opt_activate Whether to activate the behavior
* (default=true).
* @return {!goog.ui.SplitBehavior} A split behavior.
*/
goog.ui.SplitBehavior.prototype.decorate = function(element, opt_activate) {
if (this.first_ || this.second_) {
throw Error('Cannot decorate controls are already set');
}
this.decorateChildren_(element);
var activate = goog.isDefAndNotNull(opt_activate) ? !!opt_activate : true;
this.element_ = element;
this.setActive(activate);
return this;
};
/**
* Renders an element and returns the behavior.
* @param {Element} element An element to decorate.
* @param {boolean=} opt_activate Whether to activate the behavior
* (default=true).
* @return {!goog.ui.SplitBehavior} A split behavior.
*/
goog.ui.SplitBehavior.prototype.render = function(element, opt_activate) {
goog.asserts.assert(element);
goog.dom.classlist.add(element, goog.ui.SplitBehavior.CSS_CLASS);
this.first_.render(element);
this.second_.render(element);
this.collapseSides_(this.first_, this.second_);
var activate = goog.isDefAndNotNull(opt_activate) ? !!opt_activate : true;
this.element_ = element;
this.setActive(activate);
return this;
};
/**
* Activate or deactivate the behavior.
* @param {boolean} activate Whether to activate or deactivate the behavior.
*/
goog.ui.SplitBehavior.prototype.setActive = function(activate) {
if (this.isActive_ == activate) {
return;
}
this.isActive_ = activate;
if (activate) {
this.eventHandler_.listen(this.second_, this.eventType_,
goog.bind(this.behaviorHandler_, this, this.first_));
// TODO(user): should we call the handler here to sync between
// first_ and second_.
} else {
this.eventHandler_.removeAll();
}
};
/** @override */
goog.ui.SplitBehavior.prototype.disposeInternal = function() {
this.setActive(false);
goog.dispose(this.eventHandler_);
if (this.disposeFirst_) {
goog.dispose(this.first_);
}
if (this.disposeSecond_) {
goog.dispose(this.second_);
}
goog.ui.SplitBehavior.superClass_.disposeInternal.call(this);
};
/**
* Decorates two child nodes of the given element.
* @param {Element} element An element to render two of it's child nodes.
* @private
*/
goog.ui.SplitBehavior.prototype.decorateChildren_ = function(
element) {
var childNodes = element.childNodes;
var len = childNodes.length;
var finished = false;
for (var i = 0; i < len && !finished; i++) {
var child = childNodes[i];
if (child.nodeType == goog.dom.NodeType.ELEMENT) {
if (!this.first_) {
this.first_ = /** @type {goog.ui.Control} */ (goog.ui.decorate(child));
} else if (!this.second_) {
this.second_ = /** @type {goog.ui.Control} */ (goog.ui.decorate(child));
finished = true;
}
}
}
};
/**
* Collapse the the controls together.
* @param {goog.ui.Control} first The first element.
* @param {goog.ui.Control} second The second element.
* @private
*/
goog.ui.SplitBehavior.prototype.collapseSides_ = function(first, second) {
if (goog.isFunction(first.setCollapsed) &&
goog.isFunction(second.setCollapsed)) {
first.setCollapsed(goog.ui.ButtonSide.END);
second.setCollapsed(goog.ui.ButtonSide.START);
}
};
// Register a decorator factory function for goog.ui.Buttons.
goog.ui.registry.setDecoratorByClassName(goog.ui.SplitBehavior.CSS_CLASS,
function() {
return new goog.ui.SplitBehavior(null, null);
});