Backbone model.get('key') undefined after .fetch() call, even inside success callback - javascript

I'm new to Backbone.js, and just finished running through a basic tutorial to create a "user list" system (https://www.youtube.com/watch?v=FZSjvWtUxYk) where all the templates, scripts, etc are created inline. I got everything working pretty easily, so I decided to try and modularize things since I know that's the best practice. I'm following this guide to the AMD methodology (https://cdnjs.com/libraries/backbone.js/tutorials/organizing-backbone-using-modules) and have everything working properly except for one thing - when editing a user, the "current" data isn't being loaded into the form. All of the issues I've found on SO and other places so far have been solved by putting the template generating code inside the success: callback of the .fetch() call, but I'm already doing that.
Here's the code:
(I'm leaving out the main.js and app.js that handle the require.js configuration, router init, etc. They seem to be working just fine.)
// Filename: router.js
define([
'jquery',
'underscore',
'backbone',
'views/userList',
'views/editUser'
], function($, _, Backbone, UserListView, EditUserView){
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'new': 'editUser',
'edit/:id': 'editUser'
}
});
var initialize = function(){
var app_router = new AppRouter;
app_router.on('route:home', function(){
var userListView = new UserListView();
userListView.render();
});
app_router.on('route:editUser', function(id) {
var editUserView = new EditUserView();
editUserView.render({ id: id });
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
views/editUser.js
// Filename: views/editUser
define([
'jquery',
'underscore',
'backbone',
'models/user',
'text!/templates/editUser.html'
], function($, _, Backbone, UserModel, rawEditUserTemplate) {
var userListView = Backbone.View.extend({
// Element to use for this view
el: $('.page'),
// Function to call when this view is rendered
render: function(options) {
var that = this;
// If there is an ID, we are editing
if ( options.id ) {
// Create the user, passing the ID
that.editUser = new UserModel({ id: options.id });
// Fetch the user data
that.editUser.fetch({
// When the fetch is returned
success: function(userData) {
// Generate the template and pass the data in
var editUserTemplate = _.template( rawEditUserTemplate );
that.$el.html(editUserTemplate({ user: userData }));
}
})
}
else { // We are creating a new user
// Generate the template with an empty user
var editUserTemplate = _.template( rawEditUserTemplate );
this.$el.html(editUserTemplate({ user: null }));
}
},
events: {
'submit .edit-user-form': 'saveUser',
'click .delete': 'deleteUser'
},
saveUser: function(e) {
e.preventDefault();
// Get the details
var userDetails = $(e.currentTarget).serializeObject();
// Create a user model
var user = new UserModel();
// Save the user details
user.save(userDetails, {
success: function(user) {
Backbone.history.navigate('', { trigger: true });
}
});
},
deleteUser: function(e) {
e.preventDefault();
// Destroy the user we are editing
this.editUser.destroy({
// When the destroy is finished
success: function() {
// Back to home
Backbone.history.navigate('', { trigger: true });
}
});
}
});
// Our module now returns our view
return userListView;
});
templates/editUser.html
<form class="edit-user-form">
<legend><%= user ? 'Update' : 'Create' %> User</legend>
<div class="form-group">
<label for="firstname">First Name</label>
<input type="text" class="form-control" name="firstname" id="firstname" value="<%= user ? user.get('firstname') : '' %>" />
</div>
<div class="form-group">
<label for="lastname">Last Name</label>
<input type="text" class="form-control" name="lastname" id="lastname" value="<%= user ? user.get('lastname') : '' %>" />
</div>
<div class="form-group">
<label for="age">Age</label>
<input type="text" class="form-control" name="age" id="age" value="<%= user ? user.get('age') : '' %>" />
</div>
<hr />
<button class="btn btn-success" type="submit"><%= user ? 'Update' : 'Create' %></button>
<% if ( user ) { %>
<input type="hidden" name="id" id="id" value="<%= user.id %>" />
<button class="btn btn-danger delete">Delete</button>
<% }; %>
</form>
Using this code, I get a blank edit form regardless of whether or not I'm editing or creating, HOWEVER the "Create" vs "Update" text switch in the template is working properly. This means that a user object is in fact being passed, and when I add a console.log(user) into the template file, it is in fact showing me user data. When I log user.get('firstname') or any other attribute, however, it logs "undefined".

The issue was in my User model, which I didn't include above because I didn't understand at the time why it could be relevant.
I was defining it as:
var userModel = Backbone.Model.extend({
url: '/users'
});
When it should have been:
var userModel = Backbone.Model.extend({
urlRoot: '/users'
});
The wrong option was causing the API to return a collection rather than a model, so the .get() wasn't able to work properly.

Related

Login page in Oracle jet

Hi I am creating an application using Oracle JET in which after I click the Login button in the LoginTest page, it should take me to the Homepage after validation. I have managed to validate the input but I couldn't route it to the Homepage. I have tried using multiple binding but it is of no use. Could someone please help.
HTML CODE
h1>logintest</h1>
<div align="center">
<label for="username">Username</label>
<input id="username" type="text" required
data-bind="ojComponent: {component: 'ojInputText',
validators: [{type: 'regExp', options: {pattern: '[a-zA-Z0-9]{3,}',
messageDetail: 'You must enter at least 3 letters or numbers'}}],
invalidComponentTracker: tracker}" /><br /><br />
<label for="password">Password</label>
<input id="password" type="password" required
data-bind="ojComponent: {component: 'ojInputPassword',
validators: [{type: 'regExp', options : {pattern: '(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{6,}',
messageSummary : '{label} too Weak',
messageDetail: 'The password must contain atleast one uppercase, one lowercase, one number and must be 6 digits long'}}],
invalidComponentTracker: tracker}" /><br /><br />
<a class="oj-button-primary oj-button-xl"
href="http://localhost:8383/Test/index.html?root=home" id="create" type="button"
data-bind="ojComponent: {component: 'ojButton',
label: 'Login',
disabled: shouldDisableCreate()},
click: onClick"></a>
</div>
JAVASCRIPT CODE
define(['ojs/ojcore', 'knockout', 'ojs/ojinputtext', 'ojs/ojbutton', 'ojs/ojknockout-validation', 'ojs/ojmodel'
], function (oj, ko) {
/**
* The view model for the main content view template
*/
function logintestContentViewModel() {
var self = this;
self.tracker = ko.observable();
self.username = ko.observable("");
self.password = ko.observable("");
self.clickedButton = ko.observable();
self.buttonClick = function(data, event)
{
var trackerObj = ko.utils.unwrapObservable(self.tracker);
if (!this._showComponentValidationErrors(trackerObj))
{
return;
}
};
self.routePage = function(data,event)
{
self.clickedButton(event.currentTarget.id);
return true;
};
self.onClick = function()
{
self.buttonClick();
self.routePage();
}
self.shouldDisableCreate = function()
{
var trackerObj = ko.utils.unwrapObservable(self.tracker),
hasInvalidComponents = trackerObj ? trackerObj["invalidShown"] : false;
return hasInvalidComponents;
};
self._showComponentValidationErrors = function (trackerObj)
{
trackerObj.showMessages();
if (trackerObj.focusOnFirstInvalid())
return false;
};
}
return logintestContentViewModel;
});
If you are using ojRouter, then you can simply use
oj.Router.go("route name");
If you're not using ojRouter, then you can use the location object. Something like:
window.location.pathname='/homepage'
I recommend using ojRouter and it's canEnter() method for things like this.
Router cookbook demo:
http://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html?component=router&demo=simple
JSDocs for Router canEnter method
http://www.oracle.com/webfolder/technetwork/jet/jsdocs/oj.RouterState.html
You can use
oj.Router.rootInstance.go('homepage');

Backbone model.save() overwrite previous model

I have a problem when i save my model. Instead to create a new model with new cdi i always get the same cd1 and seems my model are just overwrite the previous.
Here is my simple sign up form:
Html
<form class="form-signin">
<h2 class="form-signin-heading">Please sign in</h2>
<label for="inputName" class="sr-only">Name</label>
<input type="text" id="inputName" class="form-control" placeholder="Name" required autofocus>
<label for="inputSurname" class="sr-only">Surname</label>
<input type="text" id="inputSurname" class="form-control" placeholder="Surname" required autofocus>
<label for="inputNickname" class="sr-only">Nickname</label>
<input type="text" id="inputNickname" class="form-control" placeholder="Nickname" required autofocus>
<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
<span id="btn-submit" class="btn btn-lg btn-primary btn-block" type="submit"> Sign in </span>
</form>
Model:
var User = Backbone.Model.extend({
defaults: {
inputName: "",
inputSurname: "",
inputNickname: "",
inputEmail: "",
inputPassword: "",
rememberMe: false
},
url: 'src/users.json'
});
View:
var SignupView = Backbone.View.extend({
events: {
'keypress input': 'getInputs',
'click #btn-submit': 'saveInputs'
},
initialize: function() {
// Make sure functions are called in the right scope
_.bindAll(this, 'saveInputs');
// Listen to model changes
// this.model.bind('change', this.edit)
},
getUser: function(cid) {
return this.model
},
saveInputs: function(evt) {
evt.preventDefault();
var model = this.model;
this.$el.find('input[id]').each(function() {
model.set( this.id, this.value );
});
this.model.save();
console.log(model);
this.$el.find('input').each(function() {
this.value = ""
});
}
});
Collection:
var Searchers = Backbone.Collection.extend({
model: User
});
And my app.js
var SignUp = Backbone.View.extend({
el: '.container'
});
var userModel = new User();
var searchersCollection = new Searchers();
searchersCollection.bind('change', function(rec){
console.log('A record was changed:' + rec);
});
var signupView = new SignupView({
el: $(".form-signin"),
model: userModel
});
var signup = new SignUp();
So if a user type his data in the form and then i chekc in the web console:
userModel
child {cid: "c1", attributes: Object, _changing: false, _previousAttributes: Object, changed: Object…}
but if new user type in new data and i check again web console:
userModel
child {cid: "c1", attributes: Object, _changing: false, _previousAttributes: Object, changed: Object…}
I got the same cid, i have just one model in my userModel and attributes are the last introduced.
What i need instead is to save all my users, not ovewrite them, and be able to access to them.. throught my model i guess?
I can't understand where i'm wrong :/
From what I understood what you need is a collection, not a model.
A model stands for a single entity, like a book, or a user.
The save method of a model is for syncing it's data with the persistance layer and updating itself with the data return from it
A collection is a collection of models. If you want a new model with different cid everytime the user saves the form, you should add it to a collection, backbone will initialize a new instance of the model specified in collection's model property and add it to the collection.
Since you have an array of user entities, you should be pointing a collection to users.json, not a model.
var Searchers = Backbone.Collection.extend({
url: 'src/users.json',
model: User
});
And instead of a single model, you should pass a collection for that type of models:
var signupView = new SignupView({
el: $(".form-signin"),
collection: searchersCollection
});
And in the save method pass the data that should be added as a new model to collections add method:
saveInputs: function(evt) {
evt.preventDefault();
this.collection.add({
name: "",
age: ""
});
}
Also note that you need to call the fetch method so that the collection will be populated from the endpoint.
I suggest going through the documentation and tutorials since it seems you still needs to understand the basics.
cid means ClientID and is assigned when model object is created.
U should use "idAttribute" to define the remote id field.

Comparing value from input with backbone collection data

I'm trying to create very simple login with backbonejs. Collection stores usernames and passwords. Login view has two inputs and on click it should perform check function and compare input value with data from collection.
Html part looks like this:
<div class="login-block">
<script type="text/template" id="start">
<form id="login">
<div class="input-wrapper"><input type="text" placeholder="Username" id="username" required></div>
<div class="input-wrapper"><input type="password" placeholder="Password" id="password" required></div>
<div class="input-wrapper"><button class="btn">Sign in!</button></div>
</form>
</script>
<div class="error" class="block">
Error
</div>
<div class="success">
Success
</div>
</div>
Here is my Js code:
var User = Backbone.Model.extend({
defaults: {
login: 'root',
mail: 'root#mail.com',
password: ''
}
});
var user = new User();
//variable to store username
var loginData = {
username: "",
password: ""
}
// userbase
var UserCollection = Backbone.Collection.extend({
model: User
});
var userCollection = new UserCollection([
{
username: 'Ivan',
mail: 'ivan#mail.com',
password: '1234'
},
{
username: 'test',
mail: 'test#mail.com',
password: 'test'
}
]);
// login page
var LoginView = Backbone.View.extend({
el: $(".login-block"),
events: {
"click .btn": "check"
},
check: function(){
loginData.username = this.$el.find("#username").val(); // store username
loginData.password = this.$el.find("#password").val();// store password
if (loginData.username === userCollection.each.get("username") && loginData.password === userCollection.each.get("password"))
{appRouter.navigate("success", {trigger: true});
}else{
appRouter.navigate("error", {trigger: true});
}
},
render: function () {
//$(this.el).html(this.template());
var template = _.template($('#start').html())
$(this.el).html(template());
//template: template('start');
return this;
}
});
var loginView = new LoginView({collection: userCollection});
var AppRouter = Backbone.Router.extend({
routes: {
'': 'index', // start page
'/error': 'error',
'/success': 'success'
},
index: function() {
loginView.render();
console.log("index loaded");
},
error: function(){
alert ('error');
},
success: function(){
console.log('success');
}
});
var appRouter = new AppRouter();
Backbone.history.start();
It works fine to the check function, and it stores username and password, but something is clearly wrong either with router or check function when it starts comparison. Instead of routing to success or error page, it rerenders index page.
P.S I didn't use namespacing and code in general is not of a greatest quality, but it was made for educational purpose only.
You have to add the attribute type="button" to your button, otherwise it will submit the form when clicked (See this question):
<script type="text/template" id="start">
<form id="login">
<div class="input-wrapper"><input type="text" placeholder="Username" id="username" required></div>
<div class="input-wrapper"><input type="password" placeholder="Password" id="password" required></div>
<div class="input-wrapper"><button class="btn" type="button">Sign in!</button></div>
</form>
</script>
You can also return false in the click event handler, which would cancel the default action. (submitting the form, if you don't add type="button").
For comparing the values with the hardcoded collection, you can't call each as you where doing (which is an iteration function provided by Underscore) because you would receive an error. You could use Underscore's findWhere method which is also available in Backbone collections. So the click event handler (Your check function) could look like this:
check: function(){
loginData.username = this.$el.find("#username").val(); // store username
loginData.password = this.$el.find("#password").val();// store password
if(userCollection.findWhere({username: loginData.username, password: loginData.password})){
appRouter.navigate("success", {trigger: true});
}else{
appRouter.navigate("error", {trigger: true});
}
return false;
},
You can try it on this fiddle
The logic check you're doing doesn't look like it would work to me. I would expect the following to generate an error:
userCollection.each.get('username')
the function you're calling on your collection, each, is a wrapped underscore method which takes a function callback as a parameter. If you want to check your username and password, I'd do something like this:
var user = userCollection.findWhere({ username: loginData.userName });
This will return you the model where the username matches. Then you can check the password of that model:
if (user.get('password') === loginData.password) {
// do something
} else {
// do something else
}
EDIT Heck, you can do both checks at once:
var user = userCollection.findWhere({ username: loginData.userName, password: loginData.password });
I'll leave the previous code up just to demonstrate.

Backbone Login Screen

I am fairly new to BackboneJS. After writing multiple GET implementation, I am trying to implement Login screen with Backbone JS.
Folder Structure
app
-->model
-->view
-->templates
-->server
formSignIn.html
<form class="form-signin" role="form">
<h2 class="form-signin-heading">Please sign in</h2>
<input type="email" id="email" class="form-control" placeholder="Email address" required="" autofocus="">
<input type="password" id="password" class="form-control" placeholder="Password" required="">
<label class="checkbox">
<input type="checkbox" value="remember-me"> Remember me
</label>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
Backbone View
var SignInView = Backbone.View.extend({
el:$('.container'),
template:_.template('../templates/formSignIn.html'),
events: {
"click .btn":"signIn"
},
initialize: function() {
this.model.on('change', this.render, this);
},
render: function() {
var attributes = this.model.toJSON();
this.$el.html(this.template(attributes));
},
signIn: function() {
this.model.signIn({
email: $('#email').val(),
password: $('#password').val()
});
}
});
var signInView = new SignInView({model: signInModel});
signInView.render();
Backbone Model
var SignInModel = Backbone.Model.extend({
url:function() {
'http://localhost:3000/singIn'
},
defaults: {
email:"",
password:""
},
parse: function(resp) {
return resp;
},
signIn: function() {
this.save();
}
});
var signInModel = new SignInModel();
Issues:
Template HTML is not rendering. When I open the page it shows ../templates/formSignIn.html. It means _template is not recognizing the html.
How is the view and model implementation? Is this the right way of doing? I am not very confident about calling the model's save().
In answer to your first question _.template(...) takes in a string. If you want the contents of ../templates/formSignIn.html you must include it in the dom or request it, for example using ajax.
If included in the dom it would look something it like this:
// Somewhere in the html...
<script type="text/html" id="form-signin-tpl">
<form class="form-signin" role="form">
...
</form>
</script>
// in your view
_.template($('#form-signin-tpl').html());
If you need to request the template during runtime you can use RequireJS which handles this nicely, or you could manually request it with jQuery, perhaps like this:
$.get( "path/to/templates/formSignIn.html", function( html ) {
var tpl = _.template(html);
});
In answer to the second question
the model's url parameter is a string, not a function.
You only need to define parse if you need to customize how the server's data is parsed.
This is probably more what you're going for:
var SignInModel = Backbone.Model.extend({
url: 'http://localhost:3000/singIn',
defaults: {
email:"",
password:""
},
signIn: function() {
this.save();
}
});
var signInModel = new SignInModel();
Lastly, regarding authenticating a user, a model might not be the best way to handle this. There are a few SO questions regarding athenticating a user in Backbone apps, such as this one

render view in backbone.js + rails

I am trying to render the view "new_view.js.coffee" (a form to create users) on my root page. I am using the rails-backbone gem, so far I have this:
app/views/home/index.html.erb
<div id="container">Loading...</div>
<script type="text/javascript">
$(function() {
window.newView = new Example.Views.Users.NewView({model: users});
newView.render();
Backbone.history.start();
});
</script>
Its basically a copy of this (from the rails-backbone README.md):
app/views/posts/index.html.erb
<div id="posts"></div>
<script type="text/javascript">
$(function() {
// Blog is the app name
window.router = new Example.Routers.PostsRouter({posts: <%= #posts.to_json.html_safe -%>});
Backbone.history.start();
});
My new view is this:
assets/javascripts/backbone/views/users/new_view.js.coffee
Example.Views.Users ||= {}
class Example.Views.Users.NewView extends Backbone.View
template: JST["backbone/templates/users/new"]
events:
"submit #new-user": "save"
constructor: (options) ->
super(options)
#model = new #collection.model()
#model.bind("change:errors", () =>
this.render()
)
save: (e) ->
e.preventDefault()
e.stopPropagation()
#model.unset("errors")
#collection.create(#model.toJSON(),
success: (user) =>
#model = user
window.location.hash = "/#{#model.id}"
error: (user, jqXHR) =>
#model.set({errors: $.parseJSON(jqXHR.responseText)})
)
render: ->
$(#el).html(#template(#model.toJSON() ))
this.$("form").backboneLink(#model)
return this
Here is my users router:
users_router.js.coffee
class Example.Routers.UsersRouter extends Backbone.Router
initialize: (options) ->
#users = new Example.Collections.UsersCollection()
#users.reset options.users
routes:
"new" : "newUser"
"index" : "index"
":id/edit" : "edit"
":id" : "show"
".*" : "index"
newUser: ->
#view = new Example.Views.Users.NewView(collection: #users)
$("#users").html(#view.render().el)
index: ->
#view = new Example.Views.Users.IndexView(users: #users)
$("#users").html(#view.render().el)
show: (id) ->
user = #users.get(id)
#view = new Example.Views.Users.ShowView(model: user)
$("#users").html(#view.render().el)
edit: (id) ->
user = #users.get(id)
#view = new Example.Views.Users.EditView(model: user)
$("#users").html(#view.render().el)
The "new" template:
assets/javascripts/backbone/templates/users/new.jst.ejs
<h1>New user</h1>
<form id="new-user" name="user">
<div class="field">
<label for="name"> name:</label>
<input type="text" name="name" id="name" value="<%= name %>" >
</div>
<div class="field">
<label for="email"> email:</label>
<input type="text" name="email" id="email" value="<%= email %>" >
</div>
<div class="actions">
<input type="submit" value="Create User" />
</div>
</form>
Back
When I go to localhost:3000, all I see is "Loading..." (which I put in for the purpose of seeing whether or not the app starts). Again, how would I go about rendering the new_view in the #container div above?
You have not appended your rendered content to body:
$(function() {
window.newView = new Example.Views.Users.NewView({model: users});
$('body').html(newView.render().$el)
// newView.render();
Backbone.history.start();
});

Categories

Resources