Meteor Storing and Retrieving Custom Objects in Session - javascript

I have a custom shopping cart object that I created and put it in the lib folder.
ShoppingCart = function ShoppingCart() {
this.Items = new Array();
this.grandTotal = 0.00;
}
ShoppingCart.prototype.addItem = function(Item){
this.Items.push(Item);
this.Items.sort();
this.calculateTotal();
}
I initialized the shopping cart and store it as Session.set('shoppingCart') during the page created phase.
Template.loginStatus.created = function() {
Session.set('loginShown',false);
if(!Session.get('shoppingCart')){ //set default if session shopping cart not exist
var cart = new ShoppingCart();
Session.setDefault('shoppingCart',cart);
}
Then when user click add item to cart, it will trigger this logic:
var cart = Session.get('shoppingCart');
cart.addItem(item);
Session.set('shoppingCart',cart);
Somehow, it does not work. When I take a look ad the chrome console it says undefined is not a function, pointing at cart.addItem(item) line. If I change it to this, it will work , but of course since everytime new shopping cart is created, I cannot accumulate items in the cart.
var cart = new ShoppingCart();
cart.addItem(item);
Session.set('shoppingCart',cart);
How should I store and retrieve the object from session properly? It looks like the returned object from the Session.get() somehow not considered as ShoppingCart. Did I miss any type cast?

As #Peppe L-G mentioned, you can only store EJSONs in Session. To store your custom object, you need to be able to manually transform it to and from EJSONs. Example:
_.extend(ShoppingCart, {
fromJSON: function(json) {
var obj = new ShoppingCart();
obj.grandTotal = json.grandTotal;
obj.Items = json.Items;
return obj;
},
});
_.extend(ShoppingCart.prototype, {
toJSON: function() {
return {
grandTotal: this.grandTotal,
Items: this.Items,
};
},
});
Then you can save it to Session:
Session.set('shoppingCart', cart.toJSON());
and restore:
ShoppingCart.fromJSON(Session.get('shoppingCart'));

I ran into the same problem. Essentially what is happening Meteor Sessions (and Collections) can only store EJSON types, so your ShoppingCart custom type is retrieved from the Session as a normal Object.
While you can manually transform to and from EJSONs, you may end up needing to do this repeatedly in a lot of different places. If your ShoppingCart is a member of another object, you'll have to also manually transform the member. It's better to use EJSON.addType to tell Meteor how to handle it automatically anywhere you store or retrieve an object of that type.
There's a great demo of this here: https://www.eventedmind.com/feed/meteor-create-a-custom-ejson-type. Full docs are also here: http://docs.meteor.com/#/full/ejson. But a short version is this:
Add a method to your custom type called typeName:
ShoppingCart.prototoype.typeName = function(){
return "ShoppingCart";
};
Add another method called toJSONValue:
ShoppingCart.prototype.toJSONValue = function(){
/* return a JSON compatible version of your object */
};
And finally, add the custom type to EJSON with:
EJSON.addType("ShoppingCart", function fromJSONValue(value){
/* return an object of your custom type from the JSON object 'value' */
};
NOTE: the "Type Name" in steps 1 and 3 must match exactly.

Related

How to get data as Object from dataTransfer?

I do the following:
const blockOrField = new Block();
ev.dataTransfer.setData("data", blockOrField);
When I get data in another place:
cosnt data = e.dataTransfer.getData("data");
I get data as [object Object] instead real instrance.
Before passing to data I see that it is instance:
if (blockOrField instanceof FieldDefinition) {
alert("works");
}
ev.dataTransfer.setData("data", blockOrField);
I know it should be serialized to string JSON, but I have complicated instance with composition.
If you look at the documentation for setData() it specifically says "A DOMString representing the data to add to the drag object.". So you are out of luck trying to store an object reference there.
What I would do here is create a another object somewhere and store the needed reference there with an id.
const dataTransferCache = {};
function onDragStart(ev) {
const block = new Block();
const id = GetRandomId(); // Just get an id somehow
dataTransferCache[id] = block;
ev.dataTransfer.setData("data", id);
}
function onDragEnd(ev) {
const id = ev.dataTransfer.getData("data");
const block = dataTransferCache[id];
delete dataTransferCache[id]; // Remove the value again
}
This would even support multi touch dragging if that is somehow needed. If this needs to be shared between components you could simply put the dataTransferCache in a separate file and include a reference to in in both components.

Backbone model property getting updated on update of another property

Here is my model:
var SendAlertsModel = Backbone.Model.extend({
defaults: {
customSubject: "",
customNote: "",
userList:[],
alertUserList:[]
}
});
Inside view:
initialize: function(options) {
var self= this;
if(_.isUndefined(options)===false){
self.model= new SendAlertsModel();
self.loggedInUser = app.user;
self.model.set("userList",options.previousTabData.get("userList"));
self.model.set("alertUserList",options.previousTabData.get("userList"));
self.model.get("alertUserList").push(self.loggedInUser);
}
},
The issue which i am facing here is when i push the loggedInUser to alertUserList array, it automatically pushes the same loggedInUser to userList array.
Please give your suggestions on this.
Thanks
//create new alerts model
model = new SendAlertsModel();
//assign logged in user to local variable
loggedInUser = app.user;
//set the user list to be the user list from the previous tab data
model.set("userList",options.previousTabData.get("userList"));
//set the alertUserList to be the previous tab user list by reference
model.set("alertUserList",options.previousTabData.get("userList"));
//push the logged in user into the alert user list
model.get("alertUserList").push(self.loggedInUser);
I think the issue occurs when you set the alertUserList to be the userList. As the user list is an object the alertUserList now contains a reference to the userList. It's not a copy. When you update the alertUserList you are actually updating the userList too.
Think of it like this:
var alertUserList = userList = {some object in memory};
In this line here you will want to create a copy rather:
model.set("alertUserList",options.previousTabData.get("userList"));
I'm not sure of what data type userList is, so it will depend on that. If you only need a shallow copy then you could do this using your underscore/lodash library (I assume that is what the "_" is):
model.set("alertUserList",_.clone(options.previousTabData.get("userList")));

Ext js store has Many copy to another store

I have data store "a" with hasMany attribute,trying to create new store "b" by copying hasMany records of "a" with below code,but when i use b store for combo box it throws error saying record.length is undefined
var b = Ext.create("Ext.data.store"{
model:'service'});
Ext.getStore("a").each(function(record,id){
for(var i in record){
b.add(record.raw.subservice);
}
})
It works for me, try this. I hope it helps.
function deepCloneStore (source) {
var target = Ext.create ('Ext.data.Store', {
model: source.model
});
Ext.each (source.getRange (), function (record) {
var newRecordData = Ext.clone (record.copy().data);
var model = new source.model (newRecordData, newRecordData.id);
target.add (model);
});
return target;
}
Original source code reference.
You are trying to iterate store, instead you need to iterate store.data.items.
And you do not need for cycle for record.
Try this
var b = Ext.create("Ext.data.store"{
model:'service'
});
Ext.getStore("a").data.items.each(function(record,id){
//You can add multiple records by calling .add method once
b.add(record.data.subServiceItems);
});
Write if this does not help.

Javascript - Emptying Object instead of Creating new Object

After each click, I intend to empty object editProductList. My code below is instead creating an additional new object editProductList instead of emptying the original editProductList. How do I ensure I'm emptying editProductList instead of creating a new editProductList after clicking it once more?
After the first clicking on the 'devices' column, then #edit_product_add_btn,
I'm logging:
product name, qty: Remote Tag 6
After the second clicking on the 'devices' column, then #edit_product_add_btn, the previous object remains, and it updates both the original object and new one at the same time
product name, qty: Remote Tag 7
product name, qty: Remote Tag 6
Why is it creating an additional object of the same editProductList instead of emptying the original one?
EditableGrid.prototype.mouseClicked = function(e) {
var editProductList = {};
$.ajax({
//...
success: function(response) {
editProductList = JSON.parse(response);
console.log('editProductList direct from DB: ', editProductList);
//adding products into editProductList
$('#edit_product_add_btn').on('click', function(e) {
var in_editProductList = false;
for (var product in editProductList) {
if (editProductList.hasOwnProperty(product)) {
if (editProductList[product].name === productType) {
//...
console.log('product name, qty: ', editProductList[product].name, editProductList[product].qty);
in_editProductList = true;
break;
}
}
}
if (in_editProductList === false) {
//...
var new_product_obj = { name: productType, qty: qty };
editProductList[Object.size(editProductList)] = new_product_obj;
}
});
});
}
After deconstructing your code example it became clear that you want to maintain a shopping cart.
If the user adds a product that also is already in the cart, it should simply increase the quantity of the existing item.
If the user adds a new product, it should append it to the cart.
In both cases the screen should be updated accordingly.
So there are two tasks here. Maintain a list and update the screen.
For updating the screen it is helpful to use an HTML templating library. This helps code readability and reduces the risk surface (no more manual HTML building from strings = less XSS risk). I used Mustache.js in the following example.
It is also helpful to separate the tasks so function size stays manageable.
Note how I use a custom jQuery event (dataUpdated) to decouple screen updating from list maintenance:
$(function () {
var editProductList = [];
var productListItems = Mustache.parse('{{#.}}<li class="list-group-item"><span class="badge">{{qty}}</span>{{name}}<button type="button" class="close" aria-hidden="true">×</button></li>{{/.}}');
EditableGrid.prototype.mouseClicked = function (e) {
if (this.getColumnName(columnIndex) == 'devices') {
$.post('get_requested_devices.php', {
table: this.name,
request_id: this.getRowId(rowIndex)
})
.done(function (response) {
editProductList = response;
$('#edit_product_list').trigger("dataUpdated");
});
}
};
$('#edit_product_list').on("dataUpdated", function () {
var listItems = Mustache.render(productListItems, editProductList);
$('#edit_product_list').empty().append(listItems);
});
$('#edit_product_add_btn').on('click', function (e) {
var qty = parseInt($('#edit_product_qty').val().trim(), 10);
var name = $('#edit_product_type').text().trim();
var existingProduct;
if (qty > 0) {
existingProduct = $.grep(editProductList, function (product) {
return product.name === name;
});
if (existingProduct) {
existingProduct.qty += qty;
} else {
editProductList.push({
name: name,
qty: qty
});
}
$('#edit_product_list').trigger("dataUpdated");
} else {
alert('Enter a number greater than 0');
}
});
});
Warning The above code contains references to two undefined global variables (columnIndex and rowIndex). I have no idea where they come from, I just carried them over from your code. It is a bad idea to maintain global variables for a number of reasons, the biggest one being that many nasty bugs can be traced back to global variables. Try to replace those references, either by function results or by local variables.
Recommendation This situation is the perfect use case of MVVM libraries like Knockout.js. They are designed to completely take over all UI updating for you, so all you need to do is to maintain the data model of the shopping cart. You might want to consider switching.
It's not so clear what you are trying to do.
You seem to be defining a click handler as a method of EditableGrid. So the user will need to click something for the ajax call to be executed, and then click #edit_product_add_btn to load the results into a variable which is local to the first handler? Presumably you are doing something with editProductList after the ajax call comes back, if not loading it at all is pointless, since you can't access it from anywhere else. Perhaps you want this.editProductList instead, so it is accesible from the rest of the "class"?
Are you sure you don't mean to do something like this?
EditableGrid.prototype.mouseClicked = function(e) {
var self = this;
$.ajax({
//...
success: function(response) {
self.editProductList = JSON.parse(response);
}
});
};
About mutating the current object instead of creating a new one: I don't think you gain much from trying to do this. The old object will be garbage collected as soon as the variable points to the new one. You are creating an object as soon as you do JSON.parse, so there's no escaping that.
If I missundersood your intentions, please clarify your question.

How to dynamically append a record into an Array in flex?

I have an mxml view in flex, and I need to dynamically add data to a DataGrid component.
This is where the DataGrid is initialized:
<mx:DataGrid id="myGrid" width="100%"
dataProvider="{initDG}" >
<mx:columns>
<mx:DataGridColumn dataField="Identifier" />
<mx:DataGridColumn dataField="Name" />
</mx:columns>
</mx:DataGrid>
This is the script part:
private var DGArray:Array = new Array;
[Bindable]
public var initDG:ArrayCollection;
private function onCreation():void{
initData();
}
public function initData():void {
initDG=new ArrayCollection(DGArray);
}
private function onShow():void{
for (var child:Object in children) {
var details:Array = null;
if (child instanceof String) {
var test:String = children[child].toString();
details = test.split(",");
}
//Here I need to make an object like this one:
// record = {Identifier: details[0] , Name: details[1]};
this.DGArray.push(the record created);
}
}
I did this method because it's working if DGArray was a static Array:
private var DGArray:Array = [
{Identifier:'001', Name:'Slanted and Enchanted'},
{Identifier:'002', NAme:'Brighten the Corners'}];
Can anyone tell me how to create the record and add it to DGArray?
Thanks:)
In short
Add or remove items through the ArrayCollection instance instead of through the Array instance.
And here's why
ArrayCollection - as its name suggests - is in fact nothing but a wrapper around Array, adding some functionality to it that comes in handy when working with the Flex framework.
So when you do
initDG.addItem(theNewItem);
that new item will automatically also be added to the underlying Array.
Additionally this function will also dispatch a CollectionEvent, which will notify the DataGrid that the data in its dataProvider has changed and it should be redrawn to reflect those changes.
If on the other hand you do
DGArray.push(theNewItem);
like you did, you directly alter the underlying Array. This doesn't really break anything; you'll still be able to acces the new item through e.g. ArrayCollection.getItemAt() as well, but the DataGrid was never notified of the change, hence it didn't redraw and keeps displaying the old data.

Categories