I am wondering why i am getting an empty instance each time i try to save a newly created record and it fails. I see like an empty record being added in my posts object only when i display errors.
Also i tried sorting posts which let me see the top recent created posts but the behavior is kinda odd, because as soon as the post is created, it shows at the end and then immediately goes to the top. I am wondering if this is normal or maybe there is a way to wait for the server response and push the record with some sort of fade in effect, etc. Thanks.
index.hbs
<form {{action "savePost" post on="submit"}}>
<div class="form-group {{if errors.error-content "has-error has-feedback"}}">
{{textarea value=post
rows=3
placeholder="What are you thinking?"
id="content"
class="form-control"}}
<small class="help-block text-danger">
{{errors.error-content}}
</small>
</div>
<div class="clearfix">
<div class="pull-right">
<button type="submit"
class="btn btn-sm btn-success">
Create new post
</button>
</div>
</div>
</form>
{{#each sortedPosts as |post|}}
<article class="wrapper">
<p>{{post.content}}</p>
</article>
{{else}}
<article class="wrapper">
<p>NO CURRENT POSTS TO SHOW!</p>
</article>
{{/each}}
post.js
export default DS.Model.extend({
content: DS.attr('string'),
created_at: DS.attr('date')
});
index.js route
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model() {
return this.store.findAll('post');
},
actions: {
savePost(content) {
const newPost = this.store.createRecord('post', {
content: content
});
newPost.save().then(() => {
this.controller.set('post', '');
this.controller.set('errors', []);
}).catch((resp) => {
let errors = {};
resp.errors.forEach((error) => {
errors[`error-${error.field}`] = error.messages[0];
});
this.controller.set('errors', errors);
});
},
willTransition() {
this.controller.get('post').rollbackAttributes();
this.controller.set('errors', []);
}
}
});
index.js controller
export default Ember.Controller.extend({
sortProp: ['created_at:desc'],
sortedPosts: Ember.computed.sort('model', 'sortProp')
});
Well, your this.store.createRecord will always create a new record. So if your .save() fails, you can unload the record, or you don't create your new record there, or save it for the next .save().
The question is a bit why your .save() fails. If its some kind of validation error, and the user fixes them and saves the post again, I would recommend to use the same, earlier created post again, modify it and try to .save() it again.
Another approach is to filter the records you display on the isNew flag.
With that I would bind the textarea directly to a fresh record. A computed property like this could be nice:
newRecord: Ember.computed('_newRecord.isNew', {
get() {
if(!get(this, '_newRecord') || !get(this, '_newRecord.isNew')) {
set(this, '_newRecord', this.store.createRecord('post'));
}
return get(this, '_newRecord');
}
})
Then you can directly bind your texturea's value to newRecord.content and just .save() this in your action.
For all kind of animations, checkout liquid fire.
Related
After I press delete button, the message gets deleted from database but I have to refresh the page to see the message disappear from page.
dashboard.component.html
<h2 *ngIf="user"> Hello, {{user.username}} </h2>
<a [routerLink]="['/']">Log Out</a>
<form (submit)="createMessage()">
<label>Message</label>
<input type="text" name="message" [(ngModel)]="message.message">
<input type="submit" value="create new message">
</form>
<div *ngFor="let message of messages">
<h1>{{message.userId.username}}</h1>
<h3>{{message.message}}</h3>
<h2 *ngIf="user.username === message.userId.username">
<form (submit) = "delete(message._id)">
<button type="submit" name="" value="">DeleteOne</button>
</form>
</h2>
<hr>
</div>
dashboard.component.ts
export class DashboardComponent implements OnInit {
messages: Message[] =[];
user: User;
message: Message = new Message();
currentId;
constructor(public _user: UserService, public _router:Router,private
_route: ActivatedRoute, private _message: MessageService) {
this.user = _user.currentUser;
this._route.params.subscribe((param)=>{
console.log("task details url id is: ", param.id);
this.currentId = param.id;
})
}
delete(val) {
console.log("delete method!", val);
this._message.delete({id: val})
.then((data)=>{
})
You may remove it from array once deleted, or you also can get the data back and re initialize your array.
delete(val) {
this._message.delete({id: val})
.then((data)=>{
this.messages.remove(this.messages.findIndex((message => message._id === val));
});
}
I guess _message.delete returns a promise here? One thing you could do is, when the promise resolves, reload your data (where the deleted record is not present anymore)
Here:
delete(val) {
console.log("delete method!", val);
this._message.delete({id: val})
.then((data)=>{
// reload your messages
})
The reason why your message is not removed from the UI is because your messages array is not updated. In order to do this you have 2 options:
Remove the element manually from the messages array.
Once the delete() function is resolved (inside the then or the subscribe) make another call to the backend to retrieve the current status of the messages and assign it to the messages array.
This is maddening, as there is little to no help on google/the internet for this. https://guides.emberjs.com/v2.4.0/testing/acceptance/ is also not very helpful, even though it tries. I am basically learning this from scratch. I know a modest amount of HTML, handlebars, and javascript, but emphasis on the modest.
Here is my template, much of it is copied code from my architect's design who doesn't have time to help me :
<form {{action "login" on="submit"}} class="col-md-4 col-md-offset-4">
{{#if loginFailed}}
<div class="alert">Invalid username or password.</div>
{{/if}}
<div class="form-group">
<label for="inputEmail">Email address</label>
{{input value=username type="email" class="form-control" id="inputEmail1" placeholder="Email"}}
</div>
<div class="form-group">
<label for="inputPassword">Password</label>
{{input value=password type="password" class="form-control" id="inputPassword" placeholder="Password"}}
</div>
<button type="submit" class="btn btn-default" disabled={{isProcessing}}>Log in!</button>
</form>
Note the application runs correctly (I'm able to generate a login screen which connects to my local database, and I am able to log in correctly when the credentials are correct and not login when they aren't).
There is also a large .js file for the route which has an ajax call and corresponding promise from it, which I can sort of understand, but bottom line, it works :
import Ember from 'ember';
import ajax from 'ic-ajax';
export default Ember.Route.extend({
loginFailed: false,
isProcessing: false,
beforeModel: function(){
this.store.unloadAll('security-setting');
this.store.unloadAll('org');
var user = this.modelFor('application').user;
user.setProperties({email: '', auth: ''});
},
actions: {
login: function() {
this.setProperties({
loginFailed: false,
isProcessing: true
});
var _this = this;
ajax({
url: _this.modelFor('application').url + '/signin.json/',
type: 'post',
data: {session: {email: this.controller.get("username"), password: this.controller.get("password")}},
}).then(
function(result) {
// proprietary stuff, it all works
},
function(error){
alert(error.jqXHR.responseText);
this.set('isProcessing', false);
_this.set("loginFailed", true);
}
);
},
},
reset: function() {
this.set('isProcessing', false);
this.controller.set('password', '');
this.controller.set('username', '');
}
});
Here is the acceptance test I am trying to write :
import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from 'ember-super-user/tests/helpers/start-app';
module('Acceptance | login', {
beforeEach: function() {
this.application = startApp();
},
afterEach: function() {
Ember.run(this.application, 'destroy');
}
});
test('visiting /login and fail a login attempt', function(assert) {
visit('/login');
fillIn('input.username', 'insert-username-here');
fillIn('input.password', 'insert-password-here');
click('button.submit');
// I know this assert is wrong but I haven't even gotten this far yet so I'm // not thinking about it; basically what happens is a popup appears and says // wrong-username-or-password-etc
andThen(function() {
assert.equal(currentURL(), '/login');
});
});
Execution dies on the fillIn lines of code. I really don't know what to do here, I've tried all combinations of 'input.username', 'input.inputEmail1', 'input.inputEmail'... I'm just not sure what I'm supposed to do, at all. I'm also pretty sure that 'button.submit' will not just magically work either. Then, I know I'll be even more lost when I try to fill in the andThen promise to acknowledge the fact that a popup appeared saying wrong-password-etc.
Please help; thanks very much for your time.
EDIT: I have been able to fix the fillIn parts of the test, but the click (probably the click, anyway, as the error messages are unclear as to which line is the problem) is producing some errors that I am unable to diagnose. Error messages appear in the output of the QUnit test suites that don't make sense to me --
TypeError: Cannot read property 'set' of undefined# 4286 ms
Expected:
true
Result:
false
Diff:
trufalse
at http://localhost:7357/assets/test-support.js:3592:13
at exports.default._emberTestingAdaptersAdapter.default.extend.exception (http://localhost:7357/assets/vendor.js:52460:7)
at onerrorDefault (http://localhost:7357/assets/vendor.js:43162:24)
at Object.exports.default.trigger (http://localhost:7357/assets/vendor.js:67346:11)
at Promise._onerror (http://localhost:7357/assets/vendor.js:68312:22)
at publishRejection (http://localhost:7357/assets/vendor.js:66619:15)
EDIT 2: The latest change for changing 'button' to 'submit' still doesn't work. Current error message :
Error: Element input[type='submit'] not found.# 166 ms
Expected:
true
Result:
false
Diff:
trufalse
Source:
at http://localhost:7357/assets/test-support.js:3592:13
at exports.default._emberTestingAdaptersAdapter.default.extend.exception (http://localhost:7357/assets/vendor.js:52460:7)
at onerrorDefault (http://localhost:7357/assets/vendor.js:43162:24)
at Object.exports.default.trigger (http://localhost:7357/assets/vendor.js:67346:11)
at Promise._onerror (http://localhost:7357/assets/vendor.js:68312:22)
at publishRejection (http://localhost:7357/assets/vendor.js:66619:15)
Your selector for each input is wrong. Since you gave each one an id, you can do this:
fillIn('#inputEmail1', 'insert-username-here');
fillIn('#inputPassword', 'insert-password-here');
Remember that you are using CSS selectors for the first argument of fillIn, IDs use # prefix and classes use ..
For the submit button, you did not add a class or ID, but if it is the only submit button on the page you can target it like this:
click('button[type="submit"]');
The fillIn and click functions takes in css selectors. so for example, clicking on the submit would look like,
click("input[type='submit']");
Somehow the computed property of a controller I'm accessing through the needs property won't work in the template. Other regular string properties work as expected. Below is my code.
To be clear, what I'm trying to achieve is to access properties of the userController and userModel in actually all of my templates/routes (some of which are computed). However, 'user' itself doesn't have a page, so that's why it's not added in the Router.map. It's just a really important class that handles everything user-related and handles access to the user-model.
I hope somebody with a bit more ember experience knows what I'm doing wrong. Or maybe got some advice on how to do this the ember-way? Any help is greatly appreciated!
PS: I tried to be as complete as possible, if I'm forgetting smt let me know, I'll add it.
App.Router.map(function() {
this.resource('signup');
this.resource('login');
this.resource('profile');
this.resource('practice');
this.resource('overview');
});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return this.store.find('user');
}
});
App.LoginRoute = Ember.Route.extend({
controllerName: 'application',
model: function () {}
});
App.UserRoute = Ember.Route.extend({
model: function () {
return this.store.find('user');
}
});
//UserController
App.UserController = Ember.ArrayController.extend({
//pagetitle property to test. Working.
pageTitle: 'Usercontroller',
//userArray property to test, but didn't work.
// Makes sense since the Arraycontroller return an array, so you'll have to use #each-helper
userArray: function(){
return this.get('content');
},
//particularUser computed property to test, but also didn't work.
// Neither did looping #each through the userArray property
particularUser : Ember.computed.filterBy('content' , 'username', 'Sunchild')
});
//LoginController
App.LoginController = Ember.ArrayController.extend({
needs: ['user'],
pageTitle: 'test-title loginController'
});
// Login template feeded into an outlet in the application template
<script type="text/x-handlebars" id="login">
<div class="content">
<form class="input-group">
<div class="input-row">
<label>Username</label>
<input type="text" placeholder="Username">
</div>
<div class="input-row">
<label>Email</label>
<input type="email" placeholder="ratchetframework#gmail.com">
</div>
<button class="btn btn-primary btn-block btn-outlined">Login</button>
</form>
<h3> test1:{{controllers.user.pageTitle}}</h3>
<h3> test2:{{controllers.user.userArray}}</h3>
{{#each user in controllers.user.particularUser}}
<div class="card_wrapper">
<p><h3>Username: {{{user.username}}}</h3><p>
<p>email: {{user.email}}</p>
</div>
{{/each}}
</div>
</script>
Without a user route in your router, the model hook in your UserRoute will never run. That means that the model of your UserController will always be empty. So the way you've set things up won't work for your use case. But, you have the same model on your ApplicationRoute, so why not use that controller instead?
App.ApplicationController = Ember.ArrayController.extend({
// Never use the `content` property, always use the `model` property, they are different
userArray: Ember.computed.alias('model'),
particularUser: function() {
// Be sure to only grab the first item, not an array with the first item in it
return this.get('userArray').filterBy('username', 'Sunchild').get('firstObject');
}.property('userArray.#each.username')
});
This is nice because your application route runs before any other route, so this data will always be available. So in your LoginController:
App.LoginController = Ember.ArrayController.extend({
needs: ['application'],
userArray: Ember.comptued.alias('controllers.application.userArray']
});
Hopefully that clears things up a bit.
I'm following the Discover Meteor book
For some reason, the content in edit_post.html doesn't show up with the {{#with post}}:
<template name="postEdit">
{{#with post}}
<form class="main">
<div class="control-group">
<label class="control-label" for="url">URL</label>
<div class="controls">
<input name="url" type="text" value="{{url}}" placeholder="Your URL"/>
</div>
</div>
.
.
.
</form>
{{/with}}
</template>
the content of post_edit.js:
Template.postEdit.helpers({
post: function() {
return Posts.findOne(Session.get('currentPostId'));
}
});
Template.postEdit.events({
'submit form': function(e) {
e.preventDefault();
var currentPostId = Session.get('currentPostId');
var postProperties = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
}
Posts.update(currentPostId, {$set: postProperties}, function(error) {
if (error) {
// display the error to the user
alert(error.reason);
} else {
Router.go('postPage', {_id: currentPostId});
}
});
},
'click .delete': function(e) {
e.preventDefault();
.
.
.
}
}
});
route.js:
this.route('postEdit', {
path: '/posts/:_id/edit',
data: function() { return Posts.findOne(this.params._id); }
});
The template shows up if I remove the {{#with post}}.
I'm not sure if this is an error in the book, or whether I'm doing something wrong. I'm a Meteor beginner so I have no clue.
Any suggestion to fix this?
The template helper post calls a Session variable that is never set so I think that findOne() returns a falsy value. So {{#with post}} is correctly keeping the template from displaying. Without the {{#with post}} your template is able to display a post from the data function in the router. You are calling findOne() looking for the same data twice but either method will work to get the data you want for the template.
If you want to use {{#with}} you can change your route.js to:
this.route('postEdit', {
path: '/posts/:_id/edit',
before: function() { Session.set( "currentPostId", this.params._id ); }
});
I'm looking at the code from the book and I cannot see the with block you are referring to.
In fact, it should not be there because the data context of the template is already set by the router.
Your template helper (post) is not supposed to be there since it is both unnecessary and in fact there is no already set session variable so your get returns null as expected.
Just delete your helper and the with block and let iron router provide the data context as it already does.
I have a user profile view where users can edit their profile information. Everything below works great and the update is successful. However, when I logout of the account and login with a different user account, the update fails and returns an Access denied error. It isn't until I refresh the page that I can edit the profile information again with the second account.
I know this case is very rare and a user would not normally be logging out of one account, logging in with another and trying to update their profile but I would like to better understand why this is happening. Is the client token not flushed when a user logs out or is there something else that's being preserved that requires a complete reload of the page?
On client JS:
Template.user_profile_form.events({
'click #submit_profile_btn': function(evt) {
evt.preventDefault();
var first_name = $('#profile_first_name').val()
,last_name = $('#profile_last_name').val()
,email = $('#profile_email').val()
,email_lower_case = email.toLowerCase()
,gravatar_hash = CryptoJS.MD5(email_lower_case)
;
gravatar_hash = gravatar_hash.toString(CryptoJS.enc.Hex);
// TODO need to create user sessions so that when you log out and log back in, you have a fresh session
Meteor.users.update({_id: this.userId }, {
$set: {
profile: {
first_name: first_name,
last_name: last_name,
gravatar_hash: gravatar_hash
}
}
}, function(error) {
if (!error) {
Session.set('profile_edit', 'success');
Meteor.setTimeout(function() {
Session.set('profile_edit', null);
}, 3000);
} else {
Session.set('profile_edit', 'error');
Template.user_profile_form.error_message = function() {
return error.reason;
};
}
});
Meteor.call('changeEmail', email);
}
});
The server JS:
Meteor.publish('list-all-users', function () {
return Meteor.users.find({_id: this.userId }, {
fields: {
profile: 1,
emails: 1
}
});
});
Meteor.methods({
sendEmail: function(to, from, subject, text) {
this.unblock();
Email.send({
to: to,
from: from,
subject: subject,
text: text
});
},
changeEmail: function(newEmail) {
// TODO Need to validate that new e-mail does not already exist
Meteor.users.update(Meteor.userId(), {
$set: {
emails: [{
address: newEmail,
verified: false
}]
}
});
}
});
The template:
<template name="user_profile_form">
<h2>Update Profile</h2>
<div id="profile-form">
{{#if success}}
<div class="alert alert-success">
<strong>Profile updated!</strong> Your profile has been successfully updated.
</div>
{{/if}}
{{#if error}}
<div class="alert alert-error">
<strong>Uh oh!</strong> Something went wrong and your profile was not updated. {{error_message}}.
</div>
{{/if}}
<p>
{{#each profile}}
<input type="text" id="profile_first_name" placeholder="First Name" value="{{first_name}}">
<input type="text" id="profile_last_name" placeholder="Last Name" value="{{last_name}}">
{{/each}}
<input type="email" id="profile_email" placeholder="Email" value="{{email_address}}">
</p>
</div>
<div id="submit-btn">
<input type="submit" id="submit_profile_btn" class="btn btn-primary">
</div>
</template>
The Meteor logout function does almost nothing. It certainly does not tear down Session state or the rest of your app's context. Your code must reset these variables during your app's logout event. A manual refresh of the page causes the client side JavaScript to reload wiping out existing Session data.
If you don't want to mess with the accounts-ui template internals you can use the following pattern (CoffeeScript code) to clear individual variables from the Session:
Deps.autorun (c) ->
user = Meteor.user()
if user
# Setup code on login (if required)
else if not user
# Clear session on logout
Session.set "profile_edit", undefined