Dynamically update and check an array of Objects - javascript

So I have a callback function that returns a data object from the dom (there is a list of items and every time you select an item it returns it as an object). Here is the call back function:
$scope.fClick = function( data ) {
$scope.x = data;
}
The object returned from fClick looks like this when you select an item from the dropdown : { name: "English", ticked: true }
When you deselect it from the dropdown it would return something like this:
{ name: "English", ticked: false }
Now I keep an array something like $scope.output to maintain a list of the returned objects. However, what I want to do is add an object returned from scope.fClick to $scope.output only if there isn't an object in output already with the same property "name" value. So right now in my implementation both { name: "English", ticked: false } and { name: "English", ticked: true } get added to the array. Instead I want it to update the ticked property. So, for instance if if $scope.output = { name: "English", ticked: false } and then scope.fClick returns { name: "English", ticked: true}. When I push this value to $scope.output I want it to the update the existing object's tick property so $scope.output = { name: "English", ticked: false } becomes $scope.output = { name: "English", ticked: true }
This is what I have tried:
$scope.fClick = function( data ) {
$scope.x = data;
}
$scope.$watch(function () {
return $scope.y = $scope.x;
},function (newValue, oldValue) {
var id = $scope.output.indexOf(newValue);
if(id === -1){
$scope.output.push(newValue);
}
else {
$scope.output[id].tick = newValue.tick;
}
console.log($scope.output);
},true);
How do I make this work?

Set & Get selected values, name and text of Angularjs isteven-multi-select
<div isteven-multi-select
input-model="marks"
output-model="filters.marks"
button-label="name"
item-label="name"
tick-property="ticked"
selection-mode="multiple"
helper-elements="all none filter"
on-item-click="fClick( data )"
default-label="Select marks"
max-labels="1"
max-height="250px">
</div>
Add items
$scope.marks= [
{ name: 'Mark I', value: 'Mark i', text: 'This is Mark 1', ticked: true },
{ name: 'Mark II', value: 'Mark ii', text: 'This is Mark 2' },
{ name: 'Mark III', value: 'Mark iii', text: 'This is Mark 3' }
];
Get selected item (on change)
$scope.fClick = function (data) {
console.log(data.name);
console.log(data.value);
console.log(data.text);
return;
}
Select item (Dynamically)
$scope.abc = function (data) {
console.log(data.element1, data.element2);
angular.forEach($scope.marks, function (item) {
if (item.value == data.element1) {
item.ticked = true;
}
else {
item.ticked = false;
}
});
}
Deselect item
$scope.marks.map(function (item) {
if (item.value == "")
item.ticked = true;
else
item.ticked = false
});

You can do the following by "simulate" a key/value map.
Controller
(function(){
function Controller($scope) {
$scope.data = [
{name: 'English', ticked: true},
{name: 'French', ticked: false}
];
//Represent a map key - value
$scope.output = {};
$scope.update = function(index){
//Change the ticked value by opposite
$scope.data[index].ticked = !$scope.data[index].ticked;
//Set the value to our map
$scope.output[index] = $scope.data[index].ticked;
}
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
Here, when you will update $scope.output, if the key exist, it will erase it by the new value, instead of it will create a new key/value pair.
HTML
<body ng-app="app" ng-controller="ctrl">
<ul>
<li ng-repeat="item in data">{{item.name}} {{item.ticked}} <button type="button" ng-click="update($index)">update</button></li>
</ul>
</body>

Related

Loop through and delete elements in an array of objects

In my Vue.js project I have an array of objects which I want to list through and display in the browser.
My array contains four objects, I want to display only 3. The way I choose the 3 objects are dependent on a preference setting that the user has chosen somewhere else in the project and stored in a variable (below it is called userPreference). I am currently stuck on the best and most efficient way to remove one of the objects from my array based on the userPreference value.
My v-for in my template
<ul v-for="item in getOutroItems"><li>item<li></ul>
My object:
data() {
return {
outroItems: [{ title: "outro1", text: "XYZ" }, { title: "outro2", text: "ABC" }, { title: "outro3",
text`enter code here`: "QRS" }, { title: "outro4", text: "TUV" }],
userPreference: ""
};
}
My computed property (this is what I have so far)
getOutroItems() {
this.outroItems.filter((value) => {
if(this.userPreference === "newsletter") {
/// here I want to remove outro2 from my array and return an array with the other 3 values
} else (this.userPreference === "noNewsletter") {
/// here I want to remove outro3 from my array and return an array with the other 3 values
}
})
}
So, what is the best way to remove a specific element from an array?
Thanks in advance, and let me know if anything wasn't clear enough.
Your requirement can be fulfilled by below code as array.filter just wants true or false in its return to accept or remove an element from its array.
getOutroItems() {
this.outroItems.filter((value) => {
if(this.userPreference === "newsletter") {
// here I want to remove outro2 from my array and return an array with the other 3 values
return value.title != 'outro2';
} else (this.userPreference === "noNewsletter") {
// here I want to remove outro3 from my array and return an array with the other 3 values
return value.title != 'outro3';
}
})
}
However if you want to not create another array if it is big. you should go with swapping such elements to be removed with the end indexed element in the array and popping those many elements from the array.
There are multiple ways of getting the correct items from an array.
My preferred method and in your example: Using array.filter
const outroItems = [
{ title: "outro1", text: "XYZ" },
{ title: "outro2", text: "ABC" },
{ title: "outro3", text: "QRS" },
{ title: "outro4", text: "TUV" }
];
const leftOverItems = outroItems.filter((item) => item.title !== "outro2");
console.log(leftOverItems);
Another option is to find the index of the item to remove and then remove it with splice
const outroItems = [
{ title: "outro1", text: "XYZ" },
{ title: "outro2", text: "ABC" },
{ title: "outro3", text: "QRS" },
{ title: "outro4", text: "TUV" }
];
const itemToDelete = outroItems.find((item) => item.title === "outro2");
const indexToDelete = outroItems.indexOf(itemToDelete);
outroItems.splice(indexToDelete, 1);
console.log(outroItems);
Combining any of the functions above with a function will prevent you from writing duplicate code.
const itemToRemove = (arr, attr, name) => {
return arr.filter((item) => item[attr] !== name);
}
const outroItems = [
{ title: "outro1", text: "XYZ" },
{ title: "outro2", text: "ABC" },
{ title: "outro3", text: "QRS" },
{ title: "outro4", text: "TUV" }
];
// Remove from "outroItems" where "title" is "outro2"
const removed2 = itemToRemove(outroItems, "title", "outro2");
// Remove from "outroItems" where "title" is "outro3"
const removed3 = itemToRemove(outroItems, "title", "outro3");
// Remove from "outroItems" where "text" is "TUV"
const removedTUV = itemToRemove(outroItems, "text", "TUV");
console.log(removed2);
console.log(removed3);
console.log(removedTUV);

How to filter JavaScript Objects with multiple criterias

I've got a JSON file with recipes.
Now there is a search bar on my homepage where the user can check and uncheck checkboxes to select which attribute of the recipe (name, ingredients, tags, category) he wants to search in. He can also check multiple criteria.
Now I want to filter my object based on the selected criteria.
I know if there is for example only checked the "name" I can just go for
recipes.filter(e -> e.name.indexOf(searchString) >= 0)
But how can I say dynamically "If also the ingredients are checked filter for a found result in the name OR in the ingredients".
I hope you understand. Thank you.
You can put all the attributes in an array and then use .some() to check if any of the attributes matches.
const recipes = [
{ name: "pizza", ingredients: "dough tomato mince", category: "meal" },
{ name: "pie", ingredients: "dough sugar", category: "dessert" },
{ name: "stew", ingredients: "potato mince onoin", category: "meal" },
{ name: "donut", ingredients: "sugar", category: "dessert" }
];
// Get these from the checkboxes:
const selected_attributes = [
"name",
"ingredients"
];
// Get this from the searchbar:
const seachbar_query = "do";
// Filter all recipes where the name or ingredients contains "do".
// We expect "pizza" and "pie', which contain dough in their ingredients.
// And we also expect "donuts", whose name starts with "do"
const filtered = recipes.filter( recipe => {
return selected_attributes.some( attribute => {
return recipe[ attribute ].includes( seachbar_query );
});
});
console.log( filtered );
filter is a higher order function that takes a predicate function that either returns true or false depending on the current item should be kept or not. In your case the function is a single one-liner, but any kind of function could be passed.
So if you have complex logic, I suggest that you move it out into a named function, where you can check for several conditions, and finally return a single boolean value:
function isRecipeValid(recipe) {
if (recipe.something) return false;
// any arbitrary conditional checking here
return recipe.conditionOne && recipe.conditionTwo;
}
recipes.filter(isRecipeValid);
write a function.
recipes.filter(e => {
if (whatever) return true;
else {
// additional checks.
}
});
in your case, I guess something like this:
recipes.filter(e => {
if (ingredientsAreChecked()) {
return e.name.matchesSearchString() || e.ingredients.some(i => i.matchesSearchString());
}
else {
return e.name.matchesSearchString();
}
});
or if ingredientsAreChecked() is not a computationally light thing to do, then do something like this:
if (ingredientsAreChecked()) return recipes.filter(e => ...);
else return recipes.filter(e => ...);
So you have two things: search term and fields to search.
You can build a filtering function that takes those two, and a single record (single recipe) and returns true or false.
Say your checkboxes are name, description, ingreedients.
What you do is you send your filter function the item name, but also the name of the fields you wanna search. Then insert the values there.
You could have something like this:
// Disclaimer: these recipes are made up
const recipes = [{
name: 'lemon cake',
description: 'a delicious cake',
ingreedients: ['lemon', 'flour', 'sugar'],
},
{
name: 'sour lemon cake',
description: 'a delicious cake',
ingreedients: ['lemon', 'flour', 'not sugar'],
},
{
name: 'choco brownie',
description: 'a sweet chocolate desert',
ingreedients: ['chocolate', 'milk', 'flour', 'salt', 'sugar'],
},
{
name: 'vanilla croissant',
description: 'a yummy pastry with vanilla flavour',
ingreedients: ['vanilla', 'milk', 'flour'],
}
];
// To search, we need the search term, and an array of fields by which to search
// We return ANY match, meaning if the search term is in any of the fields, it's a match
function searchAnyField(searchTerm, searchFields) {
return recipes.filter(item => {
for (let field of searchFields) {
if (item[field].indexOf(searchTerm) > -1) {
return true;
}
}
return false;
});
}
// the same, but we make sure the search term exists in ALL fields (seems dumb here, but could be a req)
function searchAllFields(searchTerm, searchFields) {
return recipes.filter(item => {
// A naive approach is to count in how many fields did we find the term and if it matches the search fields length
let foundInFields = 0;
for (let field of searchFields) {
if (item[field].indexOf(searchTerm) > -1) {
foundInFields++;
}
}
return foundInFields === searchFields.length;
});
}
// Let's see it in action
// we'lll just print out the names of found items
console.log('Brownie in name:', printNames(
searchAnyField('brownie', ['name'])));
console.log('Cake in name:', printNames(
searchAnyField('cake', ['name'])));
// Let's try multiple fields
console.log('Choc in name and desc:', printNames(
searchAnyField('choc', ['name', 'description'])));
console.log('flour anywhere:', printNames(
searchAnyField('flour', ['name', 'description', 'ingreedients'])));
console.log('sweet anywhere:', printNames(
searchAnyField('sweet', ['name', 'description', 'ingreedients'])));
// How about AND search:
console.log('cake in name AND desc:', printNames(
searchAllFields('cake', ['name', 'description'])));
console.log('cake everywhere:', printNames(
searchAllFields('cake', ['name', 'description', 'ingreedients'])));
function printNames(recipes) {
return recipes.map(r => r.name).join(', ');
}
Edit: You also said you have some nested props and whatnot. Here are more examples of how you could go about it.
const FIELDS = {
name: {
type: 'string',
path: 'name',
},
description: {
type: 'string',
path: 'name',
},
ingreedients: {
type: 'array',
path: 'name',
},
price: {
type: 'nested',
path: 'details.price',
nestedType: 'number',
}
}
// Disclaimer: these recipes are made up
const recipes = [{
name: 'lemon cake',
description: 'a delicious cake',
ingreedients: ['lemon', 'flour', 'sugar'],
details: {
price: 45,
}
},
{
name: 'sour lemon cake',
description: 'a delicious cake',
ingreedients: ['lemon', 'flour', 'not sugar'],
details: {
price: 45,
}
},
{
name: 'choco brownie',
description: 'a sweet chocolate desert',
ingreedients: ['chocolate', 'milk', 'flour', 'salt', 'sugar'],
details: {
price: 42,
},
},
{
name: 'vanilla croissant',
description: 'a yummy pastry with vanilla flavour',
ingreedients: ['vanilla', 'milk', 'flour'],
details: {
price: 45,
},
}
];
// To search, we need the search term, and an array of fields by which to search
// We return ANY match, meaning if the search term is in any of the fields, it's a match
function searchAnyField(searchTerm, searchFields) {
return recipes.filter(item => {
for (let field of searchFields) {
switch (field.type) {
case 'string':
if (item[field.path].indexOf(searchTerm) > -1) {
return true;
}
case 'array':
if (item[field.path].includes(searchTerm) > -1) {
return true;
}
case 'nested':
const props = field.path.split('.').reverse();
let prop = props.pop();
let val = item[prop];
while (val && props.length > 0) {
prop = props.pop();
val = val[prop]
}
if (field.nestedType === 'string') {
if (val && val.indexOf(searchTerm) > -1) {
return true;
}
} else if (field.nestedType === 'number') {
return val == searchTerm;
}
}
}
});
return false;
}
// Let's see it in action
// we'lll just print out the names of found items
console.log('42 anywhere:', printNames(
searchAnyField(42, [ FIELDS.price])));
console.log('42 anywhere:', printNames(
searchAnyField(42, [ FIELDS.price, FIELDS.name])));
function printNames(recipes) {
return recipes.map(r => r.name).join(', ');
}

Set Value of Dynamically Populated Select in Knockout

So I'm using KnockoutJS to populate a <select> with options and to get the value of the select.
<select data-bind="enable: cols1().length > 0, options: cols1(), optionsText: 'name', value: jCol1" id="col1"></select>
The variable cols1 holds objects with the simple format of { name: "name" } just because it needs to be objects for some of the other stuff I do on the page. Is there any way to set the value of the select from outside of the data-binds on this element?
The value part of the binding says:
Store a reference to an item that is in cols1 in jCol1
If you want to change the selection from outside of the UI, you'll have to set jCol1 to a value that is in the cols1 array. If you try to set it to anything else, knockout will reset it to the first value immediately. Switch out the commented lines of code in the example below to see this happen:
var ViewModel = function() {
this.options = ko.observableArray([
{ name: "Item 1" },
{ name: "Item 2" },
{ name: "Item 3" }
]);
this.selection = ko.observable();
this.selection.subscribe(function(newValue) {
console.log(newValue)
});
this.changeSelectionFromOutside = function() {
// This does not work because knockout does not do a
// deep comparison of objects
// this.selection({ name: "Item 3" });
// This _does_ work, because it references one of the
// options objects
this.selection(this.options()[2]);
}.bind(this);
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: options, value: selection, optionsText: 'name'"></select>
<button data-bind="click: changeSelectionFromOutside">
Set option 3
</button>
Now, you can also choose to just store a string ID (or other primitive) of your selection. This makes it easier to set things from the outside, because you only need the ID instead of a reference to the actual item:
var ViewModel = function() {
this.options = ko.observableArray([
{ name: "Item 1" },
{ name: "Item 2" },
{ name: "Item 3" }
]);
this.selection = ko.observable();
this.selection.subscribe(function(newValue) {
console.log(newValue)
});
this.changeSelectionFromOutside = function() {
this.selection("Item 3");
}.bind(this);
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: options, value: selection, optionsText: 'name', optionsValue: 'name'"></select>
<button data-bind="click: changeSelectionFromOutside">
Set option 3
</button>
Let's use the states example:
//list of US states in array
self.usStates = [
{ StateName: 'Alabama', Abbr: 'AL' },
{ StateName: 'Alaska', Abbr: 'AK' },
...
//observable from that array
self.States = ko.observableArray(self.usStates);
//the selected state
self.selectedState = ko.observable();
//set selectedState from some value received from server
self.selectedState(self.States.find("Abbr", { StateName: "", Abbr: '<<Value i.e. TX>>' }).Abbr);
//finds TX, sets state to 'Texas'
//find custom function used to find specific object in array
ko.observableArray.fn.find = function (prop, data) {
var valueToMatch = data[prop];
return ko.utils.arrayFirst(this(), function (item) {
return item[prop] === valueToMatch;
});
};
This may be overly complicated for what you're looking to do, but this is how I do it when I want to choose a value from a select based on a value from the record in the database.

Populating arrays after their definition

I have a ul containing li's which contain names of different recipe ingredients for a recipe page. I'm trying to get those ingredients and store them into a JavaScript array within an object. I already know the title of the recipe so I put that right into the object property title, but I don't know how many ingredients there will be for each recipe. Here is what I have:
var recipeobj = {
title: $('h3.title').val(),
ingredients: [
ingredient,
optional
]
}
$.each($('ul.ingredients > li > h4'), function (index, ingredient) {
recipeobj.ingredients[index].ingredient = $(ingredient).html();
recipeobj.ingredients[index].optional = false;
})
If I try to do console.log(recipeobj.ingredients) I just get the error Uncaught ReferenceError: ingredient is not defined
No doubt this is simple, I just rarely need to use arrays in JavaScript so have little experience with them.
Open your console and run it
var recipeobj = {
title: $('h3.title').html(),
// ingredients is empty for now
ingredients: []
};
$.each($('ul.ingredients > li > h4'), function(index, ingredient) {
// Get the name
var name = $(ingredient).html(),
// Find out if it is 'optional'(using a class here)
optional = $(ingredient).hasClass('optional');
// Push a new ingredient into the array
recipeobj.ingredients.push({ name: name, optional: optional });
});
console.log(recipeobj);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3 class="title">Pork and beans</h3>
<ul class="ingredients">
<li>
<h4>Pork</h4>
</li>
<li>
<h4>Beans</h4>
</li>
<li>
<h4 class="optional">Salt*</h4>
</li>
</ul>
This should output:
{
"title": "Pork and beans",
"ingredients": [
{ name : "Pork", optional : false },
{ name : "Beans", optional : false },
{ name : "Salt*", optional : true}
]
}
var rObj = {
title: $('h3.title').val(),
ingredients: [
'source cream',
'cheese',
'chopped meat'
],
optional: true
};
accessing
var rItem = rObj.ingredients[1];
or you want
var rObj = {
title: $('h3.title').val(),
ingredients: {
ingredient_list: ['one','two','three'],
optional: true
}
};
accessing
var rItem = rObj.ingredients.ingredient_list[1];
The structure you are attempting to use looks like the structure should be like
var rObj = {
title: $('h3.title').val(),
things: [{
ingredient: 'source cream',
optional: true
},
{
ingredient: 'cheese',
optional: false
}]
};
accessing
var ingred = rObj.things[1].ingredient;
var rObj = {
title: $('h3.title').val(),
ingredients : []
};
you can add ingredients:
$.each($('ul.ingredients > li > h4'), function (index, ingredient) {
rObj.ingredients.push({ingredient: $(ingredient).html(), optional :false})
})

dynamic columnDef in ng-Grid

I want to assign ng-grid columns name dynamically after value returned from database, but issue is that it get initialized before data return from ajax, and i am not able to recall gridOption so it showing balnk, so please help me that how can we construct a column name by ajax return value.
$scope.gridOptions =
{
data: 'data.Values',
columnDefs:
[
{ field: "ID", displayName: "Record Id" },
{ field: "Value", displayName: $scope.ColumnName, cellFilter: cellfilterType },
],
};
where $scope.ColumnName coming from below line...
RecordService.getRecords().then(function (data) {
$scope.ColumnName= data.something;
}
Thanks
Thanks Max for your help, I have done this with of help columnDef as below:
Step 1:
$scope.colDef = [];
Step 2:
RecordService.getRecords().then(function (data){
$scope.colDef=["ColumnName":data.something]
}
Step 3:
$scope.gridOptions = {
data: 'data.UdiValues',
columnDefs:'colDef',
filterOptions: $scope.filterOptions
};
Try to set first "default" value and after change it with promise
$scope.gridOptions =
{
data: 'data.Values',
columnDefs:
[
{
field: "ID",
displayName: "Record Id"
},
{ field: "Value",
displayName: "default",
cellFilter: cellfilterType
},
]
};
And now:
RecordService.getRecords().then(function (data) {
$scope.gridOptions.columnDefs[1].displayName = data.something;
}
The Service RecordService returns promise therefore we create promise factory like:
.factory('RecordService', ['$resource','$q', function($resource, $q) {
var data = { something: "from service" } ;
var factory = {
getRecords: function (selectedSubject) {
var deferred = $q.defer();
deferred.resolve(data);
return deferred.promise;
}
}
return factory;
}]);
Demo Fiddle
Something like this
Return json, play around, use GET mapping to strings etc?
i have done something like this :-
self.gridOptions.columnDefs = columnDefs(colDef,displayNames);
columnDef is :-
var columnDefs = function(data,cd){
var colDef= [];
var mi = null;
var colByOrder = sortedByOrder(data);
for(var i=0 ; i < colByOrder.length ; i++){
colDef.push({
width: width,
field: String(colByOrder[i][1].position),
menuItems: menuItems(this),
displayName: cd[colByOrder[i][1].position],
enableSorting: false,
type: 'string',
});
}
return colDef;
};

Categories

Resources