Why is my KO bindings need parentheses? - asp.net-mvc-4

I'm developing a website using Durandal/Knockout/Breeze/WebApi with MVC4 as the back end.
I'm querying my api via breeze like so:
var getCategories = function() {
var query =
entityQuery
.from('Categories')
.orderBy('Order');
return manager.executeQuery(query);
};
Then, on my view model:
function initCategories() {
service.getCategories()
.then(querySuccess)
.fail(queryFail);
function querySuccess(data) {
vm.categories(data.results);
};
where vm is my bounded view model and categories is of observableArray of course.
Finally, my view has:
<!-- ko foreach: categories -->
<div class="list_images">
<a data-bind="attr: { href: '#search/' + queryString() }" class="hover-shadow">
<img data-bind="attr: { src: image(), alt: name() }" width="240" height="180">
<h5 data-bind="text: name()"></h5>
</a>
</div>
<!-- /ko -->
Here's the screenshot of what data.results contains:
It works fine, except for the need of using the parentheses.
With 'normal' viewmodels I don't need parentheses in the view bindings.
I can't figure out why it happens only with breeze objects (Entities).
Edit
After further investigation I noticed that my entities are of type proto._setCtor.proto instead of just an Object. Why's that?
Even if I use the breeze manager to create a new entity - this is the object I get back :(
What's wrong here?

This isn't an answer. This is a confession that I'm mystified. I can't duplicate the problem you describe.
I understand exactly what you are asking. I agree that you should not have to use the parentheses. You should be able to write:
<h5 data-bind="text: name"></h5>
and not have to write:
<h5 data-bind="text: name()"></h5>
I downloaded the "Todo-Knockout" sample from Breeze. After confirming that it worked, I started changing it to look more like your binding example. I continued to work.
You can follow along with me, step-by-step, confirming that everything works as expected after each step.
Switched to the comment form of repeater: <!-- ko foreach: items -->
Replaced the <ul> and <li> tags with div container.
Switched to the debug version of KO (that's what you're using)
Updated to the latest KO (knockout-3.1.0.debug.js)
In the end, my revised markup looks like this:
<!-- ko foreach: items -->
<div>
<div data-bind="visible: !isEditing()">
<input type="checkbox" data-bind="checked: IsDone" />
<label data-bind="text: Description, click: $parent.editBegin, css: { done: IsDone, archived: IsArchived }"></label>
X
</div>
<div data-bind="visible: isEditing">
<form data-bind="event: { submit: $parent.editEnd }">
<input type="text" data-bind="value: Description, hasfocus: isEditing" />
</form>
</div>
</div>
<!-- /ko -->
When I break in the Chrome Developer Tools where the results are returned from the server and display data.results in the console, I get this:
[proto._setCtor.proto
CreatedAt: function dependentObservable() {
Description: function dependentObservable() {
Id: function dependentObservable() {
IsArchived: function dependentObservable() {
IsDone: function dependentObservable() {
entityAspect: ctor
isEditing: function observable() {
__proto__: Object
, proto._setCtor.proto, proto._setCtor.proto]
I'm not seeing any significant differences from your example. Do you?
What happens when you do the same thing with the same "Todo-Knockout" sample on your machine?
What browser are you using? Do you see the same misbehavior in Chrome?

Breeze serializes all of the properties of your entity into ko.computeds under the covers. It uses this to intercept changes to the properties and notify all the other places your property is used. That being said you should only have to use parans in places where you are using conditional bindings (such as when you are combining a property with some string to make a longer string. Where ever you are just binding to a standard ko binding handler you should not need it.

Related

Pass UTC offset into controller route

Fairly new to ASP.NET CORE, come from WPF background.
Having some trouble understanding how to pass local variables around.
I have a form:
<span id="clock"></span>
<form asp-action="Create" asp-controller="Session" method="post" asp-route-session="#Model.Session">
<input asp-for="#Model.Session.ClientId" class="form-control" style="display: none" />
<ejs-dropdownlist id="clinics" dataSource="#Model.LinkableClinics" placeholder="Select Clinic" popupHeight="220px" ejs-for="#Model.Session.ClinicId">
<e-dropdownlist-fields text="Name" value="ClinicId"></e-dropdownlist-fields>
</ejs-dropdownlist>
<ejs-dropdownlist id="employees" dataSource="#Model.LinkableEmployees" placeholder="Select Employee" popupHeight="220px" ejs-for="#Model.Session.EmployeeId">
<e-dropdownlist-fields text="Name" value="EmployeeId"></e-dropdownlist-fields>
</ejs-dropdownlist>
<ejs-datetimepicker id="startdatetimepicker" placeholder="Select a date and time" ejs-for="#Model.Session.StartTime"></ejs-datetimepicker>
<ejs-datetimepicker id="enddatetimepicker" placeholder="Select a date and time" ejs-for="#Model.Session.EndTime"></ejs-datetimepicker>
<div class="form-group">
<input type="submit" value="Create Session" class="btn btn-primary" />
</div>
</form>
and I would like to add asp-route-offset to my form but I can't figure out how to pass a locally calculated variable into this routing.
This is a post, so I can't just use Url.Redirect() custom url builder.
I've also tried to add a hidden field and run a calculation inside the "value"
<input asp-for="#Model.Session.Offset" class="form-control" style="display: none" value="getValue()"/>
I'm calculating my offset in my <script> section as follows:
<script type="text/javascript">
window.onload = loaded;
function loaded() {
var clockElement = document.getElementById("clock");
function updateClock(clock) {
clock.innerHTML = new Date().getTimezoneOffset() / 60;
}
}
</script>
I've tried setting variables or the #Model object here, but nothing seems to want to stick.
Anyone have experience trying to pass a variable like this? Or a better way to pass UTC offset into my timestamp?
You're trying to mix and match things happening server-side and client-side. Your script which gets the offset runs client-side, after the server has sent the response. Your Razor code runs server-side, before the client has even received the page.
Long and short, if you want to update the value, you have to do it via client-side means, namely JavaScript:
document.getElementById('#Session_Offset').value = new Date().getTimezoneOffset();
You can't use something like asp-route-offset at all, because the offset is coming from the client and can't be used to generate the post URL. As a result, you'll need to stick with the hidden input approach.

Using vee-validate for validation of dropbox

Sorry for the very basic question
I'm trying to validate a form using vee-validate. text/email/number fields are not a problem. But I couldn't find a good documentation on validating dropdown/checkbox/radio fields.
What I want is "you have to select some option from the dropdown". For that i tried
<p class="help is-danger" v-show="standard===''">Select the standard student is studing in.</p>
where standard is the property which is binded with the help of v-model="standard". This is working as intended, but i want this message to be shown when dropdown is "touched". I'm not able to figure this out.
You can use the data-vv-validate-on attribute:
data-vv-validate-on="focus"
Then whenever the dropdown is opened the validator will fire, for instance.
I found a workaround for this,
<div class="select" :class="{'is-success': standard!='', 'is-danger': standard=='' && standardIsFocused}">
<select v-model="standard" #focus.once="standardToggle()">
...
</select>
</div>
<p class="help has-text-left is-danger" v-show="standard==='' && standardIsFocused">Selecting one of the option is required.</p>
in script tags
data () {
return {
standardIsFocused: false,
},
methods: {
standardToggle() {
this.standardIsFocused = !this.standardIsFocused
}
}

Aurelia repeater: model.bind is not working for radio buttons

I am creating a set of radio buttons in Aurelia with the code like this:
<div repeat.for="option of options">
<input type="radio" id="${option}_id" name="radio_options" model.bind="option" checked.bind="optionValue"/>
<label for="${option}_id" id="${option}_label">${option}</label>
</div>
However, doing it this way I discovered that model.bind is not working - the optionValue in corresponding class is not populated when radio button is checked. Similarly when some value is assigned to optionValue in the class, the appropriate radio button is not checked. I found this happening only with repeater. Options are numbers in my case. Could you please help me to find out what may be wrong here?
The first problem is that model.bind should be used when working with objects. Since you're working with a primitive type, you should use value.bind instead.
The second problem is that input values are always strings, so when setting an initial value, it must be a string. For example:
Html:
<template>
<div repeat.for="option of options">
<input type="radio" id="${option}_id" name="radio_options" value.bind="option" checked.bind="optionValue"/>
<label for="${option}_id" id="${option}_label">${option}</label>
</div>
<p>Selected Option: ${optionValue} </p>
</template>
JS:
export class App {
options = [ 1, 2, 3, 4 ]
optionValue = '3';
}
If you really want to use int in your view-model, you can create a ValueConverter to convert the value to int when passing it to/from the view. For instance:
export class AsIntValueConverter {
fromView(value) {
return Number.parseInt(value);
}
toView(value) {
return value.toString();
}
}
Usage:
<input type="radio" id="${option}_id" name="radio_options" value.bind="option" checked.bind="optionValue | asInt"/>
Running Example https://gist.run/?id=1465151dd5d1afdb7fc7556e17baec35

Getting form data on submit?

When my form is submitted I wish to get an input value:
<input type="text" id="name">
I know I can use form input bindings to update the values to a variable, but how can I just do this on submit. I currently have:
<form v-on:submit.prevent="getFormValues">
But how can I get the value inside of the getFormValues method?
Also, side question, is there any benefit to doing it on submit rather than updating variable when user enters the data via binding?
The form submit action emits a submit event, which provides you with the event target, among other things.
The submit event's target is an HTMLFormElement, which has an elements property. See this MDN link for how to iterate over, or access specific elements by name or index.
If you add a name property to your input, you can access the field like this in your form submit handler:
<form #submit.prevent="getFormValues">
<input type="text" name="name">
</form>
new Vue({
el: '#app',
data: {
name: ''
},
methods: {
getFormValues (submitEvent) {
this.name = submitEvent.target.elements.name.value
}
}
}
As to why you'd want to do this: HTML forms already provide helpful logic like disabling the submit action when a form is not valid, which I prefer not to re-implement in Javascript. So, if I find myself generating a list of items that require a small amount of input before performing an action (like selecting the number of items you'd like to add to a cart), I can put a form in each item, use the native form validation, and then grab the value off of the target form coming in from the submit action.
You should use model binding, especially here as mentioned by Schlangguru in his response.
However, there are other techniques that you can use, like normal Javascript or references. But I really don't see why you would want to do that instead of model binding, it makes no sense to me:
<div id="app">
<form>
<input type="text" ref="my_input">
<button #click.prevent="getFormValues()">Get values</button>
</form>
Output: {{ output }}
</div>
As you see, I put ref="my_input" to get the input DOM element:
new Vue({
el: '#app',
data: {
output: ''
},
methods: {
getFormValues () {
this.output = this.$refs.my_input.value
}
}
})
I made a small jsFiddle if you want to try it out: https://jsfiddle.net/sh70oe4n/
But once again, my response is far from something you could call "good practice"
You have to define a model for your input.
<input type="text" id="name" v-model="name">
Then you you can access the value with
this.name inside your getFormValues method.
This is at least how they do it in the official TodoMVC example: https://v2.vuejs.org/v2/examples/todomvc.html (See v-model="newTodo" in HTML and addTodo() in JS)
Please see below for sample solution, I combined the use of v-model and "submitEvent" i.e. <input type="submit" value="Submit">. Used submitEvent to benefit from the built in form validation.
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="app">
<form #submit.prevent="getFormValues">
<div class="form-group">
<input type="email" class="form-control form-control-user"
v-model="exampleInputEmail"
placeholder="Enter Email Address...">
</div>
<div class="form-group">
<input type="password" class="form-control"
v-model="exampleInputPassword" placeholder="Password"> </div>
<input type="submit" value="Submit">
</form>
</div>
<script>
const vm = new Vue({
el: '#app',
methods: {
getFormValues (submitEvent) {
alert("Email: "+this.exampleInputEmail+" "+"Password: "+this.exampleInputPassword);
}
}
});
</script>
</body>
</html>
The other answers suggest assembling your json POST body from input or model values, one by one. This is fine, but you also have the option of grabbing the whole FormData of your form and whopping it off to the server in one hit. The following working example uses Vue 3 with Axios, typescript, the composition API and setup, but the same trick will work anywhere.
I like this method because there's less handling. If you're old skool, you can specify the endpoint and the encoding type directly on the form tag.
You'll note that we grab the form from the submit event, so there's no ref, and no document.getElementById(), the horror.
I've left the console.log() there to show that you need the spread operator to see what's inside your FormData before you send it.
<template>
<form #submit.prevent="formOnSubmit">
<input type="file" name="aGrid" />
<input type="text" name="aMessage" />
<input type="submit" />
</form>
</template>
<script setup lang="ts">
import axiosClient from '../../stores/http-common';
const formOnSubmit = (event: SubmitEvent) => {
const formData = new FormData(event.target as HTMLFormElement);
console.log({...formData});
axiosClient.post(`api/my-endpoint`, formData, {
headers: {
"Content-Type": "multipart/form-data",
}
})
}
</script>

Knockout observable array

In my ViewModel for my MVC4 application I have some code to get names from an ajax call and populate a simple control within my page, which is using Bootstrap 3. As you can see below I have a hard-coded array which works perfectly. With the ajax call, I see the data in the UI but it does not update my control and I have NO idea why. I have verified the data exists and I have also tried setting self.Names = ko.observableArray within the ajax call. Is there a simple reason why? As I said I see the data within my form in both scenarios but I am not seeing the update I expect.
$(document).ready(function () {
function ViewModel() {
//Make the self as 'this' reference
var self = this;
//Declare observable which will be bind with UI
self.Name = ko.observable("");
var Names = {
Name: self.Name
};
self.Name = ko.observable();
//self.Names = ko.observableArray([{ Name: "Brian" }, { Name: "Jesse" }, { Name: "James" }]);
self.Names = ko.observableArray(); // Contains the list of Names
// Initialize the view-model
$.ajax({
url: '#Url.Action("GetNames", "Home")',
cache: false,
type: 'GET',
contentType: 'application/json; charset=utf-8',
data: {},
success: function (data) {
self.Names(data); //Put the response in ObservableArray
}
});
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
});
Here is the Response from the body via the ajax call:
[{"Id":1,"Name":"Brian"},{"Id":2,"Name":"Jesse"},{"Id":3,"Name":"James"}]
My HTML
<p>Current selection is <span data-bind="text:Name"></span></p>
<div class="container">
<div class="col-sm-7 well">
<form class="form-inline" action="#" method="get">
<div class="input-group col-sm-8">
<input class="form-control" placeholder="Work Section" name="q" type="text">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">Select <span class="caret"></span></button>
<ul class="dropdown-menu" data-bind="foreach: Names">
<li class="dropdown">
</li>
</ul>
<input name="category" class="category" type="hidden">
</div>
</div>
Probably because the data coming in is either not structured the same as your bindings or observables are not set/updated properly. If they aren't observed then they wont update.
I'm not 100% sure on this, but I think you have to use the observable array functions in order for observers (UI elements bound to the array or its contents) to actually get updated. Basing this on a section from the observable array documentation on the knockout site:
2.For functions that modify the contents of the array, such as push and splice, KO’s methods automatically trigger the dependency tracking
mechanism so that all registered listeners are notified of the change,
and your UI is automatically updated.
Depending on the json, one way you might fix this is clear the observable array and reload it with data elements converted to observables:
self.Names.removeAll();
var newName = null;
for (var idx = 0; idx < data.length; idx++) {
newName = data[idx];
newName.Id = ko.observable(newName.Id);
newName.Name = ko.observable(newName.Name);
self.Names.push(newName);
}
On your html. the click function is using the Name property of the selected array element as the parameter. you don't want this, you want the latest value. So change this:
click: function() {$root.Name(Name);}
to this
//notice the new parenthesis after Name.
click: function() {$root.Name(Name());}