How to subscribe to items in collection/foreach DOM rendering - javascript

I have a view that is used to create and also update an item. I have this problem when updating where a function is running twice due to a change event on a dropdown list. What was suggested in my previous post is that I use a subscription to the value and create logic accordingly within the subscription. I think this is a great idea, however, I have no idea how I will apply these subscriptions to my model when the bindings are being applied dynamically from a view model sent to the view from a back-end controller method.
The collection items get their functionality from data-bind values provided in a foreach element loop and using $data. It would be preferred to keep this format for the solution.
<tbody data-bind="foreach: koModel.ModelData.Lines">
<tr class="form-group">
<td>...</td>
<td>..other model properties..</td>
<td>...</td>
<!-- v v v This is the property with the change event v v v -->
<td>#Html.SearchPickerMvcKOMultipleFor(
m => m.ItemNumber,
"ItemPicker",
new { #class = "form-control" }.AddMore(
"data-bind",
"MyProgramSearchPicker: ItemNumber, value: $data.ItemNumber, event: { change: ItemNumericDetails($data) }"
))</td>
<!-- ^ ^ ^ ^ ^ -->
<td>etc</td>
</tr>
</tbody>
The model is converted to JSON and then to a JavaScript variable:
#{
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
string data = Newtonsoft.Json.JsonConvert.SerializeObject(Model);
#Html.Raw("var modelData = " + data + ";");
}
var koModel = new KOModel(modelData);
How can I apply a subscription to the ItemNumber property, either by executing an inline function from the element, or another method that can modify a dynamically rendered collection?

Create a class that represents a "line" of the koModel.ModelData.Lines
function Line(itemNumber) {
var self = this;
self.ItemNumber = ko.observable(itemNumber);
self.ItemNumber.subscribe(...);
};
koModel.ModelData.Lines.push(new Line(123));
Or, you can create a function in your view model:
function koModel() {
...
self.ItemNumericDetails = function (line) {
//do your job
};
};
and in the HTML:
<td>
#Html.SearchPickerMvcKOMultipleFor(
m => m.ItemNumber,
"ItemPicker",
new { #class = "form-control" }.AddMore(
"data-bind",
"MyProgramSearchPicker: ItemNumber, value: $data.ItemNumber, event: { change: $root.ItemNumericDetails }"
))
</td>

Related

Posting multiple parameters via javascript from view MVC

I want to save any changes made in the textbox field instantaneously to my database, I have the javascript to pass back the changed value to the controller, however I am not aware how pass the unique id with this to save the changed value against.
Below is my view passing the price back to the script, how can I pass the part id with this?
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.partid, new { id = "partCode"})
</td>
<td>
#Html.DisplayFor(modelIem => item.partdesc)
</td>
<td>
#Html.TextBoxFor(modelItem => item.price, new { id = "priceTextBox", #class = "mytext rightJustified" })
</td>
</tr>
}
</table>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
$(function () {
$('#priceTextBox').change(function () {
var changedText = $(this).val();
var partsJava = $("#partCode").val();
//Post the content of your Textbox to your "YourAction" action in "YourController"
$.post('#Url.Action("EnterNewSpecialPrice2", "SpecialPrices")', { "ChangedText": changedText, "PassPartCode": partsJava }, function (data) { });
});
});
</script>
Define a class to hold both pieces of information that matches the Json object you have created in your script:
public class PartInputModel{
public string ChangedText {get;set;}
public string PassPartCode {get;set;}
}
Note the properties above must match the ones you have defined here:
{ ChangedText: changedText, PassPartCode: partsJava }
Also note you must remove the double quotes around the ChangedText and PassPartCode definition when creating the JSON structure as those are not required.
Change your controller action to receive this object and let the model binding do the work:
[HttpPost]
public ActionResult EnterNewSpecialPrice2(PartInputModel inputModel) {
// inputModel will have your two pieces of data
}
A better mechanism for storing and retrieving the id is to attach it as a data tag to the markup for the Text Box by adding a data-id tag:
#Html.TextBoxFor(m => item.price, new { id = "", #class = "priceTextBox mytext rightJustified", data_id="item.partid" })
Then change your script to read this:
var partsJava = $(this).data('id');
The advantage of this approach is that it is very easy to pass additional parameters by simply expanding your JSON structure and adding a matching property to the PartInputModel class.
You generating invalid html because of duplicate id attributes and selectors such as var partsJava = $("#partCode").val(); will only ever get the value of the first element with id="partCode". In anycase, it would be undefined since DisplayFor() does not generate a element (only text) and it has id attribute or value attribute. Change your loop to
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(m => item.partid)</td>
<td>#Html.DisplayFor(m => item.partdesc)</td>
<td>
#Html.TextBoxFor(m => item.price, new { id = "", #class = "priceTextBox mytext rightJustified", data_id="item.partid" })
</td>
</tr>
}
and then modify the script to
$('.priceTextBox').change(function () {
var changedText = $(this).val();
var partsJava = $(this).data('id');
$.post('#Url.Action("EnterNewSpecialPrice2", "SpecialPrices")', { "ChangedText": changedText, "PassPartCode": partsJava }, function (data) { });
});
});
Note the new { id="" } remove the id attribute so you do not get invalid html.
This of course assumes you have a method
public ActionResult EnterNewSpecialPrice2(string ChangedText, string PassPartCode)
in SpecialPricesController (although PassPartCode may be int?

How to update data in Meteor using Reactivevar

I have a page with a form. In this form user can add multiple rows with key and values. There is a restriction that the customFields is created on the fly, not from any subscribed collection.
...html
<template name="main">
{{#each customFields}}
<div>
<input type="text" value="{{key}}"/>
<input type="text" style="width: 300px;" value="{{value}}"/>
</div>
{{/each}}
</template
.... router.js
Router.route 'products.add',
path: '/products/add/:_id'
data:
customFields:[]
....products.js
#using customFieldSet as Reactive Var from meteor package
Template.product.created = ->
#customFieldSet = new ReactiveVar([])
Template.product.rendered = ->
self = this
Tracker.autorun ->
arr = self.customFieldSet.get()
self.data.customFields = arr
Template.product.events(
'click .productForm__addField': (e)->
t = Template.instance()
m = t.customFieldSet.get()
console.log t
m.push(
key: ''
value: ''
)
t.customFieldSet.set m
....
The last event will be trigger when I click the button. And it add another row with key and value empty to the page.
Please advise me why I actually see the reactive variable customFieldSet updated, but there is nothing changed dynamically in html.
P/s: I guess customFields is not updated via Iron router.
Basically, you're doing the thing right. However, you shouldn't be assigning the new reactive data to your template's data context, but rather access it directly from your helpers:
Template.product.helpers({
customFileds: function () {
return Template.instance().customFiledsSet.get();
},
});
Now you can use {{customFields}} in your template code and it should work reactively. Just remember that {{this.customFileds}} or {{./customFileds}} will not work in this case.

Knockout foreach binding, update value

I got ko model:
var Model = function(notes) {
this.note = ko.observableArray(notes);
};
var model = new Model([{"post":"a"},{"post":"b"}]);
ko.applyBindings(model);
And html div:
<div data-bind='foreach: note'>
<p><span data-bind='text: post'>
<p><input data-bind='value: post'>
</div>
Here's fiddle: http://jsfiddle.net/DE9bE/
I want to change my span value when new text is typed in input, like in that fiddle: http://jsfiddle.net/paulprogrammer/vwhqU/2/
But it didnt updates. How can i do that in foreach bindings?
The properties of the objects need to be made into observables.
You could do this yourself manually (i've used the plain js array map method here, if you need IE8 support you can use ko.utils.arrayMap for the same purpose):
var Model = function(notes) {
this.note = ko.observableArray(notes.map(function(note){
note.post = ko.observable(note.post);
return note;
}));
};
var model = new Model([{"post":"a"},{"post":"b"}]);
ko.applyBindings(model);
Demo
Or you can use the mapping plugin (seperate js file that you need to include) which does this (recursively) for you.
var Model = function(notes) {
this.note = ko.mapping.fromJS(notes);
};
var model = new Model([{"post":"a"},{"post":"b"}]);
ko.applyBindings(model);
Demo
If you get the whole data object from the server you could also feed it directly:
var model = ko.mapping.fromJS({
note: [{"post":"a"},{"post":"b"}]
});
ko.applyBindings(model);
Demo
What you want is an observable array with observables, or else your span will not be updated since you are changing a non-observable variable, see this post.
KnockoutJS - Observable Array of Observable objects
Try this:
HTML
<div data-bind='foreach: note'>
<p><span data-bind='text: post'></span></p>
<p><input data-bind='value: post'/></p>
</div>
JavaScript
var Model = function (notes) {
this.note = ko.observableArray(notes);
};
var Post = function (data) {
this.post = ko.observable(data);
};
var model = new Model([new Post("a"), new Post("a")]);
ko.applyBindings(model);
Demo

Calling variable in template from view of backbone

I'm using text! plug-in of require.js to load javascript templates of my backbone project.
Here it is :
<table id="showcart">
<tr><td class="cartitemhead">Item to buy</td><td class="cartitemhead" style="text-align: center;">Quantity</td></tr>
<% _.each(item, function(item) { %>
<tr><td><table class="verticallist"><tr><td rowspan="4" style="width: 120px;"><img src="<%=item.PictureName%>" alt="Product" width="95px"/></td><td style="font-weight: bold;"><%=trimString(item.Name,50)%></td></tr><tr><td><i>Available in Stock(s)!</i></td></tr><tr><td><i>Rating: 650Va-390w Input: Single</i></td></tr></table></td><td class="centertxt"><%=item.QuantityInCart%></td></tr>
<% }); %>
</table>
item variable was declared in my view.
var CartListView = Backbone.View.extend({
render: function(){
var item = deserializeJSONToObj(window.localStorage.getItem("Cart"));
var cartList = _.template(showCartTemplate, {})
$("#cartlist").html(cartList);
}
});
Model :
define(["underscore" , "backbone"],function(_ , Backbone){
var Cart = Backbone.Model.extend({
});
});
I got one error from console : Uncaught ReferenceError: item is not defined.
Any help would be much appreciated, thank you.
You need to indicate somehow the value you want to pass for backbone template. Because you have defined a variable, Underscore template is looking for a value to get replaced with.
For this reason try to pass the model value as an argument to the appended view.
var CartListView = Backbone.View.extend({
render: function(){
var item = deserializeJSONToObj(window.localStorage.getItem("Cart"));
var cartList = _.template(showCartTemplate, {})
$("#cartlist").append({item : cartList});
}
});
This way, each time when undercore find a variable item it will replace with the value passed as argument to the view.
You need to pass the item key in the object that you pass in _.template
var CartListView = Backbone.View.extend({
render: function(){
var item = deserializeJSONToObj(window.localStorage.getItem("Cart"));
var cartList = _.template(showCartTemplate, {item : item})
$("#cartlist").append({item : cartList});
}
});
Because you can not access the item variable directly in template.

Passing values to ko.computed in Knockout JS

I've been working for a bit with MVC4 SPA, with knockoutJs,
My problem is I want to pass a value to a ko.computed. Here is my code.
<div data-bind="foreach: firms">
<fieldset>
<legend><span data-bind="text: Name"></span></legend>
<div data-bind="foreach: $parent.getClients">
<p>
<span data-bind="text: Name"></span>
</p>
</div>
</fieldset>
</div>
self.getClients = ko.computed(function (Id) {
var filter = Id;
return ko.utils.arrayFilter(self.Clients(), function (item) {
var fId = item.FirmId();
return (fId === filter);
});
});
I simply want to display the Firmname as a header, then show the clients below it.
The function is being called, but Id is undefined (I've tried with 'Firm' as well), if I change:
var filter = id; TO var filter = 1;
It works fine,
So... How do you pass a value to a ko.computed? It doesn't need to be the Id, it can also be the Firm object etc.
Each firm really should be containing a list of clients, but you could use a regular function I think and pass it the firm:
self.getClientsForFirm = function (firm) {
return ko.utils.arrayFilter(self.Clients(), function (item) {
var fId = item.FirmId();
return (fId === firm.Id());
});
});
Then in html, $data is the current model, in your case the firm:
<div data-bind="foreach: $root.getClientsForFirm($data)">
Knockout doesn't allow you pass anything to a computed function. That is not what it is for. You could instead just use a regular function there if you'd like.
Another option is to have the data already in the dataset on which you did the first foreach. This way, you don't use $parent.getClients, but more like $data.clients.

Categories