Angular: 2 bugs in saving to local storage - json

I have 2 bugs in methods that save products to local storage (when a user adds them to 'favorites').
The code is part of Angular service, but it can be read without this reference also.
First bug: Sometimes, when a user saves a product to favorites, not one, but multiple products get stored.
Second bug: When I store too many products, I get an error saying that the storage is full (it's full because I parse JSON wrongly, so unwanted additional backslashes are added).
This is the codepan (you can see the result in the console): https://codepen.io/mandy555/pen/dyPVGxj
storage;
storageKeys;
x;
i = 0;
saveId;
arr;
normalArr;
productInStore;
productsInStore;
idsInStore;
newIds;
newProducts;
favorites;
storageAvailable(type) {
try {
this.storage = window[type];
this.x = '__storage_test__';
this.storage.setItem('x', this.x);
this.storage.removeItem('x');
return true;
}
catch (e) {
return e instanceof DOMException && (
// everything except Firefox
e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// acknowledge QuotaExceededError only if there's something already stored
(this.storage && this.storage.length !== 0);
}
}
storeFavorite(product = '', productId = '') {
productId = String(productId);
this.normalArr = [];
if (this.storageAvailable('localStorage')) {
this.storage = window.localStorage;
if (!this.storage.getItem('favorites_id')) {
this.saveId = productId;
this.normalArr.push(product);
} else {
this.productsInStore = JSON.parse(this.storage.getItem('favorites_product'));
if (this.storage.getItem('favorites_id').indexOf(',') === -1) {
this.arr = [this.storage.getItem('favorites_id')];
this.normalArr = this.productsInStore;
} else {
this.arr = this.storage.getItem('favorites_id').split(',');
this.normalArr = this.productsInStore;
}
if (!this.arr.includes(productId) && productId !== '') {
this.saveId = this.storage.getItem('favorites_id') + ',' + productId;
this.normalArr.push(product);
} else {
this.saveId = this.storage.getItem('favorites_id');
}
}
this.storage.setItem('favorites_id', this.saveId);
this.storage.setItem('favorites_product', JSON.stringify(this.normalArr));
} else {
this.storage = null;
}
}
deleteFavorite(productId = '') {
productId = String(productId);
this.normalArr = [];
if (this.storageAvailable('localStorage')) {
this.storage = window.localStorage;
if (this.storage.getItem('favorites_id') && this.storage.getItem('favorites_product')) {
this.productsInStore = JSON.parse(this.storage.getItem('favorites_product'));
if (this.storage.getItem('favorites_id').indexOf(',') === -1) {
this.storage.clear();
} else {
this.idsInStore = this.storage.getItem('favorites_id').split(',');
this.newIds = this.idsInStore.filter(id => {
return id !== productId;
});
this.newproducts = this.productsInStore.filter(product => {
return String(product.id) !== productId;
});
this.newIds = this.newIds.join();
this.storage.setItem('favorites_id', JSON.stringify(this.newIds));
this.storage.setItem('favorites_product', JSON.stringify(this.newproducts));
}
}
} else {
this.storage = null;
}
}

The first problem was that I forgot to assign storage to local storage. The second solution was to save x and not JSON.stringify(x) in the local storage, since x was already a string. The corrected version is here:
https://codepen.io/mandy555/pen/dyPVGxj

Related

AngularJS Selects Empty Option Even Valid Option is Avaliable

I'm using AngularJS ver. 1.2.15 on my project. And, I have a select element on one of my views as per below:
<select class="select-white form-control form-select" id="cat2_{{feed.id}}" ng-model="feed.operationstatusid" ng-change="updateCategoryAndStatus(feed, true)"></select>
And, I'm feeding this element like this:
function SetCategory2(cat1Id, feed) {
var feedId = feed.id;
var fromRuleOpStatusId = -1;
$('#cat2_' + feedId).find('option').remove();
if (cat1Id > -1) {
$('#cat2_' + feedId).append($('<option></option>').text(lang.SelectSubCategory).val(0));
$.each($scope.category2, function (index, cat2Item) {
$('#cat2_' + feedId).append($('<option></option>').text(cat2Item.statusdescription).val(cat2Item.id));
});
var isselected = false;
$.each($scope.category2, function (index, cat2Item) {
if (feed.operationstatusid == cat2Item.id) {
$('#cat2_' + feedId).val(cat2Item.id);
fromRuleOpStatusId = -1;
isselected = true;
}
else {
var feedStr = "";
if (feed.title != undefined && feed.title != null) {
feedStr = feed.title.toLowerCase();
}
if ($scope.catTitleRulesTwo) {
$.each($scope.catTitleRulesTwo, function (r_index, r_item) {
if (cat2Item.id == r_item.titleCode && !isselected) {
if (feedStr != undefined && feedStr != null && r_item != undefined && r_item != null) {
String.prototype.contains = function (str) { return this.toLowerCase().indexOf(str) !== -1; };
var text = feedStr;
if (eval(r_item.ruleexpression)) {
$('#cat2_' + feedId).val(cat2Item.id);
fromRuleOpStatusId = cat2Item.id;
isselected = true;
}
}
}
});
}
}
});
if (fromRuleOpStatusId != -1) {
feed.operationstatusid = fromRuleOpStatusId;
}
}
else {
$('#cat2_' + feedId).append($('<option></option>').text(lang.SelectSubCategory).val(0));
}
}
I am aware of the facts about eval function, but the project I'm working on is quite old, so does the code. Anyway, this is about business logic and quite irrelevant with the thing I'm going to ask (or so I was thinking).
As you can see I'm appending all the options before I set the value of the selectbox with using .val(...). I have also checked that values do match along with the data types. But, when I observe this function step by step, I saw that selected value does show up without flaw. After the code finish with my above mentioned function (SetCategory2), code goes through on of the function located on AngularJS file, named xhr.onreadystatechange. It's not a long function, so I'm sharing it also on below.
xhr.onreadystatechange = function() {
if (xhr && xhr.readyState == 4) {
var responseHeaders = null,
response = null;
if(status !== ABORTED) {
responseHeaders = xhr.getAllResponseHeaders();
response = ('response' in xhr) ? xhr.response : xhr.responseText;
}
completeRequest(callback,
status || xhr.status,
response,
responseHeaders);
}
};
After the code released from this function, respective selectbox's value is pointed at the empty option.
I have run into topics which talks about this behaviour might due to invalid option-value match, but as I described above, I append all my options before deciding the value. So, I can't figure out what I'm missing.
Thank you in advance.

how to store logs into mysql db using winston in Node.js

I am doing logging for my application using winston. I have done the file transport using this:
class LoggerHelper extends BaseHelper {
constructor(_cApp) {
super(_cApp);
this._props = {};
}
initialize() {
this._prepareConfigs();
this._createTransportObj();
this._createLoggerObj();
}
_prepareConfigs() {
this._props.dirname = this._configs.logsFolder;
this._props.filename = this._configs.filenameConvention;
this._props.datePattern = this._configs.datePattern;
this._props.maxSize = this._configs.maxSize;
this._props.level = this._configs.level;
this._props.timestamp = this._configs.timestamp;
this._props.prettyPrint = this._configs.prettyPrint;
}
_createTransportObj() {
var DailyRotateFile = winston.transports.DailyRotateFile;
this._transport = new DailyRotateFile(this._props);
}
_createLoggerObj() {
this._logger = winston.createLogger({
transports: [this._transport],
exitOnError: false
});
}
_log(type, error, description, stage, vars) {
var logMsg = {};
var msg = '';
var fileIndex = 3;
if(this._isError(error)) {
var err = error;
msg = error.message;
fileIndex = 1;
} else {
var err = new Error();
msg = error;
}
var caller_line = err.stack.split("at ")[fileIndex];
var index = caller_line.indexOf("(");
var lastIndex = caller_line.lastIndexOf(")");
index = caller_line.slice(index + 1, lastIndex);
var line = index.match(/:[0-9]+:/).toLocaleString();
line = line.replace(/[^0-9]/g, '');
var curTime = new FE.utils.date();
var timestamp = curTime.format('YYYY-MM-DD HH:MM:SS');
logMsg.level = type || 'info';
logMsg.time = timestamp || '';
logMsg.msg = msg || '';
logMsg.desc = description || '';
logMsg.stg = stage || '000';
logMsg.file = index || 'Not Found';
logMsg.stack = err.stack || 'Not Found';
logMsg.line = line || 'Not Found';
var logStr = JSON.stringify(logMsg);
this._logger.log(type, logMsg);
}
info(error, description, stage, vars) {
return this._log('info', error, description, stage, vars);
}
error(error, description, stage, vars) {
return this._log('error', error, description, stage, vars);
}
warn(error, description, stage, vars) {
return this._log('warn', error, description, stage, vars);
}
verbose(error, description, stage, vars) {
return this._log('verbose', error, description, stage, vars);
}
debug(error, description, stage, vars) {
return this._log('debug', error, description, stage, vars);
}
silly(error, description, stage, vars) {
return this._log('silly', error, description, stage, vars);
}
/**
* Checks if value is an Error or Error-like object
* #static
* #param {Any} val Value to test
* #return {Boolean} Whether the value is an Error or Error-like object
*/
_isError(val) {
return !!val && typeof val === 'object' && (
val instanceof Error || (
val.hasOwnProperty('message') && val.hasOwnProperty('stack')
)
);
}
}
module.exports = LoggerHelper;
Now I want to store my logs into a mysql db table as well. I did come across a winston plugin for Mongo but I don't see any support for storing it into a mysql db. Is there a way I can achieve this?
Thanks in advance.
I was facing the same issue recently. after doing some research I found one package 'winston-sql-transport' to save logs into mysql.
see this:
const { Logger } = require('winston');
const { SQLTransport } = require('./../lib/winston-sql-transport');
const logger = new Logger({
transports: [
new SQLTransport({
tableName: 'winston_logs',
})]
});
module.exports = logger;

Haxe IE9 xmlHTTPrequest issue

I'm having problems displaying my Haxe game in IE9. It's actually not displaying at all. We tracked down the issue inside the compiled JS file for Haxe and found out that the problem is within the haxe.HTTP API.
There are certain things that need to be checked and done for IE9 to work with xmlhttprequests. These things were not done in the Haxe API.
This is the http class without my fix:
this.url = url;
this.headers = new List();
this.params = new List();
this.async = true;
};
$hxClasses["haxe.Http"] = haxe.Http;
haxe.Http.__name__ = ["haxe","Http"];
haxe.Http.prototype = {
setParameter: function(param,value) {
this.params = Lambda.filter(this.params,function(p) {
return p.param != param;
});
this.params.push({ param : param, value : value});
return this;
}
,request: function(post) {
var me = this;
me.responseData = null;
var r = this.req = js.Browser.createXMLHttpRequest();
var onreadystatechange = function(_) {
if(r.readyState != 4) return;
var s;
try {
s = r.status;
} catch( e ) {
s = null;
}
if(s == undefined) s = null;
if(s != null) me.onStatus(s);
if(s != null && s >= 200 && s < 400) {
me.req = null;
me.onData(me.responseData = r.responseText);
} else if(s == null) {
me.req = null;
me.onError("Failed to connect or resolve host");
} else switch(s) {
case 12029:
me.req = null;
me.onError("Failed to connect to host");
break;
case 12007:
me.req = null;
me.onError("Unknown host");
break;
default:
me.req = null;
me.responseData = r.responseText;
me.onError("Http Error #" + r.status);
}
};
if(this.async) r.onreadystatechange = onreadystatechange;
var uri = this.postData;
if(uri != null) post = true; else {
var $it0 = this.params.iterator();
while( $it0.hasNext() ) {
var p = $it0.next();
if(uri == null) uri = ""; else uri += "&";
uri += encodeURIComponent(p.param) + "=" + encodeURIComponent(p.value);
}
}
try {
if(post) r.open("POST",this.url,this.async); else if(uri != null) {
var question = this.url.split("?").length <= 1;
r.open("GET",this.url + (question?"?":"&") + uri,this.async);
uri = null;
} else r.open("GET",this.url,this.async);
} catch( e1 ) {
me.req = null;
this.onError(e1.toString());
return;
}
if(!Lambda.exists(this.headers,function(h) {
return h.header == "Content-Type";
}) && post && this.postData == null) r.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
var $it1 = this.headers.iterator();
while( $it1.hasNext() ) {
var h1 = $it1.next();
r.setRequestHeader(h1.header,h1.value);
}
r.send(uri);
if(!this.async) onreadystatechange(null);
}
,onData: function(data) {
}
,onError: function(msg) {
}
,onStatus: function(status) {
}
,__class__: haxe.Http
};
and this is the code WITH the fix:
haxe.Http = function(url) {
this.url = url;
this.headers = new List();
this.params = new List();
this.async = true;
};
$hxClasses["haxe.Http"] = haxe.Http;
haxe.Http.__name__ = ["haxe","Http"];
haxe.Http.prototype = {
setParameter: function(param,value) {
this.params = Lambda.filter(this.params,function(p) {
return p.param != param;
});
this.params.push({ param : param, value : value});
return this;
}
,request: function(post) {
var me = this;
me.responseData = null;
var r = this.req = js.Browser.createXMLHttpRequest();
var onreadystatechange = function(_) {
console.log('in onreadystatechange function');
//if(r.readyState != 4) return;
console.log(r.responseText);
console.log('r.status: ' + r.status);
me.req = null;
me.onData(me.responseData = r.responseText);
};
if(typeof XDomainRequest != "undefined") {
console.log('XDomainRequest');
r.onload = onreadystatechange;
}
var uri = this.postData;
try {
console.log('calling r.open with url: ' + this.url);
r.open("GET",this.url);
} catch( e1 ) {
me.req = null;
this.onError(e1.toString());
return;
}
//r.send(uri);
//do it, wrapped in timeout to fix ie9
setTimeout(function () {
r.send();
}, 0);
//if(!this.async) onreadystatechange(null);
}
,onData: function(data) {
}
,onError: function(msg) {
}
,onStatus: function(status) {
}
,__class__: haxe.Http
};
Note that this only implements the IE9 fix without doing the if statements to keep support for other browsers. But it's really easy. the IF statement is simply
if(typeof XDomainRequest != "undefined") return new XDomainRequest();
Basically, I know what the issue is, I just have no idea how I can change these things within the core of Haxe itself.
Thanks.
https://github.com/HaxeFoundation/haxe/pull/3449
BAM! Got it working in a local version of my haxe. Fixed for all browsers and sent a pull request. :D
Good job on this one!
You have to contact the Haxe mailing list directly, most thinking heads are over there rather than here :)
https://groups.google.com/d/forum/haxelang

html, css and pure js for tree view

This is quite simple but I'm quite new with JavaScript. Here is the problem I'm facing.
I would like to have this tree view:
Route (index.html)
|--Tree 1 (tree1.html)
|--Child 1 (tree1child1.html)
|--Tree 2 (tree2.html)
|--Child 1 (tree2child1.html)
Each html will point to toggle.js to generate the tree view. My problem with the .js is: if I click on the Tree 2, Child 1 - it will show the correct page but pointing to the Tree 1, Child 1 selections as the child has same name. This is the script that I use.
function toggle(id) {
ul = "ul_" + id;
img = "img_" + id;
ulElement = document.getElementById(ul);
imgElement = document.getElementById(img);
if (ulElement) {
if (ulElement.className == 'closed') {
ulElement.className = "open";
imgElement.src = "./menu/opened.gif";
} else {
ulElement.className = "closed";
imgElement.src = "./menu/closed.gif";
}
}
} // toggle()
function searchUp(element, tagName) {
// look through the passed elements
var current = element;
do {
current = current.parentNode;
} while (current.nodeName != tagName.toUpperCase() && current.nodeName != "BODY");
return current.nodeName != tagName.toUpperCase() ? null : current;
}
function getAnchor(elements, searchText, exclude) {
// look through the passed elements
for (var i = 0; i < elements.length; i++) {
if (elements[i].innerHTML == searchText) {
if (exclude == null || exclude.innerHTML != elements[i].parentElement.innerHTML) {
// return the anchor tag
return elements[i];
}
}
if (elements[i].children != null) {
var a = getAnchor(elements[i].children, searchText, exclude);
if (a != null) {
return a;
}
}
}
return null;
}
function htmlEntities(str) {
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}
function select(tree) {
// NOTE: we need to escape the tree string to replace chevrons with their html equivalent in order to match generic strings correctly
var items = document.getElementById('menu').children; // .getElementsByTagName("a");
var anchor = getAnchor(items, htmlEntities(tree[0]), null);
var ul = searchUp(anchor, "ul");
for (var i = 1; i < tree.length; i++) {
anchor = getAnchor(ul.children, htmlEntities(tree[i]), anchor.parentElement);
ul = searchUp(anchor, "ul");
}
if (anchor != null) {
anchor.className = 'selected';
if (ul.className != 'open') {
toggle(ul.id.substr(3));
}
}
} // select()

Improving method performance

I wrote the following method that receives a list and updates the database based on certain criteria:
public void UpdateInventoryGoods(List<InventoryGoods> list, int id)
{
int index = 0;
var query = from inventoryGoods in context.InventoryGoods
where inventoryGoods.ParentId == id
select inventoryGoods;
List<InventoryGoods> goodsList = query.ToList();
using (var scope = new TransactionScope())
{
foreach (InventoryGoods i in list)
{
foreach (InventoryGoods e in goodsList)
{
if (index == 30)
{
index = 0;
context.SubmitChanges();
}
if (e.Gid == i.Gid && !getEventId(e.Id).HasValue && !e.ActionOn.HasValue)
{
e.Action = i.Action;
}
else if ((e.Gid == i.Gid && getEventId(e.Id).HasValue) && (e.Action != i.Action || i.ActionOn == DateTime.MinValue))
{
e.Action = i.Action;
e.ActionOn = null;
var allEvents = from invent in context.InventoryGoodsEvents
where invent.InventoryGood == e.Id
select invent;
List<InventoryGoodsEvents> inventoryGoodsEventsList = allEvents.ToList();
var events = from g in context.GoodsEvent
select g;
List<GoodsEvent> goodsEventList = events.ToList();
foreach (InventoryGoodsEvents goodsEvent in inventoryGoodsEventsList)
{
context.InventoryGoodsEvents.DeleteOnSubmit(goodsEvent);
foreach (GoodsEvent ge in goodsEventList)
{
if (ge.Id == goodsEvent.EventId)
{
ge.IsDeleted = true;
ge.DeletedOn = DateTime.Now;
ge.DeletedBy = System.Web.HttpContext.Current.User.Identity.Name;
}
}
}
}
++index;
}
}
context.SubmitChanges();
scope.Complete();
}
}
public int? getEventId(int InventoryGood)
{
var InventoryGoodsEvents = from i in context.InventoryGoodsEvents
where i.InventoryGood == InventoryGood
select i;
List<InventoryGoodsEvents> lst = InventoryGoodsEvents.ToList();
if (lst.Count() > 0)
{
return lst[0].EventId;
}
else
{
return null;
}
}
Though this method works well for about 500 or 1000 objects, it gets too slow or eventually times out when I feed it over 8000 objects or more.
So, where could I improve its performance a little?
Don't call the database in a loop.
Try moving the queries outside the loops like this:
public void UpdateInventoryGoods(List<InventoryGoods> list, int id)
{
int index = 0;
var query = from inventoryGoods in context.InventoryGoods
where inventoryGoods.ParentId == id
select inventoryGoods;
List<InventoryGoods> goodsList = query.ToList();
using (var scope = new TransactionScope())
{
var allEvents = from invent in context.InventoryGoodsEvents
where goodsList.Contains(invent.InventoryGood)
select invent;
List<InventoryGoodsEvents> inventoryGoodsEventsList = allEvents.ToList();
var events = from g in context.GoodsEvent
select g;
List<GoodsEvent> goodsEventList = events.ToList();
foreach (InventoryGoods i in list)
{
foreach (InventoryGoods e in goodsList)
{
if (index == 30)
{
index = 0;
context.SubmitChanges();
}
var eventId = getEventId(e.Id);
if (e.Gid == i.Gid && !eventId.HasValue && !e.ActionOn.HasValue)
{
e.Action = i.Action;
}
else if ((e.Gid == i.Gid && eventId.HasValue) && (e.Action != i.Action || i.ActionOn == DateTime.MinValue))
{
e.Action = i.Action;
e.ActionOn = null;
foreach (InventoryGoodsEvents goodsEvent in inventoryGoodsEventsList)
{
context.InventoryGoodsEvents.DeleteOnSubmit(goodsEvent);
foreach (GoodsEvent ge in goodsEventList)
{
if (ge.Id == goodsEvent.EventId)
{
ge.IsDeleted = true;
ge.DeletedOn = DateTime.Now;
ge.DeletedBy = System.Web.HttpContext.Current.User.Identity.Name;
}
}
}
}
++index;
}
}
context.SubmitChanges();
scope.Complete();
}
}
I'm no Linq expert, but I think you can probably improve getEventId (should be capital first letter btw) with something like
public int? GetEventId(int inventoryGood)
{
var firstInventoryGoodsEvent = context.InventoryGoodsEvents
.Where(i => i.InventoryGood == inventoryGood)
.FirstOrDefault();
// ...etc
}
The use of FirstOrDefault() means you don't process the whole list if you find a matching element.
There are probably other optimisations but it's quite difficult to follow what you're doing. As an example:
foreach (InventoryGoods i in list)
{
foreach (InventoryGoods e in goodsList)
{
}
}
i and e don't confer much meaning here. It might be obvious to you what they mean but they aren't very descriptive to someone who has never seen your code before. Similarly, list is not the best name for a List. List of what? Your variable name should describe it's purpose.
Edit:
I'm not sure about anything else. You seem to be using ToList() in a few places where as far as I can see it's not necessary. I don't know what effect that would have on performance, but someone cleverer than me could probably tell you.
You could also try hoisting a few of your values outside of loops, eg:
foreach (foo)
{
foreach (bar)
{
DeletedOn = DateTime.Now;
DeletedBy = System.Web.HttpContext.Current.User.Identity.Name;
}
}
can be re-written as
var deletedOn = DateTime.Now;
var deletedBy = System.Web.HttpContext.Current.User.Identity.Name;
foreach (foo)
{
foreach (bar)
{
DeletedOn = deletedOn;
DeletedBy = deletedBy;
}
}
Again, I'm not sure how much difference if any that would make, you'll need to test it and see.
It's not going in batches of 30, it's going in batches of 1.
There's a query with no criteria, so it loads the whole table. Is that your intention?
getEventId(e.Id) returns a consistent value. Don't call it twice (per loop).