com.smartclient.debug.public.sc.client.widgets.Toolbar.js Maven / Gradle / Ivy
Show all versions of smartgwt Show documentation
/*
* Isomorphic SmartClient
* Version SC_SNAPSHOT-2011-08-08 (2011-08-08)
* Copyright(c) 1998 and beyond Isomorphic Software, Inc. All rights reserved.
* "SmartClient" is a trademark of Isomorphic Software, Inc.
*
* [email protected]
*
* http://smartclient.com/license
*/
//> @class Toolbar
//
// A Toolbar creates a vertical or horizontal strip of similar components (typically Buttons)
// and provides managed resizing and reordering behavior over those components.
//
// If you are creating a bar with a mixture of different elements (eg some MenuButtons, some
// Labels, some Buttons, some custom components), you want to use a +link{ToolStrip}. A
// Toolbar is better suited for managing a set of highly similar, interchangeable components,
// such as ListGrid headers.
//
// @treeLocation Client Reference/Layout
// @visibility external
//<
// declare the class itself
isc.ClassFactory.defineClass("Toolbar", "Layout");
// add default properties to the class
isc.Toolbar.addProperties( {
//> @attr toolbar.buttons (array : null : [IRW])
// An array of button object initializers. See the Button Widget Class for standard
// button properties. The following additional properties can also be specified for
// button sizing and positioning on the toolbar itself:
//
- width--Specifies the width of this button as an absolute number of pixels, a
// named property of the toolbar that specifies an absolute number of pixels, a
// percentage of the remaining space (e.g. '60%'), or "*" (default) to allocate an
// equal portion of the remaining space.
//
- height--Specifies the height of this button.
//
- extraSpace--Specifies an optional amount of extra space, in pixels, to separate
// this button from the next button in the toolbar.
//
// @setter toolbar.setButtons()
// @see toolbar.addButtons()
// @see toolbar.removeButtons()
// @see class:Button
// @visibility external
//<
//> @attr toolbar.vertical (boolean : false : [IRW])
// Indicates whether the buttons are drawn horizontally from left to right (false), or
// vertically from top to bottom (true).
// @group appearance
// @visibility external
//<
vertical:false,
//> @attr toolbar.overflow (Overflow : Canvas.HIDDEN : IRWA)
// Clip stuff that doesn't fit
// @group clipping
//<
overflow:isc.Canvas.HIDDEN,
//> @attr toolbar.height (number : 20 : IRW)
// Default to a reasonable height
// @group sizing
//<
height:20,
//> @attr toolbar.buttonConstructor (Class : Button : IRWA)
// Default constructor for toolbar items.
// @group appearance
// @visibility external
//<
buttonConstructor:"Button",
//> @attr toolbar.canReorderItems (boolean : false : IRWA)
// If true, items can be reordered by dragging on them.
// @group dragndrop
// @visibility external
//<
canReorderItems:false,
//> @attr toolbar.canResizeItems (boolean : false : IRWA)
// If true, items (buttons) can be resized by dragging on them.
// @group dragndrop
// @visibility external
//<
canResizeItems:false,
//> @attr toolbar.canRemoveItems (boolean : false : IRWA)
// If true, items (buttons) can be dragged out of this toolbar to be dropped somewhere else
// @group dragndrop
//<
canRemoveItems:false,
//> @attr toolbar.canAcceptDrop (boolean : false : IRWA)
// If true, items (buttons) can be dropped into this toolbar, and the toolbar will
// show a drop line at the drop location. Override drop() to decide what happens when the
// item is dropped.
//
// @group dragndrop
// @visibility external
//<
//> @attr toolbar.reorderOnDrop (boolean : true : IRWA)
// On drop, should the Toolbar rearrange the buttons array? Set to false by advanced
// classes that want to manage reordering themselves.
// @group dragndrop
//<
reorderOnDrop:true,
//> @attr toolbar.tabWithinToolbar (boolean : true : IRWA)
// Should each button in the toolbar be included in the tab-order for the page, or
// should only one button in the toolbar show up in the tab-order, and arrow-keys be
// used to switch focus within the toolbar?
//<
tabWithinToolbar:true,
//> @attr toolbar.allowButtonReselect (boolean : false : IRWA)
// When a button is clicked but is already selected, should an additional
// +link{buttonSelected} event be fired?
//<
allowButtonReselect:false,
//> @attr toolbar.buttonDefaults (object : varies : [IRWA])
// Settings to apply to all buttons of a toolbar. Properties that can be applied to
// button objects can be applied to all buttons of a toolbar by specifying them in
// buttonDefaults using the following syntax:
// buttonDefaults:{property1:value1, property2:value2, ...}
// See the Button Widget Class for standard button properties.
// @group appearance
// @see class:Button
// @visibility external
//<
// The following are defaults for all toolbar buttons.
// To add properties to all buttons of ALL toolbars, change the below.
// To add properties to all buttons of a particular toolbar you're creating,
// add a "button" property to the toolbar constructor with the defaults
// you want applied to the buttons. This will automatically be added to each button.
buttonDefaults: {
click : function() {
this.Super("click", arguments);
this.parentElement.itemClick(this, this.parentElement.getButtonNumber(this))
},
doubleClick : function () {
this.Super("doubleClick", arguments);
this.parentElement.itemDoubleClick(this, this.parentElement.getButtonNumber(this))
},
setSelected : function() {
var oldState = this.isSelected();
this.Super("setSelected", arguments);
if (this.parentElement.allowButtonReselect || oldState != this.isSelected()) {
if (this.isSelected()) this.parentElement.buttonSelected(this);
else this.parentElement.buttonDeselected(this);
}
},
dragAppearance:isc.EventHandler.NONE,
// Toolbars typically manipulate the tabIndex of their buttons.
// If the user specifies a tabIndex on a toolbar button directly, assume they are
// managing the tabIndex for the button - clear the flag that marks the button as having
// it's tabIndex managed by the toolbar
setTabIndex : function (index) {
this.Super("setTabIndex", arguments);
this._toolbarManagedTabIndex = null;
},
// Override setAccessKey to take a second parameter, indicating that the accessKey is
// being set by the toolbar
// If this parameter is not passed in, assume the user / developer is setting the
// accessKey and clear the flag that marks the button's accessKey as being managed by
// the toolbar
setAccessKey : function (accessKey, managedByToolbar) {
if (!managedByToolbar) this._toolbarManagedAccessKey = null;
this.Super("setAccessKey", [accessKey]);
},
// When focus goes to a button, set the tabIndex of the button to the toolbars tabIndex.
// This means when tabbing out of the button, the focus will go to the appropriate next
// element - use the _updateFocusButton() method on the toolbar to achieve this.
focusChanged : function (hasFocus) {
if (this.hasFocus && this.parentElement._updateFocusButton) {
this.parentElement._updateFocusButton(this)
}
},
_focusInNextTabElement : function (forward, mask) {
if (this.parentElement._focusInNextTabElement) {
this.parentElement._focusInNextTabElement(forward, mask, this);
}
}
}
});
isc.Toolbar.addMethods({
//> @method toolbar.draw() (A)
// Override the draw method to set up the buttons first
// @group drawing
//<
draw : function (a,b,c,d) {
if (isc._traceMarkers) arguments.__this = this;
if (!this.readyToDraw()) return this;
// If we've never init'd our buttons, do so now by calling setButtons with no parameters
if (!this._buttonsInitialized) this.setButtons();
this.invokeSuper(isc.Toolbar, "draw", a,b,c,d);
},
//> @method toolbar.keyPress()
// Override keypress to allow navigation between the buttons on the toolbar
// @group events
//<
// Note - this is typically going to be bubbled up from the menu bar buttons
keyPress : function () {
var keyName = this.ns.EH.lastEvent.keyName;
// note - if we're allowing the user to tab between the buttons on the toolbar, we don't need
// to give them the navigation via arrow keys.
if (!this.tabWithinToolbar) {
if ((this.vertical && keyName == "Arrow_Up") ||
(!this.vertical && keyName == "Arrow_Left")) {
this._focusInNextButton(false);
return false;
} else if ((this.vertical && keyName == "Arrow_Down") ||
(!this.vertical && keyName == "Arrow_Right")){
this._focusInNextButton();
return false;
}
}
return this.Super("keyPress", arguments);
},
_focusInNextButton : function (forward, startingIndex) {
// Note - this.buttons is the list of button init objects. The live widgets are available
// via this.getMembers()
forward = (forward != false);
var focusIndex = (startingIndex != null ? startingIndex : this.getFocusButtonIndex());
if (focusIndex == null) focusIndex = (forward ? -1 : this.buttons.length);
// find the next focusable member in this direction, if any
focusIndex += forward ? 1 : -1;
while (focusIndex >=0 && focusIndex < this.buttons.length) {
var button = this.getMembers()[focusIndex];
if (button._canFocus()) {
button.focus();
// Returning true will indicate successful shift of focus
return true;
}
focusIndex += forward ? 1 : -1;
}
return false;
},
//> @method toolbar.getFocusButtonIndex() (A)
// @return (number) Index of whichever button currently has focus for keyboard input
// [On a mouse click, this will typically match the value returned by
// toolbar.getMouseOverButtonIndex(), but is likely to differ if the button
// was activated by keyboard interaction]
//<
getFocusButtonIndex : function () {
var buttons = this.getButtons(),
focusItemNum;
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].hasFocus) {
focusItemNum = i;
break;
}
}
return focusItemNum;
},
// _focusInNextTabElement() - used when we're managing synthetic focus due to showing a
// clickMask.
// Since we do custom management of our buttons' tabIndices, we need to also explicitly
// manage synthetic tabbing to them
_focusInNextTabElement : function (forward, mask, button) {
if (!isc.EH.targetIsMasked(this, mask)) {
var focusButton = button ? this.members.indexOf(button) : null;
if (!this.tabWithinToolbar) {
if (forward && focusButton == null) {
var fb = this._currentFocusButton;
if (fb != null) return this.fb.focus();
}
} else if (this._focusInNextButton(forward, focusButton)) return;
}
return this.Super("_focusInNextTabElement", arguments);
},
// Widget level _canFocus
// Override this to return true. This will ensure that if a hard mask is showing, and we're
// doing synthetic tab index management, the toolbar doesn't get skipped.
_canFocus : function (a,b,c,d) {
var members = this.members;
if (members && members.length > 0) {
for (var i = 0; i < members.length; i++) {
if (members[i]._canFocus()) return true;
}
}
return this.invokeSuper(isc.Toolbar, "_canFocus", a,b ,c,d);
},
// Override focus() to put focus into the button(s) in the toolbar
// Override 'setFocus()' to update button focus only.
setFocus : function (hasFocus) {
if (!this._readyToSetFocus()) return;
var buttonIndex = this.getFocusButtonIndex();
if (!hasFocus) {
if (buttonIndex != null && this.members) this.members[buttonIndex].setFocus(false);
} else {
// If one of our buttons already has focus, just no op.
if (buttonIndex != null) return;
if (this._currentFocusButton) this._currentFocusButton.setFocus(true);
else this._focusInNextButton();
}
},
// Override focusAtEnd() so we can put focus into the first / last button if appropriate
focusAtEnd : function (start) {
// typecast start to a boolean before passing it to 'focusInNextButton' as the 'forward'
// param.
start = !!start;
var focusIndex = (start ? -1 : this.buttons.length);
this._focusInNextButton(start, focusIndex);
},
// An internal method to set the tab index of a button, and flag the button as having it's tab index
// managed by the toolbar.
_setButtonTabIndex : function (button, newTabIndex) {
if (!button._toolbarManagedTabIndex &&
(button._getNextTabWidget() != null || button._getPreviousTabWidget() != null))
{
button._removeFromAutoTabOrder();
}
// Note that the toolbar is managing the tab index of the button
button._toolbarManagedTabIndex = true;
// update the tab index of the button.
if (button.tabIndex != newTabIndex) button._setTabIndex(newTabIndex, false);
},
// Override updateMemberTabIndex (inherited from Layout)
// to be a No-Op, since we manage our members' (buttons') tabindices
updateMemberTabIndex : function () {
},
_slotChildrenIntoTabOrder : function () {
},
// _setButtonAccessKey()
// Internal method to set the accessKey for a button within this toolbar.
// Also sets the flag '_toolbarManagedAccessKey' on the button
_setButtonAccessKey : function (button, key) {
button._toolbarManagedAccessKey = true;
// see comment in the override for setAccessKey for why we're passing in this 2nd parameter
button.setAccessKey(key, true);
},
// setupButtonFocusProperties()
// An internal method to set the tab indexes of any buttons in the toolbar without existing
// user-specified tab indexes
setupButtonFocusProperties : function () {
// first update the 'currentFocusButton' if its out of date.
// This will set the tabIndex and accessKey for the button (unless that would override an
// explicitly specified property for the button).
// Note - this.buttons is the list of button init objects.
// The actual button objects are available via this.getButtons()
var focusButton = this._currentFocusButton;
if ( (!focusButton || !isc.isA.Canvas(focusButton) ||
focusButton.visibility == isc.Canvas.HIDDEN ) && this.buttons.length > 0)
{
var newFocusButton;
for (var i = 0; i < this.members.length; i++) {
if (isc.isA.Canvas(this.members[i]) &&
this.members[i].visibility != isc.Canvas.HIDDEN)
{
newFocusButton = this.members[i];
break;
}
}
this._updateFocusButton(newFocusButton)
focusButton = this._currentFocusButton;
}
var defaultTabIndex;
if (this.tabWithinToolbar) {
defaultTabIndex = this.getTabIndex();
} else {
defaultTabIndex = -1;
}
// update the tabIndex of any buttons who have no user-specified tab index, and
// for which we haven't yet managed the tabIndex
var buttons = this.getButtons();
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
if (button != focusButton &&
(button.tabIndex == null || button._autoTabIndex))
{
//this.logWarn("updating tab index of: " + button + " to " + defaultTabIndex);
this._setButtonTabIndex(button, defaultTabIndex)
}
}
},
_updateFocusButton : function (newFocusButton) {
// Bail if the current focus button was passed in
if (this._currentFocusButton == newFocusButton) {
return;
}
// Update the accessKey for the current focus button unless it has / had an explicitly
// specified accessKey
if (newFocusButton.accessKey != this.accessKey &&
(newFocusButton.accessKey == null || newFocusButton._toolbarManagedAccessKey))
{
this._setButtonAccessKey(newFocusButton, this.accessKey)
}
// Update focus button tab index (if allocated by us)
if (newFocusButton.tabIndex == null || newFocusButton._autoTabIndex ||
newFocusButton._toolbarManagedTabIndex)
{
// set the newly focused button to the tabIndex of the Toolbar
this._setButtonTabIndex(newFocusButton, this.getTabIndex());
}
var oldFocusButton = this._currentFocusButton;
// If appropriate, remove the previous focus button from the tab order
if (oldFocusButton != null &&
(oldFocusButton.tabIndex == null || oldFocusButton._autoTabIndex ||
oldFocusButton._toolbarManagedTabIndex))
{
// Remove from tab order if we are not tabbing between buttons
if (!this.tabWithinToolbar) this._setButtonTabIndex(oldFocusButton, -1);
// Clear the accessKey property if it was added by the toolbar
if (oldFocusButton.accessKey != null &&
oldFocusButton._toolbarManagedAccessKey)
{
this._setButtonAccessKey(oldFocusButton, null)
}
}
this._currentFocusButton = newFocusButton;
},
// Override _setTabIndex() to set also update the tab index of the buttons
_setTabIndex : function (a,b,c,d) {
this.invokeSuper(isc.Toolbar, "_setTabIndex", a,b,c,d);
// if this.tabWithinToolbar is true, update each of the buttons' tab index to match the
// toolbars new tab index
if (this.tabWithinToolbar) {
var buttons = this.getButtons();
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].tabIndex == null || buttons[i]._autoTabIndex ||
buttons[i]._toolbarManagedTabIndex)
this._setButtonTabIndex(buttons[i], this.getTabIndex())
}
// otherwise use _updateFocusButton to update the tab index of the focus button only (other
// buttons' tab index will already be -1 -- no need to change)
} else {
var button = this._currentFocusButton;
if (button != null) {
this._currentFocusButton = null;
this._updateFocusButton(button);
}
}
},
// Override setAccessKey() to alo set the accessKey for the toolbar
setAccessKey : function (accessKey) {
this.Super("setAccessKey", arguments);
// use updateFocusButton to update the accessKey for the focus button
var button = this._currentFocusButton;
if (button != null) {
this._currentFocusButton = null;
this._updateFocusButton(button);
}
},
getLength : function (a,b,c,d) {
// the Toolbar allows overriding the area allocated to layout members, so that it may be
// larger or smaller than the Layout's area.
if (this.innerWidth != null) return this.innerWidth;
return this.invokeSuper(isc.Toolbar, "getLength", a,b,c,d);
},
//> @method toolbar.setButtons()
// Apply a new set of buttons to render in this toolbar as +link{toolbar.buttons}.
//
// @param [newButtons] (Array of Button Properties) properties to create each button from
// @visibility external
//<
setButtons : function (newButtons) {
// one time flag - allows us to set up our buttons on initial draw only.
// If 'setButtons' is called before draw we won't unnecessarily remove and re-add them all.
this._buttonsInitialized = true;
//this.logWarn("setButtons at\n" + this.getStackTrace());
// if buttons are passed in, use those
// Otherwise we'll just make actual button instances from the current items in this.buttons
if (newButtons) this.buttons = newButtons;
if (this.members == null) this.members = [];
// destroy any existing members, and create new buttons from scratch
var _buttons = this.members.duplicate();
for (var i = 0; i < _buttons.length ; i++) {
var oldButton = _buttons[i];
// destroy any members we automatically created from the buttons array
if (!this.buttons.contains(oldButton)) {
//this.logWarn("destroying old button " + i);
// destroying it will automagically remove it from this as a member, so no
// need to call this.removeMembers()
_buttons[i].destroy();
}
}
// now create actual button widgets
if (this.buttons == null) this.buttons = [];
var newMembers = [];
for (var i = 0; i < this.buttons.length; i++) {
var button = this.buttons[i];
// allow widgets to be placed directly in the buttons array, which we simply add as
// members and ignore. These members will not have pick up buttonDefaults, hence won't
// fire itemClick, have associated panes, allow managed resize, etc.
if (!isc.isA.Canvas(button)) button = this.makeButton(button);
newMembers[newMembers.length] = button;
if (isc.isA.StatefulCanvas(button)) {
var actionType = button.getActionType();
if (actionType == isc.StatefulCanvas.RADIO) {
// For actionType:radio buttons, remember initial selected button.
// We update this on selection change.
// This property will be returned on 'Toolbar.getSelectedButton()'
// Note - no error checking for multiple selection within a toolbar
// If each 'actionType' RADIO button has no specified radiogroup, the default
// toolbar behavior is to put them into the same radioGroup. In this case default
// radiogroup selection behavior inherited from StatefulCanvas will prevent
// multiple selection within a toolbar.
// If the user has specified a radiogroup for any actionType:RADIO buttons, we
// can't guarantee there won't be multiple selection within a toolbar.
// In this case 'getSelectedButton()' will return the most recently selected RADIO
// button within this toolbar, rather than the only selected radio button.
if (button.selected) this.lastSelectedButton = button;
}
}
}
this.addMembers(newMembers, 0);
if (this.canResizeItems) this.setResizeRules();
// Set up the tab indexes for the buttons, and the accessKey for the focus button
this.setupButtonFocusProperties();
},
// shouldHiliteAccessKey implementation for buttons in this toolbar
buttonShouldHiliteAccessKey : function () {
// If the accessKey comes from the toolbar itself, don't hilite
// otherwise we will end up with underlining of the title on multiple buttons which
// is likely to look odd.
if (this._toolbarManagedAccessKey) return false;
return this.hiliteAccessKey;
},
makeButton : function (button) {
// the default sizing behavior we want:
// - horizontal toolbars autoSize button heights to the Toolbar's height and autoSize
// button widths to the button text.
// - vertical toolbars autoSize button width to the Toolbar's width and autoSize button
// heights to the (wrapped) button text.
button.width = button.width || null;
button.height = button.height || null;
// set button properties to enable/disable dragging and dropping, so that dragging will
// be allowed on members and will bubble to the Toolbar
button.canDrag = this.canReorderItems || this.canDragSelectItems || this.canRemoveItems;
// don't override canDragResize to true on the button if it's been explicitly turned off
button.canDragResize = (button.canDragResize != null ?
button.canDragResize && this.canResizeItems : this.canResizeItems);
// toolbar allows things to be dropped on it (currently no default behavior for what happens
// on drop)
button.canAcceptDrop = this.canAcceptDrop;
// if you can drag items out of the toolbar, make the buttons droppable
button.canDrop = this.canRemoveItems;
button.shouldHiliteAccessKey = this.buttonShouldHiliteAccessKey;
// create a new button widget
//this.logWarn("creating new button " + i);
return this._makeItem(button, null);
},
//> @method toolbar._makeItem()
// Creates and returns a widget for the toolbar
// @group drawing
//
// @param [buttonProperties] (object) the button properties
// @param [rect] (object) the rectangle for this widget, e.g. {top:50, left:100, ...}
//
// @return (object) the created widget
//<
_makeItem : function (buttonProperties, rect) {
var cons = (buttonProperties.buttonConstructor
? buttonProperties.buttonConstructor
: this.buttonConstructor
)
;
cons = this.ns.ClassFactory.getClass(cons);
var item = cons.newInstance(
{autoDraw:false},
this.buttonDefaults, // isc.Toolbar class defaults
this.buttonProperties, // isc.Toolbar instance defaults
buttonProperties, // properties for this button
rect // rectangle for the button
);
if (!isc.isA.StatefulCanvas(item)) return item;
// if the button is of actionType 'radio' and the developer has not specified a
// radioGroup, set radioGroup to the ID of this toolbar
// Developer can override by setting 'radioGroup' property explicitly on the
// item's properties.
var unset;
if ((item.getActionType() == isc.StatefulCanvas.RADIO && item.radioGroup === unset)
|| item.defaultRadioGroup != null) {
var rg = item.defaultRadioGroup != null ? item.defaultRadioGroup : this.getID();
item.addToRadioGroup(rg);
}
return item;
},
//> @method toolbar.addButtons()
// Add a list of buttons to the toolbar
// @param [buttons] (Array of objects) list of button object initializers.
// @param [position] (number) position to add the new buttons at
// @visibility external
//<
addButtons : function (buttons, position) {
if (buttons == null) return;
if (!isc.isAn.Array(buttons)) buttons = [buttons];
if (!this._buttonsInitialized) this.setButtons();
buttons.removeEvery(null);
// (currently undocumented) feature - support passing in position for each
// button being added - in this case the 2nd argument will be an array of the
// same length as the buttons array.
// Break into discrete blocks of adjacent buttons so we can use standard list manipulation
// APIs rather than having to iterate through every button and add as a member individually,
// rerunning layout.
var discreteBlocks;
if (isc.isAn.Array(position)) {
if (position.length != buttons.length) {
this.logWarn("addButtons passed " + buttons.length + " buttons with " + position.length
+ " discrete positions specified. Ignoring.");
return;
}
var mapping = {};
for (var i = 0; i < position.length; i++) {
mapping[position[i]] = buttons[i];
}
// sort the positions - we'll need to add buttons starting with the
// leftmost position (Otherwise adding an item, then a second item to the left of
// it would shift the first item a slot to the right from the desired position).
position.sort();
discreteBlocks = [];
var currentBlock = {buttons:[], position:position[0]},
currentIndex = 0;
for (var i = 0; i < position.length; i++) {
var pos = position[i],
button = mapping[pos];
currentBlock.buttons.add(button);
var nextPos = position[i+1]
if (nextPos == null || nextPos != pos+1) {
discreteBlocks[currentIndex] = currentBlock;
currentIndex++
// New discrete block for the next time through this loop
if (nextPos != null) currentBlock = {buttons:[], position:nextPos};
}
}
for (var i = 0; i < discreteBlocks.length; i++) {
this.buttons.addListAt(discreteBlocks[i].buttons, discreteBlocks[i].position);
}
} else {
// Update this.buttons to include the new buttons:
this.buttons.addListAt(buttons, position);
}
// if instantRelayout is true, delay the relayout until we've added the full
// set of members
var forceReflow = this.instantRelayout;
this.instantRelayout = false;
// Add as members to the right position, and let layout handle spacing and stuff
var buttonWidgets;
if (discreteBlocks == null) {
buttonWidgets = this._createButtonInstances(buttons);
this.addMembers(buttonWidgets, position)
} else {
for (var i = 0; i < discreteBlocks.length; i++) {
var currentButtons = this._createButtonInstances(discreteBlocks[i].buttons);
this.addMembers(currentButtons, discreteBlocks[i].position);
if (buttonWidgets == null) buttonWidgets = currentButtons;
else buttonWidgets.addList(currentButtons);
}
}
if (forceReflow) {
this.instantRelayout = true;
if (this._layoutIsDirty) this._layoutIsDirty = false;
this.reflow("addButtons");
}
// setResizeRules to update dragResizing, etc.
if (this.canResizeItems) this.setResizeRules();
buttonWidgets.map("show"); // auto-show the new members
},
_createButtonInstances : function (buttons) {
var buttonWidgets = [];
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i],
// call makeButton() to convert the init block to a widget with the appropriate
// propoerties (canDrag, buttonDefaults, etc)
// Note that canvases are just integrated into the buttons block without
// attempting to modify properties, as with setButtons()
buttonWidget = isc.isA.Canvas(button) ? button : this.makeButton(button);
buttonWidgets[i] = buttonWidget;
}
return buttonWidgets;
},
//> @method toolbar.removeButtons()
// Remove a list of buttons from the toolbar
//
// @param [buttons] (Array) Array of buttons to remove. Buttons may be specified as pointers to
// the button instances contained in this toolbar, or numbers indicating the index of the buttons
// in this.buttons
.
// @visibility external
//<
removeButtons : function (buttons) {
if (buttons == null) return;
if (!isc.isAn.Array(buttons)) buttons = [buttons];
// We're going to manipulate the this.buttons array (button description objects), and
// the actual buttons in this.members - so will need pointers to both the
// button descriptor objects and the button instances.
var buttonWidgets = [];
// The buttons to remove can be specified as:
// a) Index in this.buttons
// b) Button widget
// c) Button instantiation block
// d) ID of button
for (var i =0; i < buttons.length; i ++) {
// resolve whatever object was passed in to a button instantiation block
buttons[i] = this.buttons[this.getButtonNumber(buttons[i])];
if (buttons[i] == null) {
this.logWarn("removeButtons(): unable to find button for item number " + i +
" in the array passed in. Skipping this item.");
buttons.removeItem(i);
i -= 1;
continue;
}
// get a pointer to the Canvas as well
buttonWidgets[i] = this.getButton(this.buttons.indexOf(buttons[i]))
}
var completeButtons = this.buttons;
// if (any of) the buttons aren't in this.buttons, this has no effect
completeButtons.removeList(buttons);
this.removeMembers(buttonWidgets);
// should we destroy them?
},
//> @method toolbar.getButton() ([])
// Retrieves a button widget instance (within this toolbar) from the ID / index /
// descriptor object for the button (as with the getButtonNumber() method)
// This provides a way to access a toolbar button's properties and methods directly.
// @see getButtonNumber()
// @visibility external
// @group buttons
// @param index (number | string | object) identifier for the button to retrieve
//
// @return (Button) the button, or null if the button wasn't found
//<
getButton : function (index) {
index = this.getButtonNumber(index);
return this.getMember(index);
},
//> @method toolbar.getButtonNumber() (A)
// get the index of a button in the buttons array
// The button can be specified as -
//
// - an index within this.buttons (just returned)
//
- the ID property of a button
//
- a pointer to the button descriptor object in this.buttons
//
- the actual button widget in this.members
//
// returns -1 if not found
//
// @param button (number | string | button object | button widget)
//
// @return (number) index of the button in question
// @visibility external
//<
getButtonNumber : function (button) {
// if we're passed an Object that isn't a Canvas, it might be a button configuration object
if (isc.isAn.Object(button) && !isc.isA.Canvas(button)) return this.buttons.indexOf(button);
// otherwise use normal member lookup
return this.getMemberNumber(button);
},
//> @method toolbar.getButtons()
// @group buttons
// @return (array) array of all buttons in the Toolbar
//<
getButtons : function () {
return this.members;
},
//> @method toolbar.setCanResizeItems()
// Setter for updating +link{toolbar.canResizeItems} at runtime.
// @param canResizeItems (boolean) New value for this.canResizeItems
// @visibility external
//<
setCanResizeItems : function (canResizeItems) {
if (this.canResizeItems == canResizeItems) return;
this.canResizeItems = canResizeItems;
var buttons = this.getButtons();
if (!buttons) return;
for (var i = 0; i < buttons.length; i++) {
var item = buttons[i];
item.canDragResize = (item.canDragResize != null ?
item.canDragResize && canResizeItems : canResizeItems);
}
this.setResizeRules();
},
// update which edges a button can be resized from.
//
// When you dragResize buttons, it always effects the button on the left (or top), regardless
// of which side of the boundary between the buttons you click on (this is pulled off by
// switching the dragTarget on the fly). This means the sides that each button can be resized
// from is affected by whether the adjacent buttons can be resized. setResizeRules updates
// this; it needs to be called on any reorder, addition or removal of buttons.
setResizeRules : function () {
if (!this.members) return;
var rtl = this.isRTL();
// buttons can resize along the long axis of the toolbar.
var edgeCursorMap, resizeFrom, resizeFromOneSide;
if (this.vertical) {
edgeCursorMap = {"T":isc.Canvas.ROW_RESIZE, "B":isc.Canvas.ROW_RESIZE };
resizeFrom = ["T","B"];
resizeFromOneSide = ["B"];
} else {
edgeCursorMap = {"L":isc.Canvas.COL_RESIZE, "R":isc.Canvas.COL_RESIZE };
resizeFrom = ["L","R"];
if (!rtl) {
resizeFromOneSide = ["R"];
} else {
resizeFromOneSide = ["L"];
}
}
var previousCantResize = false;
for (var i = 0; i < this.members.length; i++) {
var button = this.members[i];
if (!button.canDragResize) {
button.resizeFrom = button.edgeCursorMap = null;
previousCantResize = true;
} else {
if (previousCantResize || i == 0)
{
// the first button, or any button next to a button that can't resize, is not
// allowed to resize from it's left/top.
button.resizeFrom = resizeFromOneSide;
} else {
button.resizeFrom = resizeFrom;
}
button.edgeCursorMap = edgeCursorMap;
previousCantResize = false;
}
}
},
//> @method toolbar.getSelectedButton() (A)
// Get the button currently selected.
// @return (object) button
//<
getSelectedButton : function () {
return this.lastSelectedButton;
},
//> @method toolbar.selectButton() ([])
// Given an identifier for a button, select it.
// The button identifier can be a number (index), string (id), or object (widget or init block),
// as with the getButtonNumber() method.
//
// @see getButtonNumber()
// @group selection
// @param buttonID (number | string | object | canvas) Button / Button identifier
// @visibility external
//<
selectButton : function (buttonID) {
if (!this.members) return;
var btn = this.getButton(buttonID);
if (btn && isc.isA.StatefulCanvas(btn)) btn.select();
},
//> @method toolbar.deselectButton() ([])
// Deselects the specified button from the toolbar, where buttonID is the index of
// the button's object initializer. The button will be redrawn if necessary.
// The button identifier can be a number (index), string (id), or object (widget or init block),
// as with the getButtonNumber() method.
// @see getButtonNumber()
// @visibility external
// @group selection
// @param buttonID (number | string | object | canvas) Button / Button identifier
//<
deselectButton : function (buttonID) {
var btn = this.getButton(buttonID);
if (btn) btn.deselect();
},
//> @method toolbar.buttonSelected() (A)
// One of the toolbar button was just selected -- update other buttons as necessary
// @group selection
//
// @param button (button object) a member of this.buttons
//<
buttonSelected : function (button) {
if (button.getActionType() == isc.Button.RADIO) {
this.lastSelectedButton = button;
}
},
//> @method toolbar.buttonDeselected() (A)
// Notification that one of the toolbar buttons was just DEselected
// @group selection
//
// @param button (button object) a member of this.buttons
//<
buttonDeselected : function (button) {
},
//> @method toolbar.itemClick() ([A])
// Called when one of the buttons receives a click event
// @group event handling
// @param item (button) pointer to the button in question
// @param itemNum (number) number of the button in question
// @visibility external
//<
itemClick : function (item, itemNum) {
},
//> @method toolbar.itemDoubleClick() ([A])
// Called when one of the buttons receives a double-click event
// @group event handling
// @param item (button) pointer to the button in question
// @param itemNum (number) number of the button in question
// @visibility external
//<
itemDoubleClick : function (item, itemNum) {
},
//> @method toolbar.getMouesOverButtonIndex() (A)
// @return (number) the number of the button the mouse is currently over,
// or -1 for before all buttons, -2 for after all buttons
// See also getFocusButtonIndex()
//<
getMouseOverButtonIndex : function () {
var offset = this.vertical ? this.getOffsetY() : this.getOffsetX();
return this.inWhichPosition(this.memberSizes, offset, this.getTextDirection());
},
// Override prepareForDragging to handle dragResize / dragReorder of items in the toolbar.
prepareForDragging : function () {
// NOTE: we currently set a canDrag, canDragResize, etc flags on our children. However, we
// could manage everything from this function instead, eg, pick dragResize if there is a
// resize edge hit on the child, otherwise dragReorder.
var EH = this.ns.EH;
// This custom handling is for events bubbled from a member being drag repositioned
// (drag reorder) or drag resized.
//
var lastTarget = EH.lastEvent.target;
while (lastTarget.dragTarget) {
lastTarget = lastTarget.dragTarget;
}
var operation = EH.dragOperation;
if (( (this.canResizeItems && operation == "dragResize")
|| (this.canReorderItems && operation == "drag")
) && this.members.contains(lastTarget))
{
// If we hit a valid resize edge on a member, the member will have set the dragOperation to
// dragResize
if (operation == "dragResize") {
// for drag resizes on the length axis, do specially managed resizing. Don't interfere
// with breadth-axis resize, if enabled
if ((this.vertical && ["T","B"].contains(EH.resizeEdge)) ||
(!this.vertical && ["L","R"].contains(EH.resizeEdge)))
{
EH.dragOperation = "dragResizeMember";
// We can just return - prepareForDragging() is bubbled so was already fired
// on the member and set up EH.dragTarget in this case
return;
}
// otherwise, starting a drag on a button means dragReordering the members.
} else if (operation == "drag") {
EH.dragOperation = "dragReorder";
return;
}
}
return this.Super("prepareForDragging", arguments);
},
// Drag Reordering
// --------------------------------------------------------------------------------------------
// get the position where the button being reordered would be dropped, if dragging stopped at the
// current mouse coordinates
getDropPosition : function () {
var position = this.getMouseOverButtonIndex();
var EH = this.ns.EH,
switchInMiddle = (this.reorderStyle == "explorer" ||
(EH.dropTarget && EH.dropTarget.parentElement == this));
if (switchInMiddle && position >= 0) {
// if we are over a member, check whether we should switch to the next member or final
// coordinate
var buttonSize = this.memberSizes[position],
offset = (this.vertical ? this.getOffsetY() : this.getOffsetX());
offset -= this.memberSizes.slice(0, position).sum();
var oldPosition = position;
// switch to next coordinate in the middle of the button
if (offset > buttonSize/2) position++;
//this.logWarn("oldPosition: " + oldPosition +
// ", size: " + buttonSize +
// ", offset: " + offset +
// ", position: " + position);
}
var numMembers = this.members.length,
maxIndex = (switchInMiddle ? numMembers : numMembers - 1);
// if beyond the last member, but still within the layout rect, convert to last member
if (position == -2 && this.containsEvent()) return maxIndex;
// for reorder/self-drop interactions, when we drag out of the Layout, we revert to the
// original position. For external drops, the only remaining case is a coordinate within
// the Layout, but before all members.
var revertPosition = this.dragStartPosition || 0;
if (position < 0 || position > maxIndex) position = revertPosition;
// for reorder/self-drop, check canReorder flag
else if (EH.dragTarget && EH.dragTarget.parentElement == this &&
(this.members[position] && this.members[position].canReorder == false))
{
position = revertPosition;
}
return position;
},
// sent when button dragging for reordering begins
dragReorderStart : function () {
var EH = this.ns.EH,
startButton = EH.dragTarget
;
// if the button's canReorder property is false, it can't be reordered so forget it!
if (startButton.canReorder == false) return false;
if (startButton.showDown) startButton.setState(isc.StatefulCanvas.STATE_DOWN);
// get the item number that reordering started in (NOTE: depended on by observers like LV)
this.dragStartPosition = this.getButtonNumber(startButton);
return EH.STOP_BUBBLING;
},
// sent when button moves during drag-reorder
dragReorderMove : function () {
var EH = this.ns.EH,
startButton = EH.dragTarget,
startPosition = this.dragStartPosition,
currentPosition = this.getDropPosition();
//this.logWarn("dragReorderMove: position: " + this.getMouseOverButtonIndex() +
// ", drop position: " + this.getDropPosition());
// remember the current position (NOTE: depended on by observers like LV)
this.dragCurrentPosition = currentPosition;
// create a temporary order for the members and lay them out in that order
var members = this.members.duplicate();
members.slide(startPosition, currentPosition);
//this.logWarn("startPos: " + startPosition + ", currentPos: " + currentPosition +
// "members: " + this.members + ", reordered: " + members);
// NOTE: tell stackMembers() not to update sizes, since this is a temporary order
this.stackMembers(members, null, false);
return EH.STOP_BUBBLING;
},
// sent when button dragging for reordering ends
dragReorderStop : function () {
var EH = this.ns.EH,
startButton = EH.dragTarget,
startPosition = this.dragStartPosition,
currentPosition = this.dragCurrentPosition;
startButton.setState(isc.StatefulCanvas.STATE_UP);
if (currentPosition == startPosition) return false;
// if we're supposed to actually reorder on drop, reorder now
if (this.reorderOnDrop) this.reorderItem(currentPosition, startPosition);
// notify observers
if (this.itemDragReordered) this.itemDragReordered(startPosition, currentPosition);
return EH.STOP_BUBBLING;
},
//> @method toolbar.dragStop() (A)
// @group events, dragging
// handle a dragStop event
//<
dragStop : function () {
// NOTE: called at the end of an inter-toolbar move iteraction, not a dragReorder
var EH = this.ns.EH,
startButton = EH.dragTarget,
startPosition = this.dragStartPosition;
startButton.setState(isc.StatefulCanvas.STATE_UP);
this.hideDropLine();
return EH.STOP_BUBBLING;
},
// reorder an item programmatically
reorderItem : function (itemNum, newPosition) {
this.reorderItems(itemNum, itemNum+1, newPosition);
},
// reorder multiple items programmatically
reorderItems : function (start, end, newPosition) {
// reorder the button config
this.buttons.slideRange(start, end, newPosition);
// and array of button widgets
this.reorderMembers(start, end, newPosition);
// update which buttons can resize
this.setResizeRules();
},
// Drag Resizing (of buttons)
// --------------------------------------------------------------------------------------------
// sent whem button dragging for resizing begins
dragResizeMemberStart : function () {
var EH = this.ns.EH,
item = EH.dragTarget,
itemNum = this.getButtonNumber(item),
rtl = this.isRTL();
// if dragging from the left edge, switch to the previous item and drag resize from its right
var offsetDrag = false;
if ((!rtl && EH.resizeEdge == "L") || (rtl && EH.resizeEdge == "R")) {
offsetDrag = true;
itemNum--;
EH.resizeEdge = (rtl ? "L" : "R");
} else if (EH.resizeEdge == "T") {
offsetDrag = true;
itemNum--;
EH.resizeEdge = "B";
}
// if not in a valid item, forget it
if (itemNum < 0 || itemNum >= this.members.length || item == null) return false;
EH.dragTarget = item = this.members[itemNum];
item._oldCanDrop = item.canDrop;
item.canDrop = false;
// NOTE: depended upon by observers (ListGrid)
this._resizePosition = itemNum;
if (item.showDown) item.setState(isc.StatefulCanvas.STATE_DOWN);
if (offsetDrag) {
var mouseDownItem = this.members[itemNum+1];
if (mouseDownItem) mouseDownItem.setState(isc.StatefulCanvas.STATE_UP);
}
return EH.STOP_BUBBLING;
},
// sent whem item moves during drag-resizing
dragResizeMemberMove : function () {
var EH = this.ns.EH,
item = EH.dragTarget;
// resize the item
item.resizeToEvent();
// do an immediate redraw for responsiveness
item.redrawIfDirty("dragResize");
return EH.STOP_BUBBLING;
},
// sent whem item dragging for resizing ends
dragResizeMemberStop : function () {
var EH = this.ns.EH,
item = EH.dragTarget;
// restore old canDrop setting
item.canDrop = item._oldCanDrop;
// change appearance back
item.setState(isc.StatefulCanvas.STATE_UP);
// resize
item.resizeToEvent();
// record the new size
var newSize = (this.vertical ? item.getHeight() : item.getWidth());
this.resizeItem(this._resizePosition, newSize);
if (this.itemDragResized) this.itemDragResized(this._resizePosition, newSize); // for observers
return EH.STOP_BUBBLING;
},
// resize an item programmatically
resizeItem : function (itemNum, newSize) {
// resize the item
var item = this.members[itemNum];
if (this.vertical) item.setHeight(newSize);
else item.setWidth(newSize);
}
});
isc.Toolbar.registerStringMethods({
// itemClick handler for when an item is clicked
// (JSDoc comment next to default implementation)
itemClick : "item,itemNum",
//> @method toolbar.itemDragResized
// Observable, overrideable method - called when one of the Toolbar buttons is drag resized.
//
// @param itemNum (number) the index of the item that was resized
// @param newSize (number) the new size of the item
//
// @visibility external
//<
itemDragResized : "itemNum,newSize",
// Sent when an item is drag reordered. This can be observed to have a related widget
// rearrange itself.
itemDragReordered : "itemNum,newPosition"
});