module fb { export class ResourceBase { $promise: ng.IPromise; $conflicts: boolean; $resolved: boolean = false; $failed: boolean = false; $rootName: string; $dirty: boolean = false; $lastModification: number = null; isArray: boolean = false; $tempPK: string = null; invalidField: string[] = []; exceptionFromAPI: string[] = []; ignoredKeys: string[] = ['root', '$promise', '$resolved', '$rootName', '$dirty', '$lastModification', 'isArray', '$tempPK', '$$hashKey', 'exceptionFromAPI', 'ignoredKeys', 'invalidField', 'mergeConflicts', '$enqueueable', '$failed']; mergeConflicts: IMergeConflict[] = []; $enqueueable: boolean = true; constructor(data: any, changeTrack?: boolean, rootName?: string, isArray?: boolean, $enqueueable?: boolean, skipValidation: boolean = false) { if (typeof changeTrack === 'undefined') { changeTrack = false; } var self = this; self.$rootName = rootName; if (typeof $enqueueable !== 'undefined') { self.$enqueueable = $enqueueable; } if (typeof isArray !== 'undefined') { self.isArray = isArray; } if (data.$promise) { self.$promise = data.$promise; self.$promise.then(function (res) { if (self.preInit) { self.preInit(res); } self.init(res, changeTrack); if (self.validation) { self.validation(); } self.$resolved = true; }, function (error) { self.$failed = true; }); } else { if (self.preInit) { self.preInit(data); } var def; if (angular.element(document).injector()) { def = angular.element(document).injector().get('$q').defer(); } else { var $injector = angular.injector(['ng']); $injector.invoke(['$q', function ($q: ng.IQService) { def = $q.defer(); }]) } self.$promise = def.promise; def.resolve(data); self.init(data, changeTrack); if (!skipValidation && data && Object.keys(data).length > 0 && self.validation) { self.validation(); } self.$resolved = true; } } getPrimaryKey(): any { throw "Primary key not implemented by subclass."; } getGlobalUniqueId(): string { if (this.getPrimaryKey() === null) { var uniqueIdService = angular.element(document).injector().get('uniqueIdService') as fb.IUniqueIdService; this.$tempPK = uniqueIdService.getUniqueIdNoLimit(this.$rootName); } return this.$rootName + this.getPrimaryKey(); } getForeignKeyObject(): IForeignKeyObject { throw "Foreign key not implemented by subclass."; } validation() { } isValid() { var self = this; return self.invalidField.length === 0; } setValidation(object, modelValidation) { var attributeKeys = Object.keys(modelValidation) for (var k: number = 0; k < attributeKeys.length; k++) { var attribute = attributeKeys[k] var validationKeys = Object.keys(modelValidation[attribute]) for (var j: number = 0; j < validationKeys.length; j++) { object[attribute][validationKeys[j]] = modelValidation[attribute][validationKeys[j]]; } object[attribute].valid = fb.changeTrackValid(object[attribute]); } } init(initData: any, changeTrack) { var self = this; var getProperty = function (value: any, root: any, propertyString: string) { if (changeTrack) { return new fb.ChangeTrack(value, root, propertyString); } else { return value; } }; var getRootName = function (): string { if (self.$rootName) { return self.$rootName; } else { return ''; } }; var recurse = function (obj, data, propertyString: string) { if (typeof data === 'undefined' || data === null) { return; } else if (data instanceof Array) { // data är en array for (var k: number = 0; k < data.length; k++) { if (typeof data[k] === 'object') { var tmpObject; if (data[k] instanceof Array) { tmpObject = []; } else { tmpObject = {}; } recurse(tmpObject, data[k], propertyString + '[]'); obj[k] = tmpObject; } else { obj[k] = getProperty(data[k], self, propertyString + '[]'); } } return; } // data är ett objekt var keys = Object.keys(data); for (var i: number = 0; i < keys.length; i++) { //var commonService: fb.ICommonService = angular.element(document).injector().get('commonService'); if (self.ignoredKeys.indexOf(keys[i]) !== -1) { continue; } if (typeof data[keys[i]] === 'object') { // propertien var klassad som object if (data[keys[i]] instanceof Array) { // Propertien är en lista, kör rekursivt över alla objekt i listan obj[keys[i]] = []; recurse(obj[keys[i]], data[keys[i]], propertyString + '.' + keys[i]); } else if (fb.ChangeTrack && data[keys[i]] instanceof fb.ChangeTrack) { // Propertien är ett changetrack obj[keys[i]] = data[keys[i]]; } else if (data[keys[i]] === null) { // propertien var satt till null obj[keys[i]] = getProperty(data[keys[i]], self, propertyString + '.' + keys[i]); //new fb.ChangeTrack(data[keys[i]], self, propertyString + '.' + keys[i]); } else { // propertien är ett icke null objekt obj[keys[i]] = {}; recurse(obj[keys[i]], data[keys[i]], propertyString + '.' + keys[i]); } } else if (typeof data[keys[i]] === 'function') { // Gör ingenting om propertyn är en funktion } else if (typeof data[keys[i]] === 'undefined') { obj[keys[i]] = undefined; } else { // propertien var inte ett objekt utan innehöll ett datavärde obj[keys[i]] = getProperty(data[keys[i]], self, propertyString + '.' + keys[i]); //new fb.ChangeTrack(data[keys[i]], self, propertyString + '.' + keys[i]); } } }; // END Recurse if (typeof initData === 'undefined') { return; } else if (initData === null) { var ct = new fb.ChangeTrack(null, self, getRootName()); var keys = Object.keys(ct); for (var i: number = 0; i < keys.length; i++) { self[keys[i]] = ct[keys[i]]; } } else { recurse(self, initData, getRootName()); } if (self.postInit) { self.postInit(initData); } } preInit(initData) { return; } postInit(initData) { return; } postMerge(mergeData) { return; } exportObject(originalValue: boolean): any { if (!angular.element(document).injector) { throw 'Hittar ingen injector, så kan inte injecta service till resourceBase'; } var commonService = angular.element(document).injector().get('commonService') as fb.ICommonService; var self = this; return commonService.changeTrack.fromChangeTrackObject(self, self.isArray, originalValue); } getSaveObject(): any { return this.exportObject(false); } getOriginalObject(): any { return this.exportObject(true); } //Detta är en kopia av isDirty men loggar istället de fält som är dirtymarkerade //Ska ALDRIG användas när vi väl löst problemen getFirstDirtyChangetrack(): fb.IChangeTrack { if (!angular.element(document).injector) { throw 'Hittar ingen injector, så kan inte injecta service till resourceBase'; } var commonService = angular.element(document).injector().get('commonService') as fb.ICommonService; var self = this; var fullyRecurse = function (data) { if (typeof data === 'undefined' || data === null) { return; } var keys = Object.keys(data); for (var i: number = 0; i < keys.length; i++) { if (commonService.ignoredKeys.indexOf(keys[i]) > 0) { continue; } if (typeof data[keys[i]] === 'object') { // propertien var klassad som object if (data[keys[i]] instanceof Array) { // Propertien är en lista, kör rekursivt över alla objekt i listan for (var j: number = 0; j < data[keys[i]].length; j++) { if (data[keys[i]][j] === null) { //return data[keys[i]][j].dirty; //obj[keys[i]].push(new fb.ChangeTrack(data[keys[i]][j], self, propertyString + '.' + keys[i] + '[]')); } else if (typeof data[keys[i]][j] === 'object') { if (commonService.changeTrack.isChangeTrackObject(data[keys[i]][j])) { return checkDirtyChangeTrack(data[keys[i]][j]) === true ? data[keys[i]][j] : null; } else { var dirtyObject = fullyRecurse(data[keys[i]][j]); if (dirtyObject) { return dirtyObject; } } } else { if (commonService.changeTrack.isChangeTrackObject(data[keys[i]][j])) { if (checkDirtyChangeTrack(data[keys[i]][j])) { return data[keys[i]][j]; } } } } } else if (data[keys[i]] === null) { // propertien var satt till null //if (checkDirtyChangeTrack(data[keys[i]])) { // return true; //} } else { if (commonService.changeTrack.isChangeTrackObject(data[keys[i]])) { if (checkDirtyChangeTrack(data[keys[i]])) { return data[keys[i]]; //return true; } } else { var dirtyObj = fullyRecurse(data[keys[i]]); if (dirtyObj) { return dirtyObj; } } } } else if (typeof data[keys[i]] === 'function') { // Gör ingenting om propertyn är en funktion } else { // propertien var inte ett objekt utan innehöll ett datavärde if (checkDirtyChangeTrack(data[keys[i]])) { return data[keys[i]]; } } } } var checkDirtyChangeTrack = function (changeTrackObj: fb.IChangeTrack): boolean { if (commonService.changeTrack.isChangeTrackObject(changeTrackObj)) { if (changeTrackObj.dirty === true) { if (changeTrackObj.value == changeTrackObj.originalValue || changeTrackObj.ignoreMerge) { changeTrackObj.dirty = false; } } return changeTrackObj.dirty === true; } }; return fullyRecurse(self); } isDirty(): boolean { if (!angular.element(document).injector) { throw 'Hittar ingen injector, så kan inte injecta service till resourceBase'; } var commonService = angular.element(document).injector().get('commonService') as fb.ICommonService; var self = this; var fullyRecurse = function (data) { if (typeof data === 'undefined' || data === null) { return; } var keys = Object.keys(data); for (var i: number = 0; i < keys.length; i++) { if (commonService.ignoredKeys.indexOf(keys[i]) > 0) { continue; } if (typeof data[keys[i]] === 'object') { // propertien var klassad som object if (data[keys[i]] instanceof Array) { // Propertien är en lista, kör rekursivt över alla objekt i listan for (var j: number = 0; j < data[keys[i]].length; j++) { if (data[keys[i]][j] === null) { // Do nothing continue; } else if (typeof data[keys[i]][j] === 'object') { if (commonService.changeTrack.isChangeTrackObject(data[keys[i]][j])) { if (checkDirtyChangeTrack(data[keys[i]][j])) { // Inre if-sats för att vi inte vill gå till else:n om denna är false return true; } } else if (fullyRecurse(data[keys[i]][j])) { return true; } } else if (commonService.changeTrack.isChangeTrackObject(data[keys[i]][j]) && checkDirtyChangeTrack(data[keys[i]][j])) { return true; } } } else if (data[keys[i]] === null) { // propertien var satt till null continue; } else { if (commonService.changeTrack.isChangeTrackObject(data[keys[i]])) { if (checkDirtyChangeTrack(data[keys[i]])) { // Inre if-sats för att vi inte vill gå till else:n om denna är false return true; } } else if (fullyRecurse(data[keys[i]])) { return true; } } } else if (typeof data[keys[i]] === 'function') { // Gör ingenting om propertyn är en funktion continue; } else { // propertien var inte ett objekt utan innehöll ett datavärde if (checkDirtyChangeTrack(data[keys[i]])) { return true; } } } } var checkDirtyChangeTrack = function (changeTrackObj: fb.IChangeTrack): boolean { if (commonService.changeTrack.isChangeTrackObject(changeTrackObj)) { if (changeTrackObj.dirty === true) { if (changeTrackObj.value == changeTrackObj.originalValue || changeTrackObj.ignoreMerge) { changeTrackObj.dirty = false; } } return changeTrackObj.dirty === true; } }; return fullyRecurse(self) === true; } //Inverse är en invers merge som mergar men sätter dirty till true. merge(mergeObject: any, inverseDirty: boolean): { databaseValue: any; localValue: any; }[] { if (!angular.element(document).injector) { throw 'Hittar ingen injector, så kan inte injecta service till resourceBase'; } if (inverseDirty !== true) { inverseDirty = false; } var self = this; var commonService = angular.element(document).injector().get('commonService') as fb.ICommonService; var mergeConflicts: fb.IMergeConflict[] = []; if (typeof mergeObject.$promise !== 'undefined' && mergeObject.$resolved === false) { mergeObject.$promise.then(function (data) { commonService.merge(self, data, mergeConflicts); self.$dirty = self.isDirty(); if (mergeConflicts.length > 0) { self.mergeConflicts = mergeConflicts; } else if (self.isDirty() && mergeConflicts.length == 0) { //Forca save om vi blivit nekade sparning men inte har några konflikter var globalTimer = angular.element(document).injector().get('globalTimerService') as fb.IGlobalTimerService; globalTimer.globalSave(true); } if (self.postMerge) { self.postMerge(data); } if (self && self.$rootName === MaeklarObjektDOGen.$rootName) { //Gör enbart för mäklarobjekt just nu commonService.Events.Resources.mergeCompleted(self.getGlobalUniqueId()); } }); } else { commonService.merge(self, mergeObject, mergeConflicts); if (mergeConflicts.length > 0) { self.mergeConflicts = mergeConflicts; } if (self.postMerge) { self.postMerge(mergeObject); } if (self && self.$rootName === MaeklarObjektDOGen.$rootName) { //Gör enbart för mäklarobjekt just nu commonService.Events.Resources.mergeCompleted(self.getGlobalUniqueId()); } if (self.isDirty() && mergeConflicts.length == 0) { //Om vi är dirty efter förändring (dvs om vi gjort ändringar under sparning) så köar vi upp igen var obj = self.getFirstDirtyChangetrack(); if (obj) { if (angular.element(document).injector) { var logservice = angular.element(document).injector().get('logService') as fb.ILogService; var msg = { propertyreference: obj && obj.propertyReference, originalvalue: obj && obj.originalValue, localvalue: obj && obj.value, pk: self && self.getPrimaryKey ? self.getPrimaryKey() : null, rootname: self && self.$rootName }; logservice.log("Smutsig efter sparning", msg); } } } } this.$dirty = this.isDirty(); return mergeConflicts; } /// /// resetOriginal används när vi vill uppdatera ett värde i klienten som inte ska konflikthanteras. Vi sätter om original så att det inte blir konflikt. /// setValue(track: fb.ChangeTrack, value: any, resetOriginal?: boolean) { track.value = value; if (angular.isDefined(resetOriginal)) { track.originalValue = value; } this.applyValue(track); } applyValue(track: fb.ChangeTrack) { var self = this; track.dirty = track.originalValue != track.value; // jshint ignore:line var valid = fb.changeTrackValid(track); var validChanged = valid !== track.valid; track.valid = valid; var isNull = track.value === null; var isEmpty = track.value === ''; var isUndefined = track.value === undefined; track.missValue = isNull || isEmpty || isUndefined; if (typeof track !== 'undefined' && typeof self !== 'undefined') { var fko: IForeignKeyObject; try { fko = self.getForeignKeyObject(); } catch (ex) { // Foreign key finns inte, ska inte göra något } if (fko && fko.key && (fko.entity === fb.ForeignKeyEntity.MAEKLAROBJEKT || fko.entity === fb.ForeignKeyEntity.KONTAKT)) { var toastService= angular.element(document).injector().get('toastService') as fb.IToastService; if (validChanged) { if (track.valid) { // Ta bort toast toastService.removeToastForField(self.getGlobalUniqueId(), (track).propertyReference); } else if (!track.valid) { // Lägg till toast var $translate = angular.element(document).injector().get('$translate') as any; var $sce: ng.ISCEService = angular.element(document).injector().get('$sce'); var navigationService = angular.element(document).injector().get('navigationService') as fb.INavigationService; var link; if (fko.entity === fb.ForeignKeyEntity.MAEKLAROBJEKT) { var namn1 = navigationService.getNavigationsObjekt(fko.entity, fko.key).Visningstext || 'mäklarobjekt'; link = '' + namn1 + ''; } else if (fko.entity === fb.ForeignKeyEntity.KONTAKT) { var namn2 = navigationService.getNavigationsObjekt(fko.entity, fko.key).Visningstext || 'kontakt' link = '' + namn2 + ''; } var fieldName = $translate.instant((track).propertyReference); var toastMessage = $translate.instant('TOAST_MESSAGE.VALIDATION_ERROR.FIELD_ERROR1') + fieldName + $translate.instant('TOAST_MESSAGE.VALIDATION_ERROR.FIELD_ERROR2') + link + $translate.instant('TOAST_MESSAGE.VALIDATION_ERROR.FIELD_ERROR3'); toastService.addToast({ Message: $sce.trustAsHtml(toastMessage), EntityType: fko.entity, EntityId: fko.key, UniqueId: self.getGlobalUniqueId(), Field: (track).propertyReference }); } } if (!valid) { toastService.resetToastTimer(self.getGlobalUniqueId(), (track).propertyReference); } } if (!track.dirty) { self.$dirty = self.isDirty(); if (self.$dirty === false) { self.$lastModification = null; } } else { self.$dirty = true; self.$lastModification = new Date().getTime(); } if (self.$enqueueable) { if (!self.$dirty) { //Det här är om man ändrar tillbaka så hela objektet inte längre är smutsigt var queueService = angular.element(document).injector().get('queueService') as fb.IQueueService; queueService.get('dirty').remove(self.getGlobalUniqueId()); //Om vi inte längre är dirty så spelar errorkön inte något roll //eftersom de ändringar vi potentiellt aldrig sparade inte längre spelar någon roll self.exceptionFromAPI.length = 0; queueService.get('error').remove(self.getGlobalUniqueId()); } else { if (self.getPrimaryKey() !== null) { // Undvik entiterer som aldrig sparats self.enqueue(); } } } } } enqueue(): ng.IPromise<{}> { var self = this; if (self.$enqueueable) { if (!angular.element(document).injector) { throw 'Hittar ingen injector, så kan inte injecta service till resourceBase'; } //var globalTimerService: fb.IGlobalTimerService = angular.element(document).injector().get('globalTimerService'); var queueService = angular.element(document).injector().get('queueService') as fb.IQueueService; var q: ng.IQService = angular.element(document).injector().get('$q'); self.$dirty = true; self.$lastModification = new Date().getTime(); var deferred = q.defer(); var dirtyQueue = queueService.get('dirty'); var itemInQueue = dirtyQueue.get(self.getGlobalUniqueId()); var removeAndLog = function () { var removeThis = dirtyQueue.remove(self.getGlobalUniqueId()); var logService = angular.element(document).injector().get('logService') as fb.ILogService; var item = removeThis.Item && removeThis.Item.getSaveObject ? "\nItem: " + JSON.stringify(removeThis.Item.getSaveObject()) : ""; logService.log("Misslyckades att spara i ResourceBase: \nGlobalUniqueId: " + self.getGlobalUniqueId() + "\nisValid: " + removeThis.Item.isValid() + item); }; var enqueueNow = true; if (itemInQueue && itemInQueue.Item !== self) { var toastService = angular.element(document).injector().get('toastService') as fb.IToastService; toastService.removeToastsForUniqueId(self.getGlobalUniqueId(), self.invalidField); if (itemInQueue.Item.isValid()) { var globalTimerService = angular.element(document).injector().get('globalTimerService') as fb.IGlobalTimerService; var savedItem = globalTimerService.saveItem(self.getGlobalUniqueId()); if (savedItem && savedItem.$promise) { enqueueNow = false; savedItem.$promise.then(function () { }, function () { removeAndLog(); }).finally(function () { dirtyQueue.enqueue({ UniqueId: self.getGlobalUniqueId(), Item: self, Deferred: deferred }); }); } } } // Har inget element i kön redan eller kunde inte spara det som fanns if (enqueueNow) { if (itemInQueue && itemInQueue.Item !== self) { removeAndLog(); } dirtyQueue.enqueue({ UniqueId: self.getGlobalUniqueId(), Item: self, Deferred: deferred }); } return deferred.promise; } else { throw "Modellen är inte enqueuebar" } } dequeue(): void { if (!angular.element(document).injector) { throw 'Hittar ingen injector, så kan inte injecta service till resourceBase'; } var self = this; //var globalTimerService: fb.IGlobalTimerService = angular.element(document).injector().get('globalTimerService'); var queueService = angular.element(document).injector().get('queueService') as fb.IQueueService; self.$dirty = false; queueService.get('dirty').remove(self.getGlobalUniqueId()); } } //export interface IResourceBase { // $promise: any; // $conflicts: boolean; // $resolved: boolean; // $rootName: string; // $dirty: boolean; // $lastModification: number; // isArray: boolean; // $tempPK: string; // getSaveObject(): any; //} export interface IForeignKeyObject { entity: fb.ForeignKeyEntity; key: number; } }