class CardManager extends Listenable { constructor() { super(); this.categories = { }; this.loadCardsRequest = new Request(TimeCards.REQUEST_URI + "Cards", Request.GET, this.loadedCards.bind(this), this.cardsLoadingError.bind(this)); this.reloadCardsInterval = setInterval(this.loadCards.bind(this), 5000); this.loadCards(); this.addCategoryRequest = new Request(TimeCards.REQUEST_URI + "Category/Create", Request.POST, this.addedCategory.bind(this), this.categoryCreationError.bind(this)); this.deleteCategoryRequest = new Request(TimeCards.REQUEST_URI + "Category/Delete", Request.POST, this.deletedCategory.bind(this), this.categoryDeletionError.bind(this)); this.updateCategoryRequest = new Request(TimeCards.REQUEST_URI + "Category", Request.POST, this.updatedCategory.bind(this), this.categoryUpdateError.bind(this)); this.deleteCardRequest = new Request(TimeCards.REQUEST_URI + "Card/Delete", Request.POST, this.deletedCard.bind(this), this.cardDeletionError.bind(this)); } performInitialCalls(eventType, callback) { if (eventType == "categoryadded") { for (var categoryId in this.categories) { var addedCategory = this.categories[categoryId]; var categoryAddedEvent = new Event("categoryadded"); categoryAddedEvent.category = { category_id: addedCategory.category_id, name: addedCategory.name, icon: addedCategory.icon }; callback(categoryAddedEvent); } } else if (eventType == "cardadded") { for (var categoryId in this.categories) { var category = this.categories[categoryId]; for (var cardId in category.cards) { var addedCard = category.cards[cardId]; var cardAddedEvent = new Event("cardadded"); cardAddedEvent.card = addedCard; callback(cardAddedEvent); } } } } loadCards() { if (this.loadCardsRequest.ready) { this.loadCardsRequest.send(); } } loadedCards(categoryCards) { var addedCategories = [ ]; var removedCategories = [ ]; for (var categoryId in categoryCards) { if (!(categoryId in this.categories)) { addedCategories.push(categoryId); } } for (var categoryId in this.categories) { if (!(categoryId in categoryCards)) { removedCategories.push(categoryId); } } //Process the deleted categories. for (var i = 0; i < removedCategories.length; i++) { var removedId = removedCategories[i]; var removedCategory = this.categories[removedId]; var categoryRemovedEvent = new Event("categoryremoved"); categoryRemovedEvent.category = { category_id: removedCategory.category_id, name: removedCategory.name, icon: removedCategory.icon }; this.dispatchEvent(categoryRemovedEvent); delete this.categories[removedId]; } //Process the added categories. The new cards in this category will be handled in the cardadded event later on. for (var i = 0; i < addedCategories.length; i++) { var addedId = addedCategories[i]; var addedCategory = categoryCards[addedId]; var categoryAddedEvent = new Event("categoryadded"); categoryAddedEvent.category = { category_id: addedCategory.category_id, name: addedCategory.name, icon: addedCategory.icon }; this.dispatchEvent(categoryAddedEvent); this.categories[addedId] = { category_id: addedCategory.category_id, name: addedCategory.name, icon: addedCategory.icon, cards: { } //We are passing an empty array so that all cards in an added category are treated as new. }; } //Now iterate through the categories... for (var categoryId in this.categories) { var category = this.categories[categoryId]; var remoteCategory = categoryCards[categoryId]; //...check if the category has changed itself (and if it's not a new one, of course)... if (addedCategories.indexOf(categoryId) == -1) { var categoryData = { category_id: category.category_id, name: category.name, icon: category.icon }; var remoteCategoryData = { category_id: remoteCategory.category_id, name: remoteCategory.name, icon: remoteCategory.icon }; if (!Object.equals(categoryData, remoteCategoryData)) { this.categories[categoryId] = remoteCategory; var categoryChangedEvent = new Event("categorychanged"); categoryChangedEvent.category = remoteCategoryData; this.dispatchEvent(categoryChangedEvent); } } var addedCards = [ ]; var removedCards = [ ]; //...and check if there are new cards... for (var cardId in remoteCategory.cards) { if (!(cardId in category.cards)) { addedCards.push(cardId); } } //...as well as removed ones. for (var cardId in category.cards) { if (!(cardId in remoteCategory.cards)) { removedCards.push(cardId); } } //Process the deleted cards. for (var i = 0; i < removedCards.length; i++) { var removedCardKey = removedCards[i]; var removedCard = category.cards[removedCardKey]; var cardRemovedEvent = new Event("cardremoved"); cardRemovedEvent.card = removedCard; this.dispatchEvent(cardRemovedEvent); delete category.cards[removedCardKey]; } //Process the added cards. for (var i = 0; i < addedCards.length; i++) { var addedCardKey = addedCards[i]; var addedCard = remoteCategory.cards[addedCardKey]; var cardAddedEvent = new Event("cardadded"); cardAddedEvent.card = addedCard; this.dispatchEvent(cardAddedEvent); category.cards[addedCardKey] = addedCard; } //Check if any of the pre-existing cards has changed. for (var cardId in category.cards) { if (addedCards.indexOf(cardId) == -1) { if (!Object.equals(category.cards[cardId], remoteCategory.cards[cardId])) { category.cards[cardId] = remoteCategory.cards[cardId]; var cardChangedEvent = new Event("cardchanged"); cardChangedEvent.card = remoteCategory.cards[cardId]; this.dispatchEvent(cardChangedEvent); } } } } } addCard(cardData) { if (!cardData.id_category in this.categories) { console.error("Trying to add a card to a category that does not exist."); return; } if (cardData.card_id in this.categories[cardData.id_category].cards) { console.error("Trying to add a card that already exists."); return; } var cardAddedEvent = new Event("cardadded"); cardAddedEvent.card = cardData; this.dispatchEvent(cardAddedEvent); this.categories[cardData.id_category].cards[cardData.card_id] = cardData; } updateCard(cardData) { if (!("card_id" in cardData)) { console.error("Trying to update a card without a card key!"); return; } var foundCard = false; for (var categoryId in this.categories) { var category = this.categories[categoryId]; if (category.cards && (cardData.card_id in category.cards)) { for (var key in cardData) { category.cards[cardData.card_id][key] = cardData[key]; } var cardChangedEvent = new Event("cardchanged"); cardChangedEvent.card = category.cards[cardData.card_id]; this.dispatchEvent(cardChangedEvent); foundCard = true; break; } } if (!foundCard) { console.error("Trying to update a non-existing card!"); return; } } /** * Deletes the card with the given ID. */ deleteCard(cardId) { this.deleteCardRequest.send({ card_id: cardId }); } deletedCard(response) { //Find the category with the deleted card and delete the card in there. for (var categoryId in this.categories) { var category = this.categories[categoryId]; if (category.cards && (response.card_id in category.cards)) { //Fire the cardremoved event. var cardRemovedEvent = new Event("cardremoved"); cardRemovedEvent.card = category.cards[response.card_id]; this.dispatchEvent(cardRemovedEvent); //Delete the card locally. delete this.categories[categoryId].cards[response.card_id]; return; } } } cardDeletionError(request, status, error) { alert("Error while deleting the card:\r\n\r\n" + JSON.stringify(error)); } /** * After the card transfer request has finished successfully, we want to move the category data in the local structure as well. */ transferCardData(cardId, sourceCategoryId, targetCategoryId) { if (!(sourceCategoryId in this.categories)) { console.error("Cannot transfer the card in the CardManager. The source category does not exist."); return; } if (!(targetCategoryId in this.categories)) { console.error("Cannot transfer the card in the CardManager. The target category does not exist."); return; } if (!(cardId in this.categories[sourceCategoryId].cards)) { console.error("Cannot transfer the card in the CardManager. The card does not exist in the source category."); return; } var transferred = this.categories[sourceCategoryId].cards[cardId]; delete this.categories[sourceCategoryId].cards[cardId]; transferred.id_category = targetCategoryId; this.categories[targetCategoryId].cards[cardId] = transferred; } /** * This function will transfer a card from a source to a target category on the server as well as on the client. */ transferCard(cardId, sourceCategoryId, targetCategoryId, skipRequest = false) { if (this.transferringCardData) { console.error("Trying to transfer a card while another transfer is already in progress."); alert("Another transfer is running at this time. Please wait until it is finished."); return; } this.transferringCardData = { cardId, sourceCategoryId, targetCategoryId } //Dispatch the before event. var beforeTransferCardEvent = new Event("beforecardtransfer"); beforeTransferCardEvent.cardId = cardId; beforeTransferCardEvent.sourceCategoryId = sourceCategoryId; beforeTransferCardEvent.targetCategoryId = targetCategoryId; this.dispatchEvent(beforeTransferCardEvent); if (!skipRequest) { //Perform the transfer request. var request = new Request(TimeCards.REQUEST_URI + "Card", Request.POST, this.onCardTransferRequestDidReturn.bind(this), this.onTransferError.bind(this)); request.send({ card_id: cardId, id_category: targetCategoryId }); } } onCardTransferRequestDidReturn(responseData) { if (responseData.error) { this.onTransferError(null, null, "Not successful. Reason: " + responseData.error); return; } this.transferCardData(this.transferringCardData.cardId, this.transferringCardData.sourceCategoryId, this.transferringCardData.targetCategoryId); var afterTransferCardEvent = new Event("aftercardtransfer"); afterTransferCardEvent.cardId = this.transferringCardData.cardId; afterTransferCardEvent.sourceCategoryId = this.transferringCardData.sourceCategoryId; afterTransferCardEvent.targetCategoryId = this.transferringCardData.targetCategoryId; this.dispatchEvent(afterTransferCardEvent); this.transferringCardData = null; } onTransferError(request, status, error) { var cardTransferErrorEvent = new Event("cardtransfererror"); cardTransferErrorEvent.cardId = this.transferringCardData.cardId; cardTransferErrorEvent.sourceCategoryId = this.transferringCardData.sourceCategoryId; cardTransferErrorEvent.targetCategoryId = this.transferringCardData.targetCategoryId; this.dispatchEvent(cardTransferErrorEvent); } saveCardData(cardData, successCallback = null, errorCallback = null) { if (!cardData.card_id) { console.error("You need to provide a card key if you want to save card data."); return; } var localCardData = null; for (var categoryId in this.categories) { var category = this.categories[categoryId]; for (var cardId in category.cards) { if (cardId == cardData.card_id) { localCardData = category.cards[cardId]; break; } } if (localCardData) { break; } } if (!localCardData) { console.error("Trying to save card data for a non-existing card. Aborting."); return; } //Check if a card transfer is involved and if so, perform the transfer locally before the request is run. if ("id_category" in cardData && cardData.id_category != localCardData.id_category) { this.transferCard(cardData.card_id, localCardData.id_category, cardData.id_category); } //Perform the save request. this.cardSaveSuccessCallback = successCallback; this.cardSaveErrorCallback = errorCallback; this.savingCardData = cardData; var saveRequest = new Request(TimeCards.REQUEST_URI + "Card", Request.POST, this.onCardDataSaved.bind(this), this.cardSaveErrorCallback.bind(this)); saveRequest.send(cardData); } onCardDataSaved(response) { this.updateCard(this.savingCardData); this.cardSaveSuccessCallback(response); this.savingCardData = null; this.cardSaveSuccessCallback = null; this.cardSaveErrorCallback = null; } onCardDataSaveError(request, status, error) { this.cardSaveSuccessCallback(request, status, error); this.savingCardData = null; this.cardSaveSuccessCallback = null; this.cardSaveErrorCallback = null; } cardsLoadingError(request, status, error) { alert("Error during the loading of the cards:\r\n\r\n" + JSON.stringify(error, true)); } getCategoryById(categoryId) { if (categoryId in this.categories) { return this.categories[categoryId]; } return null; } getCard(cardId) { if (this.categories) { for (var categoryId in this.categories) { var category = this.categories[categoryId]; if (cardId in category.cards) { return category.cards[cardId]; } } } return null; } /** * Adds a new card category if it does not yet exist. * @param category is the category object containing the name and optionally an icon. */ addCategory(category) { this.addCategoryRequest.send(category); } /** * Called when the category was successfully created on the server. */ addedCategory(response) { var categoryAddedEvent = new Event("categoryadded"); categoryAddedEvent.category = response; this.dispatchEvent(categoryAddedEvent); response.cards = { }; this.categories[response.category_id] = response; } categoryCreationError(request, status, error) { //Handle errors when the page is refreshing. if (request.readyState == 0) { console.warn("Aborting request."); return; } alert("Error during the creation of the category:\r\n\r\n" + JSON.stringify(error, true)); } /** * Moves the cards inside the specified category to the category with the key passed as second parameter or deletes the cards when "delete" or nothing is passed as second parameter. * Then, this function deletes the category. */ deleteCategory(categoryId, migrationTarget = "delete") { this.deleteCategoryRequest.send({ category_id: categoryId, migration_target: migrationTarget }); } /** * Called when the category was successfully deleted on the server. * Will fire a categoryremoved event. */ deletedCategory(response) { var categoryRemovedEvent = new Event("categoryremoved"); categoryRemovedEvent.category = { category_id: response.category_id, name: this.categories[response.category_id].name, icon: this.categories[response.category_id].icon }; this.dispatchEvent(categoryRemovedEvent); delete this.categories[response.category_id]; } categoryDeletionError(request, status, error) { alert("Error during the deletion of the category:\r\n\r\n" + JSON.stringify(error, true)); } updateCategory(category) { this.updateCategoryRequest.send(category); } updatedCategory(response) { var categoryChangedEvent = new Event("categorychanged"); categoryChangedEvent.category = response; this.dispatchEvent(categoryChangedEvent); //Apply the returned values to the local data. for (var key in response) { this.categories[response.category_id][key] = response[key]; } } categoryUpdateError(request, status, error) { alert("Error while updating the category:\r\n\r\n" + JSON.stringify(error, true)); } }