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();
});
Related
I am trying to create an AutoComplete textbox, while using an EditorTemplate. The problem I am facing is that by using the Html.BeginCollectionItem() extension solution (https://www.nuget.org/packages/BeginCollectionItem/), the Id's of the EditorFor() and TextBoxFor() methods get set dynamically and this breaks my javascript. Next to that, I do not exactly know if this is even possible (and if so, how. Below you will find how far I have come).
In the main view I have a loop to generate a partial view for each item in a collection
for (int i = 0; i < Model.VoedingCollection.Count; i++)
{
#Html.EditorFor(x => x.VoedingCollection[i], "CreateVoedingTemplate")
}
The partial view CreateVoedingTemplate.cshtml uses the Html.BeginCollectionItem() method
#using (Html.BeginCollectionItem("VoedingCollection"))
{
string uniqueId = ViewData.TemplateInfo.HtmlFieldPrefix.Replace('[', '_').Replace(']', '_').ToString();
string searchId = "Search_";
string standaardVoedingId = "StandaardVoeding_";
foreach (KeyValuePair<string, object> item in ViewData)
{
if (item.Key == "Count")
{
searchId = searchId + item.Value.ToString();
standaardVoedingId = standaardVoedingId + item.Value.ToString();
}
}
<div class="form-horizontal">
<div class="form-group" id=#standaardVoedingId>
#Html.LabelFor(model => model.fk_standaardVoedingId, "Voeding naam", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.HiddenFor(model => model.fk_standaardVoedingId)
<input type="text" id='#searchId' placeholder="Search for a product"/>
</div>
</div>
</div>
<script type="text/javascript">
var id = '#uniqueId' + '_fk_standaardVoedingId'.toString();
var search = '#searchId'.toString();
var url = '#Url.RouteUrl("DefaultApi", new { httproute = "", controller = "AgendaApi" })';
$(document.getElementById(search)).autocomplete({
source: function (request, response) {
$.ajax({
url: url,
data: { query: request.term },
dataType: 'json',
type: 'GET',
success: function (data) {
response($.map(data, function (item) {
return {
label: item.standaardVoedingNaam,
value: item.standaardVoedingId
}
}));
}
})
},
select: function (event, ui) {
$(document.getElementById(search)).val(ui.item.label);
//$('#id').val(ui.item.value);
document.getElementById(id).value = ui.item.value;
return false;
},
minLength: 1
});
</script>
}
<link href="~/Content/SearchBox/jquery-ui.css" rel="stylesheet" />
<script src="~/Scripts/SearchBox/jquery-1.9.1.js"></script>
<script src="~/Scripts/SearchBox/jquery-ui.js"></script>
In the script above, I am trying to make a function where the user can type in the name of a standaardVoeding item and then get results, where, after the user selects a standaardVoeding item, the standaardVoedingId property gets set. Then, after submitting the whole form, the controller receives the standaardVoedingId (with all the other info as well)
So I guess Javascript somehow cannot handle the Razor View # code and, next to that, Html.BeginCollectionItem does something fishy because you cannot set the value of its textboxes via code during runtime. Next to that, I have tried doing alert(document.getElementById(*html.begincollectionitemId*)) and it finds the fields fine. But apparently all other methods do not work?
Is there perhaps a better solution to getting this to work?
The BeginCollectionItem() method alters the id and name attributes of the html generated by the inbuilt helpers, in your case, for the hidden input, instead of
<input ... name="fk_standaardVoedingId" .... />
it will generate
<input ... name="VoedingCollection[xxxx].fk_standaardVoedingId" .... />
where xxxx is a Guid.
While it would be possible to use javascript to extract the Guid value from the textbox (assuming that was generated correctly usind #Html.TextBoxFor()) and build the id of the associated hidden input to use as a selector, it is far easier to use class names and relative selectors.
You also need to remove your scripts and css from the partial and put that in the main view (or its layout). Apart from the inline scripts, your duplicating it for each item in your collection.
Your partial needs to be
#using (Html.BeginCollectionItem("VoedingCollection"))
{
<div class="form-horizontal">
<div class="form-group">
#Html.LabelFor(model => model.fk_standaardVoedingId, "Voeding naam", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10 item"> // add class name
#Html.HiddenFor(model => model.fk_standaardVoedingId)
<input type="text" class="search" placeholder="Search for a product"/>
</div>
</div>
</div>
}
Note the class name for the textbox and its container which also includes the hidden input. Then in the main view, the script will be
<script type="text/javascript">
var url = '#Url.RouteUrl("DefaultApi", new { httproute = "", controller = "AgendaApi" })';
// Attach the script to all textboxes
$('.search').autocomplete({
source: function (request, response) {
$.ajax({
url: url,
data: { query: request.term },
dataType: 'json',
type: 'GET',
success: function (data) {
response($.map(data, function (item) {
return {
label: item.standaardVoedingNaam,
value: item.standaardVoedingNaam, // this needs to be the name
id: item.standaardVoedingId // add property for the id
}
}));
}
})
},
select: function (event, ui) {
// Get the associated hidden input
var input = $(this).closest('.item').find('input[type="hidden"]');
// Set the value of the id property
input.val(ui.item.id);
},
minLength: 1
});
</script>
Based on your comments that your are not dynamically adding or removing items in the view, then there is no point in the extra overhead or using the BeginCollectionItem() method. Change the name of your partial to standaardvoeding.cshtml (assuming that's the name of the class) and move it to the /Views/Shared/EditorTemplates folder.
Then in the main view, replace your for loop with
#Html.EditorFor(m => m.VoedingCollection)
which will generate the correct html for each item in the collection. Finally remove the BeginCollectionItem() method from the template so that its just
<div class="form-horizontal">
<div class="form-group">
#Html.LabelFor(m => m.fk_standaardVoedingId, "Voeding naam", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10 item"> // add class name
#Html.HiddenFor(m => m.fk_standaardVoedingId)
<input type="text" class="search" placeholder="Search for a product"/>
</div>
</div>
</div>
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.
Just getting started with Parse.com and following their JavaScript ToDo app tutorial and having some issues.
Basically it will work perfectly and post to the Parse database when I just have the Username and Password but if I try to add any additional fields like email or phone, it won't send it to the database. I have already defined both email and phone fields in my Parse database.
Any help would be appreciated. Thanks!
My html for my form:
<script type="text/template" id="login-template">
<header id="header"></header>
<form class="signup-form">
<h2>Sign Up</h2>
<div class="error" style="display:none"></div>
<input type="text" id="signup-username" placeholder="Username" />
<input type="password" id="signup-password" placeholder="Create a Password" />
<input type="email" id="signup-email" placeholder="Email" />
<input type="text" id="signup-phone" placeholder="Phone" />
<button>Sign Up</button>
</form>
</div>
</script>
My JS:
var LogInView = Parse.View.extend({
events: {
"submit form.signup-form": "signUp"
},
el: ".content",
initialize: function() {
_.bindAll(this, "signUp");
this.render();
},
signUp: function(e) {
var self = this;
var username = this.$("#signup-username").val();
var password = this.$("#signup-password").val();
var email = this.$("#signup-email").val();
var phone = this.$("#signup-phone").val();
Parse.User.signUp(username, password, email, phone, { ACL: new Parse.ACL() }, {
success: function(user) {
new ManageTodosView();
self.undelegateEvents();
delete self;
},
error: function(user, error) {
self.$(".signup-form .error").html(error.message).show();
self.$(".signup-form button").removeAttr("disabled");
}
});
this.$(".signup-form button").attr("disabled", "disabled");
return false;
},
render: function() {
this.$el.html(_.template($("#login-template").html()));
this.delegateEvents();
}
});
You should use the following syntax, like specified in the javascript docs (https://parse.com/docs/js_guide):
var user = new Parse.User();
user.set("username",username);
user.set("password",password);
user.set("email",email);
user.set("phone",phone);
user.signUp(null,{
success:function(user){...},
error:function(user,error){...}
});
You can add any fields you want with the set method.
I had to change <!--<button>Submit</button>--> to <!--<input type="submit"></input>--> to get this to work but then it did. Thanks.
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.
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