I recently followed this tutorial to create a message board:
Real-time message board
The board works perfectly, but I am having trouble figuring out how to delete posts and comments from the view and SignalR.
I am basically trying to add a link to every post and comment to allow the post/comment to be deleted. I am new to SignalR and knockout, so help would be greatly appreciated.
Here is the js:
var post = function (id, message, username, date) {
this.id = id;
this.message = message;
this.username = username;
this.date = date;
this.comments = ko.observableArray([]);
this.addComment = function (context) {
var comment = $('input[name="comment"]', context).val();
if (comment.length > 0) {
$.connection.boardHub.server.addComment(this.id, comment, vm.username())
.done(function () {
$('input[name="comment"]', context).val('');
});
}
};
}
var comment = function (id, message, username, date) {
this.id = id;
this.message = message;
this.username = username;
this.date = date;
}
var vm = {
posts: ko.observableArray([]),
notifications: ko.observableArray([]),
username: ko.observable(),
signedIn: ko.observable(false),
signIn: function () {
vm.username($('#username').val());
vm.signedIn(true);
},
writePost: function () {
$.connection.boardHub.server.writePost(vm.username(), $('#message').val()).done(function () {
$('#message').val('');
});
},
}
ko.applyBindings(vm);
function loadPosts() {
$.get('/api/posts', function (data) {
var postsArray = [];
$.each(data, function (i, p) {
var newPost = new post(p.Id, p.Message, p.Username, p.DatePosted);
$.each(p.Comments, function (j, c) {
var newComment = new comment(c.Id, c.Message, c.Username, c.DatePosted);
newPost.comments.push(newComment);
});
vm.posts.push(newPost);
});
});
}
$(function () {
var hub = $.connection.boardHub;
$.connection.hub.start().done(function () {
loadPosts(); // Load posts when connected to hub
});
// Hub calls this after a new post has been added
hub.client.receivedNewPost = function (id, username, message, date) {
var newPost = new post(id, message, username, date);
vm.posts.unshift(newPost);
// If another user added a new post, add it to the activity summary
if (username !== vm.username()) {
vm.notifications.unshift(username + ' has added a new post.');
}
};
// Hub calls this after a new comment has been added
hub.client.receivedNewComment = function (parentPostId, commentId, message, username, date) {
// Find the post object in the observable array of posts
var postFilter = $.grep(vm.posts(), function (p) {
return p.id === parentPostId;
});
var thisPost = postFilter[0]; //$.grep returns an array, we just want the first object
var thisComment = new comment(commentId, message, username, date);
thisPost.comments.push(thisComment);
if (thisPost.username === vm.username() && thisComment.username !== vm.username()) {
vm.notifications.unshift(username + ' has commented on your post.');
}
};
});
I'm not sure if I need to add a delete function to the view model or the post/comment objects.
Here is the view:
<ul class="posts list-unstyled" data-bind="foreach: posts">
<li>
<p>
<span data-bind="text: username" class="username"></span><br />
</p>
<p>
<span data-bind="text: message"></span>
</p>
<p class="no-pad-bottom date-posted">Posted <span data-bind="text: moment(date).calendar()" /></p>
#*<form class="add-comment" data-bind="visible: $parent.signedIn(), submit: removePost">
<div class="row">
<div class="col-md-3">
<button class="btn btn-default" type="submit" name="delete">Delete Post</button>
</div>
</div>
</form>*#
<a href='#' data-bind='click: removePost'>Delete</a>
<div class="comments" data-bind="visible: $parent.signedIn() || comments().length > 0">
<ul class="list-unstyled" data-bind="foreach: comments, visible: comments().length > 0">
<li>
<p>
<span class="commentor" data-bind="text: username"></span>
<span data-bind="text: message"></span>
</p>
<p class=" no-pad-bottom date-posted">Posted <span data-bind="text: moment(date).calendar()" /></p>
</li>
</ul>
<form class="add-comment" data-bind="visible: $parent.signedIn, submit: addComment">
<div class="row">
<div class="col-md-9">
<input type="text" class="form-control" name="comment" placeholder="Add a comment" />
</div>
<div class="col-md-3">
<button class="btn btn-default" type="submit">Add Comment</button>
</div>
</div>
</form>
</div>
</li>
</ul>
I also need to communicate this to SignalR:
public void WritePost(string username, string message)
{
var ctx = new MessageBoardContext();
var post = new Post { Message = message, Username = username, DatePosted = DateTime.Now };
ctx.Posts.Add(post);
ctx.SaveChanges();
Clients.All.receivedNewPost(post.Id, post.Username, post.Message, post.DatePosted);
}
public void AddComment(int postId, string comment, string username)
{
var ctx = new MessageBoardContext();
var post = ctx.Posts.FirstOrDefault(p => p.Id == postId);
if (post != null)
{
var newComment = new Comment { ParentPost = post, Message = comment, Username = username, DatePosted = DateTime.Now };
ctx.Comments.Add(newComment);
ctx.SaveChanges();
Clients.All.receivedNewComment(newComment.ParentPost.Id, newComment.Id, newComment.Message, newComment.Username, newComment.DatePosted);
}
}
Thanks!
I'd add a deleteComment method on your vm which uses ajax to make a delete call to the server for the comment. If the delete is successful you can then broadcast a delete with the relevant information and update all clients.
Related
I have page ,where im showing all messages and in header im showing Total of messages by counting them and im looking for something with javascript or MVC ,when user click on link (its means message be opened) count one down (-1) of total count which is messages and then use localstorage or Cookies to remember the count for next time, when user coming back to check their messages.
Can anyone please help me or point me in the right direction!
Thanks in advance :)
For example : lets say i have 14 messages , when user click on link i should be 13
Controller:
public ActionResult Messages(RMAHistory m2)
{
string EmailID = Session["Email"].ToString();
ViewBag.CountMSG = (new DbNamespace().Besked.Where(x => x.KundensEmail == EmailID).Select(f => f.KundensEmail).ToList().Count);
var bla7 = (from RB in db.Besked
where RB.KundensEmail == EmailID
select new RMAHistory.Comment_List
{
id = RB.ID,
MSG = RB.MSG,
se = RB.Seen,
Date = RB.Date,
Forfatter = RB.Writer,
KundensEmail =RB.KundensEmail,
Id = RB.RMAID,
MSGType =RB.MSGType
});
m2.Comment_Lists = bla7.ToList();
return View(m2);
}
View:
<div class="col-lg-8 col-md-7 col-md-offset-2">
<div class="card">
<div class="card-header" data-background-color="purple">
<span class="category">(You have #ViewBag.CountMSG New Messages)</span>
</div>
<div class="content">
#if (!Model.Comment_Lists.Any())
{
<div class="bs-callout bs-callout-danger">
<h4>You Do not Have New Messages</h4>
</div>
}
else
{
foreach (var item in Model.Comment_Lists)
{
if (item.se == "Kunde")
{
<div class="bs-callout bs-callout-success message">
<div class="col-xs-3 text-right">
<a target="_blank" href="/Account/RMADetails?id=#item.Id" id="#item.id" class="btn btn-sm btn-success btn-icon btnChangestu"><i class="fa fa-envelope"></i></a>
</div>
<h4><i class="fa fa-bookmark-o" aria-hidden="true"></i> Message number #item.Id</span></h4>
<span class="text-muted"><small> #item.Date.ToString("dd/MMMM/yyy")</small></span><br />
<span class="text-muted status"><span> #item.MSGType</span></span>
<input type="hidden" name="ChangeStu" id="ChangeStu" value="read massage" />
</div>
}
}
}
</div>
</div>
</div>
JavaScript:
<script>
$(document).ready(function () {
$('.btnChangestu').click(function () {
var id = $(this).attr('id');
if (!id) {
return;
}
var button = $(this);
var status = $(this).closest('.message').find('.status');
$.ajax({
type: "POST",
url: '#Url.Action("UpdateMSGStatus", "Account")',
data: {
IDMSG: id,
ChangeStu: $("#ChangeStu").val()
},
dataType: 'json',
success: function (result) {
if (result) {
status.text("read message");
button.removeData('id')
console.log("Ok")
} else {
// display error?
console.log("error");
}
},
error: function () {
console.log('something went wrong - debug it!');
}
})
});
});
</script>
I am using the Knockout js in my single page Application,I need to carry the value of one viewmodel data to another viewModel data,So i can reduce my duplication creating same view, How i can achieve this below is my code.I got 2 different js file,which one consist of Employee ViewModel and in another Department View Model
//Employee View
<div class="Employee-view" data-role="view" id="employee">
<div data-role="content" >
<ul>
<li foreach:EmployeeData>
//Onlcick of this need to navigate to Department view and bind all values on particular Employee ID
<div databind:"click:GetDepartmentDetails">
<span data-bind:"text:EmployeeId"> <span>
<span data-bind:"text:EmployeeName"> <span>
<span data-bind:"text:EmployeeImage"> <span>
<div>
<li>
<ul>
</div>
</div>
EmployeeViewModel = new EmployeeDetailsViewModel();;
$(".Employee-view").each(function () {
ko.applyBindings(EmployeeViewModel, $(this).get(0));
});
function EmployeeViewModel(){
var self=this;
self.EmployeeData = ko.observableArray([]);
self.GetEmployee = function(UserName,Password){
var UserModel = { UserName: UserName, Password: Password}
$.ajax({
type: "POST",
dataType: "json",
url: serverUrl + 'xxx/xxx/GetEmployee',
data: UserModel,
success: function (data) {
self.EmployeeData($.map(data, function (item) {
return new EmployeeModel(item);
}))
}
});
}
//Click EVENT
self.GetDepartmentDetails=(EmployeeData){
// I am getting all the value in this ViewModel,I need to pass this value to DepartmentViewModel and i need to call the function
self.GetEmployeeByDepartment();
}
}
function EmployeeModel(data)
{
var self = this;
self.EmployeeId = ko.observable(data.EmployeeId)
self.EmployeeName = ko.observable(data.EmployeeName)
self.EmployeeImage = ko.observable(data.EmployeeImage)
}
//Department View
<div class="Department-view" data-role="view" id="Department">
<div data-role="content">
<ul>
<li data-bind:"foreach:DepartmentData ">
<div>
<span data-bind:"text:DeptId"> <span>
<span data-bind:"text:DeptName"> <span>
</div>
</li>
</ul>
</div>
</div>
//Department View Model
function DepartmentViewModel ()
{
var self = this;
self.DepartmentData = ko.observableArray([]);
self.GetEmployeeByDepartment = function(item){
employeeID = item.EmployeeId
employeename = item.Employeename
var DeptModel = { Employeename: employeeID, Employeename: employeename}
$.ajax({
type: "POST",
dataType: "json",
url: serverUrl + 'xxx/xxx/GetDepratmenDetails',
data: DeptModel ,
success: function (data) {
self.DepartmentData ($.map(data, function (item) {
return new DepartmentModel(item);
})),
});
}}
}
function DepartmentModel(data)
{
var self=this;
self.DeptId = ko.observable(data.DeptID)
self.DeptName = ko.observable(data.DeptName)
}
DepartmentViewModel = new DepartmentDetailsViewModel();
$(".Department-view").each(function () {
ko.applyBindings(DepartmentViewModel, $(this).get(0));
});
Its a duplicate question as we were not able to fix it. Please help
How to carry the data from one viewModel to another ViewModel Knockout JS
First there are many typos and wrong bindings in your view that need your attention. Example :
<ul>
<li foreach:EmployeeData> // There is no data-bind="
<div databind:"click:GetDepartmentDetails"> //databind => data-bind and You need = not : <div data-bind="click:$parent.GetDepartmentDetails">
<span data-bind:"text:EmployeeId"> <span> // You need = not :
<span data-bind:"text:EmployeeName"> <span>
<span data-bind:"text:EmployeeImage"> <span>
<div>
<li>
<ul>
You can define an observable variable in your employeeViewModel to hold a new instance of DepartmentViewModel and then you can call any functions through that variable and there is no need to re-apply ko many times.
Below I am trying to use your code by using a fake data to show you how you can approach this.
Example : https://jsfiddle.net/kyr6w2x3/157/
View:
<h1>Employee View</h1>
<div class="Employee-view" data-role="view" id="employee">
<div data-role="content" >
<ul>
<li data-bind="foreach:EmployeeData">
<div data-bind="click:$parent.GetDepartmentDetails">
<span data-bind="text:EmployeeId"> </span>
<span data-bind="text:EmployeeName"> </span>
<span data-bind="text:EmployeeImage"> </span>
</div>
</li>
</ul>
</div>
</div>
<hr>
<h1>Department View</h1>
<div data-bind="with:DepartmentVM">
<div class="Department-view" data-role="view" id="Department">
<div data-role="content">
<ul>
<li data-bind = "foreach:DepartmentData ">
<div>
<span data-bind = "text:DeptId"> </span>
<span data-bind = "text:DeptName"> </span>
</div>
</li>
</ul>
</div>
</div>
</div>
Models :
function EmployeeViewModel() {
var self = this;
// Here you define an observable variable to hold your DepartmentVM
self.DepartmentVM = ko.observable();
self.EmployeeData = ko.observableArray([]);
self.GetEmployee = function(UserName,Password){
var UserModel = { UserName: UserName, Password: Password}
$.ajax({
type: "POST",
dataType: "json",
url: serverUrl + 'xxx/xxx/GetEmployee',
data: UserModel,
success: function (data) {
self.EmployeeData($.map(data, function (item) {
return new EmployeeModel(item);
}))
}
});
}
//Here I am calling GetEmployee()
self.GetEmployee();
// Below you have a knockout function
self.GetDepartmentDetails = function (EmployeeData){
console.log(EmployeeData)
// Create a new instance of your DepartmentViewModel and then you can call the fucntion inside of it and pass your data
self.DepartmentVM(new DepartmentViewModel());
self.DepartmentVM().GetEmployeeByDepartment(EmployeeData);
}
}
function DepartmentViewModel () {
var self = this;
self.DepartmentData = ko.observableArray([]);
self.GetEmployeeByDepartment = function(item){
//Below you need () to get the value of observable variable item.EmployeeId()
employeeID = item.EmployeeId()
employeename = item.EmployeeName()
var DeptModel = { Employeename: employeeID, Employeename: employeename}
$.ajax({
type: "POST",
dataType: "json",
url: serverUrl + 'xxx/xxx/GetDepratmenDetails',
data: DeptModel ,
success: function (data) {
self.DepartmentData ($.map(data, function (item) {
return new DepartmentModel(item);
}))
}
});
}
}
function DepartmentModel(data)
{
var self = this;
self.DeptId = ko.observable(data.DeptID)
self.DeptName = ko.observable(data.DeptName)
}
function EmployeeModel(data)
{
var self = this;
self.EmployeeId = ko.observable(data.EmployeeId)
self.EmployeeName = ko.observable(data.EmployeeName)
self.EmployeeImage = ko.observable(data.EmployeeImage)
}
var EmployeeVM = new EmployeeViewModel()
ko.applyBindings(EmployeeVM);
so, I built a view with a ViewResult and the View has a text box to enter a search string and a button to submit. It works great, it return my searched items and displays on the screen. Now, my requirement is to move that text box to the home layout (below) and submit from there. But, when I press submit and it calls the ajax post, the debug shows the search string searching the viewResult and it even gets to the View associated, but the View does not render. any ideas?
Here is the the Search Box
<div style="float:right;">
<input type="text" id="searchString" name="searchString" />
<a href="#" id="search" ><img src="~/Images/Custom/searchIcon.jpg" style="width:25px; height:25px;"/></a>
</div>
Here is the javascript with the ajax post
<script>
$(document).ready(function () {
$("#search").click(function () {
var searchString = $("#searchString").val();
var datastring = { "searchString": searchString };
$.ajax({
url: "/Home/Search/",
type: "POST",
contentType: "application/json",
data: JSON.stringify(datastring),
datatype: 'json',
success: function () {
console.log('success!!');
}
});
});
});
</script>
and here is my ViewResult:
public ViewResult Search(string searchString, int? pageNumber)
{
// var searchString = fc["searchString"];
var results = new ArrayList();
var mylist = new List<SearchResult>();
var model = new SearchViewModel();
var host = "/CommunityWildlifeHabitat";
if (System.Web.HttpContext.Current.Request.IsLocal)
host = "";
// Search Communities
var search = from s in db.Communities
where s.ComunityName.Contains(searchString) || s.Description.Contains(searchString)
select s;
// Search Resource Center
var docs = from d in db.ResourceCenters
where d.Title.Contains(searchString) || d.Description.Contains(searchString)
select d;
// Set up Arraylist with type Community
foreach(var c in search)
{
var community = new SearchResult();
community.type = "community";
community.CommunityId = c.CommunityId;
community.CommunityName = c.ComunityName;
community.Description = c.Description;
community.CommunityType = c.CommunityType1.TypeName;
community.CommunityCity = c.CommunityCity;
community.CommunityState = c.CommunityState;
community.CommunityZip = c.CommunityZip;
community.Population = c.Population;
mylist.Add(community);
}
// Set up ArrayList with type ResourceCenter
foreach (var d in docs)
{
var document = new SearchResult();
document.type = "document";
document.Title = d.Title;
document.Document_Description = d.Description;
document.FilePath = d.FilePath;
document.Date = Convert.ToDateTime(d.Date);
document.UpLoadedBy = d.UpLoadedBy;
mylist.Add(document);
}
model.results = mylist;
ViewBag.results = model.results;
ViewBag.searchString = searchString;
ViewBag.Host = host;
return View(mylist.ToPagedList(pageNumber ?? 1, 10));
}
Lastly, here is my View:
<h2>Search</h2>
#using (Html.BeginForm("Search", "Home", FormMethod.Get, new { #class = "form-horizontal", role = "form" }))
{
#Html.TextBox("searchString", ViewBag.searchString as string, new { #class = "form-control", required = "required"})
<input type="submit" class="btn btn-default" value="Search" />
<hr />
if (#Model.Count != 0)
{
<h3>The following results were found for #ViewBag.searchString</h3>
foreach (var search in #Model)
{
if (#search.type == "community")
{
<div class="resource-element">
<a href="#ViewBag.Host/Communities/CommunityPage/#search.CommunityId">
<span class="resource-type pull-right">Community</span>
</a>
<h3>#search.CommunityName</h3>
<p>#search.Description</p>
<span class="">Type : #search.CommunityType</span><br />
<span class="">#search.CommunityCity, #search.CommunityState #search.CommunityZip</span><br />
<span class="">Population: #search.Population</span>
<br>
</div>
}
else
{
<div class="resource-element">
<a href="#ViewBag.Host#search.FilePath">
<span class="resource-type pull-right">Document</span>
</a>
<h3>#search.Title</h3>
<p>#search.Document_Description</p>
<span class="">#search.Date</span>
<br>
<span class="">#search.UpLoadedBy</span>
<br>
</div>
}
}
#Html.PagedListPager(Model, pageNumber => Url.Action("Search", "Home", new { searchString = #ViewBag.searchString, pageNumber }),
new PagedListRenderOptions() { Display = PagedListDisplayMode.IfNeeded, DisplayPageCountAndCurrentLocation = true })
}
else
{
if (#ViewBag.searchString != null)
{
<div class="resource-element">
<a href="#">
<span class="resource-type pull-right"></span>
</a>
<h3>No Results Found</h3>
</div>
}
}
}
if you simply change your form helper with the following
#using (Html.BeginForm("Search", "Home", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
and remove your jquery, it should post your form correctly to the server, and automatically reload the page.
There is no specific need in running an async call inside your page.
I am creating a dynamic, single-paged forum site using AngularJS as the front-end and Firebase as the back-end. The page consists of a list of threads on the left-hand side and the thread content on the right-hand side. The thread content displayed is based on the thread selected from the list.
I can successfully select a thread from the list and display its contents. However, when a thread is selected from the list, all of the other threads in the list become replicas of the selected thread. By this, I mean that the attribute values for the title, comments and votes of the selected thread are assigned to the same attributes in all of the other threads simultaneously, making them all identical. The ID of each thread does not change.
Can anybody give me some insight as to what is causing this issue? I can't identify anything in my code that would cause the attribute values of each Firebase object to be reassigned.
Here is the main.html page that contains the list and thread content sections
<div ng-controller="mainPageController">
<div>
<h3>
Welcome {{user.name}}! <button class="btn-danger img-rounded" ng-click="logout()" id="LogoutBtn">Logout</button>
</h3>
</div>
<div class="col-md-6">
<h2>All Threads</h2>
<div id="searchThreads" class="input-group col-md-5 img-rounded">
<input type="text" class="col-xs-5 form-control" ng-model="searchThread" placeholder="Search threads...">
</div>
<div id="addThread" class="input-group">
<input type="text" class="col-xs-5 form-control" ng-model="newThreadTitle" placeholder="New thread title..."/>
<button ng-click="addThread()">Add thread</button>
</div>
<!-- Thread List -->
<div>
<div ng-repeat="thread in threads | filter:searchThread | orderObjectBy:'votes'">
<button class="glyphicon glyphicon-chevron-up" ng-click="upvote(thread.$id, thread.votes)"></button> |
<button class="glyphicon glyphicon-chevron-down" ng-click="downvote(thread.$id, thread.votes)"></button>
<a href ng-click="showThread(thread)">{{thread.votes}}<span style="margin-left:1em"> {{thread.title}} by {{thread.username}}</span></a>
</div>
</div>
</div>
</div>
<!-- Thread content viiew -->
<div class="col-md-6">
<div ng-controller="threadPageController">
<h1>{{currentThread.title}} by {{currentThread.username}}</h1>
<div>
<input type="text" ng-model="newComment" placeholder="Write a comment..."/>
<button ng-click="addComment()">Add Comment</button>
</div>
<div>
<div ng-repeat="comment in currentThread.comments">{{comment.username}}: {{comment.text}}
</div>
<div ng-if="!currentThread.comments.length">There are no comments on this thread</div>
</div>
</div>
</div>
The mainPageController
angular.module('richWebApp')
.controller('mainPageController', function($scope, $location, userService, threadService, fb, $firebaseAuth, $filter){
$scope.user = userService.getLoggedInUser();
$scope.newThreadTitle = '';
$scope.currentThreadId = '';
$scope.threads = threadService.getAllThreads();
$scope.threads.$loaded().then(function(){
console.log($scope.threads)
});
$scope.users = userService.getLoggedInUsers();
$scope.addThread = function(){
if(!$scope.newThreadTitle){
return false; //Don't do anything if the text box is empty
}
var newThread = {
title: $scope.newThreadTitle,
username: $scope.user.name,
comments: [],
votes: 0
};
$scope.threads.$add(newThread);
$scope.newThread = '';
$scope.newThreadTitle = ''; //Clear the text in the input box
}
$scope.showThread = function(thread) {
$scope.$emit('handleEmit', {id: thread.$id});
};
$scope.upvote = function(threadId, threadVotes) {
var newVotes = threadVotes + 1;
var ref = new Firebase(fb.url);
var threadRef = ref.child("threads");
threadRef.child(threadId).update({
votes: newVotes
});
}
$scope.downvote = function(threadId, threadVotes) {
var newVotes = threadVotes - 1;
var ref = new Firebase(fb.url);
var threadRef = ref.child("threads");
threadRef.child(threadId).update({
votes: newVotes
});
}
$scope.logout = function(){
userService.logout();
}
});
The threadPageController
angular.module('richWebApp')
.controller('threadPageController', function($scope, $location, $routeParams, threadService, fb, userService){
$scope.$on('handleBroadcast', function (event, args) {
var threadId = args.id;
var currentThread = threadService.getThread(threadId);
currentThread.$bindTo($scope, 'currentThread') //creates $scope.thread with 3 way binding
});
$scope.newComment = '';
$scope.addComment= function(){
if(!$scope.newComment){
return false; //Don't do anything if the text box is empty
}
var currentUser = userService.getLoggedInUser();
var newComment = {
text: $scope.newComment,
username: currentUser.name
};
$scope.currentThread.comments = $scope.currentThread.comments || [];
$scope.currentThread.comments.push(newComment);
$scope.newComment = ''; //Clear the input box
}
});
threadService
angular.module("richWebApp").service("threadService", function($firebaseArray, $firebaseObject, fb){
this.getAllThreads = function(){
var ref = new Firebase(fb.url + '/threads');
return $firebaseArray(ref);
};
this.getThread = function(threadId){
var ref = new Firebase(fb.url + '/threads/' + threadId);
return $firebaseObject(ref);
};
});
Here is a fiddle
I have this html:
<div class="margin:0px; padding:0px; outline:0; border:0;" data-bind="with: notesViewModel">
<table class="table table-striped table-hover" data-bind="with: notes">
<thead><tr><th>Date Logged</th><th>Content</th><th>Logged By</th><th></th></tr>
</thead>
<tbody data-bind="foreach: allNotes">
<tr>
<td data-bind="text: date"></td>
<td data-bind="text: compressedContent"></td>
<td data-bind="text: logged"></td>
<td><img src="/images/detail.png" data-bind="click: $root.goToNote.bind($data, $index())" width="20" alt="Details"/></td>
</tr>
</tbody>
</table>
<div class="noteView" data-bind="with: chosenNote">
<div class="info">
<p><label>Date:</label><span data-bind="text: date"></span></p>
<p><label>Logged:</label><span data-bind="text: logged"></span></p>
</div>
<p class="message" data-bind="html: content"></p>
<button class="btn btn-default" data-bind="click: $root.toNotes">Back to Notes</button>
</div>
<div class="editor-label" style="margin-top:10px">
Notes
</div>
<div class="editor-field">
<textarea id="contact_note" rows="5" class="form-control" data-bind="value: $root.noteContent"></textarea>
<p data-bind="text: $root.characterCounter"></p>
<button class="btn btn-info" data-bind="click: $root.saveNotes">Save</button>
<div data-bind="html: $root.status">
</div>
</div>
</div>
And this JavaScript using knockout:
var notesViewModel = function () {
var self = this;
self.notes = ko.observable(null);
self.chosenNote = ko.observable();
self.allNotes = new Array();
self.user = "user1";
// behaviours
self.goToNote = function (noteIndex) {
self.notes(null);
self.chosenNote(new note(self.allNotes[noteIndex]));
};
self.toNotes = function () {
self.chosenNote(null);
self.notes({ allNotes: $.map(self.allNotes, function (item) { return new note(item); }) });
console.log(self.notes());
}
self.noteContent = ko.observable();
self.saveNotes = function () {
var request = $.ajax({
url: "EnquiryManagement/Contact/SaveNotes",
type: "GET",
dataType: "json",
data: { id: "1322dsa142d2131we2", content: self.noteContent() }
});
request.done(function (result, message) {
var mess = "";
var err = false;
var imgSrc = "";
if (message = "success") {
if (result.success) {
mess = "Successfully Updated";
imgSrc = "/images/tick.png";
self.allNotes.push({ date: new Date().toUTCString(), content: self.noteContent(), logged: self.user });
self.toNotes();
} else {
mess = "Server Error";
imgSrc = "/images/redcross.png";
err = true;
}
} else {
mess = "Ajax Client Error";
imgSrc = "/images/redcross.png";
err = true;
}
self.status(CRTBL.CreateMessageOutput(err, mess, imgSrc));
self.noteContent(null);
setTimeout(function () {
self.status(null);
}, 4000);
});
};
self.status = ko.observable();
self.characterCounter = ko.computed(function () {
return self.noteContent() == undefined ? 0 : self.noteContent().length;
});
};
var note = function (data) {
var self = this;
console.log(data.date);
self.date = CRTBL.FormatIsoDate(data.date);
self.content = data.content;
self.compressedContent = data.content == null ? "" : data.content.length < 25 ? data.content : data.content.substring(0, 25) + " ...";
self.logged = data.logged;
console.log(this);
};
ko.applyBindings(new notesViewModel());
When I first load the page it says:
Uncaught Error: Unable to parse bindings.
Message: ReferenceError: notes is not defined;
Bindings value: with: notes
However, I pass it null, so it shouldn't show anything, because when I do the function goToNote then do goToNotes it sets the notes observable to null
So why can't I start off with this null value?
The problem is where you have:
<div data-bind="with: notesViewModel">
That makes it look for a property "notesViewModel" within your notesViewModel, which does not exist.
If you only have one view model you can just remove that data binding and it will work fine.
If, however, you wish to apply your view model to just that div specifically and not the entire page, give it an ID or some other form of accessor, and add it as the second parameter in applyBindings, as follows:
HTML:
<div id="myDiv">
JS:
ko.applyBindings(new notesViewModel(), document.getElementById('myDiv'));
This is generally only necessary where you have multiple view models in the same page.
Like what bcmcfc has put, however, due to my scenario being a multi-viewModel scenario I don't think his solution is quite the right one.
In order to achieve the correct results, first of all I extrapolated out the self.notes = ko.observable(null); into a viewModel which makes doing the table binding far easier.
Then to fix the binding issues instead of setting an element for the bind to take place, I merely did this:
ko.applyBindings({
mainViewModel: new mainViewModel(),
notesViewModel: new notesViewModel()
});
In my original code I have two viewModels which is why I was getting this error. With this method the key is:
I don't create dependancies!
Instead of tieing the viewModel to a certain dom element which can change quite easily and cause having to go and changes things with ko, plus if I add more viewModels then it can get more complicated. I simply do:
data-bind="with: viewModel"
That way I can bind to any DOM object and I can have has many as I like.
This is the solution that solved my post.
Here is the jsfiddle