i have this Panel array coming from backend which has another array Tests. i have mapped them on my custom accordion with checkboxes. the problem i am facing is i should be able to select/deselect Tests without removing it from the from front-end like it Disappears when i deselect. how can i solve this issue?
you can see from that image
https://i.stack.imgur.com/qJUFy.png
here is my html file
<ngb-panel *ngFor="let panel of panels" id="{{panel.Id}}" [title]="panel.Name">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" [name]="panel.Id + '-' + panel.Moniker" [ngModel]="checkAllTestsSelected(panel)" (ngModelChange)="onPanelCheckboxUpdate($event, panel)" [id]="panel.Id + '-' + panel.Moniker">
<span class="custom-control-indicator"></span>
</label>
</ng-template>
<ng-template ngbPanelContent>
<div class="individual-panel" *ngFor="let test of panel.Tests">
<span class="text-dimmed">{{test.Name}}</span>
<span *ngIf="panel.Name.includes('ENA') || panel.Name.includes('Celiac')">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" [name]="test.Id + '-' + test.Code"
[ngModel]="testSelectionSession.SelectedPanelIds.indexOf(panel.Id) > -1 || testSelectionSession.SelectedPanelIds.indexOf(test.AssociatedPanel?.Id) > -1"
(ngModelChange)="onTestCheckboxUpdate($event, test, panel)"
[id]="test.Id + '-' + test.Code">
<span class="custom-control-indicator"></span>
</label>
</span>
</div>
ts file
checkAllTestsSelected(panel: TestOrderPanel) {
// get all individual test panels
let individualTestPanelIds = panel.Tests.reduce((acc, test) => {
if (test.AssociatedPanel) {
acc.push(test.AssociatedPanel.Id);
}
return acc;
}, []);
// check if all individual test panels are selected
let allIndividualTestsSelected = individualTestPanelIds.reduce(
(acc: boolean, panelId: number) =>
acc && this.panelIds.indexOf(panelId) > -1,
individualTestPanelIds.length > 0 &&
panel.Tests.length === individualTestPanelIds.length
);
// if selected, remove all individual test panels and add the panel group
if (panel.Tests.length > 0 && allIndividualTestsSelected) {
this.panelIds = this.panelIds.filter(
panelId => individualTestPanelIds.indexOf(panelId) === -1
);
this.selectedPanels = this.selectedPanels.filter(
selectedPanel => individualTestPanelIds.indexOf(selectedPanel.Id) === -1
);
this.panelIds.push(panel.Id);
this.selectedPanels.push(panel);
this.updateSession();
}
return this.panelIds.indexOf(panel.Id) > -1;
}
onPanelCheckboxUpdate($event: boolean, panel: TestOrderPanel) {
let testPanelIds = panel.Tests.reduce((acc, test) => {
if (test.AssociatedPanel) {
acc.push(test.AssociatedPanel.Id);
}
return acc;
}, []);
// Wipe any duplicates
this.panelIds = this.panelIds.filter(
panelId => panel.Id !== panelId && testPanelIds.indexOf(panelId) === -1
);
this.selectedPanels = this.selectedPanels.filter(
selectedPanel =>
panel.Id !== selectedPanel.Id &&
testPanelIds.indexOf(selectedPanel.Id) === -1
);
if ($event) {
this.panelIds.push(panel.Id);
this.selectedPanels.push(panel);
}
this.updateSession();
}
onTestCheckboxUpdate($event: boolean,
test: TestOrderPanelTest,
panel: TestOrderPanel,
index) {
let testPanelIds = panel.Tests.reduce((acc, test) => {
if (test.AssociatedPanel) {
acc.push(test.AssociatedPanel.Id);
}
return acc;
}, []);
let associatedTestPanels =
this.testSelectionSession.IndividualTestPanelsForAll.filter(
testPanel => testPanelIds.indexOf(testPanel.Id) > -1
);
// If the panel is selected and a test within the panel is deselected,
// remove the panel and back all of the individual tests
if (this.panelIds.indexOf(panel.Id) > -1 && !$event) {
this.selectedPanels = this.selectedPanels.filter(
e => e.Tests.splice(index, 1)
);
}
let clickedTestPanel = associatedTestPanels.find(
testPanel => (test.AssociatedPanel ? test.AssociatedPanel.Id : -1) ===
testPanel.Id
);
if (clickedTestPanel) {
// Wipe any duplicates
this.panelIds = this.panelIds.filter(
panelId => clickedTestPanel.Id !== panelId
);
this.selectedPanels = this.selectedPanels.filter(
panel => clickedTestPanel.Id !== panel.Id
);
// Add individual panel if checkbox selected
if ($event) {
this.panelIds = this.panelIds.concat(clickedTestPanel.Id);
this.selectedPanels = this.selectedPanels.concat(clickedTestPanel);
}
}
this.updateSession();
}
this.panelIds includes IDs of panels and this.selectedPanels includes whole panel array which is selected.
i have created a stackblitz too
my code is doing something like that stackblitz.com/edit/angular-bsszc9
and here is example of how my page will look like
stackblitz
how can i solve this problem?
thanks
Delete
if (this.panelIds.indexOf(panel.Id) > -1 && !$event) {
this.selectedPanels = this.selectedPanels.filter(
e => e.Tests.splice(index, 1)
);
}
This part doesn't do anything but removing checkboxes and panels for no logical reason in a very random way (removing the checkbox with the same index from each panel and if there isn't any remove the whole panel)
Please read: Array.prototype.splice and Array.prototype.filter to understand how is this happening.
Related
The functionality works just fine, however, I get this error in the console when clicking the trash icon while deleting the todo item.
Uncaught TypeError: Cannot read properties of undefined (reading 'isDone')
at TodoList.done_undone (script.js:68)
at HTMLLIElement.<anonymous> (script.js:104)
done_undone # script.js:68
(anonymous) # script.js:104
Here is the link to the live page: https://sarahschlueterportfolio.z22.web.core.windows.net/todolist.html
Here is my code:
HTML
<!-- Page Content -->
<div class="container">
<div class="todoHeader">
<h1>My To Do List</h1>
<input type="text" id="userInput" placeholder="Things to be done..." />
<span class="addButton" id="add_button">Add</span>
</div>
<ul id="todoListItems">
</ul>
</div>
<!-- /.container -->
JS
const todoObjectList = [];
class TodoList {
constructor(item){
this.ulElement = item;
}
add() {
const todoInput = document.querySelector("#userInput").value;
if (todoInput == "") {
alert("Please enter an item.")
} else {
const todoObject = {
id : todoObjectList.length,
todoText : todoInput,
isDone : false,
}
todoObjectList.unshift(todoObject);
this.display();
document.querySelector("#userInput").value = '';
}
}
done_undone(x) {
const selectedTodoIndex = todoObjectList.findIndex((item) => item.id == x);
todoObjectList[selectedTodoIndex].isDone == false ? todoObjectList[selectedTodoIndex].isDone = true : todoObjectList[selectedTodoIndex].isDone = false;
console.log(todoObjectList[selectedTodoIndex].isDone);
this.display();
}
deleteElement(z) {
const selectedDelIndex = todoObjectList.findIndex((item) => item.id == z);
todoObjectList.splice(selectedDelIndex,1);
this.display();
}
display() {
this.ulElement.innerHTML = "";
todoObjectList.forEach((objectItem) => {
const liElement = document.createElement("li");
const delButton = document.createElement("i");
liElement.innerText = objectItem.todoText;
liElement.setAttribute("data-id", objectItem.id);
delButton.setAttribute("data-id", objectItem.id);
delButton.classList.add("far", "fa-trash-alt");
liElement.appendChild(delButton);
delButton.addEventListener("click", function(e) {
const deleteId = e.target.getAttribute("data-id");
personalTodoList.deleteElement(deleteId);
})
liElement.addEventListener("click", function(e) {
const selectedId = e.target.getAttribute("data-id");
personalTodoList.done_undone(selectedId);
})
if (objectItem.isDone) {
liElement.classList.add("checked");
}
this.ulElement.appendChild(liElement);
})
}
}
const listSection = document.querySelector("#todoListItems");
personalTodoList = new TodoList(listSection);
document.querySelector(".addButton").addEventListener("click", function() {
personalTodoList.add()
})
I have tried looking up different solutions. One recommended putting the script at the bottom of my html instead of the top, that made no difference.
I'm fairly certain there is something wrong with the line of code before the console log in the done_undone method.
Any insight on how to clear this error would be greatly appreciated!
when you click delete you are removing element by id
deleteElement(z) {
const selectedDelIndex = todoObjectList.findIndex((item) => item.id == z);
todoObjectList.splice(selectedDelIndex,1); // element is removed from the array
this.display();
}
but done_undone is called inside of this.diaplay() in the next row
done_undone(x) {
const selectedTodoIndex = todoObjectList.findIndex((item) => item.id == x);
// selectedTodoIndex here is -1 because element is already removed
// add this condition to check if element is present in the array
if (selectedTodoIndex !== -1) {
todoObjectList[selectedTodoIndex].isDone == false ?
todoObjectList[selectedTodoIndex].isDone = true :
todoObjectList[selectedTodoIndex].isDone = false;
}
}
I have a pretty basic scenario where I have bunch of selectable/clickable elements and want to highlight the last selected/clicked. Here is my jquerish version of it where I add particular css class to the selected element and remove that class from its siblings. Is there any better way to do this with React ?
function Controls(props) {
const getSiblings = (element) => {
let siblings = [];
if (!element.parentNode) {
return siblings;
}
let sibling = element.parentNode.firstChild;
while (sibling) {
if (sibling.nodeType === 1 && sibling !== element) {
siblings.push(sibling);
}
sibling = sibling.nextSibling;
}
return siblings;
};
const handleClick = (event) => {
event.target.classList.add("symbol-preview-selected");
let siblings = getSiblings(event.target);
siblings.map((e) => {
e.classList.remove("symbol-preview-selected")
});
};
return(
<div className="grid-control-container">
<div className="flex-row-container">
<span style={{marginRight: "4px"}}>Color:</span>
<div className="flex-row-container">
{[...Array(10).keys()].map((i) => <div className={`symbol-preview symbol-`+i} onClick={handleClick} key={i.toString()}/>)}
</div>
</div>
</div>
);
}
Put the clicked element index into state instead.
function Controls(props) {
const [selectedIndex, setSelectedIndex] = useState(-1);
const makeHandleClick = i => () => {
setSelectedIndex(i);
};
return (
<div className="grid-control-container">
<div className="flex-row-container">
<span style={{ marginRight: "4px" }}>Color:</span>
<div className="flex-row-container">
{[...Array(10).keys()].map((i) => (
<div
className={'symbol-preview symbol-' + i + (i === selectedIndex ? ' symbol-preview-selected' : '')}
onClick={makeHandleClick(i)}
key={i}
/>
))}
</div>
</div>
</div>
);
}
I have a provider that provides tags for news articles (list with news). If they are more than three, then the additional tags (>3) will be grouped together (called plusTags in the example code). I can see in my console that I have all the tags, but they are not distributed correctly. Ex.
On the first page I have four news with the distributed tags "a,b,c", b", "ac" "b". On the next page, the news articles are (obviously) different, but the distribution of the tags is the SAME ("a,b,c", b", "ac" "b") as on the first page, even if the tags should be different. So the distribution follows the same pattern. I suspect it's my code in my "componentDidMount" function, as its there where I distribute all the tags. Suspect it might repeat because this function repeats itself?
public componentDidMount(): void {
let tags = [];
let plusTags = [];
if (this.props.tags != null) {
if (this.props.tags.length > 0) {
for (var i = 0; i < 3; i++) {
if (this.props.tags[i] != undefined) {
tags.push(this.props.tags[i] + " ");
}
}
for (var j = this.props.tags.length - 1; j >= 3; j--) {
if (this.props.tags[i] != undefined) {
plusTags.push(this.props.tags[j] + " ");
}
}
} else {
tags = this.props.tags;
}
}
this.setState({
tags: tags,
plusTags: plusTags
});
}
and in my render
public render(): React.ReactElement<INewsTagsProps> {
return <React.Fragment>
<div className={styles.tagContainer}>
{
this.state.tags ?
this.state.tags.map((t) => {
if (this.props.background == BackgroundType.None) {
return (
<a href={this.props.tagPageUrl + "?tag="+ t}>
<span className={styles.tagNewsTiles}>{t}</span>
</a>);
}
else {
return(
<a href={this.props.tagPageUrl + "?tag="+ t}>
<span className={styles.tagFollowedNews}>{t}</span>
</a>);
}
})
: null
}
{this.state.plusTags.length > 0 ?
<span className={`callout-target-${this.state.targetIndex} ${this.props.background == BackgroundType.None ? styles.tagNewsTiles : styles.tagFollowedNews}`}
onClick={(e) => {e.stopPropagation(); this.setState({plusTagsDialogOpen: true});}}>+ {this.state.plusTags.length}</span>
:
null
}
</div>
<Callout
className="ms-CalloutExample-callout"
gapSpace={0}
target={this.state.target}
onDismiss={() => this.closeDialog()}
hidden={!this.state.plusTagsDialogOpen}
isBeakVisible={true}
beakWidth={10}
directionalHint={DirectionalHint.topCenter}
>
<div className={styles.tagPopupWrapper}>
<div className={styles.tagPopupContainer}>
{this.state.plusTags ?
this.state.plusTags.map((t) => {
if (this.props.background == BackgroundType.None) {
return (
<a href={this.props.tagPageUrl+ "?tag="+t}>
<span className={styles.tagNewsTiles}>{t}</span>
</a>);
}
else {
return(
<a href={this.props.tagPageUrl+ "?tag="+t}>
<span className={styles.tagFollowedNews}>{t}</span>
</a>);
}
}):
null}
</div>
</div>
</Callout>
;
I have ul li element, where is written users list.
and I have one input, where users can write and filter users list.
and this ul element is hidden by default. after focus on input I'm showing this ul element and on blur I'm hiding this ul.
<input type="text" autocomplete="off" ng-model="checkData[key]" />
<ul>
<li ng-repeat="user in users | identification:checkData[key]">{{user}}</li>
</ul>
And my filter:
return (items: Array<string|number>, value: string|number) =>
{
if(typeof items !== 'undefined')
{
var filtered: Array<string|number> = [];
for(var i: number = 0, length: number = items.length; i < length; i++)
{
var element: string|number = items[i];
if((((element + '').indexOf(value + '') === 0) && element !== value) || typeof value === 'undefined')
{
filtered.push(element);
}
}
return filtered;
}
}
This works excelent, but I've one issue. When user enters some value in input, user list in ul element will be filtered.
so when user will focus again on input, in ul shows filtered data. I want to on every focus, show full list and don't delete input value.
How can Achieve this?
You can just disable the filter on focus by passing in an argument to the filter method, in this way:
<ul>
<li ng-repeat="user in users | identification:checkData[key]:isEnabled" ng-focus="isEnabled = false;">{{user}}</li>
</ul>
Modify your filter:
return (items: Array<string|number>, value: string|number, isEnabled: boolean) =>
{
if (!isEnabled) {
return items; // Return the unfiltered list ...
}
if(typeof items !== 'undefined')
{
var filtered: Array<string|number> = [];
for(var i: number = 0, length: number = items.length; i < length; i++)
{
var element: string|number = items[i];
if((((element + '').indexOf(value + '') === 0) && element !== value) || typeof value === 'undefined')
{
filtered.push(element);
}
}
return filtered;
}
}
This will do the trick. Try this one.
<input type="text" autocomplete="off" ng-model="checkData[key]" ng-focus="filterKey[key] = ''" ng-blur="filterKey[key] = checkData[key]"/>
<!-- hidden field to save search text -->
<input type="hidden" ng-model="filterKey[key]"/>
<ul>
<li ng-repeat="user in users | identification:filterKey[key]">{{user}}</li>
</ul>
I'm trying to sort a list of physicians on a page by name. There is a dropdown and the user can select ascending or descending. Neither one is working. The UI is not updating at all.
EDIT: I've changed the sort code to follow the example on the KO website.
When i step into the code i get an error when I hover over left and it says "'left' is not defined"
function SortDownloadPhysicians(){
var sortDirection = getSortDirection()
var sortByParam = getSortByParam()
if(sortByParam === "name"){
if(sortDirection === "ascending"){
self.physicians().sort(function (left, right) { return left.name == right.name ? 0 : (left.name < right.name ? -1 : 1) });
}else{
self.physicians().sort(function (left, right) { return left.name == right.name ? 0 : (left.name > right.name ? -1 : 1) });
}
}
else{ //date of birth
if(sortDirection === "ascending"){
self.physicians().sort(function (left, right) { return left.dateOfBirth == right.dateOfBirth ? 0 : (left.dateOfBirth < right.dateOfBirth ? -1 : 1) });
}else{
self.physicians().sort(function (left, right) { return left.dateOfBirth == right.dateOfBirth ? 0 : (left.dateOfBirth > right.dateOfBirth ? -1 : 1) });
}
}
Here's my sort function
function sortDownloadPage() {
var sortDirection = getSortDirection();
var sortBy = getSortBy();
// sort by name
if (sortDirection === "ascending") {
self.physicians().sort(function (a, b) {
if (a.name().toLowerCase() > b.name().toLowerCase()) {
return 1;
}
if (a.name().toLowerCase() < b.name().toLowerCase()) {
return -1;
}
// a must be equal to b
return 0;
});
} else {
self.physicians().sort(function (a, b) {
if (a.name().toLowerCase() < b.name().toLowerCase()) {
return 1;
}
if (a.name().toLowerCase() > b.name().toLowerCase()) {
return -1;
}
// a must be equal to b
return 0;
});
}
};
and here's part of the view model
var ViewModels = ViewModels || {};
(function (ViewModels) {
var DownloadVM = function (options) {
options = $.extend({
physicians: ko.utils.unwrapObservable(options.physicians || [])
}, options);
//********************************************************************************
// Private properties
var self = this,
_physicians = ko.observableArray([]),
_page = 1,
_pageSize = 2;
//********************************************************************************
// Public properties
self.physicians = ko.computed(function () {
return ko.utils.unwrapObservable(_physicians);
});
UI code
<div class="grid-list-group" data-bind="template: { name: 'physicianDownloadTemplate', foreach: physicians }">
<script type="text/html" id="physicianDownloadTemplate">
<div class="group-item clearfix" data-bind="fxVisibility: visible">
<div class="item-col selector">
<input type="checkbox" data-bind="checked: checked">
</div>
<div class="item-col photo hidden-sm hidden-xs" data-bind="click: $root.toggleOpenState">
<img class="rounded" title="Profile Picture" src="#Url.Content("~/Content/Images/profile-default.png")">
</div>
<div class="item-col field name" onclick="$(this).parent().children('.group-item-drawer').slideToggle();">
<div class="caption">Physician Name</div>
<div class="value" data-bind="text: name">{NAME}</div>
</div>
<div style="float: right;">
<div class="item-col field date-of-birth">
<div class="caption">Date of Birth</div>
<div class="value" data-bind="text: dateOfBirth">{DATE OF BIRTH}</div>
</div>
</div>
<div class="group-item-drawer clearfix" style="display: none; clear: both;" data-bind="template: { name: 'downloadItemTemplate', foreach: files }"></div>
</div>
</script>
I typically solve this problem using a computed function. The computed can subscribe to the "sorting" variable, so when it changes, it will recompute based on the new variable. From there, it is simply a matter of returning the appropriate sorting.
function vm() {
var physicians = [
'smith',
'grant',
'foreman'
];
this.sorting = ko.observable();
this.sortingOptions = ['A-Z', 'Z-A'];
this.physicians = ko.computed(function() {
var sorting = this.sorting(),
sorted = physicians.slice().sort();
if (sorting == 'Z-A')
sorted.reverse();
return sorted;
});
}
ko.applyBindings(new vm());
and in the view
<select data-bind="options: sortingOptions, value: sorting"></select>
<select data-bind="options: physicians"></select>