I am looking for a cleaner way to close my three dropdowns when I click outside of them. Currently, I am utilizing window.addEventListener('click') with some exclusions to get this done. While it works decently, I do get random errors saying that my event.target is undefined. I haven't had the best luck at repeating the error either. Anyway, my current code is below.
useEffect(() => {
// close dropdown on any click outside of button/dropdown/dropdown options
// these are simple exclusions from the global click - they include dropdowns, dropdown options, and filter/sort buttons
window.addEventListener("click", e => {
if (e.path[4].id.includes('filterSortBar') ||
e.target.id.includes('maxSlider') ||
e.target.id.includes('minSlider') ||
e.target.id.includes('minInputContainer') ||
e.target.id.includes('maxInputContainer') ||
e.target.type === 'number' ||
e.path[4].id.includes('filterButtonsContainer') ||
e.path[4].id.includes('relativeContainer') ||
e.target.textContent === 'Relevance' ||
e.target.textContent === 'Retailers' &&
!e.target.className.includes('buttonPadding')){
} else {
setAddFilterTruthy(false)
setSortTruthy(false)
setPriceTruthy(false)
}
})
}, []);
The ugliness is glaring. Basically, if the click does not contain the element, I set the truthy value to false and the dropdown closes appropriately.
Related
This is kind of complicated to explain. I'm trying to make a toggle function that adds and deletes items on a different page with useContext. Everything adds and deletes fine until I tab to the other page, and that's where the error begins. Once I do that the function ignores what's in the array and will duplicate items in the array. What's odd about it is if I console.log or manually check the new item with the current array items it shows me everything. For example in order to add the new item to the array it checks if the index of new item is -1. If it is it will add the item and if not it will delete the item. However once I leave the page it doesn't see the item anymore and adds it anyway. If I console.log the item name and new item name, I can see both, and if I use === to check it also works fine until I switch tabs and then even though it's still console logging both names somehow it's still adding the item and ignoring that it already contains the item.
The code directory in my sandbox is src/Helpers/MyPicsContext. Here is the link to my sandbox codesandbox
The tabs on the website are Picks and the search page which you can access on Picks before any items are added or by clicking the magnifying glass in the top right of page.
And here is the actual code for the context page.
export const MyPicksContextProvider = props => {
const [picksList, setPicksList] = useState(
JSON.parse(localStorage.getItem('picksList'))
||
[]
)
//console.log(picksList)
useEffect(() => {
localStorage.setItem("picksList", JSON.stringify(picksList));
}, [picksList]);
const deleteCoin = coin => {
if (picksList.length === 1) {
setPicksList([]);
} else {
setPicksList(picksList.filter(list => {
console.log(list)
//console.log(coin)
return list !== coin;
}))
}
console.log('deleted');
console.log(picksList)
}
const toggleCoin = (coin) => {
if (picksList.length === 0) {
setPicksList([...picksList, coin]);
} else {
if (picksList.indexOf(coin) === -1 ) {
setPicksList([...picksList, coin]);
console.log('added 1')
} else {
deleteCoin(coin)
}
}
}
Perhaps I just don't understand useState and prevState, but I can't seem to find any examples that apply to what I'm trying to do here. It makes total sense in creating a counter or something simple like that.
The issue is that .indexOf checks for referential equality of items, same as using ===. This means that const a = {id: 'x'}; const b = a; console.log(a === b); will output true, but const a = {id: 'x'}; const b = {id: 'x'}; console.log(a === b); will output false.
In your situation, upon changing pages / refreshing, the state is reset and loaded from local storage. However, this creates new objects which are not referentially equal. Instead of using .indexOf you want to use .find, something like array.find((element) => element.id === newItem.id) to find the index of the item. You could also do your own deep equality check (confirming every field matches), but I suspect the ID alone is sufficient.
In fact, I would also recommend only keeping the array of "picks" as an array of string ID's. Then you can lookup the full data from your table for each of these ID's. Otherwise the current price will be stored in localStorage, and could be out of date.
I have a React video player project and I have a way of skipping the video by 1 second when a keyDown event occurs. This is effective because if you hold the key down multiple events are fired so the video continues to skip at e.g. 5-second intervals up until the user releases the key.
I now want to implement it differently so when a user taps the key down once and then, even if they lift their finger the video carries on skipping until the user presses the key down once again (so they don't need to hold the key down)
How do I do this? This is a section of my code so far. It's actually for a Smart TV APP so any reference you read regards focus is just the navigation pattern for selecting divs with your TV remote. The main bit of code is inside the keyDown function that then calls handleSkipForward or handleSkipBack. Also the videoElement ref is shared across functional components using store.VideoElement.
I was thinking of something like having a boolean state hook for skipping but when that state is true how do I repeatedly request a skip of 5 seconds or is there something within the video element that you can just set to progress through a video at a set rate?
focusKey,
elementRef: containerRef,
onKeyDown: (event) => {
// if (!playerControlsVisible) {
// event.stopPropagation()
// event.preventDefault()
// return
// }
if (event.key === "Enter" || event.key === " ") {
skipDirection === "Forward"
? handleSkipForwardAction()
: handleSkipBackwardAction()
}
},
onFocus: (event) => {},
})
function handleSkipForwardAction() {
if (store.videoElement.currentTime + skipTime < duration) {
store.videoElement.currentTime += skipTime
} else if (store.videoElement.currentTime < duration) {
store.videoElement.currentTime = duration
} else return
}
function handleSkipBackwardAction() {
if (store.videoElement.currentTime - skipTime > 0) {
store.videoElement.currentTime -= skipTime
} else if (store.videoElement.currentTime > 0) {
store.videoElement.currentTime = 0
} else return
}```
It should be simple. What you need to do is implement the setInterval function.
You can just add an interval (infinite loop) and store the Interval ID on a state so that you can stop that infinite loop using the clearInterval function.
onKeyDown: (event) => {
if (event.key === "Enter" || event.key === " ") {
if (moveForward){
clearInterval(moveForward);
// Assuming you use Functional Component and `useState` hooks.
setMoveForward(null);
return;
}
const action = skipDirection === "Forward"
? handleSkipForwardAction
: handleSkipBackwardAction;
setMoveForward(setInterval(action,100));
// 100 is in milisecond. so 1000 = 1 second.
// 100 = 10 function call in 1 second. Meaning 6 second skip/second.
// You can adjust the calculation on your own.
}
},
This is not tested, so do let me know if it works or not. But the idea is to use a looping function when the user clicks the specific button and stop the looping function when the user clicks the specific button again.
References:
setInterval
clearInterval
I'm having a bit of trouble with ng-model. The function checkshow() is called whenever an option from the select box is selected. Depending on which buttons are selected it should call the function in the condition.
$scope.checkshow=function(item) {
alert(item);
var Name=item;
if ($scope.radioSelected === 'radiobutton1')
{ $scope.show(Name);
$scope.showfeature(Name);
}
else if($scope.radioSelected === 'radiobutton2' && $scope.checkboxed1 === 'detailchecked'){
alert("detail");
$scope.showDetails(Name);
}
else if($scope.radioSelected === 'radiobutton2' && $scope.checkboxed2 === 'comparechecked'){
alert("Surprise");
}
else{
$scope.showS(Name);
}
};
Only the if and else conditions are working, it never enters the else if part .
<input type="checkbox" id="chck1" disabled value="detailchecked" ng-model="checkboxed1">Detailed
<input type="checkbox" id="chck2" disabled value="comparechecked" ng-model="checkboxed2">Compare</label></td></tr>
There are 2 radiobuttions 1 and 2. I didn't write their code because conditions on them are working.
The model of a checkbox is a boolean: if the checkbox with ng-model="checkboxed1" is chceked, then $scope.checkboxed1 is set to true, otherwise it's set to false.
But you're comparing it to 'detailchecked', so that comparison will always be false.
Read the documentation: it explains how it works and has examples.
I am new to angularjs and I need to develop single page application. I have a requirement of implementation of angular validation.
I have 10 fields(input, select and listbox) on page and 2 buttons(Save and Submit). When i click on save then 6 fields(just for example) should have valid values and when i click on Submit button- 8 fields should be checked.
But problem is. if i used form tag on page then how to implement these validation.
Your help will be appreciated.
write this code in a controller
obj.hasErrorWithsubmit = function (form, field, validation) {
if (validation) {
return ($scope.submitted && form[field].$error[validation]);
}
return (form[field].$dirty && form[field].$invalid) || ($scope.submitted && form[field].$invalid);
};
obj.hasErrorWithsave = function (form, field, validation) {
if (validation) {
return ($scope.save && form[field].$error[validation]);
}
return (form[field].$dirty && form[field].$invalid) || ($scope.submitted ``&& form[field].$invalid); };
Write this in html
ng-class="{'classname':obj.hasErrorWithsubmit(Formname,'fieldname','validationType')}">
i.e
ng-class="{'classname':obj.hasErrorWithsubmit(myform,'textfield1','required')}">
ng-class="{'classname':obj.hasErrorWithsave (myform,'textfield2','required')}">
classname is a css class which makes the border red
By clicking on submit button make $scope.submitted =true
By clicking on save button make $scope.save =true
I have a kendotreeview with 3 root parent nodes.Example is shown below.
When I drop child3 into New SubGroup, the node "New Subgroup" gets expanded by default even if it was collapsed before. I would like to prevent this possibility. If New SubGroup was expanded before, then I would like to keep it as it is. The problem is that the expand gets called before the databound event and hence I am stuck here.
Please help.
parent1:
--New SubGroup
--Child2
--Child3
--Child4
parent2:
--Child4
--Child5
Code snippet:
dataBound: function (e) {
console.log("DataBound", e.node);
var nodedataitem = $("#DimMeasTree").data("kendoTreeView").dataItem($(e.node));
if (nodedataitem.FieldKey === "SubGroup" && ($(e.node).attr("aria-expanded")) === "true") {
$("#DimMeasTree").data("kendoTreeView").collapse($(e.node));
}
}
I am subscribing to my own custom expand function (i.e. subgroup_expand()) after I initialize my treeview. It is as demonstrated below:
<div id="treeview"></div>
<script>
function subgroup_expand(e) {
if (typeof event === 'undefined') {
//If browser is Firefox, the subgroup will expand and do not close automatically.
// It is because Firefox does not support "event" attribute gloabally as in IE or in Google chrome.
}
else if (!!e.node && typeof(event) !== 'undefined' && event.type !== "click" && event.type !== "dblclick") {
var nodedataitem = $("#treeview").data("kendoTreeView").dataItem($(e.node));
if (nodedataitem.FieldKey === "SubGroup") {
// e.preventDefault();
setTimeout(function () {
//Collapse the subgroup if it was not expanded by click or dblclick.
$("#treeview").data("kendoTreeView").collapse($(e.node));
}, 50);
}
}
}
$("#treeview").kendoTreeView({
dataSource: modeldata
});
var treeview = $("#treeview").data("kendoTreeView");
treeview.bind("expand", subgroup_expand);
</script>