Backbone.js and jQuery Mobile, function everytime a view is loaded - jquery-mobile

I have a 4 page jquery mobile/backbone.js app. I want to run a function to populate some inputs with an ajax call everytime a certain page is loaded. I know I can do the call on render but that is only when the app or page initially loads and won't run again unless it is refreshed.
The view looks something like this:
define([
'jquery',
'underscore',
'backbone',
'text!templates/default/parent.html'
], function($, _, Backbone, parentTemplate) {
var defaultView = Backbone.View.extend({
initialize: function() {
$(this.el).html(parentTemplate);
this.render();
},
events: {
'click #busNext': 'showTarget'
},
render: function() {
this.setValidator();
return this;
}
});
return new defaultView;
});

Here render calls onces,
If you wanted to call them again based on some condition then,
You have to call render method. Like.
this.render();
It will execute render code again and again as you wish.

Related

React-Rails: Load initial array state with ajax

I've been following along some tutorials with React and i'm starting out building an application on my own. I've come across a situation regarding components and i'm wondering if theres a best practice for this scenario. Please note, I'm just using react-rails; no flux or whatever for now.
setting the initial state with an array whose values get set through ajax and have that array display in the initial render
Here's what i'm trying to do: (stripped down for simplicity)
var ShoutList = React.createClass({
getInitialState: function(){
return {shouts: []};
},
componentDidMount: function(){
var component = this;
$.get('/api/shouts.json', function(data){
component.setState({shouts: data});
});
},
render: function(){
return (
<div>
{this.state.shouts[0].shout}
</div>);
}
});
So if I have this right, the order in which things are run go as follows:
On load, getInitialState sets shouts to an empty array
Render gets called and errors out because of trying to access the shout property on an empty array
ComponentDidMount gets called and sets the state of shouts to the data received from the ajax call. **I get an error when I try to do this in ComponentWillMount **
Render gets called again because the state has changed, but this time shouts[0].shout would contain data.
So I error out at step 2 and my work around is as follows:
var ShoutList = React.createClass({
getInitialState: function(){
return {shouts: []};
},
componentDidMount: function(){
var component = this;
$.get('/api/shouts.json', function(data){
component.setState({shouts: data});
});
},
emptyShouts: function(){
return(<div>No Shouts Yet!</div>);
},
shoutsList: function(){
return(<div>{this.state.shouts[0].shout}</div>);
},
render: function(){
if(this.state.shouts.length > 0){
return this.shoutsList();
}else {
return this.emptyShouts();
}
}
});
This works exactly like I need it to, but is there a better way of setting the initial state's array value with ajax and having it load in the initial render without having to do this if statement?
Thanks!
Without using Flux, I'd say your implementation is one of the few ways to get around this problem. Another way would be to have the logic before your render's return:
...
render: function () {
var renderedShout;
if (typeof this.state.shouts[0] === "undefined") {
renderedShout = <div>No Shouts Yet!</div>;
} else {
renderedShout = <div>{this.state.shouts[0].shout}</div>;
}
return renderedShout;
}
The advantage of doing it this way is that you will only have one return which could make it clearer for a reader in the long run.
If you want, you can try this change in your pre-edit code:
var ShoutList = React.createClass({
getInitialState: function(){
return {shouts: []};
},
componentDidMount: function(){
var component = this;
$.get('/api/shouts.json', function(data){
component.setState({shouts: data});
}.bind(this), 'json');
},
render: function(){
return (
<div>
{this.state.shouts[0].shout}
</div>);
}
});
bind your $.get call to the component making the call. It should work as expected from there.

JQuery-ui Tabs - reload page with completely new content not working

I'm loading in a report and displaying it with jquery-ui in tab format. The report is returned by an ajax call in json, and a function is formatting it into HTML. Example code below:
<div id="reportdiv">
</div>
<script>
function displayreport(objectid)
{
$( "#reportdiv" ).hide();
$( "#reportdiv" ).html("");
$.ajax({
type: "GET",
headers: { 'authtoken': getToken() },
url:'/reportservice/v1/report/'+objectid.id,
success: function(data){
if(data == null)
{
alert("That report does not exist.");
}
else
{
var retHTML = dataToTabHTML(data.config);
$("#reportdiv").html(retHTML).fadeIn(500);
$(function() {
tabs = $( "#reportdiv" ).tabs();
tabs.find( ".ui-tabs-nav" ).sortable({
axis: "x",
stop: function() {
tabs.tabs( "refresh" );
}
});
});
}
}
});
}
</script>
This works fine the first time displayreport is called. However, if the user enters another value and runs displayreport again, the "tabs" format is completely lost (the tabs are displayed as links above my sections, and clicking on a link takes you to that section further down the page).
I figured completely re-setting the reportdiv html at the beginning of the function would bring me back to original state and allow it to work normally every time. Any suggestions?
After more testing, found that destroy was the way to go. If I've set up tabs already, run the destroy, otherwise, skip the destroy (http://jsfiddle.net/scmxyras/1/) :
if(tabs!=undefined)$( "#reportdiv" ).tabs("destroy");

Backbone js: Accessing View variables from the template

I am new to Backbone js and i have written a piece of code like this;
Skymama.Views.UsersIndex = Backbone.View.extend({
template: JST['users/index'],
render: function() {
var allUsers = new Skymama.Collections.Users();
allUsers.fetch();
this.$el.html( this.template({users: allUsers }) );
return this;
},
});
How can i access the values of allUsers in the template under something like this;
<% _.each(users, function(user){ %>
<% }); %>
fetch() is an asynchronous method, you should call render after fetch() is finished.
usually you wanna initialize your collection in your view's initialize
initialize: function () {
this.collection = new Skymama.Collections.Users([]);
this.collection.fetch({reset: true});
this.listenTo(this.collection, 'reset', this.render);
},
render: function () {
this.$el.html( this.template({users: this.collection }) );
return this;
}
'reset' event will be fired on the collection when fetch() is successful.
you can also do this by attaching this.render to fetch as its callback
this.collection.fetch().done(this.render);
but you will want to bind render's context to the view if you prefer doing it this way
initialize: function () {
_.bindAll(this, 'render');
//...
}

Backbone.js understanding: fetch and display with templating

I've read many tutorials and made a search on the .net... but still I'm in trouble with Backbone.js. This is my simple scenario:
A Rails application responds to a GET request with a JSON collection of objects.
I want to dynamically build a list of table-rows with Backbone collections, when DOM is ready. This is the code is confusing me:
HTML part:
<script type="text/template" id="tmplt-Page">
<td>{{=title}}</td>
<td>{{=description}}</td>
</script>
Backbone's script:
$(function(){
var Page = Backbone.Model.extend({});
var Pages = Backbone.Collection.extend({
model: Page,
url: '/pages'
});
var pages = new Pages([
{title: 'ProvA1', description: ''},
{title: 'ProvA2', description: ''}
]);
var PageView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#tmplt-Page').html()),
render: function() {
this.$el.append(this.template(this.model.toJSON()));
return this;
}
});
var AppView = Backbone.View.extend({
el: $("#results"),
initialize: function () {
_.bindAll(this, 'render');
pages.on('reset', this.render)
},
render: function() {
this.$el.empty();
pages.each( function( page ) {
var view = new PageView({
model : page
});
this.$el.append(view.render().el);
});
return this;
}
});
var appview = new AppView;
});
Nothing renders on the screen.
There seem to be 2 problems:
1) fetch() is asynchronous, so the code is executed before the end of the ajax round-trip.
2) If I manually load some objects into the collection, this piece of code "this.template(this.model.toJSON())" does not substitute jSON attributes
EDIT :
To use mustache tags I wrote this code before all:
First, as you said, fetch() is asynchronous, but it triggers the 'reset' event when it completes, so you should add this in AppView.initialize:
pages.on('reset', this.render)
Second, you never insert the HTML of PageView anywhere. Add this in AppView.render:
// at the beginning
var self = this;
// and in the forEach loop
self.$el.append(view.el);
Third, at the beginning of AppView.render, you should clear the content of this.$el.
EDIT:
You still had a couple issues:
You are using underscore templates with mustache tags ({{ }} -> <%= %>)
Missing var self = this in render
You are not calling appview.render() ! :)
Here's your code working on jsfiddle: http://jsfiddle.net/PkuqS/

Backbone.js routes are not being triggered

I have a Rails 3.2.3 app with Backbone.js and I'm using pushState on my Backbone.history.
The Problem
When I click on a link which goes to say '/foo' to show appointment with ID: 1, then Backbone router gets to that first, which I can quickly see before Rails router takes over and complains that there is no route for /foo.
My Backbone.js code
Here is my backbone router.
window.AppointmentApp = new (Backbone.Router.extend({
routes: {
"": "index",
"foo": "foo",
"appointments": "index",
"appointments/:id": "show"
},
foo: function(){
$("#app").append("foo<br />");
},
initialize: function() {
this.appointments = new Appointments();
this.appointmentListView = new AppointmentListView({ collection: this.appointments });
this.appointmentListView.render();
},
start: function() {
Backbone.history.start({pushState: true});
},
index: function() {
$("#app").html(this.appointmentListView.el);
this.appointments.fetch();
},
show: function(id) {
console.log("Enter show");
}
}));
It should stay on the same page and attach a 'foo' to the end of the #app div, but it never does.
Backbone index viewer
window.AppointmentListView = Backbone.View.extend({
template: JST["appointments/index"],
events: {
"click .foo": function(){Backbone.history.navigate("foo");},
},
comparator: function(appointment){
return appointment.get('topic');
},
initialize: function(){
this.collection.on('reset', this.addAll, this);
},
render: function(){
this.$el.html(this.template);
this.addAll();
return this;
},
addAll: function() {
this.collection.forEach(this.addOne, this);
},
addOne: function(appointment){
var appointmentView = new AppointmentView({model: appointment});
this.$el.append(appointmentView.render().el);
}
});
app/assets/templates/appointments/Index.jst.ejs
<h1>Appointments</h1>
Say Foo
<a href=appointments/add>Add</a>
<div id="app"></div>
I was using pushState as it allows me to keep a history and the Back button functionality.
The Backbone.history.navigate doesn't call my Backbone route, it calls the Rails route instead. How do I go about fixing this?
Should I be trying to setup Backbone to accept routes such as 'appointments/1' and taking control or do I have to use a click event with a Backbone.history.navigate call like above?
You need to return false from your click .foo event handler, otherwise the browser will continue as if you'd clicked the link normally and request the actual /foo page from the server.
I think you've also got the call to Backbone.history.navigate("foo"); wrong - Backbone.history doesn't have a navigate function as far as I can see from the documentation. You should actually be calling .navigate on your Backbone.Router instance, and passing in the trigger option to cause it to call trigger the route. For example:
window.AppointmentApp.navigate("foo", { trigger : true } );
You may already know this but if you're planning on using pushState then you should really update your server side to support all the URLs that your client side does. Otherwise if a user decides to copy & paste the URL into another tab, they will just run into rails complaining that there is no route.

Resources