I have been learning Backbone.js and I am using it with an app on django where two photos are displayed initially: one is the main photo and other is thumbnail of next photo. I have returned json data containing the url of mainphoto and the thumbnail photo using Tastypie in the url /api/v1/photo. So, what I've done in Backbone is that:
// MODEL
var PhotoItem = Backbone.Model.extend({
urlRoot: '/api/v1/photo',
});
var PhotoView = Backbone.View.extend({
template: _.template($('#mainimg').html()),
initialize: function() {
this.render();
},
render: function(){
var templateArgs={
photo: this.model.get('photo')
};
alert(this.model.get('photo')); // this alerts undefined
this.$el.html(this.template(templateArgs));
}
});
var photoItem = new PhotoItem({id:1});
photoItem.fetch();
var photoView = new PhotoView({model: photoItem});
In the django-template here is the javascript where the template argument is utilized for displaying the main photo.
<script type="text/template" id="mainimg">
<img class = "main-img" id="mainimgid" src = <%= photo %> alt="main photo" />
</script>
And this is the json data that is returned for photoItem with id=1:
{"next_url": "/photos/preloaded/designstyles/thumb/arabic-living(main-photo-id)-thumbnail.png",
"parent_id": "1","photo": "/photos/preloaded/designstyles/big/arabic-bedroom.png",
"photo_id": "1", "resource_uri": "", "tags": "set([Decimal('2'), Decimal('3')])", "type": "Homedesign"}
But, the image cannot be loaded. I get a javascript 404 error:
http://localhost:8000/undefined
I guess this may be due to asynchronous loading of the code. And the src for the image remains
<img class = "main-img" id="mainimgid" src = <%= photo %> alt="main photo" />
when I see on the debugging window with Chrome debugger.
What am I missing? or Where am I wrong? Can I get help?
You cannot create the view without actually receiving the information from the server. You're basically passing an empty Backbone model to the view controller, that's why model.get('photo') is returning undefined. I'd recommend you to review basic AJAX, since that's what Backbone.Model.fetch does.
Asynchronous calls return immediately to avoid freezing the user interaction. That's the reason why you should not create the view until you do get the response from the server. Right solution will be something like this:
var photoItem = new PhotoItem({id:1}), photoView;
photoItem.fetch({
success: function () {
photoView = new PhotoView({model: photoItem});
},
error: function () {
alert('Something bad happened!);
}
});
Related
I've been trying to get my first Backbone.js app up and running, following the Backbone.js primer here.
I've followed the example through and now I'm trying to customise it for my purposes which are to simply retrieve and read a JSON file from my server. I don't need to be able to change or delete any of the data.
I've set up my html as per the primer below:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Backbone.js Primer</title>
<script type="text/javascript" src="./node_modules/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="./node_modules/underscore/underscore-min.js"></script>
<script type="text/javascript" src="./node_modules/backbone/backbone-min.js"></script>
<script type="text/javascript" src="./node_modules/moment/moment.js"></script>
<script type="text/javascript" src="./backbone.js"></script>
</head>
<body>
<div>
<h1>Transcripts Data</h1>
<div id="dailyTranscripts-app">
<ul class="dailyTranscripts-list"></ul>
</div>
</div>
</body>
</html>
I've then coded my backbone.js file as the primer describes below:
var yesterday = moment (new Date()).add(-1, 'days').format('YYYY-MM-DD')
var yesterdaysDataURL = 'https://mihndbotblob.blob.core.windows.net/mihndbot-transcripts/finalTranscripts/dailyTranscripts/' + yesterday + '.json'
// Model class for each transcript iten
var DailyTranscriptsModel = Backbone.Model.extend({
defaults: {
type: null,
MessageID: null,
MessageTime: null,
MessageChannel: null,
MessageSenderID: null,
MessageSenderName: null,
ConversationID: null,
MessageText: null,
MessageRecipientID: null,
QuickReplyDisplayText: null,
QuickReplyPayload: null,
Question: null,
Answer: null,
FollowUpPrompts: null
}
});
// Collection class for the DailyTransctipts list endpoint
var DailyTranscriptsCollection = Backbone.Collection.extend({
model: DailyTranscriptsModel,
url: yesterdaysDataURL
});
// View class for displaying each dailyTranscripts list item
var DailyTranscriptsListItemView = Backbone.View.extend({
tagName: 'li',
className: 'dailyTranscripts',
initialize: function () {
this.listenTo(this.model)
},
render: function () {
var html = '<b>Message ID: </b> ' + this.model.get('MessageID');
html += '<br><b>Message Time: </b>' + this.model.get('MessageTime');
this.$el.html(html);
return this;
}
});
// View class for rendering the list of all dailyTranscripts
var DailyTranscriptsListView = Backbone.View.extend({
el: '#dailyTranscripts-app',
initialize: function () {
this.listenTo(this.collection, 'sync', this.render);
},
render: function () {
var $list = this.$('ul.dailyTranscripts-list').empty();
this.collection.each(function (model) {
var item = new DailyTranscriptsListItemView({model: model});
$list.append(item.render().$el);
}, this);
return this;
}
});
// Create a new list collection, a list view, and then fetch list data:
var dailyTranscriptsList = new DailyTranscriptsCollection();
var dailyTranscriptsView = new DailyTranscriptsListView({collection: dailyTranscriptsList });
dailyTranscriptsList.fetch();
The major changes I've made to the code (apart from some customisations) are to remove the templates the primer uses to create the views (I couldn't get them working) and I've removed the Backbone CRUD elements as I only require my app to read data from the server, not update or delete it.
The issue I have is that whilst I'm pulling back the JSON file from the server, none of the data is rendering in the HTLM <div> as expected, it's just blank.
I know that Backbone.js is retrieving the data as when I add .then(function() {console.log(dailyTranscriptsList);}); to the final dailyTranscriptsList.fetch() call I can see the data in the browser console:
You need to wrap all of your backbone.js code within jQuery's .ready()
// backbone.js
$(document).ready(function () {
// all your backbone.js code here
})
This causes your js to run after the DOM is ready, so Backbone will know how to find the elements it needs in order for views to work.
You could also move <script type="text/javascript" src="./backbone.js"></script> to the end of the page, right before </body>
I have a list of messages. When a particular message is clicked it loads the details. This part works fine.
I want to load some other related data asynchronously when the clicked message is loaded. For that I'm nesting a view inside my messageView. However I am unable to load and access the data.
Here is my template
<script type="text/x-handlebars" id="message">
{{#view "messageThread" contentBinding="this"}}
{{#each message in view.getConversation}}
<div>Message body</div>
{{message.body}}
{{/each}}
</div>
{{/view}}
</script>
Here is the messageThreadView used in the template above
App.MessageThreadView = Ember.View.extend({
getConversation: function(){
var msg = this.get('context').model;
var sender_id = msg.sender.id;
var recipient_id = msg.recipient.id;
downloadConversation(recipient_id, sender_id);
return this.get('current_conversation');
}.property('current_conversation'),
});
Here is the asynchronous data load function called in the view above
function downloadConversation(recipient_id, sender_id){
$.getJSON(<a url>)
.then(function(data){
App.set('current_conversation', data['objects']);
});
}
How do I get view.getConversation to work as expected i.e load the data when it becomes available?
Here's the simplest pattern for asynchronous properties, especially when they are a collection. You essentially return a collection reference (in this case convo), then you asynchronously populate that collection from the reference.
App.MessageThreadView = Ember.View.extend({
getConversation: function(){
var msg = this.get('context').model,
sender_id = msg.sender.id,
recipient_id = msg.recipient.id,
convo = [];
$.getJSON(<a url>).then(function(data){
data.forEach(function(item){
convo.pushObject(item);
});
});
return convo;
}.property(), // this should be watching sender and receipient
});
I'm trying to get multiple instances of valum file uploader working on my site. It works great with one instance but anytime I loop over initialization code, wanting multiple buttons I don't see any buttons. Here's the code:
<cfoutput query="getTopics">
<script>
function createUploader(){
var uploader = new qq.FileUploader({
element: document.getElementById('file-uploader#refTopicID#'),
action: 'components/ProjectBean.cfc',
params: {method: 'Upload',
topicID: #refTopicID#,
count: #Evaluate("session.#refTopicAbv#Count")#,
topicName: '#refTopicAbv#'
},
encoding: 'multipart'
});
}
// in your app create uploader as soon as the DOM is ready
// don't wait for the window to load
window.onload = createUploader;
</script>
<div class="row" id="file-uploader#refTopicID#">
</div>
Any idea how to get multiple instance? Thanks in advance!
You're creating a javascript function inside of a loop. In other words you're defining it multiple times.
Instead you should move the createUploader function outside of your loop. And within the loop, simply call it multiple times for each of your topics.
Something like this:
<script>
function createUploader(elementID, topicID, topicCount, topicName){
var uploader = new qq.FileUploader({
element: document.getElementById(elementID),
action: 'components/ProjectBean.cfc',
params: {method: 'Upload',
topicID: topicID,
count: topicCount,
topicName: topicName
},
encoding: 'multipart'
});
}
</script>
<cfoutput query="getTopics">
<script>
createUploader('file-uploader#getTopics.refTopicID#', #getTopics.refTopicID#, #session[getTopics.refTopicAbv & "Count"]#, '#getTopics.refTopicAbv#');
</script>
<div class="row" id="file-uploader#getTopics.refTopicID#"> </div>
</cfoutput>
NB: I'm assuming the values all come from your query getTopics, so I've prefixed them with the query name to scope them properly. This isn't usually essential, but it's good practice for performance reasons (among other things).
I just start using Backbone. I have two questions to ask based on the code below.
The first issue is after I fill out the form and click the button, the model object should be created with some default attributes. However, the console.log prints the model with the newest attribute from the form I fill out before I pass the model to the new view object.
The second issue is I can successfully save the data to db, but my success call back function is not being called. could someone help me to answer these questions??
var form = document.forms[0];
var RetailerModel = Backbone.Model.extend({
urlRoot: ' retailer.php',
defaults: {
name: 'company-name',
address: 'company-address',
phone: 'company-phone',
icon: 'http://localhost/icon.png'
}
});
var RetailerCollection = Backbone.Collection.extend({
});
var RetailerView = Backbone.View.extend({
className: 'retailer',
template: _.template($('#retailer-template').html()),
initialize: function() {
//this.listenTo(this.model, 'change', this.render);
var obj = {
name: form.name.value,
address: form.address.value,
phone: form.phone.value
};
this.model.set(obj);
//why the successful callback does not work????
this.model.save(null, {success: function(model, response){console.log('successful');}});
},
render: function() {
$('#retailer-list').append(this.$el.html(this.template(this.model.toJSON())));
return this;
}
});
var RetailerViews = Backbone.View.extend({
});
$('#submit').click(function(e){
var retailer_model = new RetailerModel();
console.log(retailer_model); // this prints out the new changed attributes instead of the default ones, why???
var retailer_view = new RetailerView({model: retailer_model});
form.reset();
});
1 Get the correct state of the model
console.log(retailer_model) will not show the models attributes, it'll show the whole model, but console.log(retailer_model.attributes) will. Also bear in mind that console.log isn't always on correct, especially if you modify the object just after you log it this can lead to confusion!
To get the actual current state of the model you should make a shallow or deep copy of it. So rather than console.log(model) you could use underscores clone method for a shallow copy:
console.log(_(model).clone());
2 Success Callback on Save
To help there we'd really need to know more about your circumstances. The first thing I'd look for is whether your server is returning the right feedback. Backbone is a system based on REST standards. Are you sure your server is returning the right response code? If you use chrome, open your developer tools and then the network tab to inspect what response you're getting when posting your model. In order for the success callback to be fired the server should return a status of 200 or 201.
Another way to test that is to see whether the error callback is firing :).
I tried to see your errors, I realized that you missed these things:
1. You should do DOM related things when DOM ready event fired:
$(function() {
form = document.forms[0];
$(form).live('submit', function(e){
// ...
});
});
2. You can use submit for forms. It catches also enter click within form:
$(form).live('submit',function(){
//...
});
3. You should use return false; within submit form. It prevents default form data sending to action url.
$(form).live('submit', function(e){
// ...
return false;
});
So, it looks like this. I didn't check success callback, but I hope it will work.
<html>
<head>
<script src="jquery-1.7.2.js" type="text/javascript"></script>
<script type="text/javascript" src="underscore.js"></script>
<script type="text/javascript" src="backbone.js"></script>
<script type="text/javascript">
var RetailerModel = Backbone.Model.extend({
urlRoot: ' retailer.php',
defaults: {
name: 'company-name',
address: 'company-address',
phone: 'company-phone',
icon: 'http://localhost/icon.png'
}
});
var RetailerCollection = Backbone.Collection.extend({});
var RetailerView = Backbone.View.extend({
className: 'retailer',
//template: _.template($('#retailer-template').html()),
initialize: function() {
//this.listenTo(this.model, 'change', this.render);
var obj = {
name: form.name.value,
address: form.address.value,
phone: form.phone.value
};
this.model.set(obj);
//why the successful callback does not work????
this.model.save(null, {
success: function(model, response){
console.log('successful');
}
});
},
render: function() {
$('#retailer-list').append(this.$el.html(this.template(this.model.toJSON())));
return this;
}
});
var RetailerViews = Backbone.View.extend({});
$(function() { // you should do DOM related things when DOM ready event fired
form = document.forms[0];
$(form).live('submit', function(e){
var retailer_model = new RetailerModel();
console.log(retailer_model);
var retailer_view = new RetailerView({model: retailer_model});
form.reset();
return false; // to prevent form data sending return false
});
});
</script>
</head>
<body>
<form action="#" id="#submit">
<input type="submit" value="submit"/>
<input type="text" name="address"/>
<input type="text" name="name"/>
<input type="text" name="phone"/>
</form>
</body>
</html>
I have an Ajax.ActionLink inside a PartialView like so:
#Ajax.ActionLink(Model.IsVisible ? "Disable" : "Enable", "ToggleVisibility", "Review", new { area = "Admin", id = Model.Id }, new AjaxOptions { HttpMethod = "POST", OnComplete = "onComplete_AdminReviewOption" })
And the handling JavaScript function (declared in-line on the main View for now):
function onComplete_AdminReviewOption(ajaxContext) {
var jsonObject = ajaxContext.get_object();
}
Which throws a javascript error:
Object# has not definition for get_object().
I thought these JavaScript methods were part of the MicrosoftAjax.js / MicrosoftMvcAjax.js scripts, both of which i have included on my page.
Can anyone confirm which library these helper methods are present?
I load the required scripts in my Layout.cshtml file, then i do an AJAX call to render out the above PartialView.
So by the time i handle that function, the libraries are already loaded - which is why im confused.
Any ideas?
It looks like you are using ASP.NET MVC 3 and Razor. In this version jQuery is the default client scripting framework. No more MicrosoftAjax.js (thanks God). So:
function onComplete_AdminReviewOption(ajaxContext) {
var jsonObject = eval(ajaxContext);
}
Also don't forget to include jquery.unobtrusive-ajax.js.
If you want to use the legacy stuff you could by setting the following in your web.config:
<add key="UnobtrusiveJavaScriptEnabled" value="false" />
By default this variable is set to true.
Personally I prefer to use standard links:
#Html.ActionLink(
Model.IsVisible ? "Disable" : "Enable", // <-- this should probably be as a property directly in the view model, ex. Model.LinkText
"ToggleVisibility",
"Review",
new { area = "Admin", id = Model.Id },
new { id = "myLink" }
)
and AJAXify them using jQuery in a separate javascript file:
$(function() {
$('#myLink').click(function() {
$.post(this.href, function(result) {
// result is already a javascript object, no need to eval
});
return false;
});
});