Vue Watchers for multiple button clicks - javascript

I have multiple filters that are set to false and if clicked turn to true and must implement the specific filter. How can I use a watchers to look for the changing of my filter from false to true. Currently as I have all my functions in one watcher if one filter is clicked all filters are implemented. Whereas I need it to be only the specific filter that is related to the filter button.
<sankey-filter
label="string1"
v-model="filters.string1"
/>
<sankey-filter
label="string2"
v-model="filters.string2"
/>
<sankey-filter
label="string3"
v-model="filters.string3"
/>
data() {
return {
filters: {
statusString1: false,
statusString2: false,
statusString3: false,
}
watch: {
filters: {
handler: function(){
this.filterString1(),
this.filterString2(),
this.filterString2(),
},
deep: true

Vue $watch provide callback function two parameters (newValue, oldValue) so U can check new value each filter if true for each filter action
watch: {
filters: {
handler: function({statusString1, statusString2, statusString3}){
if (statusString1)
this.filterString1()
if (statusString2)
this.filterString2()
if (statusString3)
this.filterString3()
},
deep: true
}
}

Use a dot-delimited path as the watcher key.
watch: {
'filters.string1' () {
this.filterString1(),
},
'filters.string2' () {
this.filterString2(),
},
'filters.string3' () {
this.filterString3(),
},
}

Related

Angular Slickgrid | How to disable the row selection of checkboxSelector of selectableOverride dynamically

Want to disable the selected row items by updating the gridOptions after some button clicks.
initGridOptions() {
this.gridOptions = {
enableSorting: true,
enableFiltering: true,
enablePagination: true,
enableAutoResize: true,
autoResize: {
containerId: 'grid-wrapper',
sidePadding: 5
},
alwaysShowVerticalScroll: false,
enableCheckboxSelector: true,
enableRowSelection: true,
checkboxSelector: {
hideInFilterHeaderRow: false,
hideInColumnTitleRow: true,
},
rowSelectionOptions: {
// True (Single Selection), False (Multiple Selections)
selectActiveRow: false
}
}
}
//prepareGrid() { ...... }
disableButtonClick() {
this.gridObj.setOptions({
checkboxSelector: {
selectableOverride: (row: number, dataContext: any, grid: any) => {
// validate each row and disable the selected rows
return false;
}
}
});
}
Stackblitz Demo
I'm not sure if you can toggle the checkbox column without removing it (maybe with grid.setColumns() but it's probably better to just use the selectableOverride callback. It will allow you to dynamically change its usability on the fly (see the Wiki) and in your case just use your boolean flag to have the callback return true or false (the later will disable/remove all checkboxes)
export class Example1 implements OnInit {
prepareGrid() {
this.gridOptions = {
enableRowSelection: true,
enableCheckboxSelector: true,
checkboxSelector: {
// you can override the logic for showing (or not) the expand icon
// for example, display the expand icon only on every 2nd row
selectableOverride: (row: number, dataContext: any, grid: any) => (dataContext.id % 2 === 1)
},
multiSelect: false,
rowSelectionOptions: {
// True (Single Selection), False (Multiple Selections)
selectActiveRow: true,
},
};
}
}
As per the new comments and the stachblitz, you need to have only 1 common method and in that method you do different logic depending on what button is clicked outside. For example, if I take some code from your demo, let's use a new flag showOnlyOddRows = false and let say that when you click your external button it will turn that flag to true and as we can expect it will re-render the grid and only show the row selection on odd rows
export class AppComponent implements OnInit {
showOnlyOddRows = true;
ngOnInit(): void {
this.gridOptions = {
checkboxSelector: {
hideInFilterHeaderRow: true,
hideInColumnTitleRow: false,
selectableOverride: this.validateRowSelection.bind(this)
// or
// selectableOverride: (row: number, dataContext: any) => this.validateRowSelection(row, dataContext),
},
// ...
};
}
validateRowSelection(row: number, dataContext: any, grid: any) {
return this.showOnlyOddRows ? dataContext.id % 2 === 1 : true; // returning true means that we want to show the row selection
}
// change flag when external button is clicked and re-render grid with new row selection set
disableOddRows() {
this.showOnlyOddRows = true;
this.gridObj.invalidate(); // this will re-execute the validateRowSelection method
}
So again, do not change the override with setOptions, it will completely break the code, so don't do that. If you really need to change options of the plugin, you should use the plugin setOptions not the grid.setOptions. Something like this.angularGrid.extensionService.getSlickgridAddonInstance(ExtensionName.checkboxSelector).setOptions({ /* ... */ }) or this.angularGrid.extensionService.getSlickgridAddonInstance(ExtensionName.checkboxSelector).selectableOverride = newOverrideFn ... but again I probably wouldn't do that, it's easier to just keep 1 method with different logic inside it (like validateRowSelection shown earlier)

Can I Pass a Function To a Props on a VueJS Component

I have a rather simple component. Basically it is a dropdown select and I need to run some custom code that is in an external javascript function. However, this prop is not required on every instance of the component. So sometimes there may be nothing in the prop. Other times it might do one thing, others might do something else.
<template id="drop-list-template">
<select class="form-control"
v-model="value"
v-bind:class="{ required: isRequired, invalid: !isValid }"
v-on:blur="validate"
v-on:change="changed"> <-- This is the Prop I want to use
<option v-if="showEmptyOption" value="">{{ emptyOption }}</option>
<option v-for="i in items"
v-bind:value="i.value"
v-bind:selected="i.checked === value"
v-bind:disabled="i.enabled === false">
{{ i.text }}
</option>
</select>
</template>
So in the on-change event, it will call the changed method. That was not working. I then added a special prop to the code file:
Vue.component("drop-list", {
template: "#drop-list-template",
props: {
dataset: { type: Array, required: true },
isRequired: { type: Boolean, required: false, default: false },
emptyOption: { type: String, required: false, default: "*Select an Option *" },
showEmptyOption: { type: Boolean, required: false, default: true },
special: { required: false }
},
data: function () {
return {
items: this.dataset,
isValid: true,
value: ""
}
},
methods: {
validate: function (event) {
var Result = true;
if ((this.isRequired === true) && (this.value === ""))
Result = false;
this.isValid = Result;
return Result;
},
changed: function (event) {
if (this.special) {
AbnormalitiesAndImpressions(); <-- Obviously this works
alert("After");
this.special(); <-- Would want this to run AbnormalitiesAndImpressions
}
}
}
});
And implement it via:
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
special="AbnormalitiesAndImpressions">
</drop-list>
Where AbnormalitiesAndImpressions is just dumb right now:
function AbnormalitiesAndImpressions(lstAbs, lstImps) {
alert("Got to here");
}
When I run it, the "Got to here" alert pops up and so does the "After" alert. It then fails because this.special(); is not a function.
Bottom line is I am trying to let the user (myself in this case) create as many of these lists as needed. What will happen on some of them is they tweak what is available in other controls. So a sort of validation is going on. I just want this to be customizable per each use of the component.
I would even be fine with an anonymous function like the following:
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
special="function () { AbnormalitiesAndImpressions(); }">
</drop-list>
Update
I have updated my component slightly:
<template id="drop-list-template">
<select class="form-control"
v-model="value"
v-bind:class="{ required: isRequired, invalid: !isValid }"
v-on:blur="validate"
v-on:change="change">
<option v-if="showEmptyOption" value="">{{ emptyOption }}</option>
<option v-for="i in items"
v-bind:value="i.value"
v-bind:selected="i.checked === value"
v-bind:disabled="i.enabled === false">
{{ i.text }}
</option>
</select>
</template>
And the corresponding javascript:
Vue.component("drop-list", {
template: "#drop-list-template",
props: {
dataset: { type: Array, required: true },
isRequired: { type: Boolean, required: false, default: false },
emptyOption: { type: String, required: false, default: "*Select an Option *" },
showEmptyOption: { type: Boolean, required: false, default: true },
special: { type: Function, required: false }
},
data: function () {
return {
items: this.dataset,
isValid: true,
value: ""
}
},
methods: {
change: function (event) {
if (this.special)
this.special();
}
}
});
And the implementation:
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
:special="AbnormalitiesAndImpressions">
</drop-list>
And here is the page's Vue code:
var vm = new Vue({
el: "#app",
data: {
Result: {},
Defaults: {},
Errors: [],
Abnormalities: [],
Impressions: []
},
methods: {
AbnormalitiesAndImpressions: function () {
alert("Should get overridden");
}
}
});
vm.AbnormalitiesAndImpressions = function (lstAbs, lstImps) {
alert("Got to here: " + lstAbs + "\n" + lstImps);
}
I found that if I did not add the methods short version of AbnormalitiesAndImpressions that it would give me a Vue warning that the property did not exist. However, the version of AbnormalitiesAndImpressions at the bottom of that file actually runs. I like this as each implementation could change and they should be on the page and not on the component.
When I change the dropdown item, I do get the Got to here message. And of course it has two undefined as the lstAbs and lspImps were not passed in.
New Question
Is it possible then to pass values to my props function? In this case, they can be strings. But if I do the code below...
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
:special="AbnormalitiesAndImpressions('test')">
</drop-list>
When the page loads, the alert is popped right away and Does have the test parameter. And when I actually change the select the alert does not fire at all.
Ok, so let's do it step by step.
Correct method declaration
methods: {
change: function (event) {
if (this.special)
this.special();
}
}
This method declaration isn't good, it's changing the this context, always declare methods with arrow functions or with the shorthand syntax. Anonymous functions declared like this: function () { //... } creates a new this context, and beacuse of that this.special is always undefined. So change it to:
methods: {
change (event) {
if (this.special)
this.special();
}
}
Do it at all methods, it'll avoid a lot of headache. To another anonymous functions, always use arrow functions.
Method passed as prop
About your new question, let me explain what's happening when you set the special prop as AbnormalitiesAndImpressions with vanilla Js to clarify your mind.
Think about a method foo, just like this below:
function foo (string) {
return string;
}
Above we can see the method declaration, in Js is possible to assign a function to a variable, so, if a create a variable a it can be equals to foo, just like it:
let a = foo;
As you can see, I'm passing the function foo to the var a, not the return of the function foo, it's what you do when you set the special property as AbnormalitiesAndImpressions, because of that you can't do this: :special="AbnormalitiesAndImpressions('test')", but, if we look back to my example, one thing we can do, that is:
a('bar');
And it'll return 'bar', so, applying it to Vue, at your component drop-list, where you call the function as this.special you can pass params to the function, did you get it?

vuejs how to run emit on every array change

I've got an array in child component like this:
arrayChild:
[
{name: 'name1', text: 'text1', buttons: 'false', active: "true"},
{name: 'name2', text: 'text2', buttons: 'false', active: "false"},
...
]
I want to emit the arrayChild on every change in the name, text and active (not buttons change!)
How can I do it?
I created basic function to emit this on button click:
<btn #click="emitParent()">emit my Array</btn>
emitUp() {
this.$emit('offerArray', this.arrayChild)
}
But it emit only on button click. I need to emit this automatically on any change within name, text and active. How can I do it? Should I use some kind of computed?
Vue.js offers watchers! So you can "watch" a property and when a change is made you can do stuff.See below:
export default {
data() {
return {
propertyName: 'valueOfProperty'
}
},
watch: {
propertyName(theNewChangedValue) {
//do stuff here
}
}
}
Also consider a deep watcher,that is useful when working with array of objects
propertyName: {
handler: function(newValue) {
//do stuff when array of object changes
},
deep: true
}

Knockout Validation on Nested View Model

I am looking to use a combination of the KnockoutJS libarary, the Knockout.Mapping plugin, and the Knockout-Validation plugin to display some data that the user can manipulate.
My data is coming over as a nested object from an AJAX call, and I run that data through the mapping plugin to create a Knockout view model, customizing the validation rules with the mapping options object in ko.mapping.fromJS.
I have been successful in getting objects at the first layer (name in the Fiddle below) to show a message if the field is empty, however objects that are nested (IntroData.PlanName) do not show the validation message. Do I need to setup the mapping object differently for these nested objects?
ViewModel (sample of what is coming in my AJAX call):
var stuff = {
IntroData: {
PlanName: 'Test'
},
name: 'tes2s3t'
};
Mapping:
var validationMapping = {
IntroData: {
PlanName: {
create: function (options) {
return ko.observable(options.data).extend({
required: true
});
}
}
},
name: {
create: function (options) {
return ko.observable(options.data).extend({
required: true
});
}
}
};
Hookup:
ko.validation.init({
registerExtenders: true,
messagesOnModified: true,
insertMessages: true,
parseInputAttributes: true,
messageTemplate: null,
grouping: {
deep: true
}
}, true);
window.viewModel = ko.validatedObservable(ko.mapping.fromJS(stuff, validationMapping));
ko.applyBindings(window.viewModel);
Fiddle: http://jsfiddle.net/odxv53g9/5/
Thanks!
The documentation is not clear on this, but apparently ko.mapping.fromJS() ignores nested mappings, so the "create" method for PlanName never gets called.
You could add an explicit mapping for IntroData instead:
IntroData: {
create: function (options) {
var nestedMapping = {
PlanName: {
create: function (options) {
return ko.observable(options.data).extend({
required: true
});
}
}
}
return ko.mapping.fromJS(options.data, nestedMapping);
}
}
Here is a working fiddle: http://jsfiddle.net/odxv53g9/6/

Set global setOptions for Backbone.Stickit.js bindings

Is there any way of setting global setOptions for Backbone.Stickit.js?
That way I can avoid having to set validate: true as well as other custom options on every binding:
bindings = {
'.someEl': {
observe: 'prop1'
, setOptions: {
validate: true
}
}
, '.someOtherEl': {
observe: 'prop2'
, setOptions: {
validate: true
}
}
, '.yetAnotherEl': {
observe: 'prop3'
, setOptions: {
validate: true
}
}
};
I had seen some post about using the * selector with .addHandler:
Backbone.Stickit.addHandler({
selector: '*',
setOptions: {validate: true}
});
But that didn't work for me.
I'm sure there's a simple way that I'm missing but for now my hack was to create a method that parses my property name:
function stickTo(propName, options) {
_.extend({observe: propName}, {setOptions: {validate: true}}, options);
}
...
bindings: {
'.someEl': stickTo('prop1')
}
stickTo sets all my default options and takes an optional parameter that overrides my defaults...
The handler should have worked. I setup a fiddle which logs the arguments of Model.set to the console, every time input changes:
http://jsfiddle.net/px6UP/39/
Backbone.Stickit.addHandler({
selector: '*',
setOptions: {validate:true}
});

Categories

Resources