I have data array like this :
$scope.data = [{
name: 'joseph',
statarray: [{
status: 'Online',
status: 'Offline',
}],
active: 'yes'
},
{
name: 'arnold',
statarray: [{
status: 'Offline'
}],
active: 'no'
},
{
name: 'john',
statarray: [{
status: 'Online'
}],
active: 'yes'
}
];
$scope.findObjectByKey = function(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
};
$scope.Online = function(array){
var obj = $scope.findObjectByKey(array, 'status', 'Online');
return obj;
}
$scope.Offline = function(array){
var obj = $scope.findObjectByKey(array, 'status', 'Offline');
return obj;
}
The functions $scope.Online and $scope.Offline sorts the data according to the status Online and Offline.
Here's my view :
I have these two checkboxes as filters :
<input ng-true-value='Online' ng-false-value='' type="checkbox" ng-model="online" type="checkbox">Online
<input ng-true-value='Offline' ng-false-value='' type="checkbox" ng-model="offline" type="checkbox">Offline
<div ng-repeat="user in data|filter:online|filter:offline">
<p>{{user.name}}</p>
</div>
Currently when I click the checkbox corresponding to Online it displays the user joseph and john whose status is Online and when I click the checkbox corresponding to Offline it displays the users joseph and arnold whose status are Offline. This much is working perfectly. But when I click both the filter buttons it only displays joseph as joseph has both Online and Offline status. So an AND operation is being applied here. But I want an OR operation here. So when I click both the filter buttons I should get the output as joseph,arnold and john in the view. Any clue on how can I do it?
First, your statarray seems wrong, considering you declared one object with two properties with the same name, first we should move it to something like an array only containing the status strings ex. ['Online', 'Offline'].
You are executing the filter function only using the latest filter selected.
You need to think in a different approach to aggregate your selected filters,
something like create an filter obj.
filter = {
online: true,
offline: false
}
and iterate over then to display your data
$scope.filterArray = function(array, key, value) {
var filtered = [];
for (var i = 0; i < array.length; i++) {
var shouldInclude = false;
shouldInclude |= ($scope.filter.online && array[i].statarray.indexOf('Online') >= 0);
shouldInclude |= ($scope.filter.offline && array[i].statarray.indexOf('Offline') >= 0);
if (shouldInclude) {
filtered.push(array[i]);
}
}
return filtered;
};
This is just one possible approach, if you are able to use ES6 functions this become even simpler.
# pravin navle-
Are you sure its working same as you described below code? Because when I tried to replicated same functionality it works only for Offline and not for Online as well as Both Checked.
Related
This one's been giving me problems for a week, cross my fingers one of you can help me here...
This application was built on Laravel and the front scaffolded using Vue.
Thing is I have an array of objects that is supposed to be sent to the backend in order for it to be stored in a database. Thing is this is an editor and the idea is not reload the page every time something is changed, so here comes my problem...
The way of getting the information is through window.postMessage(), so it seems the information lingers on even after saving, since the page behavior is for it to not reload, I have tried emptying the array after firing the save function. Now it works the first time because the array is empty so there's nothing to compare it to, it also works the second time, but from the third time on, it duplicates some of the objects inside and stores them in DB.
Here's my code:
saveNewSettings() {
//THIS IS THE ARRAY I NEED TO EMPTY (ALREADY DECLARED IN THE DATA OBJECT)
/* this.newItems = [
{ id="123", name="a", otherProps="someProps" },
{ id="456", name="ab, otherProps="someProps" },
{ id="789", name="c", otherProps="someProps" },
]
*/
//THIS IS THE AN EMPTY ARRAY I'M USING TO COMPARE LATER ON... (ALREADY DECLARED IN THE DATA OBJECT)
// this.newlyCreatedItems = [];
if ( !this.newlyCreatedItems.length ) {
this.newlyCreatedItems = this.newItems;
} else {
for ( let i = 0; i < this.newItems.length; i++ ) {
for ( let j = 0; j < this.newlyCreatedItems.length; j++ ) {
if ( this.newItems[i].id == this.newlyCreatedItems[j].id ) {
this.newItems.splice( i, 1 );
}
}
}
}
//THIS IS THE SERVICE I USE TO SEND THE INFO TO THE BACK END
//THIS ONE HAS BEEN IMPORTED FROM AN SERVICE FILE
settingsService.save( this.newItems )
.then(response => {
//WHAT TO DO AFTER THE RESPONSE GOES HERE
});
}
So here's the thing, firing the function for the first time, since it's the first, doesn't duplicate anything in the database... For the second time, it works well and it only saves the new item, from the third time on, it starts duplicating.
If you need me to elaborate more, just let me know, I thank you all in advance...
Quick and dirty using jQuery:
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
var uniqueNames = [];
$.each(names, function(i, el){
if($.inArray(el, uniqueNames) === -1) uniqueNames.push(el);
});
You tagged vue.js but this problem statement is more like from JavaScript side. Basically, You are doing shallow copy of the newItems array into the newlyCreatedItems array which causing the updation issue as both are referencing to the same pointer address.
You can resolve this issue by deep copying with the help of structuredClone() method.
Live Demo :
let newItems = [
{ id: "123", name: "a", otherProps: "someProps" },
{ id: "456", name: "ab", otherProps: "someProps" },
{ id: "789", name: "c", otherProps: "someProps" }
];
let newlyCreatedItems = [];
function saveNewSettings() {
if (!newlyCreatedItems.length ) {
newlyCreatedItems = structuredClone(newItems);
} else {
for ( let i = 0; i < newItems.length; i++ ) {
for ( let j = 0; j < newlyCreatedItems.length; j++ ) {
if ( newItems[i].id == newlyCreatedItems[j].id ) {
newItems.splice( i, 1 );
}
}
}
}
}
saveNewSettings();
console.log(newlyCreatedItems);
console.log(newItems);
console.log('-------');
saveNewSettings();
console.log(newlyCreatedItems);
console.log(newItems);
I'm going to pre-face this with saying i'm not sure this is the best approach so other approaches are greatly appreciated
End Goal: To store a list of products and the toppings purchased by calling the woocommerce API and using the response data
I'm calling the woocommerce REST api that provides me a good chunk of JSON data back. In the JSON are line_items. These are the products purchased. Nested in line_items are meta_data, this is the toppings for example tomato or sauce.
Attached an image of the JSON
So what i'm trying to do is create something like this
var testOrderItems =
[{
title: "Fried Chicken Burger",
meta: [
"Lettuce",
"cheese slice",
"kethcup"
]
},
{
title: "Beef Burger",
meta: [
"Lettuce",
"cheese slice",
"kethcup"
]
}
]
which will follow my schema for oder items
var orderItems = new Schema({
title: {type: String, required: true},
meta: [{type: String}]
});
So to do this, i figured I would just do a forloop or foreach through the JSON to get all the product names and their meta. Getting actual values is easy. The hard part is creating the array or JSON object that I can then store, i'm just not sure how to create it whilst in the loop. Below are a few things I tried
let fullData = JSON.parse(result)
//parsed response from woocommerce API call
fullData.line_items.forEach((product, index) => {
//for each line item get me the product
orderItems.push(product.name)
//var namey =
//push the product name to the orderItemsArray
product.meta_data.forEach(function(meta) {
//checks for string as one of the plug-ins fills the meta with more nested information and we only want the top level string
if (typeof meta.value === 'string' || meta.value instanceof String)
// it's a string
orderItems.push(meta.value)
//Onbviously won't nest the meta with the product name just on new lines
})
});
The I thought I could do it in for loops by storing an ID ref as "i" and being able to re-reference this later in the nested loop to add the meta, i got a little lost with this
var length = fullData.line_items.length
for (let i = 0; i < length; i++) {
// console.log(i);
console.log(fullData.line_items[i].name)
for (let j = 0; j < fullData.line_items[i].meta_data.length; j++) {
var metaValue = fullData.line_items[i].meta_data[j].value
if (typeof metaValue === 'string' || metaValue instanceof String) {
console.log(fullData.line_items[i].meta_data[j].value);
stringMeta = fullData.line_items[i].meta_data[j].value
//this works but has drawbacks
//1 obviously just overwrites itself each time
//2 will stop at the end of meta so won't add items without meta
finalOrderItems = {
id: i,
name: fullData.line_items[i].name,
meta: [stringMeta]
}
}
}
}
and thats where I am, feels like this should be incredibly easy but can't quite grasp it at the moment.
You could simply create the object that represents your schema first, then return it from a map of your json Object. So, it would look like the following:
let testOrderItems = fullData.line_items.map((product)=>{
let obj = { name: product.name };
obj.meta = product.meta_data.map((meta)=>{
if (typeof meta.value === 'string' || meta.value instanceof String)
return meta.value;
}).filter((value)=>!!value);
return obj;
})
console.log(testOrderItems);
Although, the if statement seems a little redundant, since the woocommerce api will simply either have meta or not. However, you may have some plugin or something which is adding more information to the meta area so i've kept it in my example.
This looks like a job for map and reduce not forEach. map will map each object of line_items into a new object and reduce will group and organize the metas by key for each object:
var orderItems = fullData.line_items.map(function(product) { // map each product in line_items
return { // into a new object
title: product.name, // with title equals to the current product's name
meta: product.meta_data.reduce(function(acc, meta) { // and metas accumulated from each meta object in the current product's meta_data array
acc[meta.key] = acc[meta.key] || []; // first, check if there is an array for the current meta's key in the group object 'acc', if not create one
acc[meta.key].push(meta.value); // add the current meta's value to that array
return acc;
}, {})
}
});
Shorter using arrow functions:
var orderItems = fullData.line_items.map(product => ({
title: product.name,
meta: product.meta_data.reduce((acc, meta) => {
acc[meta.key] = acc[meta.key] || [];
acc[meta.key].push(meta.value);
return acc;
}, {})
}));
I have the following AngularJS model:
$scope.Model = {
Users : [{
UserId: '',
FirstName: '',
LastName: ''
}],
Products :[{
ProductId: '',
Price: ''
}]
};
If I populate this array with N users, and one user has id=1, how can I update that specific user (with id=1) the property LastName?
So for example if I will get a new AngularJS model:
$scope.UserToUpdate ={
UserId: 1,
LastName: "Smith"
};
I want to loop through the $scope.Model array and update the user with id=1 but only the FirstName property.
P.S. I don't know at what position the target user object in the array it is so basically can be at $scope.Model.Users[0] or $scope.Model.Users[1] or $scope.Model.Users[10] or at $scope.Model.Users[N] ...
You can just loop through your list of users
for (var i = 0; i < $scope.Model.Users.length; i++) {
if($scope.Model.Users[i].UserId === $scope.UserToUpdate.UserId) {
$scope.Model.Users[i].LastName = $scope.UserToUpdate.LastName;
break;
}
}
EDIT: Actually harish's answer is on to something too. Here's another solution using $filter:
var matchedUsers = $filter('filter')($scope.Model.Users, { UserId: $scope.UserToUpdate.UserId });
if (matchedUsers.length > 0) {
matchedUsers[0].LastName = $scope.UserToUpdate.LastName;
}
And don't forget to add the $filter service as a parameter in your controller declaration for this second solution.
$scope.UserToUpdate =
$scope.Model.Users.filter(function(user) { return user.FirstName == "test"; })[0];
BTW: you can add a check if the user exists..
you can use $filter
var user = $filter('filter')($scope.Model.Users, 'UserId == 1');
you are read more about $filter('filter') here
Try this! working demo http://plnkr.co/edit/scyV79HqqA7nOG9h4ezH?p=preview . Please check the console log.
angular.forEach($scope.Model[0].Users, function(value1, key1) {
var i = 0;
angular.forEach(value1, function(value, key) {
if (key == 'UserId' && $scope.UserToUpdate.UserId == value) {
$scope.Model[0].Users[i].LastName = $scope.UserToUpdate.LastName;
}
i++;
});
});
The above code updating the Model object LastName property based on UserToUpdate object (id=1)
i have a model like this
function ViewModel(){
var self = this
self.Choices = ko.observableArray([])
self.AcceptedChoices = ko.observableArray([])
self.LoadData = function(){
self.ViewAnswered()
}
self.ViewAnswered = function(){
var url = 'QuestionsApi/ViewAnswered'
var type = 'GET'
ajax(url , null , self.OnViewAnsweredComplete, type )
}
self.OnViewAnsweredComplete = function(data){
var currentAnswer = data.Answer
self.Choices(currentAnswer.Choices)
self.AcceptedChoices(currentAnswer.AcceptedChoices)
}
self.LoadData()
}
Here is my object. I have removed extra things
{
"AcceptedChoices": [94, 95],
"Choices": [{
"ChoiceId": 93,
"ChoiceText": "Never"
}, {
"ChoiceId": 94,
"ChoiceText": "Sometimes"
}, {
"ChoiceId": 95,
"ChoiceText": "Always"
}]
}
And here is binding
<u data-bind="foreach:Choices">
<li>
<input type="checkbox" name="choice[]" data-bind="value:ChoiceId,checked:$root.AcceptedChoices">
<span data-bind="text:ChoiceText">Never</span>
</li>
</u>
Now the problem is that checkboxes are not being checked due to the choices being array of objects. How can i resolve this issue? Although the same thing works for radio where there is only one selection.
Never mind i have found a solution here
checked binding does not properly compare primatives
Also it tells two ways for this. The Solution provided in fiddle is creepy so i will use the one using knockout version 3.0.0.
All i need to do is attach knockout-3.0.0.js instead of any other and then use checkedValue instead of value.
<input type="checkbox" name="choice[]"
data-bind="
checkedValue:ChoiceId,
checked:$root.AcceptedChoices"
>
And that's done. Hope it helps someone.
EDITS :
I noticed it is not working on the Chrome. So i found an alternative. I created these two functions.
self.ConvertToString = function(accepted){
var AcceptedChoices = []
ko.utils.arrayForEach(accepted, function(item) {
AcceptedChoices.push(item.toString())
})
return AcceptedChoices
}
self.ConvertToInteger = function(accepted){
var AcceptedChoices = []
ko.utils.arrayForEach(accepted, function(item) {
AcceptedChoices.push(parseInt(item))
})
return AcceptedChoices
}
And use them
self.AcceptedChoices(self.ConvertToString(currentAnswer.AcceptedChoices))
To get the value
AcceptedChoices: self.ConvertToInteger(self.AcceptedChoices()),
You need to be checking to see if the Id of a choice is in the AcceptedChoices array. Use the ko.utils array function to help do that:
checked: function() { return ko.utils.arrayFirst($root.acceptedChoices(), function(item){
return item == ChoiceId();
} !== null }
You could put this into a function on your root object:
self.isChoiceAccepted = function(choiceId){
return ko.utils.arrayFirst($root.acceptedChoices(), function(item){
return item == choiceId;
} !== null
};
then call it in your data-bind as:
checked: function() { return $root.isChoiceAccepted(ChoiceId()); }
This isn't tested, I'm not 100% sure that the arrayFirst method returns null if it doesn't find a matching item in the array, so chack that.
How do I user the JavaScript "filter" attribute as filter my JavaScript object?
I've been reading the following StackOverflow post, and am in a similar situation.
I have the following JavaScript object:
{
'cars' :
[{
"car_id" : "1",
"price" : "42999",
"make_id" : "050",
"year_built" : "2007",
"color_id" : "832"
},
..........
]}
I'm using JQuery to display controls to allow people to filter based on: Price, Make, Year Built, Color
Per that other post, I can use the following code:
// if using an old browser, define the 'filter' attribute
if (!Array.prototype.filter)
{
Array.prototype.filter = function(fun /*, thisp*/)
{
var len = this.length >>> 0;
if (typeof fun != "function")
throw new TypeError();
var res = new Array();
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
{
var val = this[i]; // in case fun mutates this
if (fun.call(thisp, val, i, this))
res.push(val);
}
}
return res;
};
}
then to perform the actual filter, I can do:
result = cars.
filter(function(p) { return p.price >= 15000 }).
filter(function(p) { return p.price <= 40000 }).
filter(function(p) { return p.year_built >= 2000 }) etc
What I don't understand is, how do I use my JQuery controls to dynamically change the filter once the filter has already been set? Meaning, let's say I have the filter applied from above, then the user changes there mind and wants to increase the maximum they are willing to pay for a car from $40,000 to $50,000.
How would I problematically modify my filter from :
filter(function(p) { return p.price <= 40000 }).
to:
filter(function(p) { return p.price <= 50000 }).
how do I use my JQuery controls to dynamically change the filter once the filter has already been set?
You don't set a filter. You call filter() with a filter function and get a filtered array back; you can't change the filter that was applied to the array afterwards. Instead you must call filter() again, and pass a different filter function.
Or the same filter function with a closure over a variable that has changed:
var minprice= 10000;
var minpricefilter= function(p) { return p.price>=minprice };
result= cars.filter(minpricefilter);
minprice= 20000;
result= cars.filter(minpricefilter);
You could use a function generator.
function createPriceFilter(price)
{
filter = function(){ return p.price >= price };
return filter;
}
Then, when you filter, always use a function generator.
cars
.filter( createPriceFilter( mySelectedPrice ) )
.filter( createSomethingFilter(...) )
. (...)
Instead of filter, how about a plain old loop:
var min_year = 2000;
var min_price = 15000;
var max_price = 40000;
function fillTable() {
clearTheTable();
for (i = 0; i < cars.length; i++) {
var car = cars[i];
if (p.price >= min_price && p.price <= max_price && p.year_built >= min_year)
addCarToTable(car);
}
}
Each time your parameters change, just call fillTable() again to regenerate the whole table.
(There are much cleverer things you can do but this is the simplest thing I could think of.)
Forget callback based filtering. Enter jOrder: http://github.com/danstocker/jorder.
Filtering by iterating over your entire table is tedious and slow. With jOrder, you search by index:
var table = jOrder(json.cars)
.index('id', ['car_id'])
.index('price', ['price'], { grouped: true, ordered: true, type: jOrder.number })
.index('maker', ['maker_id'], { grouped: true })
.index('year', ['year_built'], { grouped: true, ordered: true, type: jOrder.number })
.index('color', ['color_id'], { grouped: true });
Then you get the records you want by:
var filtered = table.where([{ price: { lower: 15000, upper: 40000 } }], { mode: jOrder.range });
Note that you can only apply one inequality filter at a time. To do more, use filtered as an input for a different jOrder table, put only the necessary index on it, and perform the second inequality filter on that one. And so on. Even if you stack up a couple of filters like this, it will be still faster than iteration by a factor of about 10 to 100 depending on the size of your table.