Exact Filtering an Array - javascript

I need help on filtering an already filtered list.
Scenario is I have a list (li item with various classes) of products that is being filtered by an ID and that ID is being pushed into an array. E.g.
var $filtersSelected = [];
Using this code, I can successfully filter the list.
var $productItems = $('ul').find('li');
$.each($productItems, function() {
var $self = $(this),
// Store classes of each product <li> item
$classes = $self.attr('class').split(/\s+/),
$match = false;
// Check if any classes of each <li> matches those in '$filtersSelected' array
$.each($classes, function(i, c) {
if ($.inArray(c, $filtersSelected) > -1) {
$match = true;
}
return $classes;
});
// Do something
if ($match) {
$self.show();
} else {
$self.hide();
}
});
Every time I click a new filter ID, the array gets appended and any item whose classes matches those n the array gets shown. However, what I need is to be able to iterate through the already filtered items.
If I already have this items... $filtersSelected = ["blue"]
["blue", "circle", "number"]
["blue", "square", "number"]
["blue", "triangle", "letter"]
and click on "number". $filtersSelected = ["blue", "number"] I only need these items to show until it gets the exact match.
["blue", "circle", "number"]
["blue", "square", "number"]
Thanks in advance.
Here's a snippet of code and how it currently works. http://jsfiddle.net/athanph/ebLyV/24/
You'll see that I got 3 sets of filter. What I want is to filter exact items based on the filters chosen. If I click "Blue" all blue items will be shown, if I click "Circle" as my second filter, I only want to show blue and circle items, and so on...

I think what you are trying to do is this:
$('ul').find('li').filter(function(){
return this.className.indexOf($filtersSelected) > -1;
}).hide();
The other elements I suppose are already visible (i.e show()).
In above I am using some javascript code because I am not that much good with jquery. In short, I used what I know :).

If I understand well what you want, this is the code:
$(function () {
var filtersSelected = [];
$('#btAddToFilter').click(function () {
var x = '.' + $('#txtAddToFilter').val();
if ($.inArray(x, filtersSelected) < 0) {
filtersSelected.push(x);
}
$('#ulProductItems').find('li').hide();
$(filtersSelected.join(', ')).show()
});
});
http://jsfiddle.net/csicky/NKFTL/

Related

Separate strings in an array in a certain order

I have a list that I took from a converted CHANGELOG.md file, and it looks like this:
["[3.0.0]","Features", "changes done in file","[2.0.1]", "Bug Fixes", "fixed login"]
What I want to do is to separate each version into its own list, like this:
["[3.0.0]", "Features", "changes done in file"],
["[2.0.1]", "Bug Fixes", "fixed login"]
Obviously, because it's a changelog, there can be multiple features and multiple bugfixes in a single version, so I want to a piece of code that separates the code appropriately.
I tried using if (string.startsWith('[')) but i couldn't manage to fit it in a loop.
Any help is appreciated.
Here's something I came up with. The code basically loops through the input array and adds each string to a currentArray variable. Everytime it hits a [ it puts the currentArray into the output and clears currentArray. At the end it removes the first element as the first element of the output will always be an empty array (since the first element of the input starts with a [)
var input = ["[3.0.0]","Features", "changes done in file","[2.0.1]", "Bug Fixes", "fixed login"];
var output = [];
var currentArray = [];
for (var i = 0; i < input.length; i++) {
if (input[i].charAt(0) == '[') {
output.push(currentArray);
currentArray = [];
}
currentArray.push(input[i]);
}
output.push(currentArray);
currentArray = [];
//Since it will take the first one, and put empty one, need to do last step.
output.splice(0, 1);
console.log(output);
// ["[3.0.0]", "Features", "changes done in file"],
// ["[2.0.1]", "Bug Fixes", "fixed login"]
Assuming that you're always working in sets of three, this is a quick and ugly approach
var data = ["[3.0.0]","Features", "changes done in file","[2.0.1]", "Bug Fixes", "fixed login"],
items = [];
data.map( (el, idx) => {
var last = items.length;
if( idx % 3 === 0 ) {
items.push( [] );
last += 1;
}
last = items[ last - 1 ];
last.push( el );
} );
console.log( JSON.stringify( items ) );
Here's an alternative solution should you prefer it:
const arr = ["[3.0.0]","Features", "changes done in file","[2.0.1]", "Bug Fixes", "fixed login"];
const newArr = [];
let tempArr = [];
arr.forEach(function(v, i) {
if(/^\[\d+.\d+.\d\]$/.test(v) && i > 0) {
newArr.push(tempArr);
tempArr = [v];
} else {
tempArr.push(v)
}
});
newArr.push(tempArr);
console.log(newArr);
This snippet loops through the items one-by-one. It uses two arrays, one to hold the final result and one to populate with items for the current version.
I am using a regex to check if the item contains one [ followed by a number, then a period, number, period, number and finally the trailing ]. This allows the other strings that are not version tags to contain that character.
If the current item is a version tag, we push tempArr (which contains the changes of the current version that we've previously filled in our loop) to our result array newArr. Then, we empty the tempArr and give it the starting value of the next version tag.
If it is not, we just push the current item to our temporary array.
It would be interesting to know if you were guaranteed to get this data in triplets, as your example seems to imply. If you knew this up front, there are many creative solutions that could emerge. For just creating a 2D Array, however, I like this approach (you can run this directly in node.js to try it out):
const original = ['[3.0.0]', 'Features', 'changes done in file', '[2.0.1]', 'Bug Fixes', 'fixed login']
function transformToChangeLog (originalArray) {
const changeLog = originalArray.reduce((newList, element) => {
element.charAt(0) === '[' // check for version string
? newList.push([element]) // If version string, then push a new Array containing that string
: newList[newList.length - 1].push(element) // If something else, tack it onto the last Array in the changelog list
return newList // whatever is returned in the reduce function is passed to the next iteration, allowing us to build this 2D array one element at a time.
}, [])
return changeLog
}
console.log(transformToChangeLog(original))
I hope that helps! I like the reduce Array method, because of it's versatility and succinctness.

UI5 remove duplicate feed list items

I have a Feed List for posting comments in my UI5 xml view
<layout:content>
<m:FeedInput post="onFeedPost" class="sapUiSmallMarginTopBottom"/>
<m:List id="feedList" showSeparators="Inner" items="{path: '/table', sorter: {path: 'DATE', descending: true}}">
<m:FeedListItem sender="{MEMBERID}" timestamp="{DATE}" text="{COMMENT}" convertLinksToAnchorTags="All"/>
</m:List>
</layout:content>
I want to not display duplicate comments that have the same text and date, but keep them in the database. My idea was to in the controller iterate over over the items to do this, but I am not sure what to do with the resulting array
var results = [];
var comments = feed.getItems();
for (var n = 0; n < comments.length - 1; n++) {
var contained = false;
for (var m = n + 1; m < comments.length; m++) {
if (comments[n].getText() === comments[m].getText() &&
comments[n].getDate() === comments[m].getDate()) {
comments.pop(m);
contained = true;
if (!results.includes(comments[n])) {
results.push(comments[n]);
}
}
}
if (!contained && !results.includes(comments[n])) {
results.push(comments[n]);
}
}
// replace list items with results array
I can't figure out how to replace the feed list's items with the new array as there is a getItems function but not a setItems function. It occurs to me there is probably a simpler more idiomatic UI5 way to do this but I haven't found it yet.
First off, the correct way to handle this situation is in the OData service. The service should remove the duplicates before sending the data to the client. If we assume, however, that you can't do this server side, then you have some options.
1.) Do not bind the list items to anything. Instead, use the ODataModel to read the data, then filter out duplicates, create a new list item and add it to the list
Read the data using the ODataModel, then pass the results to a method that will filter and add them items to the list
oModel.read("/EntitySet", {
success: function(oResponse) {
this._addCommentsToList(oResponse.results)
}.bind(this)
})
In your method to handle the results, you'll need to do three things -- create a new FeedListItem, set the binding context of the list item, and then add the list item to the list
var aDistinctComments = //use your logic to filter out duplicates
aDistinctComments.forEach(function(oComment) {
//to set the binding context, you'll need the entity key/path
var sCommentKey = oModel.createKey("/EntitySet", oComment)
//create a new binding context
var oContext = oModel.createBindingContext(sCommentKey)
//create a new FeedListItem
var oItem = new FeedListItem({
sender: "{MemberId}",
...
});
//set the context of the item and add it to the list
oItem.setBindingContext(oContext);
oList.addItem(oItem);
})
2.) Bind the list directly to the OData entity set and then when the list receives the data, iterate over the items and hide the duplicates
<List items="{/EntitySet}" updateFinished="onListUpdateFinished"....>
----- onListUpdateFinished ---
var aItems = oList.getItems();
for (var m = n + 1; m < aItems.length; m++) {
//set a boolean, true if duplicate
var bDuplicate = aItems[m].getText() ==== aItems[n].getText() &&
aItems[m].getDate() === aItems[n].getDate();
//set the visibility of the item to true if it is not a duplicate
aItems[m].setVisible(!bDuplicate)
}
3.) Read the data manually, remove duplicates, and stash it in a JSON model, and bind the table to your JSON model path
oModel.read("/EntitySet", {
success: function(oResponse) {
this._addCommentsToJSONModel(oResponse.results)
}.bind(this)
})
You can stash an array of objects in your JSON model, and then bind the table items to that path
var aDistinctComments = // your logic to get distinct comments
oJSONModel.setProperty("/comments", aDistinctComments)
oList.setModel(oJSONModel);
-----
<List items="{/comments"}....>
4.) Bind your list items to your entity set, iterate over the items, and then remove duplicates from the list. I don't recommend this approach. Removing items manually from lists bound to an entity set can lead to trouble with duplicate IDs.
var oItem = //use your logic to find a duplicate list item
oList.removeItem(oItem)
I recommend first handling this server side in the OData service, and if that's not an option, then use option 1 above. This will give you the desired results and maintain the binding context of your list items. Options 2 and 3 will get you the desired results, but depending on your applicaiton, may make working with the list more difficult.
Here is one approach :
Do not directly bind the list to your oData.
You can create a JSON model which will be the resulting model after removing duplicate items.
Bind the JSON model to the List as such:
var oList = this.getView().byId("feedList");
oList.bindAggregation("items", "pathToJsonArray", template);
(The template is feedlistitem in this case).

How to add custom rules to a calculator?

I'm making a outfit randomizer. But I would like to add some rules to it to prevent weird outfits like a white tie on a white shirt. Or any tie on a graphic tee. Or wearing a turtleneck over a shirt.
This is the code, so far:
var shirts = ["White", "navy", "light blue", "gray"];
var pants = ["black", "navy", "gray"];
var ties = ["red and blue squares", "purple", "white", "red"];
var random_shirt = shirts[Math.floor(Math.random()*shirts.length)];
var random_pants = pants[Math.floor(Math.random()*pants.length)];
var random_tie = ties[Math.floor(Math.random()*ties.length)];
document.write( " shirt: " + random_shirt + " pants: " + random_pants + " tie: " + random_tie);
I know it's done with if's and else's, but I don't know how.
Please forgive my JS illiteracy. I learned it but never actually used it. Until now.
Thanks
There are severals ways of doing this, this is my suggestion:
You can filter the pants array based on the result of random shirt
var random_shirt = [random logic];
/*
This will iterate over your pants array, returning a filtered array
with containing the items that returned true
item: the actual item
index: index of the actual item
array: original array
*/
filtered_pants = pants.filter(function(item, index, array) {
if (item == random_shirt) {
// This item won't be in the filtered array
return false;
}
if ([another custom rule]) {
return false;
}
/*
After passing all the rules return true to include this item in
the filtered array
*/
return true;
});
// Now shuffle over the filtered array
var random_pants = filtered_pants[Math.floor(Math.random()*pants.length)];
Then just repeat it with the tie
Make sure to learn the documentation for the filter method ->
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Alternatively you can use the reduce method which is similar -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
If you don't quite understand these methods, watch this playlist, it'll help a lot -> https://www.youtube.com/watch?v=BMUiFMZr7vk&list=PL0zVEGEvSaeEd9hlmCXrk5yUyqUag-n84

Joining an array on another array's value

I am using mongodb and am building an application with two collections: users and items.
I'd like for users to be able to save items that they like and go to their saved items page where the items would be displayed in the reverse order that they were saved (last saved appears first).
At the moment, I am adding every saved item's id along with the savedDate to the user's profile savedItems array like so:
user profile
{
savedItems: [{id: "", savedDate: ""}, {id: "", savedDate: ""}, etc...]
}
Whenever I need to retrieve those items, I query the items collection for objects with ids $in this array. Like this:
var items = Items.find({_id: {$in: _.pluck(user.savedItems, 'id')}});
The problem here is that I can't order those items according to the addedDate.
So is it possible to map the addedDate onto every object of the retrieved items?
(Something like: if retrievedItems.id == savedItems.id, then push the addedDate there.)
Which would result in an items array like this:
[
{id:"", itemName:"", price: "", [...], savedDate: ""},
{id:"", itemName:"", price: "", [...], savedDate: ""},
[...]
]
Thank you.
JSFIDDLE
I created two function to help you do it:
function mergeDates(listDates, listItems) {
for (var i = 0; i < listDates.length; i++) {
var index = findListIndex(listItems, listDates[i].id);
if (index != null) listItems[index].savedDate = listDates[i].savedDate;
}
return listItems;
}
function findListIndex(listItems, id) {
for (var i = 0; i < listItems.length; i++) {
if (listItems[i].id == id) return i;
}
return null;
}
The first one, mergeDates, takes your first list wich contains the date, the second list, listItems, is the list of your items.
I loop the list with dates, and for each items in it, i call a second function that return the index of the same element in the second list, so findListIndex will search all element and return it's index when he find the same id.
Now the function mergeDates, with the newly found index, will add the value savedDate to your listItems and make it equals to the one on the listDates !
Then the function return the list with all the info of the items, including the dates!
Here is how to call the function:
var listWithDate = mergeDates(savedItems, items);
Hope it helps !

How can I parse a JSON object containing a colon

I have an object which comes back as part of a return data from a REST server. It is part of an item object.
(I don't have control over the REST server so I can't change the data received):
{
"Option:Color":"Red,Green,Blue,Orange",
"Option:Size":"Small,Medium,Large"
}
What I want to end up with is some control over this, so that I can display the results when a product is selected in my app. It will appear in a modal. I am using Marionette/Backbone/Underscore/JQuery etc. but this is more of a JavaScript question.
I have tried multiple ways of getting at the data with no success. I would like to be able to have the options in a nested array, but I'd be open to other suggestions...
Basically this kind of structure
var Color=('Red', 'Green', 'Blue', 'Orange')
var Size('Small', 'Medium', 'Large')
The Object structure is fine, just need to be able to translate it to an array and take out the 'Option' keyword
Important to mention that I have no idea what the different options might be when I receive them - the bit after Options: might be any form of variation, color, size, flavour etc.
Loop through the parsed JSON and create new keys on a new object. That way you don't have to create the var names yourself; it's automatically done for you, albeit as keys in a new object.
var obj = {
"Option:Color":"Red,Green,Blue,Orange",
"Option:Size":"Small,Medium,Large"
}
function processObj() {
var newObj = {};
for (var k in obj) {
var key = k.split(':')[1].toLowerCase();
var values = obj[k].split(',');
newObj[key] = values;
}
return newObj;
}
var processedObj = processObj(obj);
for (var k in processedObj) {
console.log(k, processedObj[k])
// color ["Red", "Green", "Blue", "Orange"], size ["Small", "Medium", "Large"]
}
Edit: OP I've updated the code here and in the jsfiddle to show you how to loop over the new object to get the keys/values.
Fiddle.
var json = {
"Option:Color":"Red,Green,Blue,Orange",
"Option:Size":"Small,Medium,Large"
};
var color = json['Option:Color'].split(',');
var size = json['Option:Size'].split(',');
Try this to do get a solution without hardcoding all the option names into your code:
var x = {
"Option:Color":"Red,Green,Blue,Orange",
"Option:Size":"Small,Medium,Large"
};
var clean = {};
$.each(x, function(key, val){ //iterate over the options you have in your initial object
var optname = key.replace('Option:', ''); //remove the option marker
clean[optname] = val.split(","); //add an array to your object named like your option, splitted by comma
});
clean will contain the option arrays you want to create
EDIT: Okay, how you get the names of your object properties like "color", which are now the keys in your new object? Thats the same like before, basically:
$.each(clean, function(key, val){
//key is the name of your option here
//val is the array of properties for your option here
console.log(key, val);
});
Of course we stick to jQuery again. ;)

Categories