I'm new to KnockoutJS, I'm using it for a school project which is based on a movie API from where I get data into UI.
This is my app.js where all the javascript code is:
var ViewModel = function() {
var self = this;
self.movies = ko.observableArray;
self.error = ko.observable;
var moviesUri = '/api/movies/';
function ajaxHelper(uri, method, data) {
self.error('');
return $.ajax({
type: method,
url: uri,
dataType: 'json',
contentType: 'application/json',
data: data ? JSON.stringify(data) : null
}).fail(function(jqXHR, textStatus, errorThrown) {
self.error(errorThrown);
});
}
function getAllMovies() {
ajaxHelper(moviesUri, 'GET').done(function(data) {
self.movies(data);
});
}
getAllMovies();
};
ko.applyBindings(new ViewModel());
And this is my index.html where the data is displayed:
#section scripts {
#Scripts.Render("~/bundles/app")
}
<div class="page-header">
<h1>Movie Database API</h1>
</div>
<div class="row">
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Movies</h2>
</div>
<div class="panel-body">
<ul class="list-unstyled" data-bind="foreach: movies">
<li>
<!--<strong>
<span data-bind="text: DirectorName"></span>
</strong>:--> <span data-bind="text: Title"></span>
<small>
Details
</small>
</li>
</ul>
</div>
</div>
<div class="alert alert-danger" data-bind="visible: error">
<p data-bind="text: error"></p>
</div>
</div>
<div class="col-md-4">
<!-- TODO: Movie details -->
</div>
<div class="col-md-4">
<!-- TODO: Add new movie -->
</div>
</div>
I checked the code and it seems fine, but when I run my application I get into console this:
Uncaught ReferenceError: Unable to process binding "foreach: function
(){return movies }" Message: Unable to process binding "text: function
(){return Title }" Message: Title is not defined
Can someone point me to the right direction and tell me what am I doing wrong?
Thanks.
You are missing parenthesis in your observables declarations:
var ViewModel = function() {
var self = this;
self.movies = ko.observableArray();
//^^ here
self.error = ko.observable();
//^^ here
//...
}
Besides, beware that the properties of your observableArray will not be made observables by default (you might want to look into the mapping plugin).
I would like to use two different divs one contains a form and other contains repeat for the $scope values. Those two divs needs to use the same controller. However I am not able to share data in between divs in a desired way. Although I use factory, it only helps for me to add data to scope. I also want to edit the scope values inside the form which has another instance of the same controller.
You can find what I did in this link.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<link rel="stylesheet" href="http://www.w3schools.com/lib/w3.css">
<body>
<script>
var app = angular.module("myShoppingList", []);
app.factory('fact',function(){
products = ["milk","chese"];
tempItem = '';
tempIndex = undefined;
return {
getProducts : function() {
return products;
},
getProductByIndex : function(x){
return products[x];
},
saveProduct : function(x,item)
{
if(x==undefined)
{
products.push(item);
}
else
{
products[x] = item;
}
tempItem = '';
tempIndex = undefined;
},
editProduct : function(x)
{
tempItem = products[x];
tempIndex = x;
},
removeProduct : function(x)
{
products.splice(x, 1);
},
getTempItem : function()
{
return tempItem;
},
getTempIndex : function()
{
return tempIndex;
},
}
});
app.controller("myCtrl", function($scope, fact) {
$scope.products = fact.getProducts();
$scope.tempIndex = fact.getTempIndex();
$scope.tempItem = fact.getTempItem();
$scope.saveItem = function () {
fact.saveProduct($scope.tempIndex,$scope.tempItem);
}
$scope.editItem = function (x) {
fact.editProduct(x);
}
$scope.removeItem = function (x) {
fact.removeProduct(x);
}
});
</script>
<div ng-app="myShoppingList" ng-cloak class="w3-card-2 w3-margin" style="max-width:400px;">
<header class="w3-container w3-light-grey w3-padding-16">
<h3>My Shopping List</h3>
</header>
<div ng-controller="myCtrl">
<ul class="w3-ul">
<li ng-repeat="x in products" class="w3-padding-16">{{$index}} {{x}}
<span ng-click="editItem($index)" style="cursor:pointer;" class="w3-right w3-margin-right">||</span>
<span ng-click="removeItem($index)" style="cursor:pointer;" class="w3-right w3-margin-right">×</span>
</ul>
</div>
<div ng-controller="myCtrl" class="w3-container w3-light-grey w3-padding-16">
<form ng-submit="saveItem()">
<div class="w3-row w3-margin-top">
<div class="w3-col s10">
<input placeholder="Add shopping items here" ng-model="tempItem" class="w3-input w3-border w3-padding">
<input type="hidden" ng-model="tempIndex">
</div>
<div class="w3-col s2">
<button type="submit" class="w3-btn w3-padding w3-green">Save</button>
</div>
</div>
</form>
</div>
</div>
</body>
</html>
ISSUES
You have initialized controller twice.
You can do edit item inside controller itself.
After saving $scope values should be cleared along with clearing values in factory.
CONTROLLER and HTML
var app = angular.module("myShoppingList", []);
app.factory('fact',function(){
products = ["milk","chese"];
tempItem = '';
tempIndex = undefined;
return {
getProducts : function() {
return products;
},
getProductByIndex : function(x){
return products[x];
},
saveProduct : function(x,item)
{
if(!x)
{
products.push(item);
}
else
{
products[x] = item;
}
tempItem = '';
tempIndex = '';
},
editProduct : function(x)
{
tempItem = products[x];
tempIndex = x;
},
removeProduct : function(x)
{
products.splice(x, 1);
},
getTempItem : function()
{
return tempItem;
},
getTempIndex : function()
{
return tempIndex;
},
}
});
app.controller("myCtrl", function($scope, fact) {
$scope.products = fact.getProducts();
$scope.tempIndex = fact.getTempIndex();
$scope.tempItem = fact.getTempItem();
$scope.saveItem = function () {
fact.saveProduct($scope.tempIndex,$scope.tempItem);
$scope.tempItem = '';
$scope.tempIndex = '';
}
$scope.editItem = function (x) {
$scope.tempItem = fact.getProducts()[x];
$scope.tempIndex = x;
}
$scope.removeItem = function (x) {
fact.getProducts().splice(x, 1);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="http://www.w3schools.com/lib/w3.css">
<div ng-app="myShoppingList" ng-controller="myCtrl" ng-cloak class="w3-card-2 w3-margin" style="max-width:400px;">
<header class="w3-container w3-light-grey w3-padding-16">
<h3>My Shopping List</h3>
</header>
<div>
<ul class="w3-ul">
<li ng-repeat="x in products" class="w3-padding-16">{{$index}} {{x}}
<span ng-click="editItem($index)" style="cursor:pointer;" class="w3-right w3-margin-right">||</span>
<span ng-click="removeItem($index)" style="cursor:pointer;" class="w3-right w3-margin-right">×</span>
</ul>
</div>
<div class="w3-container w3-light-grey w3-padding-16">
<form ng-submit="saveItem()">
<div class="w3-row w3-margin-top">
<div class="w3-col s10">
<input placeholder="Add shopping items here" ng-model="tempItem" class="w3-input w3-border w3-padding">
<input type="hidden" ng-model="tempIndex">
</div>
<div class="w3-col s2">
<button type="submit" class="w3-btn w3-padding w3-green">Save</button>
</div>
</div>
</form>
</div>
</div>
My script is the following:
#using (Html.BeginFooterScripts())
{
<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/knockout/knockout-3.3.0.js"></script>
<script type="text/javascript" src="/Content/Northwestern/js/_libs/knockout.mapping/knockout.mapping.2.4.1.js"></script>
<script type="text/javascript" src="~/Content/Northwestern/js/views/TabPanel/location-card.js"></script>
<script type="text/javascript">
$(function() {
initialize();
});
</script>
<script>
$(function() {
// we must be on a detail page, we don't have a current location, so get it!
if (viewModel.currentLocation.latitude == 0 || viewModel.currentLocation.longitude == 0) {
geoLocate(function(location) {
viewModel.currentLocation.latitude = location.coords.latitude;
viewModel.currentLocation.longitude = location.coords.longitude;
displayLocation('#Model.LocationId');
}, geoLocateError);
} else {
displayLocation('#Model.LocationId');
}
});
</script>
}
my external script is :
/**********************************************
* Global variables
**********************************************/
var applied = false;
var geoLocateError = function onError(error) {
alert(error.message);
};
function ViewModel() {
var self = this;
self.currentLocation = {
latitude: 0,
longitude: 0
};
self.LocationId = ko.observable();
}
var viewModel = new ViewModel();
$(function () {
});
function initialize() {
ko.applyBindings(viewModel);
geoLocate(function(location) {
initLocation(location);
}, geoLocateError);
}
/**********************************************
* Location Functions
**********************************************/
function initLocation(location) {
viewModel.currentLocation = {
latitude: location.coords.latitude,
longitude: location.coords.longitude
};
}
function displayLocation(id) {
var apiUrl = '/api/northwestern/locations/getlocationbyid/' + id;
var data = {
'latitude': viewModel.currentLocation.latitude,
'longitude': viewModel.currentLocation.longitude
};
self.LocationId = id;
$.getJSON(apiUrl, data, function (response) {
var fragment = document.createDocumentFragment(),
container = document.createElement('div'),
viewModel = response;
fragment.appendChild(container);
// merge together all the display types into a commma-separated list
response.TypeListDisplay = $.map(response.Types, function (obj, t) {
return obj.ItemName;
}).join(', ');
ko.renderTemplate(
"location-detail-template",
viewModel, {
afterRender: function () {
$('#detail-container').html(container.innerHTML);
}
},
container
);
});
}
and here is the markup :
<div class="row">
<div class="col md-4">
<div class="section-content">
<div id="detail-container">
</div>
</div>
<script type="text/html" id="location-detail-template">
<div class="card card-locations-alt">
<div class="card-content">
<figure class="map">
<a target="_blank" href="https://www.google.com/maps/dir/Current+Location/#Model.Location.Latitude, #Model.Location.Longitude">
<img src="https://maps.googleapis.com/maps/api/staticmap?center=#Model.Location.Latitude,#Model.Location.Longitude&zoom=13&size=65x65&maptype=roadmap&markers=color:0x776EA7%7Clabel:%7C #Model.Location.Latitude,#Model.Location.Longitude">
</a>
</figure>
<div class="location-content" itemscope="" itemtype="http://schema.org/LocalBusiness">
<div class="location-name">
<h2 class="location-title" itemprop="name" data-bind="text: ItemName"></h2>
</div>
<div class="distance">
<i class="material-icons">place</i> <span data-bind="text: Distance.toFixed(1)"> Mi</span>
</div>
<div class="location-phone">
<a data-bind="attr: { 'href': clickToCallify(Phone), 'data-track-event': 'Find a Location - Detail', 'data-track-action': 'call icon' }" class="tel" itemprop="telephone"></a>
</div>
</div>
<div class="location-actions flex-container align-center no-print">
<a class="locations-icon flex-item tel" href="tel:8475358000">
<div class="call-icon uppercase">
<i class="material-icons">phone</i><br>
call
</div>
</a>
<a data-bind="attr: {'href' : 'https://www.google.com/maps/dir/Current+Location/' + Latitude + ',' + Longitude, 'data-track' : 'Find a Location', 'data-track-action' : 'directions', 'data-track-label' : ItemName }" target="_blank" class="locations-icon flex-item uppercase">
<i class="material-icons">directions</i><br>
directions
</a>
<a href="" class="location-detail locations-icon flex-item uppercase">
<i class="material-icons">info</i><br>
details
</a>
</div>
</div>
</div>
</script>
</div>
<div class="col md-7">
#(new HtmlString(Model.Body))
</div>
</div>
<br />
}
now, when I applyBindingsviewModel) under the initialize function, it works the first time, then it throws an error "cannot apply bindings muliple times for the same element.
I have tried to do a ko.cleanNode, but that did not work.
when i take the applyBindings off, I don't get the error, but the program skips over all but the last page component.
The problem is cause binding applies two times to the same elements. So the can be some other knockout binding in your code or initialize function executes second time.
If you have few models which you would like to bind to your page you can use second parameter of applyBindings, if you do not pass it model binds to body.
Optionally, you can pass a second parameter to define which part of the document you want to search for data-bind attributes.
http://knockoutjs.com/documentation/observables.html
I'm trying to keep track of the selected tab in the view model but I can't seem to make it work.
In the following code when you click a tab the header will update correctly but the content of the tab is not displayed. If you remove , click: $parent.selectSection then the contents are shown but the header does not update.
Now if you remove the data-bind="css: { active: selected }" from the li then it seems to work when you click the tabs but the button to select the second tab doesn't.
How can I make this work?
See: http://jsfiddle.net/5PgE2/3/
HTML:
<h3>
<span>Selected: </span>
<span data-bind="text: selectedSection().name" />
</h3>
<div class="tabbable">
<ul class="nav nav-tabs" data-bind="foreach: sections">
<li data-bind="css: { active: selected }">
<a data-bind="attr: { href: '#tab' + name }
, click: $parent.selectSection" data-toggle="tab">
<span data-bind="text: name" />
</a>
</li>
</ul>
<div class="tab-content" data-bind="foreach: sections">
<div class="tab-pane" data-bind="attr: { id: 'tab' + name }">
<span data-bind="text: 'In section: ' + name" />
</div>
</div>
</div>
<button data-bind="click: selectTwo">Select tab Two</button>
JS:
var Section = function (name) {
this.name = name;
this.selected = ko.observable(false);
}
var ViewModel = function () {
var self = this;
self.sections = ko.observableArray([new Section('One'),
new Section('Two'),
new Section('Three')]);
self.selectedSection = ko.observable(new Section(''));
self.selectSection = function (s) {
self.selectedSection().selected(false);
self.selectedSection(s);
self.selectedSection().selected(true);
}
self.selectTwo = function() { self.selectSection(self.sections()[1]); }
}
ko.applyBindings(new ViewModel());
There are several ways that you can handle this either using bootstrap's JS or by just having Knockout add/remove the active class.
To do this just with Knockout, here is one solution where the Section itself has a computed to determine if it is currently selected.
var Section = function (name, selected) {
this.name = name;
this.isSelected = ko.computed(function() {
return this === selected();
}, this);
}
var ViewModel = function () {
var self = this;
self.selectedSection = ko.observable();
self.sections = ko.observableArray([
new Section('One', self.selectedSection),
new Section('Two', self.selectedSection),
new Section('Three', self.selectedSection)
]);
//inialize to the first section
self.selectedSection(self.sections()[0]);
}
ko.applyBindings(new ViewModel());
Markup would look like:
<div class="tabbable">
<ul class="nav nav-tabs" data-bind="foreach: sections">
<li data-bind="css: { active: isSelected }">
<a href="#" data-bind="click: $parent.selectedSection">
<span data-bind="text: name" />
</a>
</li>
</ul>
<div class="tab-content" data-bind="foreach: sections">
<div class="tab-pane" data-bind="css: { active: isSelected }">
<span data-bind="text: 'In section: ' + name" />
</div>
</div>
</div>
Sample here: http://jsfiddle.net/rniemeyer/cGMTV/
There are a number of variations that you could use, but I think that this is a simple approach.
Here is a tweak where the active tab used the section name as a template: http://jsfiddle.net/rniemeyer/wbtvM/
I'm learning JQM and Backbone.js and I have a few problems. Im making a recipe app following the TODO list example trying to blend them both.
Anyway I can't refresh any page besides the first one, I'm getting undefined variables. I believe it has to do with the DOM and many views I have. Secondly Upon entering the recipe to search for to query the API, it displays the results first as plain links!
If I got forward a page or back a page and return to the results page it display the links as they should be the JQM style. This is because I couldn't figure how or what to append to in the JS, so I did it in the HTML.
I know this is a long shot but can anyone give me guidance as to what the hell I'm doing wrong, general advice, anything?
var Todo = Backbone.Model.extend({
defaults: function() {
return {
id: 0,
title: 'defaultname',
imgUrl: 'defaultimageurl',
order: searchTemp.nextOrder(),
rating: 0,
timeToMake: '',
salty: 0,
sour: 0,
sweet: 0,
bitter: 0,
isPerm: false,
taggedForList: false
};
},
initialize: function(){
if( !this.get('ingrs') ){
this.set({ingrs: new Array()});
}
},
saveModel: function() {
this.set({isPerm: true});
this.save();
}
});
var TodoList = Backbone.Collection.extend({
model: Todo,
localStorage: new Backbone.LocalStorage("searchTemp"),
initialize: function() {
},
nextOrder: function() {
if (!this.length) return 1;
return this.last().get('order') + 1;
},
comparator: 'order',
taggedForList: function() {
return this.where({taggedForList: true});
},
remaining: function() {
return this.without.apply(this, this.taggedForList);
},
findRecipes: function(theQuery) {
console.log("findRecipes called");
searchTemp.each(function (model) {
if (!model.isPerm) {
model.destroy();
}
});
$.ajax({
url: 'http://api.yummly.com/v1/api/recipes?_app_id=d8087d51&_app_key=005af5a16f1a8abf63660c2c784ab65f&maxResult=5&q='+theQuery,
dataType: 'jsonp',
success: function(apiStuff){
var result = new Array();
result = apiStuff; //saves the API's response as a new array
result = result.matches; //trims extra information from the json object, now only has information on the various recipes
$.each(result, function(i, item) {
var anotherRecipe= new Todo(); // makes a new model for each result
anotherRecipe.set({
id: result[i].id, //then sets the attributes
title: result[i].recipeName,
ingrs: result[i].ingredients,
imgUrl: result[i].smallImageUrls,
rating: result[i].rating,
timeToMake: result[i].totalTimeInSeconds,
});
//not all recipes support flavor ratings, so error catching must be used to avoid setting null values
try { anotherRecipe.set({ salty : result[i].flavors.salty }); } catch(e) {anotherRecipe.set({salty : "?"});} //maybe replace the error condition to setting the flavor to '?'
try { anotherRecipe.set({ sour: result[i].flavors.sour }); } catch(e) {anotherRecipe.set({sour : "?"});}
try { anotherRecipe.set({ sweet: result[i].flavors.sweet }); } catch(e) {anotherRecipe.set({sweet : "?"});}
try { anotherRecipe.set({ bitter: result[i].flavors.bitter }); } catch(e) {anotherRecipe.set({bitter : "?"});}
searchTemp.add(anotherRecipe); //adds the model to the temporary
});
} //eventually, should add something that checks for an empty search result, appending some warning if that happens
});
// console.log("search done");
}
});
var ShopItem = Backbone.Model.extend({
defaults: function() {
return {
ingr : 'ingredient',
done : false
}
},
toggle: function() {
this.save({taggedForList: !this.get("taggedForList")});
}
});
var ShopList = Backbone.Collection.extend({
localStorage: new Backbone.LocalStorage("grocery-list"),
generate: function() {
console.log("SHOP LIST! ASSSSSEMMMMBLLLLLLE!");
searchTemp.fetch();
var ingrList = searchTemp.pluck('ingrs'); //this returns an array of arrays
console.log(ingrList);
ingrList = _.union(ingrList); //this needs to get a series of arrays ( _.union(array1, array 2); )
console.log(ingrList);
},
getList: function() {
var list = new Array();
list = this.toJSON();
return list;
}
});
var Todos = new TodoList; //I am afraid to move this, 95% sure its obsolete, though
/*
var savedRecipesView = Backbone.View.extend({
tagName: "li",
initialize: function() {
this.render();
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
var template = _.template( $("#list_item").html(), {} );
this.$el.html( template );
//this.$el.html(this.template(this.model.toJSON()));
//this.$el.toggleClass('done', this.model.get('done'));
//this.input = this.$('.edit');
//return this;
},
events: {
"click input[type=button]": "sendToGroceries"
},
sendToGroceries: function() {
var temp = new Array();
temp = this.toJSON();
$.each(temp, function(i, item) {
var shopItem = new ShopItem();
shopItem.set({ name: temp[i].title });
shoppingList.add(shopItem); //use pluck [ingrs]
shopItem.save();
});
}
});
*/
window.HomeView = Backbone.View.extend({
template:_.template($('#home').html()),
render:function (eventName) {
$(this.el).html(this.template());
return this;
}
});
window.newSearchView = Backbone.View.extend({
template:_.template($('#newSearch').html()),
//this VAGUELY works, but causes visual chaos the first run through,
//still relies on the appending for that
initialize: function() {
console.log(searchTemp);
//searchTemp.bind('searchDone', this.render, this);
searchTemp.bind('add', this.render, this);
},
render:function (eventName) {
var temp = new Array(); // I think this line isnt doing anyting
results = searchTemp.toJSON();
// console.log(results);
var variables = {
recipes: results
};
$(this.el).html(this.template());
return this;
},
events: {
"keypress #recipe-search": "searchOnEnter",
//add a listener to newSearch to change what's displaye don this list
},
searchOnEnter: function(e) { //the search bar's functionality
if (e.keyCode != 13) return;
var searchin = $("input[id='recipe-search']").val();
console.log("searched for - "+ searchin);
//this function is in todoList, does an API call and
//adds a new model for each result (there will almost always be 5 results)
searchTemp.findRecipes(searchin);
}
});
window.newListView = Backbone.View.extend({
template : _.template($('#newList').html()),
initialize: function() {
},
render:function (eventName) {
recipe = this.model.toJSON(); ///INCOMPLETE, modify newlist to accept straight from JSON
var variables = {
recipe_name : this.model.get("title"),
img_url : this.model.get("imgUrl"),
timetomake: this.model.get("timeToMake"),
ingrs : this.model.get("ingrs"),
rating : this.model.get("rating"),
salty : this.model.get("salty"),
sour : this.model.get("sour"),
sweet : this.model.get("sweet"),
bitter : this.model.get("bitter")
};
$(this.el).html(this.template(variables));
return this;
},
events: {
"click #save-this": "saveModel"
},
saveModel: function() {
console.log("saveModel() called");
//console.log(permStorage.taggedForList());
//shift the model over to permStorage
//searchTemp.remove(this.model);
//console.log(this.model);
this.model.saveModel();
//console.log(this.model)
//now save permStorage to local storage
searchTemp.each(function (model) {
if(model.isPerm) {
model.save();
}
});
}
});
window.savedRecipesView = Backbone.View.extend({
template:_.template($('#savedRecipes').html()),
initialize: function() {
console.log("about to fetch from local storage...");
searchTemp.fetch();
console.log("...fetched!");
},
render:function (data) {
results = searchTemp.toJSON();
//results = results.models;
//console.log(results);
var variables = {
results: results
};
_.each(data, function(task) {
console.log("meow");
this.addOne(task);
}, this);
$(this.el).html(this.template(variables));
return this;
},
addOne: function(task) {
var view = new listItemView({ model:task });
$(this.el).append( view.render().el );
}
});
window.listItemView = Backbone.View.extend({
tagName: 'li',
template:_.template($('#list-item').html()),
initialize: function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.model.view = this;
},
events: {
"click input[type=button]" : "onClick"
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setContent();
return this;
},
onClick: function(){
searchTemp.add(this.model);
console.log("model added to searchTemp, current state of searchTemp:");
console.log(searchTemp);
}
});
window.oldListView = Backbone.View.extend({
template:_.template($('#oldList').html()),
render: function (eventName) {
$(this.el).html(this.template());
return this;
}
});
window.deleteOldView = Backbone.View.extend({
template:_.template($('#deleteOld').html()),
render: function (eventName) {
$(this.el).html(this.template());
return this;
}
});
window.shoppingListView = Backbone.View.extend({
template:_.template($('#shoppingList').html()),
initialize: function() {
shopList.generate();
},
render: function (eventName) {
var variables = {
};
$(this.el).html(this.template(variables));
return this;
}
});
var AppRouter = Backbone.Router.extend({
routes:{
"":"home",
"newSearch":"newSearch",
"newList/:id":"newList",
"savedRecipes":"savedRecipes",
"oldList":"oldList",
"deleteOld":"deleteOld",
"shoppingList":"shoppingList"
},
initialize:function () {
// Handle back button throughout the application
$('.back').live('click', function(event) {
window.history.back();
return false;
});
this.firstPage = true;
},
home:function () {
this.changePage(new HomeView());
},
newSearch:function () {
this.changePage(new newSearchView());
},
newList:function (theID) {
var tempModel = searchTemp.get(theID);
this.changePage(new newListView({
model: tempModel,
id: theID
}));
//console.log(permStorage.taggedForList());
},
savedRecipes:function () {
this.changePage(new savedRecipesView());
},
oldList:function () {
this.changePage(new oldListView());
},
deleteOld:function () {
this.changePage(new deleteOldView());
},
shoppingList:function () {
this.changePage(new shoppingListView());
},
changePage:function (page) {
$(page.el).attr('data-role', 'page');
page.render();
$('body').append($(page.el));
var transition = $.mobile.defaultPageTransition;
// We don't want to slide the first page
if (this.firstPage) {
transition = 'none';
this.firstPage = false;
}
$.mobile.changePage($(page.el), {changeHash:false, transition: transition});
}
});
$(document).ready(function () {
console.log('document ready');
app = new AppRouter();
Backbone.history.start();
searchTemp = new TodoList(); //this stores searched recipes, rename to myRecipes
shopList = new ShopList();
});
<!DOCTYPE html>
<html class="ui-mobile-rendering">
<head>
<title>RECILIST</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/jquery.mobile-1.0.1.min.css"/>
<!-- The Templates -->
<script type="text/template" id="home">
<div data-role="header" >
<h1 style="color:black">Recilist Home</h1>
</div>
<img src="store.jpg" id="vege">
<div data-role="content" style="color:red">
<!-- <h3>recilist home page</h3>
<p>Welcome to Recilist!</p>
<p>This is the Home page. </p>
-->
<h1>Save Recipes & <br/>
Create Shopping <br/>
Lists Anywhere</h1>
<p class="blurb">Create and manage your grocery shopping list, FIND and <br/>SAVE your favorite recipes from across the web,<br/> get great SAVINGS and share with your entire family - for FREEEE!!!!</p>
<ul data-role="listview" id="choices" data-inset="true">
<li><a style="color:red" href="#newSearch">Search for new Recipes</a></li>
<li><a style="color:red" href="#savedRecipes">View saved Recipes</a></li>
</ul>
</div>
<div data-role="footer" class="ui-bar" id ="footer">
<h5 style="color:black"> powered by <img src="http://static.yummly.com/api-logo.png"> </h5>
</div>
</script>
<script type="text/template" id="newSearch">
<div data-role="header">
<h1 style="color:black">Search for a new Recipe</h1>
</div>
<div data-role="content">
<input name= "recipe-search" id="recipe-search" data-icon="search" type="text" placeholder="What do you want to cook?">
<ul id="search-list" data-role="listview" data-inset="true">
<% for(var i in results) { %>
<li> <img src="<%= results[i].imgUrl %>"> <%= results[i].title %> </li>
<% } %>
</ul>
</div>
<img src="list.png" id="list">
<div data-role="footer" class="ui-bar">
Back
Home
</div>
</script>
<script type="text/template" id="newList">
<div data-role="header">
<h1 style="color:black"> <%=recipe_name%> </h1>
</div>
<div>
<div data-role="content">
<img src= <%=img_url%> >
<h4>Recipe Rating: <%= rating %> </h4>
<h4>Total time to Prepare: <%= timetomake %> </h4>
<h4>Flavor Ratings</h4>
<div class="ui-block-a"> <div class="ui-bar ui-bar-e"> <h4>saltiness</h4> <%= salty %> </div> </div>
<div class="ui-block-b"> <div class="ui-bar ui-bar-e"> <h4>sourness</h4> <%= sour %> </div> </div>
<div class="ui-block-c"> <div class="ui-bar ui-bar-e"> <h4>sweetness</h4> <%= sweet %> </div> </div>
<div class="ui-block-d"> <div class="ui-bar ui-bar-e"> <h4>bitterness</h4> <%= bitter %> </div> </div>
</div>
<div data-role="collapsible" data-collapsed="true">
<h3>Ingredients</h3>
<ul id="ingr-list" data-role="listview" data-inset="true">
<% for(var i in ingrs) { %>
<li><%= ingrs[i] %></li>
<% } %>
</ul>
</div>
<div data-role="content">
<input type="button" id="save-this" data-icon="check" value="save to My Recipies">
<div>
<img src="store3.jpg" id="aisle">
</div>
<div data-role="footer" class="ui-bar">
Back
Home
</div>
</script>
<script type="text/template" id="savedRecipes">
<div data-role="header">
<h1>My Recipes</h1>
</div>
<div data-role="content">
<p>list of saved recipes, retrieved from local storage</p>
<p>Saved Recipes:</p>
<ul id="search-list" data-role="listview" data-inset="true">
<% for(var i in results) { %>
<li> <img src="<%= results[i].imgUrl %>"> <%= results[i].title %> </li>
<% } %>
</ul>
manage saved recipes
generate shopping list
</div>
<div data-role="footer" class="ui-bar">
Back
Home
</div>
</script>
<script type="text/template" id="deleteOld">
<div data-role="header">
<h1>Delete Recipes</h1>
</div>
<div data-role="content">
<p>select which recipes you wish to delete from local storage</p>
<p>recipes:</p>
<p>(currently lacks functionality to populate this list)</p>
<ul data-role="listview" data-inset="true" id="recipe-list">
</ul>
<input type="button" data-icon="delete" value="delete selected" />
</div>
<div data-role="footer" class="ui-bar">
Back
Home
</div>
</script>
<script type="text/template" id="oldList">
<div data-role="header">
<h1>---NAME OF THE RECIPE----</h1>
</div>
<div data-role="content">
<p>This is a list of all the ingredients in this recipe</p>
<p>Ingredients:</p>
<ul data-role="listview" data-inset="true">
<li>ingredient 1</li>
<li>ingredient 2</li>
<li>ingredient 3</li>
<li>ingredient 4</li>
</ul>
<p> (button to view the recipe) </p>
</div>
<div data-role="footer" class="ui-bar">
Back
Home
</div>
</script>
<script type="text/template" id="shoppingList">
<div data-role="header">
<h1>shopping list</h1>
</div>
<div data-role="content">
<ul id="search-list" data-role="listview" data-inset="true">
<% for(var i in results) { %>
<li> <input type="checkbox" name=i id=i class="custom" /></li> <!-- these checkboxes are HIDEOUSLY DEFORMED-->
<label for=i> <%= results[i].title %> </label>
<% } %>
</ul>
</div>
<div data-role="footer" class="ui-bar">
Back
Home
</div>
</script>
<script type="text/template" id="list-item">
<li>cheese</li>
</script>
<!-- <li> <img src= <%=img_url%> > <a href="#newList/"+ <%=model_id%> +"' class='ui-link-inherit'>" + <%=model_title%> + "</a> </li> -->
<!-- The Scripts -->
<script src="lib/jquery-1.7.1.min.js"></script>
<script src="js/jqm-config.js"></script>
<script src="lib/jquery.mobile-1.0.1.min.js"></script>
<script src="js/underscore.js"></script>
<script src="lib/backbone-min.js"></script>
<script src="js/backbone.localStorage.js"</script>
<script src="js/json2.js"></script>
<script src="js/main.js"></script>
</head>
<body></body>
</html>
To enhance dynamically created list view, you need to refresh the markup using the below.
$('[data-role=listview]').listview('refresh');