/** * @class IDU.cardoMap.BergPass.Web.Client.Controls.ApplicationForm.controller.Steps * @extends Ext.ux.app.control.Controller * Controller, der sich um die Navigation zwischen den Formularschritten kümmert. * @author Robin * @date 2014-03-11 */ Ext4.define('IDU.cardoMap.BergPass.Web.Client.Controls.ApplicationForm.controller.Steps', { extend: 'Ext.ux.app.control.Controller', stores: [ 'Steps' ], views: [ 'CardContainer' ], refs: [{ ref: 'mainContainer', selector: '#mainContainer' },{ ref: 'cardContainer', selector: '#cardContainer' },{ ref: 'stepGrid', selector: '#stepGrid' },{ ref: 'prevStepButton', selector: '#buttonPrevStep' },{ ref: 'finishButton', selector: '#buttonFinish' }], /** * @inheritdoc * @protected */ init: function() { this.listen({ 'component': { '#cardContainer': { // das passiert erst nach dem Hinzufügen, da noch nicht bekannt ist, dass es sich dabei um eine Application-Komponente handeld 'add': this.onCardContainerAdd, 'deactivate': this.onCardContainerDeactivate, 'activate': this.onCardContainerActivate }, '#cardContainer > component': { 'activate': this.onCardContainerComponentActivate, 'beforedeactivate': this.onCardContainerComponentBeforeDeactivate }, '#stepGrid': { 'render': this.onStepGridRender, 'itemclick': this.onStepGridItemClick }, '#buttonPrevStep': { 'click': this.onButtonPrevStepClick }, '#buttonNextStep': { 'click': this.onButtonNextStepClick } } }); }, /** * Wird aufgerufen, wenn eine "Seite" in den Card-Container eingefügt wird. * @param {Ext.container.Container} contianer * @param {Ext.Component} component * @private */ onCardContainerAdd: function(container, component) { // Wir füllen hier unseren Store, da wir das Grid links für die Navigation // durch die einzelnen Seiten nutzen. this.getStore('Steps').add({ title: component.title, cardId: component.getId() }); }, /** * Initialisiert die Schritte. Das muss manuell gemacht werden, da das onAdd noch nicht greift. * Das liegt daran, dass der Container noch nicht Teil des Anwendungs-Layouts ist. * @private */ initSteps: function() { var container = this.getCardContainer(), store = this.getStore('Steps'), grid = this.getStepGrid(), items = container.items, active = container.getLayout().getActiveItem(); items.each(function(component) { store.add({ title: component.title, cardId: component.getId() }); }); }, /** * Wird aufgerufen, wenn die Bearbeitungsansicht aktiviert wurde. * @private */ onCardContainerActivate: function() { var sm = this.getStepGrid().getSelectionModel(), finishButton = this.getFinishButton(); finishButton.toggle(false, true); if (!this.lastSelectedStep) return; // letzten Step selektieren sm.setLocked(false); sm.select(this.lastSelectedStep, false, true); sm.setLocked(true); delete this.lastSelectedStep; }, /** * Wird aufgerufen, wenn die Bearbeitungsansicht verlassen * (und die Prüfungs-Ansicht aktiviert) wurde. * @private */ onCardContainerDeactivate: function() { // Wir merken uns den letzten Schritt und deselektieren alle Schritte. var sm = this.getStepGrid().getSelectionModel(); this.lastSelectedStep = sm.getLastSelected(); sm.setLocked(false); sm.deselectAll(true); sm.setLocked(true); }, /** * Wird nach dem Rendern des Schritte-Grids aufgerufen. * @param {IDU.cardoMap.BergPass.Web.Client.Controls.ApplicationForm.view.StepGrid} grid * @private */ onStepGridRender: function(grid) { // Wir nutzen das nur intern zum markieren, fangen aber das Klicken auf ein Item ab. // Wir fangen nicht das selectionchange-Event ab, da dann bereits der neue Schritt // selektiert ist. Das ist aber nicht immer korrekt, bspw. wenn man nach fehlerhafter // Validierung beim alten Schritt verbleibt. grid.getSelectionModel().setLocked(true); }, /** * Wird nach Änderung der Selektion im Schritte-Grid aufgerufen. * @param {Ext.grid.View} view * @param {IDU.cardoMap.BergPass.Web.Client.Controls.ApplicationForm.model.Step} record * @param {HTMLElement} item * @param {Number} index * @private */ onStepGridItemClick: function(view, record, item, index) { var cardContainer = this.getCardContainer(), layout = cardContainer.getLayout(), activeCard = layout.getActiveItem(), cardId = record.get('cardId'); // noch im Prüfungsmodus? if (this.lastSelectedStep) { delete this.lastSelectedStep; this.getMainContainer().getLayout().setActiveItem(cardContainer); this.skipValidation = true; } // activate kommt nicht, wenn schon aktiv ist, bspw. nach zurückspringen // von der "letzten Seite". if (activeCard && activeCard.getId() === cardId) this.selectStepByCard(activeCard); else layout.setActiveItem(cardId); }, /** * Wird vor dem Deaktivieren einer Card aufgerufen. * @param {Ext.Component} currentCard * @param {Ext.Component} newCard * @private */ onCardContainerComponentBeforeDeactivate: function() { var _getEntityPath = function(cmp) { // nicht getEntityPath/getRootRecord, da es einfach auch nur als Properties bei speziellen Layout-Containern zugewiesen ist. var hasIndex = typeof cmp.entityPath.getIndex() === 'number', parent = hasIndex ? cmp.entityPath.copyWithIndex(null) : cmp.entityPath.getParent(); return { record : cmp.rootRecord, o: cmp.entityPath,//.toString(); asString: cmp.entityPath.toString(), parentString: parent ? parent.toString() : null }; }, _sortEntityPaths = function(a, b) { return a.asString < b.asString ? -1 : (a.asString > b.asString ? 1 : 0); }; return function(currentCard, newCard) { // Schon abgeschickt, nur noch schauen. if (this.application.isFinished) return true; if (this.skipValidation) { delete this.skipValidation; return true; } // Validierung für die Daten der Card if (!this.isValidating) { this.isValidating = true; this.application.mask(); var entityPaths = Ext4.Array.map(currentCard.query('[entityPath]'), _getEntityPath), i = 0, len = entityPaths.length, values = {}, pathsProcessed = {}, path; // Sortieren nach Pfad, das reicht schon. Uns geht es nur darum, die inneren Sachen nicht mitzuschicken, // da das dann doppelt wäre. entityPaths.sort(_sortEntityPaths); for (; i < len; i++) { path = entityPaths[i]; if (!path.parentString || !pathsProcessed[path.parentString]) values[path.asString] = path.record.getValuesByEntityPath(path.o, true); pathsProcessed[path.asString] = true; } IDU.cardoMap.BergPass.Web.Client.Controls.ApplicationForm.ApplicationFormControlRemote.AxValidateStep( this.application.puzzleCategoryId, values, Ext4.Function.bind(this.onValidateStep, this, [currentCard, newCard], 0)); return false; } }; }(), /** * Callback für die Validierung. * @param {Ext.Component} currentCard * @param {Ext.Component} newCard * @param {Object} result * @private */ onValidateStep: function(currentCard, newCard, result) { this.application.unmask(); // Exception beim Validieren if (result.error) { this.application.alertMessage( 'Fehler bei der Validierung', result.error.Message, function() { this.isValidating = false; this.selectStepByCard(currentCard); }, this); return; } this.application.updateValidation(result.value, currentCard); if (!result.value.isValid) { var messageBox = this.application.createMessageBox({ title: 'Bitte beachten!', msg: 'Ihre Eingaben sind noch unvollständig oder fehlerhaft. Möchten Sie trotzdem fortfahren?', ui: 'bergpass-red', fbar: [{ ui: 'bergpass-red', iconAlign: 'left', iconCls: 'idu-cardomap-bergpass-client-applicationform-icon-prev', text: 'Eingaben korrigieren', handler: function() { this.isValidating = false; this.selectStepByCard(currentCard); messageBox.close(); }, scope: this },'->',{ ui: 'bergpass-green', iconAlign: 'right', iconCls: 'idu-cardomap-bergpass-client-applicationform-icon-next', text: 'Trotzdem weiter', handler: function() { this.getCardContainer().getLayout().setActiveItem(newCard); messageBox.close(); this.isValidating = false; }, scope: this }] }); } else { this.getCardContainer().getLayout().setActiveItem(newCard); this.isValidating = false; } }, /** * Wird beim Aktivieren (Umschalten einer Card im CardContainer aufgerufen. * @param {Ext.Component} component * @private */ onCardContainerComponentActivate: function(component) { this.application.fireEvent('stepchange', component.title); this.selectStepByCard(component); }, /** * Selektiert einen Schritt im Grid anhand der Komponente. * @param {Ext.Component} card * @private */ selectStepByCard: function(card) { var store = this.getStore('Steps'), index = store.indexOfId(card.getId()), prevButton = this.getPrevStepButton(), sm = this.getStepGrid().getSelectionModel(), finishButton = this.getFinishButton(); // aktiven Step selektieren sm.setLocked(false); sm.select(index, false, true); sm.setLocked(true); // Zurück-Button deaktivieren, wenn auf erster Seite prevButton.setDisabled(index === 0); finishButton.toggle(false, true); }, /** * Wird beim Klick auf den Button "Zurück" aufgerufen. * @private */ onButtonPrevStepClick: function() { this.skipValidation = true; this.stepBy(-1); }, /** * Wird beim Klick auf den Button "Weiter" aufgerufen. * @private */ onButtonNextStepClick: function() { if (!this.stepBy(1)) this.getController('Finish').finishApplication(); }, /** * Springt die angegebene Anzahl Schritte vor (positive Werte) oder zurück (negative Werte) * @param {Number} steps Die Anzahl der Schritte * @return {Boolean} Gibt an, ob weiter gesprungen wurde. * @private */ stepBy: function(steps) { var store = this.getStore('Steps'), length = store.getCount(), layout = this.getCardContainer().getLayout(), active = layout.getActiveItem(), index = store.indexOfId(active.getId()); index += steps; if (index >= 0 && index < length) { layout.setActiveItem(store.getAt(index).getId()); return true; } return false; } });