class CardCategoryView extends DragTargetView { constructor(element) { super(element); this.cardViews = { }; this.cards = { }; this.onCardUpdateBound = this.onCardUpdate.bind(this); TimeCards.dataManager.addStoreErrorListener("card", this.onStoreError.bind(this)); } build(element) { this.expandBar = document.createElement("DIV"); this.expandBar.className = "expand-bar expanded"; this.expandBar.addEventListener("click", this.onExpandBarClicked.bind(this)); element.appendChild(this.expandBar); this.expandIcon = document.createElement("DIV"); this.expandIcon.className = "expand-icon"; this.expandBar.appendChild(this.expandIcon); this.noResultsLabel = document.createElement("span"); this.noResultsLabel.innerText = "No results"; //TODO: Localize this. this.noResultsLabel.className = "no-results-label"; this.expandBar.appendChild(this.noResultsLabel); this.editForm = document.createElement("form"); this.editForm.addEventListener("submit", this.onDoneButtonPressed.bind(this)); this.expandBar.appendChild(this.editForm); this.iconLabel = document.createElement("INPUT"); this.iconLabel.className = "icon-label"; this.iconLabel.setAttribute("list", "category-icons"); this.iconLabel.readOnly = true; Validation.addAttributes(this.iconLabel, "card_category", "icon"); this.editForm.appendChild(this.iconLabel); this.titleLabel = document.createElement("INPUT"); this.titleLabel.className = "title-label"; this.titleLabel.readOnly = true; Validation.addAttributes(this.titleLabel, "card_category", "name"); this.editForm.appendChild(this.titleLabel); this.visibilitySelect = new VisibilitySelect(); this.editForm.appendChild(this.visibilitySelect.element); //TODO: When "custom" option is added for sharing category only with select users, add a dropdown here that in collapsed form only shows user's icons. this.doneButton = document.createElement("button"); this.doneButton.innerText = "Done"; this.doneButton.className = "done-button"; this.editForm.appendChild(this.doneButton); this.rightContainer = document.createElement("div"); this.rightContainer.className = "right-container"; this.expandBar.appendChild(this.rightContainer); this.visibilityIcon = document.createElement("img"); this.visibilityIcon.className = "visibility-icon colored-icon-text"; this.visibilityIcon.src = TimeCards.getResourceUri("Images/Public.webp"); this.visibilityIcon.title = "Visible to: everyone"; this.rightContainer.appendChild(this.visibilityIcon); this.categoryOptionsButton = document.createElement("BUTTON"); this.categoryOptionsButton.className = "options require_w__card_category"; UIKit.registerAction(this, this.categoryOptionsButton, "contextmenu:card-category-options"); this.rightContainer.appendChild(this.categoryOptionsButton); this.cardsContainer = document.createElement("DIV"); this.cardsContainer.className = "cards-container"; element.appendChild(this.cardsContainer); this.addCardButton = document.createElement("DIV"); this.addCardButton.className = "add-card-button require_w__card"; this.addCardButton.addEventListener("click", this.onAddCardButtonClicked.bind(this)); this.addCardButton.style.display = "none"; this.cardsContainer.appendChild(this.addCardButton); this.addCardButtonImage = document.createElement("IMG"); this.addCardButtonImage.src = TimeCards.getResourceUri("Images/Add.png"); this.addCardButton.appendChild(this.addCardButtonImage); this.dummyCardView = document.createElement("DIV"); this.dummyCardView.className = "dummy-card-view"; this.cardsContainer.insertBefore(this.dummyCardView, this.cardsContainer.lastChild); return element; } setCategoryData(categoryData) { this.categoryData = categoryData; this.iconLabel.value = this.categoryData.icon; this.titleLabel.value = this.categoryData.name; this.visibilitySelect.value = this.categoryData.visibility; //Show or hide visibility icon. //TODO: Show user icons here when custom sharing is implemented. this.visibilityIcon.style.display = this.categoryData.visibility == 0 ? "none" : ""; this.addCardButton.setAttribute("category-id", this.categoryData.category_id); this.addCardButton.style.display = ""; //Load the expanded state. var expandedState = localStorage.getItem("category_" + this.categoryData.category_id + "_expanded"); if (expandedState == 0) { this.expandBar.classList.remove("expanded"); } //Remove any preexisting data listener. TimeCards.dataManager.removeDataListener("card", this.onCardUpdateBound); //Add a card listener with a filter to this category. TimeCards.dataManager.addDataListener("card", { id_category: this.categoryData.category_id, }, this.onCardUpdateBound); } onCardUpdate(cardId, card, sortedBefore) { if (!card) { //Handle the removed card. if (!(cardId in this.cardViews)) { console.warn("Trying to remove a card view that is not in the category!"); return; } this.cardsContainer.removeChild(this.cardViews[cardId].element); delete this.cardViews[cardId]; delete this.cards[cardId]; } else if (!(cardId in this.cardViews)) { //Handle the added card. var cardView = new CardView(); cardView.setCardData(card); cardView.categoryView = this; cardView.viewController = this.viewController; cardView.element.classList.add("appeared"); this.cardsContainer.insertBefore(cardView.element, (sortedBefore && (sortedBefore in this.cardViews) ? this.cardViews[sortedBefore].element : this.cardsContainer.lastChild)); this.cardViews[cardId] = cardView; this.cards[cardId] = card; } else { //Handle the changed card. this.cards[cardId] = card; this.cardViews[cardId].setCardData(card); } } onStoreError(cardId, card, error) { } showSourceDummy(insertionBeforeElement) { this.draggingElement = insertionBeforeElement; this.cardsContainer.insertBefore(this.dummyCardView, insertionBeforeElement); this.dummyCardView.classList.add("source"); } showTargetDummy(card) { //Create a copy of the card entity and modify the category ID to sort the target dummy into the target category. var dummyCard = Object.create(card); dummyCard.id_category = this.categoryData.category_id; var sortedBefore = TimeCards.dataManager.sortIn("card", dummyCard); this.cardsContainer.insertBefore(this.dummyCardView, (sortedBefore && (sortedBefore in this.cardViews) ? this.cardViews[sortedBefore].element : this.cardsContainer.lastChild)); this.dummyCardView.classList.add("target"); } hideDummy() { this.draggingElement = null; this.dummyCardView.classList.remove("source"); this.dummyCardView.classList.remove("target"); } acceptsType(draggableType) { //Refuse the card if the user has no write permission on the cards. return (draggableType == "card") && Authentication.isResourceAvailable("card", "w"); } setFilter(filterOptions) { var matchCount = 0; for (var cardId in this.cards) { var card = this.cards[cardId]; var match = true; if (filterOptions.id_project && card.id_project != filterOptions.id_project) { match = false; } if (filterOptions.search_query) { var words = filterOptions.search_query.toLowerCase().split(" "); for (var i = 0; i < words.length; i++) { var query = words[i]; var project = TimeCards.dataManager.getEntity("project", card.id_project); if (!((project && project.name.toLowerCase().indexOf(query) != -1) || card.title.toLowerCase().indexOf(query) != -1 || this.categoryData.name.toLowerCase().indexOf(query) != -1)) { match = false; break; } } } this.cardViews[cardId].element.classList.remove("appeared"); if (!match) { if (this.cardViews[cardId].element.className.indexOf("hidden-by-filter") == -1) { this.cardViews[cardId].element.classList.add("hidden-by-filter"); } } else { this.cardViews[cardId].element.classList.remove("hidden-by-filter"); matchCount++; } } //Keep the category collapsed if it does not contain any filter result (if there are any filter options set at all). if (Object.keys(filterOptions).length > 0 && matchCount == 0) { if (this.element.className.indexOf("no-results") == -1) { this.element.classList.add("no-results"); } } else { this.element.classList.remove("no-results"); } } onDragEnter(draggedObject, validType, sourceView) { //Show the target dummy if this view is not the source. if (sourceView != this) { this.element.classList.add("dragging-over"); this.showTargetDummy(draggedObject); } } onDragExit(sourceView) { this.element.classList.remove("dragging-over"); //Hide the target dummy if this view is not the source. if (sourceView != this) { this.hideDummy(); } } onDragEnd() { this.element.classList.remove("dragging-over"); this.hideDummy(); } onDrop(droppedObject, sourceView) { //No cross-collection transfers allowed! if (sourceView.collectionView != this.collectionView || sourceView == this) { return; } //Update the dropped card's category. TimeCards.dataManager.store("card", droppedObject.card_id, { id_category: this.categoryData.category_id }); } onExpandBarClicked(event) { //Ignore clicks on buttons and non-read-only inputs. if (event.target.nodeName.toLowerCase() == "button" || (event.target.nodeName.toLowerCase() == "input" && !event.target.readOnly)) { return; } this.expandBar.classList.toggle("expanded"); localStorage.setItem("category_" + this.categoryData.category_id + "_expanded", this.expandBar.className.indexOf("expanded") == -1 ? 0 : 1); } onAddCardButtonClicked(event) { var categoryId = event.currentTarget.getAttribute("category-id"); if (!categoryId) { return; } if (!this.addCardPopover) { //Get the period popover instance. this.addCardPopover = UIKit.getPopoverById("add-card-popover"); } this.addCardPopover.setCategory(categoryId); this.addCardPopover.showForElement(this.addCardButton, "right"); } onRenameCategoryMenuItemPressed(event) { this.iconLabel.readOnly = false; this.titleLabel.readOnly = false; this.titleLabel.focus(); } onDeleteCategoryMenuItemPressed(event) { var deleteCategoryViewController = UIKit.getViewControllerById("delete-category-view-controller"); deleteCategoryViewController.setCategory(this.categoryData); this.viewController.presentModalViewController(deleteCategoryViewController); } onDoneButtonPressed(event) { event.preventDefault(); this.save(); } /** * Called when the enter key is pressed while editing an input field or the done button is pressed. * Saves the values and ends edit mode. */ save() { this.saveValues(); this.iconLabel.readOnly = true; this.titleLabel.readOnly = true; } /** * Sends the entered values in the input fields to the server. */ saveValues() { TimeCards.dataManager.store("card_category", this.categoryData.category_id, { name: this.titleLabel.value, icon: this.iconLabel.value, visibility: parseInt(this.visibilitySelect.value) }); } }