Im quite new to the vue reactive data workflow, and I'm attempting to make a image uploader, and its mainly working. Im trying to remove all true primary values from the images in the array, before updating a specific one. But I need to make a check on the "primary" values in the image array so i can make the conditional change.
I have left the image creation function at the bottom so you can see how this is made. the parent component gets all the data from server via ajax if being used to edit images this is the _images prop.
For some reason when i reference the array as images[key].primary, I get a different value than if I output the whole array in the console. Any ideas whats happening here?
Goal
Currently on the child component im clicking a make primary button and emitting the update to this component. Then in the update Method attempting to check if request is a new primary image request and make primary false on all other images. Then setting the requested image as primary. So a primary on toggle so there is only ever one primary image.
currently Im testing two images, in turn setting each one as primary true, and watching the other automatically turn false, but by the third click they are both true and nothing happens. Any ideas whats happening here?
Parent Component (v-image-upload):
export default {
components: {
'v-image': image
},
props: ['_errors', '_images', '_isMultiple'],
data() {
return {
images: _.cloneDeep(this._images),
}
},
methods: {
updateImage(request) {
//ISSUE: current images = true,true | images = false,true
console.log('current images', this.images[0].primary, this.images[1].primary, );
console.log('images', this.images);
if (request.form.primary && !this.images[request.key].primary) {
//reset all primary images
$.each(this.images, (index, value) => {
let image = value;
image.primary = false;
this.$set(this.images, index, image);
}
//update images with new image
this.$set(this.images, request.key, request.form);
},
onFileChange(e) {
let files = e.target.files || e.dataTransfer.files;
if (files.length) {
$.each(files, (index, file) => {
this.createImage(file);
});
}
e.target.value = '';
},
createImage(file) {
let reader = new FileReader();
let fileNameSegments = file.name.split('.');
let fileName = fileNameSegments[0];
let fileExtension = fileNameSegments[1];
let validExtnesions = ['jpg', 'png', 'gif'];
let placeholderImage = window.location.protocol + '//' + window.location.host + '/' + 'img/site/default/placeholder-image.png';
let primary = (this.images.length == 0) ? true : false;
reader.onload = (e) => {
let image = ($.inArray(fileExtension, validExtnesions) == -1) ? placeholderImage : e.target.result;
let data = {
name: fileName,
extension: fileExtension,
source: image,
description: null,
primary: primary,
image_directory: null,
};
this.$set(this.images, this.images.length, data);
}
reader.readAsDataURL(file);
},
}
}
Child Compononet (v-image):
export default {
props: ['_imgKey', '_image', '_errors'],
data() {
return {
form: {
id: _.clone(this._image.id),
name: _.clone(this._image.name),
description: _.clone(this._image.description),
primary: _.clone(this._image.primary),
extension: _.clone(this._image.extension),
image_directory: _.clone(this._image.image_directory),
source: _.clone(this._image.source),
},
}
},
watch: {
_image: {
handler(value) {
console.log('_image watch', this._imgKey, value.primary);
this.form.id = _.clone(value.id);
this.form.name = _.clone(value.name);
this.form.primary = _.clone(value.primary);
this.form.source = _.clone(value.source);
this.form.image_directory = _.clone(value.image_directory);
}, deep: true
}
},
methods: {
updatePrimary() {
this.form.primary = true;
this.updateImage();
},
updateImage() {
this.$emit('updateImage', {key: this._imgKey, form: this.form});
},
removeImage() {
this.$emit('removeImage', this._imgKey);
}
}
}
Update
I have been watching the vue developer tools for any changes to the image array. I push the primary button on the false image in the component this emits to the parent and calls the updateImage method. The first and second reset and update works, and changes the values. The issue has been tracked to: The 2nd 'after update' (true,false) does not match the third 'before reset'(true,true).
I made the previous changes mensioned in the comments and added a clone deep to both components data to prevent odd reactivity behaviour.
I checked the event log: 3x UpdateImage $emit by <VImage> which was expected.
Note: I moved the previous reset logic into the resetPrimary() method.
updateImage(request) {
console.log('update', request.key, request.form.primary);
console.log('b4 reset', this.images[0].primary, this.images[1].primary);
this.resetPrimary(request);
console.log('after reset', this.images[0].primary, this.images[1].primary);
this.$set(this.images, request.key, request.form);
console.log('after update', this.images[0].primary, this.images[1].primary);
},
output:
//First click - Image 1 (Expected)
update 1 true
b4 reset true false
after reset false false
after update false true
//Second click - Image 0 (Expected)
update 0 true
b4 reset false true
after reset false false
after update true false
//Third click - Image 1 (What?)
update 1 true
b4 reset true true //should be true false
after reset true true
after update true true
It appears that when attempting to update the same image a second time it somehow ignores the fact it has previously been reset to false.
Related
I'm trying to load additional data on my select (select2), but the Trigger Event is not loading the additional data. The code and results are shown below.
$('#section').select2();
var data = {
id: 1,
text: 'text_default',
anothertext: 'text_2'
};
var newOption = new Option(data.text, data.id, true, true);
$('#section').append(newOption).trigger('change'); //until this point, everything works
$('#section').trigger({
type: 'select2:select',
params: {
data: data
}
});
console.log($('#section').select2('data')[0]['text']);// return text_default
console.log($('#section').select2('data')[0]['anothertext']);// return undefined
console.log($('#section').select2('data')['anothertext']);//only for test, return undefined
console.log($('#section').data('anothertext'));//only for test, return undefined
Is anything wrong with the code?
I have an Array of statuses objects. Every status has a name, and a boolean set at false by default.
It represent checkbox in a form with filters, when a checkbox is checked bool is set at true :
const filters.statuses = [
{
name: "pending",
value: false
},
{
name: "done",
value: false
},
];
I am using Angular HTTP Params to pass params at the URL.
filters.statuses.forEach((status) => {
if (status.value) {
this.urlParams = this.urlParams.append('statuses[]', status.name);
}
});
Url params looks like when a status is checked :
&statuses%5B%5D=pending
My problem is when I want to unchecked.
I know HTTP Params is Immutable, so, I'm trying to delete the param when checkbox is unchecked, so set to false :
...else {
this.urlParams = this.urlParams.delete('statuses');
}
But, it not works, URL doesn't change.
And if I re-check to true after that, the URL looks like :
&statuses%5B%5D=pending&statuses%5B%5D=pending
How can I delete params, if the status value is false, and keep others statuses in URL ?
Project on Angular 10.
Thanks for the help.
UPDATE : It works to delete, my param name was not good :
else {
this.urlParams = this.urlParams.delete('statuses[]', status.name);
}
But, my other problem, it's when I check 2 or more checkbox, the append function write on URL : &statuses%5B%5D=pending&statuses%5B%5D=pending&statuses%5B%5D=done
I have prepared an example to try to answer your question (If I understand this right way).
You can change the checkboxes state or the URL to play with it. Also, I added helper buttons, which will navigate you to different cases (by changing the URL).
Here is the example: https://stackblitz.com/edit/angular-router-basic-example-cffkwu?file=app/views/home/home.component.ts
There are some parts. We will talk about HomeComponent.
You have ngFor which displays statuses, I handled state using ngModel (you can choose whatever you want).
You have a subscription to the activatedRoute.queryParams observable, this is how you get params and set up checkboxes (the model of the checkboxes)
You have the ngModelChange handler, this is how you change the route according to the checkboxes state
Let's focus on 2 & 3 items.
The second one. Rendering the correct state according to the route query params. Here is the code:
ngOnInit() {
this.sub = this.activatedRoute.queryParams.subscribe((params) => {
const statusesFromParams = params?.statuses || [];
this.statuses = this.statuses.map((status) => {
if (statusesFromParams.includes(status.name)) {
return {
name: status.name,
active: true,
};
}
return {
name: status.name,
active: false,
};
});
});
}
Here I parse the statuses queryParam and I set up the statuses model. I decide which is active and which is not here.
The third one. You need to update the URL according to the checkboxes state. Here is the code:
// HTML
<ng-container *ngFor="let status of statuses">
{{ status.name}} <input type="checkbox" [(ngModel)]="status.active" (ngModelChange)="onInputChange()" /> <br />
</ng-container>
// TypeScript
onInputChange() {
this.router.navigate(['./'], {
relativeTo: this.activatedRoute,
queryParams: {
statuses: this.statuses
.filter((status) => status.active)
.map((status) => status.name),
},
});
}
Here you have the ngModelChange handler. When any checkbox is checked/unchecked this handler is invoked. In the handler, I use the navigate method of the Router to change the URL. I collect actual checkboxes state and build the query parameters for the navigation event.
So, now you have a binding of the checkboxes state to the URL and vice versa. Hope this helps.
I am working on a IT ticketing system where every time a new comment or a note has been added to a ticket a notification needs to be send to the user who is following the ticket. Below code only inserts new ticket in list of tickets followed by the user if it is not already present, however if it is present it ignores it. What I need to do is if the ticket that has just been updated is already present, change clicked field to false. In my application's frontend, when user clicks the notification icon it will change clicked to TRUE but when a new comment is added clicked needs to be changed to FALSE so that the user gets notification that comment has been added to the same ticket. How do I go about achieving it?
const ReqNotificationSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: "User" },
notifications: [
{
request: { type: Schema.Types.ObjectId, ref: "Request" },
clicked: { type: Boolean, default: false },
},
],
});
if(updated){
await ReqNotificationModel.findOneAndUpdate(
{
user: follower.user,
"notifications.request": { $ne: updated._id },
},
{
$push: { notifications: { request: updated._id, clicked: false } },
},
{ new: true }
);
}
I wasn't able to do it in one step , so I tried in a 2 step approach.
const notification = await ReqNotificationModel.findOne({
user: follower.user,
});
let index = notification.notifications
.map((obj) => obj.request.toString())
.indexOf(updated._id.toString());
notification.notifications[index].clicked = false;
await notification.save();
});
I have this extremely odd issue.. every single time I try and use a chartjs object, it keeps on creating itself on a new object (new id). The initial load never works, and I have to refresh. Everytime I switch from unmounting this component to mounting, when I console.log the chart instance it's always incrementing in id. I'm confused as what's going wrong here.
here is a basic snipped of my code:
useEffect(() => {
if (chartContainer && chartContainer.current) {
const newChartInstance = new Chartjs(chartContainer.current!, chartConfig);
removeData(newChartInstance);
if (newChartInstance) {
setIsClear(true);
setChartInstance(newChartInstance);
return () => {
removeData(newChartInstance);
newChartInstance.destroy();
if (chartInstance) chartInstance.destroy();
}
}
};
}, [chartContainer]);
useEffect(() => {
if (chartInstance && delay) {
const intervalUpdate = setInterval(() => {
console.log("updating");
console.log(chartInstance);
chartInstance.update();
}, delay);
return () => clearInterval(intervalUpdate);
}
}, [chartInstance, delay])
return (
<div className={styles.chartContainer}>
<canvas ref={chartContainer} height={"140"}/>
</div>
);
On the initial load a chart is created, and I'm adding data to it. But I believe 2nd one gets created and that's holding no data & is what is being display on the canvas. I know there's two somehow because because when I log I get an id of 1, instead of 0.
When I console.log() chart instance, on the very first initial load is always:
Chart {id: 1, ctx: CanvasRenderingContext2D, canvas: canvas.chartjs-render-monitor, config: {…}, width: 1024, …}
Which I believe is why my chart is empty. When I refresh it goes back to:
Chart {id: 0, ctx: CanvasRenderingContext2D, canvas: canvas.chartjs-render-monitor, config: {…}, width: 1024, …}
which properly displays the data.
I'm wondering about the most efficient, safe and smart way to code a complex form in javascript.
Often a user form can be very complex with a lot of different states, complex checks and so on, and to do a good job a good concept is absolutely necessary.
I really believe that the best solution is a state machine.
The following code is the form core logic I did for user registration with an RFID tag key, where user can register themselves using different credentials as phone, key RFID tag, mail, and complete their registration at a later stage with the same forum.
Consider just the logic behind it at high level.
The main loop which iterates on possible transitions (in order of priority):
/* Evaluate the actual form state (status) and check if it's possible to change
* to another status with a greater priority.
* If the state transition conditions are verified the transition callback is executed
* which, in case the state doesn't, is an empty function.
*/
setNextFormStatus: function(field) {
var sts,
that = this;
function conditionsAreValid(sts) {
var context = that.statusToConditionsMap[sts];
return ( sts == 'new_account' || that[context.field].state == context.state );
}
for (sts in this.statusToConditionsMap[this.status.actual].changes) {
var transition = this.statusToConditionsMap[this.status.actual].changes[sts];
if (transition && conditionsAreValid(sts)) {
if (sts != this.status.actual) {
this.status.previous = this.status.actual;
this.status.actual = sts;
this._resetForm(); // simple reset function
transition.apply(this);
}
break;
}
}
}
All status, their transition conditions, and their transition callbacks are defined in a dictionary when status are listed in order of priority:
/*
* This is the dictionary which defines form status states machine
*
* For each status are defined the following attributes:
* · state & field: define the condition to enter this status. The field must have that state (i.e. field.state == state)
* · changes (optional): the list of possible next status from this one, ordered by priority. Each status has an handle to call
*/
this.statusToConditionsMap = {
'registered_key':{
state: 'registered',
field: 'key'
},
'processed_key_with_username':{
state: 'unlinked',
field: 'key',
changes: {
'processed_key_with_username': function() {}, // empty cause callback is unnecessary
'new_account': this._checkNotEmptyFields
}
},
'phone_already_present_confirmed':{
state: 'already_present_confirmed',
field: 'phone',
changes: {
'phone_already_present_confirmed': function() {},
'phone_already_present_unconfirmed': this.phone_already_present_unconf_data_filler,
'new_account': this._checkNotEmptyFields
}
},
'phone_already_present_unconfirmed':{
state: 'already_present_unconfirmed',
field: 'phone',
changes: {
'phone_already_present_confirmed': this.phone_already_present_data_filler,
'phone_already_present_unconfirmed': function() {},
'new_account': this._checkNotEmptyFields
}
},
'email_already_present':{
state: 'email_already_present',
field: 'email',
changes: {
'phone_already_present_confirmed': this.phone_already_present_data_filler,
'phone_already_present_unconfirmed': this.phone_already_present_unconf_data_filler,
'email_already_present': function() {},
'new_account': this._checkNotEmptyFields
}
},
'new_account':{
state:'',
field: '',
changes: {
'registered_key': this.registered_key_data_filler,
'processed_key_with_username': this.processed_desikey_data_filler,
'phone_already_present_confirmed': this.phone_already_present_data_filler,
'phone_already_present_unconfirmed': this.phone_already_present_unconf_data_filler,
'email_already_present': function() {this.showMailCheckbox(); this.runCheck('phone');},
'new_account': function() {}
}
}
}
Which can be a best practice to implement a complex form?
Any other solution or method will be appreciated.