I have an array of objects:
[{
name: "test",
type: 0,
speed: 50
}, {
name: "test2",
type: 4,
speed: 10
}, {
name: "test3",
type: 4,
speed: 67
}, {
name: "test4",
type: 2,
speed: 40
}]
I want to show the array with ng-repeat..
Until here everything is simple...
But now I want to add few buttons, each button will filter the array with other parameters...
For example:
Button one: Show only the objects where the speed is more than X,
Button two: Show only the objects where the type is Y..
etc...
I now how to use angular filters, but I couldn't understand how to use it for my needs...
Thank you very much!
The most suitable solution for your purpose it is using filter named filter. It can accept custom filtering function in argument.
HTML
<div ng-repeat="item in items|filter:filterItem"></div>
Then you should define function filterItem in your controller:
JS
// assume that first button sets `$scope.hideSlow` flag and the second `$scope.showType` respectively
$scope.filterItem = function(item) {
return (!$scope.hideSlow || item.speed > X) &&
(!$scope.showType || item.type === Y);
}
You can add extra filtering logic into this function, but do not forget write tests on it to be sure that your function filtering properly
I might suggest creating a custom filter that inspects the model to determine the result set out of the filter. Bind the buttons to methods on the scope to check from within the filter.
https://docs.angularjs.org/tutorial/step_09
Related
I'm trying to update an array (Array name is "Variables" please refer the attached screenshot) which presents inside an Object, so I want to update that array if there is word called "placeholder" in alertMessage(it's a different property presents in the same Object)
I'd appreciate any help on how to update this array in question, I tried using pop method but it didn't go as planned and I've attached screenshots of the Objects for reference
You can retrieve the string placeholder like this data['alertMessage']['en_US']['all'] and then use a conditional statement to make changes to the array inside the data object.
let data = {
alertOne: '',
alertTwo: '',
alertMessage: {
en_US: {all: 'placeholder'}
},
variables: [
{id: 0, uuid: '123'},
{id: 1, uuid: '223'},
{id: 2, uuid: '323'}
]
}
let all = data['alertMessage']['en_US']['all']
// if condition is met add a new object to the array
if(all === 'placeholder'){
data.variables = [...data.variables, {id: 3, uuid: '423'}]
}
console.log(data)
hello everyone i have array of objects, and inside each object i have also array of objects..
i did the v-for inside the v-for to display data
at first i wanted for each row to show the data of the first element of the each child of the parent array and onClick event, i wanted to change the data only in the specifique row.
infos: [{
name: 'name1',
infosName: [{
place: 'place.1.1',
surface: '100'
},
{
place: 'place.1.2',
surface: '200'
}
]
},
{
name: 'name2',
infosName: [{
place: 'place.2.1',
surface: '300'
},
{
place: 'place.2.2',
surface: '400'
}
]
}
]
i created a method to display the data and got on parametres Two indexs
this a jsfiddle to understand more the problem
Thank you
https://jsfiddle.net/f0ehwacm/2
There are several issues needing fixing, but you are on the right lines.
Most importantly, you need to store not just one "myIndex" but a separate "myIndex" for each row
That is the root cause of your problem.
Let me rephrase your question?
I believe you are hoping for four buttons. The top two buttons choose between two options.
Completely separately, the bottom two buttons choose between two options.
It would be easier for readers to understand your intention if you called the two top buttons "Question 1, Option A", and "Question 1, Option B". And then the bottom two "Question 2...". Then they would understand why when you click on one of the buttons, you want to affect the output of the table for that row only.
Avoid using generic terms like "index" and "i"
These make it unnecessarily difficult for people to understand what you intend. Better to use a concrete noun, in this case "question" or "answer", and prefix it with "i" when you mean the index, such as "iQuestion" for the index of the question and "question" for the question itself.
You seem to have a single function "getInfos" which does BOTH getting and setting of information
This is a major problem. You should separate the two functions.
When you click, you want to run a "set" function, that updates your index.
When you are simply displaying, you can access a "get" function, which does not change anything.
You need to store an index for each row
In my terminology, you need to store the index of your answer to each question.
So instead of this.myIndex starting at 0, you have it starting at [0,0]. Each of the two values can be updated separately, allowing the program to update the answer to one row (i.e. one question), while leaving the other row unchanged.
I have renamed this variable to this.myAnswer to make it easier to understand.
this.$set when writing to an array that you want Vue to react to
I initially wrote the "setAnswer" function as follows:
this.myAnswer[iQuestion]=iAnswer
However, I found that the on-screen display was not updating. This is a common problem in Vue, when you update not the main property listed in data(), but an array element of that property.
This is because Vue is not tracking the updates of the array elements, only the array itself. So if you were to reassign the entire array, Vue would notice.
The workaround is to tell Vue explicitly that you are updating something that needs to be reactive. Vue will then update it on screen.
To do this, change your assignment from this format:
this.array[index] = value
To this
this.$set(this.array, index, value)
Vue provides this function this.$set, which executes your normal this.array[index] = value and tells Vue to do the screen update.
How to cope with missing "infosName"
In response to your question in the comments. You have a convenient place to solve this: your getAnswer() function.
Change from this:
getAnswer(iQuestion,iAnswer){
return {
'name' : this.infos[iQuestion].infosName[iAnswer].place,
'surface' : this.infos[iQuestion].infosName[iAnswer].surface
}
to this:
getAnswer(iQuestion,iAnswer){
if (this.infos.length>iQuestion &&
this.infos[iQuestion].infosName &&
this.infos[iQuestion].infosName.length>iAnswer
){
return {
'name' : this.infos[iQuestion].infosName[iAnswer].place,
'surface' : this.infos[iQuestion].infosName[iAnswer].surface
}
else return {
name : "",
surface: ""
}
}
Solution
html:
<div id="app">
<div v-for="(question,iQuestion) in infos">
<div class="row d-flex">
<span style="margin-right:10px" v-for="(answer,iAnswer) in question.infosName" class="badge badge-primary" #click="setAnswer(iQuestion,iAnswer)"><i class="fa fa-eye" style="margin-right:10px;cursor: pointer"></i>{{ answer.place }}</span> </div>
<div class="row">
<p>Name : {{ getAnswer(iQuestion,myAnswer[iQuestion]).name }} </p>
<p>Surface : {{ getAnswer(iQuestion,myAnswer[iQuestion]).surface }}</p>
</div>
</div>
</div>
JS:
new Vue({
el :'#app',
data : function(){
return {
myAnswer : [0,0],
infos : [
{
name : 'name1',
infosName : [
{
place : 'Question 1, Option A',
surface : '100'
},
{
place : 'Question 2, Option B',
surface : '200'
}
]
},
{
name : 'name2',
infosName : [
{
place : 'Question 2, Option A',
surface : '300'
},
{
place : 'Question 2, Option B',
surface : '400'
}
]
}
]
}
},
methods:{
setAnswer(iQuestion,iAnswer){
this.$set(this.myAnswer,iQuestion,iAnswer)
},
getAnswer(iQuestion,iAnswer){
return {
'name' : this.infos[iQuestion].infosName[iAnswer].place,
'surface' : this.infos[iQuestion].infosName[iAnswer].surface
}
}
}
})
I have a div and a following javascript:
let usersNotContributingIds = [ 19, 20, 21 ];
let usersNotContributingNames = [ "Flavius K.", "Pogchamp", "Lazy Gopnik" ];
let contributorToBeAddedId; // the value that will be used for further actions
$("#alpaca-search-contributing-users").alpaca({
data: null,
schema: {
type: "object",
enum: usersNotContributingIds,
},
options: {
name: "pls",
label: 'Contributor Fullname',
optionLabels: usersNotContributingNames,
helper: "Select user sou want to add as a contributor",
id: "select2-search",
focus: false,
events: {
change: function() {
console.log(this.getValue().value);
contributorToBeAddedId = this.getValue().value
},
focus: function() {
console.log(this.name);
},
blur: function() {
console.log(this.name + ": blur");
},
ready: function() {
console.log(this.name);
}
},
},
postRender: function(control) {
$('#select2-search').select2();
}
});
Obviously, I want to get the newly set value, or anyhow access the selected value and use it. For example with AJAX and a button.
The problem is, that when I have 3 or less options, Alpaca render the field not as a search, but as a radio-something and the this.getValue() is null.
Is there a way to force Alpaca to NOT USE THE RADIO BUTTONS? I dont want to use them, even if I had only 1 option. Documentation just promtly states, that if there are 3 or less options, it will generate radio buttons instead of select, but it says nothing about the fact, that it breaks everything and that I would not be able to retrieve the value the same way as with select field.
If I am doing something inefficiently or wrong, please tell me, I am new with Alpaca and I just want a neat select dropdown with search, that I can use to pick users from a list with any length. Also, I would like the "null" or "none" option to not be there.
To have your select component rendered you should use the option type and set it to "select".
The issue with the value is because you're using it wrong, to get the value in alpaca you only do this.getValue() and there's no need to add .value.
FYI: If you see the error "This field should have one of the values in Flavius K., Lazy Gopnik, Pogchamp. Current value is: 19" you should update your enum array to have strings instead of ints let usersNotContributingIds = [ "19", "20", "21" ];.
Here's a working fiddle for this.
I'm wanting to disable an option if it has already been selected in one of the object groups.
So, if I selected "2013" then added another sample, "2013" would not be available in that group, unless that option is changed in the original group.
Is there an easy way to do this that I'm missing? Do I need to reactively update the schema when a selection is made?
samples:{
type: Array,
optional: true,
maxCount: 5
},
"samples.$":{
type: Object,
optional: true
},
"samples.$.sample":{
type:[String],
autoform: {
type: "select",
options: function () {
return [
{
optgroup: "Group",
options: [
{label: "2013", value: 2013},
{label: "2014", value: 2014},
{label: "2015", value: 2015}
]
}
];
}
}
},
Proof of Concept
I know this post is about 3 years old. However, I came across the same issue and want to provide an answer for all those who also stumbled over this post.
This answer is only a proof of concept and does not provide a full generic and performant solution, that could be used on production apps.
A fully generic solution would require a deep change in the code of how select field options are generated and updated in AutoForm.
Some prior notes.
I am using Autoform >=6 which provides a good API to instantly obtain field and form values in your SimpleSchema without greater trouble. SimpleSchema is included as npm package and Tracker has to be passed to it in order to ensure Meteor reactivity.
Functions like AutoForm.getFieldValue are reactive, which is a real great improvement. However, reactively changing the select options based on a reactive value causes a lot of update cycles and slows the performance (as we will see later).
Using AutoForm.getFormValues is not working, when using it within options of an Object field. While working within Array field, it will not behave reactively in Object fields, thus not update the filtering on them.
Manipulating Options for Arrays of Select Inputs (failing)
You can't use it with array types of fields. It's because if you change the select options, it applies for all your select instances in the array. It will therefore also apply to your already selected values and strips them away, too. This makes your select looks like it is always "not selected"
You can test that yourself with the following example code:
new SimpleSchema({
samples:{
type: Array,
optional: true,
maxCount: 5
},
"samples.$":{
type: String,
autoform: {
type: "select",
options: function () {
const values = AutoForm.getFormValues('sampleSchemaForm') || {};
const samples = values && values.insertDoc && values.insertDoc.samples
? values.insertDoc.samples
: [];
const mappedSamples = samples.map(x => x.sample);
const filteredOpts = [
{label: "2013", value: "2013"},
{label: "2014", value: "2014"},
{label: "2015", value: "2015"}
].filter(y => mappedSamples.indexOf(y.value) === -1);
return [
{
optgroup: "Group",
options:filteredOpts,
}
];
}
}
},
}, {tracker: Tracker});
Using fixed values on an Object Field
when taking a closer look at the schema, I saw the maxCount property. This made me think, that if you anyway have a list of max options, you could solve this by having fixed properties on a samples object (by the way: maxCount: 5 makes no sense, when there are only three select options).
This causes each select to have it's own update, that does not interfere the others. It requires an external function, that keeps track of all selected values but that came out be very easy.
Consider the following code:
export const SampleSchema = new SimpleSchema({
samples:{
type: Object,
optional: true,
},
"samples.a":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'a');
}
}
},
"samples.b":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'b');
}
}
},
"samples.c":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'c');
}
}
},
}, {tracker: Tracker});
The code above has three sample entries (a, b and c) which will let their options be computed by an external function.
This function needs to fulfill certain requirements:
filter no options if nothin is selected
filter not the option, that is selected by the current samples select
filter all other options, if they are selected by another select
The code for this function is the following:
function getOptions(samples={}, prop) {
// get keys of selections to
// determine, for which one
// we will filter options
const sampleKeys = Object.keys(samples);
// get sample values to
// determine which values
// to filter here
const sampleValues = Object.values(samples);
const filteredOptiond = [
// note that values are stored as strings anyway
// so instead of parsing let's make them strings
{label: "2013", value: "2013"},
{label: "2014", value: "2014"},
{label: "2015", value: "2015"}
].filter(option => {
// case 1: nothing is selected yet
if (sampleKeys.length === 0) return true;
// case2: this selection has a
// selected option and current option
// is the selected -> keep this option
if (sampleKeys.indexOf(prop) > -1 && option.value === samples[prop])
return true;
// case 3: this selection has no value
// but others may have selected this option
return sampleValues.indexOf(option.value) === -1;
});
return [
{
optgroup: "Group",
options: filteredOptiond,
}
]
};
Some Notes on this Concept
Good:
-it works
-you can basically extend and scale it to your desired complexity (optgroups, more fields on samples, checking against other fields with other fields etc.)
Bad:
- performance
- bound to a given (or the nearest) form context (see here)
- much more code to write, than for an array.
When building up a list of options in a select list using Javascript I'd like to attach a Javascript object to that option that I could easily retrieve in the change event.
So, while writing this question I wanted to provide some sample code and found a way to make this work; so I have a slight addition to the question. Is it bad to attach javascript object to DOM elements? Back in the day I remember there being issues with memory leaks in certain circumstances.
Here's a working example:
http://jsbin.com/afolo3/edit
var objs = [
{ id: 1, value: "One", type: "number" },
{ id: 2, value: "Two", type: "number" },
{ id: 3, value: "Three", type: "number" },
{ id: "A", value: "A", type: "char" },
{ id: "B", value: "B", type: "char" },
{ id: "C", value: "C", type: "char" },
];
var options = $.map(objs, function(item, idx) {
var opt = $("<option/>").val(item.id).text(item.value);
opt[0]["obj"] = item;
return opt;
});
$.fn.append.apply($("#select"), options)
.change(function() {
$("#select option:selected")
.each(function(){
alert(this.obj.type);
});
});
Use jQuery's .data() function instead.
Updated example: http://jsbin.com/afolo3/2
You can certainly attach objects to element instances the way you have; in fact, that's how jQuery does its data magic behind-the-scenes in the current version.
That said, since you're using jQuery already, I'd probably use the jQuery API for this (data) instead, just in case it turns out that at some stage, a browser comes on the scene where a workaround is required — you'll get the benefit of the jQuery maintainers doing the workaround for you, rather than having to do it yourself.
Here's what it would look like using data. Basically to set the data:
opt.data("obj", item);
...and to retrieve and show its type property:
alert(opt.data("obj").type);
...where in each case, opt is a jQuery object for the option element in question.
One slight "gotcha" with jQuery's data function: If you retrieve a data object you've never set, you'll get null back (rather than undefined, which would be the usual JavaScript convention).