Backbonejs not showing this.options parsed from router - javascript

for some reason itemDetails is not parsing the .options in the MenuItemDetails view.
This is the javascript code:
// ROUTER
var AppRouter = Backbone.Router.extend({
routes: {
"": "list",
"menu-items/new": "itemForm",
"menu-items/:item": "itemDetails"
},
list: function(){
$('#app').html('List screen');
},
itemDetails: function(item){
var view = new MenuItemDetails (
{
name: item,
category: 'Entree',
imagepath: 'no-image.jpg'
}
);
$('#app').html(view.render().el);
},
itemForm: function(){
$('#app').html('New item form');
}
});
// VIEWS
var MenuItemDetails = Backbone.View.extend({
render: function(){
var markup = '<div>' +
'<h1>'+this.options.name+'</h1>' +
'</div>';
this.$el.html(markup);
return this;
}
});
var app = new AppRouter();
$(function() {
Backbone.history.start();
});
The html code is:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://jashkenas.github.io/underscore/underscore-min.js"></script>
<script src="http://jashkenas.github.io/backbone/backbone-min.js"></script>
<script src="http://getbootstrap.com/dist/js/bootstrap.js"></script>
<script src="js/views/menuitemdetails.js"></script>
<script src="js/app.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="container">
<h1>Off the Backbone</h1>
<ul class="nav nav-pills">
<li>Food</li>
<li>Add Item</li>
<li>Garden Salad</li>
</ul>
<div class="container" id="app">
</div>
</div>
</body>
</html>
As you can see, the two first links are doing well but the third one is getting an error which tells that the parameters in itemDetails are not parsed to the view. Please help me with this one here.

Your router works fine. It's the way in which your passing the data from the router into the view that's broken. Here is a working jsfiddle: http://jsfiddle.net/somethingkindawierd/rEa88/
Relevant changes are defining the model
var MenuItemModel = Backbone.Model.extend();
and then passing it into the view
var view = new MenuItemDetails ({
model: new MenuItemModel({
name: item,
category: 'Entree',
imagepath: 'no-image.jpg'
})
});
then using the model when rendering
'<h1>'+this.model.get('name')+'</h1>'

Your router seems confusing :
"menu-items/new": "itemForm",
"menu-items/:item": "itemDetails"
According to this mapping, both of the following will link will map to the itemDetails method :
<li>Add Item</li>
<li>Garden Salad</li>
The correct way is to change your router to handle only one route like so :
"menu-items/:item": "wrapper"
and handle the logic correctly in wrapper :
wrapper: function(item){
if(item === 'new'){
//handle new
} else {
//handle everything else
}
HTH!

Related

In VueJs3 why can't I access data properties from the vue object? (it worked in Vue2)

Background
I've been using Vue 2 for a long time and am currently exploring Vue 3 to see what converting our existing website will entail. Because this is a conversion I plan to use the options interface for Vue 3. For the most part it seems like the conversion should be fairly painless. But I have encountered one Vue3 behavior that I find very puzzling.
Vue 2 Example
In Vue 2 if I have the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script src="https://unpkg.com/vue#2.5.16/dist/vue.min.js"></script>
</head>
<body>
<h1>Vue2 app.variable example</h1>
<!-- vue template -->
<div id="appTemplate">
<div style="margin-bottom:20px">Count: <span v-text="count"></span></div>
<button v-on:click="increment()">Increment</button>
</div>
<script type="text/javascript">
//Vue2 Example
var app = new Vue({
el: '#appTemplate',
data: {
count: 101
},
methods: {
increment: function() {
this.count++;
}
},
created: function(){
_app = this;
}
});
alert("app.count is:" + app.count)
</script>
</body>
</html>
When the page loads, the alert looks like this:
This demonstrates that after the vue object is created I can access the data properties as though they hang directly off of the vue object. This is as expected since it's documented behavior.
However, Vue 3 Behaves Differently for Me
Here is a block of analogous Vue3 code with a bit of extra code you will probably notice:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue#3.0.5/dist/vue.global.js"></script>
</head>
<body>
<h1>Vue3 app.variable example</h1>
<!-- vue template -->
<div id="appTemplate">
<div style="margin-bottom:20px">Count: <span v-text="count"></span></div>
<button v-on:click="increment()">Increment</button>
</div>
<script type="text/javascript">
//Vue3 OptionsAPI
var _app;
var app = Vue.createApp({
data: function() {
return {
count: 101
}
},
methods: {
increment: function() {
this.count++;
}
},
created: function(){
_app = this;
}
}
);
app.mount("#appTemplate");
//It's really odd that we can't access the property this way:
alert("app.count is:" + app.count);
//but this works.
alert("_app.count is:" + _app.count);
</script>
</body>
</html>
When this page loads and the first alert box is shown, app.count is undefined.
To explore this a bit more you can see in the code that I set the value of an _app variable to the value of this in the created method. And I show a 2nd alert on load that displays _app.count. And sure enough that works and displays the correct value:
So that's pretty interesting. Is it by design in Vue 3 data properties can't be accessed directly from the vue object or is something wrong with my code? It seems like a really big change from Vue 2 if it's by design. So I'd like to hope that it's not.
So here is the question: Why can't I access count via app.count after the var app = Vue.createApp ?
In Vue 2, new Vue() returns the root component.
In Vue 3, createApp() returns the application instance, and the root component is returned from the application instance's mount():
var app = Vue.createApp({
data() {
return {
count: 101,
}
}
})
👇
var root = app.mount('#appTemplate')
console.log(root.count) // => 101
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue#3.0.5/dist/vue.global.js"></script>
</head>
<body>
<h1>Vue3 app.variable example</h1>
<!-- vue template -->
<div id="appTemplate">
<div style="margin-bottom:20px">Count: <span v-text="count"></span></div>
<button v-on:click="increment()">Increment</button>
</div>
<script type="text/javascript">
//Vue3 OptionsAPI
var app = Vue.createApp({
data: function() {
return {
count: 101
}
},
methods: {
increment: function() {
this.count++;
}
},
created: function(){
_app = this;
}
}
);
var root = app.mount("#appTemplate");
alert("root.count is:" + root.count);
</script>
</body>
</html>
Alternatively, you could chain the mount() call off of createApp():
var app = Vue.createApp().mount('#appTemplate')
console.log(app.count) // => 101
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue#3.0.5/dist/vue.global.js"></script>
</head>
<body>
<h1>Vue3 app.variable example</h1>
<!-- vue template -->
<div id="appTemplate">
<div style="margin-bottom:20px">Count: <span v-text="count"></span></div>
<button v-on:click="increment()">Increment</button>
</div>
<script type="text/javascript">
//Vue3 OptionsAPI
var app = Vue.createApp({
data: function() {
return {
count: 101
}
},
methods: {
increment: function() {
this.count++;
}
},
created: function(){
_app = this;
}
}
).mount("#appTemplate");
alert("app.count is:" + app.count);
</script>
</body>
</html>
You could also access that property before mounting the app :
app._component.data().count

Backbone.js primer customisation issues

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>

Backbone.js: Clicking U.I list elements to show them in a different section of the page

I am working on a project in Backbone.js were I get the results from a Food API and then display them. I have this piece of functionality working. The next piece of functionality I need is to be able to click an item from the results list and be able to save that result, showing it in the foods tracked list on the right side of the page. The foods tracked list would show the information about the Food (Food Name, Brand and Calories) as well as a total amount of calories from all the foods tracked. I am having trouble creating this functionality because I do not know how to click a list item and have it take the item information in the html list element and place it in another part of the page.
Here is my JSfiddle link- https://jsfiddle.net/Tiquismiquis/2nLezvmg/3/
Here is my JAVASCRIPT-
$(function(){
var SearchList = Backbone.Collection.extend({
initialize: function(){
this.bind("reset", function(model, options){
console.log("Inside event");
console.log(model);
});
},
//** 1. Function "parse" is a Backbone function to parse the response properly
parse:function(response){
//** return the array inside response, when returning the array
//** we left to Backone populate this collection
return response.hits;
}
});
// The main view of the application
var App = Backbone.View.extend({
el: 'body',
events: {
"input #searchBox" : "prepCollection",
"click li" : "track"
},
initialize: function () {
this.model = new SearchList();
this.prepCollection =_.debounce(this.prepCollection, 1000);
this.$list = $('#listing');
// this.saved =$('#tracked');
},
prepCollection: function(){
var name = $('input').val();
var newUrl = "https://api.nutritionix.com/v1_1/search/" + name + "?results=0%3A20&cal_min=0&cal_max=50000&fields=item_name,brand_name,item_id,nf_calories&appId=26952a04&appKey=private_key";
if (name == ""){
this.$list.html("")
}
else{
this.model.url = newUrl;
this.model.fetch({
success: function (response, xhr) {
console.log("Inside success");
console.log(response.toJSON());
},
error: function (errorResponse) {
console.log(errorResponse)
}
});
this.listenTo(this.model, 'sync', this.render);
}
},
// track: function(){
// },
render: function(){
var terms = this.model;
var wordhtml = "";
terms.each(function (term) {
wordhtml = wordhtml + "<li>" +"<strong>" + term.get('fields')["item_name"] + '</strong>'+ ' ('+ term.get('fields')["brand_name"] + ')'+' - '+ term.get('fields')["nf_calories"] + ' Calories' + "</li>"
}, this);
this.$list.html(wordhtml);
}
});
var app = new App();
});
Here is my HTML-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Food Guide App</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-6">
<h1>Interactive Food Guide</h1>
<input type="text" id="searchBox"> <br/><br/>
<ul id="listing"></ul>
</div>
<div class="col-xs-6">
<h1>Foods Tracked</h1>
<ul id="tracked"></ul>
<p id="total">total calories: <span>0</span></p>
</div>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<!-- Backbone and Underscore -->
<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.2.1/backbone-min.js"></script>
<!-- apps functionality -->
<script src="js/app.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="js/bootstrap.min.js"></script>
</body>
</html>
There are many ways to do it - through a backbone listView or simply by adding data-* attributes to the element.
Below is an example demonstrating the latter :
Template change :
var liTemplate = '<li data-brand="<%-data.brand_name%>" data-name="<%-data.item_name%>"><strong><%-data.item_name%> (<%-data.brand_name%>)</strong></li>';
wordhtml = _.template(liTemplate)({ data : term.get('fields')});
View change :
events:{
'click li': 'track'
},
track: function(e){
var $target = $(e.currentTarget);
var itemName = $target.attr('data-name');
var brandName = $target.attr('data-brand');
//do whatever you need
}
Find the working fiddle at https://jsfiddle.net/nitincool4urchat/2nLezvmg/8/

Simplest of backbone.js examples

I'm creating a bare bones backbone example to try to learn it and am having issues getting my view to render. I've based it on Thomas Davis's tutorial but looked at many of the other apps and tutorials available.
I'm changing Davis's tutorial not only because I want to add an input box, but also because based on the backbone docs I thought it needed less code and a different structure. Obviously because I can't get this to work, I don't know what's needed and what isn't.
My ultimate goal was to just add the names in li tags within ul#friends-list, although I don't think el: 'body' will help me there.
What am I doing wrong? Thanks for any help.
My html:
<!DOCTYPE HTML>
<html>
<head>
<title>Tut</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.cdnjs.com/ajax/libs/underscore.js/1.1.4/underscore-min.js"></script>
<script type="text/javascript" src="http://ajax.cdnjs.com/ajax/libs/backbone.js/0.3.3/backbone-min.js"></script>
</head>
<body>
<input type="text" placeholder="Enter friend's name" id="input" />
<button id="add-input">Add Friend</button>
<ul id="friends-list">
</ul>
<script type="text/javascript" src="test.js"></script>
</body>
</html>
My test.js
$(function() {
Friend = Backbone.Model.extend();
//Create my model
var friends = new Friend([ {name: 'Eddard Stark'}, {name: 'Robert Baratheon'} ]);
//Create new models to be used as examples
FriendList = Backbone.Collection.extend({
model: Friend
});
//Create my collection
var friendslist = new FriendList;
//Created to hold my friends model
FriendView = Backbone.View.extend({
tagName: 'li',
events: {
'click #add-input': 'getFriend',
},
initialize: function() {
_.bindAll(this, 'render');
},
getFriend: function() {
var friend_name = $('#input').val();
var friend_model = new Friend({name: friend_name});
},
render: function() {
console.log('rendered')
},
});
var view = new FriendView({el: 'body'});
});
You had some fundamental problems with your code to get the functionality that you required. I turned your code into a jsfiddle and you can see the working solution here.
http://jsfiddle.net/thomas/Yqk5A/
Code
<!DOCTYPE HTML>
<html>
<head>
<title>Tut</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.cdnjs.com/ajax/libs/underscore.js/1.1.4/underscore-min.js"></script>
<script type="text/javascript" src="http://ajax.cdnjs.com/ajax/libs/backbone.js/0.3.3/backbone-min.js"></script>
</head>
<body>
<input type="text" placeholder="Enter friend's name" id="input" />
<button id="add-input">Add Friend</button>
<ul id="friends-list">
</ul>
<script type="text/javascript" src="test.js"></script>
</body>
</html>
$(function() {
FriendList = Backbone.Collection.extend({
initialize: function(){
}
});
FriendView = Backbone.View.extend({
tagName: 'li',
events: {
'click #add-input': 'getFriend',
},
initialize: function() {
var thisView = this;
this.friendslist = new FriendList;
_.bindAll(this, 'render');
this.friendslist.bind("add", function( model ){
alert("hey");
thisView.render( model );
})
},
getFriend: function() {
var friend_name = $('#input').val();
this.friendslist.add( {name: friend_name} );
},
render: function( model ) {
$("#friends-list").append("<li>"+ model.get("name")+"</li>");
console.log('rendered')
},
});
var view = new FriendView({el: 'body'});
});
I noticed that you wanted as little code as possible so I left some things out that you don't need such as declaring an actual model. It might be easier if you use a collection like in the example instead.
Also I have just launched a new site containing Backbone tutorials which might help solve your problem.
BackboneTutorials.com

Event binding with views and AJAX-loaded collections

I'm starting out with Backbone.JS, and have a question about how this should work.
I have a page which should load a list of sports, and then render the view. Now, because the list of sports is loaded via AJAX, I need to bind events to the collection so that it's only rendered when the data from that collection is actually loaded.
Here's what I've got so far:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Test</title>
<script src="../../src/vendor/zepto.js" type="text/javascript" charset="utf-8"></script>
<script src="../../src/vendor/underscore.js" type="text/javascript" charset="utf-8"></script>
<script src="../../src/vendor/backbone.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="sportsList"></div>
<script type="text/template" id="sports-template">
<ul>
<% _.each(sports, function(sport){ %>
<li>
<%= sport.getDescription() %>
</li>
<% }); %>
</ul>
</script>
<script type="text/javascript" charset="utf-8" src="application.js"></script>
</body>
</html>
As for the javascript code:
var Sport = Backbone.Model.extend({
getDescription: function() {
return this.get('description');
}
});
var Sports = Backbone.Collection.extend({
model: Sport,
initialize: function(options) {
this.bind("refresh", function() {
options.view.render();
});
var sports = this;
$.ajax({
url: 'http://localhost:8080/?service=ListSportsService&callback=?',
dataType: 'jsonp',
success: function(data) {
sports.refresh(data);
}
});
}
});
var SportsView = Backbone.View.extend({
el: '#sportsList',
template: _.template($('#sports-template').html()),
initialize: function() {
this.model = new Sports({view: this});
},
render: function() {
$(this.el).html(this.template({sports: this.model.models}));
return this;
}
});
var SportsController = Backbone.Controller.extend({
routes: {
'sports': 'listSports'
},
listSports: function() {
new SportsView();
}
});
$(document).ready(function(){
window.app = new SportsController();
Backbone.history.start();
});
The part that really nags me is having to pass the view to the collection and binding the refresh event there so that I can render the view once everything is loaded.
My question is, is there a DRYer/simpler way of doing this that I'm missing?
Keep in mind I'm using ZeptoJS instead of jQuery.
Your collection should read
var Sports = Backbone.Collection.extend({
model: Sport,
url: '/?service=ListSportsService&callback=?'
});
Always use Backbone.sync functionality when possible (here by specifying the url on the collection and using fetch).
Your controller should read
var SportsController = Backbone.Controller.extend({
routes: {
'sports': 'listSports'
},
listSports: function() {
//create the view with the collection as a model
this.sports = new Sports();
this.view = new SportsView({model: this.sports});
this.sports.bind("refresh", this.view.render);
// fetch all the sports
this.sports.fetch();
}
});
Enjoy.

Categories

Resources