I got knockout to work before in MVC, but unfortunately I lost the code, and need help figuring it out.
I am trying to simply put an html page in the ~/wwwsource/ folder of my MVC project, and in that page I would like to demo a simple knockout example.
(Eventually, I actually want to use knockout inside MVC Views, using knockout right alongside Razor if possible but first I just would at least like to get a simple working example going, and extend from there.
I tried the following, which worked in JSFiddle but not in Visual Studio:
<script src="lib/knockout/dist/knockout.debug.js" type="text/javascript">
// Here's my data model
var ViewModel = function (first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.fullName = ko.pureComputed(function () {
// Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName.
return this.firstName() + " " + this.lastName();
}, this);
};
ko.applyBindings(new ViewModel("Planet", "Earth"));
</script>
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
You are calling the javascript before the html has been fully rendered. So when ko.applyBindingsis called the html as only partially loaded.
Easiest solution is to wrap the javascript in a document loaded callback using jQuery (which should exist because you're using knockout).
You also have some invalid script tag syntax. Need to close knockout script tag before starting a new one for the page.
<script src="lib/knockout/dist/knockout.debug.js" type="text/javascript">
</script>
<script type="text/javascript">
// Here's my data model
var ViewModel = function (first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.fullName = ko.pureComputed(function () {
// Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName.
return this.firstName() + " " + this.lastName();
}, this);
};
$(document).ready(function(){
ko.applyBindings(new ViewModel("Planet", "Earth"));
})
</script>
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
Related
I have a following situation.
I have a variable bOk into a HTML page. I'd like to update this variable every time a checkbox.checked property was changed.
I think the best way should be using jscript but I don't know how to start this.
That is the reason I did not put a code in this question.
Is there a way to somebody to help me?
Using the onclick event, you can get the state of the checkbox with jQuery whenever it is clicked.
document.getElementById("checkbox-id").onclick = function() {
var bOk = $("#checkbox-id").is(":checked")
}
document.getElementById("checkbox-id").onclick = function() {
var bOk = $("#checkbox-id").is(":checked")
alert(bOk);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="checkbox" id="checkbox-id"/>
<input id="chkok" type="checkbox" />Tick
#{var BOK = "Hi..";}
<input type="hidden" value=#BOK id="hdnbok">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#chkok').click(function ()
{
alert($("#hdnbok").val());
})
});
I've got a jQuery Accordion and each panel contains a form. All of the forms are the same, and the inputs share the same IDs, names, and data-bind attributes.
Assuming each form has a different binding context (using ko with:), this is how I would set up the Knockout.js ViewModel if there were two forms.
However, I don't know in advance how many forms there will be. I'm rendering a PartialView (which contains the form) for every form object in the MVC ViewModel's collection of forms.
#model ViewModel
<div class="container-fluid">
<div id="jQueryAccordion">
#foreach (var form in Model.AllForms.ToList())
{
<!-- ko with: items[#form.Key] -->
Html.RenderPartial("_Form", form);
<!-- /ko -->
}
// etc.
How would I set up the Knockout.js ViewModel if I don't know how many forms there are going to be?
I suggest that you can load your partial view dinamically via ajax call & bind data knockout binding accordingly as follows:
//C# XxxController: return partial view:
public ActionResult MyView()
{
return PartialView("_MyView");
}
//Ajax call to load partial view at client side:
$.get('Xxx/MyView', function(view){
_contentHolder.html(view);
ko.applyBinding(self, _contentHolder[0]);
})
You can loop through your model collection and apply knockout binding dynamically.
As Anh Bui suggested, I would create them dynamically in the browser. Using applyBindings with markup you created with ASP.net on the server-side is a bit of a hack and means your working against Knockout, not with it.
It would be better to let Knockout take care of actually creating the forms. This means
supplying it only the data it needs to create each form as JSON
creating a Knockout template for the form markup
looping over the data with the forEach binding
The template:
<script type="text/html" id="form-template">
<form action="/target-url">
<label for="user_name">What's your name?</label>
<input type="text" data-bind="value: user_name" name="user_name" />
<label for="user_location">Where are you from?</label>
<input type="text" data-bind="value: user_location" name="user_location" />
</form>
</script>
Next you output your relevant form data as a JSON array on the server side. I haven't used ASP.net, so I can only offer you pseudo-code here:
<script type="application/javascript">
window.form_json_from_server = "
#foreach (var form in Model.AllForms.ToList())
{
// .. ASP.net JSON output magic goes here
}
";
</script>
so that the end result in your markup looks like
<script type="application/javascript">
window.form_json_from_server = "[
{ user_name: "Foo1", user_location: "Bar1" },
{ user_name: "Foo2", user_location: "Bar2" },
{ user_name: "Foo3", user_location: "Bar3" }
]";
</script>
(note that JS strings cannot contain line breaks. I've formatted it here with line breaks for easier reading)
Now we have our form data formatted as JSON, saved in a Javascript string. Next up: your Knockout view model:
var ViewModel = function ViewModel() {
var that = this,
raw_forms_object;
// we reconstitute our JSON string into a Javascript object
raw_forms_object = JSON.parse(window.form_json_from_server);
// this is where the objects made from our JSON will end up in
this.forms = ko.observableArray([]);
ko.utils.arrayForEach(raw_forms_object, function(f) {
// f contains one of our form objects, such as { user_name: "Foo1", user_location: "Bar1" }
// instead of adding f directly to the array, we make a new object in which the
// properties are observables
var form = {
user_name: ko.observable(f.user_name),
user_location: ko.observable(f.user_location),
};
// add our new form object to our observableArray
// make sure to use 'that', because 'this' is the scope of the arrayForEach callback we're in
that.forms.push(form);
});
}
Now we have an observableArray called 'forms' on our view model with our form objects in it. We use the forEach binding to make as many forms as we have form objects:
<div data-bind="template: { name: 'form-template', foreach: forms }"></div>
All that's left is applying an instance of our view model to the page:
ko.applyBindings( new ViewModel() );
If you like, you can try it out in this runnable code snippet:
var ViewModel = function ViewModel() {
var that = this,
raw_forms_object;
// we reconstitute our JSON string into a Javascript object
raw_forms_object = JSON.parse(window.form_json_from_server);
// this is where the objects made from our JSON will end up in
this.forms = ko.observableArray([]);
ko.utils.arrayForEach(raw_forms_object, function(f) {
// f contains one of our form objects, such as
// { user_name: "Foo1", user_location: "Bar1" }
// instead of adding f directly to the array, we make a new object in which the
// properties are observables
var form = {
user_name: ko.observable(f.user_name),
user_location: ko.observable(f.user_location),
};
// add our new form object to our observableArray
// make sure to use 'that', because 'this' is the scope
// of the arrayForEach callback we're in
that.forms.push(form);
});
}
ko.applyBindings( new ViewModel() );
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script type="text/html" id="form-template">
<form action="/target-url">
<label for="user_name">What's your name?</label>
<input type="text" data-bind="value: user_name" name="user_name" />
<label for="user_location">Where are you from?</label>
<input type="text" data-bind="value: user_location" name="user_location" />
</form>
</script>
<div data-bind="template: { name: 'form-template', foreach: forms }"></div>
<script type="application/javascript">
window.form_json_from_server = '[{"user_name": "Foo1","user_location": "Bar1"},{"user_name": "Foo2","user_location": "Bar2"},{"user_name": "Foo3","user_location": "Bar3"}]';
</script>
I'm trying to dynamically create and filter a jquery mobile control group containing checkboxes using knockout binding. The basic idea is that the user selects an option which filters the list of checkboxes in the control group. I've seen similar questions on here but they all seem to be a one-time binding where once bound by ko and enhanced by jqm they remain unchanged. I have that behavior working, the issue occurs when the underlying viewModel changes and ko updates the list of checkboxes in the control group. A full demo of the behavior can be found on jsfiddle here: http://jsfiddle.net/hkrauss2/JAvLk/15/
I can see that the issue is due to jqm creating a wrapper div when enhancing the control group. Ko then puts new elements above the wrapper div when updating the DOM. Basically I'm asking if anyone has solved this issue and also if anyone thinks I'm asking for trouble by integrating these two libraries? Thanks to everyone in advance.
Here is the Html:
<div id="home" data-role="page">
<div data-role="header">
<h2>Knockout Test</h2>
</div>
<div data-role="content">
<ul id="parent-view" data-role="listview" data-inset="true" data-bind="foreach: parentCategories">
<li></li>
</ul>
<p>
To reproduce the issue select Restaurants, come back and select Nightlife or Bars
</p>
</div>
</div>
<div id="list" data-role="page">
<div data-role="header">
<h2>Knockout Test</h2>
<a data-rel="back" data-icon="carat-l" data-iconpos="notext">Back</a>
</div>
<div data-role="content">
<form>
<div id="child-view" data-role="controlgroup" data-bind="foreach: childCategories, jqmRefreshControlGroup: childCategories">
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}" />
<label data-bind="text: description, attr: {for: 'categoryId' + id}" />
</div>
</form>
</div>
</div>
And the basic javascript. Note there are two external js files not listed here. One sets $.mobile.autoInitializePage = false; on the mobileinit event. The other brings in data in the form of a JSON array which is used to initialize the Categories property in the AppViewModel.
// Custom binding to handle jqm refresh
ko.bindingHandlers.jqmRefreshControlGroup = {
update: function (element, valueAccessor) {
ko.utils.unwrapObservable(valueAccessor());
try {
$(element).controlgroup("refresh");
} catch (ex) { }
}
}
function GetView(name) {
return $(name).get(0);
}
// Define the AppViewModel
var AppViewModel = function () {
var self = this;
self.currentParentId = ko.observable(0);
self.Categories = ko.observableArray(Categories); // Categories comes from sampledata.js
self.parentCategories = ko.computed(function () {
return ko.utils.arrayFilter(self.Categories(), function (item) {
return item.parentId == 0;
});
});
self.childCategories = ko.computed(function () {
return ko.utils.arrayFilter(self.Categories(), function (item) {
return item.parentId == self.currentParentId();
});
});
self.OnClick = function (viewModel, $event) {
self.currentParentId(viewModel.id);
return true;
};
};
// Create the AppViewModel
var viewModel = new AppViewModel();
// Apply bindings and initialize jqm
$(function () {
ko.applyBindings(viewModel, GetView('#parent-view'));
ko.applyBindings(viewModel, GetView('#child-view'));
$.mobile.initializePage();
});
Update
My old solution wraps each element in a ui-controlgroup-controls div, which adds unnecessary markup. However, the enhancement part is essential.
$(element).enhanceWithin().controlgroup("refresh"); /* line 16 in fiddle */
The new solution is more dynamic to maintain clean markup with no additional wrappers:
First step: Once controlgroup is created controlgroupcreate (event), add data-bind to its' container .controlgroup("container")
Second step: Add checkbox consisted of input and label. At the same time, for each element, add data-bind
Third step: Apply bindings ko.applyBindings().
The static structure of the controlgroup should be basic, it shouldn't contain any elements statically. If a checkbox is added statically, each dynamically created checkbox will be wrapped in an additional .ui-checkbox div.
<div id="child-view" data-role="controlgroup">
<!-- nothing here -->
</div>
JS
$(document).on("controlgroupcreate", "#child-view", function (e) {
$(this)
.controlgroup("container")
.attr("data-bind", "foreach: childCategories, jqmRefreshControlGroup: childCategories")
.append($('<input type="checkbox" name="checkbox" />')
.attr("data-bind", "attr: {id: 'categoryId' + id}"))
.append($('<label />')
.attr("data-bind", "text: description, attr: {for: 'categoryId' + id}"));
ko.applyBindings(viewModel, GetView('#child-view'));
});
Demo
Old solution
As of of jQuery Mobile 1.4, items should be appended to .controlgroup("container") not directly to $("[data-role=controlgroup]").
First, you need to wrap inner elements of controlgroup in div with class ui-controlgroup-controls which acts as controlgroup container.
<div id="child-view" data-role="controlgroup" data-bind="foreach: childCategories, jqmRefreshControlGroup: childCategories">
<div class="ui-controlgroup-controls">
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}" />
<label data-bind="text: description, attr: {for: 'categoryId' + id}" />
</div>
</div>
Second step, you need to enhance elements inserted into controlgroup container, using .enhanceWithin().
$(element).enhanceWithin().controlgroup("refresh"); /* line 16 in fiddle */
Demo
Omar's answer above works very well. As he mentions in the comments however it does wrap each input/label combination in their own div. This doesn't seem to affect anything visually or functionally but there is another way as outlined below. Basically it uses the containerless control flow syntax to bind the list.
New Html
<div id="child-view" data-role="controlgroup">
<!-- ko foreach: childCategories, jqmRefreshControlGroup: childCategories, forElement: '#child-view' -->
<input type="checkbox" name="checkbox-v-2a" data-bind="attr: {id: 'categoryId' + id}"></input>
<label data-bind="text: description, attr: {for: 'categoryId' + id}"></label>
<!-- /ko -->
</div>
Using the containerless syntax means that we lose the reference to the controlgroup div in the custom binding handler. To help get that back I added the id as '#child-view' in a custom binding named forElement. The magic still all happens in the custom binding handler and Omar's enhanceWithin suggestion remains the secret ingredient. Note: I needed to change the argument list to include all arguments passed by ko.
ko.bindingHandlers.jqmRefreshControlGroup = {
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
ko.utils.unwrapObservable(valueAccessor());
try {
$(allBindings.get('forElement')).enhanceWithin().controlgroup("refresh");
} catch (ex) { }
}
}
Final note: To use a custom handler on a virtual element ko needs to be notified that it is ok. The following is the updated start up statements:
// Apply bindings and initialize jqm
$(function () {
ko.virtualElements.allowedBindings.jqmRefreshControlGroup = true; // This line added
ko.applyBindings(viewModel, GetView('#parent-view'));
ko.applyBindings(viewModel, GetView('#child-view'));
$.mobile.initializePage();
});
I am new to knockoutJS. I am working on an MVC application where I want to implement knockoutJS but the scenario is bit different.
I have a page where I am showing a list. I have 3 links on the page and on click of them I am adding partial views to page accordingly. What I want to do is that whenever I add values/data to partial views, the list which is on page should be updated with knockout. In other words I want to add value to observable array when I save data from partial view.
Please let me know if this is possible or I should keep it in jquery only.
Here is the code:
Main view:
<input type="button" value="Add Partial View" onclick="LoadPartial();" />
<div id="dvContent"></div>
<h4>People</h4>
<ul data-bind="foreach: people">
<li>
Name at position <span data-bind="text: $index"> </span>:
<span data-bind="text: name"> </span>
Remove
</li>
</ul>
<button data-bind="click: addPerson">Add</button>
<script src="~/Scripts/jquery-1.7.1.js"></script>
<script src="~/Scripts/knockout-2.1.0.js"></script>
<script>
function LoadPartial() {
$.ajax({
url: "/home/index",
dataType:"html",
type: "GET",
success: function (data) {
$("#dvContent").html(data);
}
});
}
</script>
<script>
function AppViewModel() {
var self = this;
self.people = ko.observableArray([
{ name: 'Bert' },
{ name: 'Charles' },
{ name: 'Denise' }
]);
self.addPerson = function () {
self.people.push({ name: "New at " + new Date() });
};
self.removePerson = function () {
self.people.remove(this);
}
}
ko.applyBindings(new AppViewModel());
</script>
Partial View:
<table>
<tr>
<td>Add new Row</td>
<td><input type="button" value="Add" data-bind="click: addPerson"/></td>
</tr>
</table>
Thanks,
JsHunjan
It is easy to accomplish with Knockout. You need to show some code that you have tried though if you want to get some help. I will post a general answer but it isn't going to fix your use case exactly, just basically -
Create an object to hold your new item, you can do this either in the parent or the child view model, but if you do it in the child you need to pass it back to the parent.
Once you hit a save button or add or whatever in the child view model just do a .push() into the observableArray that you created ex... - myObservableArray.push(newItem());
Knockout will recognize all of the changes taking place and perform the actions you want automatically.
Hope this helps.
Ok so this is beginning to drive me insane. I have for several hours now searched and searched, and every single solution doesnt work for me. So yes, this question might be redundant, but i cant for the life of me get solutions to work.
I have a bunch of checkboxes being generated by a jquery template that is databound via knockout.js. However, it turns up unstyled. Afaik, it is something about jquery mobile does the styling before knockout renderes the template, so it ends up unstyled.
I have tried numerous methods to no avail, so i hope someone here can see what i am doing wrong.
(i am using jquery mobile 1.2.0 , jquery 1.8.2 and knockout 2.2.1)
This is the scripts:
<script type="text/javascript">
jQuery.support.cors = true;
var dataFromServer = "";
// create ViewModel with Geography, name, email, frequency and jobtype
var ViewModel = {
email: ko.observable(""),
geographyList: ["Hovedstaden","Sjælland","Fyn + øer","Nordjylland","Midtjylland","Sønderjylland" ],
selectedGeographies: ko.observableArray(dataFromServer.split(",")),
frequencySelection: ko.observable("frequency"),
jobTypes: ["Kontor (administration, sekretær og reception)","Jura","HR, Ledelse, strategi og udvikling","Marketing, kommunikation og PR","Handel og service (butik, service, værtinde og piccoline)","IT","Grafik og design","Lager, chauffør, bud mv.","Økonomi, regnskab og finans","Kundeservice, telefoninterview, salg og telemarketing","Sprog","Øvrige jobtyper"],
selectedJobTypes: ko.observableArray(dataFromServer.split(",")),
workTimes: ["Fulltid","Deltid"],
selectedWorkTimes: ko.observableArray(dataFromServer.split(","))
};
// function for returning checkbox selection as comma separated list
ViewModel.selectedJobTypesDelimited = ko.dependentObservable(function () {
return this.selectedJobTypes().join(",");
}, ViewModel);
var API_URL = "/webapi/api/Subscriptions/";
// function used for parsing json message before sent
function omitKeys(obj, keys) {
var dup = {};
var key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (keys.indexOf(key) === -1) {
dup[key] = obj[key];
}
}
}
return dup;
}
//Function called for inserting new subscription record
function subscribe() {
if($("#jobmailForm").valid()=== true){
//window.alert("add subscriptiooncalled");
var mySubscription = ko.toJS(ViewModel);
//var json = JSON.stringify(mySubscription);
var jsonSmall = JSON.stringify(omitKeys(mySubscription, ['geographyList','jobTypes','selectedJobTypesDelimited','workTimes']));
//window.alert(jsonSmall);
$.ajax({
url: API_URL,
cache: false,
type: 'POST',
contentType: 'application/json',
data: jsonSmall,
success: function (data) {
window.alert("success");
},
error: function (error) {
window.alert("ERROR STATUS: " + error.status + " STATUS TEXT: " + error.statusText);
}
});
}
}
function initializeViewModel() {
// Get the post from the API
var self = this; //Declare observable which will be bind with UI
// Activates knockout.js
ko.applyBindings(ViewModel);
}
// Handle the DOM Ready (Finished Rendering the DOM)
$("#jobmail").live("pageinit", function() {
initializeViewModel();
$('#jobmailDiv').trigger('updatelayout');
});
</script>
<script id="geographyTmpl" type="text/html">
<input type="checkbox" data-role="none" data-bind="attr: { value: $data }, attr: { id: $data }, checked: $root.selectedGeographies" />
<label data-bind="attr: { for: $data }"><span data-bind="text: $data"></span></label>
</script>
<script id="jobTypeTmpl" type="text/html">
<label><input type="checkbox" data-role="none" data-bind="attr: { value: $data }, checked: $root.selectedJobTypes" /><span data-bind="text: $data"></span></label>
</script>
Note, "jobmail" is the surrounding "page" div element, not shown here. And this is the markup:
<div data-role="content">
<umbraco:Item field="bodyText" runat="server"></umbraco:Item>
<form id="jobmailForm" runat="server" data-ajax="false">
<div id="jobmailDiv">
<p>
<label for="email">Email</label>
<input type="text" name="email" id="email" class="required email" data-bind="'value': email" />
</p>
<fieldset data-role="controlgroup" data-mini="true" data-bind="template: { name: 'geographyTmpl', foreach: geographyList, templateOptions: { selections: selectedGeographies } }">
<input type="checkbox" id="lol" />
<label for="lol">fkfkufk</label>
</fieldset>
<fieldset data-role="controlgroup" data-mini="true">
<p data-bind="template: { name: 'jobTypeTmpl', foreach: jobTypes, templateOptions: { selections: selectedJobTypes } }"></p>
</fieldset>
<fieldset data-role="controlgroup" data-mini="true">
<input type="radio" id="frequency5" name="frequency" value="5" data-bind="checked: frequencySelection" /><label for="frequency5">Højst 5 gange om ugen</label>
<input type="radio" id="frequency3" name="frequency" value="3" data-bind="checked: frequencySelection" /><label for="frequency3">Højst 3 gange om ugen</label>
<input type="radio" id="frequency1" name="frequency" value="1" data-bind="checked: frequencySelection" /><label for="frequency1">Højst 1 gang om ugen</label>
</fieldset>
<p>
<input type="button" value="Tilmeld" class="nice small radius action button" onClick="subscribe();">
</p>
Tilbage
</div>
</form>
Alternate method of invoking the restyling (doesnt work either):
$(document).on('pagebeforeshow', '#jobmail', function(){
// Get the post from the API
var self = this; //Declare observable which will be bind with UI
// Activates knockout.js
ko.applyBindings(ViewModel);
});
// Handle the DOM Ready (Finished Rendering the DOM)
$("#jobmail").live("pageinit", function() {
$('#jobmail').trigger('pagecreate');
});
Use a custom binding (Knockout) to trigger jQuery Mobile to enhance the dynamically created content produced by Knockout.
Here is a simple custom binding:
ko.bindingHandlers.jqmEnhance = {
update: function (element, valueAccessor) {
// Get jQuery Mobile to enhance elements within this element
$(element).trigger("create");
}
};
Use the custom binding in your HTML like this, where myValue is the part of your view model that changes, triggering the dynamic content to be inserted into the DOM:
<div data-bind="jqmEnhance: myValue">
<span data-bind="text: someProperty"></span>
My Button
<input type="radio" id="my-id" name="my-name" value="1" data-bind="checked: someOtherProperty" /><label for="my-id">My Label</label>
</div>
In my own case, myValue was part of an expression in an if binding, which would trigger content to be added to the DOM.
<!-- ko if: myValue -->
<span data-bind="jqmEnhance: myValue">
<!-- My content with data-bind attributes -->
</span>
<!-- /ko -->
Every dynamically generated jQuery Mobile content must be manually enhanced.
It can be done in few ways, but most common one can be done through the jQuery Mobile function .trigger( .
Example:
Enhance only page content
$('#page-id').trigger('create');
Enhance full page (header + content + footer):
$('#page-id').trigger('pagecreate');
If you want to find more about this topic take a look my other ARTICLE, to be more transparent it is my personal blog. Or find it HERE.