I cannot figure out how to use the $dispatch method to send data from a Vue child to a Vue parent. I have a component with multiple instances as follows:
Vue.component('receipt', {
template: '#receipt-template',
data: function() {
return {
tip: ''
};
},
methods: {
addSale: function() {
this.sales.push(
{amount: 1, desc: '', price: 0.00}
);
},
removeSale: function(index) {
this.sales.splice(index, 1)
}
},
computed: {
subtotal: function() {
var result = 0;
this.sales.forEach(function (sale) {
return result += +sale.price;
});
var subtotal = Math.round(100 * result) / 100;
return subtotal.toFixed(2);
},
tax: function() {
var tax = this.subtotal * .08;
return tax.toFixed(2);
},
total: function() {
var total = Number(this.subtotal) + Number(this.tax) + Number(this.tip);
return total.toFixed(2);
this.$dispatch(this.total);
}
},
props: [ 'header', 'date', 'sales' ]
})
And my Vue instance looks like:
var vm = new Vue({
el: '#content',
data: {
sales1: [
{amount: 1, desc: "Dante's Inferno", price: 13.99},
{amount: 1, desc: "Espresso", price: 5.25},
{amount: 1, desc: "The Sun Also Rises", price: 11.99},
{amount: 1, desc: "Spanish Coffee", price: 1.99}
],
sales2: [
{amount: 1, desc: "Huckleberry Finn", price: 14.95},
{amount: 1, desc: "Americano", price: 2.29},
{amount: 1, desc: "Pride & Prejudice", price: 12.95},
{amount: 1, desc: "Black Tea Latte", price: 4.25},
{amount: 1, desc: "Scone", price: 3.25}
],
company: 'Between The Covers & Grinders Cafe'
},
computed: {
grand: function() {
}
}
})
I have multiple instances of the 'Receipt' component and therefore multiple values being computed from the component's computed 'total' function. How can I dispatch the values of the component's instances 'total' functions and get my 'grandtotal' in the parent instance
You have to create a method in your component that 'dispatches' an event, like this:
methods: {
yourMethodName: function(){
this.$dispatch('eventName', data);
}
}
eventName is the name of the event that your app will be expecting and will pick when it's dispatched, the data is just the data that will be available for use when the event is picked.
In your app instance you can just define a prop called 'events' pick the event and do something with it. Like this:
events: {
'eventName': function(data){
//do something with it
}
}
Like this, everytime 'yourMethod' (your component's method) is called it will dispatch the event named 'eventName' and your app will pick and handle it.
Hope that helps!
Related
I have a simplified order model that looks likes:
order = {
_id: 1,
productGroups:[
{ productId: 1, qty: 3 },
{ productId: 2, qty: 5 }
],
cancels:[]
}
Now I have an api that cancels part of the order.
The request could be something like cancel {productId:1, qty:2}, {productId:2, qty:2} from order where orderId:1. The result should be
order = {
_id: 1,
productGroups:[
{ productId: 1, qty: 2 },
{ productId: 2, qty: 2 }
],
cancels:[
{
productGroups:[
{ productId: 1, qty: 1 },
{ productId: 2, qty: 3 }
]
}
]
}
let order = await Order.findOneAndUpdate(
{
_id: id
},
{
$inc: {
'productGroups.$.qty': cancelQty //this is the part that needs fixing. how do I get different cancelQty according to productId
},
$push: {
cancels: {
products: cancelProductGropus
}
}
},
{ new: true }
);
Now I know I can just findOne, update the model with javascript, and then .save() the model. But if possible I would like to do this update in one go. Or if it is not possible, can I fix the schema so that I can do such update in a single request?
I must iterate over array, find correspondent objects in other array an merge the result in a object.
Assume I have three arrays
var users = [
{ name: "A", type: 2, level: 1 },
{ name: "B", type: 1, level: 2 }
]
var types = [
{ description: "Type 1", id: 1 },
{ description: "Type 2", id: 2 }
]
var levels = [
{ description: "Level 1", id: 1 },
{ description: "Level 2", id: 1 }
]
I want to have following result:
var users = [
{ name: "A", type: 2, level: 1, levelDescription: "Level 1", typeDescription: "Type 2" },
{ name: "B", type: 1, level: 2, levelDescription: "Level 2", typeDescription: "Type 1" }
]
I know I can achieve it like that
var usersObservable = RX.Observable.fromArray(users);
var typesObservable = Rx.Observable.fromArray(types);
var levelsOBservable = Rx.Observable.fromArray(levels);
var uiUsers= [];// not really needed because I will use the same users array again.
usersObservable.map(function(user) {
typesObservable.filter(function(type) {
return type.id == user.type;
}).subscribeOnNext(function(userType) {
user.typeDescription = userType.description;
});
return user;
}).map(function(user) {
levelsOBservable.filter(function(level) {
return level.id == user.levelId;
}).subscribeOnNext(function(level) {
user.levelDescription = level.description;
});
return user;
})
.subscribeOnNext(function(user) {
uiUsers.push(user);
})
I would like to have a solution without nested Observables.
Thanks.
I am not sure why you are using Rx at all for this problem. You have data in space (i.e. arrays), not data over time (i.e. an observable sequence). But you force these arrays into Rx to then create a very complicated solution.
I think you are looking for something like the answer here https://stackoverflow.com/a/17500836/393615 where you would join the source array types. In your case you just "inner-join" twice to combine all three data sets.
You can archive this by using the switchMap operator that combines the result of a filtered stream with the latest value of the original stream and uses a projection function to merge the results into a single object. This can be generalised in your example such that you can use a generic higher order function in both cases. See fiddle.
Full code (ES2015, RxJS5):
const users = [
{ name: "A", type: 2, level: 1 },
{ name: "B", type: 1, level: 2 }
];
const types = [
{ description: "Type 1", id: 1 },
{ description: "Type 2", id: 2 }
];
const levels = [
{ description: "Level 1", id: 1 },
{ description: "Level 2", id: 2 }
];
const users$ = Rx.Observable.from(users);
const types$ = Rx.Observable.from(types);
const levels$ = Rx.Observable.from(levels);
function join(s$, sourceProperty, targetProperty, streamProperty) {
return function(initObj) {
const stream$ = s$.filter(x => x.id === initObj[sourceProperty]);
return Rx.Observable.combineLatest(
Rx.Observable.of(initObj),
stream$,
(obj, streamObj) => {
const prop = streamObj[streamProperty];
return Object.assign({}, obj, { [targetProperty]: prop });
}
);
};
}
users$
.switchMap(join(types$, 'type', 'typeDescription', 'description'))
.switchMap(join(levels$, 'level', 'levelDescription', 'description'))
.subscribe(x => console.log(x));
I'm newbie with MongoDB.
I have created a mapReduce on my Person collection to group cities.
db.Person.find()
[{
name: 'Bob',
addresses: [
{
street: 'Vegas Street',
neighborhood: {
name: 'Center',
city: {
name: 'Springfield'
}
}
}, {
.....
}
]
}, {
....
}]
And this is my mapReduce:
db.Person.mapReduce(function() {
for (var i = 0; i < this.address.length-1; i++) {
var address = this.address[i];
emit(address.neighborhood.city.name, 1);
}
}, function(k, v) {
return v.length;
}, { out: 'City' });
Then I use this to list my cities:
db.City.find().sort({'_id:', 1})
[{
_id: 'Springfield',
value: 3
}, {
_id: 'City B',
value: 2
}, {
...
}]
My question is about the City data, I need run the mapReduce each time I insert, update or delete on my Personcollection or it runs automatically?
How do I have autoincrement ids in mongoose? I want my ids to start like 1, 2, 3, 4, not the weird id numbers mongodb creates for you?
Here's my schema:
var PortfolioSchema = mongoose.Schema({
url: String,
createTime: { type: Date, default: Date.now },
updateTime: { type: Date, default: Date.now },
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
Use mongoose-auto-increment:
https://github.com/codetunnel/mongoose-auto-increment
var mongoose = require('mongoose');
var autoIncrement = require('mongoose-auto-increment');
var connection = ....;
autoIncrement.initialize(connection);
var PortfolioSchema = new mongoose.Schema({
url: String,
createTime: { type: Date, default: Date.now },
updateTime: { type: Date, default: Date.now },
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
//Auto-increment
PortfolioSchema.plugin(autoIncrement.plugin, { model: 'Portfolio' });
module.exports = mongoose.model('Portfolio', PortfolioSchema);
Or if you prefer to use an additional field instead of overriding _id, just add the field and list it in the auto-increment initialization:
var PortfolioSchema = new mongoose.Schema({
portfolioId: {type: Number, required: true},
url: String,
createTime: { type: Date, default: Date.now },
updateTime: { type: Date, default: Date.now },
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
//Auto-increment
PortfolioSchema.plugin(autoIncrement.plugin, { model: 'Portfolio', field: 'portfolioId' });
If you want to have a incrementing numeric value in _id then the basic process is you are going to need something to return that value from a store somewhere. One way to do this is use MongoDB itself to store data that holds the counters for the _id values for each collection, which is described within the manual itself under Create and Auto-Incrementing Sequence Field.
Then as you create each new item, you use the implemented function to get that "counter" value, and use it as the _id in your document.
When overriding the default behavior here, mongoose requires that you both specify the _id and it's type explicitly with something like _id: Number and also that you tell it to no longer automatically try to supply an ObjectId type with { "_id": false } as an option on the schema.
Here's a working example in practice:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var counterSchema = new Schema({
"_id": String,
"counter": { "type": Number, "default": 1 }
},{ "_id": false });
counterSchema.statics.getNewId = function(key,callback) {
return this.findByIdAndUpdate(key,
{ "$inc": { "counter": 1 } },
{ "upsert": true, "new": true },
callback
);
};
var sampleSchema = new Schema({
"_id": Number,
"name": String
},{ "_id": false });
var Counter = mongoose.model( 'Counter', counterSchema ),
ModelA = mongoose.model( 'ModelA', sampleSchema ),
ModelB = mongoose.model( 'ModelB', sampleSchema );
async.series(
[
function(callback) {
async.each([Counter,ModelA,ModelB],function(model,callback) {
model.remove({},callback);
},callback);
},
function(callback) {
async.eachSeries(
[
{ "model": "ModelA", "name": "bill" },
{ "model": "ModelB", "name": "apple" },
{ "model": "ModelA", "name": "ted" },
{ "model": "ModelB", "name": "oranage" }
],
function(item,callback) {
async.waterfall(
[
function(callback) {
Counter.getNewId(item.model,callback);
},
function(counter,callback) {
mongoose.model(item.model).findByIdAndUpdate(
counter.counter,
{ "$set": { "name": item.name } },
{ "upsert": true, "new": true },
function(err,doc) {
console.log(doc);
callback(err);
}
);
}
],
callback
);
},
callback
);
},
function(callback) {
Counter.find().exec(function(err,result) {
console.log(result);
callback(err);
});
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
For convience this implements a static method on the model as .getNewId() which just descriptively wraps the main function used in .findByIdAndUpdate(). This is a form of .findAndModify() as mentioned in the manual page section.
The purpose of this is that it is going to look up a specific "key" ( actually again the _id ) in the Counter model collection and perform an operation to both "increment" the counter value for that key and return the modified document. This is also aided with the "upsert" option, since if no document yet exists for the requested "key", then it will be created, otherwise the value will be incremented via $inc, and it always is so the default will be 1.
The example here shows that two counters are being maintained independently:
{ _id: 1, name: 'bill', __v: 0 }
{ _id: 1, name: 'apple', __v: 0 }
{ _id: 2, name: 'ted', __v: 0 }
{ _id: 2, name: 'oranage', __v: 0 }
[ { _id: 'ModelA', __v: 0, counter: 2 },
{ _id: 'ModelB', __v: 0, counter: 2 } ]
First listing out each document as it is created and then displaying the end state of the "counters" collection which holds the last used values for each key that was requested.
Also note those "weird numbers" serves a specific purpose of always being guranteed to be unique and also always increasing in order. And note that they do so without requiring another trip to the database in order to safely store and use an incremented number. So that should be well worth considering.
Situation:
kendo DataSource
var ordersDataSource = new kendo.data.DataSource({
type: "odata",
transport: {
read: {
url: "http://localhost/odata.svc/Orders?$expand=OrderDetails"
}
},
schema: {
type: "json",
data: function(response){
return response.value;
}
total: function(response){
return response['odata.count'];
}
},
serverPaging: true,
serverFiltering: true,
serverSorting: true
})
the json data read from the odata source is like:
{
odata.metadata: "xxxx",
odata.count: "5",
value: [
{
OrderId: 1,
OrderedDate: "2013-02-20",
OrderInfoA: "Info A",
OrderInfoB: "Info B"
OrderDetails: [
{
OrderDetailId: 6,
OrderDetailInfoC: "Info C",
OrderDetailInfoD: "Info D"
},
{
//Another OrderDetail's data
}
]
},
{
// Another Order's data
}
]
}
Question 1:
1.If I wanna define a "computed" property: OrderedDateRelative, which should be the number of days between Today(2013-02-25) and the Day the Order was Created(2013-02-20), Like: "5 days ago", HOW can i achieve this in the client side?
Answer to Question1: http://jsbin.com/ojomul/7/edit
Question 2 --UPDATE--
2.Every Order has its Nested Property OrderDetails, so is it possible to define a Calculated Field for the Nested OrderDetails Property? Like: OrderDetailInfoCAndD for each OrderDetail, and the value should be something like: OrderDetailInfoC + OrderDetailInfoD, which is "Info C Info D"?
Thanks,
dean
You can create a calculated field by specifying the model of the data source:
dataSource = new kendo.data.DataSource({
data: [
{ first: "John", last: "Doe" },
{ first: "Jane", last: "Doe" }
],
schema: {
model: {
// Calculated field
fullName: function() {
return this.get("first") + " " + this.get("last");
}
}
}
});
Here is a live demo: http://jsbin.com/ojomul/1/edit
Here is a way to use calculated field in Kendo Grid.
var crudServiceBaseUrl = "http://demos.telerik.com/kendo-ui/service",
dataSource = new kendo.data.DataSource({
transport: {
read: {
url: crudServiceBaseUrl + "/Products",
dataType: "jsonp"
}
},
pageSize: 20,
schema: {
model: {
total: function (item) {
return this.UnitPrice * this.UnitsInStock;
}
}
}
});
$("#grid").kendoGrid({
dataSource: dataSource,
pageable: true,
height: 550,
sortable: true,
filterable: true,
toolbar: ["create"],
columns: [
{ field: "UnitPrice", title: "Unit Price"},
{ field: "UnitsInStock", title: "Units In Stock", width: "120px" },
{ field: "total()", title: "Total" }]
});
Below an example to use it in a grid. It can then also sort the column.
$("#grid").kendoGrid({
dataSource: {
data: [
{ first: "John", last: "Doe" },
{ first: "Jane", last: "Doe" }
],
schema: {
model: {
// Calculated field
fullName: function() {
return this.first + " " + this.last;
},
fields: {
first: { type: "string" },
last: { type: "string" }
}
}
}
},
columns: [
{
// Trigger function of the Calculated field
field: "fullName()",
title: "Fullname"
},
{
field: "first",
title: "firstname"
}
]
});