I'm rather new to JavaScript but I am learning..
But now I am stuck, I made this JavaScript Except I get Syntax error
$(function () {
$(function () {
$('#date').datepicker({
showButtonPanel: true,
changeMonth: true,
changeYear: true,
dateFormat: "yy-mm-dd",
firstDay: 1,
onSelect: function (dateText) {
$('#EndDate').datepicker('option', 'minDate', new Date(dateText));
}
});
});
});
if (!((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i)))) {
$(function () {
$("#startTime").timePicker({
step: 15,
endTime: "23:45"
});
});
$(function () {
$("#endTime").timePicker({
step: 15,
endTime: "23:45"
});
});
$(function () {
$("#breakTime").timePicker({
step: 5,
endTime: "03:00"
});
});
}
function SetDate(dt) {
$('#date').val(dt);
}
var n = #(Model.Projects.Count);
function AddProject() {
n++;
$.ajax({
type: "GET",
url: "#(Url.Action("Project"))/" + n + "/?showDescription=false",
dataType: "html",
success: function(data) {
$('#projects').append(data);
}
});
}
It is on my AddProject function I get my error, how ever I can't see what could be wrong?
And this is the view I want to use the script on.
#if (ViewData["posted"] != null)
{
<div class="alert alert-success">
<strong>Inställningar sparade</strong>
</div>
}
#using (Html.BeginForm("Settings", "Reports", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="portlet light">
<div class="portlet-title">
<div class="caption">
<span class="caption-subject font-green-sharp bold uppercase">Inställngar</span>
</div>
</div>
<table class="table table-striped table-bordered table-hover">
<tr>
<td>Starttid:</td>
<td>#Html.TextBox("startTime", Model.Times.StartTime)</td>
</tr>
<tr>
<td>Sluttid:</td>
<td>#Html.TextBox("endTime", Model.Times.EndTime)</td>
</tr>
<tr>
<td>Rastlängd:</td>
<td>#Html.TextBox("breakTime", Model.Times.BreakTime)</td>
</tr>
</table>
</div>
<div id="projects">
#foreach (var data in Model.Projects)
{
Html.RenderPartial("Project", data, ViewData["vd"] as ViewDataDictionary);
var viewDataDictionary = ViewData["vd"] as ViewDataDictionary;
if (viewDataDictionary != null)
{
viewDataDictionary["id"] = (int)viewDataDictionary["id"] + 1;
}
}
</div>
Lägg till projekt
<button type="submit" class="btn btn-primary">Spara</button>
}
</div>
</div>
</div>
</div>
</div>
</div>
Javascript files do not support Razor compilation. Only views do that. You cannot have Razor in separate JS files.
Instead inject any required information into the view (e.g. into the elements as data- or other attributes) and have your JS pick up those values from the elements (or from a few global vars you set in the view).
I will add some examples for you...
Example HTML (attribute injection):
<a id="addProject" href="#(Url.Action("Project") + "/{id}/?showDescription=false")" class="btn btn-primary">Lägg till projekt</a>
JQuery
$('#addProject').click(function(){
var url = $(this).attr('href');
url = url.replace("{id}", myNewId);
$.ajax({
url: url
...
});
});
I strongly recommend never using inline event handlers (like onclick="") with jQuery as the separate the event code from the registration aking maintenance harder and do not have all the same features as jQuery event handling.
Example HTML (global var injection)
Place this code snippet in the view
<script type="text/javascript>
var projectCount = #(Model.Projects.Count);
// or
window.projectCount = #(Model.Projects.Count);
</script>
Then you can access projectCount from other JS files with:
var count = projectCount + 1;
or
var count = window.projectCount + 1;
Related
Say I am using an
#Html.TetboxFor(m => m.FirstName, new { data_bind= "value:first_name" })
which i am populating from my MVC viewmodel and i want to pass that value to my knockoutJs view model. I have seen the suggestion of creating a javascript object from my viewmodel. I have been able to successfully get the value by telling the viewmodel that
self.first_name = ko.observable($("#FirstName").val())
I tried using the 'text' binding which renders my data from the mvc viewmodel but does not return the data in the knockout view model and the 'value' binding clears my Html helper but does return the data back to the knockout view model. I also tried to use the 'textinput' binding. Is there a better way than what i am using or is the main way of doing this to populate the knockout view model directly from the server? Thanks
I know I will be marked down for giving you an alternate answer but that is ok because it is worth it to share a different way of solving your problem, that is simpler and more powerful.
If you do not mind a suggestion, stay away from ASP.NET MVC Razor templating, it is not powerful, it is tightly coupling your backend to your front in not much of a different way than ASP.NET Web Forms does.
Here as example of the power of KnockoutJs with ASP.NET MVC, but no Razor for binding Model data to View elements, knockoutjs is used entirely instead. A key take away is that the C# Model is built dynamically in KnockoutJs, using knockout.mapping.js. So when you add a new property to your C# model it is automatically available in your Knockout ViewModel.
Here is a snippet of a .cshtml View using no Razor syntax for the form/input model binding:
#using System.Activities.Statements
#using System.Web.Optimization
#model Mvc.Models.ProjectModel
#{
ViewBag.Title = "Start New Project";
Layout = "~/Views/Shared/_BuildLayout.cshtml";
}
#section sideMenuCustomContent{
#Html.Partial("_PartialSidebarPricing")
}
#using (Html.BeginForm("StartSubmit", "Start", null, FormMethod.Post, new { #role = "form", id = "formStart", enctype = "multipart/form-data" }))
{
<input type="hidden" value="#Url.RouteUrl("UploadStep")" data-bind="valueWithInit: 'UrlWithIdUploadStep'" />
<input type="hidden" value="#Url.Action("GetProjectModel", "Start")" data-bind="valueWithInit: 'UrlRootGetProjectModel'" />
<input type="hidden" value="#Url.Action("StyleInfo", "Start")" data-bind="valueWithInit: 'UrlRootStyleInfo'" />
<input type="hidden" value="#Url.Action("GetCoverColorSwatchUrl", "Asset")" data-bind="valueWithInit: 'UrlRootGetCoverColorImage'" />
<div class="row">
<div class="col-lg-6 col-md-6 featuredTextContainer">
<div class="style-hero" >
<h1 class="style-title" data-bind="text: Style.StyleName"></h1>
<div class="style-start-price" >Starting at: <span data-bind="text: Application.FormatCurrency(BookBasePrice())"></span></div>
<div class="style-caption" >
<p data-bind="text: Style.DisplayText" ></p>
</div>
</div>
</div>
<div id="book-options-right-column" class="col-lg-4 col-md-6 build-right-column well-black">
<div class="form-group">
<input id="tbxPrjName" type="text" placeholder="Project Name" data-bind="value: Name" class="couture-input-underline couture-text-white placeholder-center text-center" />
</div>
<div class="form-group">
<input style="width: 80%;" type="text" class="couture-input-underline couture-text-white placeholder-center text-center" placeholder="Estimated Page Count" data-bind="value: PageCountEstimated, valueUpdate: 'afterkeydown'" />
<a data-toggle="popover"
class="popover-250w"
data-placement="bottom"
data-content="Estimate just to help you with pricing, exact page count will be tracked for you. Click the Hamburger Menu in left corner to see current pricing."
data-title="Estimated Page Count">
<i class="glyphicon glyphicon-question-sign"></i>
</a>
</div>
<input type="button" id="stampYes" data-bind="click: OnTextStampingYes, css: { 'selected': TextStamping }" class="btn-couture btn-couture-transparent btn-round" value="Y" />
<input type="button" id="stampNo" data-bind="click: OnTextStampingNo, css: { 'selected': !TextStamping() }" class="btn-couture btn-couture-transparent btn-round" value="N">
</div>
<div class="form-group" data-bind="visible: Style.HasBackCoverOption">
<label>Back Cover Image</label>
<a data-toggle="popover"
class="popover-250w"
data-placement="bottom"
data-content="You’ll be able to upload your back cover image on the next page."
data-title="Back Cover Image">
<i class="glyphicon glyphicon-question-sign"></i>
</a>
<br />
<span>
<input type="button" id="BackCoverYes" data-bind="click: onBackCoverChange, css: { 'selected': CoverImageBack }" class="btn-couture btn-couture-transparent btn-round" value="Y" />
<input type="button" id="BackCoverNo" data-bind="click: onBackCoverChangeNo, css: { 'selected': !CoverImageBack() }" class="btn-couture btn-couture-transparent btn-round" value="N" />
</span>
</div>
</div>
</div>
<div class="form-group">
<div data-bind="visible: !PricingPartialShowFinalTotals()">
<button type="button" class="btn-couture btn-couture-transparent btn-couture-white btn-couture-large" data-bind="text: PricingPartialSubmitBtnText, disable: PricingPartialSubmitLock, click: formSubmit"></button>
</div>
</div>
</div>
</div>
<div class="form-group">
<div data-bind="visible: !PricingPartialShowFinalTotals()">
<button type="button" class="btn-couture btn-couture-transparent btn-couture-white btn-couture-large" data-bind="text: PricingPartialSubmitBtnText, disable: PricingPartialSubmitLock, click: formSubmit"></button>
</div>
</div>
}#*using (Html.BeginForm())*#
#section scripts {
#Scripts.Render("~/bundles/knockout")
#*<script src="/Scripts/knockout.mapping.updateData.js"></script>*#
#Scripts.Render("~/bundles/knockout/build/start")
<script id="document-ready">
var viewModel = {};
$(function() {
pageInit();
}); //document.ready
</script>
<script id="ko-bind-page-init">
function pageInit() {
viewModel = ko.mapping.fromJS(#Html.Raw(Json.Encode(Model)), BuildStartViewModelMapping);
ko.applyBindings(viewModel);
viewModel.errors.showAllMessages(false); //ko.validation init
}
</script>
}
And here is part of knockout.mapping.js (~/bundles/knockout/build/start):
var viewModel = {};
var BuildStartViewModel = function (data) {
ko.mapping.fromJS(data, BuildStartViewModelMapping, this);
};
var BuildStartViewModelMapping = {
//'ignore': ['CoverColorName', 'PaperName', 'CoverColorId'],
create: function (options) {
var self = new BuildStartViewModel(options.data);
ko.BaseViewModel.call(self);
ko.BuildCommonViewModel.call(self);
self.IsStartView(true);
/*=================================================*/
//Custom observables not in data
/*=================================================*/
self.AdditionalTextStampingYes = ko.observable(false);
self.OriginalProjectName = ko.observable(self.Name());
/*=================================================*/
self.BuildStartBgClassGet = ko.computed(function () {
var styleName = self.Style.StyleName().toLowerCase();
return 'build-start-bg-' + styleName;
});
self.PageCountEstimated.subscribe(updatePageCount);
function updatePageCount() {
if (validatePageCountEstimated(self.PageCountEstimated())) {
self.PageCount(self.PageCountEstimated());
}
}
/*=================================================*/
//New Style Selected
/*=================================================*/
self.Style.StyleId.subscribe(onStyleIdNew);
function onStyleIdNew() {
if (!self.Style.StyleId())
return;
Application.LoadingDisplayToggle();
var url = $.concatUrl(viewModel.UrlRootGetProjectModel(), viewModel.Style.StyleId());
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
async: true,
success: function (data) {
if (!data)
return;
ko.mapping.fromJS(data, BuildStartViewModelMapping, viewModel);
viewModel.errors.showAllMessages(false); //ko.validation init
//Refresh/Update Style Image
var $styleImage = $('#imgDisplayImage');
$styleImage.attr('src', '');
$styleImage.attr('src', data.Style.DisplayImage);
$('#hdnimage').val(data.Style.DisplayImage);
var $body = $('body');
console.log('TODO: UnComment When we have style images');
//$body.removeClass();
//$body.addClass('cover ' + self.BuildStartBgClassGet());
Application.LoadingDisplayToggle();
},
error: function () {
Application.LoadingDisplayToggle();
}
});
}
/*=================================================*/
self.formSubmit = saveData;
return self;
/*=================================================*/
//saveData/submit
/*=================================================*/
function saveData(data, event) {
//self.Name.isValidating.subscribe(function (isValidating) {
// if (!isValidating && self.errors().length > 0) {
// self.errors.showAllMessages(true);
// return;
// }
//});
//if (self.isValidating()) {
// setTimeout(function () {
// saveData(data, event);
// }, 50);
// return false;
//}
// ko.validation check if valid
if (self.errors().length > 0) {
self.errors.showAllMessages(true);
return;
}
var $form = $(event.currentTarget).closest('form');
if ($form.valid() && self.errors().length === 0) {
var copy = ko.toJS(this);
delete copy.errors;
delete copy.__ko_mapping__;
$.ajax({
url: $form.attr('action'),
type: 'POST',
dataType: 'json',
data: ko.toJSON(copy),
contentType: 'application/json; charset=utf-8',
success: function (response) {
var url = self.UrlWithIdUploadStep();
url = url.substr(0, url.lastIndexOf("/")); //remove id (could be StyleId and not ProjectId if new project)
url = $.concatUrl(url, response); //add project id to url, could be existing or new ProjectId
window.location.href = url;
},
beforeSend: function () {
self.PricingPartialSubmitLock(true);
Application.LoadingDisplayToggle();
},
error: function (result, error, errorThrown) {
self.PricingPartialSubmitLock(false);
Application.LoadingDisplayToggle();
Application.ToastrError(result.responseText);
}
});
}
return false;
};
}
};
And here is Scripts/knockout.mapping.updateData.js, I do not think you will need it to get started with this pattern but I included just in case:
/*
* Extension to the knockoutjs mapping plugin
* http://github.com/janhartigan/knockout-mapping-updatedata
* Requires KnockoutJS and the mapping plugin
*
* Dual licensed under the MIT or GPL Version 2 licenses.
* Jan Hartigan
*/
(function () {
/**
* A function that lets you "update from js" without overriding all the view model properties and methods. You just need to supply
* the viewModel, the original JS model on which you based your data (typically what you'd use in the mapping fromJS method), and the new JS
* object that has the updated information.
*
* #param Object viewModel
* #param Object dataModel
* #param Object jsObject
*
* #return Object (returns the viewModel)
*/
ko.mapping.updateData = function(viewModel, dataModel, jsObject) {
if (arguments.length < 3) throw new Error("When calling ko.updateData, pass: the view model, the data model, and the updated data.");
if (!viewModel) throw new Error("The view model is undefined.");
for (var i in dataModel) {
if (i in jsObject && i in viewModel && typeof dataModel[i] != 'function') {
viewModel[i](jsObject[i]);
}
}
return viewModel;
}
ko.exportSymbol('mapping.updateData', ko.mapping.updateData);
})();
Please, look into my code. I would like to avoid opening a new window in due to watching results. I would like to avoid showing results instead of current content in the same window.
I have thought about using AJAX to show results in Dialog window (JQueryUI)
https://jqueryui.com/dialog/
but I have no idea how? Thank You in advance :)
HTML: result_browser.html
<form class="form" method="post" id="resultbrowserform">
<!--Something-->
<button type="submit" class="btn btn-default"> Apply </button>
</form>
<div id="dialog" title="Details">
<p></p>
</div>
JS:
$(function () { function serializeForm(form){...}
...
$('#resultbrowserform').submit(function () {
$.ajax({
type: 'POST',
url: '/result_browser/data/',
data: serializeForm($('#resultbrowserform')),
success: function (data, status, jqXHR) {
$('#resultbrowsertablecontainer').remove();
data = data.data;
if (!data.length) {
window.alert('No data for selected time period');
return;
}
div = $('<div id="resultbrowsertablecontainer">');
table = $('<table class="table table-striped">');
header = $('<thead>');
header.append($('<tr>')
.append( $('<th>').html('Die Id'))
.append( $('<th>').html('Application'))
.append( $('<th>').html('Result'))
table.append(header);
body = $('<tbody>');
for (i = 0; i < data.length; i++) {
row = $('<tr>');
row.append( $('<td>').html(data[i].die_id));
row.append( $('<td>').html(data[i].application));
row.append( $('<td>').html(data[i].result));
<!--I would like to avoid opening a new window-->
link = $('<a>').attr('target', '_blank').attr('href', '/result_details/' + data[i].result_id + '/').html('Details');
row.append($('<td>').append(link));
body.append(row);
}
table.append(body);
div.append(table);
$('#resultbrowsercontainer').append(div);
}
});
return false;
});
});
Simply remove the target attribute and then it will open inside itself:
<!--I would like to avoid opening a new window-->
link = $('<a>').attr('href', '/result_details/' + data[i].result_id + '/').html('Details');
I have multiple google captchas on page. Code:
<script>
var qqqq =function(captcha_response){
console.log('?')
}
var CaptchaCallback = function(){
$('.g-recaptcha').each(function(index, el) {
grecaptcha.render(el, {'sitekey' : '{{ recaptcha_key}}', 'callback': 'qqqq', 'theme': 'dark'});
});
};
</script>
<script src='https://www.google.com/recaptcha/api.js?onload=CaptchaCallback&render=explicit' async defer></script>
On the page there are several blocks for reCAPTCHA:
<div class="g-recaptcha"></div>
All reCAPTCHA's render well, all with dark theme, all do verifying work, but the callback function does not get called.
When I tried single reCAPTCHA with data-callback attribute, it worked well.
I was facing the same issue. After checking the documentation again I found my problem. Try to remove the single quotation marks around your function name.
Like this:
<script>
var qqqq =function(captcha_response){
console.log('?')
}
var CaptchaCallback = function(){
$('.g-recaptcha').each(function(index, el) {
grecaptcha.render(el, {'sitekey' : '{{ recaptcha_key}}', 'callback': qqqq });
});
};
</script>
Maybe this helps someone else as well :)
Steps to implement multimple recaptcha with a callback method for disable the submit button
1) Add the reference
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
2) Add the Code that will render the captcha widget
<script>
var CaptchaCallback = function () {
grecaptcha.render('RecaptchaField1', { 'sitekey': 'xxx', callback: 'recaptchaCallback1'});
grecaptcha.render('RecaptchaField2', { 'sitekey': 'xxx', callback: 'recaptchaCallback2' });
grecaptcha.render('RecaptchaField3', { 'sitekey': 'xxx', callback: 'recaptchaCallback3' });
};
</script>
3) Add the method to remove the disable property on the submit button
$('#GetHelpButton').prop('disabled', true);
function recaptchaCallback1()
{
$('#GetHelpButton').prop('disabled', false);
}
4) Add the widget inside a form
<form id="formContact" method="post" class="login-form margin-clear" action="Landing/SendContactForm">
<div class="form-group has-feedback">
<label class="control-label">Correo electronico</label>
<input name="EmailContact" value="" id="EmailContact" type="text" class="form-control" placeholder="">
<i class="fa fa-envelope form-control-feedback"></i>
</div>
<div id="RecaptchaField1"></div>
<button id="GetHelpButton" type="submit" data-sitekey="xxxx" class="btn btn-info col-md-12 g-recaptcha">Send</button>
Try setting a unique ID for each div and then use a for loop to iterate through them. Something like this:
window.onloadCallback = function() {
var captcha = ['recaptcha1' ,'recaptcha2', 'recaptcha3']; //put your IDs here
for(var x = 0; x < captcha.length; x++){
if($('#'+captcha[x]).length){
grecaptcha.render(captcha[x], {
'sitekey' : '{{ recaptcha_key}}',
'theme' : 'dark',
'callback': function() {
console.log("Woof");
}
});
}
}
};
It might be over kill but this works. I copied the first variable in its entirety and and gave it a new name. Also, delinked it from call_files/header.html and css/header.css files. Instead, I linked it to the layout.css which governs the appearance of the second menu. I'm sure there is a way to stream line it.
<script type="text/javascript">
$().ready(function () {
var stickyPanelOptions = {
topPadding: 0,
afterDetachCSSClass: "BoxGlow_Grey2",
savePanelSpace: true,
onDetached: function (detachedPanel, spacerPanel) {
call_files/header.html(call_files/header.html() + " has been detached!");
css/header.css("background-color", "#1000ff");
},
onReAttached: function (detachedPanel) {
call_files/header.html(call_files/header.html().replace(" has been detached!", ""));
},
parentSelector: null
};
var secondstickyPanelOptions = {
topPadding: 150,
savePanelSpace: true,
onDetached: function (detachedPanel, spacerPanel) {
detachedPanel.html(detachedPanel.html() + " has been detached!");
layout.css("background-color", "#ffffff");
},
onReAttached: function (detachedPanel) {
detachedPanel.html(detachedPanel.html().replace(" has been detached!", ""));
},
parentSelector: null
};
// multiple panel example (you could also use the class ".stickypanel" to select both)
$("#Panel1").stickyPanel(stickyPanelOptions);
$("#Panel2").stickyPanel(secondstickyPanelOptions);
$("#Panel3").stickyPanel(stickyPanelOptions);
$("#UnstickPanel3").click(function () {
$("#Panel3").stickyPanel("unstick");
});
stickyPanelOptions.parentSelector = "#AbsoluteDiv";
$("#Panel4").stickyPanel(stickyPanelOptions);
stickyPanelOptions.parentSelector = "#NormalDiv";
$("#Panel5").stickyPanel(stickyPanelOptions);
});
</script>
HTML
<div id="Panel1" class="stickyPanel">
<div class="headerBGdiv"> </div>
<?php include('../call_files/pagesheader.html');?>
</div>
<div id="Panel2" class="stickyPanel">
<div id="navbar2">
<ul id="tabs">
<li>Buyer's Guide</li>
<li>Export Restrictions</li>
<li>Infrared 101</li>
<li>White Pages</li>
<li>Terminology</li>
<li>Newsroom</li>
<li>Gallery</li>
</ul>
</div>
</div>
Try This it uses separate config options for each panel.
<script type="text/javascript">
$().ready(function () {
var stickyPanelOptions = {
topPadding: 0,
savePanelSpace: true,
onDetached: function (detachedPanel, spacerPanel) {
call_files/header.html(call_files/header.html() + " has been detached!");
css/header.css("background-color", "#1000ff");
},
secondStickyPanelOptions = stickyPanelOptions,
onReAttached: function (detachedPanel) {
call_files/header.html(call_files/header.html().replace(" has been detached!", ""));
},
parentSelector: null
};
secondStickyPanelOptions.topPadding = 100;
// multiple panel example (you could also use the class ".stickypanel" to select both)
$("#Panel1").stickyPanel(stickyPanelOptions);
$("#Panel2").stickyPanel(secondStickyPanelOptions);
$("#Panel3").stickyPanel(stickyPanelOptions);
$("#UnstickPanel3").click(function () {
$("#Panel3").stickyPanel("unstick");
});
stickyPanelOptions.parentSelector = "#AbsoluteDiv";
$("#Panel4").stickyPanel(stickyPanelOptions);
stickyPanelOptions.parentSelector = "#NormalDiv";
$("#Panel5").stickyPanel(stickyPanelOptions);
});
</script>
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