Ext.ux.Wizard.js (17071B)
1 Ext.define('Ext.ux.Wizard', { 2 extend: 'Ext.window.Window', 3 // layout: 'Ext.ux.wizard.CardLayout', 4 layout: 'fit', 5 loadMaskConfig: { 6 'default': '', 7 'saving': 'Saving...', 8 'checking': 'Checking...' 9 }, 10 autoRender: true, 11 12 /** 13 * @cfg {Number} height The height of the dialog. Defaults to "400". 14 */ 15 height: 650, 16 17 /** 18 * @cfg {Number} width The width of the dialog. Defaults to "540". 19 */ 20 width: 800, 21 22 /** 23 * @cfg {Boolean} closable Wether the dialog is closable. Defaults to "true". 24 * This property will be changed by the "switchDialogState"-method, which will 25 * enable/disable controls based on the passed argument. Thus, this config property 26 * serves two purposes: Tell the init config to render a "close"-tool, and create a 27 * "beforeclose"-listener which will either return true or false, indicating if the 28 * dialog may be closed. 29 */ 30 closable: true, 31 32 /** 33 * @cfg {Boolean} resizable Wether the dialog is resizable. Defaults to "false". 34 */ 35 resizable: false, 36 37 /** 38 * @cfg {Boolean} resizable Wether the dialog is modal. Defaults to "true". 39 */ 40 modal: true, 41 42 /** 43 * @cfg {Array} cards A numeric array with the configured {@link Ext.ux.Wiz.Card}s. 44 * The index of the cards in the array represent the order in which they get displayed 45 * in the wizard (i.e. card at index 0 gets displayed in the first step, card at index 1 gets 46 * displayed in the second step and so on). 47 */ 48 cards: [], 49 50 /** 51 * @cfg {String} previousButtonText The text to render the previous-button with. 52 * Defaults to "< Back" (< Back) 53 */ 54 previousButtonText: '< Previous', 55 56 /** 57 * @cfg {String} nextButtonText The text to render the next-button with. 58 * Defaults to "Next >" (Next >) 59 */ 60 nextButtonText: 'Next >', 61 62 /** 63 * @cfg {String} cancelButtonText The text to render the cancel-button with. 64 * Defaults to "Cancel" 65 */ 66 cancelButtonText: 'Cancel', 67 68 /** 69 * @cfg {String} finishButtonText The text to render the next-button with when the last 70 * step of the wizard is reached. Defaults to "Finish" 71 */ 72 finishButtonText: 'Finish', 73 74 /** 75 * @cfg {Object} headerConfig A config-object to use with {@link Ext.ux.Wiz.Header}. 76 * If not present, it defaults to an empty object. 77 */ 78 headConfig: null, 79 80 /** 81 * @cfg {Object} sideConfig A config-object to use with {@link Ext.ux.Wizard}. 82 * If not present, it defaults to an empty object. 83 */ 84 sideConfig: null, 85 86 /** 87 * @cfg {Object} cardPanelConfig A config-object to use with {@link Ext.Panel}, which 88 * represents the card-panel in this dialog. 89 * If not present, it defaults to an empty object 90 */ 91 cardPanelConfig: {}, 92 93 /** 94 * @param {Ext.Button} The window-button for paging to the previous card. 95 * @private 96 */ 97 previousButton: null, 98 99 /** 100 * @param {Ext.Button} The window-button for paging to the next card. When the 101 * last card is reached, the event fired by and the text rendered to this button 102 * will change. 103 * @private 104 */ 105 nextButton: null, 106 107 /** 108 * @param {Ext.Button} The window-button for canceling the wizard. The event 109 * fired by this button will usually close the dialog. 110 * @private 111 */ 112 cancelButton: null, 113 114 /** 115 * @param {Ex.Panel} The card-panel that holds the various wizard cards 116 * ({@link Ext.ux.Wiz.Card}). The card-panel itself uses the custom 117 * {@link Ext.ux.layout.CardLayout}, which needs to be accessible by this class. 118 * You can get it at {@link http://www.siteartwork.de/cardlayout}. 119 * @private 120 */ 121 cardPanel: null, 122 123 /** 124 * @param {Number} currentCard The current {@link Ext.ux.Wiz.Card} displayed. 125 * Defaults to 0. 126 * @private 127 */ 128 currentCard: 0, 129 130 /** 131 * @param {Ext.ux.Wiz.Header} The header-panel of the wizard. 132 * @private 133 */ 134 headPanel: null, 135 136 /** 137 * @param {Number} cardCount Helper for storing the number of cards used 138 * by this wizard. Defaults to 0 (inherits "cards.length" later on). 139 * @private 140 */ 141 cardCount: 0, 142 143 /** 144 * Inits this component with the specified config-properties and automatically 145 * creates its components. 146 */ 147 initComponent: function () { 148 149 var c = this.initialConfig, sregion, hregion; 150 151 if (!this.sideConfig) this.sideConfig = {}; 152 if (!this.headConfig) this.headConfig = {}; 153 154 if (c.sideConfig && c.sideConfig.position == 'right') { sregion = 'east'; } else { sregion = 'west'; } 155 if (c.headConfig && c.headConfig.position == 'bottom') { hregion = 'south'; } else { hregion = 'north'; } 156 157 Ext.applyIf(this.cardPanelConfig, { region: 'center', items: (this.cards || [{}]), layout: new Ext.ux.wizard.CardLayout(), border: false, activeItem: 0, baseCls: 'ux-wizard-cardpanel' }); 158 Ext.applyIf(this.sideConfig, { region: sregion, width: 150, layout: 'fit', xtype: 'wizardheader', headerPosition: 'side', steps: this.cards.length, hidden: !(c.sideConfig) }); 159 Ext.applyIf(this.headConfig, { region: hregion, height: 150, layout: 'fit', xtype: 'wizardheader', headerPosition: 'top', steps: this.cards.length, hidden: !(c.headConfig) }); 160 161 this.initButtons(); 162 this.initPanels(); 163 164 var title = this.title || this.headConfig.title; 165 title = title || ""; 166 167 var items = []; 168 169 items.push(this.sidePanel); 170 items.push(this.headPanel); 171 items.push(this.cardPanel); 172 173 Ext.apply(this, { 174 title: title, 175 layout: 'border', 176 cardCount: this.cards.length, 177 dockedItems: [{ 178 xtype: 'toolbar', 179 dock: 'bottom', 180 ui: 'footer', 181 defaults: { minWidth: 60 }, 182 items: [ 183 { xtype: 'component', flex: 1 }, 184 this.previousButton, 185 this.nextButton, 186 this.cancelButton 187 ] 188 }], 189 items: items 190 }); 191 192 this.callParent(); 193 }, 194 195 // -------- helper 196 /** 197 * Returns the form-data of all cards in this wizard. The first index is the 198 * id of the card in this wizard, 199 * and the values are objects containing key/value pairs in the form of 200 * fieldName : fieldValue. 201 * 202 * @return {Array} 203 */ 204 getWizardData: function () { 205 var formValues = {}; 206 var cards = this.cards; 207 for (var i = 0, len = cards.length; i < len; i++) { 208 if (cards[i].form) { 209 formValues[cards[i].id] = cards[i].form.getValues(false); 210 } else { 211 formValues[cards[i].id] = {}; 212 } 213 } 214 215 return formValues; 216 }, 217 218 /** 219 * Switches the state of this wizard between disabled/enabled. 220 * A disabled dialog will have a {@link Ext.LoadMask} covering the card-panel 221 * to prevent user input, and the buttons will be rendered disabled/enabled. 222 * If the dialog is closable, the close-tool will be masked, too, and the dialog will not 223 * be closable by clicking the "close" tool. 224 * 225 * @param {Boolean} enabled "false" to prevent user input and mask the elements, 226 * otherwise true. 227 * @param {String} type The type of msg for the {@Ext.LoadMask} covering 228 * the cardPanel, as defined in the cfg property "loadMaskConfig" 229 */ 230 switchDialogState: function (enabled, type) { 231 this.showLoadMask(!enabled, type); 232 233 this.previousButton.setDisabled(!enabled); 234 this.nextButton.setDisabled(!enabled); 235 this.cancelButton.setDisabled(!enabled); 236 237 var ct = this.tools['close']; 238 239 if (ct) { 240 switch (enabled) { 241 case true: 242 this.tools['close'].unmask(); 243 break; 244 245 default: 246 this.tools['close'].mask(); 247 break; 248 } 249 } 250 251 this.closable = enabled; 252 }, 253 254 /** 255 * Shows the load mask for this wizard. By default, the cardPanel's body 256 * will be masked. 257 * 258 * @param {Boolean} show true to show the load mask, otherwise false. 259 * @param {String} type The type of message for the {@Ext.LoadMask} covering 260 * the cardPanel, as defined in the cfg property "loadMaskConfig" 261 */ 262 showLoadMask: function (show, type) { 263 if (!type) { 264 type = 'default'; 265 } 266 267 if (show) { 268 if (this.loadMask == null) { 269 this.loadMask = new Ext.LoadMask(this.body); 270 } 271 this.loadMask.msg = this.loadMaskConfig[type]; 272 this.loadMask.show(); 273 } else { 274 if (this.loadMask) { 275 this.loadMask.hide(); 276 } 277 } 278 }, 279 280 281 /** 282 * show the side panel 283 * 284 */ 285 showSidePanel: function () { 286 this.sidePanel.show(); 287 }, 288 289 290 /** 291 * show the side panel 292 * 293 */ 294 showHeadPanel: function () { 295 this.headPanel.show(); 296 }, 297 298 299 /** 300 * hide the side panel 301 * 302 */ 303 showSidePanel: function () { 304 this.sidePanel.hide(); 305 }, 306 307 308 /** 309 * hide the head panel 310 * 311 */ 312 hideHeadPanel: function () { 313 this.headPanel.hide(); 314 }, 315 316 317 318 319 /** 320 * Inits the listener for the various {@link Ext.ux.Wiz.Card}s used 321 * by this component. 322 */ 323 initEvents: function () { 324 this.callParent(); 325 326 this.on('beforeclose', this.onBeforeClose, this); 327 }, 328 329 /** 330 * Creates the head- and the card-panel. 331 * Be sure to have the custom {@link Ext.ux.layout.CardLayout} available 332 * in order to make the card-panel work as expected by this component 333 * ({@link http://www.siteartwork.de/cardlayout}). 334 */ 335 initPanels: function () { 336 var cards = this.cards; 337 var cardPanelConfig = this.cardPanelConfig; 338 339 Ext.apply(this.headConfig, { 340 steps: this.cards.length 341 }); 342 343 this.headPanel = Ext.create('Ext.ux.wizard.Header', this.headConfig); 344 345 this.sidePanel = Ext.create('Ext.ux.wizard.Header', this.sideConfig); 346 347 Ext.apply(cardPanelConfig, { 348 layout: 'card', // new Ext.ux.wizard.CardLayout(), 349 items: cards 350 }); 351 352 Ext.applyIf(cardPanelConfig, { 353 region: 'center', 354 border: false, 355 activeItem: 0 356 }); 357 358 // var cards = this.cards; 359 360 for (var i = 0, len = cards.length; i < len; i++) { 361 cards[i].on('beforeactivate', this.onCardShow, this); 362 cards[i].on('clientvalidation', this.onClientValidation, this); 363 } 364 365 this.cardPanel = Ext.create('Ext.panel.Panel', cardPanelConfig); 366 }, 367 368 /** 369 * Creates the instances for the the window buttons. 370 */ 371 initButtons: function () { 372 this.previousButton = new Ext.Button({ 373 text: this.previousButtonText, 374 id: 'wizard-move-prev', 375 disabled: true, 376 minWidth: 75, 377 handler: this.onPreviousClick, 378 scope: this 379 }); 380 381 this.nextButton = new Ext.Button({ 382 text: this.nextButtonText, 383 id: 'wizard-move-next', 384 minWidth: 75, 385 handler: this.onNextClick, 386 scope: this 387 }); 388 389 this.cancelButton = new Ext.Button({ 390 text: this.cancelButtonText, 391 handler: this.onCancelClick, 392 scope: this, 393 minWidth: 75 394 }); 395 }, 396 397 // -------- listeners 398 399 /** 400 * Listener for the beforeclose event. 401 * This listener will return true or false based on the "closable" 402 * property by this component. This property will be changed by the "switchDialogState" 403 * method, indicating if there is currently any process running that should prevent 404 * this dialog from being closed. 405 * 406 * @param {Ext.Panel} panel The panel being closed 407 * 408 * @return {Boolean} 409 */ 410 onBeforeClose: function (panel) { 411 return this.closable; 412 }, 413 414 /** 415 * By default, the card firing this event monitors user input in a frequent 416 * interval and fires the 'clientvalidation'-event along with it. This listener 417 * will enable/disable the next/finish-button in accordance with it, based upon 418 * the parameter isValid. isValid" will be set by the form validation and depends 419 * on the validators you are using for the different input-elemnts in your form. 420 * If the card does not contain any forms, this listener will never be called by the 421 * card itself. 422 * 423 * @param {Ext.ux.Wiz.Card} The card that triggered the event. 424 * @param {Boolean} isValid "true", if the user input was valid, otherwise 425 * "false" 426 */ 427 onClientValidation: function (card, isValid) { 428 if (!isValid) { 429 console.log("setting disabled in onClientValidation"); 430 this.nextButton.setDisabled(true); 431 } else { 432 this.nextButton.setDisabled(false); 433 } 434 }, 435 436 /** 437 * Listener for the "show" event of the card that gets shown in the card-panel. 438 * Renders the next/previous buttons based on the position of the card in the wizard 439 * and updates the head-panel accordingly. 440 * 441 * @param {Ext.ux.Wiz.Card} The card being shown. 442 */ 443 onCardShow: function (card) { 444 var parent = card.ownerCt; 445 446 var items = parent.items; 447 448 for (var i = 0, len = items.length; i < len; i++) { 449 if (items.get(i).id == card.id) { 450 break; 451 } 452 } 453 454 this.currentCard = i; 455 this.headPanel.updateStep(i, card.carTitle); 456 this.sidePanel.updateStep(i, card.carTitle); 457 458 if (i == len - 1) { 459 this.nextButton.setText(this.finishButtonText); 460 } else { 461 this.nextButton.setText(this.nextButtonText); 462 } 463 464 if (card.isValid()) { 465 this.nextButton.setDisabled(false); 466 } 467 468 if (i == 0) { 469 this.previousButton.setDisabled(true); 470 } else { 471 this.previousButton.setDisabled(false); 472 } 473 474 }, 475 476 477 /** 478 * Fires the 'cancel'-event. Closes this dialog if the return value of the 479 * listeners does not equal to "false". 480 */ 481 onCancelClick: function () { 482 if (this.fireEvent('cancel', this, this.getWizardData()) !== false) { 483 this.closable = true; 484 this.close(); 485 } 486 }, 487 488 /** 489 * Fires the 'finish'-event. Closes this dialog if the return value of the 490 * listeners does not equal to "false". 491 */ 492 onFinish: function () { 493 if (this.fireEvent('finish', this, this.getWizardData()) !== false) { 494 this.closable = true; 495 this.close(); 496 } 497 }, 498 499 /** 500 * Listener for the previous-button. 501 * Switches to the previous displayed {@link Ext.ux.Wiz.Card}. 502 */ 503 onPreviousClick: function (btn) { 504 if (this.currentCard > 0) { 505 // this.cardPanel.getLayout().setActiveItem(this.currentCard - 1); 506 var mywiz = btn.up('panel').cardPanel; 507 this.navigate(mywiz, 'prev'); 508 } 509 }, 510 511 /** 512 * Listener for the next-button. Switches to the next {@link Ext.ux.Wiz.Card} 513 * if the 'beforehide'-method of it did not return false. The functionality 514 * for this is implemented in {@link Ext.ux.layout.CardLayout}, which is needed 515 * as the layout for the card-panel of this component. 516 */ 517 onNextClick: function (btn) { 518 if (this.currentCard == this.cardCount - 1) { 519 this.onFinish(); 520 } else { 521 // this.cardPanel.getLayout().setActiveItem(this.currentCard + 1); 522 var p = this.cardPanel.items.items[this.currentCard]; 523 524 if (p) { 525 f = p.getForm(); 526 if (f.isValid()) { 527 this.navigate(btn.up('panel').cardPanel, "next"); 528 } else { 529 p.items.items[0].el.frame("#ff0000"); 530 } 531 } 532 } 533 }, 534 navigate: function (panel, direction) { 535 // This routine could contain business logic required to manage the navigation steps. 536 // It would call setActiveItem as needed, manage navigation button state, handle any 537 // branching logic that might be required, handle alternate actions like cancellation 538 // or finalization, etc. A complete wizard implementation could get pretty 539 // sophisticated depending on the complexity required, and should probably be 540 // done as a subclass of CardLayout in a real-world implementation. 541 var layout = panel.getLayout(); 542 layout[direction](); 543 Ext.getCmp('wizard-move-prev').setDisabled(!layout.getPrev()); 544 // Ext.getCmp('wizard-move-next').setDisabled(!layout.getNext()); 545 }, 546 afterRender: function () { 547 this.callParent(); 548 549 var ly = this.cardPanel.getLayout(); 550 } 551 });