I am trying to filter my JSON object by a specific property value set to Log: true
If an object has this property set to false, I want to filter it out. Here is an example of the JSON structure:
$scope.Main =
{
"MyBook" :
{
"Title": "The Road",
"Type" : "Text",
"Log" : false
},
"MyCat":
{
"Name" : "Penny",
"Type" : "Pet",
"Log" : true
},
"Car":
{
"Make" : "Toyota",
"Model" : "Camry",
"Type" : "Vehicle",
"Log" : false
}
}
As you can see, the objects themselves are not similar, but they all contains a log property.
Online Demo
This is how I would filtered an object while searching for a property value equals true
var sampleObj = {/* you sample object*/};
var filtered = Object.keys(sampleObj).reduce(function(arr,prop){
if(Object.keys(sampleObj[prop])
.filter(function (p) {return p === "Log";})){
if(sampleObj[prop].Log==true){
arr.push(sampleObj[prop]);
}
}
return arr;
},[]);
console.log(filtered);
Since you are using angular probably you would want to use a custom filter instead:
Something close to:
custom filter:
angular.module('myApp', []).filter('myFilter', function() {
return function(sampleObj, param1) {
return Object.keys(sampleObj).reduce(function(arr,prop){
if(Object.keys(sampleObj[prop])
.filter(function (p) {return p === "Log";})){
if(sampleObj[prop].Log==param1){
arr.push(sampleObj[prop]);
}
}
return arr;
},[]);
};
});
and in your html
<li ng-repeat="item in sampleObj | myFilter: true">
try using the underscorejs library.
you can use some of their functions like _.filter and _.has to filter the list.
here's an example of how i would try to implement that object:
var filtered = _.filter($scope.Main, function(obj) {
return _.has(obj, "Log") && obj.Log;
}
Use a custom Angular filter:
.filter('filterLog', function(){
return function(items){
for (var item in items) {
if (items[item].Log === false) delete items[item];
}
return items;
}
})
Then, in your view, you could output the filtered list like so:
<li ng-repeat="(key, value) in Main | filterLog">{{value}}</li>
If you need to use it in a controller, you could:
$scope.filtered = $filter('filterLog')($scope.Main);
Demo
Related
I have one json file which contains multiple objects inside another object. I want to fetch data but not using key of that value. I want to iterate there key and values and want to print them dynamically in angular 6.
{
"name" : "abc",
"tags" : "def",
"updated-by" : "ijk",
"property" : {
"description" : "abcd",
"type" : "string"
},
"sources" : {
"input" : {
"type" : "lmn",
"properties" : {
"key" : "opq"
}
}
}
}
Can we iterate objects like we iterates array. If anyone can help?
I would suggest referring this StackOverflow question,
As far as I know, *ngFor can be used not only for arrays but also for Objects.
Hope the above link helps.
Also for the keys whose values contain objects, you could check if the corresponding value of the key is an object.
For instance,
if( (typeof A === "object") && (A !== null) )
where A is the corresponding value of the key. If A is indeed an object, use *ngFor again to iterate over the object.
I haven't tested the following code but I hope you get an overview of what I am trying to say,
#Component({
selector: 'app-myview',
template:
`<div *ngFor="let key of objectKeys(items)">{{key + ' : ' + items[key]}}
<div *ngIf="checkFunction(items[key])">
<div *ngFor="let key2 of objectKeys(items[key])">
{{key2 + ' :' + items[key][key2]}}
</div>
</div>
</div>`
})
export class MyComponent {
objectKeys = Object.keys;
items = { keyOne: 'value 1', keyTwo: 'value 2', keyThree: 'value 3' };
constructor(){}
checkFunction(obj){
if( (typeof obj === "object") && (obj !== null) )
{
return true;
}
else{
return false;
}
}
}
var temp = {
"name" : "abc",
"tags" : "def",
"updated-by" : "ijk",
"property" : {
"description" : "abcd",
"type" : "string"
},
"sources" : {
"input" : {
"type" : "lmn",
"properties" : {
"key" : "opq"
}
}
}
};
flatten the object
var flattenObject = function(ob) {
var toReturn = {};
for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue;
if ((typeof ob[i]) == 'object') {
var flatObject = flattenObject(ob[i]);
for (var x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = ob[i];
}
}
return toReturn;
};
var flat = flattenObject(temp)
Iterate over object just like array
Object.entries(flat).forEach(entry => {
console.log(entry[0] + " => " + entry[1])
})
UPDATE
As developer003 suggested, I did this:
It works, but not at all. If I add an other property as c.details.author (string[]) or c.details.index (number), it doesn't work, and the function doesn't return nothing (error).
Here an extract of my JSON database:
[
{
"index": 1,
"name": "ad dolor ipsum quis",
"details": {
"author": ["Wallace Stephens", "Steve Ballmer"],
"game": {
"short": "tdp",
"name": "Thief: The Dark Project"
},
"newdark": {
"required": true,
"version": "1.20"
},
"firstreleasedate": "2007/04/27",
"lastupdatedate": "2017/01/28"
}
}
]
So I can look for another details properties than strings. Any idea?
ORIGINAL POST
I created a function which, when I call it as a (keyup) event, filters an HTML datatable when I type something in an input.
return c.name.toLowerCase().indexOf(input.target.value.toLowerCase()) != -1;
I want to be able to filter, not only by name, but also by details.author, details.game.name, details.firstrelease... etc.
How can I change c.name to apply these properties? Do I need to create a loop? Should I use .map()?
Right now I can think in 2 approaches:
1st:
º Create a function to parse (toLowerCase()) the property value and handle if it contains the value or not:
containsVal(property, value) {
return property.toLowerCase().indexOf(value) !== -1;
}
filterFunc(c) {
return this.containsVal(c.name, VALUE_TO_SEARCH) ||
c.details && (
this.containsVal(c.details.author, VALUE_TO_SEARCH) ||
this.containsVal(c.details.firstrelease, VALUE_TO_SEARCH) ||
(c.details.game && this.containsVal(c.details.game.name, VALUE_TO_SEARCH))
);
}
2nd:
º Map the only needed properties and filter them.
arr.map(item => {
return {
name: item.name,
author_details: item.details && item.details.author,
firstrelease_details: item.details && item.details.firstrelease,
game_name: item.details && item.details.game && item.details.game.name
};
}).filter(item => {
return Object.keys(item).some(key => {
const value = item[key];
return value && value.toLowerCase().indexOf(VALUE_TO_SEARCH) !== -1;
});
});
I'm retrieving the following structure from Firebase:
"bills" : {
"1" : { // the customer id
"orders" : {
"-KVMs10xKfNdh_vLLj_k" : [ { // auto generated
"products" : [ {
"amount" : 3,
"name" : "Cappuccino",
"price" : 2.6
} ],
"time" : "00:15:14"
} ]
}
}
}
I'm looking for a way to process this with Aurelia. I've written a value converter that allows my repeat.for to loop the object keys of orders, sending each order to an order-details component. The problem is, this doesn't pass the key, which I need for deleting a certain order ("-KVMs10xKfNdh_vLLj_k")
Should I loop over each order and add the key as an attribute myself?
Is there a better/faster way?
This answer might be a little late (sorry OP), but for anyone else looking for a solution you can convert the snapshot to an array that you can iterate in your Aurelia views using a repeat.for, for example.
This is a function that I use in all of my Aurelia + Firebase applications:
export const snapshotToArray = (snapshot) => {
const returnArr = [];
snapshot.forEach((childSnapshot) => {
const item = childSnapshot.val();
item.uid = childSnapshot.key;
returnArr.push(item);
});
return returnArr;
};
You would use it like this:
firebase.database().ref(`/bills`)
.once('value')
.then((snapshot) => {
const arr = snapshotToArray(snapshot);
});
I have a select that looks like this
<select
class="form-control"
ng-model="vm.transaction.location_from"
ng-options="l.name for l in vm.locations">
</select>
with vm.locations sourcing from the following JSON:
[
{
"id": "c0d916d7-caea-42f9-a87f-a3a1f318f35e",
"name": "Location 1"
},
{
"id": "d8a299a3-7f4b-4d32-884f-efe25af3b4d2",
"name": "Location 2"
}
]
Further, I have another select that looks like:
<select
class="form-control"
ng-model="vm.transaction.item"
ng-options="i.name for i in vm.items">
</select>
with vm.items sourcing from the following JSON:
[
{
"id": "9f582e58-45dd-4341-97a6-82fe637d769e",
"name": "20oz Soft Drink Cup",
"locations": [
{
"inventory_id": "9d5aa667-4a64-4317-a890-9b9291799b11",
"location_id": "c0d916d7-caea-42f9-a87f-a3a1f318f35e"
},
{
"inventory_id": "9d5aa667-4a64-4317-a890-9b9291799b11",
"location_id": "d8a299a3-7f4b-4d32-884f-efe25af3b4d2"
}
],
}
]
I want to, on change of the ng-mode="vm.transaction.item" select, have the ng-model="vm.transaction.location_from" be filtered to only show values that match from the locations array. I know I can use a | filter: { }, but I'm not sure what that filter should look like.
Hope this is your expected results.
Below are two options I tried ... demo | http://embed.plnkr.co/689OQztgu8F800YjBB2L/
Ref : underscorejs | angular-filter | everything-about-custom-filters-in-angular-js
// 1. filter items collection by location
angular.module('demo').filter('withLocation', function () {
return function (items, selectedLocation) {
function isLocationInLocations (elem) { return selectedLocation && elem.location_id === selectedLocation.id; }
function itemHasLocation (elm){ return (elm.locations && elm.locations.filter(isLocationInLocations).length > 0); }
return items.filter(itemHasLocation);
}});
// 2. filter function to check if option can be rendered ....
vm._filters.selectableItems = function(selectedLocation) {
return function(item) {
var locationsHasLocation = function(elem) { return selectedLocation && elem.location_id === selectedLocation.id; }
return (item.locations && item.locations.filter(locationsHasLocation).length > 0);
}
}
var app = angular.module("Test", []);
app.controller("Ctrl1", function($scope) {
$scope.location_fromArr =
[{
"id": "9f582e58-45dd-4341-97a6-82fe637d769e",
"name": "20oz Soft Drink Cup",
"locations": [{
"inventory_id": "9d5aa667-4a64-4317-a890-9b9291799b11",
"location_id": "c0d916d7-caea-42f9-a87f-a3a1f318f35e"
},{
"inventory_id": "9d5aa667-4a64-4317-a890-9b9291799b11",
"location_id": "d8a299a3-7f4b-4d32-884f-efe25af3b4d2"
}],
}];
$scope.itemArr =
[{
"id": "c0d916d7-caea-42f9-a87f-a3a1f318f35e",
"name": "Location 1"
},{
"id": "d8a299a3-7f4b-4d32-884f-efe25af3b4d2",
"name": "Location 2"
}];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="Test" ng-controller="Ctrl1">
Item
<select
class="form-control"
ng-model="item"
ng-options="i.name for i in itemArr">
</select>
Location
<select
class="form-control"
ng-model="location_from"
ng-options="l.name for l in location_fromArr | filter:{l.id: location_from.location_id}">
</select>
</div>
One way to do this is to supply a filter function to filter the locations. Something like:
vm.filterFun = function(selectedLocations) {
return function (location) {
var n;
if (!selectedLocations) {
return true;
}
for(n=0;n<selectedLocations.length;n += 1) {
if (selectedLocations[n].location_id === location.id) {
return true;
}
}
return false;
}
}
This is actually a function returning a filter function, based on the item selected.
Then in your select you apply the filter with:
<select
class="form-control"
ng-model="vm.transaction.location_from"
ng-options="l as l.name for l in vm.locations | filter:vm.filterFun(vm.transaction.item.locations)">
</select>
See plunker here.
I would forego angular filters and use the getterSetter option of ngModelOptions.
It could look something like this:
var selectedItem, selectedLocation;
var items = [];
var locations = [];
vm._items = items; // Static, always allow all items to be selected.
vm.locations = function () {
// Return differing results based on selectedItem.locations.
};
vm._transaction = {
location: function (v) {
/**
* If v is null, it is not present in the selectedItem.locations array.
* The extra check will ensure that we don't persist a filtered out location when
* selecting another item.
*/
return (v || v === null) ? (selectedLocation = v) : selectedLocation;
},
item: function (v) {
return v ? (selectedItem = v) : selectedItem;
}
};
Here's a plunker demonstrating the behaviour.
Not as simple/straight-forward as a filter, but I would bet (at least in the case of a piped filter) that you'd possibly see a slight performance gain going with this approach.
I do not have numbers to back up the above statement, and it usually boils down to the size of your dataset anyway. Grain of salt.
If you need it to function the other way around, you could write up a secondary filter like such:
function superFilter2 (arr) {
// If no location is selected, we can safely return the entire set.
if (!selectedLocation) {
return arr;
}
// Grab the current location ID.
var id = selectedLocation.id;
// Return the items that are present in the selected location.
return arr.filter(function (item) {
return item.locations.map(function (l) {
return l.location_id;
}).indexOf(id);
});
}
With that and the filter in the supplied plunker, there are some similarities that could be moved into higher order functions. Eventually with some functional sauce you could probably end up with a single god function that would work both ways.
you can do this:
<select
class="form-control"
ng-model="vm.transaction.item"
ng-change="itemCahngedFn()"
ng-options="i.name for i in vm.items">
</select>
var itemChangedFn = function(){
var filtredItems = [];
angular.forEach(vm.locations, function(item){
if(item.name == vm.transaction.item){
filtredItems .push(item.location);
}
});
vm.locations= filtredItems ;
}
i think filter:{ id : item.locations[0].location_id } should do the trick.
here is the jsfiddle
how do you think?
I have a sap.ui.table.TreeTable with several columns, one of which contains a custom control defined as a template for the column.
The TreeTable renders correctly, and the column with the custom control also renders and functions correctly ... that is until I expand an unexpanded node.
When I expand a node the Renderer throws an error
Error: adding element with duplicate id '__link0-col1-row0',
which is the column associated with the custom control.
I can workaround the problem by adding attachToggleOpenState event handler which destroys this columns template and adds it again. Expensive but it works!
this.oTable = new sap.ui.table.TreeTable("myTable", {
columns : [ new sap.ui.table.Column( {
label : "Query",
template : new sparqlish.control.queryClause({
clausePath : {
path : "viewModel>path"
}
}),
visible : true,
width : "500px"
}), ],
}).attachToggleOpenState(function(oEvent) {
// TODO workaround as expanding Tree causes duplicate id
var queryColumn = oEvent.getSource().getColumns()[0];
queryColumn.destroyTemplate();
queryColumn.setTemplate(new sparqlish.control.queryClause({
clausePath : {
path : "viewModel>path"
}
}))
});
sparqlish.control.queryClause is the custom control.
I am using openui5 version 1.28.15
Any other, less expensive, suggestions?
The custom control takes this form, however this control invokes other controls and so on.
jQuery.sap.require("sparqlish.control.conceptClause");
jQuery.sap.require("sparqlish.control.propertyClause");
jQuery.sap.require("sparqlish.control.conjunctionPropertyClause");
sap.ui.core.Control.extend("sparqlish.control.queryClause", {
metadata : {
properties : {
clausePath : {
type : "string"
}
},
events : {},
aggregations : {
_conceptClause : {
type : "sparqlish.control.conceptClause",
multiple : false
},
_propertyClause : {
type : "sparqlish.control.propertyClause",
multiple : false
},
_conjunctionPropertyClause : {
type : "sparqlish.control.conjunctionPropertyClause",
multiple : false
}
}
},
init : function() {
var self = this;
self.setAggregation("_conceptClause", new sparqlish.control.conceptClause());
self.setAggregation("_propertyClause", new sparqlish.control.propertyClause());
self.setAggregation("_conjunctionPropertyClause", new sparqlish.control.conjunctionPropertyClause());
},
renderer : function(oRm, oControl) {
if (oControl.getClausePath() != undefined) {
// TODO no point if path not yet defined
oRm.write("<div ");
oRm.writeControlData(oControl);
oRm.writeClasses();
oRm.write(">");
var currentModel = oControl.getModel("queryModel");
// Set binding context, rather than just the binding path etc, as this seems essential for satisfactory binding of
// aggregations
oControl.setBindingContext(new sap.ui.model.Context(oQueryModel, oControl.getClausePath()), "queryModel")
var currentCtx = oControl.getBindingContext("queryModel");
var currentContext = oControl.getModel("queryModel").getProperty("", currentCtx);
if (currentContext != undefined) {
var sClass = currentContext._class;
if (sClass == "Query") {
oControl.setAggregation("_conceptClause", new sparqlish.control.conceptClause().setBindingContext(oControl.getBindingContext("queryModel")));
oRm.renderControl(oControl.getAggregation("_conceptClause"));
} else if (sClass == "Clause") {
oControl.setAggregation("_propertyClause", new sparqlish.control.propertyClause().setBindingContext(oControl.getBindingContext("queryModel")));
oRm.renderControl(oControl.getAggregation("_propertyClause"));
} else if (sClass == "ConjunctionClause") {
oControl.setAggregation("_conjunctionPropertyClause", new sparqlish.control.conjunctionPropertyClause().setBindingContext(oControl
.getBindingContext("queryModel")));
oRm.renderControl(oControl.getAggregation("_conjunctionPropertyClause"));
} else {
jQuery.sap.log.fatal("Incorrect class of provided query clause");
}
}
oRm.write("</div>");
} else {
jQuery.sap.log.fatal("clausePath not defined");
}
}
});
An example of one of the dependent controls is below.
jQuery.sap.require("sparqlish.control.conceptMenu");
jQuery.sap.require("sparqlish.control.conceptFilters");
jQuery.sap.require("sparqlish.control.addClause");
sap.ui.core.Control.extend("sparqlish.control.conceptClause", {
metadata : {
properties : {
concept : "object"
},
events : {},
aggregations : {
_concept : {
type : "sparqlish.control.conceptMenu",
multiple : false
},
_conceptFilters : {
type : "sparqlish.control.conceptFilters",
multiple : false
},
_addClause : {
type : "sparqlish.control.addClause",
multiple : false
}
}
},
init : function() {
var self = this;
var conceptSelect =function(oEvent){
var currentModel = self.getModel("queryModel");
var currentContext = self.getBindingContext("queryModel");
var currentModelData = currentModel.getProperty("", currentContext);
currentModelData.conceptFilters = [];
currentModelData.clauses = {};
var sConcept = oEvent.getParameter("concept");
var oMetaModel = self.getModel("metaModel");
var oConcept=oMetaModel.getODataEntitySet(sConcept);
self.setConcept( oConcept);
self.oEntityTypeModel = new sap.ui.model.json.JSONModel();
self.oEntityTypeModel.setData(oMetaModel.getODataEntityType(oConcept.entityType));
self.setModel(self.oEntityTypeModel, "entityTypeModel");
self.getAggregation("_conceptFilters").setModel(self.oEntityTypeModel, "entityTypeModel");
self.getAggregation("_conceptFilters").getAggregation("_extendFilter").setVisible(true);
currentModel.refresh();
self.rerender();
};
self.setAggregation("_concept", new sparqlish.control.conceptMenu({
selected : function(oEvent) {
self.getAggregation("_conceptFilters").getAggregation("_extendFilter").setVisible(true);
},
changed : conceptSelect
}).bindElement("queryModel>"));
self.setAggregation("_conceptFilters", new sparqlish.control.conceptFilters().bindElement("queryModel>"));
self.setAggregation("_addClause", new sparqlish.control.addClause({
pressed : function(oEvent) {
alert("concept");
}
}).bindElement("queryModel>"));
},
renderer : function(oRm, oControl) {
oRm.addClass("conceptClause");
oRm.write("<div ");
oRm.writeControlData(oControl);
oRm.writeClasses();
oRm.write(">");
oRm.write(sap.ui.getCore().getModel("i18nModel").getProperty("conceptClauseFind"));
oRm.renderControl(oControl.getAggregation("_concept"));
oRm.renderControl(oControl.getAggregation("_conceptFilters"));
oRm.write(" ");
oRm.renderControl(oControl.getAggregation("_addClause"));
oRm.write("</div>");
}
});