Not able to bind click event in kendo mvvm row template - mvvm

I have kendo grid with a button to remove the current item and for that grid I have a row template. Here is my HTML:
<div class="form-horizontal">
<table class="fixed-table-width"
data-role="grid"
data-bind="source: data"
data-scrollable="false"
data-row-template="row-template">
<thead>
<tr>
<th>Item</th>
<th></th>
</tr>
</thead>
</table>
</div>
<script type="text/x-kendo-tmpl" id="row-template">
<tr>
<td><span data-bind="text: item"></span></td>
<td><button class="link" data-bind="click: remove"><i class="icon-trash"></i> Remove</button></br ></td>
</tr>
</script>
And that is my model:
function AddItemComponent($scope) {
if ($scope === null || $scope === undefined) throw new Error("Unknown scope, please provide the scope");
var self = this;
self.itemModel = {
item: "Item to Remove",
remove: function(i) {
self.viewModel.items = self.viewModel.items.splice(i);
}
};
self.viewModel = kendo.observable({
items: []
});
self.viewModel.items.push(self.itemModel);
};
But when I open the modal with this HTML, I get the following error:
kendo.binder.min.js?cdv=40:25 Uncaught TypeError: t.get is not a function(…)
If I remove the data-bind from the click event, there is no error and it just works fine, so what is wrong?

What you are trying to achieve is better done with a commands column
http://docs.telerik.com/kendo-ui/api/javascript/ui/grid#configuration-columns.command
data-columns="[{ field: 'item' }, { command: 'destroy' }]"
You don't neeed a row template anymore.
If you don't want to do this, try to wrap your itemModel in an observable, as get() is a method from kendo.data.ObservableObject
self.itemModel = kendo.observable({
item: "Item to Remove",
remove: function(i) {
self.viewModel.items = self.viewModel.items.splice(i);
}
});

Related

Edit form prop values not filled in the form vuejs (reusing same form component )

I have a vuejs project, where I have my parent component as ProductGroup.vue which has a child ProductGroupmodal.vue. The productGroup has list of records, upon clicking edit button on each row the modal (ProductGroupmodal.vue) should pop up with the input fields filled with the data. I have passed each row data as prop to the child component on clicking edit button. Then, I have assigned the prop data to the form object in the created() method of child component. But the form is not filled with previous records. Here is my code below:
This is Parent component.
<template>
<div class="tables-basic">
<h2 class="page-title">Product Group - <span class="fw-semi-bold">List</span></h2>
<b-button v-b-modal.modal1 #click.prevent="newModal()">Add New</b-button>
<b-row>
<b-col>
<div class="table-resposive">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th class="hidden-sm-down">Action</th>
</tr>
</thead>
<tbody>
<tr v-for="row in allData" :key="row.id">
<td>{{row.name}}</td>
<td>
<b-button v-b-modal.modal1 #click.prevent="editModal(row)">Edit</b-button>
</td>
</tr>
</tbody>
</table>
</div>
</b-col>
</b-row>
<ProductGroupModal :editValue="editValue" :editing='editing'
/>
</div>
</template>
<script>
import ProductGroupModal from '#/components/ProductGroup/ProductGroupModal.vue';
export default {
name: 'ProductGroup',
components: { ProductGroupModal },
data() {
return {
editValue:{},
editing: false,
allData:{},
};
},
methods: {
loadData() {
ProductGroupDataService.getAll()
.then(response => {
this.allData = response.data;
})
.catch(e => {
console.log(e);
});
},
newModal(){
this.editing=false;
},
editModal(row){
this.editValue = row;
this.editing = true;
},
},
mounted() {
this.loadData();
}
};
</script>
This is my Child component (ProductGroupModal.vue):
<template>
<b-modal ref="productGroupModal" id="modal1" centered v-bind:title="this.editing==true ? 'Update' : 'Add' "
#hidden="resetModal"
#ok.prevent="submitForm() ">
<v-form ref="productGroupForm" id="productGroupForm">
<v-text-field
v-model="form.name"
label="Name"
required
></v-text-field>
</v-form>
</b-modal>
</template>
<script>
import ProductGroupDataService from "#/services/ProductGroupDataService";
export default {
props: {
editValue: {
type: Object,
default: () => ({empty: true}),
},
editing: {
type: Boolean,
default: () => ({}),
}
},
name: 'productGroupModal',
data() {
return {
form: {
id: '',
name: '',
code: '',
},
};
},
methods:{
submitForm() {
var data = {
"name": this.form.name,
"code": this.form.code
};
},
created(){
if (!this.editValue.empty) {
this.form = this.editValue
console.log('edit:'+ this.editValue);
}
else{
console.log('falseedit:'+ this.editValue);
}
}
}
</script>
I think my b-modal(child component) is rendered before the data in the form is being set through prop .How can I solve it?Can anyone help me with this please!
I can't get enough info about your b-modal components.
but I think this maybe related to the props passed to child is async.
when created, the asyncprops editValue is not ready yet. so nothing is assigned to the form.
you can add v-if on the child component, render the child component only when data is ready:
<ProductGroupModal v-if="allData.length" :editValue="editValue" :editing='editing'
/>
do not write this.form = this.editValue in created hook;
computed: {
form() {
return this.editValue || {};
}
}

Angular - list of radio button not working

I'm having problems using a list of radio buttons inside a form. Below my code.
view
<form novalidate (ngSubmit)="onSubmit()" [formGroup]="perito">
<div class="form-group row">
<table class="table">
<thead></thead>
<tbody>
<tr *ngFor="let p of periti">
<td>{{p.nome}} {{p.cognome}}</td>
<td>{{p.indirizzo}} {{p.civico}} - {{p.cap}}
{{p.comune}}
</td>
<td><input formControlName="idPerito" type="radio" [value]="p.id" (change)="onSelect(p.id)"></td>
</tr>
</tbody>
</table>
</div>
</form>
controller
perito: FormGroup;
ngOnInit() {
this.perito = new FormGroup({
idPerito: new FormControl()
});
}
onSelect() {
console.log(this.perito.value);
}
The problem is that when I select one radio button:
all the other radio button get selected too
the form object is undefined
What is the correct way to manage a list of radio button? Thanks.
EDIT - this is how I populate my periti array:
this.peritiSrv.getPeriti()
.then(res => {
this.periti = res;
})
.catch();
Anyway, it cannot happen to have the same id for two periti objects, because they are primary keys.
Your form object is undefined probably because it was not instantiated by the time your component was created (constructed). Try to move the formGroup creation line to the body of the constructor for that component from the ngOnInit() hook,
controller
perito: FormGroup;
constructor() {
this.perito = new FormGroup({
idPerito: new FormControl('')
}); // empty string passed in as the initial value of the FormControl instead of no argument
}
// no ngOnInit() implementation
onSelect() {
console.log(this.perito.value);
}
See if the problem persists.

Angular2 Reactive Form Table

I am having some trouble looking up and attempting to do what I want to.
I have a table with inputs in each row, and I want each row which is created using an ngFor, to be considered a form group.
Within each form group I want a validation in that if any of the controls are filled within the row, that the entire form group needs to be filled before submission can be done.
Here is what I have so far in my template.
I don't have anything in the component yet as Angular.io and searching around for a few hours has not shown me anything close to what I want.
<form>
<table id="table" class="mdl-data-table mdl-js-data-table mdl-data-table mdl-shadow--2dp">
<thead>
<tr>
<th>Day</th>
<th>Description</th>
<th>Open Time</th>
<th>Close Time</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let scheduleDetail of scheduleDetails">
<td style="padding: 0 8px 0 8px;">{{weekdayConverter(scheduleDetail.dayId)}}</td>
<td class="pad-input">
<mdl-textfield style="max-width:100px;" type="text" class="font" name="description" [(ngModel)]="scheduleDetail.description"></mdl-textfield>
</td>
<td>
<mdl-textfield style="max-width:75px" type="text" error-msg="hh:mm" name="openTime" pattern="([01]\d|2[0-3]):?([0-5]\d)" [(ngModel)]="scheduleDetail.openTime"></mdl-textfield>
</td>
<td>
<mdl-textfield style="max-width:75px" type="text" error-msg="hh:mm" name="closeTime" pattern="([01]\d|2[0-3]):?([0-5]\d)" [(ngModel)]="scheduleDetail.closeTime"></mdl-textfield>
</td>
</tr>
</tbody>
</table>
</form>
Update
Added the following to the template:
Changed inputs to:
<mdl-textfield (keyup)="formChange(scheduleDetail)" style="max-width:100px;" type="text" class="font" name="description" [(ngModel)]="scheduleDetail.description"></mdl-textfield>
Added the following to the component:
formChange(detail:scheduleDetail){
if(this.checkValid(detail)==false)
this.scheduleDetails.filter(detail => detail == detail)[0].require=true;
else
this.scheduleDetails.filter(detail => detail == detail)[0].require=false;
this.checkForm();
}
checkValid(detail:scheduleDetail){
if(detail.description!=null && detail.description!=""){
if(this.checkTime(detail))
return true
else
return false
}
else
return true
}
checkTime(detail:scheduleDetail){
if(
(detail.openTime!=null && detail.closeTime!=null) &&
( detail.openTime!="" && detail.closeTime!="") &&
(this.checkRegExp(detail.openTime) && this.checkRegExp(detail.closeTime))
){
return true
}
else if((this.checkRegExp(detail.openTime) && this.checkRegExp(detail.closeTime))){
return true
}
else return false
}
checkRegExp(time:string){
let timeRegExp = /([01]\d|2[0-3]):?([0-5]\d)/;
if(timeRegExp.test(time)){
return true;
}
else
return false;
}
checkForm(){
let valid: boolean = true;
this.scheduleDetails.forEach(detail => {
if(detail.require==true){
valid = false;
}
});
this.scheduleDetails.forEach(detail => {
if(detail.description=="" || detail.description==null){
valid = false;
}
});
this.formValid = valid;
}
Model Driven Forms
You are using a template driven form, which is hard to scale, and maintain.
Here, i will guide you to migrate to a model driven form.
Component
export class WeekScheduleComponent {
// Our empty Form
myForm: FormGroup;
constructor(private fb: FormBuilder){
// We inject FormBuilder to our component
// Now, we instantiate myForm with FormBuilder
// Notice that myForm is a FormGroup which contains an empty FormArray
this.myForm = this.fb.group({
scheduleDetail: this.fb.array([])
})
}
addRow(){
// This function instantiates a FormGroup for each day
// and pushes it to our FormArray
// We get our FormArray
const control = <FormArray>this.myForm.controls['scheduleDetail'];
// instantiate a new day FormGroup;
newDayGroup: FormGroup = this.initItems();
// Add it to our formArray
control.push(newDayGroup);
}
initItems(): FormGroup{
// Here, we make the form for each day
return this.fb.group({
description: [null, Validators.required],
openTime: [null, Validators.required],
closeTime: [null, Validators.required]
});
}
submit(){
// do stuff and submit result
console.log(this.myForm.value);
}
}
in your template:
<form [formGroup]="myForm" *ngIf="myForm">
<table formArrayName="scheduleDetail">
<tr *ngFor="let item of myForm.controls.scheduleDetail.controls; let i=index"
[formGroupName]="i" >
<td><input type='text' formControlName="description"></td>
<td><input type='text' formControlName="openTime"></td>
<td><input type='text' formControlName="closeTime"></td>
</tr>
</table>
</form>
<button (click)="addRow()">Add new item</button>
<button (click)="submit()" [disabled]="!myForm.valid">Submit</button>
Component
import { Component, OnInit } from '#angular/core';
import {FormGroup, FormBuilder, FormControl, Validators, FormArray} from '#angular/forms';
#Component({
selector: 'app-sales',
templateUrl: './sales.component.html',
styleUrls: ['./sales.component.css']
})
export class SalesComponent implements OnInit {
myForm: FormGroup;
constructor(private fb: FormBuilder){
}
// getter to get a reference to scheduleDetail form array in myForm form group
get scheduleDetail():FormArray{
return <FormArray>this.myForm.get('scheduleDetail')
}
// add a new row, get reference to form array using getter method and push form group into it
addRow(){
this.scheduleDetail.push(this.initItems());
}
// make a form for each row!
initItems(): FormGroup{
return this.fb.group({
description: [null, Validators.required],
openTime: [null, Validators.required],
closeTime: [null, Validators.required]
});
}
submit(){
console.log(this.myForm.value)
}
ngOnInit() {
this.myForm = this.fb.group({
scheduleDetail: this.fb.array([this.initItems()])
})
}
}
Template
<form [formGroup]="myForm" *ngIf="myForm">
<table formArrayName="scheduleDetail">
<thead>
<tr>
<th >Desc</th>
<th >Open Time</th>
<th >Close Time</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of myForm.controls.scheduleDetail.controls; let i=index"
[formGroupName]="i" >
<td><input type='text' formControlName="description"></td>
<td><input type='text' formControlName="openTime"></td>
<td><input type='text' formControlName="closeTime"></td>
</tr>
</tbody>
</table>
</form>
<button (click)="addRow()">Add new item</button>
<button (click)="submit()" [disabled]="!myForm.valid">Submit</button>

dropdown will accept a value that is not part of the list as well in Knockout

When entering an E-mail address, the user will have to select the E-mail
domain from the pre-defined list (e.g., gmail.com; outlook.com; hotmail.com).
The Domain dropdown will accept a value that is not part of the list as well.
I should have both select and input feature.
HTML:
<!-- Mulitple array of emails -->
<div>
<table class="table table-bordered">
<tbody data-bind='foreach: billDeliveryEmails'>
<tr>
<td><input class='required' data-bind='value: userName' /></td>
<td><select data-bind="options: $root.availableEmailDomains(), value: domainName, optionsText: 'domainName', optionsValue: 'domainName'"></select></td>
<td><a data-bind="click:'removeDeliveryEmailAddress'">Delete</a></td>
</tr>
</tbody>
</table>
<a class="atm-bloque-link" data-bind="click:'addDeliveryEmailAddress'">Agregue otra direccion de email</a>
</div>
VM:
// Domain class
function Domain(domainName) {
var self = this;
self.domainName = domainName;
}
billDeliveryEmails : [],
availableEmailDomains : ko.observableArray([
new Domain("hotmail.com"),
new Domain("yahoo.com")
])
addDeliveryEmailAddress: function ($element, data, context, bindingContext, event) {
bindingContext.$root.billDeliveryEmails.push({
userName: "",
domainName: this.viewModel.get('availableEmailDomains')[0]
});
event.preventDefault();
},
removeDeliveryEmailAddress: function ($element, data, context, bindingContext, event) {
bindingContext.$root.billDeliveryEmails.splice(0, 1)
event.preventDefault();
}
Based on the link in your comment, here's an example:
function Domain(domainName) {
var self = this;
self.domainName = domainName;
}
var vm = function () {
var self = this;
self.browser = ko.observable();
self.browserList = ko.observableArray([
new Domain('yahoo.com'),
new Domain('hotmail.com')
])
}();
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<label>Choose a browser from this list:
<input list="browsers" name="myBrowser" data-bind="value: browser" /></label>
<datalist id="browsers" data-bind="foreach: browserList">
<option data-bind="text: domainName">
</datalist>
<br />
Your browser is: <span data-bind="text: browser"></span>
An observable array is used to populate the list and a regular observable tracks the selected value.

How to handle one to many relation ship Knockout-Entity Framework

So I have I have a simple structure where one purchase have a collection of expenses, and each expense have an account(plastic, cash, plastic#2...).
So the json my api gets is similar to this:
[
{"$id":"1","Id":1,"Name":"apple","Value":100.0,"AccountId":1,"Account":
{"$id":"2","Id":1,"Name":"Cash"}},
{"$id":"3","Id":2,"Name":"pear","Value":50.0,"AccountId":1,"Account":
{"$ref":"2"}},
{"$id":"4","Id":3,"Name":"raspberry","Value":10.0,"AccountId":1,"Account":
{"$ref":"2"}}
]
I see my json is not writing my cash account each time it needs it, it is refering it with
{"$ref":"2"}
where
{"$id":"2","Id":1,"Name":"Cash"}
so when I render my table with this html:
<table>
<tbody data-bind="foreach: gastos">
<tr>
<td data-bind="text: $data.id"></td>
<td data-bind="text: $data.name"></td>
<td data-bind="text: $data.value"></td>
<td data-bind="text: $data.account.Name"></td>
<td>
<button type="button" class="btn btn-xs">
<i class="glyphicon glyphicon-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
I get this, because the account for pear, and raspberry are nulls:
So how do you handle $ref in knockout?
I am mapping to 'gastos' this way:
$.getJSON('#ViewBag.GastosUrl', function (data) {
data.forEach(function(o) {
gastos.push(new gastoVM(o.Id, o.Name, o.Value, o.Account));
});
});
var gastoVM = function(Id, Name, Value, Account) {
var self = this;
self.id = Id;
self.name = Name;
self.value = Value;
self.account = Account;
};
thanks.
I'm not familiar with entity-framework but with the data as provided, a couple options available (JSFiddle):
Build up the account information alongside the gastos. And only provide the $id or $ref for later referencing.
var vm = {
gastos: [],
accounts: {},
accountLookup: function(accountId){
return this.accounts[accountId];
}
}
//... inside AJAX call
var accountId = o.Account["$id"]
if(accountId)
{
vm.accounts[accountId] = o.Account;
}
Use a ko utility method to lookup the account from within your array.
accountLookupByUtil: function(accountId) {
var gasto = ko.utils.arrayFirst(this.gastos, function(item) {
if(item.account['$id'] == accountId)
{
return item
}
});
return gasto.account;
}
From the html:
<td data-bind="text: $root.accountLookup($data.accountId).Name"></td>
<td data-bind="text: $root.accountLookupByUtil($data.accountId).Name"></td>
Note: Both methods are available in the fiddle, thus some properties are provided that would not be necessary depending upon the method used.