jQuery mobile: links have improper format on first load? - javascript

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');

Related

Vue js retrive new data after ajax call

I have one button when I click on this button I retrive data. But when I click second way I dont retrive new data
Here is my code
https://codepen.io/anon/pen/weobMP
Html
<button type="submit" class="btn btn-sm btn-success form-control" id="click">Filter</button>
<div class="col-md-6 cart-items" id="myjp">
<div v-for="(item, index) in items">
<div class="panel panel-info">
<div class="panel-heading" v-on:click="greet">
<h3 {{index+1}} {{item.Car}}</h3>
</div>
<div class="mojbody panel-body" v-for="tire in item.tires">
Tire: {{tire.Name}}
</div>
</div>
</div>
</div>
JS
$(function() {
var id = 1;
$("#click").click(function(){
id++;
new Vue({
el: '#myjp',
data: {
items: null
},
created: function () {
this.fetchData();
},
methods: {
fetchData: function () {
var self = this;
$.get('/retriveHistory',{id: id}, function( data ) {
self.items = data;
console.log(data);
});
},
}
});
})
});

AngularJs, How to edit $scope values in other div using the same controller with factory

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>

How should I be using ko.applyBindings ? I am getting an error when using this

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

Undefined property knockout.js + mvc

I have a masterDetails view that I populate with some data from a db (it populates fine). I added a button to the master details view, to add a step to my workflow.
My Viewmodel:
/// <reference path="_references.js" />
var viewModel = function (data) {
var self = this;
self.SelectedWorkflow = ko.observable({
Steps: ko.observableArray([]),
Name: ko.observable("")
});
self.Workflows = ko.observableArray(data);
self.addStep = function() {
self.Steps.push(new Step(SelectedWorkflow, "Assignment here", "01/01/2014", "dd:mm:ss", "mail"));
};
};
function Step(workflow,assignment, enddate, reminder, mailaddresses, type) {
var self = this;
self.Workflow = workflow;
self.StepNumber = 0;
self.Assignment = assignment;
self.Enddate = enddate;
self.Reminder = reminder;
self.MailAddresses = mailaddresses;
self.Type = type;
};
/// <reference path="workflowdetails-vm.js" />
$(document).ready(function () {
$.ajax({
url: "/WorkflowDetails/Index/",
type: "POST",
data: {},
success: function (data) {
var workflowlist = ko.mapping.fromJS(data.Workflows);
vm = new viewModel(workflowlist);
ko.applyBindings(vm);
}
});
$(".right-aligned-section").hide();
});
$(document).delegate(".show-details", "click", function () {
$(".right-aligned-section").fadeIn();
var workflow = ko.dataFor(this);
vm.SelectedWorkflow(workflow);
});
My View:
<div class="left-aligned-section">
<ul data-bind="foreach: Workflows()">
<li>
<div class="workflow-item-border">
<div>
<label data-bind="text: Name"></label>
</div>
<div>
<label data-bind="text: StartDate"></label>
</div>
<div>
Show Details
</div>
</div>
</li>
</ul>
</div>
<div class="right-aligned-section" data-bind="with: SelectedWorkflow">
<div class="steps-header">
<div class="left-aligned-div"><strong>Steps for </strong></div>
<div class="left-aligned-div" data-bind="text: Name"></div>
</div>
<button data-bind="click: addStep">add step</button>
<ul data-bind="foreach: Steps">
<li>
<div class="step-item-border">
<div>
<div class="step-label">Stepnumber: </div>
<div style="font-weight: bold" data-bind="text: StepNumber"></div>
</div>
<div>
<div class="step-label">Assignment: </div>
<div style="font-weight: bold" data-bind="text: Assignment"></div>
</div>
<div>
<div class="step-label">Mails: </div>
<div style="font-weight: bold" data-bind="text: MailAddresses"></div>
</div>
<div>
<div class="step-label">End Date: </div>
<div style="font-weight: bold" data-bind="text: Enddate"></div>
</div>
<div>
<div class="step-label">Type: </div>
<div style="font-weight: bold" data-bind="text: Type"></div>
</div>
</div>
</li>
</ul>
</div>
When I press the button - nothing happens. The error I receive is:
ReferenceError: Steps is not defined
I know what it means, but I'm not proficient enough in web development to actually fix it. Please help.
Probably this would work:
var viewModel = {};
viewModel.SelectedWorkflow = {
Steps: ko.observableArray([]),
Name: ko.observable("")
};
viewModel.Workflows = ko.observableArray(data);
viewModel.addStep = function () {
viewModel.SelectedWorkflow.Steps.push(
new Step(SelectedWorkflow, "Assignment here", "01/01/2014", "dd:mm:ss", "mail"));
}
}
You have forgotten about SelectedWorkflow.Steps I think...
Looks like you forgot a this before the Steps.push....
Edit: I have made some further changes to the click handler function
var viewModel = function (data) {
this.SelectedWorkflow = ko.observable({
Steps: ko.observableArray([]),
Name: ko.observable("")
});
this.Workflows = ko.observableArray(data);
this.addStep = function (selectedWorkflow) { // the current context is passed in from the click data-bind
selectedWorkflow.Steps.push(new Step(selectedWorkflow, "Assignment here", "01/01/2014", "dd:mm:ss", "mail"));
}
}
Edit: Fixed code

Backbone load a view within a view (as partial)

Hope someone can help me with,
I have a Backbone view, that is basically a skeleton of HTML, the HTML builds a tabbed interface, each tab obviously has a link and content area. What I am wanting to is for each content render an individual Backbone template specific to that tab.
So for example in the tab #briefs I would want to render app.projectBriefsView. I cannot for the life of figure how I would go about doing this, here is my "base/master" view that I want to load everything else into.
app.ProjectsTabsView = Backbone.View.extend({
el: "header.project",
template: _.template($("#tpl-projects-tabs").html()),
events: {
'click .js-tab-link': 'showTab',
},
initialize: function() {
this.render();
var briefView = new app.projectBriefView;
briefView.render().el;
},
render: function() {
this.$el.append(this.template());
return this;
},
showTab: function(el) {
var tabRequired = $(el.currentTarget).attr("href");
console.log(tabRequired);
$(".tab-content.active").css("display", "none").removeClass("active");
$(".tab-content"+tabRequired).addClass("active").css("display", "block");
el.preventDefault();
}
});
Template
<script type="text/template" id="tpl-projects-tabs">
<div class="tabs">
<ul>
<li>Brief + Notes</li>
<li>Dates</li>
<li>Files</li>
<li>Tasks</li>
<li>Messages</li>
<li>Comments</li>
</ul>
<div class="tab-content js-tab-content active" id="brief">Briefs</div>
<div class="tab-content js-tab-content" id="dates">Dates</div>
<div class="tab-content js-tab-content" id="files">Files</div>
<div class="tab-content js-tab-content" id="tasks">Tasks</div>
<div class="tab-content js-tab-content" id="messages">Messages</div>
<div class="tab-content js-tab-content" id="comments">Comments</div>
</div>
</script>
and here is the view I am wanting to load into the .tab-content#brief area.
app.projectBriefView = Backbone.View.extend({
el: ".tab-content",
template: _.template($("#tpl-brief-notes").html()),
events: {
},
initialize: function() {
this.render();
},
render: function() {
this.$el.append(this.template({
p: app.project.toJSON()
}));
return this;
}
});
Template
<script type="text/template" id="tpl-brief-notes">
<div class="project_info_content">
<!-- New brief form -->
<div class="brief">
<h4>Brief</h4>
<% if (p.is_owner) { %>
<div class="js-brief-text">
<% if (p.brief == undefined || p.brief == '') { %>
<p class="add-text">No Brief, Click to add</p>
<% }
} else { %>
<div>
<% }
if (p.brief != undefined || p.brief != '') { %>
<% //p.brief %>
<?= nl2br(str_replace(' ', ' ', htmlspecialchars($project['brief']))); ?>
<% } %>
</div>
<div class="inline-edit">
<% if (p.is_owner) { %>
<form action="<?= base_url(); ?>projects/edit_brief/<%= p.id %>" method="post" accept-charset="utf-8" class="inline_edit edit_brief" novalidate="novalidate">
<p>
<textarea name="brief" class="brief_edit"><%= p.brief %></textarea>
<input type="submit" name="submit" value="Update"><a class="cancel" href="#">Cancel</a>
</p>
</form>
<% } %>
</div>
</div>
<!-- End of new brief form -->
<!-- New additional notes form -->
<div class="additional_notes">
<h4>Additional notes <span class="instructions">(Editable by other users)</span></h4>
<div class="js-notes-text">
<%
if (p.additional_info == undefined || p.additional_info == '') { %>
<p class="add-text">No Notes, Click to add</p>
<% } else { %>
<% // p.additional_info %>
<?= nl2br(str_replace(' ', ' ', htmlspecialchars($project['additional_info']))); ?>
<% } %>
</div>
<div class="inline-edit">
<form action="<?= base_url(); ?>projects/edit_additional_info/<%= p.id %>" method="post" accept-charset="utf-8" class="inline_edit edit_notes" novalidate="novalidate"> <p>
<textarea name="text_details" class="notes_edit"><%= p.additional_info %></textarea>
<input type="submit" name="submit" value="Update"> <a class="cancel" href="#">Cancel</a>
</p>
</form>
</div>
</div>
</div>
<!-- End of new additional notes form -->
</div>
</script>
I thought that in the app.ProjectTabsView in the render function I would be able to do something like,
var brief = new app.projectBriefView();
brief.render().el();
to throw the output to the browser but this does not seem to work. Is there a better way to work with what I would call partial views in Backbone? How can I get my partial to the browser?
Since Backbone views can populate the browser with html dynamically, you only need to have one div element for content. This div element will then serve as a placeholder for the nested view generated by the view managing the tabs.
I had to solve a very similar problem and here is what I did:
First, define a control structure to bind a tab title to a view constructor:
var Tab = function( title, viewConstructor ) {
this.title = title;
this.viewConstructor = viewConstructor;
};
_.extend( Module.prototype, {
render : function( options ) {
this.view = new this.viewConstructor( options );
return this.view.render();
},
close : function() {
if( this.view ) {
this.view.close();
}
}
});
Then, define all your tabs in an array:
var tabs = [
new Tab('brief', app.projectBriefView),
new Tab('dates', app.projectDatesView),
//...
]
You need to clean up a little bit your template:
<script type="text/template" id="tpl-projects-tabs">
<div class="tabs">
<ul>
<li>Brief + Notes</li>
<li>Dates</li>
<li>Files</li>
<li>Tasks</li>
<li>Messages</li>
<li>Comments</li>
</ul>
<div class="tab-content js-tab-content" id="tab-content">Briefs</div>
</div>
</script>
Finally, you just have to use them in your events:
//...
showTabs : function( event ) {
event.preventDefault();
var clicked = $(el.currentTarget),
title = clicked.attr("href").replace("#", ""),
tab = _.findWhere( tabs, { title : title });
// always cleanup the preceding tab.
if ( this.activeTab ) {
this.activeTab.close();
}
this.activeTab = tab;
this.$('ul > li > .active').removeClass('active');
this.$('#tab-content').html( tab.render().el );
clicked.addClass('active');
}

Categories

Resources