PDA

View Full Version : [2.1] Ext.ux.Wiz - a wizard-component for Ext JS



Pages : [1] 2

ThorstenSuckow
26 May 2008, 5:59 AM
Edit: 31.05.2009
Changelog

fixed: dialog would be closable even if teh dialog state would be set to "disabled"; added "beforeclose"-listener to prevent closing the dialog in case the dialog is busy
fixed: loadmask-msg would not be rendered due to accessing an object property quoted
fixed: typo in Header.js (closes google issue #1)
enhancement: converted tabs to spaces


Edit: 07.05.2009
Changelog

enhancement: Updated component to work with Ext JS 3.0 while keeping backwards compatibility to Ext 2.*



Edit: 31.05.2008
Changelog

fixed: wrong regex-mask in examples/SimpleWizard.html allowed for wrong user-input data
enhancement: removed initListeners implementation to initEvents-override in Ext.ux.Wiz
fixed: added 'beforecardhide'-event to Ext.ux.Wiz.Card since some listeners for the beforehide-event could block hiding the card when CardLayout requests it after rendering
fixed: changed default hideMode in Ext.ux.Wiz.Card to 'display' to prevent rendering issues with IE7 due to the latest changes


Edit: 28.05.2008
Changelog

fixed: IE6 did not render the step-indicator images as expected, added style-rule in ext-ux-wiz.css (thanks to donssmith)
fixed: IE6 complained about multiple OR conditions in Ext.ux.Wiz.initComponent


Edit: 26.05.2008
Changelog

fixed: IE complained about semicolon in examples/SimpleWizard.html
fixed: removed "!important"-style-override in examples/SimpleWizard.html since IE doesn't like it
fixed: quoted "default" in Ext.ux.Wiz.loadMaskConfig since it's a keyword in IE


----

I was able to release my wizard-component earlier than I thought - possible due to my employer Refined Labs GmbH (http://www.refinedlabs.com).

Similiar to the livegrid (http://www.siteartwork.de/livegrid)- and youtubeplayer (http://www.siteartwork.de/youtubeplayer)-extension released earlier, this component grew as a sub-project out of the larger application I’m currently working on.

Since I wasn’t pleased with the solutions already floating around in userland, I created my own wizard-component for Ext JS. Target: Simplify setup processes and the collecting of data where a guide is useful; keep the implementation process as simple as possible, but flexible enough to allow for complex instances (this goes specially for validating user input and therefor resulting component behavior).


http://www.siteartwork.de/wizardcomponent/wizardexample.jpg




The component consists of three classes:


Ext.ux.Wiz (deriving from Ext.Window): The base class for a wizard component. A window that holds paging-buttons (previous/next step) and a button for canceling the wizard. Flexible event-listener behavior guarantees complex wizard workflows. Paging-buttons will be enabled/disabled based on the validator-rules found in the current active card (type: Ext.ux.Wiz.Card).

Ext.ux.Wiz.Header (deriving from Ext.BoxComponent): A component that can be used as a header/footer/whatever to indicate the progress in the wizard. Also shows information about the wizard’s purpose, as configured.

Ext.ux.Wiz.Card (deriving from Ext.FormPanel): Each card represents a step in the setup-process and holds either form-elements which are validated automatically (monitorValid:true) or plain informations about the progress in the wizard so far, or both. An array of instances of Ext.ux.Wiz.Card will be passed to Ext.ux.Wiz to automatically build and layout the wizard.


Blog entry: http://www.siteartwork.de/2008/05/26/extuxwiz-a-wizard-component-for-ext-js/
Project-home: http://www.siteartwork.de/wizardcomponent

jay@moduscreate.com
26 May 2008, 6:13 PM
that's sharp :)

mystix
26 May 2008, 7:26 PM
+1 million

never realised you had all these hosted on googlecode :)

donssmith
26 May 2008, 7:37 PM
Very well done!

I added background-repeat: repeat-x;


.ext-ux-wiz-Header-stepIndicator {
margin-left:28px;
float:left;
background-image:url(../images/ext-ux-wiz-stepIndicator.png) !important;
background-position:6px 0px;
background-repeat: repeat-x;
height:6px;
width:6px;
}

for the step indicator image to work correctly in IE6.

Animal
26 May 2008, 11:46 PM
Impressive as ever! Nice implementation.

gelleneu
27 May 2008, 12:29 AM
Sounds good, but I can't find the download (google code download is empty) and/or some Live Demos?

sigaref
27 May 2008, 12:31 AM
Sounds good, but I can't find the download (google code download is empty) and/or some Live Demos?

You have to checkout the code from the Google Code SVN.


svn checkout http://ext-ux-wiz.googlecode.com/svn/trunk/ ext-ux-wiz-read-only

ajaxvador
27 May 2008, 12:50 AM
nice and nice=D>

ThorstenSuckow
27 May 2008, 4:25 PM
Thanks for the feedback so far!



I added background-repeat: repeat-x;


.ext-ux-wiz-Header-stepIndicator {
margin-left:28px;
float:left;
background-image:url(../images/ext-ux-wiz-stepIndicator.png) !important;
background-position:6px 0px;
background-repeat: repeat-x;
height:6px;
width:6px;
}

for the step indicator image to work correctly in IE6.

I've added this fix, along with another bug-fix related to IE6. Latest version can be checked out from the trunk.

wm003
28 May 2008, 10:20 PM
=D>=D> Very well done as always! Thank you very much for sharing!

Ylodi
28 May 2008, 10:59 PM
Very nice, but there is a little bug (or unwanted behaviour) in your demo: enter Lastname and press tab key.

Firefox 2.0.0.14.

7085

ThorstenSuckow
29 May 2008, 12:18 AM
Very nice, but there is a little bug (or unwanted behaviour) in your demo: enter Lastname and press tab key.

Firefox 2.0.0.14.

7085


:-? nice find... I'll take care ot this, thx

jsakalos
30 May 2008, 12:53 PM
Perfect, I'm glad you've coded it.

A little flaw, can be connected to previous post. FF-2.0.0.14@openSUSE-10.3+upgrades. Maybe hideMode:'offsets' on cards would help.

Cheers,
Saki

ThorstenSuckow
30 May 2008, 3:19 PM
Perfect, I'm glad you've coded it.

A little flaw, can be connected to previous post. FF-2.0.0.14@openSUSE-10.3+upgrades. Maybe hideMode:'offsets' on cards would help.

Cheers,
Saki


Saki & all, thanks for your feedback! The bug was related to the beforehide-listeners attached to the cards, they would block the layout to hide the item right after rendering them (as per default in CardLayout). Surprisingly, I had to set the hideMode to "display" as default for the cards, to prevent rendering issues with IE7 (could have probably used doLayout right after the "show"-event fires but this seemed too expensive for me).

Additionaly, I introduced a new event called "beforecardhide" which behaves like the card's "beforehide"-event with the only difference that it only get's fired if the item (which is about to be hidden) equals to the currently active-item in the card-layout. Any additional validators you might need should listen to this event instead of the beforehide-event (see problems with CardLayout and "beforehide").

However, bug is fixed in svn, see changelog on page 1!

J.B
2 Jun 2008, 6:00 AM
This is a great little extension.

Is it possible (easy enough) to extract and use the Step 1 of X: label and indicators. I do not need the window and all the features, but I'd really like to use that progress indicator within a form.

Thanks

Fabyo
2 Jun 2008, 7:49 AM
There is the source code for download?

mystix
4 Jun 2008, 9:34 PM
@Fabyo:


You have to checkout the code from the Google Code SVN.


svn checkout http://ext-ux-wiz.googlecode.com/svn/trunk/ ext-ux-wiz-read-only

JEBriggs
7 Jun 2008, 9:23 AM
I just got the latest src from Google and ran into two problems under IE7 (FireFox works fine). First, IE7 treats default as a reserved keyword, so I modified the mask config:



loadMaskConfig : {
defaultMask : 'Saving...'
},


Second, again under IE7, I the wizard won't appear until I comment out the body style:


cardPanelConfig : {
defaults : {
baseCls : 'x-small-editor',
// bodyStyle : 'padding:40px 15px 5px 120px;background-color:#F6F6F6 !important;',
border : false
}
},

Of course, when I do that, the indicator does not show up correctly.

ThorstenSuckow
7 Jun 2008, 12:39 PM
Are you sure you're using the latest code out of the trunk? The current version still uses "default" in its config, but it's quoted, so no problems with IE7.

http://www.siteartwork.de/wizardcomponent_demo/ shows the example setup that can be found within the repository - testing with IE7 gives me no errors.

donssmith
7 Jun 2008, 12:57 PM
@JEBriggs

The latest code I have from google code has the default issue corrected. He did this by using 'default' instead of default. The code should be


loadMaskConfig : {
'default' : 'Saving...'
},


@MindPatterns
I found a small bug in showLoadMask that causes a custom loadMaskConfig not to be displayed.

This

this.loadMask.msg = this.loadMaskConfig['type'];

should be replaced with

this.loadMask.msg = this.loadMaskConfig[type];

ThorstenSuckow
7 Jun 2008, 2:14 PM
showLoadMask[/B] that causes a custom loadMaskConfig not to be displayed.

This

this.loadMask.msg = this.loadMaskConfig['type'];

should be replaced with

this.loadMask.msg = this.loadMaskConfig[type];

Thanks, I'll take care of it asap! ~o)

knarz
8 Jun 2008, 11:55 PM
Hi and thanks for this ultracool extension!

For people with submit/value access problems here is a example:



var wizard = new Ext.ux.Wiz({
title : 'Konfigurations-Assistent',
headerConfig : {
title : 'Konfigurations-Assistent'
},
// 1. Add a listener to the Wizard
listeners: {
finish: function() { saveConfig( this.getWizardData() ) }
},
....


2. In your card-configs, specify a card-id


new Ext.ux.Wiz.Card({
title : 'General Setup',
id: 1, //here
....



and then


function saveConfig(obj) {

// 3a. you can access an array with the values, where the number is the card-id (which must be specified) and followed by the fieldname
alert( obj[1]['my_field'] );

// 3b. then you can submit the values for example with a ajax request
Ext.Ajax.request({
....

donssmith
9 Jun 2008, 8:14 PM
I think I've found another bug.

It looks like the initial page of the wizard isn't getting initialized correctly if it requires validation (monitorValid : true). If you take your SimpleWizard.html and remove the opening non-validation card you'll be able to reproduce this. I think this is occurring because the card level onCardShow event handler isn't getting called for the initial page with validation. Since this doesn't get called the startMonitoring method doesn't get called.

anjelika
11 Jun 2008, 4:29 AM
Thanks for the great extension.
I have two questions, tho:
1 - if I want to change the next card content (fields) depending on a selection made on the current card (a ComboBox, let's say)...how can I achieve that?
2 - are there any other events trigerred outside finish and cancel? can I add an event for buttonNextclick?
3 - is there a way to use this extension as a form instead as a window?
Thanks

anjelika
16 Jun 2008, 12:20 AM
Theres another mysterious bug here as well. For some reason the dropdown lists are being cropped. They ought to be at least as long as the words in the list but they aren't.
I've inspected it with FireBug and can't find any CSS that shouldn't be there. Anyone else experiencing this?

JEBriggs
18 Jun 2008, 8:49 AM
Theres another mysterious bug here as well. For some reason the dropdown lists are being cropped. They ought to be at least as long as the words in the list but they aren't.
I've inspected it with FireBug and can't find any CSS that shouldn't be there. Anyone else experiencing this?

I noticed the same drop down issue, but ended up switching to radio buttons, so I didn't pursue the cause.

Unrelated to the wizard, but I couldn't find a simple way for doing validation on radio buttons because that is not built into ExtJs.

mystix
18 Jun 2008, 8:52 AM
Unrelated to the wizard, but I couldn't find a simple way for doing validation on radio buttons because that is not built into ExtJs.


have you grabbed the latest SVN copy? RadioGroups and CheckboxGroups have been added, and include validation for allowBlank.

DhakouaniM
18 Jun 2008, 8:33 PM
Hi !

I loved this component so much that I wanted to share with you some improvements I made to it.
Especially, now, the component behaves normally if the first Card contains some valdiation logic, and each Card accepts a boolean 'skip' config param which allows to skip some Cards during the process.
A card can also be dynamically set to be skipped by using the setSkip(boolean) method.

Hope it helps.... Thx for the initial great work !!

...Mehdi DHAKOUANI...

P.S.: I'm not very knowledgeable of SVN (for now), so here's the whole new code:

Ext.ux.Wiz:

Ext.namespace('Ext.ux');


/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz
* @extends Ext.Window
*
* A specific {@link Ext.Window} that models a wizard component.
* A wizard is basically a dialog that guides a user through various steps
* where he has to fill out form-data.
* A {@link Ext.ux.Wiz}-component consists typically of a {@link Ext.ux.Wiz.Header}
* and window-buttons ({@link Ext.Button}) which are linked to the {@link Ext.ux.Wiz.Card}s
* which themself represent the forms the user has to fill out.
*
* In order to switch between the cards in the wizard, you need the {@link Ext.ux.layout.CardLayout},
* which will check if an active-item can be hidden, before the requested new item will be set to
* 'active', i.e. shown. This is needed since the wizard may not allow a card to be hidden, if
* the input entered by the user was not valid. You can get this custom layout at
* {@link http://www.siteartwork.de/cardlayout}.
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz = Ext.extend(Ext.Window, {

/**
* @cfg {Object} An object containing the messages for the {@link Ext.LoadMask}
* covering the card-panel on request, whereas the property identifies the
* msg-text to show, and the value is the message text itself. Defaults to
<pre><code>
{
default : 'Saving...'
}
</code></pre>
*
* Depending on the contexts the loadMask has to be shown in (using the method
* showLoadMask of this class), the object can be configure to hold
* various messages.
<pre><code>
this.loadMaskConfig = {
default : 'Saving...',
validating : 'Please wait, validating input...',
};
// loadMask will be shown, displaying the message 'Please wait, validating input...'
this.showLoadMask(true, 'validating');
</code></pre>
*/
loadMaskConfig : {
'default' : 'Saving...'
},

/**
* @cfg {Number} height The height of the dialog. Defaults to "400".
*/
height : 400,

/**
* @cfg {Number} width The width of the dialog. Defaults to "540".
*/
width : 540,

/**
* @cfg {Boolean} closable Wether the dialog is closable. Defaults to "true".
*/
closable : true,

/**
* @cfg {Boolean} resizable Wether the dialog is resizable. Defaults to "false".
*/
resizable : false,

/**
* @cfg {Boolean} modal Wether the dialog is modal. Defaults to "true".
*/
modal : true,

/**
* @cfg {Array} cards A numeric array with the configured {@link Ext.ux.Wiz.Card}s.
* The index of the cards in the array represent the order in which they get displayed
* in the wizard (i.e. card at index 0 gets displayed in the first step, card at index 1 gets
* displayed in the second step and so on).
*/
cards : null,

/**
* @cfg {String} previousButtonText The text to render the previous-button with.
* Defaults to "&lt; Back" (< Back)
*/
previousButtonText : '&lt; Previous',

/**
* @cfg {String} nextButtonText The text to render the next-button with.
* Defaults to "Next &gt;" (Next >)
*/
nextButtonText : 'Next &gt;',

/**
* @cfg {String} cancelButtonText The text to render the cancel-button with.
* Defaults to "Cancel"
*/
cancelButtonText : 'Cancel',

/**
* @cfg {String} finishButtonText The text to render the next-button with when the last
* step of the wizard is reached. Defaults to "Finish"
*/
finishButtonText : 'Finish',

/**
* @cfg {Object} headerConfig A config-object to use with {@link Ext.ux.Wiz.Header}.
* If not present, it defaults to an empty object.
*/
headerConfig : {},

/**
* @cfg {Object} cardPanelConfig A config-object to use with {@link Ext.Panel}, which
* represents the card-panel in this dialog.
* If not present, it defaults to an empty object
*/
cardPanelConfig : {},

/**
* @param {Ext.Button} The window-button for paging to the previous card.
* @private
*/
previousButton : null,

/**
* @param {Ext.Button} The window-button for paging to the next card. When the
* last card is reached, the event fired by and the text rendered to this button
* will change.
* @private
*/
nextButton : null,

/**
* @param {Ext.Button} The window-button for canceling the wizard. The event
* fired by this button will usually close the dialog.
* @private
*/
cancelButton : null,

/**
* @param {Ex.Panel} The card-panel that holds the various wizard cards
* ({@link Ext.ux.Wiz.Card}). The card-panel itself uses the custom
* {@link Ext.ux.layout.CardLayout}, which needs to be accessible by this class.
* You can get it at {@link http://www.siteartwork.de/cardlayout}.
* @private
*/
cardPanel : null,

/**
* @param {Number} currentCard The current {@link Ext.ux.Wiz.Card} displayed.
* Defaults to -1.
* @private
*/
currentCard : -1,

/**
* @param {Ext.ux.Wiz.Header} The header-panel of the wizard.
* @private
*/
headPanel : null,

/*
* Initializes this component with the specified config-properties and automatically
* creates its components.
*/
initComponent : function() {
this.initButtons();
this.initPanels();

var title = this.title || this.headerConfig.title;
title = title || "";

Ext.apply(this, {
title : title,
layout : 'border',
buttons : [
this.previousButton,
this.nextButton,
this.cancelButton
],
items : [
this.headPanel,
this.cardPanel
]
});

this.addEvents(
/**
* @event cancel
* Fires after the cancel-button has been clicked.
* @param {Ext.ux.Wiz} this
*/
'cancel',
/**
* @event finish
* Fires after the last card was reached in the wizard and the
* next/finish-button has been clicked.
* @param {Ext.ux.Wiz} this
* @param {Object} data The collected data of the cards, whereas
* the index is the id of the card and the specific values
* are objects with key/value pairs in the form formElementName : value
*/
'finish'
);

Ext.ux.Wiz.superclass.initComponent.call(this);
},

/**
* Overrides parent implementation. This is needed because in case
* this method uses "monitorValid=true", the method "startMonitoring" must
* not be called, until the "show"-event of this card fires.
*/
initEvents : function() {
Ext.ux.Wiz.superclass.initEvents.call(this);
for (var i = 0, len = this.cards.length ; i < len ; i++) {
this.cards[i].on('show', this.onCardShow, this);
this.cards[i].on('hide', this.onCardHide, this);
this.cards[i].on('clientvalidation', this.onClientValidation, this);
}
},


// -------- helper
/*
* Returns the form-data of all cards in this wizard. The first index is the
* id of the card in this wizard,
* and the values are objects containing key/value pairs in the form of
* fieldName : fieldValue.
*
* @return {Array}
*/
getWizardData : function() {
var formValues = {};
var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++)
if (cards[i].form)
formValues[cards[i].id] = cards[i].form.getValues(false);
else
formValues[cards[i].id] = {};
return formValues;
},

/**
* Switches the state of this wizard between disabled/enabled.
* A disabled dialog will have a {@link Ext.LoadMask} covering the card-panel
* to prevent user input, and the buttons will be rendered disabled/enabled.
* If the dialog is clsable, the close-tool will be masked, too.
*
* @param {Boolean} enabled "false" to prevent user input and mask the elements,
* otherwise true.
* @param {String} type The type of msg for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
switchDialogState: function(enabled, type) {
this.showLoadMask(!enabled, type);

this.previousButton.setDisabled(!enabled);
this.nextButton.setDisabled(!enabled);
this.cancelButton.setDisabled(true);

if (this.closable) {
var ct = this.tools['close'];
if (enabled == true)
this.tools['close'].unmask();
else
this.tools['close'].mask();
}
},

/*
* Shows the load mask for this wizard. By default, the cardPanel's body
* will be masked.
*
* @param {Boolean} show True to show the load mask, False otherwise.
* @param {String} type The type of message for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
showLoadMask: function(show, type) {
if (!type)
type = 'default';

if (show) {
if (this.loadMask == null)
this.loadMask = new Ext.LoadMask(this.body);
this.loadMask.msg = this.loadMaskConfig['type'];
this.loadMask.show();
} else {
if (this.loadMask)
this.loadMask.hide();
}
},

/**
* Creates the head- and the card-panel.
* Be sure to have the custom {@link Ext.ux.layout.CardLayout} available
* in order to make the card-panel work as expected by this component
* ({@link http://www.siteartwork.de/cardlayout}).
*/
initPanels : function() {
var cards = this.cards;
var cardPanelConfig = this.cardPanelConfig;

// Initializes the cards with their parent wizard
for (var i = 0 ; i < this.cards.length ; i++)
this.cards[i].wizard = this;

// Initializes the header with their parent wizard
Ext.apply(this.headerConfig, {
steps : this.getVisibleCardsCount(),
wizard: this
});
this.headPanel = new Ext.ux.Wiz.Header(this.headerConfig);

Ext.apply(cardPanelConfig, {
layout : new Ext.ux.layout.CardLayout(),
items : cards
});
var faci = this.getFirstActiveCardIndex();
Ext.applyIf(cardPanelConfig, {
region : 'center',
border : false,
activeItem : faci
});
this.cardPanel = new Ext.Panel(cardPanelConfig);
if (this.cards[faci].monitorValid)
this.cards[faci].startMonitoring();
},

/**
* Creates the instances for the the window buttons.
*/
initButtons : function() {
this.previousButton = new Ext.Button({
text : this.previousButtonText,
disabled : true,
minWidth : 75,
handler : this.onPreviousClick,
scope : this
});

this.nextButton = new Ext.Button({
text : this.nextButtonText,
minWidth : 75,
handler : this.onNextClick,
scope : this
});

this.cancelButton = new Ext.Button({
text : this.cancelButtonText,
handler : this.onCancelClick,
scope : this,
minWidth : 75
});
},

// -------- listeners
/**
* By default, the card firing this event monitors user input in a frequent
* interval and fires the 'clientvalidation'-event along with it. This listener
* will enable/disable the next/finish-button in accordance with it, based upon
* the parameter isValid. isValid" will be set by the form validation and depends
* on the validators you are using for the different input-elemnts in your form.
* If the card does not contain any forms, this listener will never be called by the
* card itself.
*
* @param {Ext.ux.Wiz.Card} The card that triggered the event.
* @param {Boolean} isValid "true", if the user input was valid, otherwise
* "false"
*/
onClientValidation : function(card, isValid) {
if (!isValid)
this.nextButton.setDisabled(true);
else
this.nextButton.setDisabled(false);
},

/**
* This will render the "next" button as disabled since the bindHandler's delay
* of the next card to show might be lagging on slower systems
*
*/
onCardHide : function(card) {
if (this.cardPanel.layout.activeItem.id === card.id)
this.nextButton.setDisabled(true);
},


/*
* Listener for the "show" event of the card that gets shown in the card-panel.
* Renders the next/previous buttons based on the position of the card in the wizard
* and updates the head-panel accordingly.
*
* @param {Ext.ux.Wiz.Card} The card being shown.
*/
onCardShow : function(card) {
this.currentCard = this.getCardPosition(card);
this.headPanel.updateStep();

if (this.cards[this.currentCard].getNext() == -1)
this.nextButton.setText(this.finishButtonText);
else
this.nextButton.setText(this.nextButtonText);

if (card.isValid())
this.nextButton.setDisabled(false);

if (this.cards[this.currentCard].getPrevious() == -1)
this.previousButton.setDisabled(true);
else
this.previousButton.setDisabled(false);
},

/*
* Fires the 'cancel'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onCancelClick : function() {
if (this.fireEvent('cancel', this) !== false)
this.close();
},

/*
* Fires the 'finish'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onFinish : function() {
if (this.fireEvent('finish', this, this.getWizardData()) !== false)
this.close();
},

/*
* Listener for the previous-button.
* Switches to the previous displayed {@link Ext.ux.Wiz.Card}.
*/
onPreviousClick : function() {
var prevCardIndex = this.cards[this.currentCard].getPrevious();
if (prevCardIndex != -1)
this.cardPanel.getLayout().setActiveItem(prevCardIndex);
},

/**
* Listener for the next-button. Switches to the next {@link Ext.ux.Wiz.Card}
* if the 'beforehide'-method of it did not return false. The functionality
* for this is implemented in {@link Ext.ux.layout.CardLayout}, which is needed
* as the layout for the card-panel of this component.
*/
onNextClick : function() {
var nextCardIndex = this.cards[this.currentCard].getNext();
if (nextCardIndex == -1)
this.onFinish();
else
this.cardPanel.getLayout().setActiveItem(nextCardIndex);
},

getCardPosition: function(card) {
var length = this.cards.length;
for (var i = 0 ; i < length ; i++)
if (this.cards[i] == card)
return i;
return -1;
},

getVisibleCardPosition: function(card) {
var cci = this.getFirstActiveCardIndex();
if (cci == -1)
return -1;
var cc = this.cards[cci];
var vcci = 0;
while (cci != -1) {
if (this.cards[cci] == card)
break;
cci = this.cards[cci].getNext();
vcci++;
}
return vcci;
},

getFirstActiveCardIndex: function() {
var firstActiveCardIndex = 0;
while (this.cards[firstActiveCardIndex].skip && firstActiveCardIndex != -1)
firstActiveCardIndex = this.cards[firstActiveCardIndex].getNext();
return firstActiveCardIndex;
},

getVisibleCardsCount: function() {
var cci = this.getFirstActiveCardIndex();
if (cci == -1)
return 0;
var count = 0;
while (cci != -1) {
count++;
cci = this.cards[cci].getNext();
}
return count;
}
});

Ext.ux.Wiz.Card:


Ext.namespace('Ext.ux.Wiz');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz.Card
* @extends Ext.FormPanel
*
* A specific {@link Ext.FormPanel} that can be used as a card in a
* {@link Ext.ux.Wiz}-component. An instance of this card does only work properly
* if used in a panel that uses a {@see Ext.layout.CardLayout}-layout.
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz.Card = Ext.extend(Ext.FormPanel, {

/**
* @cfg {Boolean} header "True" to create the header element. Defaults to
* "false". See {@link Ext.form.FormPanel#header}
*/
header : false,

/**
* @cfg {String} hideMode Hidemode of this component. Defaults to "offsets".
* See {@link Ext.form.FormPanel#hideMode}
*/
hideMode : 'display',

/**
* @cfg {String} skip If this card should be skipped during process.Defaults to false.
*/
skip : false,

/**
* @cfg {Ext.ux.Wiz} wizard The parent wizard.
*/
wizard : null,

initComponent : function() {
this.addEvents(
/**
* @event beforecardhide
* If you want to add additional checks to your card which cannot be easily done
* using default validators of input-fields (or using the monitorValid-config option),
* add your specific listeners to this event.
* This event gets only fired if the activeItem of the ownerCt-component equals to
* this instance of {@see Ext.ux.Wiz.Card}. This is needed since a card layout usually
* hides it's items right after rendering them, involving the beforehide-event.
* If those checks would be attached to the normal beforehide-event, the card-layout
* would never be able to hide this component after rendering it, depending on the
* listeners return value.
*
* @param {Ext.ux.Wiz.Card} card The card that triggered the event
*/
'beforecardhide'
);

Ext.ux.Wiz.Card.superclass.initComponent.call(this);
},

// -------- helper
isValid : function() {
if (this.monitorValid)
return this.bindHandler();
return true;
},

// -------- overrides
/*
* Overrides parent implementation since we allow to add any element
* in this component which must not be neccessarily be a form-element.
* So before a call to "isValid()" is about to be made, this implementation
* checks first if the specific item sitting in this component has a method "isValid"
* to prevent errors.
*/
bindHandler : function() {
if (!this.bound)
return false; // stops binding

var valid = true;
this.form.items.each(function(f) {
if (f.isValid && !f.isValid(true)) {
valid = false;
return false;
}
});

if (this.buttons) {
for (var i = 0, len = this.buttons.length; i < len; i++) {
var btn = this.buttons[i];
if(btn.formBind === true && btn.disabled === valid)
btn.setDisabled(!valid);
}
}

this.fireEvent('clientvalidation', this, valid);
},

/**
* Overrides parent implementation. This is needed because in case
* this method uses "monitorValid=true", the method "startMonitoring" must
* not be called, until the "show"-event of this card fires.
*/
initEvents : function() {
var old = this.monitorValid;
this.monitorValid = false;
Ext.ux.Wiz.Card.superclass.initEvents.call(this);
this.monitorValid = old;

this.on('beforehide', this.bubbleBeforeHideEvent, this);
this.on('beforecardhide', this.isValid, this);
this.on('show', this.onCardShow, this);
this.on('hide', this.onCardHide, this);
},

// -------- listener
/*
* Checks wether the beforecardhide-event may be triggered.
*/
bubbleBeforeHideEvent : function() {
var ly = this.ownerCt.layout;
var activeItem = ly.activeItem;

if (activeItem && activeItem.id === this.id)
return this.fireEvent('beforecardhide', this);

return true;
},

/*
* Stops monitoring the form elements in this component when the
* 'hide'-event gets fired.
*/
onCardHide : function() {
if (this.monitorValid)
this.stopMonitoring();
},

/**
* Starts monitoring the form elements in this component when the
* 'show'-event gets fired.
*/
onCardShow : function() {
if (this.monitorValid)
this.startMonitoring();
},

getPrevious : function() {
// No cards before the first one
var pos = this.wizard.getCardPosition(this);
if (pos <= 0)
return -1;
while (--pos >= 0)
if (this.wizard.cards[pos].skip == false)
return pos;
return pos;
},

getNext : function() {
// No cards after the last one
var pos = this.wizard.getCardPosition(this);
if (pos >= (this.wizard.cards.length-1))
return -1;
while (++pos < this.wizard.cards.length)
if (this.wizard.cards[pos].skip == false)
return pos;
return -1;
},

setSkip: function(skip) {
if (this.wizard.cards[this.wizard.currentCard] == this)
return false;
if (this.skip != skip) {
this.skip = skip;
this.wizard.headPanel.initIndicators();
this.wizard.headPanel.updateStep();
return true;
}
return false;
}
});

Ext.ux.Wiz.Header:


Ext.namespace('Ext.ux.Wiz');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz.Header
* @extends Ext.BoxComponent
*
* A specific {@link Ext.BoxComponent} that can be used to show the current process in an
* {@link Ext.ux.Wiz}.
*
* An instance of this class is usually being created by {@link Ext.ux.Wiz#initPanels} using the
* {@link Ext.ux.Wiz#headerConfig}-object.
*
* @private
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz.Header = Ext.extend(Ext.BoxComponent, {

/*
* @cfg {Number} height The height of this component. Defaults to "55".
*/
height: 55,

/*
* @cfg {String} region The Region of this component. Since a {@link Ext.ux.Wiz}
* usually uses a {@link Ext.layout.BorderLayout}, this property defaults to
* "north". If you want to change this property, you should also change the appropriate
* css-classes that are used for this component.
*/
region: 'north',

/*
* @cfg {String} title The title that gets rendered in the head of the component. This
* should be a text describing the purpose of the wizard.
*/
title: 'Wizard',

/*
* @cfg {Number} steps The overall number of steps the user has to go through
* to finish the wizard.
*/
steps: 0,

/*
* @cfg {String} stepText The text in the header indicating the current process in the wizard.
* (defaults to "Step {0} of {1}: {2}").
* {0} is replaced with the index (+1) of the current card, {1} is replaced by the
* total number of cards in the wizard and {2} is replaced with the title-property of the
* {@link Ext.ux.Wiz.Card}
* @type String
*/
stepText: "Step {0} of {1}: {2}",

/**
* @cfg {Object} autoEl The element markup used to render this component.
*/
autoEl: {
tag : 'div',
cls : 'ext-ux-wiz-Header',
children: [ {
tag: 'div',
cls: 'ext-ux-wiz-Header-title'
}, {
tag : 'div',
children: [{
tag : 'div',
cls : 'ext-ux-wiz-Header-step'
}, {
tag : 'div',
cls : 'ext-ux-wiz-Header-stepIndicator-container'
}]
}]
},

/*
* @param {Ext.Element}
*/
titleEl : null,

/*
* @param {Ext.Element}
*/
stepEl : null,

/*
* @param {Ext.Element}
*/
imageContainer : null,

/*
* @param {Array}
*/
indicators : null,

/**
* @param {Ext.ux.Wiz}
*/
wizard : null,

/**
* @param {Ext.Template}
*/
stepTemplate : null,

/*
* @param {Number} lastVisibleCardIndex Stores the index of the last active card that
* was shown-
*/
lastVisibleCardIndex : -1,

initComponent: function() {
Ext.ux.Wiz.Header.superclass.initComponent.call(this);
this.wizard = this.initialConfig.wizard;
this.initIndicators();
},

initIndicators: function() {
this.indicators = [];
var image = null;
for (var i = 0, len = this.wizard.getVisibleCardsCount(); i < len; i++) {
image = document.createElement('div');
image.innerHTML = "*";
image.className = 'ext-ux-wiz-Header-stepIndicator';
this.indicators[i] = new Ext.Element(image);
}
},

// -------- helper
/*
* Gets called by {@link Ext.ux.Wiz#onCardShow()} and updates the header
* with the appropriate information, such as the progress of the wizard
* (i.e. which card is being shown etc.)
*
* @param {Number} currentStep The index of the card currently shown in
* the wizard
* @param {String} title The title-property of the {@link Ext.ux.Wiz.Card}
*
* @private
*/
updateStep: function() {
var currentCard = this.wizard.cards[this.wizard.currentCard];
var currentVisibleCardIndex =this.wizard.getVisibleCardPosition(currentCard);
var html = this.stepTemplate.apply({
0 : currentVisibleCardIndex + 1,
1 : this.wizard.getVisibleCardsCount(),
2 : currentCard.title
});

this.stepEl.update(html);
var els = this.el.dom.firstChild.nextSibling.lastChild.childNodes;
for (var i = 0, len = els.length ; i < len ; i++)
Ext.get(els[0]).remove();
for (var i = 0, len = this.indicators.length; i < len; i++)
this.imageContainer.appendChild(this.indicators[i]);

if (this.lastVisibleCardIndex != -1)
this.indicators[this.lastVisibleCardIndex].removeClass('ext-ux-wiz-Header-stepIndicator-active');

this.indicators[currentVisibleCardIndex].addClass('ext-ux-wiz-Header-stepIndicator-active');

this.lastVisibleCardIndex = currentVisibleCardIndex;
},

// -------- listener
/**
* Overrides parent implementation to render this component properly.
*/
onRender: function(ct, position) {
Ext.ux.Wiz.Header.superclass.onRender.call(this, ct, position);

this.stepTemplate = new Ext.Template(this.stepText),
this.stepTemplate.compile();

var el = this.el.dom.firstChild;
var ns = el.nextSibling;

this.titleEl = new Ext.Element(el);
this.stepEl = new Ext.Element(ns.firstChild);
this.imageContainer = new Ext.Element(ns.lastChild);

this.titleEl.update(this.title);

for (var i = 0, len = this.indicators.length; i < len; i++)
this.imageContainer.appendChild(this.indicators[i]);
}
});

Scorpie
18 Jun 2008, 10:45 PM
What a great plugin, I vote we take this up in the next release of Ext-core!

JEBriggs
21 Jun 2008, 7:29 AM
have you grabbed the latest SVN copy? RadioGroups and CheckboxGroups have been added, and include validation for allowBlank.

Thanks, I'll check that out.

cutigersfan
23 Jun 2008, 5:54 AM
I need this sort of functionality, but don't want to have it in a window. I'd like to put it in a a regular panel. Has anyone done this? Is this easily adaptable to a panel?

anjelika
1 Jul 2008, 12:48 PM
Hello,
This works if you need a Wiz as a panel instead of window.
However, the combo rendering is still an issue to me, couldn't make it work in FF.
I've also added some events like 'previous', 'next' for previous and next button click and 'step_change' for whenever the current card is changed.


Ext.namespace('Ext.ux');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz
* @extends Ext.Window
*
* A specific {@link Ext.Window} that models a wizard component.
* A wizard is basically a dialog that guides a user through various steps
* where he has to fill out form-data.
* A {@link Ext.ux.Wiz}-component consists typically of a {@link Ext.ux.Wiz.Header}
* and window-buttons ({@link Ext.Button}) which are linked to the {@link Ext.ux.Wiz.Card}s
* which themself represent the forms the user has to fill out.
*
* In order to switch between the cards in the wizard, you need the {@link Ext.ux.layout.CardLayout},
* which will check if an active-item can be hidden, before the requested new item will be set to
* 'active', i.e. shown. This is needed since the wizard may not allow a card to be hidden, if
* the input entered by the user was not valid. You can get this custom layout at
* {@link http://www.siteartwork.de/cardlayout}.
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz = Ext.extend(Ext.Panel, {

/**
* @cfg {Object} An object containing the messages for the {@link Ext.LoadMask}
* covering the card-panel on request, whereas the property identifies the
* msg-text to show, and the value is the message text itself. Defaults to
<pre><code>
{
default : 'Saving...'
}
</code></pre>
*
* Depending on the contexts the loadMask has to be shown in (using the method
* showLoadMask of this class), the object can be configure to hold
* various messages.
<pre><code>
this.loadMaskConfig = {
default : 'Saving...',
validating : 'Please wait, validating input...',
};
// loadMask will be shown, displaying the message 'Please wait, validating input...'
this.showLoadMask(true, 'validating');
</code></pre>
*/
loadMaskConfig : {
'default' : 'Saving...'
},

/**
* @cfg {Number} height The height of the dialog. Defaults to "400".
*/
height : 400,

/**
* @cfg {Number} width The width of the dialog. Defaults to "540".
*/
width : 540,

/**
* @cfg {Boolean} closable Wether the dialog is closable. Defaults to "true".
*/
closable : true,

/**
* @cfg {Boolean} resizable Wether the dialog is resizable. Defaults to "false".
*/
resizable : false,

/**
* @cfg {Boolean} resizable Wether the dialog is modal. Defaults to "true".
*/
modal : true,

/**
* @cfg {Array} cards A numeric array with the configured {@link Ext.ux.Wiz.Card}s.
* The index of the cards in the array represent the order in which they get displayed
* in the wizard (i.e. card at index 0 gets displayed in the first step, card at index 1 gets
* displayed in the second step and so on).
*/
cards : null,

/**
* @cfg {String} previousButtonText The text to render the previous-button with.
* Defaults to "&lt; Back" (< Back)
*/
previousButtonText : '&lt; Back',

/**
* @cfg {String} nextButtonText The text to render the next-button with.
* Defaults to "Next &gt;" (Next >)
*/
nextButtonText : 'Next &gt;',

/**
* @cfg {String} cancelButtonText The text to render the cancel-button with.
* Defaults to "Cancel"
*/
cancelButtonText : 'Cancel',

/**
* @cfg {String} finishButtonText The text to render the next-button with when the last
* step of the wizard is reached. Defaults to "Finish"
*/
finishButtonText : 'Finish',

/**
* @cfg {Object} headerConfig A config-object to use with {@link Ext.ux.Wiz.Header}.
* If not present, it defaults to an empty object.
*/
headerConfig : {},

/**
* @cfg {Object} cardPanelConfig A config-object to use with {@link Ext.Panel}, which
* represents the card-panel in this dialog.
* If not present, it defaults to an empty object
*/
cardPanelConfig : {},

/**
* @param {Ext.Button} The window-button for paging to the previous card.
* @private
*/
previousButton : null,

/**
* @param {Ext.Button} The window-button for paging to the next card. When the
* last card is reached, the event fired by and the text rendered to this button
* will change.
* @private
*/
nextButton : null,

/**
* @param {Ext.Button} The window-button for canceling the wizard. The event
* fired by this button will usually close the dialog.
* @private
*/
cancelButton : null,

/**
* @param {Ex.Panel} The card-panel that holds the various wizard cards
* ({@link Ext.ux.Wiz.Card}). The card-panel itself uses the custom
* {@link Ext.ux.layout.CardLayout}, which needs to be accessible by this class.
* You can get it at {@link http://www.siteartwork.de/cardlayout}.
* @private
*/
cardPanel : null,

/**
* @param {Number} currentCard The current {@link Ext.ux.Wiz.Card} displayed.
* Defaults to -1.
* @private
*/
currentCard : -1,

/**
* @param {Ext.ux.Wiz.Header} The header-panel of the wizard.
* @private
*/
headPanel : null,

/**
* @param {Number} cardCount Helper for storing the number of cards used
* by this wizard. Defaults to 0 (inherits "cards.length" later on).
* @private
*/
cardCount : 0,

/**
* Inits this component with the specified config-properties and automatically
* creates its components.
*/
initComponent : function()
{
this.initButtons();
this.initPanels();

var title = this.title || this.headerConfig.title;
title = title || "";

Ext.apply(this, {
title : title,
layout : 'border',
cardCount : this.cards.length,
buttons : [
this.previousButton,
this.nextButton,
this.cancelButton
],
items : [
this.headPanel,
this.cardPanel
]
});

this.addEvents(
/**
* @event cancel
* Fires after the cancel-button has been clicked.
* @param {Ext.ux.Wiz} this
*/
'cancel',
/**
* @event finish
* Fires after the last card was reached in the wizard and the
* next/finish-button has been clicked.
* @param {Ext.ux.Wiz} this
* @param {Object} data The collected data of the cards, whereas
* the index is the id of the card and the specific values
* are objects with key/value pairs in the form formElementName : value
*/
'finish',
'next',
'step_change'
);

Ext.ux.Wiz.superclass.initComponent.call(this);
},

// -------- helper
/**
* Returns the form-data of all cards in this wizard. The first index is the
* id of the card in this wizard,
* and the values are objects containing key/value pairs in the form of
* fieldName : fieldValue.
*
* @return {Array}
*/
getWizardData : function()
{
var formValues = {};
var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
if (cards[i].form) {
formValues[cards[i].id] = cards[i].form.getValues(false);
} else {
formValues[cards[i].id] = {};
}
}

return formValues;
},

/**
* Switches the state of this wizard between disabled/enabled.
* A disabled dialog will have a {@link Ext.LoadMask} covering the card-panel
* to prevent user input, and the buttons will be rendered disabled/enabled.
* If the dialog is clsable, the close-tool will be masked, too.
*
* @param {Boolean} enabled "false" to prevent user input and mask the elements,
* otherwise true.
* @param {String} type The type of msg for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
switchDialogState : function(enabled, type)
{
this.showLoadMask(!enabled, type);

this.previousButton.setDisabled(!enabled);
this.nextButton.setDisabled(!enabled);
this.cancelButton.setDisabled(true);

if (this.closable) {
var ct = this.tools['close'];
switch (enabled) {
case true:
this.tools['close'].unmask();
break;

default:
this.tools['close'].mask();
break;
}
}
},

/**
* Shows the load mask for this wizard. By default, the cardPanel's body
* will be masked.
*
* @param {Boolean} show true to show the load mask, otherwise false.
* @param {String} type The type of message for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
showLoadMask : function(show, type)
{
if (!type) {
type = 'default';
}

if (show) {
if (this.loadMask == null) {
this.loadMask = new Ext.LoadMask(this.body);
}
this.loadMask.msg = this.loadMaskConfig['type'];
this.loadMask.show();
} else {
if (this.loadMask) {
this.loadMask.hide();
}
}
},


/**
* Inits the listener for the various {@link Ext.ux.Wiz.Card}s used
* by this component.
*/
initEvents : function()
{
Ext.ux.Wiz.superclass.initEvents.call(this);

var cards = this.cards;

for (var i = 0, len = cards.length; i < len; i++) {
cards[i].on('show', this.onCardShow, this);
cards[i].on('hide', this.onCardHide, this);
cards[i].on('clientvalidation', this.onClientValidation, this);
}
},

/**
* Creates the head- and the card-panel.
* Be sure to have the custom {@link Ext.ux.layout.CardLayout} available
* in order to make the card-panel work as expected by this component
* ({@link http://www.siteartwork.de/cardlayout}).
*/
initPanels : function()
{
var cards = this.cards;
var cardPanelConfig = this.cardPanelConfig;

Ext.apply(this.headerConfig, {
steps : cards.length
});

this.headPanel = new Ext.ux.Wiz.Header(this.headerConfig);

Ext.apply(cardPanelConfig, {
layout : new Ext.ux.layout.CardLayout(),
items : cards
});

Ext.applyIf(cardPanelConfig, {
region : 'center',
border : false,
activeItem : 0
});

this.cardPanel = new Ext.Panel(cardPanelConfig);
},

/**
* Creates the instances for the the window buttons.
*/
initButtons : function()
{
this.previousButton = new Ext.Button({
id : 'btn_previous',
text : this.previousButtonText,
disabled : true,
minWidth : 75,
//hidden : true,
handler : this.onPreviousClick,
scope : this
});

this.nextButton = new Ext.Button({
id : 'btn_next',
text : this.nextButtonText,
minWidth : 75,
handler : this.onNextClick,
scope : this
});

this.cancelButton = new Ext.Button({
id : 'btn_cancel',
text : this.cancelButtonText,
handler : this.onCancelClick,
scope : this,
minWidth : 75
});
},

// -------- listeners
/**
* By default, the card firing this event monitors user input in a frequent
* interval and fires the 'clientvalidation'-event along with it. This listener
* will enable/disable the next/finish-button in accordance with it, based upon
* the parameter isValid. isValid" will be set by the form validation and depends
* on the validators you are using for the different input-elemnts in your form.
* If the card does not contain any forms, this listener will never be called by the
* card itself.
*
* @param {Ext.ux.Wiz.Card} The card that triggered the event.
* @param {Boolean} isValid "true", if the user input was valid, otherwise
* "false"
*/
onClientValidation : function(card, isValid)
{
if (!isValid) {
this.nextButton.setDisabled(true);
} else {
this.nextButton.setDisabled(false);
}
},

/**
* This will render the "next" button as disabled since the bindHandler's delay
* of the next card to show might be lagging on slower systems
*
*/
onCardHide : function(card)
{
if (this.cardPanel.layout.activeItem.id === card.id) {
this.nextButton.setDisabled(true);
}
},


/**
* Listener for the "show" event of the card that gets shown in the card-panel.
* Renders the next/previous buttons based on the position of the card in the wizard
* and updates the head-panel accordingly.
*
* @param {Ext.ux.Wiz.Card} The card being shown.
*/
onCardShow : function(card)
{
var parent = card.ownerCt;

var items = parent.items;

for (var i = 0, len = items.length; i < len; i++) {
if (items.get(i).id == card.id) {
break;
}
}

this.currentCard = i;
this.headPanel.updateStep(i, card.title);

if (i == len-1) {
this.nextButton.setText(this.finishButtonText);
} else {
this.nextButton.setText(this.nextButtonText);
}

if (card.isValid()) {
this.nextButton.setDisabled(false);
}

if (i == 0) {
this.previousButton.setDisabled(true);
} else {
this.previousButton.setDisabled(false);
}

},

/**
* Fires the 'cancel'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onCancelClick : function()
{
if (this.fireEvent('cancel', this) !== false) {
//this.close();
}
},

/**
* Fires the 'finish'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onFinish : function()
{
if (this.fireEvent('finish', this, this.getWizardData()) !== false) {
// this.close();
}
},

/**
* Listener for the previous-button.
* Switches to the previous displayed {@link Ext.ux.Wiz.Card}.
*/
onPreviousClick : function()
{
this.fireEvent('step_change', this, this.currentCard-1);
if (this.currentCard > 0) {
if (this.fireEvent('previous', this) !== false) {
this.cardPanel.getLayout().setActiveItem(this.currentCard - 1);
}
}
},

/**
* Listener for the next-button. Switches to the next {@link Ext.ux.Wiz.Card}
* if the 'beforehide'-method of it did not return false. The functionality
* for this is implemented in {@link Ext.ux.layout.CardLayout}, which is needed
* as the layout for the card-panel of this component.
*/
onNextClick : function()
{
this.fireEvent('step_change', this, this.currentCard+1);
if (this.currentCard == this.cardCount-1) {
this.onFinish();
} else {
if (this.fireEvent('next', this) !== false) {
this.cardPanel.getLayout().setActiveItem(this.currentCard+1);
}
}
}
});

lucasmarin
3 Jul 2008, 5:11 AM
I have 3 cards in my Wizard, and need call function when I go the first for second Card.

How I access the data in the object card1 and card2 by function in the WizardWindow, fired by one function in the WizardWindow too.

I can't call the function in the WizardWindow, only by card1 or card2 with
this.card1.on('beforehide') ....

I need access the event next click by WindowWizard, is possible?

My Code Structure is:




Mandic.wizard.EmailCentralWizard = Ext.extend(Ext.ux.Wiz,{


initComponent: function(){

Ext.apply(this, {

id : 'EmailCentral Wizard',
title: 'Add E-mail Account',
resizable: true,
height: 500,
headerConfig: {
title: 'Add and Manage your emails accounts in this window.'
},

cardPanelConfig: {
defaults: {
baseCls: 'x-small-editor',
bodyStyle: 'padding:20px 15px 5px 60px;background-color:#F6F6F6;',
border: false
}
},
cards: [
this.card1 = new card1(),
this.card2 = new card2(),
this.card3 = new card3()
]

})

this.fireEvent('finish',this, [this.card1, this.card2])

Mandic.wizard.EmailCentralWizard.superclass.initComponent.call(this);

},

loadMailData: function(c1){
this.host = c1.getComponent('email').getValue().split('@');
this.host = this.host[1];
return host;
},

card2Complete: function(c1, c2){
alert(this.loadMailData(c1));
}

});


/**
*
* card1 class
*
*/
card1 = Ext.extend(Ext.ux.Wiz.Card, {

initComponent: function(){

Ext.apply(this, {

id: 'card1',
title: 'E-mail Address',
items: [{
border: false,
bodyStyle: 'background:none;padding-bottom:70px;',
html: 'If you have other accounts of e-mail, you can configure up to ' +
'10 accounts in the Central E-mail and download your messages into ' +
'your mailbox mandic, without having to access each provider separately. ' +
'His messages will be placed in a folder created by the system and will ' +
'be free of viruses and spam.'
}, {
border: false,
bodyStyle: 'background:none;padding-bottom:10px;',
autoHeight: true,
html: 'Please enter your e-mail address .'
}, new Ext.form.TextField({
vtype: 'email',
name: 'email',
id: 'email',
fieldLabel: 'E-mail Address'
})],

})

card1.superclass.initComponent.call(this);
},

getEmailByHost: function(){
hostUrl = this.getComponent('email').getValue().split('@');
hostUrl = this.hostUrl[1];
return hostUrl;
}


})



/**
* card2 class
*/
card2 = Ext.extend(Ext.ux.Wiz.Card, {

initComponent: function(){

Ext.apply(this,{

id: 'card2',
title: 'Configurations',
monitorValid: true,
defaultType: 'textfield',
items: [{
name: 'email2',
id: 'email2',
fieldLabel: 'E-mail Address',
allowBlank: false,
vtype: 'email'
}, {
xtype: 'panel',
border: false,
bodyStyle: 'background:none;margin:10px 0 5px 0;',
html: 'If necessary, complete the value field bellow.'
}, {
name: 'login',
id: 'login',
fieldLabel: 'Login',
allowBlank: false
}, {
xtype: 'panel',
border: false,
bodyStyle: 'background:none;margin:10px 0 5px 0;',
html: 'The system complete the server address field automatically. <br />' +
'If necessary, complete manually the field bellow.'
}, {
name: 'server',
id: 'server',
fieldLabel: 'Server',
allowBlank: false
}, {
xtype: 'panel',
border: false,
bodyStyle: 'background:none;margin:10px 0 5px 0;',
html: 'Almost servers use POP3 Protocol'
}, {
xtype: 'radio',
fieldLabel: 'POP',
name: 'serverProtocol'
}, {
xtype: 'radio',
fieldLabel: 'IMAP',
name: 'serverProtocol'
}, {
xtype: 'radio',
fieldLabel: 'POPS',
name: 'serverProtocol'
}, {
xtype: 'radio',
fieldLabel: 'IMAPS',
name: 'serverProtocol'
}, {
xtype: 'panel',
border: false,
bodyStyle: 'background:none;margin:10px 0 5px 0;',
html: 'Complete the field bellow with your e-mail password.'
}, {
inputType: 'password',
name: 'password',
id: 'password',
fieldLabel: 'Password',
allowBlank: false
}]


})

card2.superclass.initComponent.call(this);

}

});


/**
*
* card3 class
*
*/
card3 = Ext.extend(Ext.ux.Wiz.Card, {

initComponent: function(){
Ext.apply(this,{
id : 'card3',
title : 'Finish!',
monitorValid : true,
items : [{
border: false,
bodyStyle: 'background:none;',
html: 'Thank you for testing this wizard. Your data has been collected ' +
'and can be accessed via a call to <pre><code>this.getWizardData</code></pre>' +
'When you click on the "finish"-button, the "finish"-event will be fired.<br/>' +
'If no attached listener for this event returns "false", this dialog will be ' +
'closed. <br />'
}]
})

card3.superclass.initComponent.call(this);
}

});

Thanks.

cirvine
4 Jul 2008, 1:46 AM
I have a wizard, where for reasons outside my control one of the cards has to be a GridPanel with a CheckboxSelectionModel. The Wizard itself works fine, but I can't figure out how to get a vertical scrollbar to appear on the GridPanel...

I've stripped it right down to basics, a card with just a gridpanel, no CheckboxSelectionModel, no overnesting. And no matter what I try, the Grid just refuses to show a vertical scrollbar. Is it maybe a config setting on the Card being inherited?

razor
10 Jul 2008, 5:11 AM
When removing the 'welcome' card and going straight through to form card, the validation listerener/poller is not started.

This is because the onShow of the first card is not triggered.


To overcome this, use this crude option:



wizard.show();
wizard.cards[0].show();

ThorstenSuckow
10 Jul 2008, 5:17 AM
When removing the 'welcome' card and going straight through to form card, the validation listerener/poller is not started.

This is because the onShow of the first card is not triggered.


To overcome this, use this crude option:



wizard.show();
wizard.cards[0].show();

Thanks for this issue report. I plan to do a new SVN commit at the start of next week since I'm still in Munich.

Regards

Thorsten

razor
10 Jul 2008, 5:52 AM
I wanted to have the first field of my cards (formpanels) selected. After playing with the code a bit, I have changed the function OnCardShow into the following:


onCardShow : function()
{
var comp = this.getComponent(1);
comp.focus(false,100);

if (this.monitorValid) {
this.startMonitoring();
}
}

Now the first field is selected and does not give an validation error until it looses focus.

razor
11 Jul 2008, 12:16 AM
@MindPatterns:

I would like to add some kind of logic between clicking the next button and showing the next panel.

Are you, or anybody for that matter, working on some kind of an BeforeNextCard event? Thanks.

ThorstenSuckow
11 Jul 2008, 12:30 AM
@MindPatterns:

I would like to add some kind of logic between clicking the next button and showing the next panel.

Are you, or anybody for that matter, working on some kind of an BeforeNextCard event? Thanks.

Hi Razor,

maybe this is what you're looking for:

From Card.js:



/** * @event beforecardhide
* If you want to add additional checks to your card which cannot be easily done
* using default validators of input-fields (or using the monitorValid-config option),
* add your specific listeners to this event.
* This event gets only fired if the activeItem of the ownerCt-component equals to
* this instance of {@see Ext.ux.Wiz.Card}. This is needed since a card layout usually
* hides it's items right after rendering them, involving the beforehide-event.
* If those checks would be attached to the normal beforehide-event, the card-layout
* would never be able to hide this component after rendering it, depending on the
* listeners return value.
*
* @param {Ext.ux.Wiz.Card} card The card that triggered the event
*/
'beforecardhide'


If you want to keep it simple, just override the method "isValid" in your instance of Ext.ux.Wiz.Card, and either return "true" to hide the card, or "false" if the input was invalid.
(You should probably still call the parent's implementation since it takes care of the bindHandler if monitorValid == true).

HTH.

razor
11 Jul 2008, 12:34 AM
This is indeed what I was looking for! I have played around with it, and eventually I have added the following function to Card.js:


// -------- helper
/**
* Returns the form-data of one cards in this wizard. The first index is the
* id of the current card,
* and the values are objects containing key/value pairs in the form of
* fieldName : fieldValue.
*
* @return {Array}
*/
getWizardCardData : function()
{
var formValues = {};

if (this.form) {
formValues[this.id] = this.form.getValues(false);
} else {
formValues[this.id] = {};
}

return formValues;
},

Now, I add the following listener to my card items in the main code:


, listeners : {
"beforecardhide": function () {
console.log('execute');
logic1( this.getWizardCardData() );
}
}

And with this function I can check the returned form object for data validation/logic:


function logic1(obj) {
console.log( obj['serialcard']['serialfield'] );
}



@MindPatterns

Now I hope that you will insert the skip card code (posted on previous page) to the svn ;) great work so far!

Fabyo
11 Jul 2008, 3:21 AM
I prefer this example, is the source of a code example

http://extjs.com/forum/showthread.php?t=38644&highlight=wizard

razor
11 Jul 2008, 7:45 AM
That extension is for GWT(?) kind of a big difference if I am correct. Though I have not read into GWT extensions.

A quick glance shows JAR files (cringes) ;).

Michelangelo
13 Jul 2008, 8:19 AM
Hi all,

is there a way to manage a wizard with the options like:

Step 1
Option A
Option B

Step 2
if the user Option A the step 2 shows a text field.
if the user Option A the step 2 shows another thing.

thanks

razor
13 Jul 2008, 1:00 PM
Yes-- this is possible..

Read the thread, some skip panel code has been given. Use the before card hide event to check your data.

Most answers are on the previous page (see my posts ;)).

Michelangelo
13 Jul 2008, 10:14 PM
Hi Razor,

I have found only the beforecardhide method to include in the Card.js. Is this the method suggested ? I have included it in my code but it doesn't work.

this is the snippet code of my wizard:


...

new Ext.ux.Wiz.Card({
title : 'Options',
monitorValid : false,
defaults : {
labelStyle : 'font-size:11px'
},
listeners : {
"beforecardhide": function () {
var myobj = this.getWizardCardData(); <--- this is an empty object
wizard.cards[0].show(); <--- this show the first page but the indicator shows another page
Ext.getCmp('shinewizard').cardPanel.getLayout().setActiveItem(0); <--- This way works in the firebug console but doesn't work in the code RECURSION
### How can I add the condition here ###
}},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ' Please select the item.'
},{
xtype:"radio",
fieldLabel:"Select A",
boxLabel:"",
name:"radio",
inputValue:"remote"
},{
xtype:"radio",
fieldLabel:"Select B",
boxLabel:"(suggested)",
name:"radio",
inputValue:"dump"
}]
}),
...


Thanks & Regards :)

razor
14 Jul 2008, 2:35 AM
Please note that the function 'this.getWizardCardData();' is not standard included in the wizard. I wrote this myself, the code is also added to the previous posts.

As far as I know you can not use: 'wizard.cards[0].show();' because this will interfere with the code of the wizard itself.

Best is to use the skip card code, based on the validation of the beforecardhide event.

Michelangelo
14 Jul 2008, 2:39 AM
Hi razor,

I have followed the instructions written in the previous post and the getWizardCardData doesn't retrieve the data of the form maybe my fault.

Another thing, how can I fire a function after clicking on the Finish button?

thanks again

razor
14 Jul 2008, 5:11 AM
Add a listener for the 'finish' event. Same as the beforehidecard.

Good luck.. This information can be easily found, just open the wizard.js script, find all ONsomthing functions, these are usually the events which you can hook onto.

Michelangelo
17 Jul 2008, 1:12 AM
Hi again Razor,

how can I fire a function when the finish event is fired?

I have used:



Ext.namespace("Application");

Application.Wiz = Ext.extend(Ext.ux.Wiz,
{
initComponent:function() {

Ext.apply(this, {

headerConfig : {
title : 'MyWiz'
},

cardPanelConfig : {
defaults : {
baseCls : 'x-small-editor',
bodyStyle : 'padding:10px 15px 15px 15px;background-color:#F6F6F6;',
border : false
}
},

onFinish: function () {.....}

.
.
.
.
});

Application.Wiz.superclass.initComponent.apply(this, arguments);
} // eo function initComponent

,onRender:function() {
Application.Wiz.superclass.onRender.apply(this, arguments);
} // eo function onRender
}
);

Ext.reg('Wiz', Application.Wiz);



if I use the onFinish way the wizard doesn't close at the finish event. 8-|

thanks
Regards.

ThorstenSuckow
17 Jul 2008, 1:23 AM
Hey there,


how can I fire a function when the finish event is fired?





/**
* Fires the 'finish'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onFinish : function()
{
if (this.fireEvent('finish', this, this.getWizardData()) !== false) {
this.close();
}
}


This is the default implementation in Wizard.js. Just create a listener for the "finish"-event and return either "true" or "false" (anything but a return value equaling to "false" will close the wizard"). In your case, I'd not override "onFinish" explicit.

Michelangelo
17 Jul 2008, 1:51 AM
... also this way doesn't work. In this case the wizard form closes but the code isn't fired




listeners: {
finish: function() {
Ext.Ajax.request({
url : 'php/write.php' ,
params : {
fn: 'SetConfig',

},
method: 'POST',
success: function ( result, request) {
var jsonData = Ext.util.JSON.decode(result.responseText);
Ext.MessageBox.alert(jsonData.title, jsonData.msg);
this.close();
if(jsonData.code == "1"){
this.close();
}
},
failure: function ( result, request) {
Ext.MessageBox.alert('Failed', result.responseText);
}
});
}
},


thanks

ThorstenSuckow
17 Jul 2008, 2:09 AM
... also this way doesn't work. In this case the wizard form closes but the code isn't fired




listeners: {
finish: function() {
Ext.Ajax.request({
url : 'php/write.php' ,
params : {
fn: 'SetConfig',

},
method: 'POST',
success: function ( result, request) {
var jsonData = Ext.util.JSON.decode(result.responseText);
Ext.MessageBox.alert(jsonData.title, jsonData.msg);
this.close();
if(jsonData.code == "1"){
this.close();
}
},
failure: function ( result, request) {
Ext.MessageBox.alert('Failed', result.responseText);
}
});
}
},


thanks


This might be a semantic error. Try this instead (see also docs for Observable.addListener):



listeners: {
finish : {
fn : function() { ... },
scope : yourScopeHere
}
}

Michelangelo
17 Jul 2008, 3:36 AM
Thanks for the prompt reply, I have tried also in this way but nothing.



listeners: {
finish : {
fn : function() {
Ext.Ajax.request({
url : 'php/write.php' ,
params : {
fn: 'SetConfig'

},
method: 'POST',
success: function ( result, request) {
var jsonData = Ext.util.JSON.decode(result.responseText);
Ext.MessageBox.alert(jsonData.title, jsonData.msg);
this.close();
if(jsonData.code == "1"){
this.close();
}
},
failure: function ( result, request) {
Ext.MessageBox.alert('Failed', result.responseText);
}
});
},

scope : this

}
},


In this case the wizard closes but the function doesn't fire

thanks again

ThorstenSuckow
17 Jul 2008, 3:39 AM
Thanks for the prompt reply, I have tried also in this way but nothing.

Can you provide the full source of your wizard setup?

Michelangelo
17 Jul 2008, 5:44 AM
Sure! Thanks



Ext.namespace("Application");

Application.Wiz = Ext.extend(Ext.ux.Wiz,
{
initComponent:function() {

Ext.apply(this, {

id: 'dbconnectionwizard',
title : 'Setup your database connection',

headerConfig : {
title : 'MySql Database Connection'
},

cardPanelConfig : {
defaults : {
baseCls : 'x-small-editor',
bodyStyle : 'padding:10px 15px 15px 15px;background-color:#F6F6F6;',
border : false
}
},

listeners: {
finish : {
fn : function() {
Ext.Ajax.request({
url : 'php/write.php' ,
params : {
fn: 'SetDatabaseConfig',
hostname:Ext.get('hostname').dom.value,
dbname:Ext.get('dbname').dom.value,
username:Ext.get('username').dom.value,
password:Ext.get('password').dom.value
},
method: 'POST',
success: function ( result, request) {
var jsonData = Ext.util.JSON.decode(result.responseText);
Ext.MessageBox.alert(jsonData.title, jsonData.msg);
this.close();
if(jsonData.code == "1"){
this.close();
}
},
failure: function ( result, request) {
Ext.MessageBox.alert('Failed', result.responseText);
}
});
},

scope : this

}
},

cards : [

new Ext.ux.Wiz.Card({
id: 'card1',
title : 'Welcome',
items : [{
border : false,
bodyStyle : 'background:none;',
html : 'Welcome to the database configuration wizard for your application, '+
'Here you will set the database configuration for using your application.<br/><br/>'+
'Please click the "next"-button and fill out all form values.'
}]
}),

new Ext.ux.Wiz.Card({
id: 'card2',
title : 'The Database Host Connection',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Write here your hostname. Only letters, underscores and hyphens are allowed.'
},
new Ext.form.TextField({
id : 'hostname',
name : 'hostname',
fieldLabel : 'Hostname or IP',
emptyText : 'xxx.xxx.xxx.xxx',
value : 'localhost',
allowBlank : false
})
]
}),

new Ext.ux.Wiz.Card({
id: 'card3',
title : 'The Database Name',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ' Please enter your database name.'
},
new Ext.form.TextField({
id : 'dbname',
name : 'dbname',
fieldLabel : 'Database Name',
emptyText : 'mydatabase',
value : 'shinebuilder_temp',
allowBlank : false
})
]
}),

new Ext.ux.Wiz.Card({
id: 'card4',
title : 'Your Database Credentials',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:10px;',
html : ' Please insert all fields.'
},new Ext.form.TextField({
id : 'username',
name : 'username',
fieldLabel : 'Username',
emptyText : 'myuser',
value : 'root',
allowBlank : false

}),new Ext.form.TextField({
id : 'password',
name : 'password',
inputType : 'password',
fieldLabel : 'Password',
emptyText : 'test1234',
value : '11041977',
allowBlank : false
})]
})
]


});

Application.Wiz.superclass.initComponent.apply(this, arguments);
} // eo function initComponent

,onRender:function() {
Application.Wiz.superclass.onRender.apply(this, arguments);
} // eo function onRender
}
);

Ext.reg('wiz', Application.Wiz);

ThorstenSuckow
17 Jul 2008, 12:33 PM
Sure! Thanks



Why are you mking a call to



Application.Wiz.superclass.initComponent.apply(this, arguments);


instead of



Application.Wiz.superclass.initComponent.call(this);


?

Could you rewrite your example and tell what happens?

Michelangelo
17 Jul 2008, 12:45 PM
Nothing to do.

The problem persist the Wizard listeners doesn't call the function.

thanks for the help

ThorstenSuckow
17 Jul 2008, 1:00 PM
Nothing to do.

The problem persist the Wizard listeners doesn't call the function.

thanks for the help

I had the time to test it myself now. The problem is confirmed. Until fixed in svn, please add the listener "manually", i.e. use



this.on('finish', function(){...}, this);


either in initComponent or somewhere else.

Michelangelo
18 Jul 2008, 12:34 AM
Thanks for your help! Now it works!

johnstontrav
21 Jul 2008, 12:06 AM
Has anyone managed to use a grid as one of the card options?

Im trying to create an ordering wizard, eg, name and shipping details on card 1 and many order items on card 2 (this will be the grid)

I can add the grid without a problem, but getting it to look good and fit the entire card area is something i can't do.

any pointers would be awesome!

Cheers,
Trav.

jsakalos
21 Jul 2008, 8:11 AM
Yes, in tabs. TabLayout is extension of card layout so the technique should work there too. http://examples.extjs.eu/?ex=gridintab

ThorstenSuckow
21 Jul 2008, 8:21 AM
Has anyone managed to use a grid as one of the card options?

Im trying to create an ordering wizard, eg, name and shipping details on card 1 and many order items on card 2 (this will be the grid)

I can add the grid without a problem, but getting it to look good and fit the entire card area is something i can't do.

any pointers would be awesome!

Cheers,
Trav.

Screenshot and example code would be nice. The wizard cards extend all from FormPanel, thus having FormLayout as their default layout. I've never tried to embed a grid within FormLayout, it might turn out ugly though ;)

dorgan
24 Jul 2008, 1:37 PM
Basically what I am trying to do is in a card require the click of a button and for that function to return true. Is this currently implemented??

Basically the card makes the user enter db information and I want to test these settings once the button is clicked.

dorgan
25 Jul 2008, 7:47 AM
Anything on this? Is this thread active?

kellyt
31 Jul 2008, 10:30 PM
Nice work!

Is there a way to press the enter key to move to the next step, rather than have to use the mouse or tab through to click the next step button?

mjlecomte
1 Aug 2008, 4:36 AM
Nice work!

Is there a way to press the enter key to move to the next step, rather than have to use the mouse or tab through to click the next step button?

I haven't checked the source code but you probably just need to bind the return key to whatever event/method advances to the next card. Search for keyMap, you should find many examples for doing this. You may want to even disable the return key (if it isn't already) until the validation is complete.

kellyt
3 Aug 2008, 9:31 PM
I added a keys call to the window in the wizard.js, as seen below, and it is still not moving to the next card in the wizard. I am able to get an alert working, in the key definition, if i put; fn: function() {alert("hello");}


But i cannot get it to work when using a function in the fn call in the key definition.


Am i doing something small wrong?



...(other wizard.js code)...

onNextClick : function()
{ if (this.currentCard == this.cardCount-1) {
this.onFinish();
} else {
this.cardPanel.getLayout().setActiveItem(this.currentCard+1);
}
},
keys : [{
key: [10,13],
fn: this.onNextClick,
scope:this
}]

Eki
4 Aug 2008, 1:13 AM
Thanks for this issue report. I plan to do a new SVN commit at the start of next week since I'm still in Munich.

Regards

Thorsten


When removing the 'welcome' card and going straight through to form card, the validation listerener/poller is not started.

This is because the onShow of the first card is not triggered.


To overcome this, use this crude option:



wizard.show();
wizard.cards[0].show();

This bug comes from the InitEvents method (Card.js) which is called after the show event of the first CardPanel.

To resolv this issue, a simple solution is to use the InitEvents of Ext.ux.Wiz to initialize CardPanels events.



/**
* Inits the listener for the various {@link Ext.ux.Wiz.Card}s used
* by this component.
*/
initEvents : function()
{
Ext.ux.Wiz.superclass.initEvents.call(this);

var cards = this.cards;

for (var i = 0, len = cards.length; i < len; i++) {
cards[i].on('show', this.onCardShow, this);
cards[i].on('hide', this.onCardHide, this);
cards[i].on('clientvalidation', this.onClientValidation, this);

cards[i].on('beforehide', cards[i].bubbleBeforeHideEvent, cards[i]);
cards[i].on('beforecardhide', cards[i].isValid, cards[i]);
cards[i].on('show', cards[i].onCardShow, cards[i]);
cards[i].on('hide', cards[i].onCardHide, cards[i]);
}
},

razor
5 Aug 2008, 3:17 AM
@Eki:

Great! Thanks for solving this.

fangzhouxing
6 Aug 2008, 10:19 PM
I have encountered the same problem as follows:


Second, again under IE7, I the wizard won't appear until I comment out the body style:

cardPanelConfig : {
defaults : {
baseCls : 'x-small-editor',
// bodyStyle : 'padding:40px 15px 5px 120px;background-color:#F6F6F6 !important;',
border : false
}
},



Has anyone can help me?

moegal
8 Aug 2008, 3:54 AM
does anyone have the code for this, the link seems to be inactive.
Thanks, Marty

mystix
8 Aug 2008, 8:30 AM
does anyone have the code for this, the link seems to be inactive.
Thanks, Marty

googlecode svn repo:
http://ext-ux-wiz.googlecode.com/svn/trunk

walldorff
8 Aug 2008, 5:50 PM
I made a small change in the button init in wizard.js. With this code there is only the 'next' button, unless the other ButtonText's are explicitly configured.
I have a couple of wizards where I don't need the previous and cancel button.

First the button labels as empty strings:

previousButtonText : '',
cancelButtonText : '',

Then the changes in the initComponent function:

initComponent : function()
{
this.initButtons();
this.initPanels();

var title = this.title || this.headerConfig.title;
title = title || "";

var theButtons;
if (!empty(this.cancelButtonText) && !empty(this.previousButtonText)) {
theButtons = [this.previousButton,this.nextButton,this.cancelButton];
}
else if (!empty(this.cancelButtonText)) {
theButtons = [this.nextButton,this.cancelButton];
}
else if (!empty(this.previousButtonText)) {
theButtons = [this.previousButton,this.nextButton];
}
else {
theButtons = [this.nextButton];
}

Ext.apply(this, {
title : title,
layout : 'border',
cardCount : this.cards.length,
buttons : theButtons,
items : [
this.headPanel,
this.cardPanel
]
});

this.addEvents(
/**
* @event cancel
* Fires after the cancel-button has been clicked.
* @param {Ext.ux.Wiz} this
*/
'cancel',
/**
* @event finish
* Fires after the last card was reached in the wizard and the
* next/finish-button has been clicked.
* @param {Ext.ux.Wiz} this
* @param {Object} data The collected data of the cards, whereas
* the index is the id of the card and the specific values
* are objects with key/value pairs in the form formElementName : value
*/
'finish'
);

Ext.ux.Wiz.superclass.initComponent.call(this);
},

But maybe there's another way :)

sean.zhou
8 Aug 2008, 9:19 PM
If you simply add



{ xtype: 'htmleditor', fieldLabel: 'Html Editor Test', autoHeight: true}


into the items of the second card in SimpleWizard.html, javascript error occurs.

In firefox, you may see from firebug the following exception thrown [Exception... "Component returned failure code: 0x80040111 (NS_ERROR_NOT_AVAILABLE) [nsIDOMNSHTMLDocument.designMode]" nsresult: "0x80040111 (NS_ERROR_NOT_AVAILABLE)" location: "JS frame :: http://your.host.server.com/js/extjs/ext-all-debug.js :: anonymous :: line 28447" data: no]
http://your.host.server.com/js/extjs/ext-all-debug.js


In IE 6, a runtime error is thrown from a pop-up box with following message:

Line: 28479
Error: Invalid argument

I have tried to put an htmleditor field to the second wizard card in extjs layout-browser.html example, it went fine. It also went fine if the htmleditor is only inside of the first card in SimpleWizard.html. Therefore, the problem is likely due to delayed rendering of this wizard implementation. Please help fixing it.

sean.zhou
11 Aug 2008, 2:24 PM
After more extensive search, it seems that ExtJs does have some problems with htmleditor. After played with layout-browser.html in ExtJs examples, I found htmleditor did not work when it is inside of a card panel. I have tried with the suggested fixes from other thread discussions, such as using
deferredRender:false and/or
hideMode: 'offsets' on the card panel and failed to get it work. Here is the example that shows the problem by replacing



id: 'card-1',
html: '<p>Step 2 of 3</p><p>Almost there. Please click the "Next" button to continue...</p>'


with



id: 'card-1', deferredRender:false,
items: [{xtype:'htmleditor', hideMode: 'offsets', fieldLabel: 'abcd'}]


inside of cardWizard in examples/layout-browser/layouts/basic.js. Then start examples/layout-browser/layout-browser.html. The same exception will be thrown to the firebug console in Firefox.

razor
12 Aug 2008, 11:34 PM
important note:

When using the event 'beforecardhide' it is fired both on next and previous buttons. This of course is not usefull.

To avoid this I have added the following code to Wizard.js:



lastAction : null,
..
onNextClick : function() {
this.lastAction = 'next';
..
onPreviousClick : function() {
this.lastAction = 'previous';


Now I can check in my listener code which button was pressed, and if I have to do validation of the cardpanel contents for the next cardpanel.

fangzhouxing
13 Aug 2008, 7:43 PM
I have encountered the same problem as follows:

Second, again under IE7, I the wizard won't appear until I comment out the body style:

cardPanelConfig : {
defaults : {
baseCls : 'x-small-editor',
// bodyStyle : 'padding:40px 15px 5px 120px;background-color:#F6F6F6 !important;',
border : false
}
},

Has anyone can help me?

BUMP!

Digital God
17 Aug 2008, 11:23 PM
there is a problem with "Tab" button... if i press tab the page goes down and shows another wizard card without "next" button pressed..

razor
18 Aug 2008, 3:21 AM
I have checked the behaviour of the Tab button. But the result you are explaining does not happen on my browsers FF3 and IE6.

fangzhouxing
18 Aug 2008, 6:27 AM
razor,thank you, your 'lastAction' solution just what i need now!

razor
20 Aug 2008, 1:46 AM
You are most welcome. With this small userbase of the wiz component we can hack and upgrade it more and more ;)

mask_hot
20 Aug 2008, 5:29 AM
Hi all,

I have some troubles with this useful wizard-component to do what I'd like.

In a first card I have a ComboBox.
In the second card I'd like to have two grids linked by DnD and loaded with the Combo value from the first card.

I did not succeed to display my two grids (maybe a layout issue) and I don't know how to send them the combo value to load their store.

Can somebody help me?

Thx

mask_hot
21 Aug 2008, 1:14 AM
Yes, in tabs. TabLayout is extension of card layout so the technique should work there too. http://examples.extjs.eu/?ex=gridintab

Saki, how do you do that?

I've done this and it does not work


new Ext.ux.Wiz.Card({
title : 'S&eacute;l&eacute;ction des &eacute;quipes',
id:'card3',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items :
new FFv2.PanelTeamCompetition()

}),


where my FFv2.PanelTeamCompetition() is


(...)

Ext.apply(this, {
// {{{
items : [{
xtype:'tabpanel',
activeTab:0,
autoScroll: true,
//defaults:{layout:'fit'},
items:
[ {
title:'Equipes',
hideMode: Ext.isIE ? 'offsets' : 'display' //,id:'gridtab'
,layout:'border'
,items:[
this.firstGrid, //west
this.secondGrid //center

]
}
]
}]

}); // eo apply


How do you mix cards and TabLayout?

razor
21 Aug 2008, 5:54 AM
I added my grid by placing the following code in the items config:


, // create the Grid
new Ext.grid.GridPanel({
frame : false
, border : false
, isFormField: true
, hideLabel : true
, id : 'typegrid'
, viewConfig: {
forceFit: true
}
, sm: new Ext.grid.RowSelectionModel({singleSelect:true})
, hideHeaders : true
, store: new Ext.data.JsonStore({
fields: ['name','id','boxLabel']
})
, columns: [
{header: "Type", sortable: true, dataIndex: 'boxLabel', width: 180}
, {header: "ID", sortable: true, dataIndex: 'id'}
]
, stripeRows: true
//, autoExpandColumn: 'company'
, height:150
//, width:225
//title:'Array Grid'
})

niaz
25 Aug 2008, 8:59 AM
Theres another mysterious bug here as well. For some reason the dropdown lists are being cropped. They ought to be at least as long as the words in the list but they aren't.
I've inspected it with FireBug and can't find any CSS that shouldn't be there. Anyone else experiencing this?

Hi Anjelika, am also seeing the same issue with the drop down list in combo box, i was wondering if you have found a solution to this problem

Thanks

cirvine
27 Aug 2008, 2:19 AM
For those struggling to get scrollbars on cards containing Grids, if you put


layout:'form',
autoScroll:true

on the config of the card itself, and then


viewConfig:{forceFit:true}

in the config of the Grid, then the Card will have a scrollbar, which at least enables you to scroll down and see the entire Grid.

Still not been able to get a scrollbar appearing on the actual Grid itself rather than the card though.

ClemsonJeeper
28 Aug 2008, 8:57 PM
Hi Anjelika, am also seeing the same issue with the drop down list in combo box, i was wondering if you have found a solution to this problem

Thanks

I am also seeing this issue.

I "fixed" it by setting the minListWidth on my combobox to something that is acceptable. Somewhere the list width is getting set to extremely small.

franck34
2 Sep 2008, 2:45 AM
I think I've found another bug.

It looks like the initial page of the wizard isn't getting initialized correctly if it requires validation (monitorValid : true). If you take your SimpleWizard.html and remove the opening non-validation card you'll be able to reproduce this. I think this is occurring because the card level onCardShow event handler isn't getting called for the initial page with validation. Since this doesn't get called the startMonitoring method doesn't get called.

Got the same behavior using the last svn checkout.

Any idea to fix / workaround this problem ?

Editing: Seem's that source code from DhakouaniM, at page 3 of this thread is solving the problem.

franck34
2 Sep 2008, 5:40 AM
Is somebody succeed to make Ajax validation between cards ? (and not only at the last card)

i.e card 1 -> click Next -> beforeCardHide -> AjaxRequest -> if success -> display next card.

Any suggestions are welcome

Thanks in advance,
Franck

franck34
2 Sep 2008, 6:13 AM
Is somebody succeed to make Ajax validation between cards ? (and not only at the last card)

i.e card 1 -> click Next -> beforeCardHide -> AjaxRequest -> if success -> display next card.

Any suggestions are welcome

Thanks in advance,
Franck

Finaly, because it's a non blocking validation, i've used show event on the next card, simply.

razor
4 Sep 2008, 9:27 AM
Is somebody succeed to make Ajax validation between cards ? (and not only at the last card)

i.e card 1 -> click Next -> beforeCardHide -> AjaxRequest -> if success -> display next card.

Any suggestions are welcome

Thanks in advance,
Franck

There are many posts about exactly what you're asking IN this thread. Please read all posts.

nomdeguerre
5 Sep 2008, 6:04 PM
Hi.

I found a bug (maybe) with the combobox implementation on the wizard. You see I have an automated combobox, I mean it gets its stuff from the server. Now, I get it to display one field as a dropdown listing and have another field as its value.
fieldValue : something,
displayValue : somethingelse

the problem is when I do this.getWizardData().... it gets the displayValue and not the fieldValue....

I can work around this problem though....
I just wanted to give you guys a heads up.... even better, maybe I'm not understanding something here correctly.

cheers!

nomdeguerre
8 Sep 2008, 2:46 AM
HI.

I have a problem using the wizard in uploading a file.
I really don't know if the problem lies in my use of the wizard or in my lack of knowledge in ext in general.

I basically can't upload a file... I'm relatively certain that the php file isn't even being invoked at all... *looked several ways, firebug, fiddler, some test code on the php file.

I'd like to know what I'm doing wrong that it won't even send the data to the server.

I've been working on this for hours and hours, and I've read the examples of file uploads on the forum.... and I still don't know what's going wrong.

please! help!

the finish listener is being invoked, I've saved some other data.... just not the file upload.

thanks!

Here are some code snippets:


//So create the wizard, add the listener for finishing...

var catchWizard = new Ext.ux.Wiz({

title : 'Catch Entry Wizard',

listeners: {
finish: function() { saveCatchConfig( this.getWizardData(), catchWizard ) }

.....some config and cards here.....


new Ext.ux.Wiz.Card({
title : 'Fish Photo',
id : 'card3',
fileUpload : true,
//standardSubmit : true,
url : 'wizardphp/pictureupload.php',
method:'POST',
enctype:'multipart/form-data',
timeout : 15000,
isUpload : true,
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},

.... other fields....


new Ext.form.TextField({
name : 'picture',
fieldLabel : 'Picture File',
allowBlank : true,
inputType : 'file',
vtype : 'imagefile'
})

... the function invoked when the finish event happens....


function saveCatchConfig(obj,wizard){

Ext.Ajax.request({
url : 'wizardphp/savecatch.php',
params : {
trip : obj['card0']['trip'],
species : obj['card1']['species'],
length : obj['card1']['length'],

....other parameters.......


caption : obj['card3']['caption']
//picture : obj['card3']['picture']
}
});
//Upload the file into the databas
wizard.cards[4].getForm().submit({ // Photo upload handler for card 4 (labeled as card3) of the catchWizard.
url : 'wizardphp/pictureupload.php',
//Add these parameters to determine the fishID to add the file to
params : {trip:obj['card0']['trip'], length:obj['card1']['length'], weight:['card1']['weight']}
})
}

nomdeguerre
9 Sep 2008, 6:33 AM
HI,

thanks, mystix, for adding the code tags.

I'm still working on this problem.
The way I understand the ext.ux.card is that it's been extended from a formPanel, which was extended from basicForm, which is the class responsible for uploading files.

The code above doesn't even try to submit the upload. It's like the form wasn't configured correctly to do it. I think I've configured the card correctly but I may be wrong.
I tried the same config on a normal form and it worked.

I'm running out of ideas. I might even try just getting a normal, basic form, and sticking that into the card, and doing the upload that way. I don't even know if that'll work.

please help!

thanks!

Animal
16 Sep 2008, 11:13 PM
A little extra smoothness for those that want it in their wizards.

Fade out/in transitions between cards.

Configure the Container with layoutConfig: { animate: true } The layout override uses that property as highlighted.



Ext.override(Ext.layout.CardLayout, {
setActiveItem : function(item){
item = this.container.getComponent(item);
if(this.activeItem != item){
if (item.rendered && this.animate) {

Ext.Fx.syncFx();
if (this.activeItem) {

// Correct top position of card because they have to occupy the same space
// during animation.
var n = item.getEl();
n.setStyle({
position: 'absolute',
top: this.container.getLayoutTarget().getPadding('t') + 'px'
});
this.activeItem.getEl().fadeOut({
callback: Ext.Component.prototype.hide,
scope: this.activeItem
});
}
this.activeItem = item;
item.show();
this.container.doLayout();
n.fadeIn();
Ext.Fx.sequenceFx();
} else {
if(this.activeItem){
this.activeItem.hide();
}
this.activeItem = item;
item.show();
this.container.doLayout();
}
}
}
});

perdar
22 Sep 2008, 4:45 AM
Various people mentioned having cropped dropdown's with this plugin.
I narrowed it down to the allowBlanks attribute.

It happens if you use this attribute in any fashion, true or false.

kevinwu8
1 Oct 2008, 8:28 PM
Hi..

When i remove the fist welcome card section code from this extension's example..
The "Next" button is not working..

It's mean i must need this welcome card if i want to use this extension??

thx..
Kevin

razor
3 Oct 2008, 3:38 AM
Hello Kevin,
There are some posts in this thread which explain how to overcome the issue you are having.

perdar
3 Oct 2008, 4:56 PM
Hi guys,

it may be the structure of my code... but i am having the following issue when i create my object more than once (i.e. click cancel and click my "Create Job" button).
am I failing to destroy the object on close somehow??

Here is my code


Ext.ns('plrs');

Ext.BLANK_IMAGE_URL = '/ext-2.2/resources/images/default/s.gif';

plrs.NewJobWizard = Ext.extend(Ext.ux.Wiz, {
title : ' ',

headerConfig : {
title : 'Create New Job',
version: 'v0.1 alpha'
},

cardPanelConfig : {
defaults: {
baseCls: 'x-small-editor',
bodyStyle : 'padding:40px 15px 5px 120px; background-color:#F6F6F6;',
border: true
}
},

cards : [
new Ext.ux.Wiz.Card({
title : 'Customer Details',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px bold'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ''
},
new plrs.CustomerComboBox(),
new Ext.form.DateField({
name: 'jobdate',
fieldLabel: 'Job Date',
allowBlank: false,
emptyText: 'Choose a date...',
width: 170
}),
new Ext.form.TimeField({
name: 'jobtime',
fieldLabel: 'Job Time',
increment: 15,
forceSelection: true,
allowBlank: false,
emptyText: 'Choose a time...'
}),
new Ext.form.NumberField({
name: 'totalstaffreqd',
fieldLabel: 'Staff Required',
allowBlank: false,
allowDecimals: false,
allowNegative: false,
width: 30
}),
new Ext.form.Checkbox({
name: 'jobconfirmed',
fieldLabel: 'Confirmed'
}),
new Ext.form.TextField({
name: 'test',
fieldLabel: 'test',
allowBlank: false
})
]
}),

// third card with input field email-address
new Ext.ux.Wiz.Card({
id: 'card2',
title : 'Your email-address',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ' Please enter your email-address.'
},
new Ext.form.TextField({
name : 'email',
fieldLabel : 'Email-Address',
allowBlank : false,
vtype : 'email'
})
]
}),

// fourth card with finish-message
new Ext.ux.Wiz.Card({
title : 'Finished!',
monitorValid : true,
items : [{
border : false,
bodyStyle : 'background:none;',
html : 'Thank you for testing this wizard. Your data has been collected '+
'and can be accessed via a call to <pre><code>this.getWizardData</code></pre>'+
'When you click on the "finish"-button, the "finish"-event will be fired.<br/>'+
'If no attached listener for this event returns "false", this dialog will be '+
'closed. <br />'
}]
})


]
});

Ext.reg('jobsgrid', plrs.jobsgrid);

// application main entry point
Ext.onReady(function() {

Ext.QuickTips.init();

var pg = new plrs.jobsgrid({
listeners:{
'rowdblclick':function(grid,rowIndex,e) {
alert(rowIndex);
}
}
,height: 300
,bbar: new plrs.pagingtoolbar({
store: plrs_ds_jobsgrid
,plugins: plrs_jobsgrid_filter
})
,tbar: [
{
text:'Create Job'
,handler: function() {
var jw = new plrs.NewJobWizard();
jw.show();
}

}
]
});
pg.render(document.body);

});

mirko
6 Oct 2008, 5:39 AM
Thanks for the nice extension.

Is it possible to dynamically add/remove wizard pages after render, or to show some pages conditionally?

Romantik
6 Oct 2008, 6:51 AM
In Safari (3.1.2)
DateField doesn't works:


new Ext.ux.Wiz.Card({
id: 'card1',
items : [{
xtype:'fieldset',
defaultType: 'textfield',
items :[
new Ext.form.TextField({
name : 'lastname',
fieldLabel : '* ' + langs['lastname'],
allowBlank : false
}),
new Ext.form.DateField({
name : 'birth',
fieldLabel : langs['birth']
})
]
}]
});


On the form I see 2 fields and an icon of calendar on the left. After click on it- no any reason.

Help me please
Thanks.

SamuraiJack1
8 Oct 2008, 3:04 AM
Now also available in extension repository:

http://extjs-ux.org/docs/?class=Ext.ux.Wiz

Romantik
8 Oct 2008, 6:32 AM
thanks. Very good Doc

kevinwu8
13 Oct 2008, 7:52 PM
Hi..

I want to dynamic remove card from Wiz component...
I try this code ...


Wiz.cards.remove(card3);


but it's not work...
anyone can help me resolve this problem??

gthe
14 Oct 2008, 6:06 AM
Hi guys,

it may be the structure of my code... but i am having the following issue when i create my object more than once (i.e. click cancel and click my "Create Job" button).
am I failing to destroy the object on close somehow??



Whether you have found the decision of this problem?

gthe
21 Oct 2008, 3:45 AM
Here is the modified source of 'SimpleWizard.html' with button to show wizard.
The problem - wizard show only first time.
How to solve it and enable reshow wizard ?


<!DOCTYPE htmlPUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Ext.ux.Wiz - SimpleWizard</title>

<!-- Change the path to point to your Ext 2.1 stylesheet -->
<link rel="stylesheet" type="text/css" href="../../../ext-2.1/resources/css/ext-all.css" />

<!-- Ext.ux.Wiz stylesheet -->
<link rel="stylesheet" type="text/css" href="../src/resources/css/ext-ux-wiz.css" />

</head>

<body>

<!-- Change the path to point to your Ext 2.1 files -->
<script type="text/javascript" src="../../../ext-2.1/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../../../ext-2.1/ext-all-debug.js"></script>


<!-- In order to make the component work properly, you need to get the
Ext.ux.layout.CardLayout-component from http://www.siteartwork.de/cardlayout.
Download and copy the files to your filesystem, then change this path accordingly -->
<script type="text/javascript" src="../../CardLayout/src/CardLayout.js"></script>

<!-- Ext.ux.Wiz files -->
<script type="text/javascript" src="../src/Wizard.js"></script>
<script type="text/javascript" src="../src/Header.js"></script>
<script type="text/javascript" src="../src/Card.js"></script>


<script type="text/javascript">



Ext.onReady(function(){

Ext.QuickTips.init();

var button = Ext.get('show-btn');

button.on('click', function(){
wizard.show(button);
});

var wizard = new Ext.ux.Wiz({

title : 'A simple example for a wizard',

headerConfig : {
title : 'Simple Wizard Example'
},

cardPanelConfig : {
defaults : {
baseCls : 'x-small-editor',
bodyStyle : 'padding:40px 15px 5px 120px;background-color:#F6F6F6;',
border : false
}
},

cards : [

// first card with welcome message
new Ext.ux.Wiz.Card({
title : 'Welcome',
items : [{
border : false,
bodyStyle : 'background:none;',
html : 'Welcome to the example for <strong>Ext.ux.Wiz</string>, '+
'a Ext JS user extension for creating wizards.<br/><br/>'+
'Please click the "next"-button and fill out all form values.'
}]
}),

// second card with input fields last/firstname
new Ext.ux.Wiz.Card({
title : 'Your name',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Please enter your first- and your lastname. Only letters, underscores and hyphens are allowed.'
},
new Ext.form.TextField({
name : 'firstname',
fieldLabel : 'Firstname',
allowBlank : false,
validator : function(v){
var t = /^[a-zA-Z_\- ]+$/;
return t.test(v);
}
}),
new Ext.form.TextField({
name : 'lastname',
fieldLabel : 'Lastname',
allowBlank : false,
validator : function(v){
var t = /^[a-zA-Z_\- ]+$/
return t.test(v);
}
})

]
}),

// third card with input field email-address
new Ext.ux.Wiz.Card({
title : 'Your email-address',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ' Please enter your email-address.'
},
new Ext.form.TextField({
name : 'email',
fieldLabel : 'Email-Address',
allowBlank : false,
vtype : 'email'
})
]
}),

// fourth card with finish-message
new Ext.ux.Wiz.Card({
title : 'Finished!',
monitorValid : true,
items : [{
border : false,
bodyStyle : 'background:none;',
html : 'Thank you for testing this wizard. Your data has been collected '+
'and can be accessed via a call to <pre><code>this.getWizardData</code></pre>'+
'When you click on the "finish"-button, the "finish"-event will be fired.<br/>'+
'If no attached listener for this event returns "false", this dialog will be '+
'closed. <br />(In this case, our listener will return false after a popup shows the data you just entered)'
}]
})


]
});

// show the wizard
//wizard.show();
});




</script>
<input type="button" id="show-btn" value="Show wizard" /><br /><br />
</body>
</html>

CutterBl
27 Oct 2008, 5:44 PM
I am seeing the exact same activity. I run a wizard, cancel it (or even finish it) and try to run the wizard again, but only get a blank card. Looking at the rendered source, there doesn't appear to be any content written to th inner div of a card. I even tried to add a wizard.destroy() explicitly to the last line of my on 'finish' event handlers, but that doesn't appear to be doing anything.

Anybody else worked their way through this one yet?

pablitobs
11 Nov 2008, 12:02 AM
Hi, first, this is a great wizard, I download it, plug it ,and it starts working fine....

I have this litle problem, messing arround with the code, I was trying to insert some textfields in a table layout after some textfields in a row layout like the attached picture (I made it on photoshop)

this is the code for the wizardCard


new Ext.ux.Wiz.Card({
title : 'Lugar de Nacimiento',
monitorValid : true,
defaults : {
labelWidth: 135,
labelStyle : 'font-size:11px',
blankText: 'Este campo es requerido',
msgTarget: 'side'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Por favor ingrese los datos del lugar de Nacimiento.'
},
textfield,textfield,
{
border : false,
bodyStyle : 'background:none; ',
html:'<hr>'
},{
[ textfields on colum layout should go here... but how?]
},

textfield,textfield
]
})


I've been trying with something like this:

{
layout:'column',
bodyStyle:'padding:5px',
defaults: {bodyStyle:'padding:15px'},
items: [textfield,textfield,textfield]
};

But it shows me only the field without the label.

I hope the infor is enough to figure it what is the problem.

pablitobs
11 Nov 2008, 12:04 AM
I had the same problem what I did was to use wizard.hide() and then a function to clear the fields

CutterBl
11 Nov 2008, 6:33 AM
@Pablitobs,

Check out the Table Form Layout plugin:

http://extjs.com/forum/showthread.php?t=39342&highlight=tableform

Also, which problem are you referencing when you mention clearing the fields?

pablitobs
11 Nov 2008, 4:56 PM
@Pablitobs,

Check out the Table Form Layout plugin:

http://extjs.com/forum/showthread.php?t=39342&highlight=tableform

Also, which problem are you referencing when you mention clearing the fields?

Thanks for the tip, I was talking about the problem when you click on cancel or finish and you can't get the wizard back, instead of close(), I use hide() but when I do the show() all the fields return with their filled values (if they had one), so I do hide (), then show() and clear the fields with reset().

pablitobs
13 Nov 2008, 12:05 AM
Hi Folks, I hope somebody could help me on this....

I have this :



var record_pais = new Ext.data.Record.create([{name: 'codi_pais'},{name: 'nomb_pais'}]);
var proxy_pais = new Ext.data.HttpProxy({method: 'GET',url: '/tramites/includes/combo_depen.php?proce=pais'});
var reader_pais = new Ext.data.JsonReader({root: 'resultado'},record_pais);
var pais = new Ext.data.Store({autoLoad: true,pruneModifiedRecords: true,proxy: proxy_pais,reader: reader_pais});

Then I have this:



var ln_pais = new Ext.form.ComboBox({
id: 'ln_pais',
name: 'ln_pais',
fieldLabel: 'Pa&iacute;s',
store: pais,
displayField: 'nomb_pais',
valueField: 'codi_pais',
mode: 'local',
listWidth:200,
allowBlank:false,
editable: true,
emptyText: 'Seleccione una opcion...',
triggerAction: 'all'
});


As you see the valueField is 'codi_pais' wich is a numeric value, as you can see on this:



<?php
function pais()
{
$query = 'SELECT * FROM countrylist ORDER BY name';
$query = tep_db_query($query);
$rows = mysql_num_rows($query);
for ($z=0; $z < $rows; $z++){
$data = mysql_fetch_object($query);
$arreglo['resultado'][$z] = array('codi_pais' =>$data->idcountry , 'nomb_pais' =>$data->name);
}

$json = new Services_JSON();
echo $json->encode($arreglo);

}
?>

CREATE TABLE IF NOT EXISTS `countrylist` (
`idcountry` int(11) NOT NULL auto_increment,
`name` varchar(64) NOT NULL default '',
PRIMARY KEY (`idcountry`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=242 ;




ok, till here everything is fine and clear right, it means I have a combobox fiilled from a table of countries, each one with and unique ID(idcountry) and NAME(name), and in the combobox the valueField is 'codi_pais' wich get the value from idcountry.....

then in the wizard it works fine, but at the end when I run my save function wich invokes the getWizardData : function() on the 'finish' event, the value I get is the NAME(name), not the ID(idcountry) it means if I have (id,name) = (1,'USA') I am getting 'USA' not 1, so its me or the function is getting the text value asociated with the combobox instead the real valueField?

any help will be really appreciated here.

CutterBl
1 Dec 2008, 7:51 PM
Yeah, I had a similar issue. This is related directly to the BasicForm class itself. I don't remember which post I found it on, but someone had written an override that seemed to take care of the issue:



Ext.override(Ext.form.BasicForm, {

/**
* Returns the fields in this form as an object with key/value pairs as they would be submitted using a standard form submit.
* If multiple fields exist with the same name they are returned as an array.
* @param {Boolean} asString (optional) false to return the values as an object (defaults to returning as a string)
* @return {String/Object}
*/
getValues : function(asString){
var fs = Ext.lib.Ajax.serializeForm(this.el.dom);
if(asString === true){
return fs;
}
return Ext.urlDecode(fs.replace(/\+/g, ' ')); // /\+/g forum disabled the backslash
}
});


This works on a standard form, but I don't know that I've tried it within a wizard yet, so you'll probably need to do some testing.

CutterBl
6 Dec 2008, 9:18 AM
Yes! I finally figured it out!

I started going through all of the other wizards in my application, doing additional testing, and I found that one of the wizards was working as expected. I then started doing a side by side comparison to try and find the difference that would make the one work, and others not. What I found was that I had extended the initComponent() method, applying the cards to the config and calling the superclass initComponent():



// Abbreviated card init config
initComponent: function(){
// Custom data object I'm passing in for process stuff
var data = this.data;
Ext.apply(this,{
cards : [

// first card with welcome message
new Ext.ux.Wiz.Card({
title : 'Welcome',
id: 'property_card1',
items : [{
border : false,
bodyStyle : 'background:none;',
html : 'Here we will walk you through setting up a new<br />'+
'property in RentalIncomeManager.<br/><br/>'+
'Please click the "next"-button and fill out all form values.'
}]
})
]
});
RIM.wiz.PropertyWizard.superclass.initComponent.call(this);
}


This resolved the issue, and now things are working as expected. I'm not sure what the true difference is, but it works. Thanks to all who've chimed in to help me with this issue, and I'll post this fix to the Ext.ux.Wiz thread so that others can benefit as well.

supercharge2
11 Dec 2008, 2:38 PM
I am using the skip card code. On the card, before the card I may or may not want to skip, I have a beforecardhide event. This event does a Ext.getCmp('nextcardid').setSkip(boolean);. It seems to be having a problem with the indicators and step count, Firebug says this.indicators[currentVisibleCardIndex] is undefined and it shows step 9 of 3 (9 is the total number of cards I have). If I comment out this.wizard.headPanel.initIndicators(); on the setSkip method it works but of course the step 9 of 3 and indicators are messed up. Anyone have any ideas on this?

GOTTMODUS
19 Dec 2008, 2:26 AM
Hi,

is there a way to add items dynamically?

My Problem:

The user should be able to deine some options in a couple of cards. After that the next card should read the data from the cards before and decide wich card or items in a card are displayed.

Can anyone help me with that?

rnfbr1
17 Jan 2009, 9:59 PM
Hi , there seems to be a problem when setting the id attribute in a card , the next button doesnt work . Without the id attribute it works fine , but i need to set the id in order to get the data at the end. What can be done?? , How can i get the data from all cards?? Here is the code




new Ext.ux.Wiz.Card({

title :'Nivel',

id:'2', Cannot set the id attribute , Next button does not advance to the next card

monitorValid :true,

defaults :{

labelStyle :'font-size:11px'
},


THanks in advance for your help.

CutterBl
18 Jan 2009, 1:32 PM
I'm only guessing here, but looking at your example I would say it's the id that you've chosen. The id is applied to the div container that is created for the card, and the id attribute must begin with a non number. I don't remember all of the JS rules on this, but I want to say it's a-z, or an underscore.

rnfbr1
21 Jan 2009, 9:39 PM
Thanks CutterBl , that took care of my problem :)

ec-cts
24 Jan 2009, 1:14 PM
Please, friends:

Someone could post an full example of applying plug-in which he sent all the card data to a url of the backend, when you click the finish button?


Thanks in advance

Carlos

chalu
12 Feb 2009, 12:55 AM
I think this thread (http://www.extjs.com/forum/showthread.php?p=287238#post287238) will interest you guys :):)

accilies
16 Feb 2009, 10:42 PM
Hello Everyone,

I am too at a stage where I would like to be able to add / remove cards form the wizard in runtime. Can someone please suggest a way to achieve this ?? 8-|

Awaiting response & digging for solution !

walldorff
17 Feb 2009, 6:30 AM
I've made a small adjustment to the bindHandler in Card.js, in order to validate the content of any HTMLeditor in a card.
Here's the code:

bindHandler : function()
{
if(!this.bound){
return false; // stops binding
}
var valid = true;
this.form.items.each(function(f){

if (f.xtype == 'radio' && (f.getGroupValue() == null)) {
// if no radiobutton is clicked then valid = false
valid = false;
}

/** [ HTMLeditor validation begin ] ******************************** **/
else if (f.isXType('htmleditor')) {
// if the html editor is empty or has only (non breaking) spaces
// or breaks (<br>), then valid = false
var value = f.getValue();
value = value.replace(/&nbsp;/gi,"");
value = value.replace(/<br>/gi,"");
value = value.trim();
valid = (value != '');
}
/** [ HTMLeditor validation end ] ********************************** **/

else {
if(f.isValid && !f.isValid(true)){
valid = false;
return false;
}
}

});
if(this.buttons){
for(var i = 0, len = this.buttons.length; i < len; i++){
var btn = this.buttons[i];
if(btn.formBind === true && btn.disabled === valid){
btn.setDisabled(!valid);
}
}
}
this.fireEvent('clientvalidation', this, valid);
},

chalu
17 Feb 2009, 1:00 PM
Hello Everyone,

I am too at a stage where I would like to be able to add / remove cards form the wizard in runtime. Can someone please suggest a way to achieve this ?? 8-|

Awaiting response & digging for solution !

Don't want to be seen as hijacking / diverting this thread, but you may want to look at this (http://extjs.com/forum/showthread.php?t=32132);)

accilies
17 Feb 2009, 9:25 PM
Thanks for the link... it looks good but does it support what I was supposed to be doing i.e. add cards / steps dynamically? Wanted to ask before I would start to migrate as that is the only thing stopping me from not using this component & not much description given in the first post of the thread.


Don't want to be seen as hijacking / diverting this thread, but you may want to look at this (http://extjs.com/forum/showthread.php?t=32132);)

John Sourcer
8 Mar 2009, 10:22 AM
Thanks for the excellent extension.

Has anybody managed to get scrolling working on panels in cards? I've read the scrolling message on the thread but can't get scrolling to work on an individual panel.

accilies
8 Mar 2009, 3:04 PM
Works for me. In the respective instance of Ext.ux.Wiz.Card, I have set the autoScroll: true property. Did not get any problem in that since it extends FormPanel.



.....

new Ext.ux.Wiz.Card({
id: 'card3',
autoScroll: true,
title: 'select products',
monitorValid: true,
skip: true,
.
.
.
.
})
.....

cchic
17 Mar 2009, 2:31 AM
I think I've found another bug.

It looks like the initial page of the wizard isn't getting initialized correctly if it requires validation (monitorValid : true). If you take your SimpleWizard.html and remove the opening non-validation card you'll be able to reproduce this. I think this is occurring because the card level onCardShow event handler isn't getting called for the initial page with validation. Since this doesn't get called the startMonitoring method doesn't get called.

I have the same problem.
anyone have a solution for this bug?
When i put a first card with a monitorValid, the validation is not working.

Jangla
31 Mar 2009, 5:14 AM
Hi,

is there a way to add items dynamically?

My Problem:

The user should be able to deine some options in a couple of cards. After that the next card should read the data from the cards before and decide wich card or items in a card are displayed.

Can anyone help me with that?
I too would be interested in this although from a different point of view: I'd like to be able to configure the cards from another file as the Plugin.RemoteComponent allows you to do with panels (I've tried adapting that code but I always get something along the lines of cards.length is null errors as the Wiz expects them to be there from the start).
Essentially other code on the server would read a user's configuration options and generate a file that the wizard can then read to define different questions for each user.

mike1993
9 Apr 2009, 12:14 PM
There are many posts about exactly what you're asking IN this thread. Please read all posts.

I went through this topic twice and did not see any posts w/ ajax validation between cards.

While I get "Query with identical name already exists..." and return false. But Ajax is non-blocking. so, how do I preven the wizard from going further?

Big thanks.



// 2nd card with input fields: query name
new Ext.ux.Wiz.Card({
title : 'Query name',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Please enter your new query name. Only letters, underscores and hyphens are allowed.'
}, {
xtype: 'textfield',
name : 'query_name',
allowBlank : false,
fieldLabel : 'Query Name',
maxLength: 32,
validator : function(v){
var t = /^[a-zA-Z1-9_\- ]+$/;
return t.test(v);
}
}],
listeners: {
beforecardhide: {
scope: this,
fn: function(card) {
var wizData = this.wizard.getWizardData()
var cardData = wizData[card.getId()];
console.log('query wiz. card #2: ',this.wizard.getWizardData(), card, cardData );
Ext.Ajax.request({
disableCaching: true,
method: 'POST',
url: pageConfig.queryRunUrl,
params: {
ajax: 1,
cli_id: pageConfig.cli_id,
query_name: cardData['query_name'],
rm: 'validate_name',
sid: pageConfig.ses_id
},
success: function( result, request ) {
var json = doJSON( result.responseText );
if( json.success ) {
return true;
}
else {
Ext.MessageBox.alert('Error!','Query with identical name already exists. Use different name.');
return false;
}
},
failure: function( result, request ) {
Ext.MessageBox.alert('Error!',"Could not complete AJAX request. " +
result.responseText );
return false;
}
});
}
}
}
}),

franklt69
13 Apr 2009, 9:06 AM
Hi Thorsten I was playing the wizard, using mask:



loadMaskConfig: {
sendigMap : 'Please wait, sending email...',
savingMap : 'Please wait, validating input and saving the map...'},


and never the mask appear because when I call this method never the message in the mask is show

this.showLoadMask(true, 'savingMap');



showLoadMask : function(show, type)
{
if (!type) {
type = 'default';
}

if (show) {
if (this.loadMask == null) {
this.loadMask = new Ext.LoadMask(this.body);
}
//this.loadMask.msg = this.loadMaskConfig['type']; in the code was 'type' I think is wrong
this.loadMask.msg = this.loadMaskConfig[type];
this.loadMask.show();
} else {
if (this.loadMask) {
this.loadMask.hide();
}
}
},



in the first page of this thread appear an image in the wizard, how I can setup the wizard to show images in the left side?

regards
frank

sirioz10
20 Apr 2009, 5:02 AM
I went through this topic twice and did not see any posts w/ ajax validation between cards.

While I get "Query with identical name already exists..." and return false. But Ajax is non-blocking. so, how do I preven the wizard from going further?

Big thanks.


Anybody has found a good solution for this issue?

ThorstenSuckow
6 May 2009, 10:53 PM
Ext.ux.Wiz 0.2RC1 is out and works with Ext3.0. Please note: Ext 2.0 users still need the Ext.ux.layout.CardLayout extension from http://code.google.com/p/ext-ux-layout-cardlayout/ - Ext 3.0 users need this extension for the wizard at least until http://www.extjs.com/forum/showthread.php?t=67381 has made it into the official Ext code.

ThorstenSuckow
7 May 2009, 2:12 AM
Anybody has found a good solution for this issue?

Please take a look at the bindHandler method in Ext.ux.Wiz.Card - it checks whether a "isValid" method is defined for the items in the various forms and evaluates its return value - if it equals to "false", then it prevents the user from browsing to the next card. Try to play around with this - if you do not succeed, send me over your detailed request and I'll think about a less generic solution.

ThorstenSuckow
31 May 2009, 2:53 AM
Ext.ux.Wiz 0.2RC2 is available. See the changelog on the first page for details.


Hi Thorsten I was playing the wizard, using mask:



loadMaskConfig: {[...]}

and never the mask appear because when I call this method never the message in the mask is show [...]


This was fixed. Thanks for the issue report.

MH61
16 Jul 2009, 10:45 AM
My Wizard that works in 2.2 doesn't work in 3.0.


this.getRawValue() is undefined
if(this.rendered && this.emptyTe...wValue().length < 1 && !this.hasFocus){\n
This is triggered on the show () command for the wiz. has there been a change in how the wizard is created?

jlowe
24 Jul 2009, 6:32 AM
Anybody has found a good solution for this issue?

Here's what I did:



initEvents: function() {

this.validated = false;

this.on('show', function(){
this.validated = false;
}, this);

this.on('beforecardhide', function(card){
if (!this.validated) {
this.validate();
}
return this.validated;
}, this);
},

validate: function() {
var params = this.getForm().getValues();

Ext.Ajax.request({
url: '...',
method: 'POST',
params: params,
scope: this,
success: function(response, options) {
this.validated = true;
this.parent.nextCard();
},
invalid: function(response, options) {
this.getForm().markInvalid(response.responseJson.errors.fieldErrors);
}
});
}




Note that because of the asynchronous nature of the Ajax.request, it is now the responsibility of the success handler to advance to the next card in the deck. In order to enable this, I needed to give the card a reference to the parent Wizard, and expose the following method on the Wizard:



nextCard: function() {
if (this.currentCard < this.cardCount-1) {
this.cardPanel.getLayout().setActiveItem(this.currentCard+1);
}
}

Jangla
28 Jul 2009, 12:20 AM
Can I ask something? If I have a numberfield, for example, and it's set to not allow blanks, the first time I try and go past the field without filling it in, the validation kicks in and the div I've defined to display the error message is successfully updated.

Now if I clear the field so the validation error goes away and then fill in the field incorrectly again, while the red line appears on the field, the error message div is not repopulated.

Why is this?

MH61
30 Jul 2009, 5:34 AM
I'm unable to click the previous button in 3.0.

In the cardlayout override it calls hide()



hide : function(){ if(this.fireEvent('beforehide', this) !== false){
this.doHide();
this.fireEvent('hide', this);
}
return this;
}

but this.fireEvent('beforehide', this) is returning false, even though it returned true in 2.2. I notice the fireEvent code has significantly changed.

Has anyone else run in to this issue? Does the wizard fail to add this event?

Mthor
5 Aug 2009, 6:39 AM
First off Great Wizard, thanks for the hard work!!

I too had issues with the scroll bars. I understand all the parent containers and as I can get the panel to scroll that is not the function I wanted, If you have a grid and you cannot get scroll bars in the grid then another solution is to add a paging toolbar. I did this and I think the small grid looks nicer with paging then with a scroll bar. the grid is already a small size most likly since it is in a wizard. just wanted to share what i did since i could not get scroll bars in the grid. Thanks to all the help and post people have made. this has been very helpful to me. thanks Ext also

var storePaging = {start: 0, limit: 6};

bbar: new Ext.PagingToolbar({
store: phoneStore,
pageSize: 6,
displayInfo: true,
displayMsg: 'Phones {0} - {1} of {2}',
emptyMsg: "No phones to display"
})

phoneStore.load({params:storePaging});

phpfreak
9 Aug 2009, 11:02 AM
any updates as it doesnt work on ext 3.0. the previous and next buttons do not work.
even with rev 4 of cardlayout.js ?

charleshimmer
11 Aug 2009, 4:56 PM
I can't get it to load with 3.0 either. Keep getting


TypeError: Result of expression 'Ext.ux.Wiz.Card' [undefined] is not a constructor.

Anybody have any luck with this?

pezze
12 Aug 2009, 2:58 AM
Is possible to insert a Ext.ux.form.FileUploadField in a card? I've tried to implement it but it doesn't work as in the image.



new Ext.ux.Wiz.Card({
title : 'Microsoft Excel',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [
{
xtype: 'fileuploadfield',
id: 'excelFileUpload-upload',
emptyText: 'Seleziona un file',
fieldLabel: 'File (*.xls)',
name: 'excelFileUpload',
buttonText: '',
buttonCfg: {
iconCls: 'icon-excel'
},
validator : function(v){
var t = /^[a-zA-Z_\- ]+$/;
return t.test(v);
}
}
]
}),
Thanks.

MasterAM
12 Aug 2009, 5:19 AM
I can't get it to load with 3.0 either. Keep getting


TypeError: Result of expression 'Ext.ux.Wiz.Card' [undefined] is not a constructor.Anybody have any luck with this?

@charleshimmer
The source files need to be included in a certain order (more accurately, the Wiz main cs ff firs).
Otherwise, the other constructors are overwritten.
This may be the issue you have experienced.

You can take my inclusion order as an example.



<script type="text/javascript" src="assets/scripts/classes/wiz/CardLayout.js"></script>
<script type="text/javascript" src="assets/scripts/classes/wiz/Wizard.js"></script>
<script type="text/javascript" src="assets/scripts/classes/wiz/Header.js"></script>
<script type="text/javascript" src="assets/scripts/classes/wiz/Card.js"></script>

BTW, @MindPatterns
I love this implementation.
Been working on some extra features, such as non-linear wizard scenarios (multiple start/end points, more wizard-wide data retrieval options, advancing to specific cards thus having more than one way for completing the wizard etc.)

Did this as I require some very similar wizards, and it seems like a waste to create 3-4 separate wizards when there are only 1-2 different 'slides'.

Will release it once I finish working and a bit of testing.

pezze
12 Aug 2009, 5:25 AM
You can take my inclusion order as an example.



<script type="text/javascript" src="assets/scripts/classes/wiz/CardLayout.js"></script>
<script type="text/javascript" src="assets/scripts/classes/wiz/Wizard.js"></script>
<script type="text/javascript" src="assets/scripts/classes/wiz/Header.js"></script>
<script type="text/javascript" src="assets/scripts/classes/wiz/Card.js"></script>



Unfortunatelly the library are already in the correct order. Any other solutions?

Thanks.

charleshimmer
12 Aug 2009, 7:34 AM
Thanks Master AM! That was it!

I am really interested in what you mentioned about having non-linear wizards. I could see this being really helpful.

charleshimmer
19 Aug 2009, 11:45 AM
I'm finding that date or time form fields don't work in the wizard form panel. Anybody else experiencing this or figure out a solution? I noticed somebody else had problems with a upload field.

charleshimmer
19 Aug 2009, 12:41 PM
In case this helps anybody else. I couldn't get the date or time fields to trigger. When I comment out line 800 in ext-all.css like this


.ext-safari .x-form-field-wrap .x-form-trigger{
/*right:0;*/
}

The date field works fine, and the time field will trigger, but gets whiped clear of its selection as soon as it loses focus.

Mthor
20 Aug 2009, 11:27 AM
was wondering if anyone had any examples on skipping cards, I have many cards and need to skip a set of cards depending on if a checkbox is selected or not, I can skip one card and get to finish, but when I add multiple cards i run into problems.

in card 6 you can see i have some code to skip one card if not checked and to go to the finish card if checked, this works if there are only 8 cards,

what i am looking for is if in card 6 the checkbox gets checked then i need to fill out cards 7 - 14 if the button is not checked skip to the final card.

all code is posted below, thanks for the help, if you scroll to the text in red, that is the start of the wizard




Ext.onReady(function(){

// location store for location drop down
var locationProxy = getExtProxy('Locations/GetLocations');
// create the Data Store
var locationStore = new Ext.data.Store({
// load using HTTP
proxy: locationProxy,
remoteSort: true,

// the return will be XML, so lets set up a reader
reader: new Ext.data.XmlReader({
record: 'locations/location',
id: '@id',
totalRecords: 'total'
}, [
{name: 'select', mapping: '@select'},
{name: 'cn'},
{name: 'glid'}
])
});
// combo box for a list of locations
var locationCombo = new Ext.form.ComboBox({
name : 'cn',
fieldLabel : 'Location',
valueField: "glid",
allowBlank : false,
store:locationStore,
mode: 'local',
dataIndex: 'cn',
displayField: 'cn',
triggerAction: 'all',
selectOnFocus:true,
listWidth: 170
})

//End

// User Types
var usertypeProxy = getExtProxy('EXCH/GetCreateUserData');
// create the Data Store
var usertypeStore = new Ext.data.Store({
// load using HTTP
proxy: usertypeProxy,
remoteSort: true,

// the return will be XML, so lets set up a reader
reader: new Ext.data.XmlReader({
record: 'userTypes/UserType',
id: '@id',
totalRecords: 'total'
}, [
{name: 'select', mapping: '@select'},
{name: 'Description'},
{name: 'StatusTypeCode'}
])
});
// combo box for a list of user Type
var userTypeCombo = new Ext.form.ComboBox({
name : 'Description',
fieldLabel : 'User Type',
valueField: "StatusTypeCode",
allowBlank : false,
store:usertypeStore,
mode: 'local',
dataIndex: 'Description',
displayField: 'Description',
triggerAction: 'all',
selectOnFocus:true,
listWidth: 170
})

//End

//var and store for the phone grid
var phoneProxy = getExtProxy('AXL/GetAvailablePhones');
// create the Data Store
var phoneStore = new Ext.data.Store({
// load using HTTP
proxy: phoneProxy,
remoteSort: true,

// the return will be XML, so lets set up a reader
reader: new Ext.data.XmlReader({
record: 'phone',
id: '@id',
totalRecords: 'total'
}, [
{name: 'select', mapping: '@select'}, {name: 'name'}, {name: 'description'},{name: 'location'}, {name: 'dirnumber'}
])
});

phoneStore.setDefaultSort('name', 'asc');

var storePaging = {start: 0, limit: 5};
// column model for phone grid
var colModel = new Ext.grid.ColumnModel([
{header: "Description", width: 125, hideable: false, name: 'description', dataIndex: 'description', sortable: true},
{header: "Phone ID", width: 125, hideable: false, name: 'name', dataIndex: 'name', sortable: true},
{header: "Location", width: 150, hideable: false, name: 'location', dataIndex: 'location', sortable: true}
]);
colModel.defaultSortable= true;
// userSM is to select a row and get back the cell data
var userSM = new Ext.grid.RowSelectionModel({
singleSelect: true,
listeners: {
rowselect: function(sm, row, rec) {
var rec = userSM.getSelected();
//var wiz = Ext.getCmp("phoneGrid").getSelections();
confirm(rec.get('description') + ' ' + rec.get('name') + ' ' + rec.get('location'))
}
}
});
// phone grid
var phoneGrid = new Ext.grid.GridPanel({
store: phoneStore,
id: 'phonegrid',
cm:colModel,
viewConfig: { forceFit: true },
autoExpandColumn: 2,
width: 400,
height: 130,
sm: userSM,
loadMask: true,
layout: 'fit',
bbar: new Ext.PagingToolbar({
store: phoneStore,
pageSize: 5,
displayInfo: true,
displayMsg: 'Phones {0} - {1} of {2}',
emptyMsg: "No phones to display"
})
});

/*
* this is a top tool bar if we need one.
tbar: [
{
xtype:'button',
text: 'Add Phone',
icon: 'js/Libs/assets/drop-add.gif',
iconCls:'icon-magnifier'
}
],
*/
// end phone grid

// auto build user name and display name
firstName = new Ext.form.TextField({
fieldLabel: 'First Name',
name: 'firstname',
dataIndex:'firstname',
allowBlank : false,
validator : function(v){
var t = /^[a-zA-Z_\- ]+$/;
return t.test(v);
}
});

lastName = new Ext.form.TextField({
fieldLabel: 'Last Name',
name: 'lastname',
dataIndex:'lastname',
allowBlank:false,
validator : function(v){
var t = /^[a-zA-Z_\- ]+$/;
return t.test(v);
}
});
lastName.on('change', BuildUsername);

userName = new Ext.form.TextField({
fieldLabel: 'User Name',
name: 'username',
dataIndex:'username',
width: 200
});
userName.on('change', BuildDisplayName);

displayName = new Ext.form.TextField({
fieldLabel: 'Display Name',
name: 'displayname',
dataIndex:'displayname',
width: 200
});

// end autobuild

Ext.QuickTips.init();
// start of the wizard
var wizard = new Ext.ux.Wiz({
title : 'Add a User',
headerConfig : {
title : ' '
},

cardPanelConfig : {
defaults : {
baseCls : 'x-small-editor',
bodyStyle : 'padding:20px 15px 5px 65px;background-color:#F6F6F6;',
border : false
}
},

cards : [
// card 1 id='name'
new Ext.ux.Wiz.Card({
title : 'Your name',
id : 'name',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : '<span class="welcome">Welcome to the add user Wizard.</span> </br></br> <span class="descriptionText">Please enter your first and last name in the text fields</span>'
},
firstName,
lastName
]
}),

// card 2 id="userName"
new Ext.ux.Wiz.Card({
title : 'User name and display name',
id : 'userName',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ' <span class="descriptionText">The user name and display name have been created based on your first and last name that you inputed on the previous page. If no changes need to be made please click next to continue. If you need to make a change to your user name then do so here, the display name will change for you, but your first and last name will stay the same.</span>'
},
userName,
displayName
]
}),

// card 3 id="password
new Ext.ux.Wiz.Card({
title : 'Password',
id : 'password',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ' <span class="descriptionText">Please enter your password. The passwords must match to continue.</span>'
},
new Ext.form.TextField({
name : 'password',
id : 'validatePassword',
fieldLabel : 'Password',
vtype : 'alphanum',
inputType : 'password',
allowBlank : false
}),
new Ext.form.TextField({
name : 'confirmpassword',
fieldLabel : 'Confirn Password',
vtype : 'alphanum',
inputType : 'password',
validator : function() {
if (this.getValue() == Ext.getCmp('validatePassword').getValue())
return true;
return false;
},
invalidText: 'Password Entries Should Match',
allowBlank: false
})
]
}),

// card 4 id="userType"
new Ext.ux.Wiz.Card({
title : 'User Type',
id : 'userType',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : '<span class="descriptionText">Please select what type of user this will be for the company. click administrator checkbox if the user is to be an administrator.</span>'
},
userTypeCombo,
new Ext.form.Checkbox({
name : 'admin',
fieldLabel : 'Administrator',
allowBlank : true
})
]
}),

// card 5 id="location"
new Ext.ux.Wiz.Card({
title : 'Location',
id : 'location',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ' <span class="descriptionText">Select the location for the user. The location that you select should be where the user is going to be located.</span>'
},
locationCombo
]
}),


// card 6 id="phone"
new Ext.ux.Wiz.Card({
autoScroll: true,
title : 'Select a phone',
id : 'phone',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:5px;',
html : '<span class="descriptionText">Select a row and click next to add a phone from the list to a user. If you would like to add a phone to the list, select the check box and click next. To unselect a row hold down CTRL and click on the selected row</span>'
},
new Ext.form.Checkbox({
id : 'addPhone',
name : 'addPhone',
fieldLabel : 'Add Phone',
allowBlank : true,
listeners: {
check: function(checkbox, checked) {
if (checked) {
wizard.cards[6].skip = false;
Ext.getCmp('phonegrid').disable();
}
else {
wizard.cards[6].show = true;
Ext.getCmp('phonegrid').enable();
}
}
}
}),
phoneGrid
]
}),

// card 7 id="createphone"
new Ext.ux.Wiz.Card({
title : 'Creat Phone',
id : 'createphone',
monitorValid : true,
skip: true,
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Please enter the phones mac address and serial number.'
},
new Ext.form.TextField({
name : 'MACAddress',
fieldLabel : 'Mac Address',
allowBlank : false,
autoCreate : {tag: 'input', type: 'text', maxlength: '12', autocomplete: 'off'},
validator : function(v){
var t = /[0-9]/;
return t.test(v);
}
}),
new Ext.form.TextField({
name : 'serialNumber',
fieldLabel : 'Serial Number',
allowBlank : false
})
]
}),

// card 8 id="description"
new Ext.ux.Wiz.Card({
title : 'Finished!',
id : 'finish',
monitorValid : true,
skip : true,
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ' Please enter a description for the phone.'
},
new Ext.form.TextField({
name : 'description',
fieldLabel : 'Description',
allowBlank : false,
validator : function(v){
var t = /^[a-zA-Z_\- ]+$/
return t.test(v);
}
})
]
}),

// card 9 id="phonetype"
new Ext.ux.Wiz.Card({
title : 'Finished!',
id : 'finish',
monitorValid : true,
skip : true,
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ' Please enter a phone type.'
},
new Ext.form.ComboBox({
name : 'phoneType',
fieldLabel : 'Phone Type',
allowBlank : false
})
]
}),

// card 10 id="rentaldevice"
new Ext.ux.Wiz.Card({
title : 'Rental Device',
id : 'rentaldevice',
monitorValid : true,
skip : true,
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Select the checkbox if this phone is a rental device.'
},
new Ext.form.Checkbox({
name : 'device',
fieldLabel : 'Rental Device',
allowBlank : false
})
]
}),

// card 11 id="phonemodel"
new Ext.ux.Wiz.Card({
title : 'Phone Model',
id : 'phonemodel',
monitorValid : true,
skip : true,
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Please select the phone model.'
},
new Ext.form.ComboBox({
name : 'phoneModel',
fieldLabel : 'Phone Model',
allowBlank : false
})
]
}),

// card 12 id="phoneTemplate"
new Ext.ux.Wiz.Card({
title : 'Phone Template',
id : 'phoneTemplate',
monitorValid : true,
skip : true,
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Please select a phone template.'
},
new Ext.form.ComboBox({
name : 'phoneTemplate',
fieldLabel : 'Phone Template',
allowBlank : false
})
]
}),

// card 13 id="phonelocation"
new Ext.ux.Wiz.Card({
title : 'Phone Location',
id : 'phonelocation',
monitorValid : true,
skip : true,
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Please select a location for your phone.'
},
new Ext.form.ComboBox({
name : 'phonelocation',
fieldLabel : 'Phone Location',
allowBlank : false
})
]
}),

// card 14 id="addonmodules"
new Ext.ux.Wiz.Card({
title : 'Add On Modules',
id : 'addonmodules',
monitorValid : true,
skip : true,
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Do you want to add on modules and a remote device pool.'
},
new Ext.form.Checkbox({
name : 'yes',
fieldLabel : 'Yes',
allowBlank : false
}),
new Ext.form.Checkbox({
name : 'no',
fieldLabel : 'No',
allowBlank : false
})
]
}),

// card 15 id="finish"
new Ext.ux.Wiz.Card({
title : 'Finished!',
id : 'finish',
monitorValid : true,
items : [{
border : false,
bodyStyle : 'background:none;',
html : '<span class="welcome">Thank you for adding a user.</span><br/> '+
'<span class="descriptionText"Your data has been collected.<br/>'+
'When you click on the "finish" button, the user infromation you entered will be saved.</span>'
}]
})


]
});

// show the wizard
wizard.show();
phoneStore.load({params:storePaging});
locationStore.load({params:storePaging});
usertypeStore.load({params:storePaging});
});

//BUILD USER NAME AND DISPLAY NAME.
function BuildUsername() {
var domain = document.getElementsByName('domain')[0];

if (userName.getValue() == "") {
userName.setValue(firstName.getValue().toLowerCase().substring(0,1) + lastName.getValue().toLowerCase());
}
displayName.setValue(firstName.getValue() + ' ' + lastName.getValue() + ' (' + userName.getValue() + '@' + domain.value + ')');
}

function BuildDisplayName() {
var domain = document.getElementsByName('domain')[0];

displayName.setValue(firstName.getValue() + ' ' + lastName.getValue() + ' (' + userName.getValue() + '@' + domain.value + ')');
}
//END USERNAME AND DISPLAY NAME....................................

charleshimmer
25 Aug 2009, 3:16 PM
If anybody was having trouble with date fields or combo boxes in Safari, using this css override worked for me.

Same as before except this time it doesn't touch the original ext-all.css file which will get whiped out if you upgrades versions.


.ext-safari .x-form-field-wrap .x-form-trigger{
right:inherit;
}

roque
22 Sep 2009, 7:25 AM
I solved this and other issues moving all the handlers declaration to the initComponent. I dont know why the handler declaration is in the initEvents that's called too late.
I also think the Card specific handler declararion must reside in the Card instead of the wizard.

Card.js
initComponent : function() {
.....
Ext.ux.Wiz.Card.superclass.initComponent.call(this);

this.on('beforehide', this.bubbleBeforeHideEvent, this);
this.on('beforecardhide', this.isValid, this);
this.on('show', this.onCardShow, this);
this.on('hide', this.onCardHide, this);
}

Wizard.js
initComponent : function() {
.....
Ext.ux.Wiz.superclass.initComponent.call(this);

var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
cards[i].on('show', this.onCardShow, this);
cards[i].on('hide', this.onCardHide, this);
cards[i].on('clientvalidation', this.onClientValidation, this);
}
}

roque
28 Sep 2009, 7:28 PM
If we try to lazy loading the Card in the wizard, it wont work because of the code in Wizard.js

var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
cards[i].on('show', this.onCardShow, this);
cards[i].on('hide', this.onCardHide, this);
cards[i].on('clientvalidation', this.onClientValidation, this);
}
}

and the reason is because cards[i] is undefined at this time, as the cards are not created because of the lazy loading. To solve this problem instead of attaching a listener for the card show and hide events in the wizard, I created two custom events in the Card (onCardShow and onCardHide) that I will bubble it to the container (the wizard).

Then the fix looks like:

Card.js

initComponent : function() {
.....
Ext.ux.Wiz.Card.superclass.initComponent.call(this);

this.on('beforehide', this.bubbleBeforeHideEvent, this);
this.on('beforecardhide', this.isValid, this);
this.on('show', this.onCardShow, this);
this.on('hide', this.onCardHide, this);
this.enableBubble('oncardshow', 'oncardhide', 'clientvalidation');
}


onCardHide: function() {
if (this.monitorValid) {
this.stopMonitoring();
}
this.fireEvent('oncardhide', this);
}

onCardShow: function() {
if (this.monitorValid) {
this.startMonitoring();
}
this.fireEvent('oncardshow', this);

}

});
Ext.reg('xcard', Ext.ux.Wiz.Card);

Wizard.js

initComponent : function() {
.....
Ext.ux.Wiz.superclass.initComponent.call(this);
this.on({
'oncardshow': this.onCardShow,
'oncardhide': this.onCardHide,
'clientvalidation': this.onClientValidation,
'beforeclose': this.onBeforeClose,
scope: this
});


onCardHide : function(card)
{
if (this.cardPanel.layout.activeItem.id === card.id) {
this.nextButton.setDisabled(true);
}
},


onCardShow : function(card)
{

var parent = card.ownerCt;

var items = parent.items;

for (var i = 0, len = items.length; i < len; i++) {
if (items.get(i).id == card.id) {
break;
}
}

this.currentCard = i;
this.headPanel.updateStep(i, card.title);

if (i == len-1) {
this.nextButton.setText(this.finishButtonText);
} else {
this.nextButton.setText(this.nextButtonText);
}

if (card.isValid()) {
this.nextButton.setDisabled(false);
}

if (i == 0) {
this.previousButton.setDisabled(true);
} else {
this.previousButton.setDisabled(false);
}

}


Example:

Ext.onReady(function(){

Ext.QuickTips.init();

var wizard = new Ext.ux.Wiz({

title : 'A simple example for a wizard',

headerConfig : {
title : 'Simple Wizard Example'
},

cardPanelConfig : {
defaults : {
baseCls : 'x-small-editor',
bodyStyle : 'padding:40px 15px 5px 120px;background-color:#F6F6F6;',
border : false
}
},

cards : [{
xtype: 'xcard',
title: 'Your email-address',
monitorValid : true,
defaults:{
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ' Please enter your email-address.'
}, {
xtype: 'textfield',
name : 'email',
fieldLabel : 'Email-Address',
allowBlank : false,
vtype : 'email'
}]
}, {
xtype: 'xcard',
title : 'Welcome',
items : [{
border : false,
bodyStyle : 'background:none;',
html : 'Welcome to the example for <strong>Ext.ux.Wiz</string>, '+
'a Ext JS user extension for creating wizards.<br/><br/>'+
'Please click the "next"-button and fill out all form values.'
}]
}, {
xtype: 'xcard',
title: 'Your name',
monitorValid : true,
defaults: {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Please enter your first- and your lastname. Only letters, underscores and hyphens are allowed.'
}, {
xtype: 'textfield',
name : 'firstname',
fieldLabel : 'Firstname',
allowBlank : false,
validator : function(v){
var t = /^[a-zA-Z_\- ]+$/;
return t.test(v);
}
}]
}]
});

// show the wizard
wizard.show();
});

charleshimmer
29 Sep 2009, 7:44 AM
Sweet. I will try this on my implementation of the wizard (been ported to a panel) and report back.

Thanks for posting this.

charleshimmer
5 Oct 2009, 10:59 AM
I tried moving those to the initComponent and it didn't change the behavoir. The only thing that seems to work is adding this to the css



.ext-safari .x-form-field-wrap .x-form-trigger{
right:inherit;
}

mschwartz
5 Oct 2009, 11:00 AM
Where's that scriptlet when you need it?

charleshimmer
5 Oct 2009, 11:02 AM
scriptlet? What's that?

mschwartz
5 Oct 2009, 11:11 AM
scriptlet? What's that?

See post #152 in this thread, and also see this thread:

http://www.extjs.com/forum/showthread.php?t=64377

Jangla
6 Oct 2009, 7:48 AM
...is it possible to force this wizard into a tab on a tab panel? If so, how?

charleshimmer
6 Oct 2009, 8:10 AM
That is how I am using it.

Here is how I'm doing it.

Normally there is 3 files, wizard.js, card.js, and the cardlayout extension file. Since the files have to be included in the right order I just put them into one file called WizardAll.js.

Here is my WizardAll.js which turns the wizard into a panel (which can be a tab panel) instead of a window.

Ext.namespace('Ext.ux');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz
* @extends Ext.Window
*
* A specific {@link Ext.Window} that models a wizard component.
* A wizard is basically a dialog that guides a user through various steps
* where he has to fill out form-data.
* A {@link Ext.ux.Wiz}-component consists typically of a {@link Ext.ux.Wiz.Header}
* and window-buttons ({@link Ext.Button}) which are linked to the {@link Ext.ux.Wiz.Card}s
* which themself represent the forms the user has to fill out.
*
* In order to switch between the cards in the wizard, you need the {@link Ext.ux.layout.CardLayout},
* which will check if an active-item can be hidden, before the requested new item will be set to
* 'active', i.e. shown. This is needed since the wizard may not allow a card to be hidden, if
* the input entered by the user was not valid. You can get this custom layout at
* {@link http://www.siteartwork.de/cardlayout}.
*
* Note:
* When data has been collected and teh "onFinish" listener triggers an AJAX-request,
* you should call the "switchDialogState" method so that the the dialog shows a loadmask.
* Once the requests finishes, call "switchDialogState" again, specially before any call
* to the "close" method of this component, otherwise the "closable" property of this
* instance might prevent a "close" operation for this dialog.
*
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz = Ext.extend(Ext.Panel, {

/**
* @cfg {Object} An object containing the messages for the {@link Ext.LoadMask}
* covering the card-panel on request, whereas the property identifies the
* msg-text to show, and the value is the message text itself. Defaults to
<pre><code>
{
default : 'Saving...'
}
</code></pre>
*
* Depending on the contexts the loadMask has to be shown in (using the method
* showLoadMask of this class), the object can be configure to hold
* various messages.
<pre><code>
this.loadMaskConfig = {
default : 'Saving...',
validating : 'Please wait, validating input...',
};
// loadMask will be shown, displaying the message 'Please wait, validating input...'
this.showLoadMask(true, 'validating');
</code></pre>
*/
loadMaskConfig: {
'default': 'Saving...'
},

/**
* @cfg {Number} height The height of the dialog. Defaults to "400".
*/
height: 400,

/**
* @cfg {Number} width The width of the dialog. Defaults to "540".
*/
width: 540,

/**
* @cfg {Boolean} closable Wether the dialog is closable. Defaults to "true".
* This property will be changed by the "switchDialogState"-method, which will
* enable/disable controls based on the passed argument. Thus, this config property
* serves two purposes: Tell the init config to render a "close"-tool, and create a
* "beforeclose"-listener which will either return true or false, indicating if the
* dialog may be closed.
*/
closable: false,

/**
* @cfg {Boolean} resizable Wether the dialog is resizable. Defaults to "false".
*/
resizable: false,

/**
* @cfg {Boolean} resizable Wether the dialog is modal. Defaults to "true".
*/
modal: false,

/**
* @cfg {Array} cards A numeric array with the configured {@link Ext.ux.Wiz.Card}s.
* The index of the cards in the array represent the order in which they get displayed
* in the wizard (i.e. card at index 0 gets displayed in the first step, card at index 1 gets
* displayed in the second step and so on).
*/
cards: null,

/**
* @cfg {String} previousButtonText The text to render the previous-button with.
* Defaults to "&lt; Back" (< Back)
*/
previousButtonText: '&lt; Previous',

/**
* @cfg {String} nextButtonText The text to render the next-button with.
* Defaults to "Next &gt;" (Next >)
*/
nextButtonText: 'Next &gt;',

/**
* @cfg {String} cancelButtonText The text to render the cancel-button with.
* Defaults to "Cancel"
*/
cancelButtonText: 'Cancel',

/**
* @cfg {String} finishButtonText The text to render the next-button with when the last
* step of the wizard is reached. Defaults to "Finish"
*/
finishButtonText: 'Finish',

/**
* @cfg {Object} headerConfig A config-object to use with {@link Ext.ux.Wiz.Header}.
* If not present, it defaults to an empty object.
*/
headerConfig: {},

/**
* @cfg {Object} cardPanelConfig A config-object to use with {@link Ext.Panel}, which
* represents the card-panel in this dialog.
* If not present, it defaults to an empty object
*/
cardPanelConfig: {},

/**
* @param {Ext.Button} The window-button for paging to the previous card.
* @private
*/
previousButton: null,

/**
* @param {Ext.Button} The window-button for paging to the next card. When the
* last card is reached, the event fired by and the text rendered to this button
* will change.
* @private
*/
nextButton: null,

/**
* @param {Ext.Button} The window-button for canceling the wizard. The event
* fired by this button will usually close the dialog.
* @private
*/
cancelButton: null,

/**
* @param {Ex.Panel} The card-panel that holds the various wizard cards
* ({@link Ext.ux.Wiz.Card}). The card-panel itself uses the custom
* {@link Ext.ux.layout.CardLayout}, which needs to be accessible by this class.
* You can get it at {@link http://www.siteartwork.de/cardlayout}.
* @private
*/
cardPanel: null,

/**
* @param {Number} currentCard The current {@link Ext.ux.Wiz.Card} displayed.
* Defaults to -1.
* @private
*/
currentCard: -1,

/**
* @param {Ext.ux.Wiz.Header} The header-panel of the wizard.
* @private
*/
headPanel: null,

/**
* @param {Number} cardCount Helper for storing the number of cards used
* by this wizard. Defaults to 0 (inherits "cards.length" later on).
* @private
*/
cardCount: 0,

/**
* Inits this component with the specified config-properties and automatically
* creates its components.
*/
initComponent: function () {
this.initButtons();
this.initPanels();

var title = this.title || this.headerConfig.title;
title = title || "";

Ext.apply(this, {
title: title,
layout: 'border',
cardCount: this.cards.length,
buttons: [
this.previousButton, this.nextButton, this.cancelButton],
items: [
this.headPanel, this.cardPanel]
});

this.addEvents(
/**
* @event cancel
* Fires after the cancel-button has been clicked.
* @param {Ext.ux.Wiz} this
*/
'cancel',
/**
* @event finish
* Fires after the last card was reached in the wizard and the
* next/finish-button has been clicked.
* @param {Ext.ux.Wiz} this
* @param {Object} data The collected data of the cards, whereas
* the index is the id of the card and the specific values
* are objects with key/value pairs in the form formElementName : value
*/
'finish');

Ext.ux.Wiz.superclass.initComponent.call(this);
},

// -------- helper
/**
* Returns the form-data of all cards in this wizard. The first index is the
* id of the card in this wizard,
* and the values are objects containing key/value pairs in the form of
* fieldName : fieldValue.
*
* @return {Array}
*/
getWizardData: function () {
var formValues = {};
var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
if (cards[i].form) {
Ext.apply(formValues, cards[i].form.getValues(false));
}
}

return formValues;
},

/**
* Resets all the form fields throughout the wizard and sets the resets the
* wizards card back to the first card.
*/
resetWizard: function () {
/* reset data if rendered */
if (this.rendered) {
var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
if (cards[i].form) {
cards[i].form.reset();
}
}

/* select first card */
this.cardPanel.getLayout().setActiveItem(0);
}
},

/**
* Switches the state of this wizard between disabled/enabled.
* A disabled dialog will have a {@link Ext.LoadMask} covering the card-panel
* to prevent user input, and the buttons will be rendered disabled/enabled.
* If the dialog is closable, the close-tool will be masked, too, and the dialog will not
* be closable by clicking the "close" tool.
*
* @param {Boolean} enabled "false" to prevent user input and mask the elements,
* otherwise true.
* @param {String} type The type of msg for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
switchDialogState: function (enabled, type) {
this.showLoadMask(!enabled, type);

this.previousButton.setDisabled(!enabled);
this.nextButton.setDisabled(!enabled);
this.cancelButton.setDisabled(true);

var ct = this.tools['close'];

if (ct) {
switch (enabled) {
case true:
this.tools['close'].unmask();
break;

default:
this.tools['close'].mask();
break;
}
}

this.closable = enabled;
},

/**
* Shows the load mask for this wizard. By default, the cardPanel's body
* will be masked.
*
* @param {Boolean} show true to show the load mask, otherwise false.
* @param {String} type The type of message for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
showLoadMask: function (show, type) {
if (!type) {
type = this.loadMaskConfig[type];
}

if (show) {
if (this.loadMask == null) {
this.loadMask = new Ext.LoadMask(this.body);
}
this.loadMask.msg = type;
this.loadMask.show();
} else {
if (this.loadMask) {
this.loadMask.hide();
}
}
},

/**
* Inits the listener for the various {@link Ext.ux.Wiz.Card}s used
* by this component.
*/
initEvents: function () {
Ext.ux.Wiz.superclass.initEvents.call(this);

this.on('beforeclose', this.onBeforeClose, this);

var cards = this.cards;

for (var i = 0, len = cards.length; i < len; i++) {
cards[i].on('show', this.onCardShow, this);
cards[i].on('hide', this.onCardHide, this);
cards[i].on('clientvalidation', this.onClientValidation, this);
}
},

/**
* Creates the head- and the card-panel.
* Be sure to have the custom {@link Ext.ux.layout.CardLayout} available
* in order to make the card-panel work as expected by this component
* ({@link http://www.siteartwork.de/cardlayout}).
*/
initPanels: function () {
var cards = this.cards;
var cardPanelConfig = this.cardPanelConfig;

Ext.apply(this.headerConfig, {
steps: cards.length
});

this.headPanel = new Ext.ux.Wiz.Header(this.headerConfig);

Ext.apply(cardPanelConfig, {
layout: new Ext.ux.layout.CardLayout(),
items: cards
});

Ext.applyIf(cardPanelConfig, {
region: 'center',
border: false,
activeItem: 0
});

this.cardPanel = new Ext.Panel(cardPanelConfig);
},

/**
* Creates the instances for the the window buttons.
*/
initButtons: function () {
this.previousButton = new Ext.Button({
text: this.previousButtonText,
disabled: true,
minWidth: 75,
handler: this.onPreviousClick,
scope: this
});

this.nextButton = new Ext.Button({
text: this.nextButtonText,
minWidth: 75,
handler: this.onNextClick,
scope: this
});

this.cancelButton = new Ext.Button({
text: this.cancelButtonText,
handler: this.onCancelClick,
scope: this,
minWidth: 75
});
},

// -------- listeners
/**
* Listener for the beforeclose event.
* This listener will return true or false based on the "closable"
* property by this component. This property will be changed by the "switchDialogState"
* method, indicating if there is currently any process running that should prevent
* this dialog from being closed.
*
* @param {Ext.Panel} panel The panel being closed
*
* @return {Boolean}
*/
onBeforeClose: function (panel) {
return this.closable;
},

/**
* By default, the card firing this event monitors user input in a frequent
* interval and fires the 'clientvalidation'-event along with it. This listener
* will enable/disable the next/finish-button in accordance with it, based upon
* the parameter isValid. isValid" will be set by the form validation and depends
* on the validators you are using for the different input-elemnts in your form.
* If the card does not contain any forms, this listener will never be called by the
* card itself.
*
* @param {Ext.ux.Wiz.Card} The card that triggered the event.
* @param {Boolean} isValid "true", if the user input was valid, otherwise
* "false"
*/
onClientValidation: function (card, isValid) {
if (!isValid) {
this.nextButton.setDisabled(true);
} else {
this.nextButton.setDisabled(false);
}
},

/**
* This will render the "next" button as disabled since the bindHandler's delay
* of the next card to show might be lagging on slower systems
*
*/
onCardHide: function (card) {
if (this.cardPanel.layout.activeItem.id === card.id) {
this.nextButton.setDisabled(true);
}
},

/**
* Listener for the "show" event of the card that gets shown in the card-panel.
* Renders the next/previous buttons based on the position of the card in the wizard
* and updates the head-panel accordingly.
*
* @param {Ext.ux.Wiz.Card} The card being shown.
*/
onCardShow: function (card) {
var parent = card.ownerCt;

var items = parent.items;

for (var i = 0, len = items.length; i < len; i++) {
if (items.get(i).id == card.id) {
break;
}
}

this.currentCard = i;
this.headPanel.updateStep(i, card.title);

if (i == len - 1) {
this.nextButton.setText(this.finishButtonText);
} else {
this.nextButton.setText(this.nextButtonText);
}

if (card.isValid()) {
this.nextButton.setDisabled(false);
}

if (i == 0) {
this.previousButton.setDisabled(true);
} else {
this.previousButton.setDisabled(false);
}

},

/**
* Fires the 'cancel'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onCancelClick: function () {
if (this.fireEvent('cancel', this) !== false) {
this.win = this.findParentByType('window');
if (this.win) {
this.win.hide();
} else {
this.close();
}
}
},

/**
* Fires the 'finish'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onFinish: function () {
if (this.fireEvent('finish', this, this.getWizardData()) !== false) {
this.win = this.findParentByType('window');
if (!this.win) {
this.close();
}
}
},

/**
* Listener for the previous-button.
* Switches to the previous displayed {@link Ext.ux.Wiz.Card}.
*/
onPreviousClick: function () {
if (this.currentCard > 0) {
this.cardPanel.getLayout().setActiveItem(this.currentCard - 1);
}
},

/**
* Listener for the next-button. Switches to the next {@link Ext.ux.Wiz.Card}
* if the 'beforehide'-method of it did not return false. The functionality
* for this is implemented in {@link Ext.ux.layout.CardLayout}, which is needed
* as the layout for the card-panel of this component.
*/
onNextClick: function () {
if (this.currentCard == this.cardCount - 1) {
this.onFinish();
} else {
this.cardPanel.getLayout().setActiveItem(this.currentCard + 1);
}
}
});

Ext.namespace('Ext.ux.Wiz');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz.Header
* @extends Ext.BoxComponent
*
* A specific {@link Ext.BoxComponent} that can be used to show the current process in an
* {@link Ext.ux.Wiz}.
*
* An instance of this class is usually being created by {@link Ext.ux.Wiz#initPanels} using the
* {@link Ext.ux.Wiz#headerConfig}-object.
*
* @private
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz.Header = Ext.extend(Ext.BoxComponent, {

/**
* @cfg {Number} height The height of this component. Defaults to "55".
*/
height: 55,

/**
* @cfg {String} region The Region of this component. Since a {@link Ext.ux.Wiz}
* usually uses a {@link Ext.layout.BorderLayout}, this property defaults to
* "north". If you want to change this property, you should also change the appropriate
* css-classes that are used for this component.
*/
region: 'north',

/**
* @cfg {String} title The title that gets rendered in the head of the component. This
* should be a text describing the purpose of the wizard.
*/
title: 'Wizard',

/**
* @cfg {Number} steps The overall number of steps the user has to go through
* to finish the wizard.
*/
steps: 0,

/**
* @cfg {String} stepText The text in the header indicating the current process in the wizard.
* (defaults to "Step {0} of {1}: {2}").
* {0} is replaced with the index (+1) of the current card, {1} is replaced by the
* total number of cards in the wizard and {2} is replaced with the title-property of the
* {@link Ext.ux.Wiz.Card}
* @type String
*/
stepText: "Step {0} of {1}: {2}",

/**
* @cfg {Object} autoEl The element markup used to render this component.
*/
autoEl: {
tag: 'div',
cls: 'ext-ux-wiz-Header',
children: [{
tag: 'div',
cls: 'ext-ux-wiz-Header-title'
},
{
tag: 'div',
children: [{
tag: 'div',
cls: 'ext-ux-wiz-Header-step'
},
{
tag: 'div',
cls: 'ext-ux-wiz-Header-stepIndicator-container'
}]
}]
},

/**
* @param {Ext.Element}
*/
titleEl: null,

/**
* @param {Ext.Element}
*/
stepEl: null,

/**
* @param {Ext.Element}
*/
imageContainer: null,

/**
* @param {Array}
*/
indicators: null,

/**
* @param {Ext.Template}
*/
stepTemplate: null,

/**
* @param {Number} lastActiveStep Stores the index of the last active card that
* was shown-
*/
lastActiveStep: -1,

// -------- helper
/**
* Gets called by {@link Ext.ux.Wiz#onCardShow()} and updates the header
* with the approppriate information, such as the progress of the wizard
* (i.e. which card is being shown etc.)
*
* @param {Number} currentStep The index of the card currently shown in
* the wizard
* @param {String} title The title-property of the {@link Ext.ux.Wiz.Card}
*
* @private
*/
updateStep: function (currentStep, title) {
var html = this.stepTemplate.apply({
0: currentStep + 1,
1: this.steps,
2: title
});

this.stepEl.update(html);

if (this.lastActiveStep != -1) {
this.indicators[this.lastActiveStep].removeClass('ext-ux-wiz-Header-stepIndicator-active');
}

this.indicators[currentStep].addClass('ext-ux-wiz-Header-stepIndicator-active');

this.lastActiveStep = currentStep;
},

// -------- listener
/**
* Overrides parent implementation to render this component properly.
*/
onRender: function (ct, position) {
Ext.ux.Wiz.Header.superclass.onRender.call(this, ct, position);

this.indicators = [];
this.stepTemplate = new Ext.Template(this.stepText);
this.stepTemplate.compile();

var el = this.el.dom.firstChild;
var ns = el.nextSibling;

this.titleEl = new Ext.Element(el);
this.stepEl = new Ext.Element(ns.firstChild);
this.imageContainer = new Ext.Element(ns.lastChild);

this.titleEl.update(this.title);

var image = null;
for (var i = 0, len = this.steps; i < len; i++) {
image = document.createElement('div');
image.innerHTML = " ";
image.className = 'ext-ux-wiz-Header-stepIndicator';
this.indicators[i] = new Ext.Element(image);
this.imageContainer.appendChild(image);
}
}
});

Ext.namespace('Ext.ux.Wiz');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz.Card
* @extends Ext.FormPanel
*
* A specific {@link Ext.FormPanel} that can be used as a card in a
* {@link Ext.ux.Wiz}-component. An instance of this card does only work properly
* if used in a panel that uses a {@see Ext.layout.CardLayout}-layout.
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz.Card = Ext.extend(Ext.FormPanel, {

/**
* @cfg {Boolean} header "True" to create the header element. Defaults to
* "false". See {@link Ext.form.FormPanel#header}
*/
header: false,

/**
* @cfg {Strting} hideMode Hidemode of this component. Defaults to "offsets".
* See {@link Ext.form.FormPanel#hideMode}
*/
hideMode: 'display',

initComponent: function () {
this.addEvents(
/**
* @event beforecardhide
* If you want to add additional checks to your card which cannot be easily done
* using default validators of input-fields (or using the monitorValid-config option),
* add your specific listeners to this event.
* This event gets only fired if the activeItem of the ownerCt-component equals to
* this instance of {@see Ext.ux.Wiz.Card}. This is needed since a card layout usually
* hides it's items right after rendering them, involving the beforehide-event.
* If those checks would be attached to the normal beforehide-event, the card-layout
* would never be able to hide this component after rendering it, depending on the
* listeners return value.
*
* @param {Ext.ux.Wiz.Card} card The card that triggered the event
*/
'beforecardhide');

Ext.ux.Wiz.Card.superclass.initComponent.call(this);

},

// -------- helper
isValid: function () {
if (this.monitorValid) {
return this.bindHandler();
}

return true;
},

// -------- overrides
/**
* Overrides parent implementation since we allow to add any element
* in this component which must not be neccessarily be a form-element.
* So before a call to "isValid()" is about to be made, this implementation
* checks first if the specific item sitting in this component has a method "isValid" - if it
* does not exists, it will be added on the fly.
*/
bindHandler: function () {
this.form.items.each(function (f) {
if (!f.isValid) {
f.isValid = Ext.emptyFn;
}
});

Ext.ux.Wiz.Card.superclass.bindHandler.call(this);
},

/**
* Overrides parent implementation. This is needed because in case
* this method uses "monitorValid=true", the method "startMonitoring" must
* not be called, until the "show"-event of this card fires.
*/
initEvents: function () {
var old = this.monitorValid;
this.monitorValid = false;
Ext.ux.Wiz.Card.superclass.initEvents.call(this);
this.monitorValid = old;

this.on('beforehide', this.bubbleBeforeHideEvent, this);

this.on('beforecardhide', this.isValid, this);
this.on('show', this.onCardShow, this);
this.on('hide', this.onCardHide, this);
},

// -------- listener
/**
* Checks wether the beforecardhide-event may be triggered.
*/
bubbleBeforeHideEvent: function () {
var ly = this.ownerCt.layout;
var activeItem = ly.activeItem;

if (activeItem && activeItem.id === this.id) {
return this.fireEvent('beforecardhide', this);
}

return true;
},

/**
* Stops monitoring the form elements in this component when the
* 'hide'-event gets fired.
*/
onCardHide: function () {
if (this.monitorValid) {
this.stopMonitoring();
}
},

/**
* Starts monitoring the form elements in this component when the
* 'show'-event gets fired.
*/
onCardShow: function () {
if (this.monitorValid) {
this.startMonitoring();
}
}

});

Jangla
6 Oct 2009, 9:59 AM
Awesome! I'm at home now but will have a look at your implementation method tomorrow.

charleshimmer
6 Oct 2009, 10:01 AM
Also, you'll want to make sure you include this css if you want any combo boxes to render or work correctly


.ext-safari .x-form-field-wrap .x-form-trigger{
right:inherit;
}

Jangla
7 Oct 2009, 1:32 AM
Had a look and it seems to work very well. Seem to have a lost a lot of styling though. Mainly the loss of anything to do with .x-window*

charleshimmer
7 Oct 2009, 6:30 AM
Are you still have the ext-ux-wiz.css ?

Jangla
7 Oct 2009, 6:36 AM
yup :)

charleshimmer
7 Oct 2009, 6:37 AM
Weird, I didn't (at lest think I didn't) experience any loss of styling. I did add some of my own custom styling. Are you using Ext 3.x?

Maybe submit a screenshot.

Jangla
7 Oct 2009, 7:03 AM
Yes, I'm using Ext3.

Screenshot attached.

As mentioned above though, the missing stuff appears to be x-window related which means it's not rendering correctly in the first place as x-window css is in ext-all.css. Note the lack of styling on the buttons too.

You can also see it in action here: http://www.holiday-chateau.com/index.php?id=10790

charleshimmer
7 Oct 2009, 7:16 AM
Oh I see what you're saying, since it's not a window it doesn't have the blue window-ness around it.

I don't think it would be too hard to add some css and get the same effect. I'm curious to see how it looks. I'll post the changes when I get around to it. Or if you beat me to it, do the same.

Jangla
7 Oct 2009, 7:32 AM
Beat me to it - I hadn't considered that it wasn't inside a window and the tab panel as a whole was being rendered to a div.

This may also be something to do with the issues with the styling of the buttons etc. (although I've just tried it in a window and the buttons don't improve but the wizard does....slightly!).

wemerson.januario
7 Oct 2009, 7:14 PM
nice work

charleshimmer
8 Oct 2009, 7:57 AM
As far as the buttons go, I just think that's how the new ExtJS 3.0 buttons are whether they are in a window or not. Could be wrong but in my app they all look like that.

swarm
12 Oct 2009, 12:26 AM
Hi,

Does anyone know of any issues using tbar or bbar or Ext.Toolbar inside a card that has an editor grid in it?

I've got a straightforward editorgrid on one of the cards but the toolbar doesn't display.

I've tried creating a toolbar outside the grid just to see if the toolbar worked at all and it wouldn't display anything.

I'm using ext 3.0 and FF 3.5.

Just wondering if it's something that it's designed not to handle...


Thanks guys

charleshimmer
13 Oct 2009, 9:33 AM
@Jangla

If you want your footer and buttons to look more like they did when it was a window you can add the following config items



this.setAppWiz = new Ext.ux.Wiz({
title: 'Set Appointment',
listeners: {
'deactivate': function () {
this.setAppWiz.resetWizard();
},
scope: this
},
cancelButtonText: 'Cancel Appointment',
headerConfig: {
title: 'High Probability Set Appointment Wizard',
height: 65
},
footerStyle: 'background:#ced9e7;border-top:1px solid #99BBE8;',
cardPanelConfig: {
defaults: {
baseCls: 'x-window',
bodyStyle: 'padding:20px 15px 5px 155px;background-color:#F6F6F6;border-width:0px;',
border: false
}
},
cards: [


Here is a screenshot. I think this is what you meant.

http://www.balancedlivingsystem.com/assets/files/public_share/wizard_footer.png

charleshimmer
13 Oct 2009, 9:38 AM
Also, I found that by adding the baseCls x-window to my card panels, that it fixed some of the rendering issues with combo boxes and the datepicker. This only applies if you are putting the wizard in a panel.



this.setAppWiz = new Ext.ux.Wiz({
title : 'Set Appointment',
listeners:{
'deactivate':function(){
this.setAppWiz.resetWizard();
},
scope:this
},
cancelButtonText:'Cancel Appointment',
headerConfig : {
title : 'High Probability Set Appointment Wizard',
height:65
},
footerStyle:'background:#ced9e7;border-top:1px solid #99BBE8;',
cardPanelConfig : {
defaults : {
baseCls : 'x-window',
bodyStyle : 'padding:20px 15px 5px 155px;background-color:#F6F6F6;border-width:0px;',
border : false
}
},
cards : [

anjelika
24 Oct 2009, 11:07 AM
I know there were some problems when using an upload field in the wizard (the file would not be posted on 'Next' button press, etc..).
Anyone has some experience with this? An example would be nice.
Thanks in advance.

pezze
26 Oct 2009, 2:19 AM
I know there were some problems when using an upload field in the wizard (the file would not be posted on 'Next' button press, etc..).
Anyone has some experience with this? An example would be nice.
Thanks in advance.

I'm using upload wizard without any problem. I've modified the upload code and Wizard too but I don't remember where, so here it is the code I'm using:

Ext.ux.Wiz.Wizard.js



Ext.namespace('Ext.ux');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz
* @extends Ext.Window
*
* A specific {@link Ext.Window} that models a wizard component.
* A wizard is basically a dialog that guides a user through various steps
* where he has to fill out form-data.
* A {@link Ext.ux.Wiz}-component consists typically of a {@link Ext.ux.Wiz.Header}
* and window-buttons ({@link Ext.Button}) which are linked to the {@link Ext.ux.Wiz.Card}s
* which themself represent the forms the user has to fill out.
*
* In order to switch between the cards in the wizard, you need the {@link Ext.ux.layout.CardLayout},
* which will check if an active-item can be hidden, before the requested new item will be set to
* 'active', i.e. shown. This is needed since the wizard may not allow a card to be hidden, if
* the input entered by the user was not valid. You can get this custom layout at
* {@link http://www.siteartwork.de/cardlayout}.
*
* Note:
* When data has been collected and teh "onFinish" listener triggers an AJAX-request,
* you should call the "switchDialogState" method so that the the dialog shows a loadmask.
* Once the requests finishes, call "switchDialogState" again, specially before any call
* to the "close" method of this component, otherwise the "closable" property of this
* instance might prevent a "close" operation for this dialog.
*
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz = Ext.extend(Ext.Window, {

/**
* @cfg {Object} An object containing the messages for the {@link Ext.LoadMask}
* covering the card-panel on request, whereas the property identifies the
* msg-text to show, and the value is the message text itself. Defaults to
<pre><code>
{
default : 'Saving...'
}
</code></pre>
*
* Depending on the contexts the loadMask has to be shown in (using the method
* showLoadMask of this class), the object can be configure to hold
* various messages.
<pre><code>
this.loadMaskConfig = {
default : 'Saving...',
validating : 'Please wait, validating input...',
};
// loadMask will be shown, displaying the message 'Please wait, validating input...'
this.showLoadMask(true, 'validating');
</code></pre>
*/
loadMaskConfig : {
'default' : 'Saving...'
},

/**
* @cfg {Number} height The height of the dialog. Defaults to "400".
*/
height : 400,

/**
* @cfg {Number} width The width of the dialog. Defaults to "540".
*/
width : 540,

/**
* @cfg {Boolean} closable Wether the dialog is closable. Defaults to "true".
* This property will be changed by the "switchDialogState"-method, which will
* enable/disable controls based on the passed argument. Thus, this config property
* serves two purposes: Tell the init config to render a "close"-tool, and create a
* "beforeclose"-listener which will either return true or false, indicating if the
* dialog may be closed.
*/
closable : true,

/**
* @cfg {Boolean} resizable Wether the dialog is resizable. Defaults to "false".
*/
resizable : false,

/**
* @cfg {Boolean} resizable Wether the dialog is modal. Defaults to "true".
*/
modal : true,

/**
* @cfg {Array} cards A numeric array with the configured {@link Ext.ux.Wiz.Card}s.
* The index of the cards in the array represent the order in which they get displayed
* in the wizard (i.e. card at index 0 gets displayed in the first step, card at index 1 gets
* displayed in the second step and so on).
*/
cards : null,

/**
* @cfg {String} previousButtonText The text to render the previous-button with.
* Defaults to "&lt; Back" (< Back)
*/
previousButtonText : '&lt; Previous',

/**
* @cfg {String} nextButtonText The text to render the next-button with.
* Defaults to "Next &gt;" (Next >)
*/
nextButtonText : 'Next &gt;',

/**
* @cfg {String} cancelButtonText The text to render the cancel-button with.
* Defaults to "Cancel"
*/
cancelButtonText : 'Cancel',

/**
* @cfg {String} finishButtonText The text to render the next-button with when the last
* step of the wizard is reached. Defaults to "Finish"
*/
finishButtonText : 'Finish',

/**
* @cfg {Object} headerConfig A config-object to use with {@link Ext.ux.Wiz.Header}.
* If not present, it defaults to an empty object.
*/
headerConfig : {},

/**
* @cfg {Object} cardPanelConfig A config-object to use with {@link Ext.Panel}, which
* represents the card-panel in this dialog.
* If not present, it defaults to an empty object
*/
cardPanelConfig : {},

/**
* @param {Ext.Button} The window-button for paging to the previous card.
* @private
*/
previousButton : null,

/**
* @param {Ext.Button} The window-button for paging to the next card. When the
* last card is reached, the event fired by and the text rendered to this button
* will change.
* @private
*/
nextButton : null,

/**
* @param {Ext.Button} The window-button for canceling the wizard. The event
* fired by this button will usually close the dialog.
* @private
*/
cancelButton : null,

/**
* @param {Ex.Panel} The card-panel that holds the various wizard cards
* ({@link Ext.ux.Wiz.Card}). The card-panel itself uses the custom
* {@link Ext.ux.layout.CardLayout}, which needs to be accessible by this class.
* You can get it at {@link http://www.siteartwork.de/cardlayout}.
* @private
*/
cardPanel : null,

/**
* @param {Number} currentCard The current {@link Ext.ux.Wiz.Card} displayed.
* Defaults to -1.
* @private
*/
currentCard : -1,

/**
* @param {Ext.ux.Wiz.Header} The header-panel of the wizard.
* @private
*/
headPanel : null,

/**
* @param {Number} cardCount Helper for storing the number of cards used
* by this wizard. Defaults to 0 (inherits "cards.length" later on).
* @private
*/
cardCount : 0,

/**
* Inits this component with the specified config-properties and automatically
* creates its components.
*/
initComponent : function()
{
this.initButtons();
this.initPanels();

var title = this.title || this.headerConfig.title;
title = title || "";

Ext.apply(this, {
title : title,
layout : 'border',
cardCount : this.cards.length,
buttons : [
this.previousButton,
this.nextButton,
this.cancelButton
],
items : [
this.headPanel,
this.cardPanel
]
});

this.addEvents(
/**
* @event cancel
* Fires after the cancel-button has been clicked.
* @param {Ext.ux.Wiz} this
*/
'cancel',
/**
* @event finish
* Fires after the last card was reached in the wizard and the
* next/finish-button has been clicked.
* @param {Ext.ux.Wiz} this
* @param {Object} data The collected data of the cards, whereas
* the index is the id of the card and the specific values
* are objects with key/value pairs in the form formElementName : value
*/
'finish'
);

Ext.ux.Wiz.superclass.initComponent.call(this);
},

// -------- helper
/**
* Returns the form-data of all cards in this wizard. The first index is the
* id of the card in this wizard,
* and the values are objects containing key/value pairs in the form of
* fieldName : fieldValue.
*
* @return {Array}
*/
getWizardData : function()
{
var formValues = {};
var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
if (cards[i].form) {
formValues[cards[i].id] = cards[i].form.getValues(false);
} else {
formValues[cards[i].id] = {};
}
}

return formValues;
},

/**
* Switches the state of this wizard between disabled/enabled.
* A disabled dialog will have a {@link Ext.LoadMask} covering the card-panel
* to prevent user input, and the buttons will be rendered disabled/enabled.
* If the dialog is closable, the close-tool will be masked, too, and the dialog will not
* be closable by clicking the "close" tool.
*
* @param {Boolean} enabled "false" to prevent user input and mask the elements,
* otherwise true.
* @param {String} type The type of msg for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
switchDialogState : function(enabled, type)
{
this.showLoadMask(!enabled, type);

this.previousButton.setDisabled(!enabled);
this.nextButton.setDisabled(!enabled);
this.cancelButton.setDisabled(true);

var ct = this.tools['close'];

if (ct) {
switch (enabled) {
case true:
this.tools['close'].unmask();
break;

default:
this.tools['close'].mask();
break;
}
}

this.closable = enabled;
},

/**
* Shows the load mask for this wizard. By default, the cardPanel's body
* will be masked.
*
* @param {Boolean} show true to show the load mask, otherwise false.
* @param {String} type The type of message for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
showLoadMask : function(show, type)
{
if (!type) {
type = 'default';
}

if (show) {
if (this.loadMask == null) {
this.loadMask = new Ext.LoadMask(this.body);
}
this.loadMask.msg = this.loadMaskConfig[type];
this.loadMask.show();
} else {
if (this.loadMask) {
this.loadMask.hide();
}
}
},


/**
* Inits the listener for the various {@link Ext.ux.Wiz.Card}s used
* by this component.
*/
initEvents : function()
{
Ext.ux.Wiz.superclass.initEvents.call(this);

this.on('beforeclose', this.onBeforeClose, this);

var cards = this.cards;

for (var i = 0, len = cards.length; i < len; i++) {
cards[i].on('show', this.onCardShow, this);
cards[i].on('hide', this.onCardHide, this);
cards[i].on('clientvalidation', this.onClientValidation, this);
}
},

/**
* Creates the head- and the card-panel.
* Be sure to have the custom {@link Ext.ux.layout.CardLayout} available
* in order to make the card-panel work as expected by this component
* ({@link http://www.siteartwork.de/cardlayout}).
*/
initPanels : function()
{
var cards = this.cards;
var cardPanelConfig = this.cardPanelConfig;

Ext.apply(this.headerConfig, {
steps : cards.length
});

this.headPanel = new Ext.ux.Wiz.Header(this.headerConfig);

Ext.apply(cardPanelConfig, {
layout : new Ext.ux.layout.CardLayout(),
items : cards
});

Ext.applyIf(cardPanelConfig, {
region : 'center',
border : false,
activeItem : 0
});

this.cardPanel = new Ext.Panel(cardPanelConfig);
},

/**
* Creates the instances for the the window buttons.
*/
initButtons : function()
{
this.previousButton = new Ext.Button({
text : this.previousButtonText,
disabled : true,
minWidth : 75,
handler : this.onPreviousClick,
scope : this
});

this.nextButton = new Ext.Button({
text : this.nextButtonText,
minWidth : 75,
handler : this.onNextClick,
scope : this
});

this.cancelButton = new Ext.Button({
text : this.cancelButtonText,
handler : this.onCancelClick,
scope : this,
minWidth : 75
});
},

// -------- listeners

/**
* Listener for the beforeclose event.
* This listener will return true or false based on the "closable"
* property by this component. This property will be changed by the "switchDialogState"
* method, indicating if there is currently any process running that should prevent
* this dialog from being closed.
*
* @param {Ext.Panel} panel The panel being closed
*
* @return {Boolean}
*/
onBeforeClose : function(panel)
{
return this.closable;
},

/**
* By default, the card firing this event monitors user input in a frequent
* interval and fires the 'clientvalidation'-event along with it. This listener
* will enable/disable the next/finish-button in accordance with it, based upon
* the parameter isValid. isValid" will be set by the form validation and depends
* on the validators you are using for the different input-elemnts in your form.
* If the card does not contain any forms, this listener will never be called by the
* card itself.
*
* @param {Ext.ux.Wiz.Card} The card that triggered the event.
* @param {Boolean} isValid "true", if the user input was valid, otherwise
* "false"
*/
onClientValidation : function(card, isValid)
{
if (!isValid) {
this.nextButton.setDisabled(true);
} else {
this.nextButton.setDisabled(false);
}
},

/**
* This will render the "next" button as disabled since the bindHandler's delay
* of the next card to show might be lagging on slower systems
*
*/
onCardHide : function(card)
{
if (this.cardPanel.layout.activeItem.id === card.id) {
this.nextButton.setDisabled(true);
}
},


/**
* Listener for the "show" event of the card that gets shown in the card-panel.
* Renders the next/previous buttons based on the position of the card in the wizard
* and updates the head-panel accordingly.
*
* @param {Ext.ux.Wiz.Card} The card being shown.
*/
onCardShow : function(card)
{
var parent = card.ownerCt;

var items = parent.items;

for (var i = 0, len = items.length; i < len; i++) {
if (items.get(i).id == card.id) {
break;
}
}

this.currentCard = i;
this.headPanel.updateStep(i, card.title);

if (i == len-1) {
this.nextButton.setText(this.finishButtonText);
} else {
this.nextButton.setText(this.nextButtonText);
}

if (card.isValid()) {
this.nextButton.setDisabled(false);
}

if (i == 0) {
this.previousButton.setDisabled(true);
} else {
this.previousButton.setDisabled(false);
}

},


/**
* Fires the 'cancel'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onCancelClick : function()
{
if (this.fireEvent('cancel', this) !== false) {
this.close();
}
},

/**
* Fires the 'finish'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onFinish : function()
{
if (this.fireEvent('finish', this, this.getWizardData()) !== false) {
this.close();
}
},

/**
* Listener for the previous-button.
* Switches to the previous displayed {@link Ext.ux.Wiz.Card}.
*/
onPreviousClick : function()
{
if (this.currentCard > 0) {
this.cardPanel.getLayout().setActiveItem(this.currentCard - 1);
}
},

/**
* Listener for the next-button. Switches to the next {@link Ext.ux.Wiz.Card}
* if the 'beforehide'-method of it did not return false. The functionality
* for this is implemented in {@link Ext.ux.layout.CardLayout}, which is needed
* as the layout for the card-panel of this component.
*/
onNextClick : function()
{
if (this.currentCard == this.cardCount-1) {
this.onFinish();
} else {
this.cardPanel.getLayout().setActiveItem(this.currentCard+1);
}
}
});

Ext.ux.Wiz.Card.js



Ext.namespace('Ext.ux.Wiz');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz.Card
* @extends Ext.FormPanel
*
* A specific {@link Ext.FormPanel} that can be used as a card in a
* {@link Ext.ux.Wiz}-component. An instance of this card does only work properly
* if used in a panel that uses a {@see Ext.layout.CardLayout}-layout.
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz.Card = Ext.extend(Ext.FormPanel, {

/**
* @cfg {Boolean} header "True" to create the header element. Defaults to
* "false". See {@link Ext.form.FormPanel#header}
*/
header : false,

/**
* @cfg {Strting} hideMode Hidemode of this component. Defaults to "offsets".
* See {@link Ext.form.FormPanel#hideMode}
*/
hideMode : 'display',

initComponent : function()
{
this.addEvents(
/**
* @event beforecardhide
* If you want to add additional checks to your card which cannot be easily done
* using default validators of input-fields (or using the monitorValid-config option),
* add your specific listeners to this event.
* This event gets only fired if the activeItem of the ownerCt-component equals to
* this instance of {@see Ext.ux.Wiz.Card}. This is needed since a card layout usually
* hides it's items right after rendering them, involving the beforehide-event.
* If those checks would be attached to the normal beforehide-event, the card-layout
* would never be able to hide this component after rendering it, depending on the
* listeners return value.
*
* @param {Ext.ux.Wiz.Card} card The card that triggered the event
*/
'beforecardhide'
);


Ext.ux.Wiz.Card.superclass.initComponent.call(this);

},

// -------- helper
isValid : function()
{
if (this.monitorValid) {
return this.bindHandler();
}

return true;
},
/**
* Returns the form-data of one cards in this wizard. The first index is the
* id of the current card,
* and the values are objects containing key/value pairs in the form of
* fieldName : fieldValue.
*
* @return {Array}
*/
getWizardCardData : function()
{
var formValues = {};

if (this.form) {
formValues[this.id] = this.form.getValues(false);
} else {
formValues[this.id] = {};
}

return formValues;
},

// -------- overrides

/**
* Overrides parent implementation since we allow to add any element
* in this component which must not be neccessarily be a form-element.
* So before a call to "isValid()" is about to be made, this implementation
* checks first if the specific item sitting in this component has a method "isValid" - if it
* does not exists, it will be added on the fly.
*/
bindHandler : function()
{
this.form.items.each(function(f){
if(!f.isValid){
f.isValid = Ext.emptyFn;
}
});

Ext.ux.Wiz.Card.superclass.bindHandler.call(this);
},

/**
* Overrides parent implementation. This is needed because in case
* this method uses "monitorValid=true", the method "startMonitoring" must
* not be called, until the "show"-event of this card fires.
*/
initEvents : function()
{
var old = this.monitorValid;
this.monitorValid = false;
Ext.ux.Wiz.Card.superclass.initEvents.call(this);
this.monitorValid = old;

this.on('beforehide', this.bubbleBeforeHideEvent, this);

this.on('beforecardhide', this.isValid, this);
this.on('show', this.onCardShow, this);
this.on('hide', this.onCardHide, this);
},

// -------- listener
/**
* Checks wether the beforecardhide-event may be triggered.
*/
bubbleBeforeHideEvent : function()
{
var ly = this.ownerCt.layout;
var activeItem = ly.activeItem;

if (activeItem && activeItem.id === this.id) {
return this.fireEvent('beforecardhide', this);
}

return true;
},

/**
* Stops monitoring the form elements in this component when the
* 'hide'-event gets fired.
*/
onCardHide : function()
{
if (this.monitorValid) {
this.stopMonitoring();
}
},

/**
* Starts monitoring the form elements in this component when the
* 'show'-event gets fired.
*/
onCardShow : function()
{
if (this.monitorValid) {
this.startMonitoring();
}
}

});

Ext.ux.Wiz.Header.js



Ext.namespace('Ext.ux.Wiz');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz.Header
* @extends Ext.BoxComponent
*
* A specific {@link Ext.BoxComponent} that can be used to show the current process in an
* {@link Ext.ux.Wiz}.
*
* An instance of this class is usually being created by {@link Ext.ux.Wiz#initPanels} using the
* {@link Ext.ux.Wiz#headerConfig}-object.
*
* @private
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz.Header = Ext.extend(Ext.BoxComponent, {

/**
* @cfg {Number} height The height of this component. Defaults to "55".
*/
height : 55,

/**
* @cfg {String} region The Region of this component. Since a {@link Ext.ux.Wiz}
* usually uses a {@link Ext.layout.BorderLayout}, this property defaults to
* "north". If you want to change this property, you should also change the appropriate
* css-classes that are used for this component.
*/
region : 'north',

/**
* @cfg {String} title The title that gets rendered in the head of the component. This
* should be a text describing the purpose of the wizard.
*/
title : 'Wizard',

/**
* @cfg {Number} steps The overall number of steps the user has to go through
* to finish the wizard.
*/
steps : 0,

/**
* @cfg {String} stepText The text in the header indicating the current process in the wizard.
* (defaults to "Step {0} of {1}: {2}").
* {0} is replaced with the index (+1) of the current card, {1} is replaced by the
* total number of cards in the wizard and {2} is replaced with the title-property of the
* {@link Ext.ux.Wiz.Card}
* @type String
*/
stepText : "Step {0} of {1}: {2}",

/**
* @cfg {Object} autoEl The element markup used to render this component.
*/
autoEl : {
tag : 'div',
cls : 'ext-ux-wiz-Header',
children : [{
tag : 'div',
cls : 'ext-ux-wiz-Header-title'
}, {
tag : 'div',
children : [{
tag : 'div',
cls : 'ext-ux-wiz-Header-step'
}, {
tag : 'div',
cls : 'ext-ux-wiz-Header-stepIndicator-container'
}]
}]
},

/**
* @param {Ext.Element}
*/
titleEl : null,

/**
* @param {Ext.Element}
*/
stepEl : null,

/**
* @param {Ext.Element}
*/
imageContainer : null,

/**
* @param {Array}
*/
indicators : null,

/**
* @param {Ext.Template}
*/
stepTemplate : null,

/**
* @param {Number} lastActiveStep Stores the index of the last active card that
* was shown-
*/
lastActiveStep : -1,

// -------- helper
/**
* Gets called by {@link Ext.ux.Wiz#onCardShow()} and updates the header
* with the approppriate information, such as the progress of the wizard
* (i.e. which card is being shown etc.)
*
* @param {Number} currentStep The index of the card currently shown in
* the wizard
* @param {String} title The title-property of the {@link Ext.ux.Wiz.Card}
*
* @private
*/
updateStep : function(currentStep, title)
{
var html = this.stepTemplate.apply({
0 : currentStep+1,
1 : this.steps,
2 : title
});

this.stepEl.update(html);

if (this.lastActiveStep != -1) {
this.indicators[this.lastActiveStep].removeClass('ext-ux-wiz-Header-stepIndicator-active');
}

this.indicators[currentStep].addClass('ext-ux-wiz-Header-stepIndicator-active');

this.lastActiveStep = currentStep;
},


// -------- listener
/**
* Overrides parent implementation to render this component properly.
*/
onRender : function(ct, position)
{
Ext.ux.Wiz.Header.superclass.onRender.call(this, ct, position);

this.indicators = [];
this.stepTemplate = new Ext.Template(this.stepText);
this.stepTemplate.compile();

var el = this.el.dom.firstChild;
var ns = el.nextSibling;

this.titleEl = new Ext.Element(el);
this.stepEl = new Ext.Element(ns.firstChild);
this.imageContainer = new Ext.Element(ns.lastChild);

this.titleEl.update(this.title);

var image = null;
for (var i = 0, len = this.steps; i < len; i++) {
image = document.createElement('div');
image.innerHTML = " ";
image.className = 'ext-ux-wiz-Header-stepIndicator';
this.indicators[i] = new Ext.Element(image);
this.imageContainer.appendChild(image);
}
}
});

Ext.ux.Wiz.Panel.js (use this instead of Ext.ux.Wiz.Wizard if you want to include the wizard in a panel and not in a window)


Ext.namespace('Ext.ux');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz
* @extends Ext.Window
*
* A specific {@link Ext.Window} that models a wizard component.
* A wizard is basically a dialog that guides a user through various steps
* where he has to fill out form-data.
* A {@link Ext.ux.Wiz}-component consists typically of a {@link Ext.ux.Wiz.Header}
* and window-buttons ({@link Ext.Button}) which are linked to the {@link Ext.ux.Wiz.Card}s
* which themself represent the forms the user has to fill out.
*
* In order to switch between the cards in the wizard, you need the {@link Ext.ux.layout.CardLayout},
* which will check if an active-item can be hidden, before the requested new item will be set to
* 'active', i.e. shown. This is needed since the wizard may not allow a card to be hidden, if
* the input entered by the user was not valid. You can get this custom layout at
* {@link http://www.siteartwork.de/cardlayout}.
*
* Note:
* When data has been collected and teh "onFinish" listener triggers an AJAX-request,
* you should call the "switchDialogState" method so that the the dialog shows a loadmask.
* Once the requests finishes, call "switchDialogState" again, specially before any call
* to the "close" method of this component, otherwise the "closable" property of this
* instance might prevent a "close" operation for this dialog.
*
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz = Ext.extend(Ext.Panel, {

/**
* @cfg {Object} An object containing the messages for the {@link Ext.LoadMask}
* covering the card-panel on request, whereas the property identifies the
* msg-text to show, and the value is the message text itself. Defaults to
<pre><code>
{
default : 'Saving...'
}
</code></pre>
*
* Depending on the contexts the loadMask has to be shown in (using the method
* showLoadMask of this class), the object can be configure to hold
* various messages.
<pre><code>
this.loadMaskConfig = {
default : 'Saving...',
validating : 'Please wait, validating input...',
};
// loadMask will be shown, displaying the message 'Please wait, validating input...'
this.showLoadMask(true, 'validating');
</code></pre>
*/
loadMaskConfig : {
'default' : 'Saving...'
},

/**
* @cfg {Number} height The height of the dialog. Defaults to "400".
*/
height : 400,

/**
* @cfg {Number} width The width of the dialog. Defaults to "540".
*/
width : 540,

/**
* @cfg {Boolean} closable Wether the dialog is closable. Defaults to "true".
* This property will be changed by the "switchDialogState"-method, which will
* enable/disable controls based on the passed argument. Thus, this config property
* serves two purposes: Tell the init config to render a "close"-tool, and create a
* "beforeclose"-listener which will either return true or false, indicating if the
* dialog may be closed.
*/
closable : true,

/**
* @cfg {Boolean} resizable Wether the dialog is resizable. Defaults to "false".
*/
resizable : false,

/**
* @cfg {Boolean} resizable Wether the dialog is modal. Defaults to "true".
*/
modal : true,

/**
* @cfg {Array} cards A numeric array with the configured {@link Ext.ux.Wiz.Card}s.
* The index of the cards in the array represent the order in which they get displayed
* in the wizard (i.e. card at index 0 gets displayed in the first step, card at index 1 gets
* displayed in the second step and so on).
*/
cards : null,

/**
* @cfg {String} previousButtonText The text to render the previous-button with.
* Defaults to "&lt; Back" (< Back)
*/
previousButtonText : '&lt; Previous',

/**
* @cfg {String} nextButtonText The text to render the next-button with.
* Defaults to "Next &gt;" (Next >)
*/
nextButtonText : 'Next &gt;',

/**
* @cfg {String} cancelButtonText The text to render the cancel-button with.
* Defaults to "Cancel"
*/
cancelButtonText : 'Cancel',

/**
* @cfg {String} finishButtonText The text to render the next-button with when the last
* step of the wizard is reached. Defaults to "Finish"
*/
finishButtonText : 'Finish',

/**
* @cfg {Object} headerConfig A config-object to use with {@link Ext.ux.Wiz.Header}.
* If not present, it defaults to an empty object.
*/
headerConfig : {},

/**
* @cfg {Object} cardPanelConfig A config-object to use with {@link Ext.Panel}, which
* represents the card-panel in this dialog.
* If not present, it defaults to an empty object
*/
cardPanelConfig : {},

/**
* @param {Ext.Button} The window-button for paging to the previous card.
* @private
*/
previousButton : null,

/**
* @param {Ext.Button} The window-button for paging to the next card. When the
* last card is reached, the event fired by and the text rendered to this button
* will change.
* @private
*/
nextButton : null,

/**
* @param {Ext.Button} The window-button for canceling the wizard. The event
* fired by this button will usually close the dialog.
* @private
*/
cancelButton : null,

/**
* @param {Ex.Panel} The card-panel that holds the various wizard cards
* ({@link Ext.ux.Wiz.Card}). The card-panel itself uses the custom
* {@link Ext.ux.layout.CardLayout}, which needs to be accessible by this class.
* You can get it at {@link http://www.siteartwork.de/cardlayout}.
* @private
*/
cardPanel : null,

/**
* @param {Number} currentCard The current {@link Ext.ux.Wiz.Card} displayed.
* Defaults to -1.
* @private
*/
currentCard : -1,

/**
* @param {Ext.ux.Wiz.Header} The header-panel of the wizard.
* @private
*/
headPanel : null,

/**
* @param {Number} cardCount Helper for storing the number of cards used
* by this wizard. Defaults to 0 (inherits "cards.length" later on).
* @private
*/
cardCount : 0,

/**
* Inits this component with the specified config-properties and automatically
* creates its components.
*/
initComponent : function()
{
this.initButtons();
this.initPanels();

var title = this.title || '' //this.headerConfig.title;
title = title || "";

Ext.apply(this, {
title : title,
layout : 'border',
cardCount : this.cards.length,
buttons : [
this.previousButton,
this.nextButton/*,
this.cancelButton*/
],
items : [
this.headPanel,
this.cardPanel
]
});

this.addEvents(
/**
* @event cancel
* Fires after the cancel-button has been clicked.
* @param {Ext.ux.Wiz} this
*/
'cancel',
/**
* @event finish
* Fires after the last card was reached in the wizard and the
* next/finish-button has been clicked.
* @param {Ext.ux.Wiz} this
* @param {Object} data The collected data of the cards, whereas
* the index is the id of the card and the specific values
* are objects with key/value pairs in the form formElementName : value
*/
'finish'
);

Ext.ux.Wiz.superclass.initComponent.call(this);
},

// -------- helper
/**
* Returns the form-data of all cards in this wizard. The first index is the
* id of the card in this wizard,
* and the values are objects containing key/value pairs in the form of
* fieldName : fieldValue.
*
* @return {Array}
*/
getWizardData : function()
{
var formValues = {};
var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
if (cards[i].form) {
formValues[cards[i].id] = cards[i].form.getValues(false);
} else {
formValues[cards[i].id] = {};
}
}

return formValues;
},

/**
* Switches the state of this wizard between disabled/enabled.
* A disabled dialog will have a {@link Ext.LoadMask} covering the card-panel
* to prevent user input, and the buttons will be rendered disabled/enabled.
* If the dialog is closable, the close-tool will be masked, too, and the dialog will not
* be closable by clicking the "close" tool.
*
* @param {Boolean} enabled "false" to prevent user input and mask the elements,
* otherwise true.
* @param {String} type The type of msg for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
switchDialogState : function(enabled, type)
{
this.showLoadMask(!enabled, type);

this.previousButton.setDisabled(!enabled);
this.nextButton.setDisabled(!enabled);
this.cancelButton.setDisabled(true);

var ct = this.tools['close'];

if (ct) {
switch (enabled) {
case true:
this.tools['close'].unmask();
break;

default:
this.tools['close'].mask();
break;
}
}

this.closable = enabled;
},

/**
* Shows the load mask for this wizard. By default, the cardPanel's body
* will be masked.
*
* @param {Boolean} show true to show the load mask, otherwise false.
* @param {String} type The type of message for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
showLoadMask : function(show, type)
{
if (!type) {
type = 'default';
}

if (show) {
if (this.loadMask == null) {
this.loadMask = new Ext.LoadMask(this.body);
}
this.loadMask.msg = this.loadMaskConfig[type];
this.loadMask.show();
} else {
if (this.loadMask) {
this.loadMask.hide();
}
}
},


/**
* Inits the listener for the various {@link Ext.ux.Wiz.Card}s used
* by this component.
*/
initEvents : function()
{
Ext.ux.Wiz.superclass.initEvents.call(this);

this.on('beforeclose', this.onBeforeClose, this);

var cards = this.cards;

for (var i = 0, len = cards.length; i < len; i++) {
cards[i].on('show', this.onCardShow, this);
cards[i].on('hide', this.onCardHide, this);
cards[i].on('clientvalidation', this.onClientValidation, this);
}
},

/**
* Creates the head- and the card-panel.
* Be sure to have the custom {@link Ext.ux.layout.CardLayout} available
* in order to make the card-panel work as expected by this component
* ({@link http://www.siteartwork.de/cardlayout}).
*/
initPanels : function()
{
var cards = this.cards;
var cardPanelConfig = this.cardPanelConfig;

Ext.apply(this.headerConfig, {
steps : cards.length
});

this.headPanel = new Ext.ux.Wiz.Header(this.headerConfig);

Ext.apply(cardPanelConfig, {
layout : new Ext.ux.layout.CardLayout(),
items : cards
});

Ext.applyIf(cardPanelConfig, {
region : 'center',
border : false,
activeItem : 0
});

this.cardPanel = new Ext.Panel(cardPanelConfig);
},

/**
* Creates the instances for the the window buttons.
*/
initButtons : function()
{
this.previousButton = new Ext.Button({
text : this.previousButtonText,
disabled : true,
minWidth : 75,
handler : this.onPreviousClick,
scope : this
});

this.nextButton = new Ext.Button({
text : this.nextButtonText,
minWidth : 75,
handler : this.onNextClick,
scope : this
});

this.cancelButton = new Ext.Button({
text : this.cancelButtonText,
handler : this.onCancelClick,
scope : this,
minWidth : 75
});
},

// -------- listeners

/**
* Listener for the beforeclose event.
* This listener will return true or false based on the "closable"
* property by this component. This property will be changed by the "switchDialogState"
* method, indicating if there is currently any process running that should prevent
* this dialog from being closed.
*
* @param {Ext.Panel} panel The panel being closed
*
* @return {Boolean}
*/
onBeforeClose : function(panel)
{
return this.closable;
},

/**
* By default, the card firing this event monitors user input in a frequent
* interval and fires the 'clientvalidation'-event along with it. This listener
* will enable/disable the next/finish-button in accordance with it, based upon
* the parameter isValid. isValid" will be set by the form validation and depends
* on the validators you are using for the different input-elemnts in your form.
* If the card does not contain any forms, this listener will never be called by the
* card itself.
*
* @param {Ext.ux.Wiz.Card} The card that triggered the event.
* @param {Boolean} isValid "true", if the user input was valid, otherwise
* "false"
*/
onClientValidation : function(card, isValid)
{
if (!isValid) {
this.nextButton.setDisabled(true);
} else {
this.nextButton.setDisabled(false);
}
},

/**
* This will render the "next" button as disabled since the bindHandler's delay
* of the next card to show might be lagging on slower systems
*
*/
onCardHide : function(card)
{
if (this.cardPanel.layout.activeItem.id === card.id) {
this.nextButton.setDisabled(true);
}
},


/**
* Listener for the "show" event of the card that gets shown in the card-panel.
* Renders the next/previous buttons based on the position of the card in the wizard
* and updates the head-panel accordingly.
*
* @param {Ext.ux.Wiz.Card} The card being shown.
*/
onCardShow : function(card)
{
var parent = card.ownerCt;

var items = parent.items;

for (var i = 0, len = items.length; i < len; i++) {
if (items.get(i).id == card.id) {
break;
}
}

this.currentCard = i;
this.headPanel.updateStep(i, card.title);

if (i == len-1) {
this.nextButton.setText(this.finishButtonText);
} else {
this.nextButton.setText(this.nextButtonText);
}

if (card.isValid()) {
this.nextButton.setDisabled(false);
}

if (i == 0) {
this.previousButton.setDisabled(true);
} else {
this.previousButton.setDisabled(false);
}

},


/**
* Fires the 'cancel'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onCancelClick : function()
{
if (this.fireEvent('cancel', this) !== false) {
this.close();
}
},

/**
* Fires the 'finish'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onFinish : function()
{
if (this.fireEvent('finish', this, this.getWizardData()) !== false) {
//this.close();
this.nextButton.setDisabled(true);
}
},

/**
* Listener for the previous-button.
* Switches to the previous displayed {@link Ext.ux.Wiz.Card}.
*/
onPreviousClick : function()
{
if (this.currentCard > 0) {
this.cardPanel.getLayout().setActiveItem(this.currentCard - 1);
}
},

/**
* Listener for the next-button. Switches to the next {@link Ext.ux.Wiz.Card}
* if the 'beforehide'-method of it did not return false. The functionality
* for this is implemented in {@link Ext.ux.layout.CardLayout}, which is needed
* as the layout for the card-panel of this component.
*/
onNextClick : function()
{
if (this.currentCard == this.cardCount-1) {
this.onFinish();
} else {
this.cardPanel.getLayout().setActiveItem(this.currentCard+1);
}
}
});

Ext.ux.UploadDialog.3.0.js (work with ExtJs 3.0 and Wizard Panel/Window)



/**
* This namespace should be in another file but I dicided to put it here for consistancy.
*/
Ext.namespace('Ext.ux.Utils');

/**
* This class implements event queue behaviour.
*
* @class Ext.ux.Utils.EventQueue
* @param function handler Event handler.
* @param object scope Handler scope.
*/
Ext.ux.Utils.EventQueue = function(handler, scope){
if (!handler) {
throw 'Handler is required.';
}
this.handler = handler;
this.scope = scope || window;
this.queue = [];
this.is_processing = false;

/**
* Posts event into the queue.
*
* @access public
* @param mixed event Event identificator.
* @param mixed data Event data.
*/
this.postEvent = function(event, data){
data = data || null;
this.queue.push({
event: event,
data: data
});
if (!this.is_processing) {
this.process();
}
}

this.flushEventQueue = function(){
this.queue = [];
}, /**
* @access private
*/
this.process = function(){
while (this.queue.length > 0) {
this.is_processing = true;
var event_data = this.queue.shift();
this.handler.call(this.scope, event_data.event, event_data.data);
}
this.is_processing = false;
}
}

/**
* This class implements Mili's finite state automata behaviour.
*
* Transition / output table format:
* {
* 'state_1' : {
* 'event_1' : [
* {
* p|predicate: function, // Transition predicate, optional, default to true.
* // If array then conjunction will be applyed to the operands.
* // Predicate signature is (data, event, this).
* a|action: function|array, // Transition action, optional, default to Ext.emptyFn.
* // If array then methods will be called sequentially.
* // Action signature is (data, event, this).
* s|state: 'state_x', // New state - transition destination, optional, default to
* // current state.
* scope: object // Predicate and action scope, optional, default to
* // trans_table_scope or window.
* }
* ]
* },
*
* 'state_2' : {
* ...
* }
* ...
* }
*
* @param mixed initial_state Initial state.
* @param object trans_table Transition / output table.
* @param trans_table_scope Transition / output table's methods scope.
*/
Ext.ux.Utils.FSA = function(initial_state, trans_table, trans_table_scope){
this.current_state = initial_state;
this.trans_table = trans_table ||
{};
this.trans_table_scope = trans_table_scope || window;
Ext.ux.Utils.FSA.superclass.constructor.call(this, this.processEvent, this);
}

Ext.extend(Ext.ux.Utils.FSA, Ext.ux.Utils.EventQueue, {

current_state: null,
trans_table: null,
trans_table_scope: null,

/**
* Returns current state
*
* @access public
* @return mixed Current state.
*/
state: function(){
return this.current_state;
},

/**
* @access public
*/
processEvent: function(event, data){
var transitions = this.currentStateEventTransitions(event);
if (!transitions) {
throw "State '" + this.current_state + "' has no transition for event '" + event + "'.";
}
for (var i = 0, len = transitions.length; i < len; i++) {
var transition = transitions[i];

var predicate = transition.predicate || transition.p || true;
var action = transition.action || transition.a || Ext.emptyFn;
var new_state = transition.state || transition.s || this.current_state;
var scope = transition.scope || this.trans_table_scope;

if (this.computePredicate(predicate, scope, data, event)) {
this.callAction(action, scope, data, event);
this.current_state = new_state;
return;
}
}

throw "State '" + this.current_state + "' has no transition for event '" + event + "' in current context";
},

/**
* @access private
*/
currentStateEventTransitions: function(event){
return this.trans_table[this.current_state] ? this.trans_table[this.current_state][event] || false : false;
},

/**
* @access private
*/
computePredicate: function(predicate, scope, data, event){
var result = false;

switch (Ext.type(predicate)) {
case 'function':
result = predicate.call(scope, data, event, this);
break;
case 'array':
result = true;
for (var i = 0, len = predicate.length; result && (i < len); i++) {
if (Ext.type(predicate[i]) == 'function') {
result = predicate[i].call(scope, data, event, this);
}
else {
throw ['Predicate: ', predicate[i], ' is not callable in "', this.current_state, '" state for event "', event].join('');
}
}
break;
case 'boolean':
result = predicate;
break;
default:
throw ['Predicate: ', predicate, ' is not callable in "', this.current_state, '" state for event "', event].join('');
}
return result;
},

/**
* @access private
*/
callAction: function(action, scope, data, event){
switch (Ext.type(action)) {
case 'array':
for (var i = 0, len = action.length; i < len; i++) {
if (Ext.type(action[i]) == 'function') {
action[i].call(scope, data, event, this);
}
else {
throw ['Action: ', action[i], ' is not callable in "', this.current_state, '" state for event "', event].join('');
}
}
break;
case 'function':
action.call(scope, data, event, this);
break;
default:
throw ['Action: ', action, ' is not callable in "', this.current_state, '" state for event "', event].join('');
}
}
});

// ---------------------------------------------------------------------------------------------- //

/**
* Ext.ux.UploadDialog namespace.
*/
Ext.namespace('Ext.ux.UploadDialog');

/**
* @class Ext.ux.form.BrowseButton
* @extends Ext.Button
* Ext.Button that provides a customizable file browse button.
* Clicking this button, pops up a file dialog box for a user to select the file to upload.
* This is accomplished by having a transparent <input type="file"> box above the Ext.Button.
* When a user thinks he or she is clicking the Ext.Button, they're actually clicking the hidden input "Browse..." box.
* Note: this class can be instantiated explicitly or with xtypes anywhere a regular Ext.Button can be except in 2 scenarios:
* - Panel.addButton method both as an instantiated object or as an xtype config object.
* - Panel.buttons config object as an xtype config object.
* These scenarios fail because Ext explicitly creates an Ext.Button in these cases.
* Browser compatibility:
* Internet Explorer 6:
* - no issues
* Internet Explorer 7:
* - no issues
* Internet Explorer 8:
* - no issues
* Firefox 3 - Windows:
* - pointer cursor doesn't display when hovering over the button.
* @author loeppky - based on the work done by MaximGB in Ext.ux.UploadDialog (http://extjs.com/forum/showthread.php?t=21558)
* The follow the curosr float div idea also came from MaximGB. With patches for IE8 by 4Him.
* @see http://extjs.com/forum/showthread.php?t=29032
* @constructor
* Create a new BrowseButton.
* @param {Object} config Configuration options
*/
Ext.ux.UploadDialog.BrowseButton = Ext.extend(Ext.Button, {
/*
* Config options:
*/
/**
* @cfg {String} inputFileName
* Name to use for the hidden input file DOM element. Deaults to "file".
*/
inputFileName: 'file',
/**
* @cfg {Boolean} debug
* Toggle for turning on debug mode.
* Debug mode doesn't make clipEl transparent so that one can see how effectively it covers the Ext.Button.
* In addition, clipEl is given a green background and floatEl a red background to see how well they are positioned.
*/
debug: false,


/*
* Private constants:
*/
/**
* @property FLOAT_EL_WIDTH
* @type Number
* The width (in pixels) of floatEl.
* It should be less than the width of the IE "Browse" button's width (65 pixels), since IE doesn't let you resize it.
* We define this width so we can quickly center floatEl at the mouse cursor without having to make any function calls.
* @private
*/
FLOAT_EL_WIDTH: 60,

/**
* @property FLOAT_EL_HEIGHT
* @type Number
* The heigh (in pixels) of floatEl.
* It should be less than the height of the "Browse" button's height.
* We define this height so we can quickly center floatEl at the mouse cursor without having to make any function calls.
* @private
*/
FLOAT_EL_HEIGHT: 18,


/*
* Private properties:
*/
/**
* @property buttonCt
* @type Ext.Element
* Element that contains the actual Button DOM element.
* We store a reference to it, so we can easily grab its size for sizing the clipEl.
* @private
*/
buttonCt: null,
/**
* @property clipEl
* @type Ext.Element
* Element that contains the floatEl.
* This element is positioned to fill the area of Ext.Button and has overflow turned off.
* This keeps floadEl tight to the Ext.Button, and prevents it from masking surrounding elements.
* @private
*/
clipEl: null,
/**
* @property floatEl
* @type Ext.Element
* Element that contains the inputFileEl.
* This element is size to be less than or equal to the size of the input file "Browse" button.
* It is then positioned wherever the user moves the cursor, so that their click always clicks the input file "Browse" button.
* Overflow is turned off to preven inputFileEl from masking surrounding elements.
* @private
*/
floatEl: null,
/**
* @property inputFileEl
* @type Ext.Element
* Element for the hiden file input.
* @private
*/
inputFileEl: null,
/**
* @property originalHandler
* @type Function
* The handler originally defined for the Ext.Button during construction using the "handler" config option.
* We need to null out the "handler" property so that it is only called when a file is selected.
* @private
*/
originalHandler: null,
/**
* @property originalScope
* @type Object
* The scope originally defined for the Ext.Button during construction using the "scope" config option.
* While the "scope" property doesn't need to be nulled, to be consistent with originalHandler, we do.
* @private
*/
originalScope: null,
/**
* @property BROWSERS_OFFSET
* @type Object
* The browsers specific offsets used to position the clipping element for better overlay tightness. For
* Ext 3, Ext 2 offsets are used unless there is an Ext 3 entry.
* @private
* @author 4Him
*/
BROWSERS_OFFSETS: {
Ext2: {
IE8: {left: -8, top: -16, width: 16, height: 22},
IE: {left: -8, top: -3, width: 16, height: 6},
Opera: {left: -8, top: -3, width: -18, height: -1},
Gecko: {left: -8, top: -6, width: 16, height: 10},
Safari:{left: -4, top: -2, width: 6, height: 6}
},
Ext3: {
IE8: {left: -7, width: 10},
IE: {left: -3, width: 6},
Gecko: { width: 11}
}
},
/**
* @property isExt2x
* @type boolean
* Whether we are currently using Ext 2.x
* @private
* @author 4Him
*/
isExt2x: Ext.version.match(/^2\./),


/*
* Protected Ext.Button overrides
*/
/**
* @see Ext.Button.initComponent
*/
initComponent: function(){
Ext.ux.UploadDialog.BrowseButton.superclass.initComponent.call(this);
// Store references to the original handler and scope before nulling them.
// This is done so that this class can control when the handler is called.
// There are some cases where the hidden file input browse button doesn't completely cover the Ext.Button.
// The handler shouldn't be called in these cases. It should only be called if a new file is selected on the file system.
this.originalHandler = this.handler;
this.originalScope = this.scope;
this.handler = null;
this.scope = null;
},

/**
* @see Ext.Button.onRender
*/
onRender: function(ct, position){
Ext.ux.UploadDialog.BrowseButton.superclass.onRender.call(this, ct, position); // render the Ext.Button
// Patch for compatibility with 3.x (@author 4Him, based on dario's 05-10-2009 post)
if(this.isExt2x) {
this.buttonCt = this.el.child('.x-btn-center em');
} else {
this.buttonCt = this.el.child('.x-btn-mc em');
}
this.buttonCt.position('relative'); // this is important!
var styleCfg = {
position: 'absolute',
overflow: 'hidden',
top: '0px', // default
left: '0px' // default
};
// browser specifics for better overlay tightness - modified by 4Him
for(var browser in this.BROWSERS_OFFSETS.Ext2) {
if(Ext['is'+browser]) {
Ext.apply(styleCfg, {
left: this.getBrowserOffset(browser, 'left')+'px',
top: this.getBrowserOffset(browser, 'top')+'px'
});
break;
}
}
this.clipEl = this.buttonCt.createChild({
tag: 'div',
style: styleCfg
});
this.setClipSize();
this.clipEl.on({
'mousemove': this.onButtonMouseMove,
'mouseover': this.onButtonMouseMove,
scope: this
});

this.floatEl = this.clipEl.createChild({
tag: 'div',
style: {
position: 'absolute',
width: this.FLOAT_EL_WIDTH + 'px',
height: this.FLOAT_EL_HEIGHT + 'px',
overflow: 'hidden'
}
});


if (this.debug) {
this.clipEl.applyStyles({
'background-color': 'green'
});
this.floatEl.applyStyles({
'background-color': 'red'
});
} else {
// We don't set the clipEl to be transparent, because IE 6/7 occassionaly looses mouse events for transparent elements.
// We have listeners on the clipEl that can't be lost as they're needed for realligning the input file element.
this.floatEl.setOpacity(0.0);
}

// Cover cases where someone tabs to the button:
// Listen to focus of the button so we can translate the focus to the input file el.
var buttonEl = this.el.child(this.buttonSelector);
buttonEl.on('focus', this.onButtonFocus, this);
// In IE, it's possible to tab to the text portion of the input file el.
// We want to listen to keyevents so that if a space is pressed, we "click" the input file el.
if (Ext.isIE) {
this.el.on('keydown', this.onButtonKeyDown, this);
}

this.createInputFile();
},


/*
* Private helper methods:
*/
/**
* Returns an offset based on this.BROWSERS_OFFSET
* If currently using Ext 3.x, tries to find a value for 3.x and if there is none for 3.x, it
* returns a value for 2.x
* @param {string} the desired offset. Can be one of the following: 'left', 'top', 'width', 'height'
* @param {string} browser the browser for which to return the offset
* @return {int} the desired offset
* @author 4Him
*/
getBrowserOffset: function(browser, which) {
if(!this.isExt2x && this.BROWSERS_OFFSETS.Ext3[browser] && this.BROWSERS_OFFSETS.Ext3[browser][which]) {
return this.BROWSERS_OFFSETS.Ext3[browser][which];
} else {
return this.BROWSERS_OFFSETS.Ext2[browser][which];
}
},

/**
* Sets the size of clipEl so that is covering as much of the button as possible.
* @private
*/
setClipSize: function(){
if (this.clipEl) {
var width = this.buttonCt.getWidth();
var height = this.buttonCt.getHeight();
// The button container can have a width and height of zero when it's rendered in a hidden panel.
// This is most noticable when using a card layout, as the items are all rendered but hidden,
// (unless deferredRender is set to true).
// In this case, the clip size can't be determined, so we attempt to set it later.
// This check repeats until the button container has a size.
if (width === 0 || (height === 0 && !Ext.isIE8)) { // ugly hack (Ext.isIE8) by 4Him
this.setClipSize.defer(100, this);
} else {
// Loop by 4Him
for(var browser in this.BROWSERS_OFFSETS.Ext2) {
if(Ext['is'+browser]) {
width = width + this.getBrowserOffset(browser, 'width');
height = height + this.getBrowserOffset(browser, 'height');
break;
}
}
this.clipEl.setSize(width, height);
}
}
},

/**
* Creates the input file element and adds it to inputFileCt.
* The created input file elementis sized, positioned, and styled appropriately.
* Event handlers for the element are set up, and a tooltip is applied if defined in the original config.
* @private
*/
createInputFile: function(){
// When an input file gets detached and set as the child of a different DOM element,
// straggling <em> elements get left behind.
// I don't know why this happens but we delete any <em> elements we can find under the floatEl to prevent a memory leak.
this.floatEl.select('em').each(function(el){
el.remove();
});
this.inputFileEl = this.floatEl.createChild({
tag: 'input',
type: 'file',
size: 1, // must be > 0. It's value doesn't really matter due to our masking div (inputFileCt).
name: this.inputFileName || Ext.id(this.el),
tabindex: this.tabIndex,
// Use the same pointer as an Ext.Button would use. This doesn't work in Firefox.
// This positioning right-aligns the input file to ensure that the "Browse" button is visible.
style: {
position: 'absolute',
cursor: 'pointer',
right: '0px',
top: '0px'
}
});
this.inputFileEl = this.inputFileEl.child('input') || this.inputFileEl;
// IE8 needs opacity on the 'file input' element - @author 4Him
if(Ext.isIE8) {
this.inputFileEl.setOpacity(0.0);
}

// setup events
this.inputFileEl.on({
'click': this.onInputFileClick,
'change': this.onInputFileChange,
'focus': this.onInputFileFocus,
'select': this.onInputFileFocus,
'blur': this.onInputFileBlur,
scope: this
});

// add a tooltip
if (this.tooltip) {
if (typeof this.tooltip == 'object') {
Ext.QuickTips.register(Ext.apply({
target: this.inputFileEl
}, this.tooltip));
} else {
this.inputFileEl.dom[this.tooltipType] = this.tooltip;
}
}
},

/**
* Redirecting focus to the input file element so the user can press space and select files.
* @param {Event} e focus event.
* @private
*/
onButtonFocus: function(e){
if (this.inputFileEl) {
this.inputFileEl.focus();
e.stopEvent();
}
},

/**
* Handler for the IE case where once can tab to the text box of an input file el.
* If the key is a space, we simply "click" the inputFileEl.
* @param {Event} e key event.
* @private
*/
onButtonKeyDown: function(e){
if (this.inputFileEl && e.getKey() == Ext.EventObject.SPACE) {
this.inputFileEl.dom.click();
e.stopEvent();
}
},

/**
* Handler when the cursor moves over the clipEl.
* The floatEl gets centered to the cursor location.
* @param {Event} e mouse event.
* @private
*/
onButtonMouseMove: function(e){
var xy = e.getXY();
xy[0] -= this.FLOAT_EL_WIDTH / 2;
xy[1] -= this.FLOAT_EL_HEIGHT / 2;
this.floatEl.setXY(xy);
},

/**
* Add the visual enhancement to the button when the input file recieves focus.
* This is the tip for the user that now he/she can press space to select the file.
* @private
*/
onInputFileFocus: function(e){
if (!this.isDisabled) {
this.el.addClass("x-btn-over");
}
},

/**
* Removes the visual enhancement from the button.
* @private
*/
onInputFileBlur: function(e){
this.el.removeClass("x-btn-over");
},

/**
* Handler when inputFileEl's "Browse..." button is clicked.
* @param {Event} e click event.
* @private
*/
onInputFileClick: function(e){
e.stopPropagation();
},

/**
* Handler when inputFileEl changes value (i.e. a new file is selected).
* @private
*/
onInputFileChange: function(){
if (this.originalHandler) {
this.originalHandler.call(this.originalScope, this);
}
},


/*
* Public methods:
*/
/**
* Detaches the input file associated with this BrowseButton so that it can be used for other purposed (e.g. uplaoding).
* The returned input file has all listeners and tooltips applied to it by this class removed.
* @param {Boolean} whether to create a new input file element for this BrowseButton after detaching.
* True will prevent creation. Defaults to false.
* @return {Ext.Element} the detached input file element.
*/
detachInputFile: function(noCreate){
var result = this.inputFileEl;

if (typeof this.tooltip == 'object') {
Ext.QuickTips.unregister(this.inputFileEl);
} else {
this.inputFileEl.dom[this.tooltipType] = null;
}
this.inputFileEl.removeAllListeners();
this.inputFileEl = null;

if (!noCreate) {
this.createInputFile();
}
return result;
},

/**
* @return {Ext.Element} the input file element attached to this BrowseButton.
*/
getInputFile: function(){
return this.inputFileEl;
},

/**
* @see Ext.Button.disable
*/
disable: function(){
Ext.ux.UploadDialog.BrowseButton.superclass.disable.call(this);
this.inputFileEl.dom.disabled = true;
},

/**
* @see Ext.Button.enable
*/
enable: function(){
Ext.ux.UploadDialog.BrowseButton.superclass.enable.call(this);
this.inputFileEl.dom.disabled = false;
}
});


/**
* Toolbar file upload browse button.
*
* @class Ext.ux.UploadDialog.TBBrowseButton
*/
Ext.ux.UploadDialog.TBBrowseButton = Ext.extend(Ext.ux.UploadDialog.BrowseButton, {
hideParent: true,

onDestroy: function(){
Ext.ux.UploadDialog.TBBrowseButton.superclass.onDestroy.call(this);
if (this.container) {
this.container.remove();
}
}
});

/**
* Record type for dialogs grid.
*
* @class Ext.ux.UploadDialog.FileRecord
*/
Ext.ux.UploadDialog.FileRecord = Ext.data.Record.create([{
name: 'filename'
}, {
name: 'state',
type: 'int'
}, {
name: 'note'
}, {
name: 'input_element'
}, {
name: 'filetype'
}]);

Ext.ux.UploadDialog.FileRecord.STATE_QUEUE = 0;
Ext.ux.UploadDialog.FileRecord.STATE_FINISHED = 1;
Ext.ux.UploadDialog.FileRecord.STATE_FAILED = 2;
Ext.ux.UploadDialog.FileRecord.STATE_PROCESSING = 3;

/**
* Dialog class.
*
* @class Ext.ux.UploadDialog.Dialog
*/
Ext.ux.UploadDialog.Dialog = function(config){
var default_config = {
//autoScroll : false,
border: false,
width: 450,
height: 300,
minWidth: 450,
minHeight: 300,
plain: true,
constrainHeader: true,
draggable: true,
closable: true,
maximizable: false,
minimizable: false,
resizable: true,
autoDestroy: true,
closeAction: 'hide',
title: this.i18n.title,
cls: 'ext-ux-uploaddialog-dialog',
// --------
url: '',
base_params: {},
permitted_extensions: [],
reset_on_hide: true,
allow_close_on_upload: false,
upload_autostart: false,
post_var_name: 'file'
}
config = Ext.applyIf(config ||
{}, default_config);
config.layout = 'absolute';

Ext.ux.UploadDialog.Dialog.superclass.constructor.call(this, config);
}

Ext.extend(Ext.ux.UploadDialog.Dialog, Ext.Window, {

fsa: null,

state_tpl: null,

form: null,

grid_panel: null,

progress_bar: null,

is_uploading: false,

initial_queued_count: 0,

upload_frame: null,

/**
* @access private
*/
//--------------------------------------------------------------------------------------------- //
initComponent: function(){
Ext.ux.UploadDialog.Dialog.superclass.initComponent.call(this);

// Setting automata protocol
var tt = {
// --------------
'created': {
// --------------
'window-render': [{
action: [this.createForm, this.createProgressBar, this.createGrid],
state: 'rendering'
}],
'destroy': [{
action: this.flushEventQueue,
state: 'destroyed'
}]
},
// --------------
'rendering': {
// --------------
'grid-render': [{
action: [this.fillToolbar/*, this.updateToolbar JYJ*/],
state: 'ready'
}],
'destroy': [{
action: this.flushEventQueue,
state: 'destroyed'
}]
},
// --------------
'ready': {
// --------------
'file-selected': [{
predicate: [this.fireFileTestEvent, this.isPermittedFile],
action: this.addFileToUploadQueue,
state: 'adding-file'
}, {
// If file is not permitted then resetting internal input type file.
action: this.resetAddButton
}],
'grid-selection-change': [{
action: this.updateToolbar
}],
'remove-files': [{
action: [this.removeFiles, this.fireFileRemoveEvent]
}],
'reset-queue': [{
action: [this.resetQueue, this.fireResetQueueEvent]
}],
'start-upload': [{
predicate: this.hasUnuploadedFiles,
action: [this.setUploadingFlag, this.saveInitialQueuedCount, this.updateToolbar, this.updateProgressBar, this.prepareNextUploadTask, this.fireUploadStartEvent],
state: 'uploading'
}, { // Has nothing to upload, do nothing.
}],
'stop-upload': [{ // We are not uploading, do nothing. Can be posted by user only at this state.
}],
'hide': [{
predicate: [this.isNotEmptyQueue, this.getResetOnHide],
action: [this.resetQueue, this.fireResetQueueEvent]
}, { // Do nothing
}],
'destroy': [{
action: this.flushEventQueue,
state: 'destroyed'
}]
},
// --------------
'adding-file': {
// --------------
'file-added': [{
predicate: this.isUploading,
action: [this.incInitialQueuedCount, this.updateProgressBar, this.fireFileAddEvent],
state: 'uploading'
}, {
predicate: this.getUploadAutostart,
action: [this.startUpload, this.fireFileAddEvent],
state: 'ready'
}, {
action: [this.updateToolbar, this.fireFileAddEvent],
state: 'ready'
}]
},
// --------------
'uploading': {
// --------------
'file-selected': [{
predicate: [this.fireFileTestEvent, this.isPermittedFile],
action: this.addFileToUploadQueue,
state: 'adding-file'
}, {
// If file is not permitted then resetting internal input type file.
action: this.resetAddButton
}],
'grid-selection-change': [{ // Do nothing.
}],
'start-upload': [{ // Can be posted only by user in this state.
}],
'stop-upload': [{
predicate: this.hasUnuploadedFiles,
action: [this.resetUploadingFlag, this.abortUpload, this.updateToolbar, this.updateProgressBar, this.fireUploadStopEvent],
state: 'ready'
}, {
action: [this.resetUploadingFlag, this.abortUpload, this.updateToolbar, this.updateProgressBar, this.fireUploadStopEvent, this.fireUploadCompleteEvent],
state: 'ready'
}],
'file-upload-start': [{
predicate: this.fireBeforeFileUploadStartEvent,
action: [this.uploadFile, this.findUploadFrame, this.fireFileUploadStartEvent]
}, {
action: this.postFileUploadCancelEvent
}],
'file-upload-success': [{
predicate: this.hasUnuploadedFiles,
action: [this.resetUploadFrame, this.updateRecordState, this.updateProgressBar, this.prepareNextUploadTask, this.fireUploadSuccessEvent]
}, {
action: [this.resetUploadFrame, this.resetUploadingFlag, this.updateRecordState, this.updateToolbar, this.updateProgressBar, this.fireUploadSuccessEvent, this.fireUploadCompleteEvent],
state: 'ready'
}],
'file-upload-error': [{
predicate: this.hasUnuploadedFiles,
action: [this.resetUploadFrame, this.updateRecordState, this.updateProgressBar, this.prepareNextUploadTask, this.fireUploadErrorEvent]
}, {
action: [this.resetUploadFrame, this.resetUploadingFlag, this.updateRecordState, this.updateToolbar, this.updateProgressBar, this.fireUploadErrorEvent, this.fireUploadCompleteEvent],
state: 'ready'
}],
'file-upload-failed': [{
predicate: this.hasUnuploadedFiles,
action: [this.resetUploadFrame, this.updateRecordState, this.updateProgressBar, this.prepareNextUploadTask, this.fireUploadFailedEvent]
}, {
action: [this.resetUploadFrame, this.resetUploadingFlag, this.updateRecordState, this.updateToolbar, this.updateProgressBar, this.fireUploadFailedEvent, this.fireUploadCompleteEvent],
state: 'ready'
}],
'file-upload-canceled': [{
predicate: this.hasUnuploadedFiles,
action: [this.setRecordCanceledState, this.updateProgressBar, this.prepareNextUploadTask, this.fireUploadCanceledEvent]
}, {
action: [this.resetUploadingFlag, this.setRecordCanceledState, this.updateToolbar, this.updateProgressBar, this.fireUploadCanceledEvent, this.fireUploadCompleteEvent],
state: 'ready'
}],
'hide': [{
predicate: this.getResetOnHide,
action: [this.stopUpload, this.repostHide]
}, { // Do nothing.
}],
'destroy': [{
predicate: this.hasUnuploadedFiles,
action: [this.resetUploadingFlag, this.abortUpload, this.fireUploadStopEvent, this.flushEventQueue],
state: 'destroyed'
}, {
action: [this.resetUploadingFlag, this.abortUpload, this.fireUploadStopEvent, this.fireUploadCompleteEvent, this.flushEventQueue],
state: 'destroyed'
}]
},
// --------------
'destroyed': { // --------------
}
}
this.fsa = new Ext.ux.Utils.FSA('created', tt, this);

// Registering dialog events.
this.addEvents({
'filetest': true,
'fileadd': true,
'fileremove': true,
'resetqueue': true,
'uploadsuccess': true,
'uploaderror': true,
'uploadfailed': true,
'uploadcanceled': true,
'uploadstart': true,
'uploadstop': true,
'uploadcomplete': true,
'beforefileuploadstart': true,
'fileuploadstart': true
});

// Attaching to window events.
this.on('render', this.onWindowRender, this);
this.on('beforehide', this.onWindowBeforeHide, this);
this.on('hide', this.onWindowHide, this);
this.on('destroy', this.onWindowDestroy, this);

// Compiling state template.
this.state_tpl = new Ext.Template("<div class='ext-ux-uploaddialog-state ext-ux-uploaddialog-state-{state}'> </div>").compile();

this.filetype_tpl = new Ext.Template("<div class='ext-ux-uploaddialog-filetype file-{filetype}'> </div>").compile();

},

createForm: function(){
this.form = Ext.DomHelper.append(this.body, {
tag: 'form',
method: 'post',
action: this.url,
style: 'position: absolute; left: -100px; top: -100px; width: 100px; height: 100px'
});
},

createProgressBar: function(){
this.progress_bar = this.add(new Ext.ProgressBar({
x: 0,
y: 0,
anchor: '0',
value: 0.0,
text: this.i18n.progress_waiting_text
}));
},

createGrid: function(){
var store = new Ext.data.Store({
proxy: new Ext.data.MemoryProxy([]),
reader: new Ext.data.JsonReader({}, Ext.ux.UploadDialog.FileRecord),
sortInfo: {
field: 'state',
direction: 'DESC'
},
pruneModifiedRecords: true
});

var cm = new Ext.grid.ColumnModel([ //new Ext.grid.RowNumberer(),

{
header: this.i18n.state_col_title,
width: this.i18n.state_col_width,
resizable: false,
dataIndex: 'state',
sortable: true,
width: 40,
renderer: this.renderStateCell.createDelegate(this)
}, {
header: this.i18n.filetype_col_title,
width: this.i18n.filetype_col_width,
resizable: false,
dataIndex: 'filetype',
sortable: true,
width: 40,
renderer: this.renderFiletypeCell.createDelegate(this)
}, {
header: this.i18n.filename_col_title,
width: this.i18n.filename_col_width,
dataIndex: 'filename',
sortable: true,
width: 100,
renderer: this.renderFilenameCell.createDelegate(this)
}, {
id: 'note',
header: this.i18n.note_col_title,
width: this.i18n.note_col_width,
dataIndex: 'note',
sortable: true,
width: 100,
renderer: this.renderNoteCell.createDelegate(this)
}]);

this.grid_panel = new Ext.grid.GridPanel({
ds: store,
cm: cm,

x: 0,
y: 22,
anchor: '0 0',
border: true,

cls: 'ext-ux-uploaddialog',
//stripeRows : true,
//autoScroll : true,
//width:350,
//height:500,
autoExpandColumn: 'note',

viewConfig: {
autoFill: true,
forceFit: true
},


bbar: new Ext.Toolbar()
});
this.grid_panel.on('render', this.onGridRender, this);

this.add(this.grid_panel);

this.grid_panel.getSelectionModel().on('selectionchange', this.onGridSelectionChange, this);
},

fillToolbar: function(){
var tb = this.grid_panel.getBottomToolbar();
tb.x_buttons = {}

tb.x_buttons.add = tb.addItem(new Ext.ux.UploadDialog.TBBrowseButton({
input_name: this.post_var_name,
text: this.i18n.add_btn_text,
tooltip: this.i18n.add_btn_tip,
iconCls: 'ext-ux-uploaddialog-addbtn',
handler: this.onAddButtonFileSelected,
scope: this
}));

tb.x_buttons.remove = tb.addButton({
text: this.i18n.remove_btn_text,
tooltip: this.i18n.remove_btn_tip,
iconCls: 'ext-ux-uploaddialog-removebtn',
handler: this.onRemoveButtonClick,
scope: this
});

tb.x_buttons.reset = tb.addButton({
text: this.i18n.reset_btn_text,
tooltip: this.i18n.reset_btn_tip,
iconCls: 'ext-ux-uploaddialog-resetbtn',
handler: this.onResetButtonClick,
scope: this
});

tb.add('-');

tb.x_buttons.upload = tb.addButton({
text: this.i18n.upload_btn_start_text,
tooltip: this.i18n.upload_btn_start_tip,
iconCls: 'ext-ux-uploaddialog-uploadstartbtn',
handler: this.onUploadButtonClick,
scope: this
});

tb.add('-');

/* JYJ
var indicElt = Ext.DomHelper.append(tb.getEl(), {
tag: 'div',
cls: 'ext-ux-uploaddialog-indicator-stoped',
html: ' '
});*/
var indicElt = new Ext.BoxComponent({
autoEl: {
tag: 'div',
cls: 'ext-ux-uploaddialog-indicator-stoped',
html: ' '
}
});
tb.x_buttons.indicator = tb.addItem(new Ext.Toolbar.Item(indicElt));

tb.add('->');

tb.x_buttons.close = tb.addButton({
text: this.i18n.close_btn_text,
tooltip: this.i18n.close_btn_tip,
handler: this.onCloseButtonClick,
scope: this
});
},

renderFiletypeCell: function(data, cell, record, row_index, column_index, store){
//return 'toto'; //this.state_tpl.apply({state: data});
return this.filetype_tpl.apply({
filetype: data
});
},

renderStateCell: function(data, cell, record, row_index, column_index, store){
return this.state_tpl.apply({
state: data
});
},

renderFilenameCell: function(data, cell, record, row_index, column_index, store){
var view = this.grid_panel.getView();
var f = function(){
try {
Ext.fly(view.getCell(row_index, column_index)).child('.x-grid3-cell-inner').dom['qtip'] = data;
}
catch (e) {
}
}
f.defer(1000);
return data;
},

renderNoteCell: function(data, cell, record, row_index, column_index, store){
var view = this.grid_panel.getView();
var f = function(){
try {
Ext.fly(view.getCell(row_index, column_index)).child('.x-grid3-cell-inner').dom['qtip'] = data;
}
catch (e) {
}
}
f.defer(1000);
return data;
},

getFileExtension: function(filename){
var result = null;
var parts = filename.split('.');
if (parts.length > 1) {
result = parts.pop();
}
return result;
},

isPermittedFileType: function(filename){
var result = true;
if (this.permitted_extensions.length > 0) {
result = this.permitted_extensions.indexOf(this.getFileExtension(filename)) != -1;
}
return result;
},

isPermittedFile: function(browse_btn){
var result = false;
var filename = browse_btn.getInputFile().dom.value;

if (this.isPermittedFileType(filename)) {
result = true;
}
else {
Ext.Msg.alert(this.i18n.error_msgbox_title, String.format(this.i18n.err_file_type_not_permitted, filename, this.permitted_extensions.join(this.i18n.permitted_extensions_join_str)));
result = false;
}

return result;
},

fireFileTestEvent: function(browse_btn){
return this.fireEvent('filetest', this, browse_btn.getInputFile().dom.value) !== false;
},

addFileToUploadQueue: function(browse_btn){
var input_file = browse_btn.detachInputFile();

input_file.appendTo(this.form);
input_file.setStyle('width', '100px');
input_file.dom.disabled = true;

var filetype = this.getFileExtension(input_file.dom.value);

var store = this.grid_panel.getStore();
store.add(new Ext.ux.UploadDialog.FileRecord({
state: Ext.ux.UploadDialog.FileRecord.STATE_QUEUE,
filename: input_file.dom.value,
note: this.i18n.note_queued_to_upload,
input_element: input_file,
filetype: filetype
}));
this.fsa.postEvent('file-added', input_file.dom.value);
},

fireFileAddEvent: function(filename){
this.fireEvent('fileadd', this, filename);
},

updateProgressBar: function(){
if (this.is_uploading) {
var queued = this.getQueuedCount(true);
var value = 1 - queued / this.initial_queued_count;
this.progress_bar.updateProgress(value, String.format(this.i18n.progress_uploading_text, this.initial_queued_count - queued, this.initial_queued_count));
}
else {
this.progress_bar.updateProgress(0, this.i18n.progress_waiting_text);
}
},

updateToolbar: function(){
var tb = this.grid_panel.getBottomToolbar();
if (this.is_uploading) {
tb.x_buttons.remove.disable();
tb.x_buttons.reset.disable();
tb.x_buttons.upload.enable();
if (!this.getAllowCloseOnUpload()) {
tb.x_buttons.close.disable();
}
Ext.fly(tb.x_buttons.indicator.getEl()).replaceClass('ext-ux-uploaddialog-indicator-stoped', 'ext-ux-uploaddialog-indicator-processing');
tb.x_buttons.upload.setIconClass('ext-ux-uploaddialog-uploadstopbtn');
tb.x_buttons.upload.setText(this.i18n.upload_btn_stop_text);
tb.x_buttons.upload.getEl().child(tb.x_buttons.upload.buttonSelector).dom[tb.x_buttons.upload.tooltipType] = this.i18n.upload_btn_stop_tip;
}
else {
tb.x_buttons.remove.enable();
tb.x_buttons.reset.enable();
tb.x_buttons.close.enable();
Ext.fly(tb.x_buttons.indicator.getEl()).replaceClass('ext-ux-uploaddialog-indicator-processing', 'ext-ux-uploaddialog-indicator-stoped');
tb.x_buttons.upload.setIconClass('ext-ux-uploaddialog-uploadstartbtn');
tb.x_buttons.upload.setText(this.i18n.upload_btn_start_text);
tb.x_buttons.upload.getEl().child(tb.x_buttons.upload.buttonSelector).dom[tb.x_buttons.upload.tooltipType] = this.i18n.upload_btn_start_tip;

if (this.getQueuedCount() > 0) {
tb.x_buttons.upload.enable();
}
else {
tb.x_buttons.upload.disable();
}

if (this.grid_panel.getSelectionModel().hasSelection()) {
tb.x_buttons.remove.enable();
}
else {
tb.x_buttons.remove.disable();
}

if (this.grid_panel.getStore().getCount() > 0) {
tb.x_buttons.reset.enable();
}
else {
tb.x_buttons.reset.disable();
}
}
},

saveInitialQueuedCount: function(){
this.initial_queued_count = this.getQueuedCount();
},

incInitialQueuedCount: function(){
this.initial_queued_count++;
},

setUploadingFlag: function(){
this.is_uploading = true;
},

resetUploadingFlag: function(){
this.is_uploading = false;
},

prepareNextUploadTask: function(){
// Searching for first unuploaded file.
var store = this.grid_panel.getStore();
var record = null;

store.each(function(r){
if (!record && r.get('state') == Ext.ux.UploadDialog.FileRecord.STATE_QUEUE) {
record = r;
}
else {
r.get('input_element').dom.disabled = true;
}
});

record.get('input_element').dom.disabled = false;
record.set('state', Ext.ux.UploadDialog.FileRecord.STATE_PROCESSING);
record.set('note', this.i18n.note_processing);
record.commit();

this.fsa.postEvent('file-upload-start', record);
},

fireUploadStartEvent: function(){
this.fireEvent('uploadstart', this);
},

removeFiles: function(file_records){
var store = this.grid_panel.getStore();
for (var i = 0, len = file_records.length; i < len; i++) {
var r = file_records[i];
r.get('input_element').remove();
store.remove(r);
}
},

fireFileRemoveEvent: function(file_records){
for (var i = 0, len = file_records.length; i < len; i++) {
this.fireEvent('fileremove', this, file_records[i].get('filename'), file_records[i]);
}
},

resetQueue: function(){
var store = this.grid_panel.getStore();
store.each(function(r){
r.get('input_element').remove();
});
store.removeAll();
},

fireResetQueueEvent: function(){
this.fireEvent('resetqueue', this);
},

uploadFile: function(record){
Ext.Ajax.request({
url: this.url,
params: this.base_params || this.baseParams || this.params,
method: 'POST',
form: this.form,
isUpload: true,
success: this.onAjaxSuccess,
failure: this.onAjaxFailure,
scope: this,
record: record
});
},

fireBeforeFileUploadStartEvent: function(record){
return this.fireEvent('beforefileuploadstart', this, record.get('filename'), record) !== false;
},

postFileUploadCancelEvent: function(record){
this.fsa.postEvent('file-upload-canceled', record);
},

setRecordCanceledState: function(record){
record.set('state', Ext.ux.UploadDialog.FileRecord.STATE_FAILED);
record.set('note', this.i18n.note_canceled);
record.commit();
},

fireUploadCanceledEvent: function(record){
this.fireEvent('uploadcanceled', this, record.get('filename'), record);
},

fireFileUploadStartEvent: function(record){
this.fireEvent('fileuploadstart', this, record.get('filename'), record);
},

/*
* echo json_encode(array('success'=>$success, 'error'=>$msg, 'error_num'=>$retCode, 'param'=>$extra));
*/
updateRecordState: function(data){
if ('success' in data.response && data.response.success) {
data.record.set('state', Ext.ux.UploadDialog.FileRecord.STATE_FINISHED);
/*var msg = '';
if (this.i18n.note_upload_success.indexOf('{0}')>0) {
msg = String.format(this.i18n.note_upload_success, data.response.message || data.response.error);
} else {
msg = data.response.message || data.response.error || this.i18n.note_upload_success ;
}
data.record.set(
'note', msg
);*/
}
else {
data.record.set('state', Ext.ux.UploadDialog.FileRecord.STATE_FAILED);
/*var msg = '';
if (this.i18n.note_upload_error.indexOf('{0}')>0) {
msg = String.format(this.i18n.note_upload_error, data.response.message || data.response.error);
} else {
msg = data.response.message || data.response.error || this.i18n.note_upload_error ;
}
data.record.set(
'note', msg
);*/
}
data.record.set('note', this.makeNoteMessage(data.response));

data.record.commit();
},

formatFileSize: function(s){
return Ext.util.Format.fileSize(s);
},

makeNoteMessage: function(response){
var msg;
var model = null;
if (!response.success) {
if (response.error_num) {
switch (response.error_num) {
case 100: //define ("ERROR_UNKNOWN", 100);
model = this.i18n.note_upload_error;
break;
case 101: //define ("ERROR_FILESIZE_EXCEED_PHP_LIMIT", 101);
model = this.i18n.note_upload_error_filesize_exceed_php_limit;
break;
case 102: //define ("ERROR_FILESIZE_EXCEED_PHP_FORM_LIMIT", 102);
model = this.i18n.note_upload_error_filesize_exceed_php_form_limit;
break;
case 103: //define ("ERROR_FILESIZE_EXCEED_LIMIT", 103);
model = this.i18n.note_upload_error_filesize_exceed_limit;
break;
case 104: //define ("ERROR_UPLOADIR_DOES_NOT_EXIST", 104);
model = this.i18n.note_upload_error_uploadir_does_not_exist;
break;
case 105: //define ("ERROR_UPLOADIR_NOT_WRITABLE", 105);
model = this.i18n.note_upload_error_uploadir_not_writable;
break;
case 106: //define ("ERROR_FILE_PARTIALLY_UPLOADED", 106);
model = this.i18n.note_upload_error_file_partially_loaded;
break;
case 107: //define ("ERROR_FILE_NOT_UPLOADED", 107);
model = this.i18n.note_upload_error_file_not_uploaded;
break;
default:
model = this.i18n.note_upload_error;
break;
}
if (model && model.indexOf('{0}') > 0 && response.param) {
msg = String.format(model, response.param);
}
else {
msg = model;
}
}
else {
msg = response.message || response.error || this.i18n.note_upload_error;
}
}
else {
if (this.i18n.note_upload_success.indexOf('{0}') > 0 && response.param) {
var size = this.formatFileSize(response.param);
msg = String.format(this.i18n.note_upload_success, size);
}
else {
msg = response.message || this.i18n.note_upload_success;
}
}
return msg;
},

fireUploadSuccessEvent: function(data){
this.fireEvent('uploadsuccess', this, data.record.get('filename'), data.response, data.record);
},

fireUploadErrorEvent: function(data){
this.fireEvent('uploaderror', this, data.record.get('filename'), data.response, data.record);
},

fireUploadFailedEvent: function(data){
this.fireEvent('uploadfailed', this, data.record.get('filename'), data.record);
},

fireUploadCompleteEvent: function(){
this.fireEvent('uploadcomplete', this);
},

findUploadFrame: function(){
this.upload_frame = Ext.getBody().child('iframe.x-hidden:last');
},

resetUploadFrame: function(){
this.upload_frame = null;
},

removeUploadFrame: function(){
if (this.upload_frame) {
this.upload_frame.removeAllListeners();
this.upload_frame.dom.src = 'about:blank';
this.upload_frame.remove();
}
this.upload_frame = null;
},

abortUpload: function(){
this.removeUploadFrame();

var store = this.grid_panel.getStore();
var record = null;
store.each(function(r){
if (r.get('state') == Ext.ux.UploadDialog.FileRecord.STATE_PROCESSING) {
record = r;
return false;
}
});

record.set('state', Ext.ux.UploadDialog.FileRecord.STATE_FAILED);
record.set('note', this.i18n.note_aborted);
record.commit();
},

fireUploadStopEvent: function(){
this.fireEvent('uploadstop', this);
},

repostHide: function(){
this.fsa.postEvent('hide');
},

flushEventQueue: function(){
this.fsa.flushEventQueue();
},

resetAddButton: function(browse_btn){
browse_btn.detachInputFile();
},

/**
* @access private
*/
// -------------------------------------------------------------------------------------------- //
onWindowRender: function(){
this.fsa.postEvent('window-render');
},

onWindowBeforeHide: function(){
return this.isUploading() ? this.getAllowCloseOnUpload() : true;
},

onWindowHide: function(){
this.fsa.postEvent('hide');
},

onWindowDestroy: function(){
this.fsa.postEvent('destroy');
},

onGridRender: function(){
this.fsa.postEvent('grid-render');
},

onGridSelectionChange: function(){
this.fsa.postEvent('grid-selection-change');
},

onAddButtonFileSelected: function(btn){
this.fsa.postEvent('file-selected', btn);
},

onUploadButtonClick: function(){
if (this.is_uploading) {
this.fsa.postEvent('stop-upload');
}
else {
this.fsa.postEvent('start-upload');
}
},

onRemoveButtonClick: function(){
var selections = this.grid_panel.getSelectionModel().getSelections();
this.fsa.postEvent('remove-files', selections);
},

onResetButtonClick: function(){
this.fsa.postEvent('reset-queue');
},

onCloseButtonClick: function(){
this[this.closeAction].call(this);
},

/*
* echo json_encode(array('success'=>$success, 'error'=>$msg, 'error_num'=>$retCode, 'param'=>$extra));
*/
onAjaxSuccess: function(response, options){
var json_response = {
'success': false,
'error': this.i18n.note_upload_error
}
try {
var rt = response.responseText;
var filter = rt.match(/^<[^>]+>((?:.|\n)*)<\/[^>]+>$/);
if (filter) {
rt = filter[1];
}
json_response = Ext.util.JSON.decode(rt);
}
catch (e) {
}

var data = {
record: options.record,
response: json_response
}

if ('success' in json_response && json_response.success) {
this.fsa.postEvent('file-upload-success', data);
}
else {
this.fsa.postEvent('file-upload-error', data);
}
},

onAjaxFailure: function(response, options){
var data = {
record: options.record,
response: {
'success': false,
'error': this.i18n.note_upload_failed
}
}

this.fsa.postEvent('file-upload-failed', data);
},

/**
* @access public
*/
// -------------------------------------------------------------------------------------------- //
startUpload: function(){
this.fsa.postEvent('start-upload');
},

stopUpload: function(){
this.fsa.postEvent('stop-upload');
},

getUrl: function(){
return this.url;
},

setUrl: function(url){
this.url = url;
},

getBaseParams: function(){
return this.base_params;
},

setBaseParams: function(params){
this.base_params = params;
},

getUploadAutostart: function(){
return this.upload_autostart;
},

setUploadAutostart: function(value){
this.upload_autostart = value;
},

getAllowCloseOnUpload: function(){
return this.allow_close_on_upload;
},

setAllowCloseOnUpload: function(value){
this.allow_close_on_upload = value;
},

getResetOnHide: function(){
return this.reset_on_hide;
},

setResetOnHide: function(value){
this.reset_on_hide = value;
},

getPermittedExtensions: function(){
return this.permitted_extensions;
},

setPermittedExtensions: function(value){
this.permitted_extensions = value;
},

isUploading: function(){
return this.is_uploading;
},

isNotEmptyQueue: function(){
return this.grid_panel.getStore().getCount() > 0;
},

getQueuedCount: function(count_processing){
var count = 0;
var store = this.grid_panel.getStore();
store.each(function(r){
if (r.get('state') == Ext.ux.UploadDialog.FileRecord.STATE_QUEUE) {
count++;
}
if (count_processing && r.get('state') == Ext.ux.UploadDialog.FileRecord.STATE_PROCESSING) {
count++;
}
});
return count;
},

hasUnuploadedFiles: function(){
return this.getQueuedCount() > 0;
}
});

// ---------------------------------------------------------------------------------------------- //

var p = Ext.ux.UploadDialog.Dialog.prototype;
p.i18n = {
title: 'File upload dialog',
state_col_title: 'State',
state_col_width: 70,
filename_col_title: 'Filename',
filename_col_width: 230,

filetype_col_title: 'Type',
filetype_col_width: 70,

note_col_title: 'Note',
note_col_width: 150,
add_btn_text: 'Add',
add_btn_tip: 'Add file into upload queue.',
remove_btn_text: 'Remove',
remove_btn_tip: 'Remove file from upload queue.',
reset_btn_text: 'Reset',
reset_btn_tip: 'Reset queue.',
upload_btn_start_text: 'Upload',
upload_btn_stop_text: 'Abort',
upload_btn_start_tip: 'Upload queued files to the server.',
upload_btn_stop_tip: 'Stop upload.',
close_btn_text: 'Close',
close_btn_tip: 'Close the dialog.',
progress_waiting_text: 'Waiting...',
progress_uploading_text: 'Uploading: {0} of {1} files complete.',
error_msgbox_title: 'Error',
permitted_extensions_join_str: ',',
err_file_type_not_permitted: 'Selected file extension isn\'t permitted.<br/>Please select files with following extensions: {1}',
note_queued_to_upload: 'Queued for upload.',
note_processing: 'Uploading...',
note_upload_failed: 'Server is unavailable or internal server error occured.',
note_upload_success: 'OK.',
note_upload_error: 'Upload error.',
note_aborted: 'Aborted by user.',
note_canceled: 'Upload canceled',

note_upload_error_filesize_exceed_php_limit: 'The file size exeeds the upload_max_filesize limit fixed in php.ini',
note_upload_error_filesize_exceed_php_form_limit: 'The file size exeeds the post_max_size limit fixed in php.ini',
note_upload_error_filesize_exceed_limit: 'File too large php limit MaxSize={0}Mb',
note_upload_error_uploadir_does_not_exist: 'Upload dir {0} does not exist',
note_upload_error_uploadir_not_writable: 'Upload dir {0} is not writeable',
note_upload_error_file_partially_loaded: 'Only part of the file was uploaded',
note_upload_error_file_not_uploaded: 'File not uploaded'

}
Ext.ux.UploadDialog.3.0.it.utf-8.js (upload dialog italian translation):


Ext.apply(
Ext.ux.UploadDialog.Dialog.prototype.i18n,
{
title: 'Finestra di gestione per upload di files',
state_col_title: 'Stato',
state_col_width: 70,
filename_col_title: 'Nome file',
filename_col_width: 230,
note_col_title: 'Note',
note_col_width: 150,
add_btn_text: 'Aggiungi',
add_btn_tip: 'Aggiungi file nella coda di upload.',
remove_btn_text: 'Rimuovi',
remove_btn_tip: 'Rimuovi file dalla coda di upload.',
reset_btn_text: 'Reset',
reset_btn_tip: 'Azzera la coda.',
upload_btn_start_text: 'Upload',
upload_btn_stop_text: 'Interrompi',
upload_btn_start_tip: 'Effettua l\'upload dei files verso il server.',
upload_btn_stop_tip: 'Interrompi l\'upload.',
close_btn_text: 'Chiudi',
close_btn_tip: 'Chiudi la finestra.',
progress_waiting_text: 'Premere il pulsante Aggiungi per caricare un file...',
progress_uploading_text: 'Sto eseguendo l\'upload: {0} of {1} files completi.',
error_msgbox_title: 'Errore',
permitted_extensions_join_str: ',',
err_file_type_not_permitted: 'Estensione non permessa.<br/>Si prega di selezionare solo files con le seguenti estensioni: {1}',
note_queued_to_upload: 'Accodato per upload.',
note_processing: 'Uploading...',
note_upload_failed: 'Il server è temporaneamente inaccessibile, oppure si è verificato errore interno.',
note_upload_success: 'OK.',
note_upload_error: 'Errore in upload.',
note_aborted: 'Interrotto dall\'utente',
note_canceled: 'Upload interrotto'
}
);

pezze
26 Oct 2009, 2:28 AM
I know there were some problems when using an upload field in the wizard (the file would not be posted on 'Next' button press, etc..).
Anyone has some experience with this? An example would be nice.
Thanks in advance.

I repost my solution since it seems that extjs forum hates my posts ;)

Ext.ux.Wiz.Wizard.js


Ext.namespace('Ext.ux');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz
* @extends Ext.Window
*
* A specific {@link Ext.Window} that models a wizard component.
* A wizard is basically a dialog that guides a user through various steps
* where he has to fill out form-data.
* A {@link Ext.ux.Wiz}-component consists typically of a {@link Ext.ux.Wiz.Header}
* and window-buttons ({@link Ext.Button}) which are linked to the {@link Ext.ux.Wiz.Card}s
* which themself represent the forms the user has to fill out.
*
* In order to switch between the cards in the wizard, you need the {@link Ext.ux.layout.CardLayout},
* which will check if an active-item can be hidden, before the requested new item will be set to
* 'active', i.e. shown. This is needed since the wizard may not allow a card to be hidden, if
* the input entered by the user was not valid. You can get this custom layout at
* {@link http://www.siteartwork.de/cardlayout}.
*
* Note:
* When data has been collected and teh "onFinish" listener triggers an AJAX-request,
* you should call the "switchDialogState" method so that the the dialog shows a loadmask.
* Once the requests finishes, call "switchDialogState" again, specially before any call
* to the "close" method of this component, otherwise the "closable" property of this
* instance might prevent a "close" operation for this dialog.
*
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz = Ext.extend(Ext.Window, {

/**
* @cfg {Object} An object containing the messages for the {@link Ext.LoadMask}
* covering the card-panel on request, whereas the property identifies the
* msg-text to show, and the value is the message text itself. Defaults to
<pre><code>
{
default : 'Saving...'
}
</code></pre>
*
* Depending on the contexts the loadMask has to be shown in (using the method
* showLoadMask of this class), the object can be configure to hold
* various messages.
<pre><code>
this.loadMaskConfig = {
default : 'Saving...',
validating : 'Please wait, validating input...',
};
// loadMask will be shown, displaying the message 'Please wait, validating input...'
this.showLoadMask(true, 'validating');
</code></pre>
*/
loadMaskConfig : {
'default' : 'Saving...'
},

/**
* @cfg {Number} height The height of the dialog. Defaults to "400".
*/
height : 400,

/**
* @cfg {Number} width The width of the dialog. Defaults to "540".
*/
width : 540,

/**
* @cfg {Boolean} closable Wether the dialog is closable. Defaults to "true".
* This property will be changed by the "switchDialogState"-method, which will
* enable/disable controls based on the passed argument. Thus, this config property
* serves two purposes: Tell the init config to render a "close"-tool, and create a
* "beforeclose"-listener which will either return true or false, indicating if the
* dialog may be closed.
*/
closable : true,

/**
* @cfg {Boolean} resizable Wether the dialog is resizable. Defaults to "false".
*/
resizable : false,

/**
* @cfg {Boolean} resizable Wether the dialog is modal. Defaults to "true".
*/
modal : true,

/**
* @cfg {Array} cards A numeric array with the configured {@link Ext.ux.Wiz.Card}s.
* The index of the cards in the array represent the order in which they get displayed
* in the wizard (i.e. card at index 0 gets displayed in the first step, card at index 1 gets
* displayed in the second step and so on).
*/
cards : null,

/**
* @cfg {String} previousButtonText The text to render the previous-button with.
* Defaults to "&lt; Back" (< Back)
*/
previousButtonText : '&lt; Previous',

/**
* @cfg {String} nextButtonText The text to render the next-button with.
* Defaults to "Next &gt;" (Next >)
*/
nextButtonText : 'Next &gt;',

/**
* @cfg {String} cancelButtonText The text to render the cancel-button with.
* Defaults to "Cancel"
*/
cancelButtonText : 'Cancel',

/**
* @cfg {String} finishButtonText The text to render the next-button with when the last
* step of the wizard is reached. Defaults to "Finish"
*/
finishButtonText : 'Finish',

/**
* @cfg {Object} headerConfig A config-object to use with {@link Ext.ux.Wiz.Header}.
* If not present, it defaults to an empty object.
*/
headerConfig : {},

/**
* @cfg {Object} cardPanelConfig A config-object to use with {@link Ext.Panel}, which
* represents the card-panel in this dialog.
* If not present, it defaults to an empty object
*/
cardPanelConfig : {},

/**
* @param {Ext.Button} The window-button for paging to the previous card.
* @private
*/
previousButton : null,

/**
* @param {Ext.Button} The window-button for paging to the next card. When the
* last card is reached, the event fired by and the text rendered to this button
* will change.
* @private
*/
nextButton : null,

/**
* @param {Ext.Button} The window-button for canceling the wizard. The event
* fired by this button will usually close the dialog.
* @private
*/
cancelButton : null,

/**
* @param {Ex.Panel} The card-panel that holds the various wizard cards
* ({@link Ext.ux.Wiz.Card}). The card-panel itself uses the custom
* {@link Ext.ux.layout.CardLayout}, which needs to be accessible by this class.
* You can get it at {@link http://www.siteartwork.de/cardlayout}.
* @private
*/
cardPanel : null,

/**
* @param {Number} currentCard The current {@link Ext.ux.Wiz.Card} displayed.
* Defaults to -1.
* @private
*/
currentCard : -1,

/**
* @param {Ext.ux.Wiz.Header} The header-panel of the wizard.
* @private
*/
headPanel : null,

/**
* @param {Number} cardCount Helper for storing the number of cards used
* by this wizard. Defaults to 0 (inherits "cards.length" later on).
* @private
*/
cardCount : 0,

/**
* Inits this component with the specified config-properties and automatically
* creates its components.
*/
initComponent : function()
{
this.initButtons();
this.initPanels();

var title = this.title || this.headerConfig.title;
title = title || "";

Ext.apply(this, {
title : title,
layout : 'border',
cardCount : this.cards.length,
buttons : [
this.previousButton,
this.nextButton,
this.cancelButton
],
items : [
this.headPanel,
this.cardPanel
]
});

this.addEvents(
/**
* @event cancel
* Fires after the cancel-button has been clicked.
* @param {Ext.ux.Wiz} this
*/
'cancel',
/**
* @event finish
* Fires after the last card was reached in the wizard and the
* next/finish-button has been clicked.
* @param {Ext.ux.Wiz} this
* @param {Object} data The collected data of the cards, whereas
* the index is the id of the card and the specific values
* are objects with key/value pairs in the form formElementName : value
*/
'finish'
);

Ext.ux.Wiz.superclass.initComponent.call(this);
},

// -------- helper
/**
* Returns the form-data of all cards in this wizard. The first index is the
* id of the card in this wizard,
* and the values are objects containing key/value pairs in the form of
* fieldName : fieldValue.
*
* @return {Array}
*/
getWizardData : function()
{
var formValues = {};
var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
if (cards[i].form) {
formValues[cards[i].id] = cards[i].form.getValues(false);
} else {
formValues[cards[i].id] = {};
}
}

return formValues;
},

/**
* Switches the state of this wizard between disabled/enabled.
* A disabled dialog will have a {@link Ext.LoadMask} covering the card-panel
* to prevent user input, and the buttons will be rendered disabled/enabled.
* If the dialog is closable, the close-tool will be masked, too, and the dialog will not
* be closable by clicking the "close" tool.
*
* @param {Boolean} enabled "false" to prevent user input and mask the elements,
* otherwise true.
* @param {String} type The type of msg for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
switchDialogState : function(enabled, type)
{
this.showLoadMask(!enabled, type);

this.previousButton.setDisabled(!enabled);
this.nextButton.setDisabled(!enabled);
this.cancelButton.setDisabled(true);

var ct = this.tools['close'];

if (ct) {
switch (enabled) {
case true:
this.tools['close'].unmask();
break;

default:
this.tools['close'].mask();
break;
}
}

this.closable = enabled;
},

/**
* Shows the load mask for this wizard. By default, the cardPanel's body
* will be masked.
*
* @param {Boolean} show true to show the load mask, otherwise false.
* @param {String} type The type of message for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
showLoadMask : function(show, type)
{
if (!type) {
type = 'default';
}

if (show) {
if (this.loadMask == null) {
this.loadMask = new Ext.LoadMask(this.body);
}
this.loadMask.msg = this.loadMaskConfig[type];
this.loadMask.show();
} else {
if (this.loadMask) {
this.loadMask.hide();
}
}
},


/**
* Inits the listener for the various {@link Ext.ux.Wiz.Card}s used
* by this component.
*/
initEvents : function()
{
Ext.ux.Wiz.superclass.initEvents.call(this);

this.on('beforeclose', this.onBeforeClose, this);

var cards = this.cards;

for (var i = 0, len = cards.length; i < len; i++) {
cards[i].on('show', this.onCardShow, this);
cards[i].on('hide', this.onCardHide, this);
cards[i].on('clientvalidation', this.onClientValidation, this);
}
},

/**
* Creates the head- and the card-panel.
* Be sure to have the custom {@link Ext.ux.layout.CardLayout} available
* in order to make the card-panel work as expected by this component
* ({@link http://www.siteartwork.de/cardlayout}).
*/
initPanels : function()
{
var cards = this.cards;
var cardPanelConfig = this.cardPanelConfig;

Ext.apply(this.headerConfig, {
steps : cards.length
});

this.headPanel = new Ext.ux.Wiz.Header(this.headerConfig);

Ext.apply(cardPanelConfig, {
layout : new Ext.ux.layout.CardLayout(),
items : cards
});

Ext.applyIf(cardPanelConfig, {
region : 'center',
border : false,
activeItem : 0
});

this.cardPanel = new Ext.Panel(cardPanelConfig);
},

/**
* Creates the instances for the the window buttons.
*/
initButtons : function()
{
this.previousButton = new Ext.Button({
text : this.previousButtonText,
disabled : true,
minWidth : 75,
handler : this.onPreviousClick,
scope : this
});

this.nextButton = new Ext.Button({
text : this.nextButtonText,
minWidth : 75,
handler : this.onNextClick,
scope : this
});

this.cancelButton = new Ext.Button({
text : this.cancelButtonText,
handler : this.onCancelClick,
scope : this,
minWidth : 75
});
},

// -------- listeners

/**
* Listener for the beforeclose event.
* This listener will return true or false based on the "closable"
* property by this component. This property will be changed by the "switchDialogState"
* method, indicating if there is currently any process running that should prevent
* this dialog from being closed.
*
* @param {Ext.Panel} panel The panel being closed
*
* @return {Boolean}
*/
onBeforeClose : function(panel)
{
return this.closable;
},

/**
* By default, the card firing this event monitors user input in a frequent
* interval and fires the 'clientvalidation'-event along with it. This listener
* will enable/disable the next/finish-button in accordance with it, based upon
* the parameter isValid. isValid" will be set by the form validation and depends
* on the validators you are using for the different input-elemnts in your form.
* If the card does not contain any forms, this listener will never be called by the
* card itself.
*
* @param {Ext.ux.Wiz.Card} The card that triggered the event.
* @param {Boolean} isValid "true", if the user input was valid, otherwise
* "false"
*/
onClientValidation : function(card, isValid)
{
if (!isValid) {
this.nextButton.setDisabled(true);
} else {
this.nextButton.setDisabled(false);
}
},

/**
* This will render the "next" button as disabled since the bindHandler's delay
* of the next card to show might be lagging on slower systems
*
*/
onCardHide : function(card)
{
if (this.cardPanel.layout.activeItem.id === card.id) {
this.nextButton.setDisabled(true);
}
},


/**
* Listener for the "show" event of the card that gets shown in the card-panel.
* Renders the next/previous buttons based on the position of the card in the wizard
* and updates the head-panel accordingly.
*
* @param {Ext.ux.Wiz.Card} The card being shown.
*/
onCardShow : function(card)
{
var parent = card.ownerCt;

var items = parent.items;

for (var i = 0, len = items.length; i < len; i++) {
if (items.get(i).id == card.id) {
break;
}
}

this.currentCard = i;
this.headPanel.updateStep(i, card.title);

if (i == len-1) {
this.nextButton.setText(this.finishButtonText);
} else {
this.nextButton.setText(this.nextButtonText);
}

if (card.isValid()) {
this.nextButton.setDisabled(false);
}

if (i == 0) {
this.previousButton.setDisabled(true);
} else {
this.previousButton.setDisabled(false);
}

},


/**
* Fires the 'cancel'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onCancelClick : function()
{
if (this.fireEvent('cancel', this) !== false) {
this.close();
}
},

/**
* Fires the 'finish'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onFinish : function()
{
if (this.fireEvent('finish', this, this.getWizardData()) !== false) {
this.close();
}
},

/**
* Listener for the previous-button.
* Switches to the previous displayed {@link Ext.ux.Wiz.Card}.
*/
onPreviousClick : function()
{
if (this.currentCard > 0) {
this.cardPanel.getLayout().setActiveItem(this.currentCard - 1);
}
},

/**
* Listener for the next-button. Switches to the next {@link Ext.ux.Wiz.Card}
* if the 'beforehide'-method of it did not return false. The functionality
* for this is implemented in {@link Ext.ux.layout.CardLayout}, which is needed
* as the layout for the card-panel of this component.
*/
onNextClick : function()
{
if (this.currentCard == this.cardCount-1) {
this.onFinish();
} else {
this.cardPanel.getLayout().setActiveItem(this.currentCard+1);
}
}
});

Ext.ux.Wiz.Card.js


Ext.namespace('Ext.ux.Wiz');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz.Card
* @extends Ext.FormPanel
*
* A specific {@link Ext.FormPanel} that can be used as a card in a
* {@link Ext.ux.Wiz}-component. An instance of this card does only work properly
* if used in a panel that uses a {@see Ext.layout.CardLayout}-layout.
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz.Card = Ext.extend(Ext.FormPanel, {

/**
* @cfg {Boolean} header "True" to create the header element. Defaults to
* "false". See {@link Ext.form.FormPanel#header}
*/
header : false,

/**
* @cfg {Strting} hideMode Hidemode of this component. Defaults to "offsets".
* See {@link Ext.form.FormPanel#hideMode}
*/
hideMode : 'display',

initComponent : function()
{
this.addEvents(
/**
* @event beforecardhide
* If you want to add additional checks to your card which cannot be easily done
* using default validators of input-fields (or using the monitorValid-config option),
* add your specific listeners to this event.
* This event gets only fired if the activeItem of the ownerCt-component equals to
* this instance of {@see Ext.ux.Wiz.Card}. This is needed since a card layout usually
* hides it's items right after rendering them, involving the beforehide-event.
* If those checks would be attached to the normal beforehide-event, the card-layout
* would never be able to hide this component after rendering it, depending on the
* listeners return value.
*
* @param {Ext.ux.Wiz.Card} card The card that triggered the event
*/
'beforecardhide'
);


Ext.ux.Wiz.Card.superclass.initComponent.call(this);

},

// -------- helper
isValid : function()
{
if (this.monitorValid) {
return this.bindHandler();
}

return true;
},
/**
* Returns the form-data of one cards in this wizard. The first index is the
* id of the current card,
* and the values are objects containing key/value pairs in the form of
* fieldName : fieldValue.
*
* @return {Array}
*/
getWizardCardData : function()
{
var formValues = {};

if (this.form) {
formValues[this.id] = this.form.getValues(false);
} else {
formValues[this.id] = {};
}

return formValues;
},

// -------- overrides

/**
* Overrides parent implementation since we allow to add any element
* in this component which must not be neccessarily be a form-element.
* So before a call to "isValid()" is about to be made, this implementation
* checks first if the specific item sitting in this component has a method "isValid" - if it
* does not exists, it will be added on the fly.
*/
bindHandler : function()
{
this.form.items.each(function(f){
if(!f.isValid){
f.isValid = Ext.emptyFn;
}
});

Ext.ux.Wiz.Card.superclass.bindHandler.call(this);
},

/**
* Overrides parent implementation. This is needed because in case
* this method uses "monitorValid=true", the method "startMonitoring" must
* not be called, until the "show"-event of this card fires.
*/
initEvents : function()
{
var old = this.monitorValid;
this.monitorValid = false;
Ext.ux.Wiz.Card.superclass.initEvents.call(this);
this.monitorValid = old;

this.on('beforehide', this.bubbleBeforeHideEvent, this);

this.on('beforecardhide', this.isValid, this);
this.on('show', this.onCardShow, this);
this.on('hide', this.onCardHide, this);
},

// -------- listener
/**
* Checks wether the beforecardhide-event may be triggered.
*/
bubbleBeforeHideEvent : function()
{
var ly = this.ownerCt.layout;
var activeItem = ly.activeItem;

if (activeItem && activeItem.id === this.id) {
return this.fireEvent('beforecardhide', this);
}

return true;
},

/**
* Stops monitoring the form elements in this component when the
* 'hide'-event gets fired.
*/
onCardHide : function()
{
if (this.monitorValid) {
this.stopMonitoring();
}
},

/**
* Starts monitoring the form elements in this component when the
* 'show'-event gets fired.
*/
onCardShow : function()
{
if (this.monitorValid) {
this.startMonitoring();
}
}

});

Ext.ux.Wiz.Header.js


Ext.namespace('Ext.ux.Wiz');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz.Header
* @extends Ext.BoxComponent
*
* A specific {@link Ext.BoxComponent} that can be used to show the current process in an
* {@link Ext.ux.Wiz}.
*
* An instance of this class is usually being created by {@link Ext.ux.Wiz#initPanels} using the
* {@link Ext.ux.Wiz#headerConfig}-object.
*
* @private
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz.Header = Ext.extend(Ext.BoxComponent, {

/**
* @cfg {Number} height The height of this component. Defaults to "55".
*/
height : 55,

/**
* @cfg {String} region The Region of this component. Since a {@link Ext.ux.Wiz}
* usually uses a {@link Ext.layout.BorderLayout}, this property defaults to
* "north". If you want to change this property, you should also change the appropriate
* css-classes that are used for this component.
*/
region : 'north',

/**
* @cfg {String} title The title that gets rendered in the head of the component. This
* should be a text describing the purpose of the wizard.
*/
title : 'Wizard',

/**
* @cfg {Number} steps The overall number of steps the user has to go through
* to finish the wizard.
*/
steps : 0,

/**
* @cfg {String} stepText The text in the header indicating the current process in the wizard.
* (defaults to "Step {0} of {1}: {2}").
* {0} is replaced with the index (+1) of the current card, {1} is replaced by the
* total number of cards in the wizard and {2} is replaced with the title-property of the
* {@link Ext.ux.Wiz.Card}
* @type String
*/
stepText : "Step {0} of {1}: {2}",

/**
* @cfg {Object} autoEl The element markup used to render this component.
*/
autoEl : {
tag : 'div',
cls : 'ext-ux-wiz-Header',
children : [{
tag : 'div',
cls : 'ext-ux-wiz-Header-title'
}, {
tag : 'div',
children : [{
tag : 'div',
cls : 'ext-ux-wiz-Header-step'
}, {
tag : 'div',
cls : 'ext-ux-wiz-Header-stepIndicator-container'
}]
}]
},

/**
* @param {Ext.Element}
*/
titleEl : null,

/**
* @param {Ext.Element}
*/
stepEl : null,

/**
* @param {Ext.Element}
*/
imageContainer : null,

/**
* @param {Array}
*/
indicators : null,

/**
* @param {Ext.Template}
*/
stepTemplate : null,

/**
* @param {Number} lastActiveStep Stores the index of the last active card that
* was shown-
*/
lastActiveStep : -1,

// -------- helper
/**
* Gets called by {@link Ext.ux.Wiz#onCardShow()} and updates the header
* with the approppriate information, such as the progress of the wizard
* (i.e. which card is being shown etc.)
*
* @param {Number} currentStep The index of the card currently shown in
* the wizard
* @param {String} title The title-property of the {@link Ext.ux.Wiz.Card}
*
* @private
*/
updateStep : function(currentStep, title)
{
var html = this.stepTemplate.apply({
0 : currentStep+1,
1 : this.steps,
2 : title
});

this.stepEl.update(html);

if (this.lastActiveStep != -1) {
this.indicators[this.lastActiveStep].removeClass('ext-ux-wiz-Header-stepIndicator-active');
}

this.indicators[currentStep].addClass('ext-ux-wiz-Header-stepIndicator-active');

this.lastActiveStep = currentStep;
},


// -------- listener
/**
* Overrides parent implementation to render this component properly.
*/
onRender : function(ct, position)
{
Ext.ux.Wiz.Header.superclass.onRender.call(this, ct, position);

this.indicators = [];
this.stepTemplate = new Ext.Template(this.stepText);
this.stepTemplate.compile();

var el = this.el.dom.firstChild;
var ns = el.nextSibling;

this.titleEl = new Ext.Element(el);
this.stepEl = new Ext.Element(ns.firstChild);
this.imageContainer = new Ext.Element(ns.lastChild);

this.titleEl.update(this.title);

var image = null;
for (var i = 0, len = this.steps; i < len; i++) {
image = document.createElement('div');
image.innerHTML = " ";
image.className = 'ext-ux-wiz-Header-stepIndicator';
this.indicators[i] = new Ext.Element(image);
this.imageContainer.appendChild(image);
}
}
});

Ext.ux.Wiz.Panel.js (use this instead of Ext.ux.Wiz.Wizard.js if you want to use Wizard in a Panel e not in a Window)



Ext.namespace('Ext.ux');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz
* @extends Ext.Window
*
* A specific {@link Ext.Window} that models a wizard component.
* A wizard is basically a dialog that guides a user through various steps
* where he has to fill out form-data.
* A {@link Ext.ux.Wiz}-component consists typically of a {@link Ext.ux.Wiz.Header}
* and window-buttons ({@link Ext.Button}) which are linked to the {@link Ext.ux.Wiz.Card}s
* which themself represent the forms the user has to fill out.
*
* In order to switch between the cards in the wizard, you need the {@link Ext.ux.layout.CardLayout},
* which will check if an active-item can be hidden, before the requested new item will be set to
* 'active', i.e. shown. This is needed since the wizard may not allow a card to be hidden, if
* the input entered by the user was not valid. You can get this custom layout at
* {@link http://www.siteartwork.de/cardlayout}.
*
* Note:
* When data has been collected and teh "onFinish" listener triggers an AJAX-request,
* you should call the "switchDialogState" method so that the the dialog shows a loadmask.
* Once the requests finishes, call "switchDialogState" again, specially before any call
* to the "close" method of this component, otherwise the "closable" property of this
* instance might prevent a "close" operation for this dialog.
*
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz = Ext.extend(Ext.Panel, {

/**
* @cfg {Object} An object containing the messages for the {@link Ext.LoadMask}
* covering the card-panel on request, whereas the property identifies the
* msg-text to show, and the value is the message text itself. Defaults to
<pre><code>
{
default : 'Saving...'
}
</code></pre>
*
* Depending on the contexts the loadMask has to be shown in (using the method
* showLoadMask of this class), the object can be configure to hold
* various messages.
<pre><code>
this.loadMaskConfig = {
default : 'Saving...',
validating : 'Please wait, validating input...',
};
// loadMask will be shown, displaying the message 'Please wait, validating input...'
this.showLoadMask(true, 'validating');
</code></pre>
*/
loadMaskConfig : {
'default' : 'Saving...'
},

/**
* @cfg {Number} height The height of the dialog. Defaults to "400".
*/
height : 400,

/**
* @cfg {Number} width The width of the dialog. Defaults to "540".
*/
width : 540,

/**
* @cfg {Boolean} closable Wether the dialog is closable. Defaults to "true".
* This property will be changed by the "switchDialogState"-method, which will
* enable/disable controls based on the passed argument. Thus, this config property
* serves two purposes: Tell the init config to render a "close"-tool, and create a
* "beforeclose"-listener which will either return true or false, indicating if the
* dialog may be closed.
*/
closable : true,

/**
* @cfg {Boolean} resizable Wether the dialog is resizable. Defaults to "false".
*/
resizable : false,

/**
* @cfg {Boolean} resizable Wether the dialog is modal. Defaults to "true".
*/
modal : true,

/**
* @cfg {Array} cards A numeric array with the configured {@link Ext.ux.Wiz.Card}s.
* The index of the cards in the array represent the order in which they get displayed
* in the wizard (i.e. card at index 0 gets displayed in the first step, card at index 1 gets
* displayed in the second step and so on).
*/
cards : null,

/**
* @cfg {String} previousButtonText The text to render the previous-button with.
* Defaults to "&lt; Back" (< Back)
*/
previousButtonText : '&lt; Previous',

/**
* @cfg {String} nextButtonText The text to render the next-button with.
* Defaults to "Next &gt;" (Next >)
*/
nextButtonText : 'Next &gt;',

/**
* @cfg {String} cancelButtonText The text to render the cancel-button with.
* Defaults to "Cancel"
*/
cancelButtonText : 'Cancel',

/**
* @cfg {String} finishButtonText The text to render the next-button with when the last
* step of the wizard is reached. Defaults to "Finish"
*/
finishButtonText : 'Finish',

/**
* @cfg {Object} headerConfig A config-object to use with {@link Ext.ux.Wiz.Header}.
* If not present, it defaults to an empty object.
*/
headerConfig : {},

/**
* @cfg {Object} cardPanelConfig A config-object to use with {@link Ext.Panel}, which
* represents the card-panel in this dialog.
* If not present, it defaults to an empty object
*/
cardPanelConfig : {},

/**
* @param {Ext.Button} The window-button for paging to the previous card.
* @private
*/
previousButton : null,

/**
* @param {Ext.Button} The window-button for paging to the next card. When the
* last card is reached, the event fired by and the text rendered to this button
* will change.
* @private
*/
nextButton : null,

/**
* @param {Ext.Button} The window-button for canceling the wizard. The event
* fired by this button will usually close the dialog.
* @private
*/
cancelButton : null,

/**
* @param {Ex.Panel} The card-panel that holds the various wizard cards
* ({@link Ext.ux.Wiz.Card}). The card-panel itself uses the custom
* {@link Ext.ux.layout.CardLayout}, which needs to be accessible by this class.
* You can get it at {@link http://www.siteartwork.de/cardlayout}.
* @private
*/
cardPanel : null,

/**
* @param {Number} currentCard The current {@link Ext.ux.Wiz.Card} displayed.
* Defaults to -1.
* @private
*/
currentCard : -1,

/**
* @param {Ext.ux.Wiz.Header} The header-panel of the wizard.
* @private
*/
headPanel : null,

/**
* @param {Number} cardCount Helper for storing the number of cards used
* by this wizard. Defaults to 0 (inherits "cards.length" later on).
* @private
*/
cardCount : 0,

/**
* Inits this component with the specified config-properties and automatically
* creates its components.
*/
initComponent : function()
{
this.initButtons();
this.initPanels();

var title = this.title || '' //this.headerConfig.title;
title = title || "";

Ext.apply(this, {
title : title,
layout : 'border',
cardCount : this.cards.length,
buttons : [
this.previousButton,
this.nextButton/*,
this.cancelButton*/
],
items : [
this.headPanel,
this.cardPanel
]
});

this.addEvents(
/**
* @event cancel
* Fires after the cancel-button has been clicked.
* @param {Ext.ux.Wiz} this
*/
'cancel',
/**
* @event finish
* Fires after the last card was reached in the wizard and the
* next/finish-button has been clicked.
* @param {Ext.ux.Wiz} this
* @param {Object} data The collected data of the cards, whereas
* the index is the id of the card and the specific values
* are objects with key/value pairs in the form formElementName : value
*/
'finish'
);

Ext.ux.Wiz.superclass.initComponent.call(this);
},

// -------- helper
/**
* Returns the form-data of all cards in this wizard. The first index is the
* id of the card in this wizard,
* and the values are objects containing key/value pairs in the form of
* fieldName : fieldValue.
*
* @return {Array}
*/
getWizardData : function()
{
var formValues = {};
var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
if (cards[i].form) {
formValues[cards[i].id] = cards[i].form.getValues(false);
} else {
formValues[cards[i].id] = {};
}
}

return formValues;
},

/**
* Switches the state of this wizard between disabled/enabled.
* A disabled dialog will have a {@link Ext.LoadMask} covering the card-panel
* to prevent user input, and the buttons will be rendered disabled/enabled.
* If the dialog is closable, the close-tool will be masked, too, and the dialog will not
* be closable by clicking the "close" tool.
*
* @param {Boolean} enabled "false" to prevent user input and mask the elements,
* otherwise true.
* @param {String} type The type of msg for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
switchDialogState : function(enabled, type)
{
this.showLoadMask(!enabled, type);

this.previousButton.setDisabled(!enabled);
this.nextButton.setDisabled(!enabled);
this.cancelButton.setDisabled(true);

var ct = this.tools['close'];

if (ct) {
switch (enabled) {
case true:
this.tools['close'].unmask();
break;

default:
this.tools['close'].mask();
break;
}
}

this.closable = enabled;
},

/**
* Shows the load mask for this wizard. By default, the cardPanel's body
* will be masked.
*
* @param {Boolean} show true to show the load mask, otherwise false.
* @param {String} type The type of message for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
showLoadMask : function(show, type)
{
if (!type) {
type = 'default';
}

if (show) {
if (this.loadMask == null) {
this.loadMask = new Ext.LoadMask(this.body);
}
this.loadMask.msg = this.loadMaskConfig[type];
this.loadMask.show();
} else {
if (this.loadMask) {
this.loadMask.hide();
}
}
},


/**
* Inits the listener for the various {@link Ext.ux.Wiz.Card}s used
* by this component.
*/
initEvents : function()
{
Ext.ux.Wiz.superclass.initEvents.call(this);

this.on('beforeclose', this.onBeforeClose, this);

var cards = this.cards;

for (var i = 0, len = cards.length; i < len; i++) {
cards[i].on('show', this.onCardShow, this);
cards[i].on('hide', this.onCardHide, this);
cards[i].on('clientvalidation', this.onClientValidation, this);
}
},

/**
* Creates the head- and the card-panel.
* Be sure to have the custom {@link Ext.ux.layout.CardLayout} available
* in order to make the card-panel work as expected by this component
* ({@link http://www.siteartwork.de/cardlayout}).
*/
initPanels : function()
{
var cards = this.cards;
var cardPanelConfig = this.cardPanelConfig;

Ext.apply(this.headerConfig, {
steps : cards.length
});

this.headPanel = new Ext.ux.Wiz.Header(this.headerConfig);

Ext.apply(cardPanelConfig, {
layout : new Ext.ux.layout.CardLayout(),
items : cards
});

Ext.applyIf(cardPanelConfig, {
region : 'center',
border : false,
activeItem : 0
});

this.cardPanel = new Ext.Panel(cardPanelConfig);
},

/**
* Creates the instances for the the window buttons.
*/
initButtons : function()
{
this.previousButton = new Ext.Button({
text : this.previousButtonText,
disabled : true,
minWidth : 75,
handler : this.onPreviousClick,
scope : this
});

this.nextButton = new Ext.Button({
text : this.nextButtonText,
minWidth : 75,
handler : this.onNextClick,
scope : this
});

this.cancelButton = new Ext.Button({
text : this.cancelButtonText,
handler : this.onCancelClick,
scope : this,
minWidth : 75
});
},

// -------- listeners

/**
* Listener for the beforeclose event.
* This listener will return true or false based on the "closable"
* property by this component. This property will be changed by the "switchDialogState"
* method, indicating if there is currently any process running that should prevent
* this dialog from being closed.
*
* @param {Ext.Panel} panel The panel being closed
*
* @return {Boolean}
*/
onBeforeClose : function(panel)
{
return this.closable;
},

/**
* By default, the card firing this event monitors user input in a frequent
* interval and fires the 'clientvalidation'-event along with it. This listener
* will enable/disable the next/finish-button in accordance with it, based upon
* the parameter isValid. isValid" will be set by the form validation and depends
* on the validators you are using for the different input-elemnts in your form.
* If the card does not contain any forms, this listener will never be called by the
* card itself.
*
* @param {Ext.ux.Wiz.Card} The card that triggered the event.
* @param {Boolean} isValid "true", if the user input was valid, otherwise
* "false"
*/
onClientValidation : function(card, isValid)
{
if (!isValid) {
this.nextButton.setDisabled(true);
} else {
this.nextButton.setDisabled(false);
}
},

/**
* This will render the "next" button as disabled since the bindHandler's delay
* of the next card to show might be lagging on slower systems
*
*/
onCardHide : function(card)
{
if (this.cardPanel.layout.activeItem.id === card.id) {
this.nextButton.setDisabled(true);
}
},


/**
* Listener for the "show" event of the card that gets shown in the card-panel.
* Renders the next/previous buttons based on the position of the card in the wizard
* and updates the head-panel accordingly.
*
* @param {Ext.ux.Wiz.Card} The card being shown.
*/
onCardShow : function(card)
{
var parent = card.ownerCt;

var items = parent.items;

for (var i = 0, len = items.length; i < len; i++) {
if (items.get(i).id == card.id) {
break;
}
}

this.currentCard = i;
this.headPanel.updateStep(i, card.title);

if (i == len-1) {
this.nextButton.setText(this.finishButtonText);
} else {
this.nextButton.setText(this.nextButtonText);
}

if (card.isValid()) {
this.nextButton.setDisabled(false);
}

if (i == 0) {
this.previousButton.setDisabled(true);
} else {
this.previousButton.setDisabled(false);
}

},


/**
* Fires the 'cancel'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onCancelClick : function()
{
if (this.fireEvent('cancel', this) !== false) {
this.close();
}
},

/**
* Fires the 'finish'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onFinish : function()
{
if (this.fireEvent('finish', this, this.getWizardData()) !== false) {
//this.close();
this.nextButton.setDisabled(true);
}
},

/**
* Listener for the previous-button.
* Switches to the previous displayed {@link Ext.ux.Wiz.Card}.
*/
onPreviousClick : function()
{
if (this.currentCard > 0) {
this.cardPanel.getLayout().setActiveItem(this.currentCard - 1);
}
},

/**
* Listener for the next-button. Switches to the next {@link Ext.ux.Wiz.Card}
* if the 'beforehide'-method of it did not return false. The functionality
* for this is implemented in {@link Ext.ux.layout.CardLayout}, which is needed
* as the layout for the card-panel of this component.
*/
onNextClick : function()
{
if (this.currentCard == this.cardCount-1) {
this.onFinish();
} else {
this.cardPanel.getLayout().setActiveItem(this.currentCard+1);
}
}
});

Ext.ux.UploadDialog.3.0.js (works with extjs 3.0 and wizard panel/window)


/**
* This namespace should be in another file but I dicided to put it here for consistancy.
*/
Ext.namespace('Ext.ux.Utils');

/**
* This class implements event queue behaviour.
*
* @class Ext.ux.Utils.EventQueue
* @param function handler Event handler.
* @param object scope Handler scope.
*/
Ext.ux.Utils.EventQueue = function(handler, scope){
if (!handler) {
throw 'Handler is required.';
}
this.handler = handler;
this.scope = scope || window;
this.queue = [];
this.is_processing = false;

/**
* Posts event into the queue.
*
* @access public
* @param mixed event Event identificator.
* @param mixed data Event data.
*/
this.postEvent = function(event, data){
data = data || null;
this.queue.push({
event: event,
data: data
});
if (!this.is_processing) {
this.process();
}
}

this.flushEventQueue = function(){
this.queue = [];
}, /**
* @access private
*/
this.process = function(){
while (this.queue.length > 0) {
this.is_processing = true;
var event_data = this.queue.shift();
this.handler.call(this.scope, event_data.event, event_data.data);
}
this.is_processing = false;
}
}

/**
* This class implements Mili's finite state automata behaviour.
*
* Transition / output table format:
* {
* 'state_1' : {
* 'event_1' : [
* {
* p|predicate: function, // Transition predicate, optional, default to true.
* // If array then conjunction will be applyed to the operands.
* // Predicate signature is (data, event, this).
* a|action: function|array, // Transition action, optional, default to Ext.emptyFn.
* // If array then methods will be called sequentially.
* // Action signature is (data, event, this).
* s|state: 'state_x', // New state - transition destination, optional, default to
* // current state.
* scope: object // Predicate and action scope, optional, default to
* // trans_table_scope or window.
* }
* ]
* },
*
* 'state_2' : {
* ...
* }
* ...
* }
*
* @param mixed initial_state Initial state.
* @param object trans_table Transition / output table.
* @param trans_table_scope Transition / output table's methods scope.
*/
Ext.ux.Utils.FSA = function(initial_state, trans_table, trans_table_scope){
this.current_state = initial_state;
this.trans_table = trans_table ||
{};
this.trans_table_scope = trans_table_scope || window;
Ext.ux.Utils.FSA.superclass.constructor.call(this, this.processEvent, this);
}

Ext.extend(Ext.ux.Utils.FSA, Ext.ux.Utils.EventQueue, {

current_state: null,
trans_table: null,
trans_table_scope: null,

/**
* Returns current state
*
* @access public
* @return mixed Current state.
*/
state: function(){
return this.current_state;
},

/**
* @access public
*/
processEvent: function(event, data){
var transitions = this.currentStateEventTransitions(event);
if (!transitions) {
throw "State '" + this.current_state + "' has no transition for event '" + event + "'.";
}
for (var i = 0, len = transitions.length; i < len; i++) {
var transition = transitions[i];

var predicate = transition.predicate || transition.p || true;
var action = transition.action || transition.a || Ext.emptyFn;
var new_state = transition.state || transition.s || this.current_state;
var scope = transition.scope || this.trans_table_scope;

if (this.computePredicate(predicate, scope, data, event)) {
this.callAction(action, scope, data, event);
this.current_state = new_state;
return;
}
}

throw "State '" + this.current_state + "' has no transition for event '" + event + "' in current context";
},

/**
* @access private
*/
currentStateEventTransitions: function(event){
return this.trans_table[this.current_state] ? this.trans_table[this.current_state][event] || false : false;
},

/**
* @access private
*/
computePredicate: function(predicate, scope, data, event){
var result = false;

switch (Ext.type(predicate)) {
case 'function':
result = predicate.call(scope, data, event, this);
break;
case 'array':
result = true;
for (var i = 0, len = predicate.length; result && (i < len); i++) {
if (Ext.type(predicate[i]) == 'function') {
result = predicate[i].call(scope, data, event, this);
}
else {
throw ['Predicate: ', predicate[i], ' is not callable in "', this.current_state, '" state for event "', event].join('');
}
}
break;
case 'boolean':
result = predicate;
break;
default:
throw ['Predicate: ', predicate, ' is not callable in "', this.current_state, '" state for event "', event].join('');
}
return result;
},

/**
* @access private
*/
callAction: function(action, scope, data, event){
switch (Ext.type(action)) {
case 'array':
for (var i = 0, len = action.length; i < len; i++) {
if (Ext.type(action[i]) == 'function') {
action[i].call(scope, data, event, this);
}
else {
throw ['Action: ', action[i], ' is not callable in "', this.current_state, '" state for event "', event].join('');
}
}
break;
case 'function':
action.call(scope, data, event, this);
break;
default:
throw ['Action: ', action, ' is not callable in "', this.current_state, '" state for event "', event].join('');
}
}
});

// ---------------------------------------------------------------------------------------------- //

/**
* Ext.ux.UploadDialog namespace.
*/
Ext.namespace('Ext.ux.UploadDialog');

/**
* @class Ext.ux.form.BrowseButton
* @extends Ext.Button
* Ext.Button that provides a customizable file browse button.
* Clicking this button, pops up a file dialog box for a user to select the file to upload.
* This is accomplished by having a transparent <input type="file"> box above the Ext.Button.
* When a user thinks he or she is clicking the Ext.Button, they're actually clicking the hidden input "Browse..." box.
* Note: this class can be instantiated explicitly or with xtypes anywhere a regular Ext.Button can be except in 2 scenarios:
* - Panel.addButton method both as an instantiated object or as an xtype config object.
* - Panel.buttons config object as an xtype config object.
* These scenarios fail because Ext explicitly creates an Ext.Button in these cases.
* Browser compatibility:
* Internet Explorer 6:
* - no issues
* Internet Explorer 7:
* - no issues
* Internet Explorer 8:
* - no issues
* Firefox 3 - Windows:
* - pointer cursor doesn't display when hovering over the button.
* @author loeppky - based on the work done by MaximGB in Ext.ux.UploadDialog (http://extjs.com/forum/showthread.php?t=21558)
* The follow the curosr float div idea also came from MaximGB. With patches for IE8 by 4Him.
* @see http://extjs.com/forum/showthread.php?t=29032
* @constructor
* Create a new BrowseButton.
* @param {Object} config Configuration options
*/
Ext.ux.UploadDialog.BrowseButton = Ext.extend(Ext.Button, {
/*
* Config options:
*/
/**
* @cfg {String} inputFileName
* Name to use for the hidden input file DOM element. Deaults to "file".
*/
inputFileName: 'file',
/**
* @cfg {Boolean} debug
* Toggle for turning on debug mode.
* Debug mode doesn't make clipEl transparent so that one can see how effectively it covers the Ext.Button.
* In addition, clipEl is given a green background and floatEl a red background to see how well they are positioned.
*/
debug: false,


/*
* Private constants:
*/
/**
* @property FLOAT_EL_WIDTH
* @type Number
* The width (in pixels) of floatEl.
* It should be less than the width of the IE "Browse" button's width (65 pixels), since IE doesn't let you resize it.
* We define this width so we can quickly center floatEl at the mouse cursor without having to make any function calls.
* @private
*/
FLOAT_EL_WIDTH: 60,

/**
* @property FLOAT_EL_HEIGHT
* @type Number
* The heigh (in pixels) of floatEl.
* It should be less than the height of the "Browse" button's height.
* We define this height so we can quickly center floatEl at the mouse cursor without having to make any function calls.
* @private
*/
FLOAT_EL_HEIGHT: 18,


/*
* Private properties:
*/
/**
* @property buttonCt
* @type Ext.Element
* Element that contains the actual Button DOM element.
* We store a reference to it, so we can easily grab its size for sizing the clipEl.
* @private
*/
buttonCt: null,
/**
* @property clipEl
* @type Ext.Element
* Element that contains the floatEl.
* This element is positioned to fill the area of Ext.Button and has overflow turned off.
* This keeps floadEl tight to the Ext.Button, and prevents it from masking surrounding elements.
* @private
*/
clipEl: null,
/**
* @property floatEl
* @type Ext.Element
* Element that contains the inputFileEl.
* This element is size to be less than or equal to the size of the input file "Browse" button.
* It is then positioned wherever the user moves the cursor, so that their click always clicks the input file "Browse" button.
* Overflow is turned off to preven inputFileEl from masking surrounding elements.
* @private
*/
floatEl: null,
/**
* @property inputFileEl
* @type Ext.Element
* Element for the hiden file input.
* @private
*/
inputFileEl: null,
/**
* @property originalHandler
* @type Function
* The handler originally defined for the Ext.Button during construction using the "handler" config option.
* We need to null out the "handler" property so that it is only called when a file is selected.
* @private
*/
originalHandler: null,
/**
* @property originalScope
* @type Object
* The scope originally defined for the Ext.Button during construction using the "scope" config option.
* While the "scope" property doesn't need to be nulled, to be consistent with originalHandler, we do.
* @private
*/
originalScope: null,
/**
* @property BROWSERS_OFFSET
* @type Object
* The browsers specific offsets used to position the clipping element for better overlay tightness. For
* Ext 3, Ext 2 offsets are used unless there is an Ext 3 entry.
* @private
* @author 4Him
*/
BROWSERS_OFFSETS: {
Ext2: {
IE8: {left: -8, top: -16, width: 16, height: 22},
IE: {left: -8, top: -3, width: 16, height: 6},
Opera: {left: -8, top: -3, width: -18, height: -1},
Gecko: {left: -8, top: -6, width: 16, height: 10},
Safari:{left: -4, top: -2, width: 6, height: 6}
},
Ext3: {
IE8: {left: -7, width: 10},
IE: {left: -3, width: 6},
Gecko: { width: 11}
}
},
/**
* @property isExt2x
* @type boolean
* Whether we are currently using Ext 2.x
* @private
* @author 4Him
*/
isExt2x: Ext.version.match(/^2\./),


/*
* Protected Ext.Button overrides
*/
/**
* @see Ext.Button.initComponent
*/
initComponent: function(){
Ext.ux.UploadDialog.BrowseButton.superclass.initComponent.call(this);
// Store references to the original handler and scope before nulling them.
// This is done so that this class can control when the handler is called.
// There are some cases where the hidden file input browse button doesn't completely cover the Ext.Button.
// The handler shouldn't be called in these cases. It should only be called if a new file is selected on the file system.
this.originalHandler = this.handler;
this.originalScope = this.scope;
this.handler = null;
this.scope = null;
},

/**
* @see Ext.Button.onRender
*/
onRender: function(ct, position){
Ext.ux.UploadDialog.BrowseButton.superclass.onRender.call(this, ct, position); // render the Ext.Button
// Patch for compatibility with 3.x (@author 4Him, based on dario's 05-10-2009 post)
if(this.isExt2x) {
this.buttonCt = this.el.child('.x-btn-center em');
} else {
this.buttonCt = this.el.child('.x-btn-mc em');
}
this.buttonCt.position('relative'); // this is important!
var styleCfg = {
position: 'absolute',
overflow: 'hidden',
top: '0px', // default
left: '0px' // default
};
// browser specifics for better overlay tightness - modified by 4Him
for(var browser in this.BROWSERS_OFFSETS.Ext2) {
if(Ext['is'+browser]) {
Ext.apply(styleCfg, {
left: this.getBrowserOffset(browser, 'left')+'px',
top: this.getBrowserOffset(browser, 'top')+'px'
});
break;
}
}
this.clipEl = this.buttonCt.createChild({
tag: 'div',
style: styleCfg
});
this.setClipSize();
this.clipEl.on({
'mousemove': this.onButtonMouseMove,
'mouseover': this.onButtonMouseMove,
scope: this
});

this.floatEl = this.clipEl.createChild({
tag: 'div',
style: {
position: 'absolute',
width: this.FLOAT_EL_WIDTH + 'px',
height: this.FLOAT_EL_HEIGHT + 'px',
overflow: 'hidden'
}
});


if (this.debug) {
this.clipEl.applyStyles({
'background-color': 'green'
});
this.floatEl.applyStyles({
'background-color': 'red'
});
} else {
// We don't set the clipEl to be transparent, because IE 6/7 occassionaly looses mouse events for transparent elements.
// We have listeners on the clipEl that can't be lost as they're needed for realligning the input file element.
this.floatEl.setOpacity(0.0);
}

// Cover cases where someone tabs to the button:
// Listen to focus of the button so we can translate the focus to the input file el.
var buttonEl = this.el.child(this.buttonSelector);
buttonEl.on('focus', this.onButtonFocus, this);
// In IE, it's possible to tab to the text portion of the input file el.
// We want to listen to keyevents so that if a space is pressed, we "click" the input file el.
if (Ext.isIE) {
this.el.on('keydown', this.onButtonKeyDown, this);
}

this.createInputFile();
},


/*
* Private helper methods:
*/
/**
* Returns an offset based on this.BROWSERS_OFFSET
* If currently using Ext 3.x, tries to find a value for 3.x and if there is none for 3.x, it
* returns a value for 2.x
* @param {string} the desired offset. Can be one of the following: 'left', 'top', 'width', 'height'
* @param {string} browser the browser for which to return the offset
* @return {int} the desired offset
* @author 4Him
*/
getBrowserOffset: function(browser, which) {
if(!this.isExt2x && this.BROWSERS_OFFSETS.Ext3[browser] && this.BROWSERS_OFFSETS.Ext3[browser][which]) {
return this.BROWSERS_OFFSETS.Ext3[browser][which];
} else {
return this.BROWSERS_OFFSETS.Ext2[browser][which];
}
},

/**
* Sets the size of clipEl so that is covering as much of the button as possible.
* @private
*/
setClipSize: function(){
if (this.clipEl) {
var width = this.buttonCt.getWidth();
var height = this.buttonCt.getHeight();
// The button container can have a width and height of zero when it's rendered in a hidden panel.
// This is most noticable when using a card layout, as the items are all rendered but hidden,
// (unless deferredRender is set to true).
// In this case, the clip size can't be determined, so we attempt to set it later.
// This check repeats until the button container has a size.
if (width === 0 || (height === 0 && !Ext.isIE8)) { // ugly hack (Ext.isIE8) by 4Him
this.setClipSize.defer(100, this);
} else {
// Loop by 4Him
for(var browser in this.BROWSERS_OFFSETS.Ext2) {
if(Ext['is'+browser]) {
width = width + this.getBrowserOffset(browser, 'width');
height = height + this.getBrowserOffset(browser, 'height');
break;
}
}
this.clipEl.setSize(width, height);
}
}
},

/**
* Creates the input file element and adds it to inputFileCt.
* The created input file elementis sized, positioned, and styled appropriately.
* Event handlers for the element are set up, and a tooltip is applied if defined in the original config.
* @private
*/
createInputFile: function(){
// When an input file gets detached and set as the child of a different DOM element,
// straggling <em> elements get left behind.
// I don't know why this happens but we delete any <em> elements we can find under the floatEl to prevent a memory leak.
this.floatEl.select('em').each(function(el){
el.remove();
});
this.inputFileEl = this.floatEl.createChild({
tag: 'input',
type: 'file',
size: 1, // must be > 0. It's value doesn't really matter due to our masking div (inputFileCt).
name: this.inputFileName || Ext.id(this.el),
tabindex: this.tabIndex,
// Use the same pointer as an Ext.Button would use. This doesn't work in Firefox.
// This positioning right-aligns the input file to ensure that the "Browse" button is visible.
style: {
position: 'absolute',
cursor: 'pointer',
right: '0px',
top: '0px'
}
});
this.inputFileEl = this.inputFileEl.child('input') || this.inputFileEl;
// IE8 needs opacity on the 'file input' element - @author 4Him
if(Ext.isIE8) {
this.inputFileEl.setOpacity(0.0);
}

// setup events
this.inputFileEl.on({
'click': this.onInputFileClick,
'change': this.onInputFileChange,
'focus': this.onInputFileFocus,
'select': this.onInputFileFocus,
'blur': this.onInputFileBlur,
scope: this
});

// add a tooltip
if (this.tooltip) {
if (typeof this.tooltip == 'object') {
Ext.QuickTips.register(Ext.apply({
target: this.inputFileEl
}, this.tooltip));
} else {
this.inputFileEl.dom[this.tooltipType] = this.tooltip;
}
}
},

/**
* Redirecting focus to the input file element so the user can press space and select files.
* @param {Event} e focus event.
* @private
*/
onButtonFocus: function(e){
if (this.inputFileEl) {
this.inputFileEl.focus();
e.stopEvent();
}
},

/**
* Handler for the IE case where once can tab to the text box of an input file el.
* If the key is a space, we simply "click" the inputFileEl.
* @param {Event} e key event.
* @private
*/
onButtonKeyDown: function(e){
if (this.inputFileEl && e.getKey() == Ext.EventObject.SPACE) {
this.inputFileEl.dom.click();
e.stopEvent();
}
},

/**
* Handler when the cursor moves over the clipEl.
* The floatEl gets centered to the cursor location.
* @param {Event} e mouse event.
* @private
*/
onButtonMouseMove: function(e){
var xy = e.getXY();
xy[0] -= this.FLOAT_EL_WIDTH / 2;
xy[1] -= this.FLOAT_EL_HEIGHT / 2;
this.floatEl.setXY(xy);
},

/**
* Add the visual enhancement to the button when the input file recieves focus.
* This is the tip for the user that now he/she can press space to select the file.
* @private
*/
onInputFileFocus: function(e){
if (!this.isDisabled) {
this.el.addClass("x-btn-over");
}
},

/**
* Removes the visual enhancement from the button.
* @private
*/
onInputFileBlur: function(e){
this.el.removeClass("x-btn-over");
},

/**
* Handler when inputFileEl's "Browse..." button is clicked.
* @param {Event} e click event.
* @private
*/
onInputFileClick: function(e){
e.stopPropagation();
},

/**
* Handler when inputFileEl changes value (i.e. a new file is selected).
* @private
*/
onInputFileChange: function(){
if (this.originalHandler) {
this.originalHandler.call(this.originalScope, this);
}
},


/*
* Public methods:
*/
/**
* Detaches the input file associated with this BrowseButton so that it can be used for other purposed (e.g. uplaoding).
* The returned input file has all listeners and tooltips applied to it by this class removed.
* @param {Boolean} whether to create a new input file element for this BrowseButton after detaching.
* True will prevent creation. Defaults to false.
* @return {Ext.Element} the detached input file element.
*/
detachInputFile: function(noCreate){
var result = this.inputFileEl;

if (typeof this.tooltip == 'object') {
Ext.QuickTips.unregister(this.inputFileEl);
} else {
this.inputFileEl.dom[this.tooltipType] = null;
}
this.inputFileEl.removeAllListeners();
this.inputFileEl = null;

if (!noCreate) {
this.createInputFile();
}
return result;
},

/**
* @return {Ext.Element} the input file element attached to this BrowseButton.
*/
getInputFile: function(){
return this.inputFileEl;
},

/**
* @see Ext.Button.disable
*/
disable: function(){
Ext.ux.UploadDialog.BrowseButton.superclass.disable.call(this);
this.inputFileEl.dom.disabled = true;
},

/**
* @see Ext.Button.enable
*/
enable: function(){
Ext.ux.UploadDialog.BrowseButton.superclass.enable.call(this);
this.inputFileEl.dom.disabled = false;
}
});


/**
* Toolbar file upload browse button.
*
* @class Ext.ux.UploadDialog.TBBrowseButton
*/
Ext.ux.UploadDialog.TBBrowseButton = Ext.extend(Ext.ux.UploadDialog.BrowseButton, {
hideParent: true,

onDestroy: function(){
Ext.ux.UploadDialog.TBBrowseButton.superclass.onDestroy.call(this);
if (this.container) {
this.container.remove();
}
}
});

/**
* Record type for dialogs grid.
*
* @class Ext.ux.UploadDialog.FileRecord
*/
Ext.ux.UploadDialog.FileRecord = Ext.data.Record.create([{
name: 'filename'
}, {
name: 'state',
type: 'int'
}, {
name: 'note'
}, {
name: 'input_element'
}, {
name: 'filetype'
}]);

Ext.ux.UploadDialog.FileRecord.STATE_QUEUE = 0;
Ext.ux.UploadDialog.FileRecord.STATE_FINISHED = 1;
Ext.ux.UploadDialog.FileRecord.STATE_FAILED = 2;
Ext.ux.UploadDialog.FileRecord.STATE_PROCESSING = 3;

/**
* Dialog class.
*
* @class Ext.ux.UploadDialog.Dialog
*/
Ext.ux.UploadDialog.Dialog = function(config){
var default_config = {
//autoScroll : false,
border: false,
width: 450,
height: 300,
minWidth: 450,
minHeight: 300,
plain: true,
constrainHeader: true,
draggable: true,
closable: true,
maximizable: false,
minimizable: false,
resizable: true,
autoDestroy: true,
closeAction: 'hide',
title: this.i18n.title,
cls: 'ext-ux-uploaddialog-dialog',
// --------
url: '',
base_params: {},
permitted_extensions: [],
reset_on_hide: true,
allow_close_on_upload: false,
upload_autostart: false,
post_var_name: 'file'
}
config = Ext.applyIf(config ||
{}, default_config);
config.layout = 'absolute';

Ext.ux.UploadDialog.Dialog.superclass.constructor.call(this, config);
}

Ext.extend(Ext.ux.UploadDialog.Dialog, Ext.Window, {

fsa: null,

state_tpl: null,

form: null,

grid_panel: null,

progress_bar: null,

is_uploading: false,

initial_queued_count: 0,

upload_frame: null,

/**
* @access private
*/
//--------------------------------------------------------------------------------------------- //
initComponent: function(){
Ext.ux.UploadDialog.Dialog.superclass.initComponent.call(this);

// Setting automata protocol
var tt = {
// --------------
'created': {
// --------------
'window-render': [{
action: [this.createForm, this.createProgressBar, this.createGrid],
state: 'rendering'
}],
'destroy': [{
action: this.flushEventQueue,
state: 'destroyed'
}]
},
// --------------
'rendering': {
// --------------
'grid-render': [{
action: [this.fillToolbar/*, this.updateToolbar JYJ*/],
state: 'ready'
}],
'destroy': [{
action: this.flushEventQueue,
state: 'destroyed'
}]
},
// --------------
'ready': {
// --------------
'file-selected': [{
predicate: [this.fireFileTestEvent, this.isPermittedFile],
action: this.addFileToUploadQueue,
state: 'adding-file'
}, {
// If file is not permitted then resetting internal input type file.
action: this.resetAddButton
}],
'grid-selection-change': [{
action: this.updateToolbar
}],
'remove-files': [{
action: [this.removeFiles, this.fireFileRemoveEvent]
}],
'reset-queue': [{
action: [this.resetQueue, this.fireResetQueueEvent]
}],
'start-upload': [{
predicate: this.hasUnuploadedFiles,
action: [this.setUploadingFlag, this.saveInitialQueuedCount, this.updateToolbar, this.updateProgressBar, this.prepareNextUploadTask, this.fireUploadStartEvent],
state: 'uploading'
}, { // Has nothing to upload, do nothing.
}],
'stop-upload': [{ // We are not uploading, do nothing. Can be posted by user only at this state.
}],
'hide': [{
predicate: [this.isNotEmptyQueue, this.getResetOnHide],
action: [this.resetQueue, this.fireResetQueueEvent]
}, { // Do nothing
}],
'destroy': [{
action: this.flushEventQueue,
state: 'destroyed'
}]
},
// --------------
'adding-file': {
// --------------
'file-added': [{
predicate: this.isUploading,
action: [this.incInitialQueuedCount, this.updateProgressBar, this.fireFileAddEvent],
state: 'uploading'
}, {
predicate: this.getUploadAutostart,
action: [this.startUpload, this.fireFileAddEvent],
state: 'ready'
}, {
action: [this.updateToolbar, this.fireFileAddEvent],
state: 'ready'
}]
},
// --------------
'uploading': {
// --------------
'file-selected': [{
predicate: [this.fireFileTestEvent, this.isPermittedFile],
action: this.addFileToUploadQueue,
state: 'adding-file'
}, {
// If file is not permitted then resetting internal input type file.
action: this.resetAddButton
}],
'grid-selection-change': [{ // Do nothing.
}],
'start-upload': [{ // Can be posted only by user in this state.
}],
'stop-upload': [{
predicate: this.hasUnuploadedFiles,
action: [this.resetUploadingFlag, this.abortUpload, this.updateToolbar, this.updateProgressBar, this.fireUploadStopEvent],
state: 'ready'
}, {
action: [this.resetUploadingFlag, this.abortUpload, this.updateToolbar, this.updateProgressBar, this.fireUploadStopEvent, this.fireUploadCompleteEvent],
state: 'ready'
}],
'file-upload-start': [{
predicate: this.fireBeforeFileUploadStartEvent,
action: [this.uploadFile, this.findUploadFrame, this.fireFileUploadStartEvent]
}, {
action: this.postFileUploadCancelEvent
}],
'file-upload-success': [{
predicate: this.hasUnuploadedFiles,
action: [this.resetUploadFrame, this.updateRecordState, this.updateProgressBar, this.prepareNextUploadTask, this.fireUploadSuccessEvent]
}, {
action: [this.resetUploadFrame, this.resetUploadingFlag, this.updateRecordState, this.updateToolbar, this.updateProgressBar, this.fireUploadSuccessEvent, this.fireUploadCompleteEvent],
state: 'ready'
}],
'file-upload-error': [{
predicate: this.hasUnuploadedFiles,
action: [this.resetUploadFrame, this.updateRecordState, this.updateProgressBar, this.prepareNextUploadTask, this.fireUploadErrorEvent]
}, {
action: [this.resetUploadFrame, this.resetUploadingFlag, this.updateRecordState, this.updateToolbar, this.updateProgressBar, this.fireUploadErrorEvent, this.fireUploadCompleteEvent],
state: 'ready'
}],
'file-upload-failed': [{
predicate: this.hasUnuploadedFiles,
action: [this.resetUploadFrame, this.updateRecordState, this.updateProgressBar, this.prepareNextUploadTask, this.fireUploadFailedEvent]
}, {
action: [this.resetUploadFrame, this.resetUploadingFlag, this.updateRecordState, this.updateToolbar, this.updateProgressBar, this.fireUploadFailedEvent, this.fireUploadCompleteEvent],
state: 'ready'
}],
'file-upload-canceled': [{
predicate: this.hasUnuploadedFiles,
action: [this.setRecordCanceledState, this.updateProgressBar, this.prepareNextUploadTask, this.fireUploadCanceledEvent]
}, {
action: [this.resetUploadingFlag, this.setRecordCanceledState, this.updateToolbar, this.updateProgressBar, this.fireUploadCanceledEvent, this.fireUploadCompleteEvent],
state: 'ready'
}],
'hide': [{
predicate: this.getResetOnHide,
action: [this.stopUpload, this.repostHide]
}, { // Do nothing.
}],
'destroy': [{
predicate: this.hasUnuploadedFiles,
action: [this.resetUploadingFlag, this.abortUpload, this.fireUploadStopEvent, this.flushEventQueue],
state: 'destroyed'
}, {
action: [this.resetUploadingFlag, this.abortUpload, this.fireUploadStopEvent, this.fireUploadCompleteEvent, this.flushEventQueue],
state: 'destroyed'
}]
},
// --------------
'destroyed': { // --------------
}
}
this.fsa = new Ext.ux.Utils.FSA('created', tt, this);

// Registering dialog events.
this.addEvents({
'filetest': true,
'fileadd': true,
'fileremove': true,
'resetqueue': true,
'uploadsuccess': true,
'uploaderror': true,
'uploadfailed': true,
'uploadcanceled': true,
'uploadstart': true,
'uploadstop': true,
'uploadcomplete': true,
'beforefileuploadstart': true,
'fileuploadstart': true
});

// Attaching to window events.
this.on('render', this.onWindowRender, this);
this.on('beforehide', this.onWindowBeforeHide, this);
this.on('hide', this.onWindowHide, this);
this.on('destroy', this.onWindowDestroy, this);

// Compiling state template.
this.state_tpl = new Ext.Template("<div class='ext-ux-uploaddialog-state ext-ux-uploaddialog-state-{state}'> </div>").compile();

this.filetype_tpl = new Ext.Template("<div class='ext-ux-uploaddialog-filetype file-{filetype}'> </div>").compile();

},

createForm: function(){
this.form = Ext.DomHelper.append(this.body, {
tag: 'form',
method: 'post',
action: this.url,
style: 'position: absolute; left: -100px; top: -100px; width: 100px; height: 100px'
});
},

createProgressBar: function(){
this.progress_bar = this.add(new Ext.ProgressBar({
x: 0,
y: 0,
anchor: '0',
value: 0.0,
text: this.i18n.progress_waiting_text
}));
},

createGrid: function(){
var store = new Ext.data.Store({
proxy: new Ext.data.MemoryProxy([]),
reader: new Ext.data.JsonReader({}, Ext.ux.UploadDialog.FileRecord),
sortInfo: {
field: 'state',
direction: 'DESC'
},
pruneModifiedRecords: true
});

var cm = new Ext.grid.ColumnModel([ //new Ext.grid.RowNumberer(),

{
header: this.i18n.state_col_title,
width: this.i18n.state_col_width,
resizable: false,
dataIndex: 'state',
sortable: true,
width: 40,
renderer: this.renderStateCell.createDelegate(this)
}, {
header: this.i18n.filetype_col_title,
width: this.i18n.filetype_col_width,
resizable: false,
dataIndex: 'filetype',
sortable: true,
width: 40,
renderer: this.renderFiletypeCell.createDelegate(this)
}, {
header: this.i18n.filename_col_title,
width: this.i18n.filename_col_width,
dataIndex: 'filename',
sortable: true,
width: 100,
renderer: this.renderFilenameCell.createDelegate(this)
}, {
id: 'note',
header: this.i18n.note_col_title,
width: this.i18n.note_col_width,
dataIndex: 'note',
sortable: true,
width: 100,
renderer: this.renderNoteCell.createDelegate(this)
}]);

this.grid_panel = new Ext.grid.GridPanel({
ds: store,
cm: cm,

x: 0,
y: 22,
anchor: '0 0',
border: true,

cls: 'ext-ux-uploaddialog',
//stripeRows : true,
//autoScroll : true,
//width:350,
//height:500,
autoExpandColumn: 'note',

viewConfig: {
autoFill: true,
forceFit: true
},


bbar: new Ext.Toolbar()
});
this.grid_panel.on('render', this.onGridRender, this);

this.add(this.grid_panel);

this.grid_panel.getSelectionModel().on('selectionchange', this.onGridSelectionChange, this);
},

fillToolbar: function(){
var tb = this.grid_panel.getBottomToolbar();
tb.x_buttons = {}

tb.x_buttons.add = tb.addItem(new Ext.ux.UploadDialog.TBBrowseButton({
input_name: this.post_var_name,
text: this.i18n.add_btn_text,
tooltip: this.i18n.add_btn_tip,
iconCls: 'ext-ux-uploaddialog-addbtn',
handler: this.onAddButtonFileSelected,
scope: this
}));

tb.x_buttons.remove = tb.addButton({
text: this.i18n.remove_btn_text,
tooltip: this.i18n.remove_btn_tip,
iconCls: 'ext-ux-uploaddialog-removebtn',
handler: this.onRemoveButtonClick,
scope: this
});

tb.x_buttons.reset = tb.addButton({
text: this.i18n.reset_btn_text,
tooltip: this.i18n.reset_btn_tip,
iconCls: 'ext-ux-uploaddialog-resetbtn',
handler: this.onResetButtonClick,
scope: this
});

tb.add('-');

tb.x_buttons.upload = tb.addButton({
text: this.i18n.upload_btn_start_text,
tooltip: this.i18n.upload_btn_start_tip,
iconCls: 'ext-ux-uploaddialog-uploadstartbtn',
handler: this.onUploadButtonClick,
scope: this
});

tb.add('-');

/* JYJ
var indicElt = Ext.DomHelper.append(tb.getEl(), {
tag: 'div',
cls: 'ext-ux-uploaddialog-indicator-stoped',
html: ' '
});*/
var indicElt = new Ext.BoxComponent({
autoEl: {
tag: 'div',
cls: 'ext-ux-uploaddialog-indicator-stoped',
html: ' '
}
});
tb.x_buttons.indicator = tb.addItem(new Ext.Toolbar.Item(indicElt));

tb.add('->');

tb.x_buttons.close = tb.addButton({
text: this.i18n.close_btn_text,
tooltip: this.i18n.close_btn_tip,
handler: this.onCloseButtonClick,
scope: this
});
},

renderFiletypeCell: function(data, cell, record, row_index, column_index, store){
//return 'toto'; //this.state_tpl.apply({state: data});
return this.filetype_tpl.apply({
filetype: data
});
},

renderStateCell: function(data, cell, record, row_index, column_index, store){
return this.state_tpl.apply({
state: data
});
},

renderFilenameCell: function(data, cell, record, row_index, column_index, store){
var view = this.grid_panel.getView();
var f = function(){
try {
Ext.fly(view.getCell(row_index, column_index)).child('.x-grid3-cell-inner').dom['qtip'] = data;
}
catch (e) {
}
}
f.defer(1000);
return data;
},

renderNoteCell: function(data, cell, record, row_index, column_index, store){
var view = this.grid_panel.getView();
var f = function(){
try {
Ext.fly(view.getCell(row_index, column_index)).child('.x-grid3-cell-inner').dom['qtip'] = data;
}
catch (e) {
}
}
f.defer(1000);
return data;
},

getFileExtension: function(filename){
var result = null;
var parts = filename.split('.');
if (parts.length > 1) {
result = parts.pop();
}
return result;
},

isPermittedFileType: function(filename){
var result = true;
if (this.permitted_extensions.length > 0) {
result = this.permitted_extensions.indexOf(this.getFileExtension(filename)) != -1;
}
return result;
},

isPermittedFile: function(browse_btn){
var result = false;
var filename = browse_btn.getInputFile().dom.value;

if (this.isPermittedFileType(filename)) {
result = true;
}
else {
Ext.Msg.alert(this.i18n.error_msgbox_title, String.format(this.i18n.err_file_type_not_permitted, filename, this.permitted_extensions.join(this.i18n.permitted_extensions_join_str)));
result = false;
}

return result;
},

fireFileTestEvent: function(browse_btn){
return this.fireEvent('filetest', this, browse_btn.getInputFile().dom.value) !== false;
},

addFileToUploadQueue: function(browse_btn){
var input_file = browse_btn.detachInputFile();

input_file.appendTo(this.form);
input_file.setStyle('width', '100px');
input_file.dom.disabled = true;

var filetype = this.getFileExtension(input_file.dom.value);

var store = this.grid_panel.getStore();
store.add(new Ext.ux.UploadDialog.FileRecord({
state: Ext.ux.UploadDialog.FileRecord.STATE_QUEUE,
filename: input_file.dom.value,
note: this.i18n.note_queued_to_upload,
input_element: input_file,
filetype: filetype
}));
this.fsa.postEvent('file-added', input_file.dom.value);
},

fireFileAddEvent: function(filename){
this.fireEvent('fileadd', this, filename);
},

updateProgressBar: function(){
if (this.is_uploading) {
var queued = this.getQueuedCount(true);
var value = 1 - queued / this.initial_queued_count;
this.progress_bar.updateProgress(value, String.format(this.i18n.progress_uploading_text, this.initial_queued_count - queued, this.initial_queued_count));
}
else {
this.progress_bar.updateProgress(0, this.i18n.progress_waiting_text);
}
},

updateToolbar: function(){
var tb = this.grid_panel.getBottomToolbar();
if (this.is_uploading) {
tb.x_buttons.remove.disable();
tb.x_buttons.reset.disable();
tb.x_buttons.upload.enable();
if (!this.getAllowCloseOnUpload()) {
tb.x_buttons.close.disable();
}
Ext.fly(tb.x_buttons.indicator.getEl()).replaceClass('ext-ux-uploaddialog-indicator-stoped', 'ext-ux-uploaddialog-indicator-processing');
tb.x_buttons.upload.setIconClass('ext-ux-uploaddialog-uploadstopbtn');
tb.x_buttons.upload.setText(this.i18n.upload_btn_stop_text);
tb.x_buttons.upload.getEl().child(tb.x_buttons.upload.buttonSelector).dom[tb.x_buttons.upload.tooltipType] = this.i18n.upload_btn_stop_tip;
}
else {
tb.x_buttons.remove.enable();
tb.x_buttons.reset.enable();
tb.x_buttons.close.enable();
Ext.fly(tb.x_buttons.indicator.getEl()).replaceClass('ext-ux-uploaddialog-indicator-processing', 'ext-ux-uploaddialog-indicator-stoped');
tb.x_buttons.upload.setIconClass('ext-ux-uploaddialog-uploadstartbtn');
tb.x_buttons.upload.setText(this.i18n.upload_btn_start_text);
tb.x_buttons.upload.getEl().child(tb.x_buttons.upload.buttonSelector).dom[tb.x_buttons.upload.tooltipType] = this.i18n.upload_btn_start_tip;

if (this.getQueuedCount() > 0) {
tb.x_buttons.upload.enable();
}
else {
tb.x_buttons.upload.disable();
}

if (this.grid_panel.getSelectionModel().hasSelection()) {
tb.x_buttons.remove.enable();
}
else {
tb.x_buttons.remove.disable();
}

if (this.grid_panel.getStore().getCount() > 0) {
tb.x_buttons.reset.enable();
}
else {
tb.x_buttons.reset.disable();
}
}
},

saveInitialQueuedCount: function(){
this.initial_queued_count = this.getQueuedCount();
},

incInitialQueuedCount: function(){
this.initial_queued_count++;
},

setUploadingFlag: function(){
this.is_uploading = true;
},

resetUploadingFlag: function(){
this.is_uploading = false;
},

prepareNextUploadTask: function(){
// Searching for first unuploaded file.
var store = this.grid_panel.getStore();
var record = null;

store.each(function(r){
if (!record && r.get('state') == Ext.ux.UploadDialog.FileRecord.STATE_QUEUE) {
record = r;
}
else {
r.get('input_element').dom.disabled = true;
}
});

record.get('input_element').dom.disabled = false;
record.set('state', Ext.ux.UploadDialog.FileRecord.STATE_PROCESSING);
record.set('note', this.i18n.note_processing);
record.commit();

this.fsa.postEvent('file-upload-start', record);
},

fireUploadStartEvent: function(){
this.fireEvent('uploadstart', this);
},

removeFiles: function(file_records){
var store = this.grid_panel.getStore();
for (var i = 0, len = file_records.length; i < len; i++) {
var r = file_records[i];
r.get('input_element').remove();
store.remove(r);
}
},

fireFileRemoveEvent: function(file_records){
for (var i = 0, len = file_records.length; i < len; i++) {
this.fireEvent('fileremove', this, file_records[i].get('filename'), file_records[i]);
}
},

resetQueue: function(){
var store = this.grid_panel.getStore();
store.each(function(r){
r.get('input_element').remove();
});
store.removeAll();
},

fireResetQueueEvent: function(){
this.fireEvent('resetqueue', this);
},

uploadFile: function(record){
Ext.Ajax.request({
url: this.url,
params: this.base_params || this.baseParams || this.params,
method: 'POST',
form: this.form,
isUpload: true,
success: this.onAjaxSuccess,
failure: this.onAjaxFailure,
scope: this,
record: record
});
},

fireBeforeFileUploadStartEvent: function(record){
return this.fireEvent('beforefileuploadstart', this, record.get('filename'), record) !== false;
},

postFileUploadCancelEvent: function(record){
this.fsa.postEvent('file-upload-canceled', record);
},

setRecordCanceledState: function(record){
record.set('state', Ext.ux.UploadDialog.FileRecord.STATE_FAILED);
record.set('note', this.i18n.note_canceled);
record.commit();
},

fireUploadCanceledEvent: function(record){
this.fireEvent('uploadcanceled', this, record.get('filename'), record);
},

fireFileUploadStartEvent: function(record){
this.fireEvent('fileuploadstart', this, record.get('filename'), record);
},

/*
* echo json_encode(array('success'=>$success, 'error'=>$msg, 'error_num'=>$retCode, 'param'=>$extra));
*/
updateRecordState: function(data){
if ('success' in data.response && data.response.success) {
data.record.set('state', Ext.ux.UploadDialog.FileRecord.STATE_FINISHED);
/*var msg = '';
if (this.i18n.note_upload_success.indexOf('{0}')>0) {
msg = String.format(this.i18n.note_upload_success, data.response.message || data.response.error);
} else {
msg = data.response.message || data.response.error || this.i18n.note_upload_success ;
}
data.record.set(
'note', msg
);*/
}
else {
data.record.set('state', Ext.ux.UploadDialog.FileRecord.STATE_FAILED);
/*var msg = '';
if (this.i18n.note_upload_error.indexOf('{0}')>0) {
msg = String.format(this.i18n.note_upload_error, data.response.message || data.response.error);
} else {
msg = data.response.message || data.response.error || this.i18n.note_upload_error ;
}
data.record.set(
'note', msg
);*/
}
data.record.set('note', this.makeNoteMessage(data.response));

data.record.commit();
},

formatFileSize: function(s){
return Ext.util.Format.fileSize(s);
},

makeNoteMessage: function(response){
var msg;
var model = null;
if (!response.success) {
if (response.error_num) {
switch (response.error_num) {
case 100: //define ("ERROR_UNKNOWN", 100);
model = this.i18n.note_upload_error;
break;
case 101: //define ("ERROR_FILESIZE_EXCEED_PHP_LIMIT", 101);
model = this.i18n.note_upload_error_filesize_exceed_php_limit;
break;
case 102: //define ("ERROR_FILESIZE_EXCEED_PHP_FORM_LIMIT", 102);
model = this.i18n.note_upload_error_filesize_exceed_php_form_limit;
break;
case 103: //define ("ERROR_FILESIZE_EXCEED_LIMIT", 103);
model = this.i18n.note_upload_error_filesize_exceed_limit;
break;
case 104: //define ("ERROR_UPLOADIR_DOES_NOT_EXIST", 104);
model = this.i18n.note_upload_error_uploadir_does_not_exist;
break;
case 105: //define ("ERROR_UPLOADIR_NOT_WRITABLE", 105);
model = this.i18n.note_upload_error_uploadir_not_writable;
break;
case 106: //define ("ERROR_FILE_PARTIALLY_UPLOADED", 106);
model = this.i18n.note_upload_error_file_partially_loaded;
break;
case 107: //define ("ERROR_FILE_NOT_UPLOADED", 107);
model = this.i18n.note_upload_error_file_not_uploaded;
break;
default:
model = this.i18n.note_upload_error;
break;
}
if (model && model.indexOf('{0}') > 0 && response.param) {
msg = String.format(model, response.param);
}
else {
msg = model;
}
}
else {
msg = response.message || response.error || this.i18n.note_upload_error;
}
}
else {
if (this.i18n.note_upload_success.indexOf('{0}') > 0 && response.param) {
var size = this.formatFileSize(response.param);
msg = String.format(this.i18n.note_upload_success, size);
}
else {
msg = response.message || this.i18n.note_upload_success;
}
}
return msg;
},

fireUploadSuccessEvent: function(data){
this.fireEvent('uploadsuccess', this, data.record.get('filename'), data.response, data.record);
},

fireUploadErrorEvent: function(data){
this.fireEvent('uploaderror', this, data.record.get('filename'), data.response, data.record);
},

fireUploadFailedEvent: function(data){
this.fireEvent('uploadfailed', this, data.record.get('filename'), data.record);
},

fireUploadCompleteEvent: function(){
this.fireEvent('uploadcomplete', this);
},

findUploadFrame: function(){
this.upload_frame = Ext.getBody().child('iframe.x-hidden:last');
},

resetUploadFrame: function(){
this.upload_frame = null;
},

removeUploadFrame: function(){
if (this.upload_frame) {
this.upload_frame.removeAllListeners();
this.upload_frame.dom.src = 'about:blank';
this.upload_frame.remove();
}
this.upload_frame = null;
},

abortUpload: function(){
this.removeUploadFrame();

var store = this.grid_panel.getStore();
var record = null;
store.each(function(r){
if (r.get('state') == Ext.ux.UploadDialog.FileRecord.STATE_PROCESSING) {
record = r;
return false;
}
});

record.set('state', Ext.ux.UploadDialog.FileRecord.STATE_FAILED);
record.set('note', this.i18n.note_aborted);
record.commit();
},

fireUploadStopEvent: function(){
this.fireEvent('uploadstop', this);
},

repostHide: function(){
this.fsa.postEvent('hide');
},

flushEventQueue: function(){
this.fsa.flushEventQueue();
},

resetAddButton: function(browse_btn){
browse_btn.detachInputFile();
},

/**
* @access private
*/
// -------------------------------------------------------------------------------------------- //
onWindowRender: function(){
this.fsa.postEvent('window-render');
},

onWindowBeforeHide: function(){
return this.isUploading() ? this.getAllowCloseOnUpload() : true;
},

onWindowHide: function(){
this.fsa.postEvent('hide');
},

onWindowDestroy: function(){
this.fsa.postEvent('destroy');
},

onGridRender: function(){
this.fsa.postEvent('grid-render');
},

onGridSelectionChange: function(){
this.fsa.postEvent('grid-selection-change');
},

onAddButtonFileSelected: function(btn){
this.fsa.postEvent('file-selected', btn);
},

onUploadButtonClick: function(){
if (this.is_uploading) {
this.fsa.postEvent('stop-upload');
}
else {
this.fsa.postEvent('start-upload');
}
},

onRemoveButtonClick: function(){
var selections = this.grid_panel.getSelectionModel().getSelections();
this.fsa.postEvent('remove-files', selections);
},

onResetButtonClick: function(){
this.fsa.postEvent('reset-queue');
},

onCloseButtonClick: function(){
this[this.closeAction].call(this);
},

/*
* echo json_encode(array('success'=>$success, 'error'=>$msg, 'error_num'=>$retCode, 'param'=>$extra));
*/
onAjaxSuccess: function(response, options){
var json_response = {
'success': false,
'error': this.i18n.note_upload_error
}
try {
var rt = response.responseText;
var filter = rt.match(/^<[^>]+>((?:.|\n)*)<\/[^>]+>$/);
if (filter) {
rt = filter[1];
}
json_response = Ext.util.JSON.decode(rt);
}
catch (e) {
}

var data = {
record: options.record,
response: json_response
}

if ('success' in json_response && json_response.success) {
this.fsa.postEvent('file-upload-success', data);
}
else {
this.fsa.postEvent('file-upload-error', data);
}
},

onAjaxFailure: function(response, options){
var data = {
record: options.record,
response: {
'success': false,
'error': this.i18n.note_upload_failed
}
}

this.fsa.postEvent('file-upload-failed', data);
},

/**
* @access public
*/
// -------------------------------------------------------------------------------------------- //
startUpload: function(){
this.fsa.postEvent('start-upload');
},

stopUpload: function(){
this.fsa.postEvent('stop-upload');
},

getUrl: function(){
return this.url;
},

setUrl: function(url){
this.url = url;
},

getBaseParams: function(){
return this.base_params;
},

setBaseParams: function(params){
this.base_params = params;
},

getUploadAutostart: function(){
return this.upload_autostart;
},

setUploadAutostart: function(value){
this.upload_autostart = value;
},

getAllowCloseOnUpload: function(){
return this.allow_close_on_upload;
},

setAllowCloseOnUpload: function(value){
this.allow_close_on_upload = value;
},

getResetOnHide: function(){
return this.reset_on_hide;
},

setResetOnHide: function(value){
this.reset_on_hide = value;
},

getPermittedExtensions: function(){
return this.permitted_extensions;
},

setPermittedExtensions: function(value){
this.permitted_extensions = value;
},

isUploading: function(){
return this.is_uploading;
},

isNotEmptyQueue: function(){
return this.grid_panel.getStore().getCount() > 0;
},

getQueuedCount: function(count_processing){
var count = 0;
var store = this.grid_panel.getStore();
store.each(function(r){
if (r.get('state') == Ext.ux.UploadDialog.FileRecord.STATE_QUEUE) {
count++;
}
if (count_processing && r.get('state') == Ext.ux.UploadDialog.FileRecord.STATE_PROCESSING) {
count++;
}
});
return count;
},

hasUnuploadedFiles: function(){
return this.getQueuedCount() > 0;
}
});

// ---------------------------------------------------------------------------------------------- //

var p = Ext.ux.UploadDialog.Dialog.prototype;
p.i18n = {
title: 'File upload dialog',
state_col_title: 'State',
state_col_width: 70,
filename_col_title: 'Filename',
filename_col_width: 230,

filetype_col_title: 'Type',
filetype_col_width: 70,

note_col_title: 'Note',
note_col_width: 150,
add_btn_text: 'Add',
add_btn_tip: 'Add file into upload queue.',
remove_btn_text: 'Remove',
remove_btn_tip: 'Remove file from upload queue.',
reset_btn_text: 'Reset',
reset_btn_tip: 'Reset queue.',
upload_btn_start_text: 'Upload',
upload_btn_stop_text: 'Abort',
upload_btn_start_tip: 'Upload queued files to the server.',
upload_btn_stop_tip: 'Stop upload.',
close_btn_text: 'Close',
close_btn_tip: 'Close the dialog.',
progress_waiting_text: 'Waiting...',
progress_uploading_text: 'Uploading: {0} of {1} files complete.',
error_msgbox_title: 'Error',
permitted_extensions_join_str: ',',
err_file_type_not_permitted: 'Selected file extension isn\'t permitted.<br/>Please select files with following extensions: {1}',
note_queued_to_upload: 'Queued for upload.',
note_processing: 'Uploading...',
note_upload_failed: 'Server is unavailable or internal server error occured.',
note_upload_success: 'OK.',
note_upload_error: 'Upload error.',
note_aborted: 'Aborted by user.',
note_canceled: 'Upload canceled',

note_upload_error_filesize_exceed_php_limit: 'The file size exeeds the upload_max_filesize limit fixed in php.ini',
note_upload_error_filesize_exceed_php_form_limit: 'The file size exeeds the post_max_size limit fixed in php.ini',
note_upload_error_filesize_exceed_limit: 'File too large php limit MaxSize={0}Mb',
note_upload_error_uploadir_does_not_exist: 'Upload dir {0} does not exist',
note_upload_error_uploadir_not_writable: 'Upload dir {0} is not writeable',
note_upload_error_file_partially_loaded: 'Only part of the file was uploaded',
note_upload_error_file_not_uploaded: 'File not uploaded'

}
Hope this help.

anjelika
27 Oct 2009, 12:40 AM
Thanks, will try that...I hope it works.
Did you manage to actually upload the file when you change the card (click Next) or at the end (Finish)?

pezze
27 Oct 2009, 5:53 AM
Thanks, will try that...I hope it works.
Did you manage to actually upload the file when you change the card (click Next) or at the end (Finish)?

I've used the normal upload callback.

For example:



function getExcelUploadDialog(){
if (!excelUploadDialog) {
excelUploadDialog = new Ext.ux.UploadDialog.Dialog({
url: '/Secure/Upload/Js-Upload.aspx',
reset_on_hide: false,
allow_close_on_upload: true,
upload_autostart: true,
post_var_name: 'upload',
permitted_extensions: ['xls']
});

excelUploadDialog.on('uploadsuccess', onExcelUploadSuccess);
}
return excelUploadDialog;
}

function showExcelUploadDialog(btn){
getExcelUploadDialog().show(btn.getEl());
}

letterNumber = 0;

function onExcelUploadSuccess(dialog, filename, data, record){
var jsonFileObject = Ext.util.JSON.decode(data.jsonObject);

Ext.getCmp('excelUploadRealFilename').setValue(jsonFileObject.realFileName);

dialog.hide();
}


In the end of procedure I've my filename already loaded in 'excelUploadRealFilename' hidden field.

Here it is how the hidden field appears in a card:



new Ext.form.TextField({
id : 'excelUploadRealFilename',
allowBlank: false,
hidden: true
}),


so if the field is empty the customer can't go to the next card.

anjelika
27 Oct 2009, 6:24 AM
Thanks for the example but this is not the solution I was looking for.
You are using another extension in order to upload the file. I want just to insert an upload form field into the wizard, click to browse for my file and when I press 'Next' to go to the next card...right then to upload the file.
Using the UploadDialog it worked for me also but is not what I wanted.
Will try to make a compromise and maybe user the UploadDialog extension, will see...
Thanks

Spirit
29 Oct 2009, 1:06 AM
works...thx

swarm
30 Oct 2009, 7:28 AM
Howdy all, what a brilliant UX this is!!

Has anyone experienced any of these two issues though?

1) I can add a grid or editorgrid quite nicely - but if I want a tbar or bbar it just doesn't display... Doesn't work on FF or IE.

2) Random columnness... a form with two columns. Displays fine in IE but not in FF(!)

Here's the code for the card... seems pretty normal to me. The only way I can get the columns to display in FF is to open my firebug toolbar and close it again. It's only the stuff inside the columns that doesn't show.




new Ext.ux.Wiz.Card({
title: 'Mandatory Contact Information',
id: 'card-3',
defaults: {labelStyle : 'font-size:11px'},
items: [{
border : false,
bodyStyle : 'background:none; padding: 0 0 25px 0;',
html : 'We require an email address and contact address as part of Account Creation.<br/<br>Please be reassured that NES only contact you if there is a genuine business reason to do so and will <b>never</b> pass this information onto third parties.'

},{
//xtype: 'container',
layout: 'column',
width: 600,
border: false,
defaults: { columnWidth: '.5', border: false },
items: [{
bodyStyle: 'padding-right:5px;',
items: [{
layout: 'form',
border: false,
defaults: {labelStyle : 'font-size:11px'},
items: [{
xtype : 'textfield',
fieldLabel : 'Email Address',
allowBlank : false
}]
}]
},{
bodyStyle: 'padding-left:5px;',
items: [{
layout: 'form',
border: false,
defaults: {labelStyle : 'font-size:11px'},
items: [{
xtype : 'textfield',
fieldLabel : 'Mobile Phone',
allowBlank : false
}]
}]
}]
},{
border : false,
bodyStyle : 'background:none; padding: 25px 0 25px 0;',
html : 'We use postcode look-up software to ensure the address is valid; all you need to do is insert your postcode and select from the list returned to the screen.'
},{
//xtype: 'container',
layout: 'column',
width: 600,
border: false,
defaults: { columnWidth: '.5', border: false },
items: [{
bodyStyle: 'padding-right:5px;',
items: [{
layout: 'form',
border: false,
defaults: {labelStyle : 'font-size:11px'},
items: [{
xtype : 'textfield',
fieldLabel : 'Postcode',
allowBlank : false
},new Ext.Button({
text : 'Address Lookup',
icon : 'images/icons/gifs/find.gif',
xtype : 'button',
width : 75,
style : 'padding:5px 0px 25px 131px;'
})]
}]
},{
bodyStyle: 'padding-left:5px;',
items: [{
layout: 'form',
border: false,
defaults: {labelStyle : 'font-size:11px'},
items: [{
xtype : 'textfield',
fieldLabel : 'Address Line 1',
disabled : true
},{
xtype : 'textfield',
fieldLabel : 'Address Line 2',
disabled : true
},{
xtype : 'textfield',
fieldLabel : 'City',
disabled : true
},{
xtype : 'textfield',
fieldLabel : 'Postcode',
disabled : true
}]

}]
}]
},{
border : false,
bodyStyle : 'background:none; padding: 25px 0 0 0;',
html : 'The "Next" button remains disabled until an email address and valid address have been entered.'
}]

}),



Any ideas? to either of the problems?

swarm
30 Oct 2009, 7:53 AM
1) I can add a grid or editorgrid quite nicely - but if I want a tbar or bbar it just doesn't display... Doesn't work on FF or IE.



I've managed to resolve this issue - it's not quite as neat as i'd normally make it - but this seems to work:



var directorateGrid = new Ext.grid.EditorGridPanel({
title : 'Your Professional Details',
store : professionStore,
clicksToEdit : 1,
height : 100,
width : 485,
forceFit : true,
tbar : [ new Ext.Button({
text: 'Add another Profession',
icon: 'images/icons/gifs/add.gif'
// etc.......
})
],
columns:[{
.....
.........



Now just the issue with the columns. I've look elsewhere on the forum and on this post but not getting anywhere - anyone have any ideas?

prodigy7
5 Nov 2009, 1:16 AM
Thank you very much for this ux - really great!

A question: Is it possible, define somehow the cards with xtype? So far I can see, currently isn't possible and I tried it with Ext.Reg(....) but somehow it look like that the current handling can't work if an card is defined with xtype.

prodigy7
5 Nov 2009, 2:45 AM
Okay ... my current result:
--- Ext.UX.Wiz.Card.js.ORIG 2009-11-05 11:37:56.000000000 +0100
+++ Ext.UX.Wiz.Card.js 2009-11-05 11:38:11.000000000 +0100
@@ -145,3 +145,5 @@
}

});
+
+Ext.reg('wizardcard', Ext.ux.Wiz.Card);So far I understand, this Ext.reg add the xtype ... the initEvents is also called but when the
var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
cards[i].on('show', this.onCardShow, this);
cards[i].on('hide', this.onCardHide, this);
cards[i].on('clientvalidation', this.onClientValidation, this);
}
(Wizard.js) parts come, I get this javascript error:
cards[i].on is not a function
[Break on this error] cards[i].on('show', this.onCardShow, this);
Ext.UX.W...Wizard.js (Row 343)The code I used for this test:
var winHandler = new Ext.ux.Wiz({
id : 'content',
title : 'This is an window',
height : 480,
width : 640,
headerConfig : {
title : ''
},

cardPanelConfig : {
defaults : {
baseCls : 'x-small-editor',
bodyStyle : 'overflow:auto;',
border : false
}
},

cards: [{
xtype: 'wizardcard',
title: 'test1',
items: [{
border: false,
html: 'aaaa'
}]
}]
});

winHandler.show();
I think, I've "only" an error in reasoning, but somehow I need an "kick".

udalaitz
10 Nov 2009, 10:28 AM
First of all, great extension, very useful.
And second, I've got a problem when rendering the contents of a card under the "column" layout.

It seems to be the same problem as swarm.
I'm using Extjs 3.0.0.
In FF3.5 the contents of the card will only be rendered after I minimize firebug or change tab or maximize firebug, ... otherwise nothing will appear. In IE8, nothing happens, no content at all.

If I add the "forceLayout : true" property to the card, the contents are rendered but they are all broken. (see pics attached)

Here is an example: (It's basically the SimpleWizard.html code with the card causing me trouble)



Ext.onReady(function(){

Ext.QuickTips.init();

var wizard = new Ext.ux.Wiz({

title : 'A simple example for a wizard',

headerConfig : {
title : 'Simple Wizard Example'
},

cardPanelConfig : {
defaults : {
baseCls : 'x-small-editor',
bodyStyle : 'padding:40px 15px 5px 120px;background-color:#F6F6F6;',
border : false
}
},

cards : [

// first card with welcome message
new Ext.ux.Wiz.Card({
title : 'Welcome',
items : [{
border : false,
bodyStyle : 'background:none;',
html : 'Welcome to the example for <strong>Ext.ux.Wiz</string>, '+
'a Ext JS user extension for creating wizards.<br/><br/>'+
'Please click the "next"-button and fill out all form values.'
}]
}),

new Ext.ux.Wiz.Card({
title : 'Create a new schedule',
id:'cd_create_schedule_id',
name: 'cd_create_schedule',
defaults : {
labelStyle : 'font-size:11px; width:60px;',
cls:'x-fieldset-custom',
forceLayout : true,
border: false
},
items : [{
html: '<b>Create a new schedule</b>' ,
border : false,
bodyStyle : 'background:none;',
padding : '0 10px 10px 0px'
},{
xtype:'fieldset',
layout:'form',
defaults : {
cls:'x-fieldset-custom',
border: false,
labelWidth: 60
},
items:[{
// column layout with 2 columns
xtype:'fieldset',
layout:'column' ,
defaults:{
columnWidth:0.5 ,
layout:'form',
bodyStyle:'padding:0 18px 0 0;',
border: false
},
items:[{
// left column
xtype:'fieldset',
defaults:{anchor:'100%' , border: false},
items:[{
xtype: 'timefield',
fieldLabel: 'Starting',
name: 'starting_tab'
},{
xtype: 'timefield',
fieldLabel: 'Ending',
name: 'ending_tab'
}]
},{
// right column
xtype:'fieldset',
defaults:{anchor:'100%'},
items:[{
xtype: 'checkbox',
checked: true,
hideLabel: true,
// labelSeparator: ' ',
boxLabel: 'Kill Jobs at end',
ctCls : 'x-wizard-label-size',
name: 'endTask'
}]
}]
},{
xtype: 'fieldset',
layout:'column',
items:[{
layout:'form',
columnWidth: 0.3,
xtype: 'radio',
boxLabel: 'Run Only',
name: 'rb-custwidth_1',
checked: false,
listeners:{
'check' : function(comp, checked){
if (checked == true){
Ext.getCmp('run_only_text_id').setDisabled( false );
Ext.getCmp('run_only_label_id').setDisabled( false );
} else {
// Clear the data
Ext.getCmp('run_only_text_id').setRawValue('');

// Disable the components
Ext.getCmp('run_only_text_id').setDisabled( true );
Ext.getCmp('run_only_label_id').setDisabled( true );
}
}
}
},{
layout:'form',
columnWidth: 0.3,
xtype: 'textfield',
id:'run_only_text_id',
hideLabel: true,
disabled: true
},{
layout:'form',
columnWidth: 0.4,
xtype: 'label',
id:'run_only_label_id',
style: 'margin-left:5px;',
text: 'times',
disabled: true
}]
}]
}]

}),


// fourth card with finish-message
new Ext.ux.Wiz.Card({
title : 'Finished!',
monitorValid : true,
items : [{
border : false,
bodyStyle : 'background:none;',
html : 'Thank you for testing this wizard. Your data has been collected '+
'and can be accessed via a call to <pre><code>this.getWizardData</code></pre>'+
'When you click on the "finish"-button, the "finish"-event will be fired.<br/>'+
'If no attached listener for this event returns "false", this dialog will be '+
'closed. <br />(In this case, our listener will return false after a popup shows the data you just entered)'
}]
})


]
});

// show the wizard
wizard.show();
});


Thanks in advance.

swarm
11 Nov 2009, 2:39 AM
@ udalaitz

Try a doLayout() - That seemed to work nicely for me...

Also double check that you're not over nesting - I was :(

udalaitz
11 Nov 2009, 2:55 AM
Thanks swarm, it's working now.

I also found that without adding "doLayout()" , if I change the layout in Wizard.js from:
line 368:


Ext.apply(cardPanelConfig, {
layout : new Ext.ux.layout.CardLayout(),
items : cards
});
to:


Ext.apply(cardPanelConfig, {
layout : 'card',
items : cards
});
it works.

I prefer your solution because I don't totally understand mine. I looked at the Ext.ux.layout.CardLayout.js file and I don't see the problem. The weird thing is that the wizard seems to work fine too without this extension.

charleshimmer
11 Nov 2009, 9:11 AM
I'm not completely sure but I believe that is because ExtJS 3.x has a card layout now, the wizard extentsion was originally written in Extjs 2.x.

agalue
23 Nov 2009, 6:51 AM
Hello, I'm trying to use the wizard with 3.0.3 but it is not working. When you click on next button, nothing happen.

To reproduce the problem, run the wizard example against extjs 3.0.3.

Any suggestions?

charleshimmer
23 Nov 2009, 6:56 AM
I had problems with more than just the wizard in 3.0.3 so I'm sticking with 3.0.2 while they work out the kinks in 3.0.3.

ThorstenSuckow
23 Nov 2009, 6:59 AM
Use this code for Wizard.js until fixed in SVN



('Ext.ux');

/**
* Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de>
* @url http://www.siteartwork.de/wizardcomponent
*/

/**
* @class Ext.ux.Wiz
* @extends Ext.Window
*
* A specific {@link Ext.Window} that models a wizard component.
* A wizard is basically a dialog that guides a user through various steps
* where he has to fill out form-data.
* A {@link Ext.ux.Wiz}-component consists typically of a {@link Ext.ux.Wiz.Header}
* and window-buttons ({@link Ext.Button}) which are linked to the {@link Ext.ux.Wiz.Card}s
* which themself represent the forms the user has to fill out.
*
* In order to switch between the cards in the wizard, you need the {@link Ext.ux.layout.CardLayout},
* which will check if an active-item can be hidden, before the requested new item will be set to
* 'active', i.e. shown. This is needed since the wizard may not allow a card to be hidden, if
* the input entered by the user was not valid. You can get this custom layout at
* {@link http://www.siteartwork.de/cardlayout}.
*
* Note:
* When data has been collected and teh "onFinish" listener triggers an AJAX-request,
* you should call the "switchDialogState" method so that the the dialog shows a loadmask.
* Once the requests finishes, call "switchDialogState" again, specially before any call
* to the "close" method of this component, otherwise the "closable" property of this
* instance might prevent a "close" operation for this dialog.
*
*
* @constructor
* @param {Object} config The config object
*/
Ext.ux.Wiz = Ext.extend(Ext.Window, {

/**
* @cfg {Object} An object containing the messages for the {@link Ext.LoadMask}
* covering the card-panel on request, whereas the property identifies the
* msg-text to show, and the value is the message text itself. Defaults to
<pre><code>
{
default : 'Saving...'
}
</code></pre>
*
* Depending on the contexts the loadMask has to be shown in (using the method
* showLoadMask of this class), the object can be configure to hold
* various messages.
<pre><code>
this.loadMaskConfig = {
default : 'Saving...',
validating : 'Please wait, validating input...',
};
// loadMask will be shown, displaying the message 'Please wait, validating input...'
this.showLoadMask(true, 'validating');
</code></pre>
*/
loadMaskConfig : {
'default' : 'Saving...'
},

/**
* @cfg {Number} height The height of the dialog. Defaults to "400".
*/
height : 400,

/**
* @cfg {Number} width The width of the dialog. Defaults to "540".
*/
width : 540,

/**
* @cfg {Boolean} closable Wether the dialog is closable. Defaults to "true".
* This property will be changed by the "switchDialogState"-method, which will
* enable/disable controls based on the passed argument. Thus, this config property
* serves two purposes: Tell the init config to render a "close"-tool, and create a
* "beforeclose"-listener which will either return true or false, indicating if the
* dialog may be closed.
*/
closable : true,

/**
* @cfg {Boolean} resizable Wether the dialog is resizable. Defaults to "false".
*/
resizable : false,

/**
* @cfg {Boolean} resizable Wether the dialog is modal. Defaults to "true".
*/
modal : true,

/**
* @cfg {Array} cards A numeric array with the configured {@link Ext.ux.Wiz.Card}s.
* The index of the cards in the array represent the order in which they get displayed
* in the wizard (i.e. card at index 0 gets displayed in the first step, card at index 1 gets
* displayed in the second step and so on).
*/
cards : null,

/**
* @cfg {String} previousButtonText The text to render the previous-button with.
* Defaults to "&lt; Back" (< Back)
*/
previousButtonText : '&lt; Previous',

/**
* @cfg {String} nextButtonText The text to render the next-button with.
* Defaults to "Next &gt;" (Next >)
*/
nextButtonText : 'Next &gt;',

/**
* @cfg {String} cancelButtonText The text to render the cancel-button with.
* Defaults to "Cancel"
*/
cancelButtonText : 'Cancel',

/**
* @cfg {String} finishButtonText The text to render the next-button with when the last
* step of the wizard is reached. Defaults to "Finish"
*/
finishButtonText : 'Finish',

/**
* @cfg {Object} headerConfig A config-object to use with {@link Ext.ux.Wiz.Header}.
* If not present, it defaults to an empty object.
*/
headerConfig : {},

/**
* @cfg {Object} cardPanelConfig A config-object to use with {@link Ext.Panel}, which
* represents the card-panel in this dialog.
* If not present, it defaults to an empty object
*/
cardPanelConfig : {},

/**
* @param {Ext.Button} The window-button for paging to the previous card.
* @private
*/
previousButton : null,

/**
* @param {Ext.Button} The window-button for paging to the next card. When the
* last card is reached, the event fired by and the text rendered to this button
* will change.
* @private
*/
nextButton : null,

/**
* @param {Ext.Button} The window-button for canceling the wizard. The event
* fired by this button will usually close the dialog.
* @private
*/
cancelButton : null,

/**
* @param {Ex.Panel} The card-panel that holds the various wizard cards
* ({@link Ext.ux.Wiz.Card}). The card-panel itself uses the custom
* {@link Ext.ux.layout.CardLayout}, which needs to be accessible by this class.
* You can get it at {@link http://www.siteartwork.de/cardlayout}.
* @private
*/
cardPanel : null,

/**
* @param {Number} currentCard The current {@link Ext.ux.Wiz.Card} displayed.
* Defaults to -1.
* @private
*/
currentCard : -1,

/**
* @param {Ext.ux.Wiz.Header} The header-panel of the wizard.
* @private
*/
headPanel : null,

/**
* @param {Number} cardCount Helper for storing the number of cards used
* by this wizard. Defaults to 0 (inherits "cards.length" later on).
* @private
*/
cardCount : 0,

/**
* Inits this component with the specified config-properties and automatically
* creates its components.
*/
initComponent : function()
{
this.initButtons();
this.initPanels();

var title = this.title || this.headerConfig.title;
title = title || "";

Ext.apply(this, {
title : title,
layout : 'border',
cardCount : this.cards.length,
buttons : [
this.previousButton,
this.nextButton,
this.cancelButton
],
items : [
this.headPanel,
this.cardPanel
]
});

this.addEvents(
/**
* @event cancel
* Fires after the cancel-button has been clicked.
* @param {Ext.ux.Wiz} this
*/
'cancel',
/**
* @event finish
* Fires after the last card was reached in the wizard and the
* next/finish-button has been clicked.
* @param {Ext.ux.Wiz} this
* @param {Object} data The collected data of the cards, whereas
* the index is the id of the card and the specific values
* are objects with key/value pairs in the form formElementName : value
*/
'finish'
);

Ext.ux.Wiz.superclass.initComponent.call(this);
},

// -------- helper
/**
* Returns the form-data of all cards in this wizard. The first index is the
* id of the card in this wizard,
* and the values are objects containing key/value pairs in the form of
* fieldName : fieldValue.
*
* @return {Array}
*/
getWizardData : function()
{
var formValues = {};
var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
if (cards[i].form) {
formValues[cards[i].id] = cards[i].form.getValues(false);
} else {
formValues[cards[i].id] = {};
}
}

return formValues;
},

/**
* Switches the state of this wizard between disabled/enabled.
* A disabled dialog will have a {@link Ext.LoadMask} covering the card-panel
* to prevent user input, and the buttons will be rendered disabled/enabled.
* If the dialog is closable, the close-tool will be masked, too, and the dialog will not
* be closable by clicking the "close" tool.
*
* @param {Boolean} enabled "false" to prevent user input and mask the elements,
* otherwise true.
* @param {String} type The type of msg for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
switchDialogState : function(enabled, type)
{
this.showLoadMask(!enabled, type);

this.previousButton.setDisabled(!enabled);
this.nextButton.setDisabled(!enabled);
this.cancelButton.setDisabled(!enabled);

var ct = this.tools['close'];

if (ct) {
switch (enabled) {
case true:
this.tools['close'].unmask();
break;

default:
this.tools['close'].mask();
break;
}
}

this.closable = enabled;
},

/**
* Shows the load mask for this wizard. By default, the cardPanel's body
* will be masked.
*
* @param {Boolean} show true to show the load mask, otherwise false.
* @param {String} type The type of message for the {@Ext.LoadMask} covering
* the cardPanel, as defined in the cfg property "loadMaskConfig"
*/
showLoadMask : function(show, type)
{
if (!type) {
type = 'default';
}

if (show) {
if (this.loadMask == null) {
this.loadMask = new Ext.LoadMask(this.body);
}
this.loadMask.msg = this.loadMaskConfig[type];
this.loadMask.show();
} else {
if (this.loadMask) {
this.loadMask.hide();
}
}
},


/**
* Inits the listener for the various {@link Ext.ux.Wiz.Card}s used
* by this component.
*/
initEvents : function()
{
Ext.ux.Wiz.superclass.initEvents.call(this);

this.on('beforeclose', this.onBeforeClose, this);
},

/**
* Creates the head- and the card-panel.
* Be sure to have the custom {@link Ext.ux.layout.CardLayout} available
* in order to make the card-panel work as expected by this component
* ({@link http://www.siteartwork.de/cardlayout}).
*/
initPanels : function()
{
var cards = this.cards;
var cardPanelConfig = this.cardPanelConfig;

Ext.apply(this.headerConfig, {
steps : cards.length
});

this.headPanel = new Ext.ux.Wiz.Header(this.headerConfig);

Ext.apply(cardPanelConfig, {
layout : new Ext.ux.layout.CardLayout(),
items : cards
});

Ext.applyIf(cardPanelConfig, {
region : 'center',
border : false,
activeItem : 0
});

var cards = this.cards;

for (var i = 0, len = cards.length; i < len; i++) {
cards[i].on('show', this.onCardShow, this);
cards[i].on('hide', this.onCardHide, this);
cards[i].on('clientvalidation', this.onClientValidation, this);
}

this.cardPanel = new Ext.Panel(cardPanelConfig);
},

/**
* Creates the instances for the the window buttons.
*/
initButtons : function()
{
this.previousButton = new Ext.Button({
text : this.previousButtonText,
disabled : true,
minWidth : 75,
handler : this.onPreviousClick,
scope : this
});

this.nextButton = new Ext.Button({
text : this.nextButtonText,
minWidth : 75,
handler : this.onNextClick,
scope : this
});

this.cancelButton = new Ext.Button({
text : this.cancelButtonText,
handler : this.onCancelClick,
scope : this,
minWidth : 75
});
},

// -------- listeners

/**
* Listener for the beforeclose event.
* This listener will return true or false based on the "closable"
* property by this component. This property will be changed by the "switchDialogState"
* method, indicating if there is currently any process running that should prevent
* this dialog from being closed.
*
* @param {Ext.Panel} panel The panel being closed
*
* @return {Boolean}
*/
onBeforeClose : function(panel)
{
return this.closable;
},

/**
* By default, the card firing this event monitors user input in a frequent
* interval and fires the 'clientvalidation'-event along with it. This listener
* will enable/disable the next/finish-button in accordance with it, based upon
* the parameter isValid. isValid" will be set by the form validation and depends
* on the validators you are using for the different input-elemnts in your form.
* If the card does not contain any forms, this listener will never be called by the
* card itself.
*
* @param {Ext.ux.Wiz.Card} The card that triggered the event.
* @param {Boolean} isValid "true", if the user input was valid, otherwise
* "false"
*/
onClientValidation : function(card, isValid)
{
if (!isValid) {
this.nextButton.setDisabled(true);
} else {
this.nextButton.setDisabled(false);
}
},

/**
* This will render the "next" button as disabled since the bindHandler's delay
* of the next card to show might be lagging on slower systems
*
*/
onCardHide : function(card)
{
if (this.cardPanel.layout.activeItem.id === card.id) {
this.nextButton.setDisabled(true);
}
},


/**
* Listener for the "show" event of the card that gets shown in the card-panel.
* Renders the next/previous buttons based on the position of the card in the wizard
* and updates the head-panel accordingly.
*
* @param {Ext.ux.Wiz.Card} The card being shown.
*/
onCardShow : function(card)
{
var parent = card.ownerCt;

var items = parent.items;

for (var i = 0, len = items.length; i < len; i++) {
if (items.get(i).id == card.id) {
break;
}
}

this.currentCard = i;
this.headPanel.updateStep(i, card.title);

if (i == len-1) {
this.nextButton.setText(this.finishButtonText);
} else {
this.nextButton.setText(this.nextButtonText);
}

if (card.isValid()) {
this.nextButton.setDisabled(false);
}

if (i == 0) {
this.previousButton.setDisabled(true);
} else {
this.previousButton.setDisabled(false);
}

},


/**
* Fires the 'cancel'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onCancelClick : function()
{
if (this.fireEvent('cancel', this) !== false) {
this.close();
}
},

/**
* Fires the 'finish'-event. Closes this dialog if the return value of the
* listeners does not equal to "false".
*/
onFinish : function()
{
if (this.fireEvent('finish', this, this.getWizardData()) !== false) {
this.close();
}
},

/**
* Listener for the previous-button.
* Switches to the previous displayed {@link Ext.ux.Wiz.Card}.
*/
onPreviousClick : function()
{
if (this.currentCard > 0) {
this.cardPanel.getLayout().setActiveItem(this.currentCard - 1);
}
},

/**
* Listener for the next-button. Switches to the next {@link Ext.ux.Wiz.Card}
* if the 'beforehide'-method of it did not return false. The functionality
* for this is implemented in {@link Ext.ux.layout.CardLayout}, which is needed
* as the layout for the card-panel of this component.
*/
onNextClick : function()
{
if (this.currentCard == this.cardCount-1) {
this.onFinish();
} else {
this.cardPanel.getLayout().setActiveItem(this.currentCard+1);
}
}
});

wemerson.januario
23 Nov 2009, 7:12 AM
You fixed the problems with the first next button, but when slide for the second, the problem keeps on the ux. I type the firt name e the last name but the next button doesn`t appears. Thanks

wemerson.januario
23 Nov 2009, 7:41 AM
Thanks, I got the last version in the svn and now everything is ok. I just update your new wizard.js

agalue
23 Nov 2009, 7:52 AM
MindPatterns,

Thank you very much. That did the trick... the wizard is working now with 3.0.3.

swarm
23 Nov 2009, 8:17 AM
Thank you Mindpatterns,

I was having similar issues as well and your updated wizard.js has done the trick :)

Excellent extension!

hatch79
25 Nov 2009, 9:43 AM
This looks exactly like what I need, however I went to the svn at http://ext-ux-wiz.googlecode.com/svn/trunk/ but the wizard.js links to ext-2.1 instead of version 3, please tell me where to find the files for ext version 3.x.

Warm regards,
JT

esh
30 Nov 2009, 12:27 AM
Hello, every ExtJS developer:
I'm already view each post in this thread and trace Wizard.js,
but I still can't solve the following problem.

Sorry for my poor skill and English, thanks~

1.
About sequential control, how to switch to specific card?
If I set a radio panel and each radio item will assign a value.
When user click the next button, and depend on the radio button value,
switch to specific card.

code example:


items: [{
xtype: 'textfield',
name: 'UID',
id: 'uid',
fieldLabel: 'UserID '
}, {
checked: true,
fieldLabel: 'Select Function',
boxLabel: 'Create Job',
name: 'functional',
inputValue: 'cJob',
}, {
fieldLabel: '',
boxLabel: 'Update Job',
name: 'functional',
inputValue: 'uJob',
}, {
fieldLabel: '',
boxLabel: 'Delete Job',
name: 'functional',
inputValue: 'dJob',
},{
fieldLabel: '',
boxLabel: 'Query Job',
name: 'functional',
inputValue: 'qJob',
}],

If inputValue is qJob, click the "next" button, It will show the card#6.

2.
May I let the "Next" button become "Finish" button? In my case, when some card appears,
it means the final step comes. So the "Next" button should be "Finish"

From previous example. First card is the ratio button panel, let user choose what he want.
If "Create Job" show the next one. "Update Job" show card#4, "Delete Job" show card#5 and
"Query Job" show card#6.

The complex sequential control is difficult. When I switch to card#5 which is "Delete Job",
click previous button on card#5, it should back to the first card not card#4.

Juvs
14 Dec 2009, 10:36 AM
@esh, some modification to this extension in order to support the behavior you request:


...
/**
* Listener for the previous-button.
* Switches to the previous displayed {@link Ext.ux.Wiz.Card}.
*/
onPreviousClick : function()
{
var currentCardPanel = this.cardPanel.getLayout().activeItem;

if (this.currentCard > 0) {
if (!Ext.isEmpty(currentCardPanel.prevCardFn)) {
this.currentCard = currentCardPanel.prevCardFn.call(this, currentCardPanel);
this.cardPanel.getLayout().setActiveItem(this.currentCard );
} else if (!Ext.isEmpty(currentCardPanel.prevCard)) {
this.currentCard = currentCardPanel.prevCard;
this.cardPanel.getLayout().setActiveItem(this.currentCard);
} else {
this.cardPanel.getLayout().setActiveItem(this.currentCard - 1);
}

}
},

/**
* Listener for the next-button. Switches to the next {@link Ext.ux.Wiz.Card}
* if the 'beforehide'-method of it did not return false. The functionality
* for this is implemented in {@link Ext.ux.layout.CardLayout}, which is needed
* as the layout for the card-panel of this component.
*/
onNextClick: function(){
var currentCardPanel = this.cardPanel.getLayout().activeItem;
if (currentCardPanel.isFinish || (this.currentCard == this.cardCount - 1)) {
this.onFinish();
} else {

if (!Ext.isEmpty(currentCardPanel.nextCardFn)) {
this.currentCard = currentCardPanel.nextCardFn.call(this, currentCardPanel);
this.cardPanel.getLayout().setActiveItem(this.currentCard);
} else if (!Ext.isEmpty(currentCardPanel.nextCard)) {
this.currentCard = currentCardPanel.nextCard;
this.cardPanel.getLayout().setActiveItem(this.currentCard);
} else {
this.cardPanel.getLayout().setActiveItem(this.currentCard+1);
}
}
},
...

The idea is to provide your card with the following possible attributes:

nextCardFn: Function, returns the next index card to render on the wizard, receives the current card (panel) as parameter in order to verified the current values of the card.
nextCard: Integer, the next index card to render on the wizard.
prevCardFn : Function, returns the previous index card to render on the wizard, receives the current card (panel) as parameter in order to verified the current values of the card.
prevCard: Integer, the next previous card to render on the wizard.
isFinish: Boolean, True is the current card is the last one.

Example:


new Ext.ux.Wiz.Card({
title : 'Some card on wizard...',
items : [{
...
}],
nextCardFn: function(panel){
var someValueFromForm = ....;
if (someValueFromForm == 0 || someValueFromForm == 2) {
return 1; //Next index to show in wizard
} else {
return 2; //Next index to show in wizard
}

}
}),
...
new Ext.ux.Wiz.Card({
title : 'Some card on wizard...',
items : [{
...
}],
nextCard: 10 //Go direct to card index 10
}),
...
new Ext.ux.Wiz.Card({
title : 'Some card on wizard...',
items : [{
...
}],
isFinish: true //This is the last card, so show finish button
}),

By the way, I haven't tested the header bread crumbs with this solution, I recommend to turn off when you have this kind of "jumps". Actually I turned off on my wizard, only use the card title.

Enjoy...

kmiyashiro
14 Dec 2009, 3:45 PM
For some reason, I can't get the "Next" button to be active on the first screen. If I set monitorValid to false, it works. But if I set it to true, it never gets enabled even when all the fields are valid.

I even tried removing the welcome card in the example and the Next button is never enabled even when you enter valid in puts in the First and Last name fields.

I am using the Wizard.js that MindPatterns posted above and everything else from the latest SVN.

Juvs
14 Dec 2009, 3:49 PM
For some reason, I can't get the "Next" button to be active on the first screen. If I set monitorValid to false, it works. But if I set it to true, it never gets enabled even when all the fields are valid.

I even tried removing the welcome card in the example and the Next button is never enabled even when you enter valid in puts in the First and Last name fields.

I am using the Wizard.js that MindPatterns posted above and everything else from the latest SVN.

Any sample code...

kmiyashiro
14 Dec 2009, 4:14 PM
Just use the example.html that comes with the UX except take out the first card. I am using ext 3.0.3


Ext.onReady(function(){

Ext.QuickTips.init();

var wizard = new Ext.ux.Wiz({

title : 'A simple example for a wizard',

headerConfig : {
title : 'Simple Wizard Example'
},

cardPanelConfig : {
defaults : {
baseCls : 'x-small-editor',
bodyStyle : 'padding:40px 15px 5px 120px;background-color:#F6F6F6;',
border : false
}
},

cards : [

// second card with input fields last/firstname
new Ext.ux.Wiz.Card({
title : 'Your name',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : 'Please enter your first- and your lastname. Only letters, underscores and hyphens are allowed.'
},
new Ext.form.TextField({
name : 'firstname',
fieldLabel : 'Firstname',
allowBlank : false,
validator : function(v){
var t = /^[a-zA-Z_\- ]+$/;
return t.test(v);
}
}),
new Ext.form.TextField({
name : 'lastname',
fieldLabel : 'Lastname',
allowBlank : false,
validator : function(v){
var t = /^[a-zA-Z_\- ]+$/
return t.test(v);
}
})

]
}),

// third card with input field email-address
new Ext.ux.Wiz.Card({
title : 'Your email-address',
monitorValid : true,
defaults : {
labelStyle : 'font-size:11px'
},
items : [{
border : false,
bodyStyle : 'background:none;padding-bottom:30px;',
html : ' Please enter your email-address.'
},
new Ext.form.TextField({
name : 'email',
fieldLabel : 'Email-Address',
allowBlank : false,
vtype : 'email'
})
]
}),

// fourth card with finish-message
new Ext.ux.Wiz.Card({
title : 'Finished!',
monitorValid : true,
items : [{
border : false,
bodyStyle : 'background:none;',
html : 'Thank you for testing this wizard. Your data has been collected '+
'and can be accessed via a call to <pre><code>this.getWizardData</code></pre>'+
'When you click on the "finish"-button, the "finish"-event will be fired.<br/>'+
'If no attached listener for this event returns "false", this dialog will be '+
'closed. <br />(In this case, our listener will return false after a popup shows the data you just entered)'
}]
})


]
});

// show the wizard
wizard.show();
});

kmiyashiro
14 Dec 2009, 4:25 PM
Further debugging shows that onClientValidation is only triggered when the form is created. The "invalid" class is added to the input when it is invalid, but the clientvalidation event is never triggered for the first card after it is created.


/**
* Starts monitoring the form elements in this component when the
* 'show'-event gets fired.
*/
onCardShow : function()
{
if (this.monitorValid) {
this.startMonitoring();
}
}

I'm guessing the first card never triggers the 'show' event and thus is never monitored for validation?

edit: Yup, if I trigger the show manually via firebug, it starts monitoring as expected... I guess I can manually fire the event for the first card.

edit2: Nevermind, eki posted a solution earlier in the thread http://www.extjs.com/forum/showthread.php?p=203731#post203731

reci
16 Dec 2009, 5:08 PM
To enable re-use of a Window when closeAction: 'hide'
Wizard.js



onCancelClick : function()
{
if (this.fireEvent('cancel', this) !== false) {
this[this.closeAction](); //this.close(); or this.hide();
}
},
onFinish : function()
{
if (this.fireEvent('finish', this, this.getWizardData()) !== false) {
this[this.closeAction]();
}
}

alienwebz
23 Dec 2009, 10:52 AM
Do we know if this works with Ext 3.1? I am using the demo provided but the wizard just stays on the first card.

Yes i checked all of the links they all point to the correct files and i downloaded cardlayout.js, the only change i made was that the ext files are 3.1 not 2.1.

charleshimmer
23 Dec 2009, 10:55 AM
I've used the wizard with 3.1. 3.1 had too many bugs so I switched back to 3.0.2 but the wizard component did work fine for me with 3.1 before I switched back to 3.0.2.

alienwebz
23 Dec 2009, 11:19 AM
Do we know if this works with Ext 3.1? I am using the demo provided but the wizard just stays on the first card.

Yes i checked all of the links they all point to the correct files and i downloaded cardlayout.js, the only change i made was that the ext files are 3.1 not 2.1.


I've used the wizard with 3.1. 3.1 had too many bugs so I switched back to 3.0.2 but the wizard component did work fine for me with 3.1 before I switched back to 3.0.2.

Yeah iv got it working the solution was again by adding


wizard.cards[0].show();


to the end of the simplewizard.html,

has anyone added a progress bar to the card indicator like what is seen on the GWT example of this component?

Also how can i add an image to the bottom left side of the wizard like is in the image in the first post?

mike1993
23 Dec 2009, 1:03 PM
Looks like 'show' event never fires, thus leaving
currentCard : -1

So, call to onNextClick() sets active item to 0.

I've added
currentCard: 0 to my wizard config.

alienwebz
23 Dec 2009, 6:13 PM
How do i add an icon to my card like the one in the screenshot of this component? (bottom left) and can it be different for each card?

jmariani
25 Dec 2009, 8:32 AM
So, I made a small change to add icons to the buttons.

Added the following properties to Wizard.js:


previousButtonIconCls : 'ext-ux-wiz-previousButton',
nextButtonIconCls : 'ext-ux-wiz-nextButton',
finishButtonIconCls : 'ext-ux-wiz-finishButton',
cancelButtonIconCls : 'ext-ux-wiz-cancelButton',
Changed function initButtons on Wizard.js like this:

initButtons : function()
{
this.previousButton = new Ext.Button({
text : this.previousButtonText,
disabled : true,
minWidth : 75,
handler : this.onPreviousClick,
scope : this,
iconCls : this.previousButtonIconCls
});

this.nextButton = new Ext.Button({
text : this.nextButtonText,
minWidth : 75,
handler : this.onNextClick,
scope : this,
iconCls : this.nextButtonIconCls,
iconAlign: 'right'
});

this.cancelButton = new Ext.Button({
text : this.cancelButtonText,
handler : this.onCancelClick,
scope : this,
iconCls : this.cancelButtonIconCls,
minWidth : 75
});
},
Changed function onCardShow in Wizard.js to this:


onCardShow : function(card)
{
var parent = card.ownerCt;

var items = parent.items;

for (var i = 0, len = items.length; i < len; i++) {
if (items.get(i).id == card.id) {
break;
}
}

this.currentCard = i;
this.headPanel.updateStep(i, card.title);

if (i == len-1) {
this.nextButton.setText(this.finishButtonText);
this.nextButton.setIconClass(this.finishButtonIconCls);
} else {
this.nextButton.setText(this.nextButtonText);
this.nextButton.setIconClass(this.nextButtonIconCls);
}

if (card.isValid()) {
this.nextButton.setDisabled(false);
}

if (i == 0) {
this.previousButton.setDisabled(true);
} else {
this.previousButton.setDisabled(false);
}

},
Added the following to ext-ux-wiz.css:


.ext-ux-wiz-previousButton {
background-image:url(../images/ext-ux-wiz-previous-button.png) ! important;
background-repeat:no-repeat;
}
.ext-ux-wiz-nextButton {
background-image:url(../images/ext-ux-wiz-next-button.png) ! important;
background-repeat:no-repeat;
}
.ext-ux-wiz-cancelButton {
background-image:url(../images/ext-ux-wiz-cancel-button.png) ! important;
background-repeat:no-repeat;
}

.ext-ux-wiz-finishButton {
background-image:url(../images/ext-ux-wiz-finish-button.png) ! important;
background-repeat:no-repeat;
}
And copy previous, next, cancel and finish icons of your choice to ext-ux-wiz/src/images, and name them accordingly to the css definitions.

Here's a sample:

http://www.facebook.com/photo.php?pid=413868&l=d4db38bb4e&id=1430524019

Enjoy.

jmariani
18 Jan 2010, 10:14 AM
Hi.

I would like to implement card replacing based on previous card information.

For instance, I set card(2) as a dummy card, and based on what I choose on card(1), I want card(2) to be wizCardA or wizCardB.

If I do something like:

var wizCardA = new Ext.ux.Wiz.Card({...});
var wizCardB = new Ext.ux.Wiz.Card({...});

var newBusinessPartnerWizard = new Ext.ux.Wiz({cards: [card1, DummyCard]});

And later:

wizard.cards[2] = wizCardA;

it doesn't work. The wizard shows the DummyCard I set at Wizard creation instead wizCardA.

Any clues?

TIA!

Juvs
18 Jan 2010, 11:00 AM
I have the need to put cards on wizard that I already create and extends from FormPanel or GridPanel, so I need a way to empower them to be used on Wizard, so I create this plugin:


/**
* Plugin to provide same functionallity as Ext.ux.Wizard.Card to any
* panel, also the panel should establish the attribute isValid on true
* and the panel must have the function isValid returning true or false
* to know is the data is valid or not.
* @param {Object} config
* @author Juvs
*/
Ext.ux.plugins.CardWizardEnabler = function(config){

// initialize plugin
this.init = function(panel){

panel.addEvents(
/**
* @event beforecardhide
* If you want to add additional checks to your card which cannot be easily done
* using default validators of input-fields (or using the monitorValid-config option),
* add your specific listeners to this event.
* This event gets only fired if the activeItem of the ownerCt-component equals to
* this instance of card. This is needed since a card layout usually
* hides it's items right after rendering them, involving the beforehide-event.
* If those checks would be attached to the normal beforehide-event, the card-layout
* would never be able to hide this component after rendering it, depending on the
* listeners return value.
*
* @param {Ext.Panel} card The card that triggered the event
*/
'beforecardhide'
);
panel.on("show", function(panel){
if (panel.monitorValid) {
panel.startMonitoring();
}
}, panel);

panel.on("hide", function(panel){
if (panel.monitorValid) {
panel.stopMonitoring();
}
}, panel);

panel.on('beforehide', function(){
var ly = this.ownerCt.layout;
var activeItem = ly.activeItem;

if (activeItem && activeItem.id === this.id) {
return this.fireEvent('beforecardhide', this);
}

return true;
}, panel);

Ext.apply(panel, {
hideMode : Ext.isIE ? 'display' : 'offsets'
});

if (!panel.isXType("form", false)) {
Ext.apply(panel, {
/**
* Start monitor for function validation from panel
*/
startMonitoring: function(){
if (!this.bound) {
this.bound = true;
Ext.TaskMgr.start({
run: this.bindHandler,
interval: this.monitorPoll || 200,
scope: this
});
}
},
/**
* Stops monitor task for validation from panel
*/
stopMonitoring: function(){
this.bound = false;
},
/**
* @private
* Executes the validation function and trigger
* validation event
*/
bindHandler: function(){
if (!this.bound) {
return false; // stops binding
}
var valid = false;
if (!Ext.isEmpty(this.isValid)) {
valid = this.isValid();
}
this.fireEvent('clientvalidation', this, valid);
}
});
}
}
}

Just use in your component, remember to set the attribute monitorValid on true and in case the panel is not a form you must provide the isValid function:


...
monitorValid: true,
plugins: [new Ext.ux.plugins.CardWizardEnabler()],
...

Enjoy...

lakilevi
21 Jan 2010, 3:44 AM
Thanks for the great job!
It works fine in extjs 3.1.
But unfortunately not in 3.1.1 beta. I click on the "Next" button and does nothing happen.

Could you tell me please, what to correct to be usable also in 3.1.1. beta?

Thank you very much!

ThorstenSuckow
21 Jan 2010, 3:54 AM
Thanks for the great job!
It works fine in extjs 3.1.
But unfortunately not in 3.1.1 beta. I click on the "Next" button and does nothing happen.

Could you tell me please, what to correct to be usable also in 3.1.1. beta?

Thank you very much!

Yes, it's related to http://www.extjs.com/forum/showthread.php?t=67381 - they have changed the CardLayout to consider the hidden-state of the container which is about to be deactivated. This makes the additional Ext.ux.layout.CardLayout obsolete. However, since 3.1.1 is only beta yet, the next release will take a while until 3.1.1 gets final.

Stju
2 Feb 2010, 10:50 AM
Maybe some sort of workaround until 3.1.1 gets final?

pezze
3 Feb 2010, 1:13 AM
Maybe some sort of workaround until 3.1.1 gets final?

I've simply changed default currentCard to 0 instead of -1 as Mike wrote here:

http://www.extjs.com/forum/showthread.php?p=421918#post421918

jmariani
4 Feb 2010, 11:15 PM
Try something like this in the definition of your Wizard:



cardPanelConfig: {
defaults: {
baseCls: 'x-small-editor',
//bodyStyle: 'padding:15px 15px 5px 15px;background-color:#F6F6F6;',
bodyStyle: "padding:40px 15px 5px 219px;background-color:#FFFFFF;background-image:url('images/Partner_Icon.jpg');background-repeat:no-repeat;background-position:bottom left;",
border: false
}
},

jmariani
5 Feb 2010, 1:48 PM
How do i add an icon to my card like the one in the screenshot of this component? (bottom left) and can it be different for each card?

Try something like this in the definition of your Wizard:


cardPanelConfig: {
defaults: {
baseCls: 'x-small-editor',
//bodyStyle: 'padding:15px 15px 5px 15px;background-color:#F6F6F6;',
bodyStyle: "padding:40px 15px 5px 219px;background-color:#FFFFFF;background-image:url('images/Partner_Icon.jpg');background-repeat:no-repeat;background-position:bottom left;",
border: false
}
},




http://www.extjs.com/forum/images/misc/progress.gif http://www.extjs.com/forum/images/buttons/edit.gif (http://www.extjs.com/forum/editpost.php?do=editpost&p=434198)

swarm
22 Feb 2010, 3:05 AM
Having issues with displaying tbar or bbar in grids that are in a card.

They just render blank for some reason. It was working fine in 3.1.0...

Any thoughts?

swarm
3 Mar 2010, 2:18 PM
Not only toolbars, but I've discovered combo boxes don't render correctly in IE 6, 7, 8, Safari (Mac) or Chrome (Mac & Windows). Firefox doesn't have a problem with them though...

I'm also getting an error when closing the wizard on IE 6 and 7 about 'parentNode' being null...

Does anyone else know if Ext.ux.wiz is compatible with 3.3.1?

Thanks,

alienwebz
15 Mar 2010, 10:19 PM
I cannot seem to get any JSON data to load at all when using that data in the wizard.

Has anyone tried to load a grouping grid or load data into a textfield etc. using JSON and php?

I have tried for weeks to get this to work, i have also tried to load a php file as a script but i get random errors.

Really if someone would like to share some code with how they did what im looking to do that would be great!

pezze
16 Mar 2010, 12:34 AM
I cannot seem to get any JSON data to load at all when using that data in the wizard.

Has anyone tried to load a grouping grid or load data into a textfield etc. using JSON and php?

I have tried for weeks to get this to work, i have also tried to load a php file as a script but i get random errors.

Really if someone would like to share some code with how they did what im looking to do that would be great!

Wll, I think you have to post some code. I've used JsonData in Wizard component without problems.

alienwebz
16 Mar 2010, 8:59 AM
Wll, I think you have to post some code. I've used JsonData in Wizard component without problems.

Could you give an example of your wizard?

I'm trying to get a grouping grid using json working.

alienwebz
16 Mar 2010, 9:30 PM
Wll, I think you have to post some code. I've used JsonData in Wizard component without problems.

JSON:
http://examples.extjs.eu/get-grid-data.php

Card With Grid that will not load:

card_servercheck = Ext.extend(Ext.ux.Wiz.Card, {

initComponent: function(){
this.monitorValid = true;

this.baseCls = 'x-small-editor';

this.title = 'Server Status';

this.items = [
{xtype: 'examplegrid'}
];

card_servercheck.superclass.initComponent.call(this);
}
});

Ext.ns('Example');

// example grid
Example.Grid = Ext.extend(Ext.grid.GridPanel, {
initComponent:function() {
var config = {
store:new Ext.data.GroupingStore({
reader: new Ext.data.JsonReader({}, [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'},
{name: 'industry'},
{name: 'desc'}
]),
url: 'get-grid-data.php',
sortInfo:{field: 'company', direction: "ASC"},
groupField:'industry'
})
,columns:[{
id:'company'
,header:"Company"
,width:40, sortable:true
,dataIndex:'company'
},{
header:"Price"
,width:20
,sortable:true
,renderer:Ext.util.Format.usMoney
,dataIndex:'price'
},{
header:"Change"
,width:20
,sortable:true
,dataIndex:'change'
},{
header:"% Change"
,width:20
,sortable:true
,dataIndex:'pctChange'
},{
header:"Last Updated"
,width:20, sortable:true
,renderer:Ext.util.Format.dateRenderer('m/d/Y')
,dataIndex:'lastChange'
}],
view: new Ext.grid.GroupingView({

forceFit:true,
groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
})
,loadMask:true
}; // eo config object

// apply config
Ext.apply(this, Ext.apply(this.initialConfig, config));


// call parent
Example.Grid.superclass.initComponent.apply(this, arguments);

// load the store at the latest possible moment
this.on({
afterlayout:{scope:this, single:true, fn:function() {
this.store.load({params:{start:0, limit:30}});
}}
});

} // eo function initComponent

});

Ext.reg('examplegrid', Example.Grid);

The grid will display but is empty, i dont think the store is loading.

pezze
17 Mar 2010, 1:18 AM
Have you tried with autoload: true instead of afterlayout event loading?

alienwebz
17 Mar 2010, 7:56 PM
Have you tried with autoload: true instead of afterlayout event loading?

Yeah I tried that. It doesn't display any errors but it is still not loading.

CarlosLuiz
29 Mar 2010, 5:44 AM
I'm looking everewhere to download this extension but i just can´t find it. please help me.

thanks.

ThorstenSuckow
29 Mar 2010, 5:54 AM
I have made some changes to the ux that grant compatibility to Ext > 3.1.1 . If you do not want to check the ux out of its central repository using SVN, I'll add a download to its google project home later.

CarlosLuiz
1 Apr 2010, 7:41 AM
When i add a monitorValid in the first Card, the 'Next' button is always disabled, and i can't continue with the wizard. It happens only in the first Card.

franck34
20 Apr 2010, 2:04 AM
I have made some changes to the ux that grant compatibility to Ext > 3.1.1 . If you do not want to check the ux out of its central repository using SVN, I'll add a download to its google project home later.

Hi, i don't see any changes on google svn regarding ext > 3.1.1 compatibility.

Should be great if you can provide it asap (i'm stucked with 3.2.0) with a story of card show event or perhaps monitorvalid, i didn't debug a lot

It should be really great if you can update a svn on google or everywhere you want, thanks in advance !

Stju
27 Apr 2010, 3:31 AM
Two missing things(rewind wizzard, and reset to initial state):
in file Wizzard.js


rewind :function(){
this.currentCard = 0;
this.cardPanel.getLayout().setActiveItem(this.currentCard);
},
reset: function(){
var cards = this.cards;
for (var i = 0, len = cards.length; i < len; i++) {
if (cards[i].form) cards[i].form.reset();
}
}

Stju
29 Apr 2010, 8:11 PM
monitorValid issue workaround(as a sidenote-first card must be plain Welcome Text!):


onClientValidation : function(card, isValid)
{
//if card 0 , ignore validation! Use validation events only for card it belongs to!
if(this.currentCard>0 && this.cardPanel.layout.activeItem.id === card.id){
if (!isValid) {
this.nextButton.setDisabled(true);
} else {
this.nextButton.setDisabled(false);
}
}else{
this.nextButton.setDisabled(false);
}
},

CarlosLuiz
10 May 2010, 10:14 AM
I'm trying to get a fileUploadField in the function getWizardData in the 'wizard' class, but it didn´t work.

someone can help me.

thanks

charleshimmer
25 May 2010, 2:48 PM
I know several others have commented that the upload form doesn't work with the wizard component, but if I put the exact same upload form inside a normal window it works fine. The error I am getting is that the file was never submitted if I use the wizard component. Any ideas on where the Wizard component could be interfering with the upload form submit?

TheColonel
5 Jul 2010, 10:21 PM
I've been trying to update to 3.2.1 from 2.3.0 and this component was one the the last issues. I've been slamming my head against the wall trying to figure out why the 'close' and 'clientvalidation' events were not getting fired.

I finally seem to have had a breakthrough. It turns out the card component is checking the card(FormPanel) for the 'bound' property in the 'bindHandler'. This property doesn't seem to be present in the newer FormPanel class.

I have tentatively instituted a fix that seems to have everything working properly. Just removed that check and all good so far.

I changed



bindHandler : function()
{
if(!this.bound){
return false; // stops binding
}
to


bindHandler : function()
{
if(!this.bound){
// return false; // stops binding
}


I also changed the default currentCard to 0 in the Wizard component to 0.

I did notice that this line was not even present in an earlier version of the component. I'm using the latest version from the ux repository. Hope this helps someone. If anyone could verify that this is legit fix that would be great.

That change is in the Ext.ux.Wiz.Card component.

ccrotty
20 Jul 2010, 1:36 PM
Where is the latest source located, the googlecode location or the ux repository?

In the ux repository, it appears that the Card object has some of the later changes, like in the bindHandler function.

But the wizard class in the ux.repository is using the Ext.ux.layout.CardLayout() while the googlecode source is using the Ext.layout.CardLayout(). I thought since the changes in 3.X, we should be using the Ext.layout.CardLayout class?

The googlecode source also has more complete doc/comments.

Thanks,
Chris

KRavEN
22 Jul 2010, 6:13 AM
Where is the latest source located, the googlecode location or the ux repository?

In the ux repository, it appears that the Card object has some of the later changes, like in the bindHandler function.

But the wizard class in the ux.repository is using the Ext.ux.layout.CardLayout() while the googlecode source is using the Ext.layout.CardLayout(). I thought since the changes in 3.X, we should be using the Ext.layout.CardLayout class?

The googlecode source also has more complete doc/comments.

Thanks,
Chris

Now I'm confused. I've been using the google repo but it looks like it's still using Ext.ux.layout.CardLayout(). I've tested and both
Ext.ux.layout.CardLayout() and Ext.layout.CardLayout() work in Extjs 3.2.

http://code.google.com/p/ext-ux-wiz/source/browse/trunk/src/Wizard.js

ccrotty
22 Jul 2010, 7:54 AM
Checking again, I manually changed the google code to use the Ext.layout.CardLayout.

The ux repository was added 12/6/2009 with no apparent updates, while the last google code update occurred on 1/24/2010.
These last changes to the google repository are not in the ux repository. (These changes are the ones mentioned by the author in http://www.sencha.com/forum/showthread.php?36627-2.1-Ext.ux.Wiz-a-wizard-component-for-Ext-JS&p=411639#post411639)

So it appears that the google code repository has the latest updates.

ccrotty
27 Jul 2010, 10:04 AM
Were you able to solve the problem with the fileuploadfield?

I am having the same issue as the submit is not transmitting as a multipart-form.




I know several others have commented that the upload form doesn't work with the wizard component, but if I put the exact same upload form inside a normal window it works fine. The error I am getting is that the file was never submitted if I use the wizard component. Any ideas on where the Wizard component could be interfering with the upload form submit?

yugikhoi
30 Aug 2010, 5:55 AM
Hi, I am just beginner and looking for how to submit form with ext.ux.wiz. I understand that I need to call listeners: {finish : function(){} to get submit. However, with extjs form i normally do


formPanel.getForm().submit({
url: 'test.jsp',
method:'POST',
success: function(f,a){
Ext.getCmp('myform').close();
Ext.Msg.alert('Success', "Your form has been successfully added");
},


But i cannot found how to submit form in wizard. Can you show me code to do that?

Thanks a lot.

anestesiadorhvs
8 Oct 2010, 10:55 AM
I´m trying a similar approach that yugikhoi, I'd like lo load default values in wizard cards with a reader (like in a form) and submit then in the same way a form do, calling the method submit.
Is this possible?
Thanks in advance

occiso
10 Oct 2010, 2:02 AM
Excellent component.

I'm trying to create a card containing several textfield and a grid. any example or help?

occiso
11 Oct 2010, 11:01 AM
Regards,

I am having a problem with that component. I do not know if it is something of CardLayout or wizard it turns out, when moving from one card to another I can not display some items correctly, or the page or menu items nor checkbox. The funny is that: when resizing the window displays the data correctly. you can see the pictures:

Display before resizing the screen
http://img6.glowfoto.com/images/2010/10/11-1132322143L.jpg
(si no veis la imagen) http://img6.glowfoto.com/images/2010/10/11-1132322143L.jpg


Display after resizing the screen
http://img5.glowfoto.com/images/2010/10/11-1110523984L.jpg
(si no veis la imagen)[url]http://img5.glowfoto.com/images/2010/10

guyfomi
29 Nov 2010, 6:59 AM
nice work Thank for sharing!

Jack_S
1 Dec 2010, 7:18 PM
Hello,

What is teh link to latest version which with > 3.1. I've gone thrown a few pages on this ux and its unclear where the correct/latest code version lies.

Thanks

Jack S.

talha06
12 Jan 2011, 7:33 AM
it's so useful, thank u so much for sharing it..

slckysr
14 Jan 2011, 6:56 AM
i have a problem with the sending of my data with an ajax request



listeners: {
finish: function(){
mycards= this.getWizardData();

Ext.Ajax.request({
url : 'process.php' ,
params : {
advname: mycards[1]['newadvertisername']
},
method: 'GET',
success: function ( result, request) {
},
failure: function ( result, request) {
},
jsonData: Ext.util.JSON.encode(mycards[1]['newadvertisername']/*dataObj*/)
});
}
},


i used the params and the encode() method to see if one of them are working. anyway i tried to sent with GET and POST but nothing httpwatch is going on like i would: no request no response :) what am i making wrong ?

slckysr
14 Jan 2011, 7:01 AM
ok it worked i forgot a stupid error

slckysr
24 Jan 2011, 6:04 AM
how can i change the left title in the header field of the wizard at runtime. i want to use this item as an explaining text for each card.

each card has a other step, so i want to give my users a short introduction at each card step

samir_ware
2 Mar 2011, 2:58 AM
Hello all
This is really a very useful plugin. Thanks a lot for developing one.
I was trying to render a combobox in a card. Though its getting cropped and not displayed properly. can anyone please tell me the reason/fix for the same.
I could see some of the post related to this. Though I was not able to find the answer for the same.
Thanks
Samir

drunkmoose
14 Mar 2011, 9:42 AM
The plugin is not available @ Google Code anymore.