I have two views, one for rendering google maps and another to render list of events that contain's latitude and longitude to render markers on the view that contains google maps. When I try to pass the view that manages the map to the initialize method of the second view and save an instance reference, display in console the following error:
Here's my code
app.js
var ev = new Application();
ev.Router = Backbone.Router.extend({
routes: {
"": "home",
"evento/:id" : "evento"
},
home: function(){
var mapView = new ev.views.Home();
$('#home').html(mapView.el);
$('#rightcolumn').html(new ev.views.Home_Eventos(mapView).el);
},
evento: function(id){
$('#rightcolumn').html(new ev.views.Evento(id).el);
}
});
$(document).on('ready', function() {
// Load HTML templates for the app
ev.templateLoader.load(['shell', 'home', 'home_list_eventos', 'evento'], function () {
ev.shell = new ev.views.Shell({el: "#shell"});
ev.router = new ev.Router();
Backbone.history.start();
});
});
home.js
ev.views.Home = Backbone.View.extend({
map: 'null',
initialize: function(){
this.template = _.template(ev.templateLoader.get('home'));
this.render();
},
initMap: function(){
var that = this;
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var pos;
pos = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
var infowindow = new google.maps.InfoWindow({
map: that.map,
position: pos
});
that.map.setCenter(pos);
});
}
var mapOptions = {
zoom: 12,
};
that.map = new google.maps.Map(that.$el.find('#map_canvas')[0],mapOptions);
google.maps.event.addDomListener(window, "resize", function() {
var center = that.map.getCenter();
google.maps.event.trigger(that.map, "resize");
that.map.setCenter(center);
});
},
render: function(){
this.$el.html(this.template());
this.initMap();
return this;
}
});
home_events.js
ev.views.Home_Eventos = Backbone.View.extend({
initialize: function(mapView){
this.template = _.template(ev.templateLoader.get('home_list_eventos'));
this.mapView = mapView;
console.log(this.mapView.map);
this.render();
console.log("sou inicializado");
},
render: function(){
var that = this;
var imagens = new ev.models.ImagemCollection();
imagens.fetch({
success: function(){
that.$el.html(that.template({imagens: imagens.models}));
var marcadores = imagens.models;
setTimeout(function() {
_.each(marcadores, function(marcador){
var myLatlng = new google.maps.LatLng(marcador.get('latitude'),marcador.get('longitude'));
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
});
});
}, 6000);
return that;
}
});
}
});
Templates of document:
shell.html:
<!-- Fixed navbar -->
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
...
</nav>
<div class="wrapper-home" id="home">
</div><!--/.fluid-container-->
home.html:
<div id="leftcolumn">
<div id="map_canvas"></div>
</div>
<div id="rightcolumn">
<div class="row">
<div class="col-xs-5 coluna-input">
<form role="form">
<div class="form-group">
<label for="pesquisar">Pesquise</label>
<input type="text" class="form-control" id="pesquisar" placeholder="Pesquisar...">
</div>
<div class="form-group">
<label for="quando">Quando</label>
<input type="text" class="form-control" id="quando" placeholder="Quando">
</div>
</form>
</div>
<div class="col-xs-5 coluna-input">
<form role="form">
<div class="form-group">
<label for="pac-input">Onde</label>
<input type="text" class="form-control" id="pac-input" placeholder="Onde">
</div>
<div class="form-group">
<label for="genero">Género</label>
<input type="text" class="form-control" id="genero" placeholder="Genero">
</div>
</form>
</div>
<div class="col-xs-5 coluna-input">
<button id="search" type="button" class="btn btn-primary botao">Pesquisar</button>
</div>
<div class="col-xs-5 coluna-input">
<button type="button" class="btn btn-danger botao">Mais Filtros</button>
</div>
</div>
<hr class="linha"/>
<div class="row" id="lista">
<div class="table">
<tbody>
<% _.each(imagens, function(imagem){ %>
<tr>
<div class="col-xs-5 coluna-input">
<img src="<%= imagem.get('imagem') %>" class="img-responsive back" alt="Responsive image">
Ver Mais
</div>
</tr>
<% }); %>
</tbody>
</div>
</div>
</div>
You initialize new ev.views.Home_Eventos(mapView) as a parameter to $.html(), which is fine except that the render function in that view is 'async' because of the fetch in it. So the el it returns should be an empty <div> since the success callback doesn't return until after the initialize is complete.
Related
This is how I am using the component and sending the parameters using a Custom Element:
<div class="container" data-bind="with: DevoteeList">
<div class="row" style="padding: 10px;">
<div class="col-md-8"></div>
<div class="col-md-4">
<ko-pager params="Data: Devotees,
Modifier: $parent.DevoteeModifier,
PageCount: DevoteesPageCount(),
Url: '#Url.Action("SelectDevotees", "Devotee", new { a = 1 })'"></ko-pager>
</div>
</div>
This is how I am defining a Knockout Component. It is a Pager that I want to use at few places. But, I am receiving the error: Uncaught Error: Unable to process binding "with: function (){return SelectDevotees }"
Message: Unable to process binding "with: function (){return DevoteeList }"
Message: Unable to process binding "component: function () { return l }"
Message: Component 'ko-pager': Unknown template value: [object Object]
ko.components.register('ko-pager', {
viewModel: function (params) {
var self = this;
self.currentPage = ko.observable(1);
self.pages = ko.observableArray([]);
self.PageCount = ko.observable(params.PageCount);
//self.currentPage.subscribe(function (nv) {
// self.GetPage(self.parent);
//});
self.GetPages = function () {
for (var i = 1; i <= params.PageCount ; i++) {
self.pages.push(i);
}
return self.pages;
};
self.FirstPage = function () {
self.GetPage(1);
};
self.PrevPage = function () {
if (self.currentPage() > 1) {
var pn = self.currentPage() - 1;
self.GetPage(pn);
}
};
self.LastPage = function () {
self.GetPage(params.PageCount);
};
self.NextPage = function () {
if (self.currentPage() < params.PageCount) {
var pn = self.currentPage() + 1;
self.GetPage(pn);
}
};
self.GetPage = function (pg) {
if (pg == null)
pg = self.currentPage();
else
self.currentPage(pg);
var url = params.Url + '&pageNumber=' + pg;
$.get(url, function (data) {
var t = ko.mapping.fromJS(data);
if (params.Modifier) {
params.Modifier(t);
}
params.Data(t());
});
};
},
template: { element: document.getElementById('ko-ajax-pager') }
});
<div id="ko-ajax-pager" style="display: none;">
<div class="row" style="padding: 10px;" data-bind="visible: PageCount > 1">
<div class="col-md-1"></div>
<div class="col-md-2">
<input type="button" value="First" class="btn" data-bind="click: FirstPage" />
</div>
<div class="col-md-2">
<input type="button" value="Prev" class="btn" data-bind="click: PrevPage" />
</div>
<div class="col-md-2">
<select data-bind="options: GetPages(), value: currentPage, event: { change: GetPage(null) }">
</select>
</div>
<div class="col-md-2">
<input type="button" value="Next" class="btn" data-bind="click: NextPage" />
</div>
<div class="col-md-2">
<input type="button" value="Last" class="btn" data-bind="click: LastPage" />
</div>
<div class="col-md-1"></div>
</div>
</div>
Can someone please figure out, what is wrong?
I faced a problem and can't solve it. Maybe somobody would help me with this one.
I have MVC partial view which has table with AJAX pagination.
<div id="mainTable">
#using (Ajax.BeginForm("OrganizationMemberList", "Team", new AjaxOptions()
{
InsertionMode = InsertionMode.ReplaceWith,
UpdateTargetId = "mainTable",
HttpMethod = "POST",
Url = #Url.Action("OrganizationMemberList", "Team")
}))
{
<div class="widget">
<div class="widget-body">
<div class="table-responsive">
<div class="form-group">
<div class="input-group">
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">#ResourceMain.Search</button>
</span>
#Html.TextBoxFor(x => x.SearchString, new { #class = "form-control", #autocomplete = "off" })
</div>
</div>
<table class="table table-hover">
<thead>
<tr>
<th>#Html.DisplayNameFor(x => x.Source.FirstOrDefault().Name)</th>
</tr>
</thead>
<tbody>
#foreach (var employee in Model.Source)
{
<tr>
<td class="hidden">
#employee.Upn
</td>
<td>
#employee.Name
</td>
<td>
<i class="pointerSelect glyphicon glyphicon-plus" data-bind="click: function(name, upn){ AddUser('#employee.Name', '#employee.Upn' ) }"></i>
</td>
<td>
<i class="pointerSelect glyphicon glyphicon-king" data-bind="click: function(name, upn){ AddLeader('#employee.Name', '#employee.Upn' ) }"></i>
</td>
</tr>
}
</tbody>
</table>
#Html.Pagination(Model, "OrganizationMemberList", "mainTable")
</div>
</div>
</div>
}
</div>
and I have Click bindings on <i> tag.
When main page loads with this partial everything is working. The problem comes when I change the page after table reload bindings are not working.
My main page:
<div class="row">
<div id="something" class="col-md-3">
#Html.Action("OrganizationMemberList", "Team")
</div>
<div class="col-md-9">
<div class="widget">
<div class="widget-body">
#using (Html.BeginForm(null,null,null,FormMethod.Post, new {#class = "form-horizontal" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(x => x.Name)
#Html.ValidationMessageFor(x => x.Name)
#Html.TextBoxFor(x => x.Name, new {#class = "form-control"})
</div>
<div class="form-group">
#Html.LabelFor(x => x.Leader)
<i class="glyphicon glyphicon-king"></i>
<div data-bind="with: team">
<div style="max-width: 160px;" class="input-group">
<div class="input-group-addon">
<span data-bind="text: Leader().FullName"></span>
</div>
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(x => x.User)
<i class="glyphicon glyphicon-user"></i>
<div data-bind="foreach: users">
<div style="max-width: 160px;" class="input-group">
<div class="input-group-addon">
<span data-bind="text: FullName"></span>
</div>
<div class="input-group-addon">
<i class="pointerSelect glyphicon glyphicon-remove" data-bind="click: $root.RemoveUser"></i>
</div>
</div>
</div>
</div>
}
</div>
</div>
</div>
</div>
<script src="~/Scripts/models/TeamViewModel.js"></script>
And my Knock ViewModel looks like this:
$(function() {
var teamModel = function(team) {
var self = this;
self.id = ko.observable(team ? team.Id : 0);
self.User = ko.observableArray(team ? team.User : []);
self.Name = ko.observable(team ? team.Name : "");
self.Leader = ko.observable(team ? team.Leader : "");
};
var userModel = function (user) {
var self = this;
self.Id = ko.observable(user ? user.Id : 0);
self.FullName = ko.observable(user ? user.FullName : "");
self.UserUpn = ko.observable(user ? user.UserUpn : "");
};
var teamManagement = function() {
var self = this;
self.team = ko.observable(new teamModel());
self.users = ko.observableArray([]);
self.AddUser = function (name, upn) {
var user = { FullName: name, UserUpn: upn };
var obj = new userModel(user);
self.users.push(obj);
};
self.RemoveUser = function (user) {
self.users.remove(user);
};
self.AddLeader = function (name, upn) {
var user = { FullName: name, UserUpn: upn };
var obj = new userModel(user);
self.team().Leader(obj);
};
};
ko.applyBindings(new teamManagement());
});
Anybody has a clue?
So I made a research regarding my questuin and I came with this kind of solution.
First I need to split my teamManagement viewModel in to two viewModel.
One that is responsible for PartialView bindings and second one responsible for main page binding.
So created partialViewTable viewModel that has AddUser and AddLeader functionality.
Then I add Id to the container where PartialView is loaded. I called it PartialContainer and also added id to the main page I called it MainPageContainer.
In JS file I applyBindings like this:
ko.applyBindings(new teamManagement(), document.getElementById("MainPageContainer"));
ko.applyBindings(new partialViewTable(), document.getElementById("PartialContainer"));
Also created onSuccess function for the Ajax.Begin form.
function applyBinding() {
ko.cleanNode($("body")[0]);
ko.applyBindings(new partialViewTable(), document.getElementById("something"));
};
Now when you partialView is reloaded via Ajax it will clear old bindings and apply new binding to the new content.
I am trying to write a editable table using Backbone.js.
This is my Backbone.js app:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Resume builder!</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.5/css/bootstrap.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.5/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<style>
.jumbotron {
height: 100vh;
}
body {
background-color: white !important;
}
.table-bordered th,
.table-bordered td {
border: 1px solid #ddd!important
}
</style>
</head>
<body>
<div class="jumbotron">
<div class=container>
<h1>Resume builder</h1>
<table class="table table-bordered">
<thead>
<tr>
<th>Degree</th>
<th>Starting date</th>
<th>Ending date</th>
<th>Details</th>
</tr>
</thead>
<tbody id="informations">
<script type="text/template" id="info-row">
<tr>
<td>
<%=title%>
</td>
<td>
<%=start%>
</td>
<td>
<%=end%>
</td>
<td>
<%=details%>
</td>
<td>
<botton class="btn btn-primary" data-action="modify">edit</botton>
<botton class="btn btn-danger" data-action="delete">delete</botton>
</td>
<tr>
</script>
</tbody>
</table>
<div id="actions">
<botton class="btn btn-primary" id="addInformation">Add information</botton>
</div>
<script type="text/template" id="info-modify">
<div class="modal fade" id="edit-modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">INFORMATION</h4>
</div>
<div class="modal-body">
<form role="form">
<div>
<div class="radio">
<label><input type="radio" data-type="education" name="type" <%= (type == "education") ? "checked" : ""%>> Education</label>
</div>
<div class="radio">
<label><input type="radio" data-type="experience" name="type" <%= (type == "experience") ? "checked" : ""%>> Experience</label>
</div>
</div>
<div class="form-group">
<label>Title for Degree or experience (e.g. Computer Sci. | Software dev.):</label>
<input type="text" class="form-control" value="<%=title%>" name="title">
</div>
<div class="form-group">
<label>Start date:</label>
<input type="number" class="form-control" value="<%=start%>" name="start">
</div>
<div class="form-group">
<label>End date:</label>
<input type="number" class="form-control" value="<%=end%>" name="end">
</div>
<div class="form-group">
<label>Details:</label>
<textarea rows="5" class="form-control" name="details"><%=details%></textarea>
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</script>
</div>
</div>
<div id="modal">
</div>
</body>
<script>
$(function() {
var baseModalView = Backbone.View.extend({
el: $("#modal"),
className: 'modal fade hide',
template: _.template($("#info-modify").html()),
events: {
'hidden': 'teardown',
"click [type='submit']": "notify"
},
initialize: function() {
_.bindAll(this, "show", "teardown", "render", "notify");
this.render();
this.show();
},
show: function() {
this.$el.modal('show');
},
teardown: function() {
this.$el.data('modal', null);
this.remove();
},
render: function() {
this.$el.empty();
this.setElement(this.template(this.model.toJSON()));
},
notify: function() {
var self = this;
this.model.set({
type: self.$el.find("[type='radio']:checked").data("type"),
start: self.$el.find("[name='start']").val(),
end: self.$el.find("[name='end']").val(),
details: self.$el.find("textarea").text()
});
}
});
var InformationModel = Backbone.Model.extend({
id: -1,
defaults: {
type: " ",
title: " ",
start: 2015,
end: 2016,
details: " "
}
});
var InformationView = Backbone.View.extend({
events: {
"click [data-action='modify']": "modifyInformation",
"click [data-action='delete']": "deleteInformation"
},
template: _.template($("#info-row").html()),
initialize: function() {
_.bindAll(this, "render", "modifyInformation", "deleteInformation");
this.render();
},
render: function() {
this.setElement(this.template(this.model.toJSON()));
},
modifyInformation: function() {
var self = this;
modalView = new baseModalView({
model: self.model,
template: _.template($("#info-modify").html())
});
},
deleteInformation: function() {
this.model.destroy();
}
})
var InformationCollection = Backbone.Collection.extend({
url: "../dummy/",
model: InformationModel
});
var InformationsView = Backbone.View.extend({
el: $("#informations"),
initialize: function() {
_.bindAll(this, "render", "addInformation");
var self = this;
this.collection = new InformationCollection();
this.collection.bind("all", this.render, this);
this.collection.fetch();
// I know this is not a Backbone way...
$("#addInformation").on("click", function() {
self.addInformation();
});
},
render: function() {
this.$el.empty();
this.collection.each(function(information) {
var informationView = new InformationView({
model: information
});
this.$el.append(informationView.el);
}, this);
},
addInformation: function() {
var self = this;
modalView = new baseModalView({
model: new InformationModel()
});
}
});
new InformationsView();
$("form").submit(function(e) {
e.preventDefault();
return false;
});
});
</script>
</html>
Question:
After I edit a table row and click on submit, Backbone send a strange GET request.
The form is being submitted when you press the submit button.
This code
$("form").submit(function(e) {
e.preventDefault();
return false;
});
Won't actually stop this from happening, as when this code is executed, the form doesn't yet exist in the dom. It's only added when you create that modal view.
This is not tested, but a quick fix should be to:
$(document).on("submit", "form", function(e) {
e.preventDefault();
return false;
});
This will respond to all "submit" events on the page, and then check if they belong to a "form" element before processing the function.
Another solution, which I think is preferable as all functionality is encapsulated in your view, is to replace the submit button with a regular button in the modal template.
<button type="button" class="btn btn-success">Submit</button>
This should stop the submit event from firing, but you'll need to change the action handler the top of the baseModalView view.
If you have a form in a Backbone view, handle the form in that view, without global jQuery events.
Say you have this simple view template:
<div id="test-view">
<form id="test-form">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>
</div>
Then the view only needs to catch the submit for its form:
var FormView = Backbone.View.extend({
events: {
"submit #test-form": "onSubmit"
},
onSubmit: function(e) {
e.preventDefault();
console.log("test-form submit prevented");
}
});
var view = new FormView({
el: $('#test-view')
});
I've been struggling with this problem for 3 days and hope to find some a nice solution...:
I'm trying to make get Google Maps' current location values(lonlongitude, lat latitude) in inside of a modal, and bring back that those values to the input page's form inputs which are going to be filled out.
Here's my code:
main.html.erb(parent page)
<form action="/ask/fine/<%= #chap.id %>" class="col-md-12 clearfix">
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h1>Current Location</h1>
<input type="hidden" id="lon" value="" />
<input type="hidden" id="lat" value="" />
<input type="button" class="btn btn-info" data-toggle="modal" data-target="#myModal_map" value="CURRENT LOCATION CHECK">
</div>
<div class="panel-body">
<textarea class="form-control" rows="10" name="content" placeholder="What's happening?" required></textarea>
</div>
</div>
<input type="submit" class="btn btn-info btn-block btn-lg" value="Submit">
</form>
_modal_map.html.erb(modal)
<!DOCTYPE html>
<html>
<head>
<title>Geolocation</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<style>
#map-canvas {
height: 300px;
margin: 0px;
padding: 0px
}
</style>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true"></script>
<script>
var map;
function initialize() {
var mapOptions = {
zoom: 13
};
map = new google.maps.Map(document.getElementById('map-canvas'),
mapOptions);
// Try HTML5 geolocation
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var pos = new google.maps.LatLng(position.coords.latitude,
position.coords.longitude);
var lonlon = position.coords.longitude.toString();
var latlat = position.coords.latitude.toString();
document.getElementById('lon').innerHTML = lonlon;
document.getElementById('lat').innerHTML = latlat;
var lons = position.coords.longitude.toString().substr(0,10);
var lats = position.coords.latitude.toString().substr(0,10);
var lonlat = lats + "|" + lons;
var vReturn = new Object();
vReturn.lon = lonlon;
vReturn.lat = latlat;
window.returnValue = vReturn;
var infowindow = new google.maps.InfoWindow({
map: map,
position: pos,
content: lonlat
});
map.setCenter(pos);
}, function() {
handleNoGeolocation(true);
});
} else {
// Browser doesn't support Geolocation
handleNoGeolocation(false);
}
}
function handleNoGeolocation(errorFlag) {
if (errorFlag) {
var content = 'Error: The Geolocation service failed.';
} else {
var content = 'Error: Your browser doesn\'t support geolocation.';
}
var options = {
map: map,
position: new google.maps.LatLng(60, 105),
content: content
};
var infowindow = new google.maps.InfoWindow(options);
map.setCenter(options.position);
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="myModal_map" class="modal fade">
<div class="modal-dialog modal-lg" style="margin-top: 10%;">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">YOUR LOCATION</h4>
</div>
<div class="modal-body" style="height: 350px;">
<div id="map-canvas"></div>
</div>
<div class="modal-footer">
<input type="hidden" id="lon" value="" />
<input type="hidden" id="lat" value="" />
<input type="button" data-dismiss="modal" class="btn btn-primary" onclick="javascript:return done()" value="SAVE">
</div>
</div>
</div>
</div>
</body>
<script>
function done() {
var vReturnValue = new Object();
vReturnValue.lon = document.getElementById('lon').value
vReturnValue.lat = document.getElementById('lat').value
window.returnValue = vReturnValue;
});
</script>
</html>
I tried using returnValue and sessionStorage without success. How can I successfully submit those two input values(longitude, latitude) inside the parent page's form?
you can use window.opener to reference the parent window from the child window as long as the two pages are served from the same domain.
window.opener === parentWindow
If your modal is served from a different domain, you'll need to use postMessage hopefully you won't have that issue cause it's pain to set that up.
I'm using knockoutjs for the first time in an attempt to have a list of tasks and then to be able to click on one to show it in a popup form. The issue I'm having is with data-bind="click: $parent.edit", which calls ToDoListViewModel/edit.
When I first started writing the list code, it would pass in the current row's todo object as the first parameter into the edit function. After adding the second view model for my add/edit popup form, it no longer behaves this way. Calling $parent.edit still calls the edit function, however now it passes in an empty object { }.
It seems like I'm running into some sort of conflicting bindings issue, any ideas?
Here is the to do list html:
<table class="table table-striped table-hover" data-bind="visible: isVisible">
<thead>
<tr>
<th>To-Do</th>
<th>Estimate</th>
<th>Deadline</th>
#if (Model == null) { <th>Property</th> }
<th>Tenant</th>
<th style="display: none;">Assigned To</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: todos">
<tr data-bind="visible: isVisible()">
<td><a class="pointer" title="Edit To-Do" data-bind="click: $parent.edit, text: Task"></a></td>
<td data-bind="text: FormattedAmount"></td>
<td data-bind="text: FormattedDueDate"></td>
<td data-bind="visible: $parent.showPropertyColumn, text: PropertyName"></td>
<td data-bind="text: TenantName"></td>
<td>
<div class="btn-group pull-right">
<a class="btn btn-primary pointer default-click-action" data-bind="click: $parent.markComplete">Mark Complete</a>
<button class="btn btn-primary dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a class="pointer" data-bind="click: $parent.edit">Edit To-Do</a></li>
<li><a class="pointer" data-bind="click: $parent.remove">Delete To-Do</a></li>
</ul>
</div>
</td>
</tr>
</tbody>
</table>
And here is the popup html:
#model Housters.Schemas.Models.Property.Listing
<div id="popup" class="modal fade" style="display: none;" data-bind="with: form">
#using (Html.BeginForm("SaveTodo", "Properties", FormMethod.Post, new { Id = "form", #class = "form-horizontal" }))
{
<div class="modal-header">
<a class="close" data-dismiss="modal">×</a>
<h3>To-Do Information</h3>
</div>
<div class="modal-body">
<input id="id" name="id" type="hidden" data-bind="value: todo().Id" />
<input id="originalListingId" type="hidden" value="#(Model != null ? Model.IdString : "")" />
<div class="control-group #(Model != null ? " hide" : "")">
<label class="control-label" for="listingId">Property</label>
<div class="controls">
#Html.DropDownList("listingId", ViewBag.Listings as SelectList, "None",
new Dictionary<string, Object> { { "data-bind", "value: todo().ListingId" } })
</div>
</div>
<div class="control-group">
<label class="control-label" for="tenantId">Tenant</label>
<div class="controls">
<select id="tenantId" name="tenantId" class="span11" data-bind="value: todo().TenantId">
<option value="">None</option>
</select>
<p class="help-block">Is this task related to a certain tenant?</p>
</div>
</div>
<div class="control-group">
<label class="control-label" for="amount">Estimate to Complete</label>
<div class="controls">
<div class="input-prepend"><span class="add-on">$</span><input type="text" id="amount" name="todo.Amount" maxlength="10"
class="span11 number" data-bind="value: todo().Amount" /></div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="dueDate">Due Date</label>
<div class="controls">
<input type="text" id="dueDate" name="todo.DueDate" maxlength="10"
class="span11 date" data-bind="value: todo().DueDate" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="task">Task<span class="required-asterisk">*</span></label>
<div class="controls">
<textarea id="task" name="todo.Task" rows="4" class="span11 required" data-bind="value: todo().Task"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<a class="btn pointer" data-dismiss="modal">Close</a>
<a id="mark-complete-popup" class="btn btn-primary" data-dismiss="modal" data-bind="visible: (todo().Id && !todo().IsComplete), click: markComplete">Mark Complete</a>
<button type="button" class="btn btn-primary" data-bind="click: save">Save</button>
</div>
}
</div>
And lastly, here is the javascript defining the view models:
function ToDoList () {
if(!(this instanceof arguments.callee))
return new arguments.callee();
var parent = this;
this.showCompleted = ko.observable(false);
this.tenantFilter = new PropertyTenantFilter();
this.viewModel = {
list: new ToDoListViewModel(),
form: new ToDoFormViewModel()
};
this.init = function () {
//get all tenants.
utils.block($("#grid-content"), "Loading");
this.tenantFilter.init(function () {
//initialize view model.
ko.applyBindings(this.viewModel);
//setup controls & events.
$("#dueDate").datepicker();
$("#listingId").change(this.tenantFilter.getByListing.bind(this.tenantFilter)).change();
} .bind(this));
};
function ToDoListViewModel() {
//init.
var self = this;
self.todos = ko.observableArray([]);
//computed.
self.showPropertyColumn = ko.computed(function () {
return $("#originalListingId").val().length == 0;
});
self.isVisible = ko.computed(function () {
return _.find(self.todos(), function (todo) { return todo.isVisible(); }) != null;
});
//operations.
self.add = function () {
//set form field values.
parent.viewModel.form.fill(new schemas.ToDo({}, parent));
//show popup.
$("#popup").modal("show");
};
self.edit = function (todo) {
console.debug("edit: " + JSON.stringify(todo));
//set form field values.
parent.viewModel.form.fill(todo);
//update tenants dropdown for selected listing.
parent.tenantFilter.getByListing();
//show popup.
$("#popup").modal("show");
};
self.markComplete = function (todo) {
parent.markComplete(todo);
};
self.remove = function (todo) {
var result = confirm("Are you sure that you want to delete this To-Do?");
if (result) {
//save changes.
utils.ajax(basePath + "properties/deletetodo",
{ id: todo.Id },
function (success) {
//refresh results.
self.todos.remove(todo);
//show result.
utils.showSuccess('The To-Do has been deleted successfully');
}
);
}
};
self.toggleShowCompleted = function () {
parent.showCompleted(!parent.showCompleted());
$("#showCompletedTodos").text(parent.showCompleted() ? "Show Active" : "Show Completed");
};
self.update = function (todo) {
var existingToDo = _.find(self.todos(), function (item) { return item.Id() == todo.Id(); });
existingToDo = todo;
};
//load todos from server.
utils.ajax(basePath + "properties/gettodos",
{ id: $("#originalListingId").val(), showCompleted: parent.showCompleted() },
function (results) {
var mappedTodos = $.map(results, function (item) { return new schemas.ToDo(item, parent); });
self.todos(mappedTodos);
$("#grid-content").unblock();
}
);
}
function ToDoFormViewModel() {
//init.
var self = this;
self.todo = ko.observable({});
utils.setupPopupForm(self.saved);
//operations.
self.fill = function (todo, isEdit) {
//set form field values.
self.todo = todo;
if (todo.Id()) {
//update tenants dropdown for selected listing.
parent.tenantFilter.getByListing();
}
//show popup.
$("#popup").modal("show");
};
self.save = function (todo) {
self.todo = todo;
$("#form").submit();
};
self.saved = function (result) {
var todo = new schemas.ToDo(result.todo, parent);
if (result.isInsert)
parent.viewModel.list.todos().push(todo);
else
parent.viewModel.list.update(todo);
utils.showSuccess("Your To-Do has been saved successfully.");
};
self.markComplete = function (todo) {
parent.markComplete(todo);
};
}
this.markComplete = function (todo) {
var result = confirm("Are you sure that you want to mark this To-Do complete?");
if (result) {
//save changes.
utils.ajax(basePath + "properties/marktodocomplete", {
id: todo.Id()
},
function () {
todo.IsComplete(true);
//show success.
utils.showSuccess('Your To-Do has been marked completed');
} .bind(this)
);
}
}
this.init();
}
One possible solution is to replace:
data-bind="click: $parent.edit"
with
data-bind="click:$root.viewModel.list.edit"