Read XML and append in view using Backbone js - javascript

How to read XML and append in view with Backbone.
XML file has been read and successfully appended in view. But i don't know how to approach in Backbone structure (Using its Model).
XML file (read asseturl , width, height)
<library>
<assets>
<asset asseturl="img_1.png" width="100" height="150"></asset>
<asset asseturl="img_2.png" width="200" height="250"></asset>
<asset asseturl="img_3.png" width="300" height="350"></asset>
</assets>
</library>
Backbone js code
var Book = Backbone.Model.extend();
var Books = Backbone.Collection.extend({
model: Book,
url: "file.xml",
parse: function (data)
{
var $xml = $(data);
return $xml.find('assets').map(function()
{
var bookTitle = $(this).find('asset').each(function(){
var this_url = $(this).attr('asseturl');
var this_width = $(this).attr('width');
var this_height = $(this).attr('height');
$('.character-list').append('<li><span class="asset char">'+
'<img width="'+this_width+'" height="'+this_height+'" src="'+this_url+'">'+
'</span></li>');
});
return {title: bookTitle};
}).get();
},
fetch: function (options)
{
options = options || {};
options.dataType = "xml";
return Backbone.Collection.prototype.fetch.call(this, options);
}
});
var bookListView = Backbone.View.extend({
initialize: function ()
{
this.listenTo(this.collection, "sync", this.render);
},
render: function ()
{
console.log(this.collection.toJSON());
}
});
var bks = new Books();
new bookListView({collection: bks});
bks.fetch();
HTML code to append
<ul class="character-list">
</ul>
Even-though the above append functionality works for me, it's not good practice to approach this in Backbone parse function.

Don't put the rendering logic into the collection's parse function.
The collection's role is to manage models and syncing with an API. It's the view's responsibility to render.
First, let's simplify the collection parsing. From the Backbone documentation, parse should do the following only:
The function is passed the raw response object, and should return the
array of model attributes to be added to the collection.
parse: function(response) {
var $xml = $(response);
// this will return an array of objects
return $xml.find('assets').children('asset').map(function() {
var $asset = $(this);
// returns raw "model" attributes
return {
asseturl: $asset.attr('asseturl'),
width: $asset.attr('width'),
height: $asset.attr('height')
};
}).get();
},
Then, make a simple view for each asset:
var BookView = Backbone.View.extend({
tagName: 'li',
template: _.template('<span class="asset char"><img width="<%= width %>" height="<%= height %>" src="<%= asseturl %>"></span>'),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
And it's in the list view that the rendering of each assets is handled.
var BookListView = Backbone.View.extend({
initialize: function() {
this.childViews = [];
this.listenTo(this.collection, "sync", this.render);
},
render: function() {
this.$el.empty();
this.collection.each(this.renderBook, this);
return this;
},
renderBook: function(model) {
var view = new BookView({ model: model });
this.childViews.push(view);
this.$el.append(view.render().el);
},
});
To use it:
var bks = new Books(),
view = new BookListView({ el: $('.character-list'), collection: bks });
view.render();
bks.fetch();

Related

render not getting called for one api and is for another

I have configured a simple backbone Model and View using an underscore template. The exact same configuration is used for two separate APIs.
API 1 works as expected.
To reproduce the problem, comment out the url for API 1 and uncomment the url for API 2.
As you can see I have normalized the response data for both apis, the exact same data structure is returned for both apis. However, the render method for API 2 is not called. To make matters even more strange, on rare occasions render does get called by API 2.
What am I missing here?
// Model
var Quote = Backbone.Model.extend({
// API 1
//urlRoot: 'http://quotes.stormconsultancy.co.uk/quotes/1.json',
// API 2
urlRoot: 'http://quotes.rest/qod.json',
parse: function (data){
try{
data = data['contents'].quotes[0];
}
catch(e){
}
var rd = {author:data.author, quote:data.quote}
console.log("parsed", typeof rd, rd);
return rd;
},
// UPDATE as suggested by cory
initialize: function() {
this.on('all', function(eventName) {
console.log('QuoteModel: ' + eventName);
});
}
});
// View
var QuoteView = Backbone.View.extend({
initialize: function() {
this.template = _.template($('#quote-template').html());
this.listenTo(this.model, 'change', this.render);
},
render: function(){
console.log("render", this.model.attributes)
this.$el.html(this.template(this.model.attributes));
}
});
var quoteM = new Quote();
quoteM.fetch();
$(document).ready(function() {
var quoteV = new QuoteView({
el: $('#quote'),
model: quoteM
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script>
<script type="text/html" id="quote-template">
<p>The author is : <%= author %></p>
<p>The content is : <%= quote %></p>
</script>
<div id="quote"></div>
You have a race condition, where you fetch before creating the view.
So if the fetch finishes before the document is ready, the change event gets triggered before the view has started listening to the model.
The simplest solution
$(document).ready(function() {
var quoteM = new Quote();
var quoteV = new QuoteView({
el: $('#quote'),
model: quoteM
});
// fetch after
quoteM.fetch();
});
The best solution
var API_DOMAIN = "http://quotes.rest/";
// Reusable model
var Quote = Backbone.Model.extend({});
// reusable quotes collection
var QuoteCollection = Backbone.Collection.extend({
model: Quote,
// simple generic parse
parse: function(response) {
return response.contents.quotes;
},
});
// View
var QuoteView = Backbone.View.extend({
// GOOD: gets called once
template: _.template($('#quote-template').html()),
initialize: function() {
// BAD: gets called for every view
// this.template = _.template($('#quote-template').html());
this.listenTo(this.model, 'change', this.render);
},
render: function() {
console.log("render", this.model.attributes)
this.$el.html(this.template(this.model.toJSON()));
// Backbone standard for chaining
return this;
}
});
$(function() {
var quoteV,
collection = new QuoteCollection();
collection.fetch({
url: API_DOMAIN + 'qod.json',
success: function(collection, response, options) {
// only create the view when you need it
quoteV = new QuoteView({
el: $('#quote'),
model: collection.first()
});
// manually render to be 100% in control. The event now only
// serves if the model really changes.
quoteV.render();
}
});
});
<div id="quote"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script>
<script type="text/html" id="quote-template">
<p>The author is :
<%= author %>
</p>
<p>The content is :
<%= quote %>
</p>
</script>
Add some logging for the events on your Quote model, and you should be able to track down the problem quickly.
var Quote = Backbone.Model.extend({
initialize: function() {
this.on('all', function(eventName) {
console.debug('QuoteModel: ' + eventName);
});
}
});

Print content of popup dialong by javascript in backbone project

I have a button in and it is used for calling a dialog. Dialog's content is a pdf got from jasper report.
Here is the backbone view of dialog :
var framSrc = getRSUR; //getting pdf from jasper reporting server
var ReportView = Backbone.View.extend({
tagName : "div",
id : "report",
initialize: function() {
},
events : {
'click #print' : 'printDialog'
},
'printDialog' : function(){
printReport();
},
render: function(){
this.$el.html('<div><a id="print"><img src="printer.png" width="30px"><span style="position:relative;bottom:10px; left:10px;">Print Report</span></a></div><iframe id="agingFrame" src="'+framSrc+'"></iframe>');
return this;
}
});
printReport() :
function printReport(){
try{
var oIframe = document.getElementById('agingFrame');
var oContent = document.getElementById('agingFrame').innerHTML;
var oDoc = (oIframe.contentWindow || oIframe.contentDocument);
if (oDoc.document) oDoc = oDoc.document;
oDoc.write("<html><head><title>title</title>");
oDoc.write("</head><body onload='this.focus(); this.print();'>");
oDoc.write(oContent + "</body></html>");
oDoc.close();
}
catch(e){
self.print();
}
}
But what I've got from printing is the full page of the web page, not the pdf content in iframe.

Backbone search (always not found)

my searchform with backbone works.. except it always says that the item is not found so I think I always send an empty array so yes, then it's logic it won't find anything.
My searchresult view:
var ArtikelSearchResultsView = Backbone.View.extend({
el: '#searchResults',
render: function ( query_encoded ) {
var query = decodeURIComponent(query_encoded.replace(/\+/g, "%20"));
var result_artikels = _.filter(this.model.models, function (artikel_model) {
var artikel = artikel_model.attributes;
for (var key in artikel) {
if ( artikel[key].toLowerCase().indexOf( query.toLowerCase() ) >= 0 )
{
return true;
}
}
return false;
});
// Show results
var template = $("#search-results").html();
var result_html = _.template( template, { artikels: result_artikels, query: query } );
this.$el.html( result_html );
}
});
My router sends this:
searchResults: function(query){
artikelSearchView.render(query);
var artikelSearchResultsView = new ArtikelSearchResultsView({ model: Artikel });
artikelSearchResultsView.render(query);
}
Artikel is in this case:
var Artikel = Backbone.Model.extend({
urlRoot: 'api/items.json',
defaults: {
titel: 'Titel niet opgegeven',
url_titel: 'unieke sleutel urltitel',
img_path: 'geen image toegevoegd',
commentaar: 'Commentaar niet opgegeven',
categorie: 'Categorie niet opgegeven',
waardering: 0,
artikel: 'Artikel niet opgegeven'
},
initialize: function(){
if(!this.get('description')){
var lazy = 'This user was too lazy too add a description';
this.set('description', lazy);
}
}
});
Full code: http://pastebin.com/Y9zi6aGH (Awere that I use Artikel and Artikels in different ways, I know it's bad practice but that's the way I go for now) So my question is: Can someone fix me this so I get searchresult? If I press "a" that should give allmost all my objects but it gives me nothing in results.
I have made some changes to your code in order to work :
1- In your view ArtikelSearchResultsView, I have bound it's model (collection) reset event to it's render method, thus once it's model is reset from the server it call it's render method :
var ArtikelSearchResultsView = Backbone.View.extend({
el: '#searchResults',
initialize: function(){
this.model.bind('reset', this.render, this);
} ...
2- change
var artikels = new Artikels();
var artikel = new Artikels();
to
var artikels = new Artikels();
var artikel = new Artikel();
3- And finally change your router :
searchResults: function(query){
var artikelSearchResultsView = new ArtikelSearchResultsView({ model: artikels });
artikels.fetch();
}
I have also removed the filtering from ArtikelSearchResultsView render method in order to test it, now the 'this.model.models' is populated with the data you receive from the server.

Catching form submissions in Backbone

I have a URL route that has a parameter whose value is not known ahead of time when the page is loaded. For example:
/read/book/page/:page
But when the user is selection screen they get to type in which page number they want to start on then click submit. Is it possible to catch this form submission and place it in the URL? Normally there is a question mark (?) right because its a GET request. But Backbone how can backbone catch that?
Demo:
http://jsfiddle.net/vpetrychuk/PT2tU/
JS:
var Model = Backbone.Model.extend({
url : function () {
return 'http://fiddle.jshell.net/echo/json?page=' + this.get('page');
},
// remove it
parse : function (response) {
response || (response = {});
response.justToTriggerChangeEvent = Math.random();
return response;
},
getPageContent : function () {
return 'Here should be page a content for page #' + this.get('page');
}
});
var View = Backbone.View.extend({
el : '[data-page]',
events : {
'submit' : 'submit'
},
initialize : function () {
this.listenTo(this.model, 'change', this.showPage);
},
showPage : function () {
this.$('[data-page-content]').html(this.model.getPageContent());
},
submit : function (e) {
e.preventDefault();
var page = this.$('[data-page-num]').val();
if (page) {
app.navigate('read/book/page/' + page, true);
}
}
});
var Router = Backbone.Router.extend({
routes : {
'read/book/page/:page' : 'page'
},
initialize : function (options) {
this.bookModel = options.bookModel;
},
page : function (page) {
this.bookModel.set('page', page);
this.bookModel.fetch();
}
});
var model = new Model();
var view = new View({model:model});
var app = new Router({bookModel:model});
Backbone.history.start();
HTML:
<div data-page>
<form>
<label><input type="text" data-page-num /></label>
<button type="submit">Submit</button>
</form>
<div data-page-content></div>
</div>

My view dont recieve data from my Controller

I will just show the part of the code I am having problens. If you think that you need to see all the code, let me know.
My Controller:
index: function() {
var documents = new App.Collections.Documents();
documents.fetch({
success: function() {
new App.Views.Index({ collection: documents });
},
error: function() {
new Error({ message: "Error loading documents." });
}
});
},
My View:
App.Views.Index = Backbone.View.extend({
initialize: function() {
console.log(this.documents);
this.documents = this.options.documents;
this.render();
},
render: function() {
if(this.documents.length > 0) {
var out = "<h3><a href='#new'>Create New</a></h3><ul>";
_(this.documents).each(function(item) {
out += "<li><a href='#documents/" + item.id + "'>" + item.escape('title') + "</a></li>";
});
out += "</ul>";
} else {
out = "<h3>No documents! <a href='#new'>Create one</a></h3>";
}
$(this.el).html(out);
$('#app').html(this.el);
}
});
The console.log of document code is: undefined
In the controller, var document is valid.
WHYYyY? How I can Access the json sent by the Controller in the view?
Thanks,
console.log(this.documents);
this.documents = this.options.documents;
Your using console.log before this.documents is even set.
UPDATE:
new App.Views.Index({ collection: documents });
this.documents = this.options.documents;
Wouldn't it be this.documents = this.options.collection; ? Because I don't see you passing the option.documents variable anywhere.
You pass collection: documents, so in your view you access it with this.collection.
Check out this jsfiddle that shows it working: http://jsfiddle.net/dira/TZZnA/
I dont know backbone too well ... but is this.documents defined before entering the initialize function ? seems not to be - try switching around the lines :
this.documents = this.options.documents; // define the variable
console.log(this.documents); // console log it

Categories

Resources