Updating an element in a Knockout JS Observable Array - c#

I've been reading this site and finally have a question that wasn't already answered specifically enough for my needs, so here goes.
I have an observable array created from a data array passed to my KO view model in a C# application.
self.Stuff = ko.observableArray(data.Stuff);
This array doesn't have observable elements and there lies my problem. I need to edit an element (status) via an onclick. I know I need to either make the elements observable (not sure how with the data.Stuff portion) or do a "valueHasMutated" but I'm not entirely sure how that syntax would work.
Of course, my push and remove work fine since they trigger the arrayobservable and refresh the view.
if ($form.valid() && isValidStuff) {
self.Stuff.push({ ABC: self.ABCInput(), XYZ: self.XYZInput(), Status: self.StatusInput()});
self.resetValues();
}
self.removeStuff = function () {
self.Stuff.remove(this);
};
self.StuffStatusChng= function (){
//What to do?
self.Stuff.vauleHasMutated();
};
Any help or push in the right direction would be a great help, thanks! If I don't have enough info, please let me know what I can provide.
Thanks,

How about using the knockout mapping plugin:
http://knockoutjs.com/documentation/plugins-mapping.html
It has a fromJSON method that takes an array of json strings and makes an observable array with observable property for each property:
A fiddle can be found here: http://jsfiddle.net/jiggle/uzn7Z/, notice once bound, you can update the first name and it will update the property accordingly
HTML:
<div data-bind="foreach: people">
<div>
<input type="text" data-bind="value:firstName"/>
<span data-bind="text: firstName"></span>
<span data-bind="text: lastName"></span>
</div>
</div>
Code:
var stuff = '[{"firstName":"fred","lastName":"bloggs"},{"firstName":"david","lastName":"frost"}]';
console.log(stuff);
var people = ko.mapping.fromJSON(stuff);
console.log(people());
var viewModel ={};
viewModel.people=people;
ko.applyBindings(viewModel);
Just need to download and include knockout.mapping.js.

Does this help you?
var OneStuff = function (data) {
this.ABC = ko.observable(data.ABC);
this.XYZ = ko.observable(data.XYZ);
this.Status = ko.observable(data.Status);
};
self.Stuff = ko.observableArray(ko.utils.arrayMap(data.Stuff, function (oneStuffData) {
return new OneStuff(oneStuffData);
}));
This will make the individual properties observable.
To push:
self.Stuff.push(new OneStuff({ ABC: self.ABCInput(), XYZ: self.XYZInput(), Status: self.StatusInput()}));

Related

Pass list of object from View to controller using HttpPost

I am struggling a bit with passing list of object to C# code from view. If I pass a simple string it works fine. but its not working for List. So I am sure I am missing something here.
View
<div class="row control-actions">
#{
List<MyObject> test = ViewBag.ObjectsList;
<button type="button" class="btn btn-primary btn-wide" onclick="addAllObjectsToExp('#test')">Add All</button>
}
</div>
<script type="text/javascript">
function addAllObjectsToExp(objList) {
$.post('#Url.Action("AddAllObjectsToExp", "ExportObjects")', { objectsList: objList},
function (result) {
$('#numbers').html(result);
});
}
</script>
Code
[HttpPost]
[OutputCache(Location = System.Web.UI.OutputCacheLocation.None, NoStore = false, Duration = 0)]
public int AddAllObjectsToExp(List<MyObject> objectsList)
{
foreach(MyObject obj in objectList)
{
//Do something here
}
//and return an integer
}
While debugging I can see that the #test variable is getting populated with the list of MyObject. But when I reach the code side its always null.
Please let me know if i am missing something here. Also tell me if more information is needed.
You're passing a C# object into a Javascript function. It doesn't know how to read that. You should serialize it into JSON before passing it in.
If you use Json.NET you can do it by
ViewBag.ObjectsList = JsonConvert.SerializeObject(yourlist);
Then you can continue as you were.
Some notes:
You should try to start using ViewModels instead of putting things in the ViewBag. On the Javascript side you should bind event handlers for things like clicking instead of using onclick as it would make your code much more manageable and reusable.

update viewmodel in knockout and update the UI

below is my javascript that loads the model and i binded it to a dropdown and a table to show data. which works and shows the data.
var _observableViewModel = null;
$(document).ready(function () {
var jsonModel = '#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(this.Model))';
_observableViewModel = ko.mapping.fromJSON(jsonModel);
ko.applyBindings(_observableViewModel);
});
once a user selects the an item from the dropdownlist i am calling a ajax function that returns jsonresult and want to update the viewmodel and update the table as well. i tried something below but no effect.
i am using mvc.
thanks for the help!
function GetData() {
$.getJSON("/Home/Test", function (data) {
ko.mapping.updateModel(data);
})
since you are already using ko.mapping you really should use the mapping overload specifying a target :
ko.mapping.fromJSON(jsonModel, {}, _observableViewModel)
It will update the observable as before, as well as calling the observable valueWillMutate / valueHasMutated methods to update the UI.

Collection iteration in javascript using inline server tags

I have an asp.net user control which exposes a public IEnumerable object. This could be converted to a list or array if it helps to answer the question. What I would like to do is to loop through all of the server objects within a javascript function and do something with the contents of each item. I would like to achieve this using inline server tags if possible. Something like the below.
function iterateServerCollection()
{
foreach(<%=PublicCollection %>)
{
var somevalue = <%=PublicCollection.Current.SomeValue %>;
}
}
Is it possible to achieve this?
Managed to achieve what I wanted thanks to the comment from geedubb. Here's what the working javascript looks like.
var myCollection = <%= new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(myCollection) %>;
for(var i in myCollection)
{
var somevalue = myCollection[i].SomeValue;
}
I would do as others have suggested and serialize the object, you could even use an ajax call to grab the data from the start of that function
function iterateServerCollection()
{
//Ajax call to get data from server HttpHandler/WebAPI/Service etc.
for(var item in resultObject) {
//you can use item.WhateverPropertyIsOnObject
}
}

Using jquery templating with c#'s dynamics?

I didn't think this would be that hard, but I am trying to pass a class that has a dynamic for a property (which is currently being set as an expando object in the c#) into the View. In this view, a good bit is getting rendered w/ some jQuery Templating and I thought that if I had this in the c# code:
public dynamic SomeProperty {get;set;}
...
SomeProperty = new ExpandoObject();
SomeProperty.SomeValue = "5";
return View(TheClassThatContainsSomeProperty);
Such that in the jquery templating I could do:
${SomeProperty.SomeValue}
...and that it would hopefully work. It doesn't... If you inspect the response, you can see that it essentially gets sent over as a dictionary:
SomeProperty: [{SomeValue, Value:5}]
0: {Key:SomeValue, Value:5}
which leads to (I guess) my next question: Is there an easy way to access dictionaries in jquery templating? I did try this:
${SomeProperty["SomeValue"]}
to no avail either. At this point the only thing I know to do is to leverage the ability to put a function in the template (as copied here from the jquery website):
Template:
<tr><td>${getLanguages(Languages, " - ")}</td></tr>
Code:
function getLanguages( data, separator ) {
return data.join( separator );
}
So am I over complicating this? Can I easily either 1) access a dynamic value from jquery template or 2) Easily lookup a value from a dictionary in jquery template?
ExpandoObject derives from IEnumerable<KeyValuePair<string, Object>>, and most serializers will recognize a dynamic as this type when you assign an ExpandoObject. This is why you see an array type on the javascript side with named Key::Value pairs.
ExpandoObject Class (System.Dynamic) # MSDN
One alternative to using ExpandoObject is to use C# anonymous types. When serialized to json, these map field by field as you expect.
Anonymous Types (C# Programming Guide) # MSDN
It is possible to access values declared with dynamic from jQuery, but most likely you won't be returning a MVC View() with the model to be consumed with jQuery, as any server-side view template engine (razor, etc.) can already perform the same template activities with less overhead. Instead, jQuery templates are better used with Ajax calls.
Here are code examples demonstrating three cases where a variable declared dynamic on the server is consumed with jQuery templates in the browser.
The first example uses an anonymous type for the member field SomeValue, and has a jQuery template that treats it as a member object.
The second example uses an array of anonymous types for the member field SomeValue and has a template that uses {{each}} syntax to enumerate the items. Note that this is a scenario where things can go badly with dynamic, as you get no strongly-typed support and must either know the correct type or discover it at the time you access it.
The third example uses an ExpandoObject for the member field SomeValue, and has a jQuery template like the first example (single member object). Note that in this case, we need to use a helper function pivotDictionaryMap() to pivot Key::Value pairs into object members.
Starting with a blank C# MVC3 Web Project, we need to modify three files to demonstrate these examples.
Inside _Layout.cshtml, add script includes for jQuery templates and a proper version of jQuery in your <head> element.
<script src="#Url.Content("~/Scripts/jquery-1.8.2.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.tmpl.js")" type="text/javascript"></script>
Inside HomeController.cs, we'll add some methods that return json ActionResults. Also, for brevity we'll just declare a class SomeModelType here; and note that a proper application would probably have this class declared in its Models.
using System.Dynamic; // up top...
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult SomeDataSource(int id)
{
dynamic d = new { innerId = 99, innerLabel = "inside object" };
SomeModelType obj = new SomeModelType(id, "new object");
obj.SomeValue = d;
return Json(obj, "text/plain");
}
public ActionResult SomeDataSourceWithArray(int id)
{
dynamic d1 = new { innerId = 99, innerLabel = "inside object (first array member)" };
dynamic d2 = new { innerId = 100, innerLabel = "inside object (second array member)" };
SomeModelType obj = new SomeModelType(id, "new object");
obj.SomeValue = new object[] {d1, d2};
return Json(obj, "text/plain");
}
public ActionResult SomeDataSourceWithExpando(int id)
{
dynamic d = new ExpandoObject();
d.innerId = 99;
d.innerLabel = "inside object";
SomeModelType obj = new SomeModelType(id, "new object");
obj.SomeValue = d;
return Json(obj, "text/plain");
}
}
public class SomeModelType
{
public SomeModelType(int initId, string initLabel)
{
Id = initId;
Label = initLabel;
}
public int Id { get; set; }
public string Label { get; set; }
public dynamic SomeValue { get; set; }
}
Finally, in the default view, we will add script tags for the templates and the javascript necessary to consume them. Note the use of $.post() and not $.get(), as a JsonResult in MVC disallows GET requests by default (you can turn these on with an attribute).
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<script id="someDataTemplate" type="text/x-jquery-tmpl">
Item <b>${Id}</b> is labeled "<i>${Label}</i>" and has an inner item with id <b>${SomeValue.innerId}</b> whose label is "<i>${SomeValue.innerLabel}</i>".
</script>
<h3>SomeDataSource Example #1 (Single Item)</h3>
<div id="someData">
</div>
<script id="someDataArrayTemplate" type="text/x-jquery-tmpl">
Item <b>${Id}</b> is labeled "<i>${Label}</i>" and has these inner items:
<ul>
{{each SomeValue}}
<li><b>${innerId}</b> has a label "<i>${innerLabel}</i>".</li>
{{/each}}
</ul>
</script>
<h3>SomeDataSource Example #2 (Array)</h3>
<div id="someArrayData">
</div>
<script id="someDataTemplateFromExpandoObject" type="text/x-jquery-tmpl">
Item <b>${Id}</b> is labeled "<i>${Label}</i>" and has an inner item with id <b>${SomeValue.innerId}</b> whose label is "<i>${SomeValue.innerLabel}</i>".
</script>
<h3>SomeDataSource Example #3 (Single Item, Expando Object)</h3>
<div id="someDataFromExpandoObject">
</div>
<script type="text/javascript">
function pivotDictionaryMap(src)
{
var retval = {};
$.each(src, function(index, item){
retval[item.Key] = item.Value;
});
return retval;
}
</script>
<script type="text/javascript">
$(document).ready(function() {
// Ajax Round-Trip to fill example #1
$.post("/Home/SomeDataSource/5", function(data) {
$("#someDataTemplate").tmpl(data).appendTo("#someData");
}, "json");
// Ajax Round-Trip to fill example #2
$.post("/Home/SomeDataSourceWithArray/67", function(data) {
$("#someDataArrayTemplate").tmpl(data).appendTo("#someArrayData");
}, "json");
// Ajax Round-Trip to fill example #3
$.post("/Home/SomeDataSourceWithExpando/33", function(data) {
data.SomeValue = pivotDictionaryMap(data.SomeValue);
$("#someDataTemplateFromExpandoObject").tmpl(data).appendTo("#someDataFromExpandoObject");
}, "json");
});
</script>
I won't mark my own answer as "correct" just on premise that I don't like this answer. If someone figures out what I was trying to do above, I'll gladly give you the points.
function getDisplayValue(data, toMatchOn) {
var _return = $.grep(data, function (n, i) { return (n.Key == toMatchOn); })[0];
if (_return != null)
return _return.Value;
return "";
}
and in the jquery template:
${getDisplayValue(DisplayFields, 'Something')}
So I was able to get this to work with the method described above so here it is as a possible solution but I don't know javascript well enough to know how bad of a performance hit this could be creating (and as I research it, I'll update this answer) but I thought javascript dictionaries were optimized against their key value, so the fact that MVC doesn't serialize expando as a true javascript dictionary seems to make this answer very inefficient. And the fact that I originally took this tack with the dynamic c# object was that I originally thought this would serialize down into a cleaner form. Anyway, this works but Occam's Razor is just making this feel way too complicated.
Not sure if this will help or not, but have a look at this gist. It is hard to tell from your code snippets but if you are turning that ExpandoObject into JSON, then try wrapping it on a DynamicJsonObject first, as done in the gist.
Code from the Gist copy/pasted for those who don't want to click the link:
// By default, Json.Encode will turn an ExpandoObject into an array of items,
// because internally an ExpandoObject extends IEnumerable<KeyValuePair<string, object>>.
// You can turn it into a non-list JSON string by first wrapping it in a DynamicJsonObject.
[TestMethod]
public void JsonEncodeAnExpandoObjectByWrappingItInADynamicJsonObject()
{
dynamic expando = new ExpandoObject();
expando.Value = 10;
expando.Product = "Apples";
var expandoResult = System.Web.Helpers.Json.Encode(expando);
var dictionaryResult = System.Web.Helpers.Json.Encode(new DynamicJsonObject(expando));
Assert.AreEqual("{\"Value\":10,\"Product\":\"Apples\"}", expandoResult); // FAILS (encodes as an array instead)
Assert.AreEqual("{\"Value\":10,\"Product\":\"Apples\"}", dictionaryResult); // PASSES
}
You're right that performance is going to be sketchy if you're iterating over an array to find a key. But you should be able to comfortably convert the key-value-pair array (that the server sends back) into a "true" Javascript dictionary/map. Eg:
var kvps = [ {key: "test", value: "expando"}, {key: "hello", value: "world" } ];
var map = {};
kvps.forEach(function(kvp) { map[kvp.key] = kvp.value; } );
console.log( JSON.stringify(map) );
{"test":"expando","hello":"world"}
Of course, if you're nested objects, then you'd have to apply recursion to the above approach to make it work.

javascript + asp.net - Calling javascript functions from c# and passing an object

I'm fairly new to web development.
Basically I'm using a HTML5 canvas to render content, and javascript functions which clear and render to the canvas.
I'm hoping to call a web service to get data that would affect content, which I believe is most easily done from C#, and was wondering how I could use this to periodically call a javascript function that would update values that are being rendered to the canvas?
To make this clearer, I have something like this:
Oh I'm also using jquery by the way, partly because I was told to.
Page.asp:
<body>
<form id="form1" runat="server">
<canvas id="canv" width="200" height="200">
Cannot display canvas because your web browser is a fail.
</canvas>
<script type="text/javascript">
var ctrls = new Array();
$(document).ready(function () {
var canvas;
var context;
canvas = $("#canv").get(0);
if (!canvas) {
alert("Failed to get canvas");
return;
}
context = canvas.getContext('2d');
if (!context) {
alert("Failed to get context");
}
var x;
x = new Object();
x.value = 0;
x.parent2d = context;
ctrls.push(x);
window.setInterval(Render, 25);
});
function Render() {
var i;
for (i = 0; i < ctrls.length; i++) {
ctrls[i].parent2d.clearRect(0, 0, 200, 200);
//Draw stuff.
}
}
function Update(newVal) {
var i;
for (i = 0; i < ctrls.length; i++) {
ctrls[i].value = newVal; //For example
}
}
</script>
</form>
</body>
What is the easiest (if it's even possible) way to call the Update(newVal) function from C# (Page.asp.cs), and how could this be set up to be done periodically?
What would be the limitations on what object(s) could be passed to this function?
Dictionary<String, Double>
would be very useful.
When exactly does update need to be called after page load? If it's every five seconds, it might be easier to carefully set up some sort of Javascript-based interval (making sure you are checking certain conditions to ensure that the interval quits upon any error conditions).
Depending on what data you have, you may want to setup a method in C# that given certain POST or GET parameters passed to it via a jQuery $.ajax() call.
For instance:
$(document).ready(function() {
var intervalId = setInterval("runUpdate()", 5000);
});
function runUpdate() {
//Make an ajax call here, setting up a variable called 'data'
update(data);//Data is newVal, which was setup in the ajax call above
}
That code would run the update function every five seconds.
Firstly from Javascript I would normally avoid using xml based web services. The returned data is XML which has lots of unessesary tags which can cause transfer to be slow.
Instead look at JSON. Its much faster. However not so easy for authentication. To help you understand its power both twitter and facebook use it in there APIs.
JQuery can consume JSON really easy
$.getJSON(someurl, function(jData) {
//use returned data
}
You might find it useful to read this:
http://encosia.com/2008/03/27/using-jquery-to-consume-aspnet-json-web-services/
Hope this helps.

Categories