Why is my (selectionChange) continuously executing? - html

I have created an angular project using a custom multi-select-autocomplete that has
#Output() selectionChange: EventEmitter<any[]> = new EventEmitter<any[]>();
As well as the following method
onSelectionChange(val : any) {
const filteredValues = this.getFilteredOptionsValues();
let count = 0;
if (this.multiple) {
this.selectedValue?.filter(item => {
if (filteredValues.includes(item)) {
count++;
}
});
this.selectAllChecked = count === this.filteredOptions.length;
}
this.selectedValue = val.value;
this.selectionChange.emit(this.selectedValue);
}
I use it in my other components as such
<div style="width: 100%; margin-right: 26px; margin-bottom: 15px;">
<multi-select-autocomplete class="input-medium"
[placeholder]="'Search and Select Brands'"
[options]="companies"
[display]="'name'"
[value]="'id'"
[labelCount]="10"
[label]="'Brands'"
(selectionChange)="selectBrand($event)">
</multi-select-autocomplete>
</div>
</div>
But I come across an issue where the (selectionChange) method is continuously firing even before I make a selection.
Any ideas where I may have gone wrong?

Related

HTML doesn't update properly on child element after sorting an array of items on parent component when using updated()

I am sorting an array of so called 'activities' in my customElement using LitElement:
#property({ type: Array }) private activityListLocal: Array<Activity> = [];
in the parent customElement called "find-activity".
Each activity is being rendered here.
${repeat(
this.activityListLocal,
activity =>
html` <div class="activity-container">
<div class="activity">
<activity-info .activity=${activity}></activity-info>
</div>
<div class="activity" class="rating">
<activity-rating
.activity=${activity}
#appactivityremoveclick=${() => this.deleteActivity(activity)}
></activity-rating>
</div>
</div>`
)}
This is how it looks visually:
2 activities marked for some impression
On clicking the button "Highest Rating", I am sorting the list of activities:
sortActivityListLocal() {
this.activityListLocal = [...this.activityList];
this.activityListLocal = this.activityListLocal.sort((a, b) => (a.avgRating < b.avgRating ? 1 : -1));
}
if (category === 'all') {
this.activityListLocal = this.activityList;
} else if (category === 'Highest Rating') {
this.sortActivityListLocal();
if (this.activityListLocal.length === 0) {
this.nothingHere.style.display = 'block';
}
}
//....
}
Note: this.activityList is a local copy of the server response.
In the image, you see the two sliders, which should be updated to move with the activity if the position on the page changes. The issue: The "my rating" slider does not properly "move" with the activity, if it has been changed/dragged after the page has been loaded for the first time.
Before:
Activities are loaded in properly, cinema has a higher rating than Tennis
After:
Activities are sorted properly, all sliders are correctly "moved" if "myRating" has not been changed/dragged
But if the slider was dragged after inital load in, and then selecting the "highest rating" category and therefore sorting the array, it stays in place:
Before:
After loading
Dragging the slider (not even requesting an update with a click on the refresh icon, issue happening in both cases)
Modification leading to the issue
After:
Issue visible
The interesting thing, the slider has the correct! value in the html inspector, but the display is not showing it. Why is this happening?
Code of the component holding the sliders:
import { LitElement, html } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { httpClient } from '../../../http-client';
import { PageMixin } from '../../page.mixin';
import { Activity, Rating } from '../find-activity';
import componentStyle from './activity-rating.css';
#customElement('activity-rating')
// eslint-disable-next-line #typescript-eslint/no-unused-vars
class ActivityRatingComponent extends PageMixin(LitElement) {
static styles = componentStyle;
#property({ reflect: true }) activity = {} as Activity;
#property() rating = {} as Rating;
#query('#deleteButton') private deleteButton!: HTMLImageElement;
private currentSliderValue = -1;
async updated() {
console.log(
`Personal rating for ${this.activity.title} is ${this.activity.personalRating}, avgRating ${this.activity.avgRating}, currentSliderValue ${this.currentSliderValue}`
);
this.currentSliderValue = this.activity.personalRating ? this.activity.personalRating : 0;
console.log(`Current slider value after: ${this.currentSliderValue}`);
if (this.activity.deletepermission === false) this.deleteButton.style.display = 'none';
else this.deleteButton.style.display = 'inline';
}
render() {
return html`
${this.renderNotification()}
<div class="outer-rating">
<p>${this.activity.motivationtitle}</p>
<div class="slidecontainer">
<label for="overallRating">Overall Rating</label>
<input
type="range"
min="0"
max="100"
value=${this.activity.avgRating ? this.activity.avgRating : 0}
class="slider"
id="overallRating"
disabled
/>
</div>
<div class="slidecontainer">
<label for="myRating">My Rating</label>
<input
type="range"
min="0"
max="100"
value=${this.activity.personalRating ? this.activity.personalRating : '0'}
class="slider"
id="myRating"
#change="${(e: Event) => this.readcurrentSliderValue(e)}"
/>
<img id="personalSlider" src="/refresh.png" alt="update" #click=${this.savecurrentSliderValueToDb} />
<img
class="remove-task"
src="/deleteicon.png"
alt="update"
id="deleteButton"
#click="${this.confirmDelete}"
/>
</div>
</div>
`;
}
confirmDelete(e: Event) {
const target = e.target as HTMLInputElement;
if (target) {
const result = confirm('Want to delete?');
if (result) {
this.emit('appactivityremoveclick');
}
}
}
readcurrentSliderValue(e: Event) {
const target = e.target as HTMLInputElement;
if (e) {
this.currentSliderValue = Number(target?.value);
console.log('Read new slider value ' + Number(target?.value));
}
}
async savecurrentSliderValueToDb() {
const partialRating: Partial<Rating> = {
activityid: this.activity.id,
rating: Number(this.currentSliderValue) //userID is not included here as it is being provided by the auth Middleware on patch request.
};
await httpClient.patch(`rating/${this.activity.id}${location.search}`, partialRating);
const responseRatingAll = await httpClient.get(`rating/findAverageRating/${this.activity.id}` + location.search);
try {
this.activity.avgRating = (await responseRatingAll.json()).results;
this.activity.personalRating = partialRating.rating ? partialRating.rating : 0;
} catch (error) {
this.showNotification((error as Error).message, 'error');
}
this.requestUpdate();
}
emit(eventType: string, eventData = {}) {
const event = new CustomEvent(eventType, {
detail: eventData,
bubbles: true,
composed: true
});
this.dispatchEvent(event);
}
}
Visual confirmation that slider has the correct value, but doesn't show it.
Thank you :)
Edit: In addition to the answer below - specifically for the case where "you want to force a value to be set on an element". Lit has an optimization where "if a value hasn't changed, don't do anything". Rendering the same value to an expression will not cause the expression to update. To make sure Lit updates the expression if the underlying DOM value has changed use the live directive.
The native browser input elements default behavior is:
When the value attribute is changed, update the input elements value property.
After a manual user interaction (such as typing into the input element if it is a text input), the value attribute no longer updates the input property.
After the value property has been updated the attribute no longer causes the property to update.
Therefore by setting the value property the value updates.
Because of that browser behavior, in Lit you can use a property expression to set the value property.
I.e.: <input .value=${this.activity.avgRating ? this.activity.avgRating : 0}.
Below is an example of the browser input behavior. Click the two buttons. One will update the value attribute, the other the value property.
Then interact with the input. Type in it. Now the attribute button will stop working.
const inputEl = document.querySelector('input')
const getRandomValue = () => String(Math.random()).slice(0, 5)
document.querySelector("#btn-attr")
.addEventListener("click", () => {
inputEl.setAttribute('value', getRandomValue())
});
document.querySelector("#btn-prop")
.addEventListener("click", () => {
inputEl.value = getRandomValue()
});
<input value="12345">
<button id="btn-attr">Change input attribute</button>
<button id="btn-prop">Change input property</button>

Custom validation message on 'type=url'

I am using type=url in my form to only allow a url to be passed through.
However, using the standard validation message is as follows
Is there a way to change this? Here is the design what I am trying to achive (outline search bar, add message beneath, and make text in search box orange)
Here is function after my form
function searchIt() {
let form = document.querySelector('form')
console.log(form)
form.addEventListener('submit', async(e) => {
// onclick or the event that start the call
interval = setInterval(() => {
progress = progress >= 100 ? 100 : progress + 1
document.getElementById('myprogress').style.width = `${progress}%`
// end interval and wait at 100%
if(progress == 100) clearInterval(interval);
}, maxTime/100)
document.getElementById('loadingcontainer').style.display = ""
e.preventDefault()
let urlIN = form.url.value
let url = encodeURIComponent(urlIN)
console.log(url)
try {
const data = await fetch('/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: url
})
}).then(res => {
document.open()
res.text().then(function(text) {
document.write(text)
// Hide the progressbar, stop the timer and reset progress
clearInterval(interval);
progress = 0;
document.getElementById('myprogress').style.width = "0%"
document.getElementById('loadingcontainer').style.display = "none";
});
})
} catch (err) {
console.error(err)
}
})
}
Rather than write all the necessary validation for URLs, you can use the HTML5 input type "url" in place of type "text", which has all that validation built in:
<input type="url" name="location">
That will require a properly formed URL, but ftp:// is also proper.
You can further constrain it by using the
pattern attribute for your requirements "beginning in http://, https://, includes www.".
Here, using novalidate on the <form_> prevents the browser from showing it's own messages, then you can test a field's validity with field.checkValidity()
const urlField = document.getElementById('onlyweb');
const messagebox = document.getElementById('errormessage');
document.getElementById('testform')
.addEventListener('submit', function(e) {
e.preventDefault();
const valid = urlField.checkValidity();
console.log('valid =', valid);
if (valid) {
messagebox.innerHTML = ''; // clear any message that might be there
messagebox.classList.add('hidden');
}
else {
messagebox.innerHTML = '<span class="errmsg">You need to include http:// or https:// and include the "www." prefix.</span>';
messagebox.classList.remove('hidden');
}
});
input:invalid {
border-color: red;
}
div {
margin-bottom: 1em;
}
#errormessage {
font-family: sans-serif;
color: red;
border: 1px solid gray;
padding: 1em;
}
#errormessage.hidden {
display: none;
}
<form id="testform" action="#" novalidate>
<div>
Invalid fields will have a red border
</div>
<div>
<label for="onlyweb">Only Web URLs</label>
<input type="url"
name="onlyweb" id="onlyweb"
pattern="^https?://www\..*$"
>
</div>
<div>
<input type="submit" value="Test It">
</div>
<div id="errormessage" class="hidden">
</div>
</form>
I recommend just manipulating classes and writing CSS using the :valid and :invalid pseudo-selectors, rather than changing a bunch of field.style.something=newvalue which have to be coordinated to make sure you do & undo things consistently.
It also mixes style into your code, instead of keeping it in CSS where it belongs.
See MDN's Client-side form validation
for The Constraint Validation API
especially where it gives "A more detailed example"
Yes, it is perfectly possible. However, you must create the validation yourself by attaching a onsubmit function to the form like so:
var input = document.getElementsByTagName('input')[0];
var expression = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi;
var regex = new RegExp(expression);
var errorMsg = document.getElementById("errorMessage");
function val(){
var value = input.value;
if(regex.test(value)){
input.style.borderColor="green";
input.style.color="green";
return true;
}else{
input.style.borderColor="red";
input.style.color="red";
errorMsg.style.display="block";
return false;
}
}
#errorMessage{
color:red;
display:none;
}
.input{
width: 80%;
margin: auto;
}
input{
width: 80%;
margin: auto;
padding: 2em;
border-radius: 25px;
outline: none;
border: 1px solid black;
}
<form action="stackoverflow.com" onsubmit="return val()">
<div class="input">
<input>
<p id="errorMessage">Yikes! That's not a valid URL.</p>
</div>
</form>
You can use the input type url and then check the validity using .checkValidity().From Mozilla on the checkValidity method: checks whether the element has any constraints and whether it satisfies them. If the element fails its constraints, the browser fires a cancelable invalid event at the element, and then returns false.
It's also important to note that when one says URL validation, what exactly do they refer to when referencing a url? One could write in a url address bar, www.stackoverflow.com and they would end up at stackoverflows web site, however, the URL validation will require that the valid protocol http, https is called in the url address in order to validate using an input url type. More info on url can be found here... What is a URL? -Mozilla
const input = document.getElementById("url");
const errorMessage = document.getElementById("errorMessage");
const validate = document.getElementById("validate");
validate.addEventListener('click', (e) => {
e.preventDefault();
if (input.checkValidity() !== true) {
input.className = 'false';
input.classList.remove('true');
errorMessage.style.display = 'block';
} else {
input.className = 'true';
input.classList.remove('false');
errorMessage.style.display = 'none';
}
})
#errorMessage {
display: none;
color: orange;
}
.true {
border: 1px solid green;
color: green;
}
.false {
border: 1px solid orange;
color: orange;
}
input#url {
border-radius: .9rem;
padding: .7rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form novalidate>
<input id="url" type="url" required />
<input type="submit" name="validate" value="Check" id="validate">
</form>
<p id="errorMessage">Yikes! That's not a valid URL.</p>

setInterval won't change image on Safari, but on Chrome

I have a mock array that distributes data. One component uses this data to display a list of cases. Each case has allocated images. When one case is being hovered, only then these images are being displayed, but only one at a time - every interval of 300ms the images changes.
My code works, but I have trouble with Safari - the image won't change. Somehow Safari can't handle it. Increasing the interval from 300m to 3000ms made it work, but that's not the way I want it to work.
Looking at the code in safari proves that the image actually switches every 300ms, since the img source changes - but the change won't be displayed.
BTW I tried it with Chrome and it worked fine.
export class CaseListComponent implements OnInit {
counter = 1;
cases;
interval;
image: string;
array = [];
mouseEnter(url: string, url2: string, url3: string, name: string) {
clearInterval(this.interval);
this.array = [url, url2, url3];
this.image = this.array[0];
this.changeImage();
}
changeImage() {
this.interval = setInterval(() => {
this.image = this.array[this.counter];
this.counter = this.counter === 2 ? 0 : this.counter + 1;
}, 300);
}
mouseLeave() {
clearInterval(this.interval);
this.image = null;
this.highlightedItem = null;
}
constructor(private casesService: CasesService) {}
ngOnInit() {
this.cases = this.casesService.data;
}
}
<div class="container-fluid d-flex justify-content-center">
<div class="row">
<div class="col-12 text-center" *ngFor="let case of cases" [class.z-index]="highlightedItem === case.name">
<p class="d-inline-block" routerLink="/cases/{{ case.id }}" (mouseenter)="mouseEnter(case.image, case.image2, case.image3, case.name)" (mouseleave)="mouseLeave()"
[style.color]="highlightedItem !== case.name && highlightedItem !== null ? '#f1f1f1' : '#33393D'">{{ case.name }}</p>
</div>
</div>
<img *ngIf="!!image" [src]="image" alt="image" class="position-fixed align-self-center">
</div>
Following my comment, you should use observables.
They are heavily used in Angular and you can rely on it for your change detection.
Here is a simple example of the observables in action, without Angular. Simply adapt this to your code (and ask if you have any issue) to make Angular handle the picture changes.
Sandbox
import { fromEvent, Subscription, timer } from "rxjs";
import {} from "rxjs/operators";
const container = document.querySelector(".container");
let subscription: Subscription;
let count = 0;
const cycler = timer(0, 500);
fromEvent(container, "mouseenter").subscribe(() => {
subscription = cycler.subscribe(() => {
count++;
container.textContent = count;
});
});
fromEvent(container, "mouseleave").subscribe(() => {
subscription && subscription.unsubscribe();
container.textContent = "";
count = 0;
});

Why are my React rendered HTML elements changing positions after refresh?

I have a react component that I am using as checkpoint to check if the user has viewed a certain section of the site.
class ContentSection extends Component {
constructor(props) {
super(props);
}
render() {
const allParagraphs = [];
for (let i = 0; i < this.props.paragraphs.length; i++) {
let p = this.props.paragraphs[i];
allParagraphs.push(
<Paragraph key={i} image={p["img"]} text={p["text"]} />
);
}
return (
<div className="cs">
<ContentSectionCheckPointContainer
uniqueIndex={this.props.uniqueIndex}
/>
<h4 className="sectionTitle">THIS IS A SECTION!!!</h4>
{allParagraphs}
</div>
);
}
}
And this is the ContentSectionCheckPointContainer
const mapDispatchToProps = dispatch => {
return {
unlock: index => dispatch(Unlock_Index_Action(index))
};
};
const mapStateToProps = state => {
return {
CheckPoints: [...state.CheckPoints]
};
};
class ContentSectionCheckPoint extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.myRect = null;
this.checkVisibility = this.checkVisibility.bind(this);
}
componentDidMount() {
this.checkVisibility();
window.addEventListener('scroll', this.checkVisibility);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.checkVisibility);
}
checkVisibility() {
if (this.myRef.current) {
let rect = this.myRef.current.getBoundingClientRect();
var viewHeight = Math.max(
document.documentElement.clientHeight,
window.innerHeight
);
let b = !(rect.bottom < 0 || rect.top - viewHeight >= 0);
if (b !== this.props.CheckPoints[this.props.uniqueIndex]) {
if (b) {
this.props.unlock(this.props.uniqueIndex);
}else{
this.props.unlock(this.props.uniqueIndex);
}
}
}
}
render() {
this.checkVisibility();
return (
<div ref={this.myRef} className="cscp">
{this.props.CheckPoints[this.props.uniqueIndex] && <p>hi</p>}
</div>
);
}
}
const ContentSectionCheckPointContainer = connect(
mapStateToProps, mapDispatchToProps)(ContentSectionCheckPoint);
As you can see I ran a visibility check on scroll, which works fine. However, I wanted to also run the visibility check immediately when the page is loaded before any scrolling occur.
It is to my understanding that componentDidMount is when React already rendered an element for the first time, so I wanted to do the check then. However, I was trying to render two ContentSection components, each containing their own check point. The latter check point for unknown reason is positioned higher than it appears on screen during componentDidMount, resulting in my visibility check returning true even though it is not visible. If I refresh the page, its position is correct again and the visibility check is fine.
This problem only seem to occur during the first time when I open up a new tab to load my code, but no longer occurs after a refresh on that tab.
Any idea why?

Scope value not updating in view in IONIC 1 and Angular js

I have a requirement like saving two previous login details.
I am done with it. But my view will update only on refresh.But scope values are updated.
Tried with scope.apply,digest,timeout. But nothing seems to work here.
$scope.loginUserName=localStorage.getItem("loginUserName");
$scope.userName=localStorage.getItem("userName");
$scope.mobileNumber=localStorage.getItem("mobileNumber");
$scope.loginData = {};
$scope.userLogin = function(loginData) {
userService.userLogin(loginData).then(function(success) {
var res=success.message;
if(res==='success'){
if(localStorage.getItem("userName1")==null || localStorage.getItem("userName1") == success.firstName){
localStorage.setItem("userName1",success.firstName);
localStorage.setItem("loginUserName",success.firstName);
}else if(localStorage.getItem("userName2")==null || localStorage.getItem("userName2") == success.firstName ){
localStorage.setItem("userName2",success.firstName);
localStorage.setItem("loginUserName",success.firstName);
}
localStorage.setItem("userName",success.firstName);
$scope.userName=success.firstName;
$scope.mobileNumber = success.mobileNumber;
$scope.loginData = {};
$state.go('app.home');
}else{
$scope.message ='Wrong pin.Try again or click Forgot password to reset it.';
}
},function(error){
});
};
$scope.loginPerson = function(mobileNumber,userName){
localStorage.setItem("loginUserName",userName);
// here userName is updating,but not reflecting in view
$scope.loginUserName=localStorage.getItem("loginUserName");
//setTimeout(function(){ $scope.$apply(); });
console.log("In loginPerson:"+userName);
$state.go('app.start');
}
start.html
<span ng-if="loginUserName !=null">
<p class="startP">Enter pin for {{loginUserName}}
<i class="icon ion-chevron-down" ui-sref="app.loginOptions">
</i></p>
</span>
State
//Here is the state details,I have same controller for two state.
.state('app.loginOptions', {
url: '/loginOptions',
views: {
'menuContent': {
templateUrl: 'templates/loginOptions.html',
controller:'LoginCtrl'
}
}
})
.state('app.start',{
url:'/start',
views:{
'menuContent':{
templateUrl:'templates/start.html',
controller:'LoginCtrl'
}
}
EDIT
I have used within object also,But nothing is changed.
step 1) please use angular copy while get data from localstorage to $scope $scope.xyz=angular.copy(localstorage.get('key'))
after implement step 1 then not work use $scope.$apply(); after set value in $scope.
try to use loginUserName as a property of an object instead a property of scope directly. Sometimes angularjs fail to update view for these values.
Like
$scope.data={
loginUserName:""
};
Then inside your function
$scope.userLogin = function(loginData) {
...
$scope.data.loginUserName=localStorage.getItem("loginUserNa‌​me");
// To check it
console.log($scope.data);
}
html
<span ng-if="data.loginUserName !=null">
...
</span>
Update
Change the loginPerson function like below.
$scope.loginPerson = function(mobileNumber,userName){
localStorage.setItem("loginUserName",userName);
$scope.data.loginUserName=localStorage.getItem("loginUserName");
console.log("In loginPerson:"+userName);
console.log($scope.data);
}