I am pushing Objects to a $localStorage array for persistence. I also check this array to see if an object is present before adding / removing an Object (if present it should splice if not present then it will push).
When I refresh my page the data returned from $localStorage doesn't seem to be the same as it was pre-refresh as my check function doesn't work, despite it looking EXACTLY the same on inspection.
Objects being pushed are structured like this:
{
"createdAt": "2015-04-24T10:21:21.649Z",
"difficulty": "Hard",
"exerciseDescription": "Lie on your back on a bench and take hold",
"exerciseID": "3101",
"exerciseName": "Bench Press",
"images": [8679, 8680, 8682],
"tags": ["Barbell", "Horizontal Flexion", "Extension", "Strength", "Chest", "Triceps", "Shoulder", "Elbow, Wrist & Hand"],
"updatedAt": "2015-09-09T20:14:59.681Z",
"words": ["bench", "press", "chest"],
"objectID": "ak6t7ukQdY",
"_highlightResult": {
"exerciseName": {
"value": "Bench Press",
"matchLevel": "none",
"matchedWords": []
}
}
}
Check if object if present (toggle add/remove)
$scope.addExerciseToProgramme = function(exercise) {
if (!$localStorage.Programme) {
$localStorage.Programme = [];
}
var index = $localStorage.Programme.indexOf(exercise);
if (index > -1) {
$localStorage.Programme.splice(index, 1);
} else {
$localStorage.Programme.push(exercise);
}
}
Function to watch/load $localStorage
$scope.$watch(function() {
return $localStorage.Programme
}, function(programme) {
$scope.programme = programme;
});
ng-class to check if exercise is in programme
<i class="exercise-add-indicator ion-ios-checkmark-outline" ng-class="{'orange': programme.indexOf(exercise) > -1}"></i>
Problem
There are two problems with this:
Following refresh, the ng-class doesn't conditionally add the class depending on the content of my $scope.programme
The addExerciseToProgramme function doesn't respect the indexOf check and pushes the exercise object to the array regardless!
Array.prototype.indexOf() uses strict equality: An expression comparing Objects is only true if the operands reference the same Object.
You shouldn't use this when working with localStorage.
When saving an object to localStorage it's turned into a string. When retrieved it's turned into an object again.
This will however be a new object, even if it looks exactly the same.
For example, this will yield false:
var object1 = { id: 1 };
var object2 = { id: 1 };
console.log(object1 === object2);
To get it working you can implement a custom function that retrieves the index based on the value of a property of your choosing. Note that it should be unique.
For example:
$scope.getExerciseIndex = function(exercise) {
var index = -1;
if (!$scope.programme) return index;
for (var i = 0; i < $scope.programme.length; i++) {
if ($scope.programme[i].exerciseID !== exercise.exerciseID) continue;
index = i;
break;
}
return index;
};
$scope.exerciseExists = function(exercise) {
var index = $scope.getExerciseIndex(exercise);
return index > -1;
};
$scope.addExerciseToProgramme = function(exercise) {
if (!$localStorage.Programme) {
$localStorage.Programme = [];
}
var index = $scope.getExerciseIndex(exercise);
if (index > -1) $localStorage.Programme.splice(index, 1);
else $localStorage.Programme.push(exercise);
};
HTML:
... ng-class="{'orange': exerciseExists(exercise) }" ...
Demo: http://plnkr.co/edit/R6TEisvQ7gkDcwBgw0D1?p=preview
Related
So I have a Table made from some json data...
{
"AKH":{
"name": "Amonkhet",
"code": "AKH"
"cards": [
{
"artist": "Izzy",
"cmc": 3,
"colorIdentity": [
"W"
],
"colors": [
"White"
],
"id": "df3a6e0336684c901358f3ff53ec82ff5d7cdb9d",
"imageName": "gideon of the trials",
"layout": "normal",
"loyalty": 3,
"manaCost": "{1}{W}{W}",
"multiverseid": 426716,
"name": "Gideon of the Trials",
"number": "14",
"rarity": "Mythic Rare",
"subtypes": [
"Gideon"
],
"text": "+1: Until your next turn, prevent all damage target permanent would deal.\n0: Until end of turn, Gideon of the Trials becomes a 4/4 Human Soldier creature with indestructible that's still a planeswalker. Prevent all damage that would be dealt to him this turn.\n0: You get an emblem with \"As long as you control a Gideon planeswalker, you can't lose the game and your opponents can't win the game.\"",
"type": "Planeswalker — Gideon",
"types": [
"Planeswalker"
]
},
The Table row ends up looking like this for each of the cards. at the moment I only Attach the ID, Card name, and Mana Cost to each row
<td><a href="#" onclick="showInfo(this.id)"
id="df3a6e0336684c901358f3ff53ec82ff5d7cdb9d">Gideon of the Trials</a></td>
Now I want to search through these cards. (Keep in mind there are over 17,000 different cards that will be on this list) I can get it to find the things.. But I'm having several different issues... Either it finds them all but doesn't hide the rest of the list, or it hides the whole list and only displays one of the found cards.
So question A... What am I missing to make the search work correctly?
$(document).on('change', 'input[type=checkbox]', function() {
var lis = $('.cardsRow')
$('input[type=checkbox]').filter(':checked').each(function(){
filterKeyB = $(this).attr('id')
filterKeyA = $(this).attr('name')
$.each(json, function(setCode, setListing) {
$.each(setListing.cards,function(cardNum, cardListing){
var x = Object.keys(cardListing)
var y = Object.keys(cardListing).map(function (key){
return cardListing[key]
})
for (i = 0; (i < x.length); i++) {
if(x[i] === filterKeyA){
if (y[i] instanceof Array){
var holder = y[i]
var valueArr =[]
for(var k = 0; k < holder.length; k++){
valueArr = holder.join('|').toLowerCase().split('|')
var foundIt = valueArr.includes(filterKeyB)
}
}else{
var stringy = y[i]
var stringyA= stringy.toLowerCase().replace(/\s/g, '')
if (stringyA === filterKeyB){
var foundIt = true
}
}
if(foundIt === true){
$winner = cardListing.name
for (k = 0; (k < lis.length); k++){
if (lis[k].innerText.indexOf($winner) != -1) {
$(lis[k]).show()
}
}
}
}
}
})
Question B... Since you are already here... Would it be better practice to attach the data that can be searched to the element itself? Maybe just the most searched (Like Name and Mana) and have more advanced queries go through the data again?
I don't understand why the code isn't working or even how it works, it looks like it references some functions that aren't defined in the sample. But I can share with you a really simple/intuitive way to filter stuff, I hope you find it useful.
Native filter method is so useful for what you're trying to do, it takes a callback that takes current element as an arg and returns true or false, if true, the element is included in the new array it produces.
But filter only takes one function, and you have many filters, so let's make a function that combines many filter Fns together into one fn, so you can pass them in all at once:
const combineFilters = (...fns) => val => fns.reduce((prev, curr) => prev || curr(val), false);
OK, how about storing the names of the filter functions as keys in an object so we can reference them using a string? That way we could give each checkbox an ID corresponding to the name of the filter function they are supposed to apply, and makes things really easy to implement (and read):
const filterFns = {
startsWithG(card) {
return card.name[0] === 'G';
},
//etc.
};
OK, time to get the IDs of all the checkboxes that are clicked, then map them into an array of functions.
const filters = $('input[type=checkbox]')
.filter(':checked')
.map((e, i) => $(i).attr('id'))
.get()
.map(fnName => filterFns[fnName])
(Assume the relevant data is stored in a var called...data.) We can use combineFilters combined with filters (array of Fns) to activate all of the relevant filters, then map the resulting array of matching objects into the HTML of your choosing.
const matches = data.cards
.filter(combineFilters(...filters))
.map(card => `<div>${card.name}</div>` );
Then time to update DOM with your matches!
As others have noted, if you need to do any more complicated filtering on objects or arrays, lodash library is your friend!
Essentially what I am trying to achieve here is to check if the Barcode inputted/scanned on the form which is stored in self.trackfile is already in the list of files.
self.files() is an array of arrays, each time the file is added it pushes another array from self.trackfile into self.files(), once all the files have been added into the list they can be 'tracked' and sent back to the server.
I am having trouble getting this to work in IE11 (Compatibility Mode), this works fine in Chrome. I have done some searching around and not found a workaround.
The line var fb = self.files()[x].Barcode(); throws the following error in IE: Object doesn't support property or method 'Barcode'.
If you could help me identify a workaround that would be fantastic!
addFile Script
self.addFile = function () {
var index = 0;
if(index < self.files().length){
var i = 0;
for (x in self.files()){
var fb = self.files()[x].Barcode();
var tb = self.trackfile.Barcode();
if(fb==tb){
i += 1;
}
}
if(i > 0){
alert("Error: File Already Exists in List");
}
else {
self.files.push(new TrackFile(self.trackfile));
}
}
else {
self.files.push(new TrackFile(self.trackfile));
}
}
Example of files()
[
{
"Location": "Location 1",
"TransactionMode": "Send",
"ServicePoint": "Service Point 2",
"Status": "Incomplete / Open",
"Comments": "",
"Barcode": "0123456789",
"BarcodeImageBase64": ""
},
{
"Location": "Location 1",
"TransactionMode": "Send",
"ServicePoint": "ServicePoint 1",
"Status": "Incomplete / Open",
"Comments": "",
"Barcode": "9876543210",
"BarcodeImageBase64": ""
}
]
console.log(self.files()[x]);
Try looping through your array with indexes instead of the for (x in foo) construct. You're probably running into a random property on the prototype of the array that IE adds which obviously wouldn't contain a Barcode.
See: Why is using "for...in" with array iteration a bad idea?
So I figured out how to get around this rather than trying to return a value from a nested array I created an array of just the barcodes:
self.justBarcodes = ko.computed(function() {
var barcodes = ko.utils.arrayMap(this.files(), function(item) {
return item.Barcode();
});
return barcodes.sort();
}, self);
Then I looped through the self.justBarcodes() array to check if the barcode already exists:
for (var x = 0; x < self.justBarcodes().length; x++){
var fb = self.justBarcodes()[x];
var tb = self.trackfile.Barcode();
if(fb==tb){
i += 1;
}
}
Now it works as it should!
If laptop model and serial id are same, i've to add new field totalModel and increase count. For example in below case: serialid "1" and laptop model "xyz" are coming two time so i want to add "totalModel" count as 2 and so on. How can i achieve this in jquery
This question is not really about jQuery, it is about mapping and filtering arrays and objects. However, we can use some jQuery convenience methods to solve it.
A large part of solving these problems is by properly defining what you want to do. It sounds from your question that you want to get a map of unique serial ids per laptop model type. We can use JavaScript's Array.prototype.reduce to produce just such a map (Note that we will take the 'sold' value for the first of each laptop model we encounter):
var laptop_models = data.reduce(function (memo, obj) {
if (!memo[obj.laptopModel]) {
memo[obj.laptopModel] = {
unique_serial_ids: [],
sold: obj.sold
};
}
if ($.inArray(obj.serialid, memo[obj.laptopModel].unique_serial_ids) === -1) {
memo[obj.laptopModel].unique_serial_ids.push(obj.serialid);
}
return memo;
}, {});
Next, we can map our laptop_models object into the array you specified as your expected result:
var result = $.map(laptop_models, function (laptop_model, model_name) {
return {
laptopModel: model_name,
totalModel: laptop_model.unique_serial_ids.length,
sold: laptop_model.sold
};
});
You got the idea already. Iterate through the array.
if them item is in a hash, increment the count, otherwise, add to the hash and set the count to 1
var hash = {};
for (var i = 0;i<data.length;i++) {
if (hash[data[i].laptopModel) {
hash[data[i].laptopModel]++;
}
else
hash[data[i].laptopModel] = 1;
}
var data = [
{
"serialid": 1,
"laptopModel": "xyz",
"sold": "yes",
"cnt": 5
},
{
"serialid" :1,
"laptopModel": "xyz",
"sold": "yes",
"cnt": 4
},
{
"serialid": 1,
"laptopModel": "abc",
"sold": "yes",
"cnt": 3
},
{
"serialid": 3,
"laptopModel": "xyz",
"sold": "yes",
"cnt": 2
}];
var result = []; //work if result = {};
var tempArray = []; // used to store unique name to prevent complex loop
data.forEach(function(item){
if($.inArray(item.laptopModel, tempArray)< 0){// unique name
result.push(formatData(item));
tempArray.push(item.laptopModel);
}
else{
var indexNew = $.inArray(item.laptopModel, tempArray);
result[indexNew]["totalModel"] += 1;
}
});
function formatData(item){
return{
"laptopModel": item.laptopModel,
"sold": item.sold,
"totalModel": 1
}
}
alert(JSON.stringify(result)); //expect array 2 item but it's empty array
console.log(result); //Will have result 2 item when I view console window
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I have this array of pages urls, now i need to make a hierarchy of out it.
So from this:
allPages = [
{ "url": "/polygon/color/red.html", "name": "Red" },
{ "url": "/polygon/color/blue.html", "name": "Blue" },
{ "url": "/polygon/shape/tri.html", "name": "Triangle" },
{ "url": "/weight/heavy.html", "name": "Heavy Item" }
];
To this:
siteMap = [
polygon:
color:
[{url:"red.html", name:"Red"}],
[{url:"blue.html", name:"Blue"}],
shape:
[{url:"tri.html", name:"Triangle"}],
weight:
[{url:"heavy.html", name:"Heavy Item"}],
];
The final structure can be object or array. But i can only use JS, not jQuery nor php.
EDIT: Changed Input data into array of objects. Sorry for making this harder.
Fiddle: https://jsfiddle.net/ss92kw4a/2/
several steps:
First we split the strings into arrays:
for(i in all){
all[i] = all[i].substring(1).split("/");
}
Next we do a recursive insertion:
function insert(o, a){
if(a.length == 0){return; }
if(!o[a[0]]){
o[a[0]] = {};
}
insert(o[a[0]], a.slice(1));
}
We start the recursion like this:
ans = {};
all.forEach(function(entry){
insert(ans, entry);
});
All done. The result tree is now in the ans object:
console.log(ans);
UPDATE: this code makes the last level an array: https://jsfiddle.net/ss92kw4a/3/
You may use something like this:
var allPages = [
"/polygon/color/red.html",
"/polygon/color/green.html",
"/polygon/color/blue.html",
"/polygon/shape/tri.html",
"/polygon/shape/rect.html",
"/weight/heavy.html",
"/weight/light.html"
];
var siteMap = {};
for (var i in allPages) {
var fragments = allPages[i].match(/[^\/]+/g);
if (!!fragments) {
var currentMember = siteMap;
for (var j in fragments) {
fragment = fragments[j];
if(!currentMember.hasOwnProperty(fragment)) {
currentMember[fragment] = {};
}
currentMember = currentMember[fragment];
}
}
}
Might be enhanced, notably in the fact that ending leaves are objects,
but it works.
UPDATE - Thanks for all the great answers and incredibly fast response. I've learned a great deal from the suggested solutions. I ultimately chose the answer I did because the outcome was exactly as I asked, and I was able to get it working in my application with minimal effort - including the search function. This site is an invaluable resource for developers.
Probably a simple task, but I can't seem to get this working nor find anything on Google. I am a Javascript novice and complex JSON confuses the hell out of me. What I am trying to do is make a PhoneGap Application (Phone Directory) for our company. I'll try to explain my reasoning and illustrate my attempts below.
I have JSON data of all of our employees in the following format:
[
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer"
},
{
"id":"2",
"firstname":"Mark",
"lastname":"Banana",
"jobtitle":"Artist"
},
... and so on
]
The mobile framework (Framework 7) that I am using offers a "Virtual List" solution which I need to take advantage of as our directory is fairly large. The virtual list requires you to know the exact height of each list item, however, you can use a function to set a dynamic height.
What I am trying to do is create "headers" for the alphabetical listing based on their last name. The JSON data would have to be restructured as such:
[
{
"title":"A"
},
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer"
},
{
"title":"B"
},
{
"id":"2",
"firstname":"Mark",
"lastname":"Banana",
"jobtitle":"Artist"
},
... and so on
]
I've been able to add key/value pairs to existing objects in the data using a for loop:
var letter, newLetter;
for(var i = 0; i < data.length; i++) {
newLetter = data[i].lastname.charAt(0);
if(letter != newLetter) {
letter = newLetter
data[i].title = letter;
}
}
This solution changes the JSON, thus outputting a title bar that is connected to the list item (the virtual list only accepts ONE <li></li> so the header bar is a div inside that bar):
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer",
"title":"A"
},
{
"id":"1",
"firstname":"Mike",
"lastname":"Apricot",
"jobtitle":"Engineer",
"title":""
}
This solution worked until I tried implementing a search function to the listing. When I search, it works as expected but looks broken as the header titles ("A", "B", etc...) are connected to the list items that start the particular alphabetical section. For this reason, I need to be able to separate the titles from the existing elements and use them for the dynamic height / exclude from search results.
The question: How can I do a for loop that inserts [prepends] a NEW object (title:letter) at the start of a new letter grouping? If there is a better way, please enlighten me. As I mentioned, I am a JS novice and I'd love to become more efficient programming web applications.
var items = [
{ "lastname":"Apple" },
{ "lastname":"Banana" },
{ "lastname":"Box" },
{ "lastname":"Bump" },
{ "lastname":"Can" },
{ "lastname":"Switch" }
];
var lastC = null; //holds current title
var updated = []; //where the updated array will live
for( var i=0;i<items.length;i++) {
var val = items[i]; //get current item
var firstLetter = val.lastname.substr(0,1); //grab first letter
if (firstLetter!==lastC) { //if current title does not match first letter than add new title
updated.push({title:firstLetter}); //push title
lastC = firstLetter; //update heading
}
updated.push(val); //push current index
}
console.log(updated);
Well right now you have an array of objects - prefixing the title as its own object may be a bit confusing - a better structure may be:
[
{
title: "A",
contacts: [
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer",
"title":"A"
}
]
Given your current structure, you could loop and push:
var nameIndexMap = {};
var newContactStructure = [];
for (var i = 0; i < data.length; i++) {
var letter = data[i].lastname.charAt(0);
if (nameIndexMap.hasOwnProperty(letter)) {
//push to existing
newContactStructure[nameIndexMap[letter]].contacts.push(data[i])
} else {
//Create new
nameIndexMap[letter] = newContactStructure.length;
newContactStructure.push({
title: letter,
contacts: [
data[i]
]
});
}
}
newContactStructure will now contain your sorted data.
Demo: http://jsfiddle.net/7s50k104/
Simple for loop with Array.prototype.splice will do the trick:
for (var i = 0; i < data.length; i++) {
if (i == 0 || data[i-1].lastname[0] !== data[i].lastname[0]) {
data.splice(i, 0, {title: data[i].lastname[0]});
i++;
}
}
Demo. Check the demo below.
var data = [
{"lastname":"Apple"},
{"lastname":"Banana"},
{"lastname":"Bob"},
{"lastname":"Car"},
{"lastname":"Christ"},
{"lastname":"Dart"},
{"lastname":"Dog"}
];
for (var i = 0; i < data.length; i++) {
if (i == 0 || data[i-1].lastname[0] !== data[i].lastname[0]) {
data.splice(i, 0, {title: data[i].lastname[0]});
i++;
}
}
alert(JSON.stringify( data, null, 4 ));