I am learning Meteor JS and followed a tutorial to build a menu builder shopping list. I am trying to add some features to it. I added one feature successfully, but now I am trying to create an organization feature where users can join an organization and see all shopping lists related to that organization. The first step is to allow for adding an organization by users.
The form appears, and I was able to insert in to the database from the console, but when I use autoform the objects are not being inserted into the database.
I recently upgraded from Meteor 1.3 to 1.4. I don't believe that is an issue since all of the other forms on the app are inserting properly still.
I have a feeling it has something to do with subscribe/publish, but I am not sure what I am doing wrong.
HTML- neworganization.html
<template name='NewOrganization'>
<div class='new-organization-container'>
<i class='fa fa-close'></i>
{{#autoForm collection='Organizations' id='insertOrganizationForm' type='insert'}}
<div class='form-group'>
{{> afQuickField name='organization'}}
</div>
<div class='form-group'>
{{> afQuickField name='members'}}
</div>
<button type="submit" class="btn btn-primary">Add</button>
{{/autoForm}}
</div>
</template>
organizations.html
<template name='Organizations'>
<h3>Your Organizations</h3>
{{#if $.Session.get 'newOrganization'}}
{{> NewOrganization }}
{{else}}
<button class='btn btn-organization btn-primary'>Add an Organization</button>
<button class='btn btn-join'>Join an Organization</button>
<button class='btn btn-deny'>Leave an Organization</button>
{{/if}}
<section class='organization-list'>
{{#if Template.subscriptionsReady}}
{{#each organizationList}}
{{> OrganizationItem}}
{{/each}}
{{else}}
<p>Loading...</p>
{{/if}}
JS- organizations.js
Template.Organizations.onCreated(function() {
this.autorun(() => {
this.subscribe('organizations');
});
});
Template.Organizations.helpers({
organizations() {
return Organizations.find({});
}
});
Template.Organizations.events({
'click .btn-organization': () => {
Session.set('newOrganization', true);
}
});
Template.NewOrganization.helpers({
organizationList: () => {
var organizationItems = Organizations.find({});
return organizationItems;
}
});
newOrganization.js
if (Meteor.isClient) {
Meteor.subscribe('organizations');
}
Template.NewOrganization.events ({
'click .fa-close': function () {
Session.set('newOrganization', false);
}
});
collections/organizations.js
import SimpleSchema from 'simpl-schema';
SimpleSchema.extendOptions(['autoform']);
Organizations = new Mongo.Collection('organizations');
Organizations.allow({
insert: function(userId){
return !!userId;
},
update: function(userId, doc){
return !!userId;
}
});
OrganizationSchema = new SimpleSchema ({
organization: {
label: "Organization Name",
type: String
},
id: {
label: "ID",
type: String,
autoform: {
type: "hidden"
}
},
members: {
type: Array
},
"members.$": Object,
"members.$.name": String,
"members.$.role": String,
inOrganization: {
type: Boolean,
defaultValue: true,
autoform: {
type: 'hidden'
}
},
createdAt: {
type: Date,
label: "CreatedAt",
autoform: {
type: "hidden"
},
autoValue: function() {
return new Date();
}
}
});
Meteor.methods({
deleteOrganizations: function(id) {
Organizations.remove(id);
}
});
Organizations.attachSchema(OrganizationSchema);
The problem is in the way the Schema was designed. I had inserted an id into the schema. My reasoning was that I wanted to have a way to add and remove members from an organization. What I did not take into account was that Mongo autogenerates an id for database object and by designing my schema in this way, I was creating a conflict. I removed the id from my schema and removed the problem.
Here is the new collections/organizations.js file:
import SimpleSchema from 'simpl-schema';
SimpleSchema.extendOptions(['autoform']);
Organizations = new Mongo.Collection('organizations');
Organizations.allow({
insert: function(userId){
return !!userId;
},
update: function(userId, doc){
return !!userId;
}
});
OrganizationSchema = new SimpleSchema ({
organization: {
label: "Organization Name",
type: String
},
members: {
type: Array
},
"members.$": Object,
"members.$.name": String,
"members.$.role": String,
inOrganization: {
type: Boolean,
defaultValue: true,
autoform: {
type: 'hidden'
}
},
createdAt: {
type: Date,
label: "CreatedAt",
autoform: {
type: "hidden"
},
autoValue: function() {
return new Date();
}
}
});
Meteor.methods({
deleteOrganizations: function(id) {
Organizations.remove(id);
}
});
SimpleSchema.debug = true;
Organizations.attachSchema(OrganizationSchema);
Related
I don't get how i am supposed to save only a single object a not the whole array. I am trying to create a movie watchlist. If I click "add to watchlist", the single object should be saved in LocalStorage. If I hit remove from watchlist, the object should get removed. I tried to write down methods to regulate all of that, but i guess somethings wrong. The data comes from an API request. Here's the code:
<template>
<div>
<div class="card" v-for="movie in movies"
:key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button type="submit" #click="storeMovie" >
Aggiungi
</button>
<button type="submit" #click="removeMovie">
Rimuovi
</button>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
//Cambiare il nome con quello del componente creato
name: 'HomeComp',
data () {
return {
movies: [],
movie: "",
}
},
mounted () {
axios
.get('https://api.themoviedb.org/3/movie/popular?api_key=###&language=it-IT&page=1&include_adult=false®ion=IT')
.then(response => {
this.movies = response.data.results
// console.log(response.data.results)
})
.catch(error => {
console.log(error)
this.errored = true
})
.finally(() => this.loading = false)
if (localStorage.movies) {
this.movies = JSON.parse(localStorage.movies);
}
},
watch: {
movies: {
handler(newMovies) {
localStorage.movies = JSON.stringify(newMovies);
},
deep:true
}
},
methods: {
getMovie() {
this.movies = JSON.parse(localStorage.getItem("movie"));
},
storeMovie() {
if (this.movie.length) {
// push the new movie to list
this.movies.push(this.movie);
// store the data in localStorage
localStorage.setItem("movies", JSON.stringify(this.movies));
// clear the input
this.movie = "";
}
},
removeMovie() {
localStorage.removeItem('movie');
}
},
}
</script>
<style scoped lang="scss">
/*Inserire style componente*/
</style>
tried to parse ad stringify, but i think i'm doing it wrong in some way. Also written some methods, not working
Few observations as per the code you posted :
As you want to store the new movie through input, Aggiungi button should come outside of v-for loop.
For removeStore event, You need to pass the store id from a template so that we can filter out the movies array.
Live Demo :
new Vue({
el: '#app',
data: {
movies: [],
movie: ''
},
mounted() {
// This data will come from API, Just for a demo purpose I am using mock data.
this.movies = [{
id: 1,
title: 'Movie A',
release_date: '06/12/2022'
}, {
id: 2,
title: 'Movie B',
release_date: '07/12/2022'
}, {
id: 3,
title: 'Movie C',
release_date: '08/12/2022'
}, {
id: 4,
title: 'Movie D',
release_date: '09/12/2022'
}, {
id: 5,
title: 'Movie E',
release_date: '10/12/2022'
}]
},
methods: {
storeMovie() {
const newMovieID = this.movies.at(-1).id + 1;
this.movies.push({
id: newMovieID,
title: this.movie,
release_date: '06/12/2022'
})
},
removeMovie(movieID) {
this.movies = this.movies.filter(({ id }) => id !== movieID)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
Add new movie : <input type="text" v-model="movie"/>
<button type="submit" #click="storeMovie()">
Aggiungi
</button>
</div><br>
<div class="card" v-for="movie in movies"
:key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button type="submit" #click="removeMovie(movie.id)">
Rimuovi
</button>
</div>
</div>
Im creating an online blog posting webpage that uses Sequelize and Handlebars to display the pages. The problem I am having is that I'm trying to give the user an option to update the contents of a post they have made, and I am able to change the title of the post and save it, but no matter what I do, changing the body of the post does not save as well.
Heres the code for the Post Model (/models/Post.js)
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/connection');
// Post Model
class Post extends Model {
static upvote(body, models) {
return models.Vote.create({
user_id: body.user_id,
post_id: body.post_id,
}).then(() => {
return Post.findOne({
where: {
id: body.post_id,
},
attributes: [
'id',
'title',
'created_at',
// use raw MySQL aggregate function query to get a count of how many votes the post has and return it under the name `vote_count`
[
sequelize.literal(
'(SELECT COUNT(*) FROM vote WHERE post.id = vote.post_id)'
),
'vote_count',
],
]
})
})
}
}
Post.init(
{
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
title: {
type: DataTypes.STRING,
allowNull: false
},
post_text: {
type: DataTypes.TEXT,
allowNull: false,
validate: {
len: [4]
}
},
user_id: {
type: DataTypes.INTEGER,
references: {
model: 'user',
key: 'id'
}
}
},
{
sequelize,
freezeTableName: true,
underscored: true,
modelName: 'post'
}
);
module.exports = Post;
This is the handlebars template (views/edit-post.handlebars)
<article>
← Back to dashboard
<h2>
Edit Post
</h2>
<form class="edit-post-form">
<div>
<input name="post-title" type="text" value="{{post.title}}" />
</div>
<div>
<textarea name="post-text">{{post.post_text}}</textarea>
</div>
<div>
{{post.vote_count}} {{format_plural "point" post.vote_count}} by you on {{format_date post.created_at}}
|
{{post.comments.length}} {{format_plural "comment" post.comments.length}}
</div>
<button type="submit">Save post</button>
<button type="button" class="delete-post-btn">Delete post</button>
</form>
</article>
<form class="comment-form">
<div>
<textarea name="comment-body"></textarea>
</div>
<div>
<button type="submit">add comment</button>
</div>
</form>
{{> comments post.comments}}
<script src="/javascript/edit-post.js"></script>
<script src="/javascript/delete-post.js"></script>
<script src="/javascript/comment.js"></script>
Here's the Javascript file (/public/javascript/edit-post.js)
async function editFormHandler(event) {
event.preventDefault();
const id = window.location.toString().split('/')[
window.location.toString().split('/').length - 1
];
const title = document.querySelector('input[name="post-title"]').value;
const post_text = document.querySelector('textarea[name="post-text"]').value;
const response = await fetch(`/api/posts/${id}`, {
method: 'PUT',
body: JSON.stringify({
title,
post_text
}),
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {
document.location.replace('/dashboard/');
} else {
alert(response.statusText);
}
}
document
.querySelector('.edit-post-form')
.addEventListener('submit', editFormHandler);
And the route function where the routes are located (controllers/dashboard-routes.js)
const router = require('express').Router();
const sequelize = require('../config/connection');
const { Post, User, Comment } = require('../models');
const withAuth = require('../utils/auth');
router.get('/', withAuth, (req, res) => {
Post.findAll({
where: {
// use the ID from the session
user_id: req.session.user_id,
},
attributes: [
'id',
'title',
'post_text',
'created_at',
[
sequelize.literal(
'(SELECT COUNT(*) FROM vote WHERE post.id = vote.post_id)'
),
'vote_count',
],
],
include: [
{
model: Comment,
attributes: ['id', 'comment_text', 'post_id', 'user_id', 'created_at'],
include: {
model: User,
attributes: ['username'],
},
},
{
model: User,
attributes: ['username'],
},
],
})
.then((dbPostData) => {
// serialize data before passing to template
const posts = dbPostData.map((post) => post.get({ plain: true }));
res.render('dashboard', { posts, loggedIn: true });
})
.catch((err) => {
console.log(err);
res.status(500).json(err);
});
});
//GET api/posts/edit/i
router.get('/edit/:id', withAuth, (req, res) => {
Post.findOne({
where: {
id: req.params.id,
},
attributes: [
'id',
'title',
'post_text',
'user_id',
'created_at',
[
`(SELECT COUNT(*) FROM vote WHERE post.id = vote.post_id)`,
'vote_count',
],
],
include: [
{
model: Comment,
attributes: ['id', 'comment_text', 'post_id', 'user_id', 'created_at'],
include: {
model: User,
attributes: ['username'],
},
},
{
model: User,
attributes: ['username'],
},
],
})
.then((dbPostData) => {
if (dbPostData) {
const post = dbPostData.get({ plain: true });
res.render('edit-post', {
post,
loggedIn: true,
});
} else {
res.status(404).end();
}
})
.catch((err) => {
console.log(err);
res.status(500).json(err);
});
});
module.exports = router;
Im pretty sure this is all of the related code but the rest is on: https://github.com/Zacharycampanelli/hi_tech_blog
I've tried changing the object the body is stored in from to and neither seem to be giving me any luck
I'm trying to create project like Trello. I do not know how to do it properly.
I created function init in AngularJS Controller where i put http requests:
$scope.loadLists();
$scope.loadsCards();
Scripts:
$scope.loadLists = function () {
return ApiService.staff.list()
.then(function (resp) {
for (var i = 0; i < resp.length; i++) {
$scope.lists[i] = resp[i];
}
})
}
$scope.loadsCards = function () {
return ApiService.staff.cards()
.then(function (resp) {
for (var i = 0; i < resp.length; i++) {
$scope.cards = resp;
}
console.log($scope.cards)
})
}
I'm downloading tasks to $scope.cards
In console.log() we can see:
Array [ Object, Object, Object, Object, Object, Object, Object, Object ]
where the object consists of
var CardSchema = new Schema({
name: { type: String, maxlength: 20, required: true },
list: { type: Schema.Types.ObjectId, ref: 'List' },
updated: { type: Date, default: Date.now },
created: { type: Date, default: Date.now },
active: Boolean
});
And now I do not know what to do so that the cards are displayed only those in the given column that are assigned to the list. I mean : task.list == list._id
for the moment I did it
<div ng-repeat="list in lists track by $index">
<div style="float: left; margin-left: 5px;">
<div id="tasks">
<h3>{{list.name}}</h3>{{$index}}
<ul>
<li ng-repeat="task in cards">
<div ng-hide="task.list == list._id">
{{task.name}}
</div>
<i ng-click="removeTask(task)" class="fa fa-trash-o"></i></li>
</ul>
<form ng-submit="addTask(list._id, $index, newTask)">
<input type="text" ng-model="newTask" placeholder="add a new task" required />
</form>
</div>
</div>
</div>
But it does not work and so it probably can not be if I want to still create a database for the card field
Position (Later to enable drag & dropping)
Can anyone tell me how to properly display cards in lists?
EDIT;;;
thank you very much for the help.
Can you explain more to me because I still have a problem with the cards
I did directiv in angularJS:
App.directive('myList', function() {
return {
restrict: 'E',
replace: true,
template: '<div style="float: left; margin-left: 5px;"><div id="tasks">{{list.name}}<br>{{card.name}}</div></div>'
};
});
App.directive('myCard', function() {
return {
restrict: 'E',
replace: true,
template: '<div style="float: left; margin-left: 5px;"><div id="tasks">{{card.name}}</div></div>'
};
});
and in index.ejs
<my-list ng-repeat="list in lists" list-data="list"></my-list>
<my-card ng-repeat="card in listData.cards" card-data="card"></my-card>
I also did AJAX inquiry after all cards :
https://gist.github.com/anonymous/ed17c3fd675ea4361cb8fbd78e94cb37
name: its name card
list: its _id list
In $scope.cards I stores AJAX inquiry after all cards,
Its my card model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CardSchema = new Schema({
name: { type: String, maxlength: 20, required: true },
// description: { type: String, maxlength: 300 },
list: { type: Schema.Types.ObjectId, ref: 'List' },
updated: { type: Date, default: Date.now },
created: { type: Date, default: Date.now },
active: Boolean
});
module.exports = mongoose.model('Card', CardSchema);
And I have no idea how this loop looks, would you help me somehow?
It's not that easy to solve with two ng-repeat's. You may want to create your list and card directives because eventually they will be complex views:
<my-list ng-repeat="list in lists" list-data="list"></my-list>
And in my-list directive, you can create a loop to to render cards:
<my-card ng-repeat="card in listData.cards" card-data="card"></my-card>
I have problem with method on the server side. It is invoked two times, but it should only one. Error occur when I change password. To change password I use Accounts.setpassword(). Below code of that method:
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { TAPi18n } from 'meteor/tap:i18n';
import { Accounts } from 'meteor/accounts-base';
import _ from 'lodash';
Meteor.methods({
'users/change-user-settings'(formData) {
if (!this.userId) {
throw new Meteor.Error(401, TAPi18n.__('errors.you_are_not_logged'));
}
check(formData, App.Schemas.userSettingsSchema);
//change password
if (formData.password) {
Accounts.setPassword(this.userId, formData.password, {logout: false});
}
//change email
if (formData.email) {
Meteor.users.update({_id: this.userId}, {
$set: {
'emails.0.address': formData.email
}
});
}
//change phone number
if (formData.phoneNumber) {
Meteor.users.update({_id: this.userId}, {
$set: {
'phoneNumber': formData.phoneNumber
}
});
}
return true;
}
});
My autoform form and schema to it:
<template name="userSettingsTemplate">
<div class="user-settings-form-wrapper">
<h4>Change settings</h4>
{{ #autoForm id="userSettingsForm" type="method" meteormethod="users/change-user-settings" schema=getUserSettingsSchema class="form-horizontal"}}
{{ > afQuickField name="password" label="Change password: " template="bootstrap3-horizontal" label-class="col-xs-6" input-col-class="col-xs-6"}}
{{ > afQuickField name="passwordConfirmation" label="Confirm password: " template="bootstrap3-horizontal" label-class="col-xs-6" input-col-class="col-xs-6"}}
{{ > afQuickField name="email" label="E-mail: " template="bootstrap3-horizontal" label-class="col-xs-6" input-col-class="col-xs-6"}}
{{ > afQuickField name="phoneNumber" label="Phone number: " template="bootstrap3-horizontal" label-class="col-xs-6" input-col-class="col-xs-6"}}
<div class="form-group">
<button type="submit" class="btn btn-primary full-width">Send</button>
</div>
{{ / autoForm }}
</div>
</template>
Schema:
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
import { TAPi18n } from 'meteor/tap:i18n';
import { Meteor } from 'meteor/meteor';
Meteor.startup(() => {
// for running tests this is temporary workaround
try {
SimpleSchema.messages({
"passwordMismatch": "passwords are not the same"
});
}
catch (e) {
console.log(e.message, e.name, e.stack);
}
});
App.Schemas.userSettingsSchema = new SimpleSchema({
password: {
type: String,
optional: true,
min: 6,
autoform: {
type: "password"
}
},
passwordConfirmation: {
type: String,
min: 6,
optional: true,
autoform: {
type: "password"
},
custom: function() {
if (this.value !== this.field('password').value) {
return "passwordMismatch";
}
}
},
email: {
type: String,
optional: true,
regEx: SimpleSchema.RegEx.Email
},
phoneNumber: {
type: String,
optional: true
}
});
Just a side note. Unless I'm missing something here, you should not be using Accounts.setPassword() like that. It's a server side method, so the new password gets sent as plain text over the wire. Instead, take a look at Accounts.changePassword(), which runs on client and is meant to do what you want to do.
First of all I have to say that I'm new in Angular and node technologies. So sorry for my ignorance.
I get this error when I try to save an Entity from edition view: 'Cast to ObjectId failed for value "[object Object]" at path "category"'.
Well, I've got these code:
HTML:
<form class="form-horizontal" data-ng-submit="update()" novalidate>
<fieldset>
<div class="form-group">
<label for="listaCat">Categoría:</label>
<select id="listaCat" class="form-control" data-ng-Fmodel="notification.category" data-ng-options="c.name for c in listaCategorias track by c._id">
</select>
</div>
<div class="form-group">
<label class="control-label" for="name">Descripción</label>
<div class="controls">
<input type="text" data-ng-model="notification.name" id="name" class="form-control" placeholder="Descripción" required>
</div>
</div>
<div class="form-group">
<input type="submit" value="Guardar" class="btn btn-default">
</div>
<div data-ng-show="error" class="text-danger">
<strong data-ng-bind="error"></strong>
</div>
</fieldset>
</form>`
Angular controller:
$scope.update = function() {
var notification = $scope.notification;
notification.$update(function() {
$location.path('notifications/' + notification._id);
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
Server side controller:
var mongoose = require('mongoose'),
errorHandler = require('./errors.server.controller'),
Notification = mongoose.model('Notification'),
_ = require('lodash');
exports.update = function(req, res) {
var notification = req.notification;
notification = _.extend(notification , req.body);
notification.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(notification);
}
});
};
Mongoose Model:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var NotificationSchema = new Schema({
name: {
type: String,
default: '',
required: 'Rellena la notificación',
trim: true
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
},
category: {
type: Schema.ObjectId,
ref: 'Category'
}
});
mongoose.model('Notification', NotificationSchema);
var CategorySchema = new Schema({
name: {
type: String,
default: '',
required: 'Rellena la categoría',
trim: true
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Category', CategorySchema);
So, if I debug inside Server controller at update method with WebStorm, I can see that req.body comes with each attribute well formed, but after convert req.body into Notification Mongoose Model with:
notification = _.extend(notification , req.body);
the category attribute is not a Model but an ObjectId. It seems as lodash.extend is not working properly for complex attributes. I've tried many other ways of cloning the object but without success.
Finally I solved it, with this line inside the angular controller:
notification.category = $scope.notification.category._id;
notification.$update(function() {
Anyway, I think that this is not the right way. I guess there must be a way of copying the req.body properties into a mongoose model without doing it manually for the complex properties.
Thanks a lot in advance!
Since you are working on AngularJS and ExpressJS, i would suggest you to use $resource service which is exactly meant for interacting with the rest API.
**$resource** contains these default set of actions:
{ 'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'} };
There is nice documentation available in the link that i shared above.
In your case:
i assume, http://localhost:300/notifications/:id, this might be your rest url where you want to perform update action.
You can create your custom services like:
var module = angular.module('myapp.services',['ngResource']);
module.factory('MyAppUpdateService',function($resource){
return $resource('notifications/:id',
{
id: '#id'
},
{
'update': { method:'PUT' }
}
);
});
Now inside your angular app controller you can inject this service as dependency and hence it will be available to perform update in that REST url.
angular.module('myapp',['ngResource','myapp.services']);
angular.module('myapp').controller('MeetupsController',['$scope','$resource','$state','$location','MeetupUpdateService','socket',
function($scope,$resource,$state,$location, MyAppUpdateService){
$scope.updateMeetup = function(){
$scope.updateService = new MyAppUpdateService();
$scope.updateService.name = $scope.notification.name;
.
.
.
$scope.updateService.$update({id:$scope.notification.category._id},function(result){
$location.path("/meetup/")
});
}
})]);
So this was just an example, if you want more comprehensive implementation. Look here, i am creating a MEAN seed of my own, and i am doing the same.
Any doubt please do ask.