I'm adding some items to localStorage, using jQuery/JS, which is all fine but in trying to remove a specific item within the array (if it's the same item) is proving difficult.
In my console logs (for testing) it seems to clear the [Object] but it's not updating the key. Perhaps my hierarchy is wrong... any ideas?
//
function addToStorage(elem, name) {
localData = localStorage.getItem(name + 'Storage');
var typefaces;
if (localData == 'null' || !localData) {
typefaces = [];
} else {
typefaces = JSON.parse(localData);
}
typefaceID = elem.find('input').val();
typefaceName = elem.find('input').attr('data-name');
typefacePrice = elem.find('input').attr('data-price');
typefaceQty = 1;
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
//console.log(index);
//console.log(typefaces);
typefaces.splice(index, 1);
//console.log(typefaces);
}
}
}
});
typefaces.push({
'id': typefaceID,
'name': typefaceName,
'price': typefacePrice,
'qty': typefaceQty
});
localStorage.setItem(name + 'Storage', JSON.stringify(typefaces));
}
//
$(document).on('click', 'summary.cart td.font', function(e) {
e.preventDefault();
addTo = $(this);
addTo.each(function() {
addToStorage(addTo, 'drf');
});
});
This is an example of the localData once it's been added to.
[
{
"id":"dr-raymond-desktop-40374",
"name":"DR-Raymond",
"format":"Desktop (OTF)",
"price":"15.00",
"qty":1
},
{
"id":"dr-raymond-webfont-39949",
"name":"DR-Raymond",
"format":"Webfont (WOFF)",
"price":"15.00",
"qty":1
}
]
Never add/remove elements from an array while iterating over it using "foreach". Instead, try this:
for (index = typefaces.length - 1; index >= 0; index--){
value = typefaces[index];
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
typefaces.splice(index, 1);
}
}
}
});
Another way to do this more elegantly is by using ES6 filter():
typefaces = typefaces.filter(value => !(value && value.id == typefaceID && name == "drf"));
Also, you are comparing localData to the literal string 'null' which is kind of pointless. Your second condition - if (!localData) is enough in this case, and will handle it properly.
The problem lies in your splice method usage. Note that, according to MDN splice modifies the array in place and returns a new array containing the elements that have been removed. So when using a loop and trying to remove the some elements, splice will make shifting of elements. This is because iterating incrementally through the array, when you splice it, the array is modified in place, so the items are "shifted" and you end up skipping the iteration of some. Looping backwards fixes this because you're not looping in the direction you're splicing.
Solution 1
Loop backwards while splicing.
for(var i = typefaces.length; i--;)
{
if (typefaces[i] !== null)
{
if (typefaces[i] == typefaceID)
{
typefaces.splice(i, 1);
}
}
}
Working bin link here.
Solution 2
Its usually faster to generate a new array instead of modifying the existing one. So, your code will look like
ty = [];
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id != typefaceID) {
ty.push(value);
}
}
});
typefaces = ty;
Working bin link is here.
After that there is no problem found in getting and setting your localStorage.
You have an additional error in your code, you write
if (value !== null) {
if (value.id == typefaceID) {
// name will always have the value drf
// since you pass it in your function
// when you call it addToStorage(addTo, 'drf');
if (name == 'drf') {
typefaces.splice(index, 1);
}
}
}
when it should be
if (value !== null) {
if (value.id == typefaceID) {
// here the comparison should be between value.name and name
if (value.name == name) {
typefaces.splice(index, 1);
}
}
}
which can be also written
if (value !== null) {
if (value.id == typefaceID && value.name == name) {
typefaces.splice(index, 1);
}
}
You are mutating the array by splicing it. The only localstorage items that are being re written are the ones that are left in the array.
So delete the item from the storage when you splice an object from the array.
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
//console.log(index);
//console.log(typefaces);
var removedTypeFace = typefaces.splice(index, 1);
//console.log(typefaces);
if(removedTypeFace) {
localStorage.removeItem(removedTypeFace.name + 'Storage');
}
}
}
}
});
OK. You're going to kick yourself. The first time you pull from localStorage, the getItem method DOES return a null value, but you're trying to compare it to a string ('null', in quotes). Fail. I changed localData == 'null' to localData == null.
The second part of the expression tries to see the value returned from getItem is actually defined, which is good. I changed that to be more explicit, by way of typeof.
Explicit variable declarations help, too.
function addToStorage(elem, name) {
var localData = localStorage.getItem(name + 'Storage');
var typefaces;
if (localData == null || typeof(localData) == 'undefined') {
typefaces = [];
} else {
typefaces = JSON.parse(localData);
}
var typefaceID = elem.find('input').val();
var typefaceName = elem.find('input').attr('data-name');
var typefacePrice = elem.find('input').attr('data-price');
var typefaceQty = 1;
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
//console.log(index);
//console.log(typefaces);
typefaces.splice(index, 1);
//console.log(typefaces);
}
}
}
});
typefaces.push({
'id': typefaceID,
'name': typefaceName,
'price': typefacePrice,
'qty': typefaceQty
});
localStorage.setItem(name + 'Storage', JSON.stringify(typefaces));
}
//
jQuery(document).on('click', 'summary.cart td.font', function(e) {
e.preventDefault();
addTo = $(this);
addTo.each(function() {
addToStorage(addTo, 'drf');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<summary class='cart'><table><tr>
<td class='font' >Click Here for Item 1
<input data-name='abc' data-price='2.00' value="Item 1" /></td></tr>
<tr>
<td class='font' >Click Here for Item 2
<input data-name='def' data-price='4.00' value="Item 2" /></td></tr></table>
</summary>
Related
My api response looks like this:
id: (...)
user_id: (...)
symptoms: "Sore throat, Headache"
id: (...)
user_id: (...)
symptoms: "Anorexia (Loss of appetite), Shortness of breath (Difficult in breathing), Myalgias (Muscle pains), Sore throat, Headache"
I am trying to match a users symptoms to existing symptoms categories. A user can have upto 14 symptoms. I keep getting an error of Cannot read property 'slice' of undefined when i split and slice the array to get individual symptoms and match them.
When i try to put a default value for the object if a user has less than 14 symptoms, the error persists.
My code:
getSymNum (symp, c) {
var counter = 0
for (var xc in c) {
var symp1 = c[xc].symptoms.split(',')[0]
var symp2 = c[xc].symptoms.split(',')[1].slice(1)
var symp3 = c[xc].symptoms.split(',')[2].slice(2)
var symp4 = c[xc].symptoms.split(',')[3].slice(3)
var symp5 = c[xc].symptoms.split(',')[4].slice(4)
var symp6 = c[xc].symptoms.split(',')[5].slice(5)
if (symp3 !== undefined){
console.log("hello ha")
}
if (symp1 === symp) {
counter++
} else if (symp2 === symp) {
counter++
} else if (symp3 === symp) {
counter++
} else if (symp4 === symp) {
counter++
} else if (symp5 === symp) {
counter++
} else if (symp6 === symp) {
counter++
}
}
return counter
},
You can optimize the check by using array/string methods like contains() or indexOf():
etSymNum (symp, c) {
var counter = 0
for (var xc in c) {
if(c[xc].symptoms.indexOf(symp) !== -1){
counter++;
}
}
return counter
},
I got these two functions, and they work great.
But since I only call global.replaceFields from global.translateAll then I want to get rid of global.replaceFields and put its functionality inside global.translateAll
How would you go about merging global.replaceFields into global.translateAll without losing the current functionality?
Thanks :)
// Translate everything in that field
global.translateAll = (textfield, usersLanguage) => {
for (var property in textfield) {
if (!textfield.hasOwnProperty(property)) {
return false;
} else if (typeof textfield[property] !== "object") {
textfield[property] = global.replaceFields(textfield[property], usersLanguage);
} else {
global.translateAll(textfield[property], usersLanguage);
}
}
}
// Translate everything in that field
global.replaceFields = (textfield, usersLanguage) => {
// Keep running until all fields are replaced
while (textfield.indexOf("{{") != -1) {
// isolate the field
let fromField = textfield.substring((textfield.indexOf("{{") + 2), (textfield.indexOf("}}")));
let toField = ""
// If its a translated text
if (fromField.indexOf("trans") != -1) {
toField = usersLanguage[fromField];
textfield = textfield.replace("{{" + fromField + "}}", toField);
}
}
return (textfield);
}
This should work
global.translateAll = (textfield, usersLanguage) => {
var replaceFields = (textfield, usersLanguage) => {
// Keep running until all fields are replaced
while (textfield.indexOf("{{") != -1) {
// isolate the field
let fromField = textfield.substring((textfield.indexOf("{{") + 2), (textfield.indexOf("}}")));
let toField = ""
// If its a translated text
if (fromField.indexOf("trans") != -1) {
toField = usersLanguage[fromField];
textfield = textfield.replace("{{" + fromField + "}}", toField);
}
}
return (textfield);
}
for (var property in textfield) {
if (!textfield.hasOwnProperty(property)) {
return false;
} else if (typeof textfield[property] !== "object") {
textfield[property] = replaceFields(textfield[property], usersLanguage);
} else {
global.translateAll(textfield[property], usersLanguage);
}
}
}
Working on a project that is taking in 5 similar SQL databases, and I need to detect and filter out duplicates. I think I'm on the right track, but I'm not quite there yet. I am attempting to follow these steps to accomplish this:
start a .forEach() for the main array passing in an item object.
create a filtered array via let filtered = Array.filter(x => x.id !== item.id); to keep from checking against itself.
start a .forEach() for the filtered array passing in comparison as the parameter.
initialize variables for similarity in Name, Phone, and Email fields.(i.e.nameSimilarity, phoneSimilarity, and emailSimilarity)
If item.email and comparison.email aren't empty, compare the strings and store the similarity percentage in emailSimilarity else emailSimilarity=0.
If item.phone and comparison.phone aren't empty, compare the strings and store the similarity percentage in phoneSimilarity else phoneSimilarity=0.
Combine item.firstName and item.lastName into an variable called itemFullName and combine comparison.firstName and comparison.lastName into a variable called comparisonFullName.
If itemFullName and comparisonFullName aren't empty, compare the strings and store the similarity percentage in nameSimilarity else nameSimilarity=0.
if any of the percentages in emailSimilarity, nameSimilarity, or phoneSimilarity, add item plus the similarity variables and comparison.id to the duplicates array, and splice it out of the original array.
This is the code that I've written to follow these steps, but it appears that I'm getting duplicate entries in the duplicates array. I'm not sure why it's not working as expected, but I have a hunch that I can't really expect the original array to mutate inside the forEach() operation.
fullArray.forEach(item => {
let filtered = fullArray.filter(x => x.externalId !== item.externalId);
filtered.forEach(comparison => {
let emailSimilarity, phoneSimilarity, nameSimilarity;
if ((item.email !== '') && (comparison.email !== '')) {
emailSimilarity = strcmp.jaro(item.email, comparison.email);
} else {
emailSimilarity = 0;
}
if ((item.phone !== '') && (comparison.phone !== '')) {
phoneSimilarity = strcmp.jaro(item.phone, comparison.phone);
} else {
phoneSimilarity = 0;
}
let itemFullName = `${item.firstName} ${item.LastName}`.trim() || '';
let comparisonFullName = `${comparison.firstName} ${comparison.LastName}`.trim();
if (((itemFullName !== '') && (comparisonFullName !== '')) || ((itemFullName.indexOf('Group')! > 0) && (comparisonFullName.indexOf('Group') !>0))) {
nameSimilarity = strcmp.jaro(itemFullName, comparisonFullName);
} else {
nameSimilarity = 0;
}
if ((emailSimilarity || phoneSimilarity || nameSimilarity) > 0.89) {
let dupesOutput = Object.assign({}, item, { similarName: nameSimilarity, similarEmail: emailSimilarity, similarPhone: phoneSimilarity, similarTo: comparison.externalId });
dupes.push(dupesOutput);
fullArray = fullArray.filter(x => x.externalId !== item.externalId);
}
});
});
Where's the issue?
Assuming the similarity check is working, the problem is that you're reassigning a new array to fullArray while still being in the forEach loop of the old one.
I'd suggest you use Array.filter:
var filteredArray = fullArray.filter(item => {
return !fullArray.some(comparison => {
if(comparison.externalId==item.externalId)
return false;
let emailSimilarity, phoneSimilarity, nameSimilarity;
if ((item.email !== '') && (comparison.email !== '')) {
emailSimilarity = strcmp.jaro(item.email, comparison.email);
} else {
emailSimilarity = 0;
}
if ((item.phone !== '') && (comparison.phone !== '')) {
phoneSimilarity = strcmp.jaro(item.phone, comparison.phone);
} else {
phoneSimilarity = 0;
}
let itemFullName = `${item.firstName} ${item.LastName}`.trim() || '';
let comparisonFullName = `${comparison.firstName} ${comparison.LastName}`.trim();
if (((itemFullName !== '') && (comparisonFullName !== '')) || ((itemFullName.indexOf('Group')! > 0) && (comparisonFullName.indexOf('Group') !>0))) {
nameSimilarity = strcmp.jaro(itemFullName, comparisonFullName);
} else {
nameSimilarity = 0;
}
if ((emailSimilarity || phoneSimilarity || nameSimilarity) > 0.89) {
let dupesOutput = Object.assign({}, item, { similarName: nameSimilarity, similarEmail: emailSimilarity, similarPhone: phoneSimilarity, similarTo: comparison.externalId });
dupes.push(dupesOutput);
return true;
}else
return false;
});
});
I want to avoid reading the previous objects pushed in the JSON array. As shown in the image.
I'm Self learning these concepts. so i need help, about is this the right method to add and read values.
Also i dont know how to ask this question technically. so i would appreciate if someone would tell me how this question should be asked. So that i can atleast improve it for better understanding.
JQUERY
$("#click").click(function(event)
{
event.preventDefault();
var $form = $('#myform');
var $boxes =$("input[id=myCheckboxes]:checked").length;
if($boxes==0)
{
alert("Choose atleast one Category");
}
else if($form.valid() && $boxes>0)
{
//if form is valid action is performed
var data = $( "#myform" ).serializeArray();//serialize the data
var valuesArray = $('input:checkbox:checked').map( function() {
return this.value;
}).get().join(",");
data.push({ name: 'panel', value: valuesArray});
//convert json array into object
var loginFormObject = {};
$.each(data,
function(i, v) {
loginFormObject[v.name] = v.value;
});
array.push(loginFormObject);
alert("Added Successfully");
viewFunction(array);
return false;
}
})
//view function
function viewFunction(array)
{
console.log(array);
var panel_arr = ["", "Regular", "Reduced Fee", "Limited Assurance","Court Programs"];
var ul_block = $("<ul/>");
$.each(array, function(i, data)
{
var panels = data.panel.split(",");
var uli_block = $("<ul/>");
$.each(panels, function(j, jdata)
{
var ulii_block = $("<ul/>");
$edit = $('<a/>').attr('href', 'javascript:;').addClass('btn btn-default active').attr('role', 'button').text('Edit')
.css('margin-left', 5);
$del = $('<a/>').addClass('btn btn-default active').attr('role', 'button').text('Delete')
.css('margin-left', 5);
$(ulii_block).append($("<li/>").html(data.ptitle).append($edit,$del));
$(uli_block).append($("<li/>").html('<span class="Collapsable">'+panel_arr[panels[j]]+'</span>')).append(ulii_block);
$edit.click(editFunction.bind(null, data));//bind data to function
});
$(ul_block).append($("<li/>").html('<span class="Collapsable">'+data.gpanel+'</span>').append(uli_block));
});
$("#addMember").append(ul_block);
$(".Collapsable").click(function () {
$(this).parent().children().toggle();
$(this).toggle();
});
$(".Collapsable").each(function(){
$(this).parent().children().toggle();
$(this).toggle();
});
}
i made this method to compare between 2 of my json objects:
//tempObj is old object and newObj is well your new JSON, this function returns bool
function isDifferentObj(tempObj, newObj) {
var tempObjLength = Object.keys(tempObj).length;
var newObjLength = Object.keys(newObj).length;
if (newObjLength >= tempObjLength) {
for (var key in newObj) {
if (typeof tempObj[key] != "undefined") {
if (newObj[key] != tempObj[key]) {
return true;
}
} else {
return true;
}
}
return false;
} else {
for (var key in tempObj) {
if (typeof newObj[key] != "undefined") {
if (tempObj[key] != newObj[key]) {
return true;
}
} else {
return true;
}
}
return false;
}
}
After a lot of trouble i found my problem. I was appending the result every time.
The code line which was making the trouble was this.
$("#addMember").append(ul_block);
I changed it to
$("#addMember").html(ul_block);
hence avoiding duplicates.
Updates at the bottom
I wrote a filter to manage what I displace in my ng-repeat. However, when the data being returned should be an empty array, in IE 9+ I get a crash, but in FF and Chrome it works fine.
BuildFilter below is called from angular.module('app').filter(filterId, buildFilter);
function buildFilter() {
return function (input, limitTo, keyWords) {
var outputPre = [];
var outputPost = [];
var d = new Date();
console.log('filter event: '
+ d.getHours() + ":"
+ d.getMinutes() + ":"
+ d.getSeconds());
var outputPre = [];
if (!(limitTo === null
|| limitTo === undefined
|| limitTo === '')) {
for (var i = 0; i < input.length; i++) {
if (input[i] !== null && input[i] !== undefined) {
switch (limitTo) {
case 'edi':
if (input[i].dateReleased === null) {
outputPre.push(input[i]);
}
break;
case 'rel':
if (input[i].dateReleased !== null
&& input[i].dateRetired === null) {
outputPre.push(input[i]);
}
break;
case 'ret':
if (input[i].dateRetired !== null) {
outputPre.push(input[i]);
}
break;
default:
outputPre.push(input[i]);
}
}
}
}
else {
for (var i = 0; i < input.length; i++) {
outputPre.push(input[i]);
}
}
//Sanity Check Log Entry
console.log('pre count: ' + outputPre.length);
if (!(keyWords === null
|| keyWords === undefined
|| keyWords === '')) {
var tkw = keyWords.toLocaleLowerCase();
for (var i = 0; i < outputPre.length; i++) {
var tn = outputPre[i].name.toLocaleLowerCase();
if (tn.indexOf(tkw) > -1) {
outputPost.push(outputPre[i]);
}
}
}
else {
for (var i = 0; i < outputPre.length; i++) {
outputPost.push(outputPre[i]);
}
}
//Sanity Check Log Entry
console.log('post count: ' + outputPost.length);
return outputPost;
};
};
My sample data is:
var data= [ //for input
{
id: 0,
dateCreated: '1/1/2014',
dateReleased: null,
dateRetired: null,
name: 'Sample Data',
},
{
id: 1,
dateCreated: '1/1/2014',
dateReleased: null,
dateRetired: null,
name: 'Other Sample Data',
},
]
limitTo supports the following values: 'edi' 'rel', 'ret'
keyWords is just any string. It just looks to see if any part of the string is in the name field.
Update to post:
Reduced the code to the following.
function isEdi(obj) {
if ((obj.dateReleased === null)
&& (obj.dateRetired === null)) {
return true;
}
return false;
}
function isRel(obj) {
if ((obj.dateReleased !== null)
&& (obj.dateRetired === null)) {
return true;
}
return false;
}
function isRet(obj) {
if ((obj.dateReleased !== null)
&& (obj.dateRetired !== null)) {
return true;
}
return false;
}
function buildFilter() {
return function (input, limitTo) {
var output = [];
switch (limitTo) {
case 'edi':
output = input.filter(isEdi);
break;
case 'rel':
output = input.filter(isRel);
break;
case 'ret':
output = input.filter(isRet);
break;
default:
output = input;
}
return output;
};
};
IE crashes when the return is [], but works fine if the return as at least 1 record.
2ns Update to post:
Changed the repeat to ng-repeat="obj in objlist | filter:getObJFilter(objFilter) | orderBy:['+dateRetired','+dateReleased','+name']">
objFilter is a parameter to get the filter function to pass to filter. This way I am just extending the native filter rather than creating a new filter.
So my controller contains the following.
$scope.getFilter = function (val) {
switch (val) {
case 'edi':
return isEdi();
break;
case 'rel':
return isRel();
break;
case 'ret':
return isRet();
break;
default:
return function (obj) {return true };
}
}
function isEdi() {
return function(obj){
if ((obj.dateReleased === null)
&& (obj.dateRetired === null)) {
return true;
}
return false;
}}
function isRel() {
return function (obj) {
if ((obj.dateReleased !== null)
&& (obj.dateRetired === null)) {
return true;
}
return false;
}
}
function isRet() {
return function (obj) {
if ((obj.dateReleased !== null)
&& (obj.dateRetired !== null)) {
return true;
}
return false;
}
}
I believe that I have done everything I can to eliminate the filter as being the problem. So I am now to where I believe there is a problem with an empty set being rendered in IE.
I will post a full test example in Plunkr tonight.
Note: I am also using AngularUI and Angular for UI Bootstrap. Shortcut for getting needed directives. However, I have had problems with UI Bootstrap on other issues, so I am going to replace a few more of their components to isolate the problem some more.
3rd Update I have removed all 3rd party directives. I did have UI Bootstrap to support TBS, so I eliminated that to save my sanity.
Put
<html xmlns:ng="http://angularjs.org">
or
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
Follow this
This isn't a problem with Angular.js, but with the UI.Bootstrap directives.
Since I couldn't replicate the issue in Plunker, I started to look at the other directives. Eliminated the tab control out of the SPA, and replaced it with my own custom tabs based on TBS. Problem went away.
Further investigation reveals that this may be caused by a recursion problem in the UI.Bootstrap tab. So I will document my findings, and post to that GitHub.
I wouldn't have found this without the simple suggestion of posting code to Plunker, so I am the angular crew again.
My UI looked like:
<tabset>
<tab header="some header">
<div ng-repeat="...">
...
So the tabset and tab directive were getting fired at every change in my filter on the repeat. So I have removed the tabset and tab, and just replaced them with the standard TBS tabs.