Calling Meteor methods in React components - meteor

Currently I'm working on a project based on Meteor as back end and React as front end. I really enjoyed simplicity untill I removed insecure package and have to deal with Meteor methods. Right now I need to perform a basic insert operation and I'm just stucked!
I have a form as component (in case eventually I'd like to use this form not only for inserting items but for editing those items as well) and here's my code for this form:
AddItemForm = React.createClass({
propTypes: {
submitAction: React.PropTypes.func.isRequired
},
getDefaultProps() {
return {
submitButtonLabel: "Add Item"
};
},
render() {
return (
<div className="row">
<form onSubmit={this.submitAction} className="col s12">
<div className="row">
<div className="input-field col s6">
<input
id="name"
placeholder="What"
type="text"
/>
</div>
<div className="input-field col s6">
<input
placeholder="Amount"
id="amount"
type="text"
/>
</div>
</div>
<div className="row">
<div className="input-field col s12">
<textarea
placeholder="Description"
id="description"
className="materialize-textarea">
</textarea>
</div>
</div>
<div className="row center">
<button className="btn waves-effect waves-light" type="submit">{this.props.submitButtonLabel}</button>
</div>
</form>
</div>
);
}
});
This chunk of code is used as a form component, I have a prop submitAction which I use in let's say add view:
AddItem = React.createClass({
handleSubmit(event) {
event.preventDefault();
const
name = $('#name').val(),
amount = $('#amount').val(),
description = $('#description').val();
Items.insert(
{
name: name,
range: range,
description: description,
createdAt: new Date(),
ownerId: Meteor.userId()
},
function(error) {
if (error) {
console.log("error");
} else {
FlowRouter.go('items');
};
}
);
},
render() {
return (
<div className="row">
<h1 className="center">Add Item</h1>
<AddItemForm
submitButtonLabel="Add Event"
submitAction={this.handleSubmit}
/>
</div>
);
}
});
As you can see I directly grab values by IDs then perform insert operation which works absolutely correct, I can even get this data displayed.
So now I have to remove insecure package and rebuild the whole operation stack using methods, where I actually stucked.
As I understand all I should do is to grab same data and after that perform Meteor.call, but I don't know how to pass this data correctly into current method call. I tried considering this data right in the method's body which doesn't work (I used the same const set as in AddItem view). Correct me if I'm wrong, but I don't think this method knows something about where I took the data (or may be I don't really get Meteor's method workflow), so by this moment I ended up with this code as my insert method:
Meteor.methods({
addItem() {
Items.insert({
name: name,
amount: amount,
description: description,
createdAt: new Date(),
ownerId: Meteor.userId()
});
}
});
and this is how I changed my handleSubmit function:
handleSubmit(event) {
event.preventDefault();
const
name = $('#name').val(),
amount = $('#amount').val(),
description = $('#description').val();
Meteor.call('addItem');
},
Also I tried declaring method like this:
'addItem': function() {
Items.insert({
// same code
});
}
but it also didn't work for me.
Again, as I understand the problem isn't about data itself, as I wrote before it works just right with insecure package, the problem is how the heck should I get this data on the server first and right after that pass this to the client using methods (also console gives no even warnings and right after I submit the form, the page reloads)?
I've already seen some tutorials and articles in the web and didn't find desicion, hope to get help here.

You can add your data as parameters in your Meteor call function. You can also add a callback function to check on the success of the call.
handleSubmit(event) {
event.preventDefault();
const
name = $('#name').val(),
amount = $('#amount').val(),
description = $('#description').val();
Meteor.call('addItem', name, amount, description, function(err, res) {
if (err){
console.log(JSON.stringify(err,null,2))
}else{
console.log(res, "success!")
}
});
},
In your Meteor methods:
Meteor.methods({
addItem(name, amount, description) {
var Added = Items.insert({
name: name,
amount: amount,
description: description,
createdAt: new Date(),
ownerId: Meteor.userId()
});
return Added
}
});

Related

Pushing data to object in different component using POST

TL;DR I want to show submitted posts instantly instead of having to refresh my page
Using the Wordpress REST API I am able to create a new post without any issue. The post is being displayed as soon as the page refreshes, so what I want to do is update the posts object in my Hello.vue file as soon as I create that post so I don't need to refresh to show my newest posts.
I'm not really sure where to start - I've removed all of the experiments I've done so far (importing Post in Create, defining props, pushing to an array, reading about object reactivity on the official Vue documentation, nothing helped).
My App.js consists of the <router> object which shows Hello.vue and a component called Create which displays the Create.vue component. This is how my app currently looks like:
My App.vue file:
<template>
<div id="app">
<section class="posts">
<router-view></router-view>
<create></create>
</section>
</div>
</template>
<script>
import Create from '#/components/Create.vue'
export default {
name: 'app',
components: {
Create
}
}
</script>
<style lang="scss">
#import '../src/assets/styles/style.scss'
</style>
My Hello.vue which displays all the posts:
<template>
<div>
<section class="posts__Feed">
<ul class="posts__List">
<post v-for="item in posts" :item="item" :key="item.id"></post>
</ul>
</section>
</div>
</template>
<script>
var postsUrl = '/wp-json/wp/v2/posts/'
import Post from '#/components/Post.vue'
export default {
name: 'hello',
props: ['responseData'],
components: {
Post
},
data () {
return {
posts: []
}
},
beforeCreate () {
this.$http.get(postsUrl).then((response) => {
this.posts = response.data
})
}
}
</script>
And finally, the Create.vue file which creates the post:
<template>
<div>
<section class="posts__Create">
<form class="posts__CreateForm" v-on:submit="createPosts">
<div class="posts__CreateFormWrapper" v-bind:class="{ 'is-Loading': loading }">
<p>
<input v-model="formInfo.title" type="text" name="title" id="title" placeholder="Name" :disabled="formSent">
</p>
<p>
<textarea v-model="formInfo.content" name="content" id="content" cols="20" rows="10" maxlength="140" placeholder="Message" :disabled="formSent"></textarea>
</p>
<p>
<button :disabled="formSent">Send</button>
</p>
</div>
</form>
</section>
</div>
</template>
<script>
var postsUrl = '/wp-json/wp/v2/posts/'
export default {
name: 'create',
data () {
return {
formInfo: [],
responseData: [],
loading: false,
formSent: false
}
},
methods: {
createPosts (e) {
e.preventDefault()
var info = this.formInfo
// Check if fields are empty
if (this.formInfo.title && this.formInfo.content) {
this.loading = true
// POST
this.$http.post(postsUrl, info).then((response) => {
this.formSent = true
this.loading = false
// get body data
this.responseData = response.data
})
}
} // EOF createPosts
}
}
</script>
Any help would be much appreciated!
I ended up using an event bus as suggested by wotex. First, I've createad a file called bus.js with the below code:
import Vue from 'vue'
export const EventBus = new Vue()
Next, import bus.js to both .vue layouts using:
import { EventBus } from '#/bus.js'
Now emit the event as soon as a new post is created (this is sitting in my axios POST request inside the Create.vue file):
EventBus.$emit('newPost', this.responseData)
And finally, check if the event has happened on the other end (my Hello.vue file):
EventBus.$on('newPost', function (postData) {
Thanks for pointing me in the right direction!

Meteor, publish:composite. how to access joined data in the template?

So I used publishComposite to do a collection join in Meteor. I have a parent collection (Subscriptions) with a user_id foreign key. I look up the user name in the Meteor.users collection to get the actual username, but how do I actually print this in the html template. My subscription data is there but how do I actually refer to the username?
Here is the publish code:
//publish subscriptions course view
Meteor.publishComposite('adminCourseSubscriptions', function(courseId){
return {
//get the subs for the selected course
find: function(){
return Subscriptions.find(
{course_id: courseId}
);
},
children:
[
{
//get the subscriber details for the course
find: function(sub){
return Meteor.users.find({_id:sub.user_id});
}
}
]
};
});
here are the template subdcriptions:
Template.adminCourseDetail.helpers({
courseDetail: function(id){
var id = FlowRouter.getParam('id');
return Courses.findOne({ _id: id });
},
courseSubscriptions: function(){
var id = FlowRouter.getParam('id');
return Subscriptions.find({course_id:id})
},
users: function(){
return Meteor.users.find();
}
});
and the template (which is garbage) ps the course details come from a separate collection. It was easier and I think more performant to get the details separately and this works fine. It's just the username that I cannot display correctly:
<template name="adminCourseDetail">
<h1>Course Details</h1>
<p>Title: {{courseDetail.title}}</p>
<p>Description: {{courseDetail.description}}</p>
<p>Start Date: {{courseDetail.startDate}}</p>
<p>Number of sessions: {{courseDetail.sessions}}</p>
<p>Duration: {{courseDetail.duration}}</p>
<p>Price: {{courseDetail.price}}</p>
<p>{{userTest}}</p>
edit
delete
<h2>Course Subscriptions</h2>
{{#each courseSubscriptions}}
<div class="row">
<div class="col-md-3">{{username}}</div>
<div class="col-md-3">{{sub_date}}</div>
</div>
{{/each}}
</template>
Thanks in advance for any suggestions!
As far as I understand your question, documents of the Subscriptions collection contain only the attribute user_id, referencing the corresponding user document in the Meteor.users collection. If this is the case, then you need to add an additional template helper which returns the username:
Template.adminCourseDetail.helpers({
// ...
getUsername: function() {
if (this.user_id) {
let user = Meteor.users.find({
_id: this.user_id
});
return user && user.username;
}
return "Anonymous";
}
// ...
});
After that, just replace {{username}} with {{getUsername}}:
<template name="adminCourseDetail">
<!-- ... -->
<h2>Course Subscriptions</h2>
{{#each courseSubscriptions}}
<div class="row">
<div class="col-md-3">{{getUsername}}</div>
<div class="col-md-3">{{sub_date}}</div>
</div>
{{/each}}
<!-- ... -->
</template>
Probably you misunderstood the concept of the reywood:publish-composite package. Using Meteor.publishComposite(...) will just publish a reactive join, but it will not return a new set of joined data.
For anyone else having a similar issue and looking at my specfic example. In my case the following code worked. Based on Matthias' answer:
In the template helper:
getUsername: function() {
let user = Meteor.users.findOne({
_id: this.user_id
});
return user;
}
and then in the template:
{{getUsername.username}}
My each block is looping through the cursor returned from the subscriptions collection rather than the course collection which is why it is simpler than the code Matthias provided.

React event handlers not binding properly

I am building a React app with Semantic UI in Meteor. I have had two places where event handlers don't seem to be functioning in any capacity, and I haven't found anything online with a problem to the same extent.
Below is my React class. I have tried various ways of calling the eventHandler methods, but nothing works. That also seems irrelevant since I can't even get an anonymous function to run.
SaveSearchPopout = React.createClass({
getInitialState: function() {
return {username: "", queryname: ""};
},
handleUsernameChange:function(e) {
console.log(e.target.value);
this.setState({username: e.target.value})
},
handleQuerynameChange:function(e) {
this.setState({queryname: e.target.value})
},
handleSave:function(e) {
console.log("handling save");console.log(e);
e.preventDefault();
alert("saving!");
return false;
},
render: function() {
console.log(this);
return (
<div className="ui modal saveSearchPopout">
<div className="header">Save Search</div>
<div className="content">
<form className="ui form" onSubmit={function() {console.log("test");}}>
<div className="field">
<input type="text" name="username"
placeholder="Username"
value={this.state.username}
onChange={function() {console.log("update")}} />
</div>
<div className="field">
<input type="text" name="queryname"
placeholder="Name this search"
value={this.state.queryname}
onChange={this.handleQuerynameChange}></input>
</div>
<div className="actions">
<div className="ui cancel button">Cancel</div>
</div>
<button type="submit">Click</button>
<button className="ui button" type="button"
onClick={function() {console.log("saving");}}>Save</button>
</form>
</div>
</div>
);
}
});
The class is rendered from another classes method which looks like:
saveSearch: function() {
var backingDiv = document.createElement('div');
backingDiv.id = 'shadowPopupBack';
document.getElementsByClassName('content-container')[0].appendChild(backingDiv);
ReactDOM.render(<SaveSearchPopout />, backingDiv);
//this.props.saveSearch;
$('.ui.modal.saveSearchPopout')
.modal({
closeable:false,
onDeny: function() {
var container = document.getElementsByClassName('content-container')[0];
var modalContainer = document.getElementById('shadowPopupBack');
container.removeChild(modalContainer);
}
})
.modal('show');
},
The only button that works is the Semantic UI cancel button.
Has anyone else run into this or have any idea what I am missing. Thanks for the help.
Don't know if this is the case, but in newer versions of React (or JSX), when you pass a function to an HTML component or a custom component, that function is not automatically bound to this instance.
You must bind them manually. For example:
onChange={this.handleQuerynameChange.bind(this)}
Or you could use arrow functions, because they will automatically bind to this:
onChange={e => this.handleQuerynameChange(e)}
I eventually found the answer here. Semantic UI modal component onClose with React
and I haven't worked through it yet, but it looks like this is more Reactive solution than using jQuery to bind eventHandlers: http://www.agilityfeat.com/blog/2015/09/using-react-js-and-semantic-ui-to-create-stylish-apps.
Hope this is helpful to someone else.

angular-file-upload: additional properties/options to each file in a multi-file upload?

I'm using nervgh's angular-file-upload, https://github.com/nervgh/angular-file-upload/wiki/Module-API.
Is there a way to use the angular-file-upload and allow additional properties to each file when doing a multi-file upload?
I'm using their image sample to start out with: http://nervgh.github.io/pages/angular-file-upload/examples/image-preview/
Trying to add a boolean to each file that the user can set and then I use that on the server side when it's picked up.
You can use formData property shown in Properties section to send to server whatever you need.
formData {Array}: Data to be sent along with the files.
If you're using PHP in server side, I think this post can help you out.
The question is rather old, but as the documentation didn't really help me much, I would like to note down my solution here:
This is how my html looks like (look for "options"):
<div ng-controller="UploadCtrl2" nv-file-drop="" uploader="uploader" filters="customFilter">
<div class="progress progress-xs margin-top-5 margin-bottom-20">
<div class="progress-bar" role="progressbar" ng-style="{ 'width': uploader.progress + '%' }"></div>
</div>
<div class="row">
<div class="col-md-6">
<div ng-show="uploader.isHTML5">
<div class="well my-drop-zone" nv-file-drop="" options="{formData:[{folder:'attachments'}, {recordid:0}]}" uploader="uploader">
Dateien hierher ziehen.
</div>
</div>
</div>
<div class="col-md-6">
<span class="btn btn-primary btn-o btn-file margin-bottom-15"> Dateien auswählen
<input type="file" nv-file-select="" options="{formData:[{folder:'attachments'}, {recordid:0}]}" uploader="uploader" multiple />
</span>
</div>
</div>
</div>
And this is my controller (look for "fileItemOptions"):
app.controller('UploadCtrl2', ['$rootScope', '$scope', 'FileUploader', 'Store',
function ($rootScope, $scope, FileUploader, Store) {
var fileItemOptions = {};
var uploader = $scope.uploader = new FileUploader({
url: $rootScope.app.api.url + '/?c=uploads&a=set&authToken=' + encodeURIComponent(Store.get('X-Xsrf-Token')),
});
// FILTERS
uploader.filters.push({
name: 'customFilter',
fn: function (item/*{File|FileLikeObject}*/, options) {
if(options) fileItemOptions = options;
return this.queue.length < 10;
}
});
uploader.removeAfterUpload = true;
// CALLBACKS
uploader.onAfterAddingFile = function (fileItem, options) {
//console.info('onAfterAddingFile', fileItem);
if(fileItemOptions.formData) {
fileItem.formData = fileItemOptions.formData;
}
};
uploader.onAfterAddingAll = function (addedFileItems) {
setTimeout(function () {
console.log(uploader);
uploader.uploadAll();
}, 500);
};
uploader.onCompleteAll = function () {
$scope.$parent.run.uploadComplete();
fileItemOptions = {}; // cleanup
};
}]);
Whenever a file is added, the custom filter stores the option object in a global variable. The callback "onAfterAddingFile" will read that variable and it to the fileItem object. Quite hacky, but this was the only way I got it running.

Iron Router / User details issue in Meteor

I am relatively new to Meteor and have been stuck on an issue for awhile. I have a /users/:_id route that is supposed to display details specific to that user id. However, whenever I hit that route, it displays information for the currently logged in user, NOT of the user whose details I want to view.
Here's my route:
Router.route('/users/:_id', {name: 'Users', controller: 'usersDetailController'});
Here's my usersDetailController:
usersDetailController = RouteController.extend({
waitOn: function () {
Meteor.subscribe('userProfileExtended', this.params._id);
},
onBeforeAction: function () {
var currUserId = Meteor.userId();
var currUser = Meteor.users.findOne({_id: currUserId});
console.log('admin? ' + currUser.isAdmin);
if (!currUser.isAdmin) {
this.render('accessDenied');
} else {
this.next();
}
},
action: function() {
this.render('Users');
}
});
And here's my server/publish:
Meteor.publish('userProfileExtended', function() {
return Meteor.users.find({_id: this.userId});
});
User Details template:
<template name="Users">
<form>
{{#with user}}
<div class="panel panel-default">
<div class="panel-heading">{{profile.companyName}} Details</div>
<div class="row">
<div class="col-md-4">
<div class="panel-body">
<p><label>Company: </label><input id="Company" type="text" value={{profile.companyName}}></p>
<p><label>Email: </label><input id="Email" type="text" value={{emails.address}}></p>
<p><label>Phone: </label><input id="Phone" type="text" value={{profile.phoneNum}}></p>
<p><label>Tire Markup: </label><input id = "tireMarkup" type="text" value={{profile.tireMarkup}}></p>
<p><button class="saveUserDetails">Save</button></p>
<p><button class="deleteUser">Delete User</button></p>
</div>
</div>
</div>
</div>
{{/with}}
Here's my Template Helper:
Template.Users.helpers({
user: function() {
return Meteor.users.findOne();
}
});
Can someone help? I think the issue is the way i reference "this.userId"...
Thank you!!
You need to change your publish function to use the userId parameter you specify when subscribing :
Meteor.publish('userProfileExtended', function(userId) {
return Meteor.users.find(userId,{
fields:{
'username':1,
'profile.firstName':1,
'profile.lastName'
}
});
});
In the publish function, userId will equal whatever value you call Meteor.subscribe with, in this case it will hold this.params._id.
Beware of using the proper syntax for route parameters, if you declare a path of /users/:_id, you need to reference the param using this.params._id.
Also note that it's insecure to publish the whole user document to the client if you only need to show specific fields in the interface, that's why you want to use the fields option of Collection.find to only publish a subset of user documents.
EDIT :
I would recommend using the route data function to specify the data context you want to apply when rendering your template, something like this :
data: function(){
return {
user: Meteor.users.findOne(this.params._id)
};
}

Resources