I would like to link multiple inputs via VueJS and compute the values.
The inputs are to track vehicle mileage, and therefore any given day's value, cannot be less than the previous day's value.
So far I have come up with the following (it's very convoluted and I know it can be tidied up but I'll improve on that later). It doesn't work, as you can't change any value apart from Monday Start.
https://jsfiddle.net/mstnorris/qbgtpm34/1/
HTML
<table id="app" class="table">
<thead>
<tr>
<th>Day</th>
<th>Start</th>
<th>End</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Mon</th>
<td>
<input type="number" class="form-control" v-model="mon_start" number id="mon_start" placeholder="Monday Start Mileage">
</td>
<td>
<input type="number" class="form-control" v-model="mon_end" number id="mon_end" placeholder="Monday End Mileage">
</td>
</tr>
<tr>
<th scope="row">Tue</th>
<td>
<input type="number" class="form-control" v-model="tue_start" number id="tue_start" placeholder="Tuesday Start Mileage">
</td>
<td>
<input type="number" class="form-control" v-model="tue_end" number id="tue_end" placeholder="Tuesday End Mileage">
</td>
</tr>
<tr>
<th scope="row">Wed</th>
<td>
<input type="number" class="form-control" v-model="wed_start" number id="wed_start" placeholder="Wednesday Start Mileage">
</td>
<td>
<input type="number" class="form-control" v-model="wed_end" number id="wed_end" placeholder="Wednesday End Mileage">
</td>
</tr>
<tr>
<th scope="row">Thu</th>
<td>
<input type="number" class="form-control" v-model="thu_start" number id="thu_start" placeholder="Thursday Start Mileage">
</td>
<td>
<input type="number" class="form-control" v-model="thu_end" number id="thu_end" placeholder="Thursday End Mileage">
</td>
</tr>
<tr>
<th scope="row">Fri</th>
<td>
<input type="number" class="form-control" v-model="fri_start" number id="fri_start" placeholder="Friday Start Mileage">
</td>
<td>
<input type="number" class="form-control" v-model="fri_end" number id="fri_end" placeholder="Friday End Mileage">
</td>
</tr>
<tr>
<th scope="row">Sat</th>
<td>
<input type="number" class="form-control" v-model="sat_start" number id="sat_start" placeholder="Saturday Start Mileage">
</td>
<td>
<input type="number" class="form-control" v-model="sat_end" number id="sat_end" placeholder="Saturday End Mileage">
</td>
</tr>
<tr>
<th scope="row">Sun</th>
<td>
<input type="number" class="form-control" v-model="sun_start" number id="sun_start" placeholder="Sunday Start Mileage">
</td>
<td>
<input type="number" class="form-control" v-model="sun_end" number id="sun_end" placeholder="Sunday End Mileage">
</td>
</tr>
</tbody>
</table>
VueJS
new Vue({
el: "#app",
data: {
mon_start: '',
mon_end: '',
tue_start: '',
tue_end: '',
wed_start: '',
wed_end: '',
thu_start: '',
thu_end: '',
fri_start: '',
fri_end: '',
sat_start: '',
sat_end: '',
sun_start: '',
sun_end: ''
},
computed: {
mon_end: function() {
return this.mon_start
},
tue_start: function () {
return this.mon_end
},
tue_end: function() {
return this.tue_start
},
wed_start: function () {
return this.tue_end
},
wed_end: function() {
return this.wed_start
},
thu_start: function () {
return this.wed_end
},
thu_end: function() {
return this.thu_start
},
fri_start: function () {
return this.thu_end
},
fri_end: function() {
return this.fri_start
},
sat_start: function () {
return this.fri_end
},
sat_end: function() {
return this.sat_start
},
sun_start: function () {
return this.sat_end
},
sun_end: function() {
return this.sun_start
}
}
})
Why does this not work so far?
You are mixing data property names with computed properties, so the value will be read from the computed property (get) and it will try to write it back to the same computed property, which wont work since you haven't assigned a setter.
Also, always showing the value of mon_start in mon_end etc. will not allow you to show the updated value of mon_end in mon_end.
Computed: Getters and Setters
What you actually want is a custom action to happen when a new value is set (e.g. mon_start is set to 2, thus all other fields should be set to 2).
Now we could just say: If mon_start is updated, update mon_end. If mon_end is updated, update tue_start. And so on.
This can be achieved using computed setters:
mon_start_model: {
get: function() {
return this.mon_start
},
set: function(val) {
this.mon_start = val
this.mon_end_model = this.mon_start
}
},
I've simplified the example a little bit (using only Monday and Tuesday).
Avoiding confusing the user
As an extra condition for user-friendliness, you probably only want the next value to update if the next value is smaller or equal to the previous value.
Eg. mon_start is 1 at the beginning, then you update mon_end to 5, then you update mon_start to 3. You probably want mon_end to keep the value 5, right?
new Vue({
el: "#app",
data: {
mon_start: 0,
mon_end: 0,
tue_start: 0,
tue_end: 0,
},
computed: {
mon_start_model: {
get: function() {
return this.mon_start
},
set: function(val) {
this.mon_start = val
this.mon_end_model = Math.max(this.mon_end, this.mon_start)
}
},
mon_end_model: {
get: function() {
return this.mon_end
},
set: function(val) {
this.mon_end = val
this.tue_start_model = Math.max(this.tue_start, this.mon_end)
}
},
tue_start_model: {
get: function () {
return this.tue_start
},
set: function(val) {
this.tue_start = val
this.tue_end_model = Math.max(this.tue_end, this.tue_start)
}
},
tue_end_model: {
get: function() {
return this.tue_end
},
set: function(val) {
this.tue_end = val
}
}
}
})
https://jsfiddle.net/qbgtpm34/5/
Related
Hi I have a form like this:
<tr v-for="(post, index) in posts" v-bind:index="index">
<td>{{ post.rut }}</td>
<td>{{ post.names }} {{ post.father_lastname }} {{ post.mother_lastname }}</td>
<td>
<input type="number" class="form-control" id="exampleInputEmail1" v-model="form.amount[index]" placeholder="Ingresa el monto">
</td>
</tr>
How you can see it has v-model="form.amount[index]" but what I want to do it's this:
<input type="number" class="form-control" id="exampleInputEmail1" v-model="form.amount[post.rut]" placeholder="Ingresa el monto">
I mean I ant to assign my own index my custom index I wonder how can I do that??
My vuejs code is this:
data: function() {
return {
form: {
amount: [],
},
I declared amount array in vuejs how you can see above but I need to assign my own index in array because if I send the data and I check the array in this moment, it is:
0 => value, 1 => value
but I need to do this
'2714155' => value, '4578745' => value...
how can I do that? Thanks
Declare your data as an object, then assign value to specific keys.
data() {
return {
form: {
amount: {},
},
}
}
You can use your desired layout as is.
<input
type="number"
class="form-control"
id="exampleInputEmail1"
v-model="form.amount[post.rut]" <-- here you assign a specific key
placeholder="Ingresa el monto">
You can achieve this by using an object instead of an array, as javascript doesn't have associative array's like php. You can achieve an associative array with the help of objects instead.
See the working example below:
Vue.config.productionTip = false
Vue.config.devtools = false
new Vue({
el: '#app',
data: {
posts: [{
rut: 2714155,
names: 'John',
father_lastname: 'Doe',
mother_lastname: 'Foo'
},
{
rut: 4578745,
names: 'Lion',
father_lastname: 'Doe',
mother_lastname: 'Bar'
}
],
form: {
amount: {
'2714155': 1,
'4578745': 2
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table>
<tr v-for="post in posts" :key="post.rut">
<td>{{ post.rut }}</td>
<td>{{ post.names }} {{ post.father_lastname }} {{ post.mother_lastname }}</td>
<td>
<input type="number" class="form-control" id="exampleInputEmail1" v-model="form.amount[post.rut]" placeholder="Ingresa el monto">
<span>Model Value: {{ form.amount[post.rut] }}</span>
</td>
</tr>
</table>
</div>
When i do dd($data); all data will shown but when i remove dd and simply submit my form then problem always come undefined index sku.
This is my controller for product attribute
public function addProductAttributes(Request $request){
if($request->isMethod('post')){
$data=$request->all();
foreach($data['sku'] as $key => $value){
if($value != null){
$attr = new ProductsAttribute();
$attr->product_id = $data['product_id'][$key];
$attr->sku = $data['sku'][$key];
$attr->stock=$data['stock'][$key];
$attr->price= $data['price'][$key];
$attr->size= $data['size'][$key];
$attr->status= $data['status'][$key];
$attr->save();
}
}
}
if($request->expectsJson()){
return response()->json([
'message'=>'Product has been Update Successfully',
]);
};
}
This is my Product Attribute Template. This is Dynamically Add / Remove input fields in Vue js so i can i add multiple data array
<form role="form" method="POST" #submit.prevent="addProductAttribute">
<table >
<tbody>
<tr v-for="(attr,index) in attribute" :key="index">
<td><input type="text" v-model="product_id" class="form-control"></td>
<td><input type="text" v-model="attr.sku" placeholder="SKU" class="form-control"></td>
<td><input type="text" v-model="attr.size" placeholder="Size" class="form-control"></td>
<td><input type="text" v-model="attr.stock" placeholder="Stock" class="form-control"></td>
<td><input type="text" v-model="attr.price" placeholder="Price" class="form-control"></td>
<td><input type="text" v-model="attr.status" placeholder="Status" class="form-control"></td>
<td><a #click="addAttribute()" v-show="index == attribute.length-1" title="Add More Product Attribute"><i class="fas fa-plus" style="color: #00b44e"></i></a> <a title="Remove" #click="removeAttribute(index)" v-show="index || ( !index && attribute.length > 1)"><i class="fas fa-minus" style="color: red"></i></a></td>
</tr>
</tbody>
<tfoot>
<tr><button type="submit" class=" btn-outline-primary btn-lg">Add</button></tr>
</tfoot>
</table>
</form>
and my Script file is
<script>
export default {
mounted() {
let app = this;
let id = app.$route.params.id;
app.product_id = id;
this.readProductAttribute();
},
data(){
return{
product_id:'',
product_name:'',
attributes:[],
product:'',
attribute:[
{
product_id:'',
sku:'',
price:'',
stock:'',
size:'',
status:'',
}
]
}
},
methods: {
removeAttribute(index){
this.attribute.splice(index, 1);
},
addAttribute(){
this.attribute.push({ size: '',sku:'',price:'',stock:'',status:'' });
},
addProductAttribute: function(e){
axios.post(`/admin/add-product-attribute`,{
myArray: this.attribute
})
.then(function(response){
console.log(response);
})
}
}
}
Your data lies within the key 'myArray' so changing the loop to the following should help on your error.
foreach($data['myArray'] as $key => $value){
The access logic for each attr is also flawed. You should only access it on their key, secondly it seems like product_id is not always there in your data, so you can use nullcoalescent to return empty string if key is not there.
$attr->product_id = $data['product_id'] ?? '';
$attr->sku = $data['sku'] ?? '';
$attr->stock=$data['stock'] ?? '';
$attr->price= $data['price'] ?? '';
$attr->size= $data['size'] ?? '';
$attr->status= $data['status'] ?? '';
I'm using Laravel 5.6 and Vuejs 2.
If I click on my checkbox and make the value true it's supposed to save a 1 in the database and if I click my checkbox and make the value false it saves a 0.
The problem I'm having is that if I click my checkbox and make it true, it doesn't save the correct value, no changes is made to the database and I don't get any errors. If I click on my checkbox and make it false, it saves the 0 correctly.
I did notice that even when my value is supposed to be true, I do get a false when I dd($category->has('active')
I'm not sure where I'm going wrong or how to fix it.
My vue file
<template>
<div class="card-body">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Active</th>
<th scope="col">Title</th>
<th scope="col">Edit</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="(category, index) in categoriesNew" >
<td>
<label>checkbox 1</label>
<input name="active" type="checkbox" v-model="category.active" #click="checkboxToggle(category.id)">
</td>
<td>
{{ category.title }}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: ['categories'],
data(){
return {
categoriesNew: this.categories
}
},
methods: {
checkboxToggle(id){
console.log(id);
axios.put('/admin/category/active/'+id, {
categories: this.categoriesNew
}).then((response) => {
//Create success flash message
})
},
},
mounted() {
console.log('Component mounted.')
}
}
</script>
my routes
Route::put('admin/products/updateAll', 'Admin\ProductsController#updateAll')->name('admin.products.updateAll');
Route::put('admin/category/active/{id}', 'Admin\CategoryController#makeActive')->name('admin.category.active');
Route::resource('admin/category', 'Admin\CategoryController');
Route::resource('admin/products', 'Admin\ProductsController');
my CategoryController#makeActive
public function makeActive(Request $request, $id)
{
$category = Category::findOrFail($id);
if($request->has('active'))
{
$category->active = 1;
}else{
$category->active = 0;
}
$category->save();
}
I hope I made sense. If there is anything that isn't clear or if you need me to provide more info, please let me know
Try changing this line
categories: this.categoriesNew
to
categories: category.active
and add a data prop at the top called category.active: ''
I've managed to get it to work. This is what I did.
vue file
<template>
<div class="card-body">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Active</th>
<th scope="col">Title</th>
<th scope="col">Edit</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="(category, index) in categories" >
<td>
<label>checkbox 1</label>
<input type="checkbox" v-model="category.active" #click="checkboxToggle(category)">
</td>
<td>
{{ category.title }}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: ['attributes'],
data(){
return {
categories: this.attributes,
}
},
methods: {
checkboxToggle (category) {
axios.put(`/admin/category/${category.id}/active`, {
active: !category.active
}).then((response) => {
console.log(response)
})
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
my routes
Route::put('admin/category/{category}/active', 'Admin\CategoryController#makeActive')->name('admin.category.active');
and my CategoryController#makeActive
public function makeActive(Request $request, $id)
{
$category = Category::findOrFail($id);
if(request('active') === true)
{
$category->active = 1;
}else{
$category->active = 0;
}
$category->save();
}
I am getting below error. Script has been attached in screenshot.
Please let me know the solution step by step.
<span class="left tooltip_link"><i class="fa fa-question-circle"></i></span>
</td>
</tr>
<tr>
<td class="text-right" width="200">
<label for="ContentPlaceHolder1_txtDescription" id="ContentPlaceHolder1_lblDescription">Description : </label>
</td>
<td>
<input name="ctl00$ContentPlaceHolder1$txtDescription" type="text" value="290802016 " maxlength="200" id="ContentPlaceHolder1_txtDescription" title="Enter Description" class="left" style="width:150px;" />
<span class="left tooltip_link"><i class="fa fa-question-circle"></i></span>
</td>
</tr>
</table>
</div>
<div class="clear">
<span id="ContentPlaceHolder1_rfvFileName" class="FontClass" style="display:none;"></span>
<span id="ContentPlaceHolder1_rfvValidFileName" class="FontClass" style="display:none;"></span>
<span id="ContentPlaceHolder1_rfvDesc" class="FontClass" style="display:none;"></span>
<span id="ContentPlaceHolder1_rfvValidDescription" class="FontClass" style="display:none;"></span>
</div>
</div>
</div>
<div class="mar-bot-10 clear">
<input type="submit" name="ctl00$ContentPlaceHolder1$btnAdd" value="Save" onclick="DataTool.ValidateAndToggleVisiblity(Page_ClientValidate('valForSchedule'));WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("ctl00$ContentPlaceHolder1$btnAdd", "", true, "valForSchedule", "", false, false))" id="ContentPlaceHolder1_btnAdd" title="Click to add new schedule" class="btn-clear" />
<input type="submit" name="ctl00$ContentPlaceHolder1$btnReset" value="Reset" id="ContentPlaceHolder1_btnReset" class="btn-clear" />
</div>
|0|hiddenField|__EVENTTARGET||0|hiddenField|__EVENTARGUMENT||452|hiddenField|__VIEWSTATE|/wEPBbgCSDRzSUFBQUFBQUFFQVB2UHlNL0thV2x1YW1CbWJHUm96cDhpeHBRR0lwZ1lnYVJBR3I4WWt4eG5abDVlYWxGR1NXNE9xNWROY1VsUmZsNjZuWDlBaUdkd3ZHTktibWFlalQ1VVRLRkd3U1pSSWFNb05jMVdYVmxkSVQ4dk9TY3pPZHRXM1NjLzNiKzBSRVBUV3QwT3lGUUFzbTMwRSsxU21KaEJock9BMUxQS1pwU1VGRmpwNjJjbUp4YVZGaGNVNk9VWGxHUVdKeFlVRk9zbDUrZW1NQW1CbExLSFpSWm5KdVdrWnFRd0NRUDU4a3pwS1V4eU1JWUNqS0VJWTZoQ3ZRRzJKU1Mxb29SVnhMa29OYkVrVmNIVktUaElJVGc1SXpXbE5DYzFKUVVBY3dqaFdRSUJBQUE9ZBpxCDqRIEungR80i/4HRMqKquCH|8|hiddenField|__VIEWSTATEGENERATOR|29933832|0|asyncPostBackControlIDs|||0|postBackControlIDs|||72|updatePanelIDs||tctl00$ContentPlaceHolder1$UpdatePanel1,ContentPlaceHolder1_UpdatePanel1|0|childUpdatePanelIDs|||71|panelsToRefreshIDs||ctl00$ContentPlaceHolder1$UpdatePanel1,ContentPlaceHolder1_UpdatePanel1|4|asyncPostBackTimeout||3600|18|formAction||./AddSchedule.aspx|12|pageTitle||Add Schedule|57|arrayDeclaration|Page_ValidationSummaries|document.getElementById("ContentPlaceHolder1_vldSummary")|58|arrayDeclaration|Page_Validators|document.getElementById("ContentPlaceHolder1_rfvFileName")|63|arrayDeclaration|Page_Validators|document.getElementById("ContentPlaceHolder1_rfvValidFileName")|54|arrayDeclaration|Page_Validators|document.getElementById("ContentPlaceHolder1_rfvDesc")|66|arrayDeclaration|Page_Validators|document.getElementById("ContentPlaceHolder1_rfvValidDescription")|194|scriptBlock|ScriptPath|/data/ScriptResource.axd?d=PIkWxDViaZE5PI1d5VC9u3oaIGXTi2MiwQcE00IlXrZtNMOjZbsuJ0QIWWw4HReSlnuBaIUBUZZbJyN_wtr3SycmM_LR-6SrO9qBExQmsX44PlsjganwUlmgp8zJhCIB2B9n40PUePHmOsGPVqSntMHbDWA1&t=1c6690ce|267|scriptStartupBlock|ScriptContentNoTags|
(function(id) {
var e = document.getElementById(id);
if (e) {
e.dispose = function() {
Array.remove(Page_ValidationSummaries, document.getElementById(id));
}
e = null;
}
})('ContentPlaceHolder1_vldSummary');
|281|scriptStartupBlock|ScriptContentNoTags|
var Page_ValidationActive = false;
if (typeof(ValidatorOnLoad) == "function") {
ValidatorOnLoad();
}
function ValidatorOnSubmit() {
if (Page_ValidationActive) {
return ValidatorCommonOnSubmit();
}
else {
return true;
}
}
|184|scriptStartupBlock|ScriptContentNoTags|
document.getElementById('ContentPlaceHolder1_rfvFileName').dispose = function() {
Array.remove(Page_Validators, document.getElementById('ContentPlaceHolder1_rfvFileName'));
}
|194|scriptStartupBlock|ScriptContentNoTags|
document.getElementById('ContentPlaceHolder1_rfvValidFileName').dispose = function() {
Array.remove(Page_Validators, document.getElementById('ContentPlaceHolder1_rfvValidFileName'));
}
|176|scriptStartupBlock|ScriptContentNoTags|
document.getElementById('ContentPlaceHolder1_rfvDesc').dispose = function() {
Array.remove(Page_Validators, document.getElementById('ContentPlaceHolder1_rfvDesc'));
}
|200|scriptStartupBlock|ScriptContentNoTags|
document.getElementById('ContentPlaceHolder1_rfvValidDescription').dispose = function() {
Array.remove(Page_Validators, document.getElementById('ContentPlaceHolder1_rfvValidDescription'));
}
|90|onSubmit||if (typeof(ValidatorOnSubmit) == "function" && ValidatorOnSubmit() == false) return false;|6|expando|document.getElementById('ContentPlaceHolder1_vldSummary')['displaymode']|"List"|16|expando|document.getElementById('ContentPlaceHolder1_vldSummary')['validationGroup']|"valForSchedule"|37|expando|document.getElementById('ContentPlaceHolder1_rfvFileName')['controltovalidate']|"ContentPlaceHolder1_txtScheduleName"|3|expando|document.getElementById('ContentPlaceHolder1_rfvFileName')['focusOnError']|"t"|27|expando|document.getElementById('ContentPlaceHolder1_rfvFileName')['errormessage']|"Schedule name is required"|6|expando|document.getElementById('ContentPlaceHolder1_rfvFileName')['display']|"None"|16|expando|document.getElementById('ContentPlaceHolder1_rfvFileName')['validationGroup']|"valForSchedule"|39|expando|document.getElementById('ContentPlaceHolder1_rfvFileName')['evaluationfunction']|"RequiredFieldValidatorEvaluateIsValid"|2|expando|document.getElementById('ContentPlaceHolder1_rfvFileName')['initialvalue']|""|37|expando|document.getElementById('ContentPlaceHolder1_rfvValidFileName')['controltovalidate']|"ContentPlaceHolder1_txtScheduleName"|3|expando|document.getElementById('ContentPlaceHolder1_rfvValidFileName')['focusOnError']|"t"|129|expando|document.getElementById('ContentPlaceHolder1_rfvValidFileName')['errormessage']|"Schedule name should only contain \u0027_\u0027 as special character and alpha numeric values with max lenght of 50 characters."|6|expando|document.getElementById('ContentPlaceHolder1_rfvValidFileName')['display']|"None"|16|expando|document.getElementById('ContentPlaceHolder1_rfvValidFileName')['validationGroup']|"valForSchedule"|43|expando|document.getElementById('ContentPlaceHolder1_rfvValidFileName')['evaluationfunction']|"RegularExpressionValidatorEvaluateIsValid"|23|expando|document.getElementById('ContentPlaceHolder1_rfvValidFileName')['validationexpression']|"^[A-Za-z0-9 _]{1,50}$"|36|expando|document.getElementById('ContentPlaceHolder1_rfvDesc')['controltovalidate']|"ContentPlaceHolder1_txtDescription"|3|expando|document.getElementById('ContentPlaceHolder1_rfvDesc')['focusOnError']|"t"|25|expando|document.getElementById('ContentPlaceHolder1_rfvDesc')['errormessage']|"Description is required"|6|expando|document.getElementById('ContentPlaceHolder1_rfvDesc')['display']|"None"|16|expando|document.getElementById('ContentPlaceHolder1_rfvDesc')['validationGroup']|"valForSchedule"|39|expando|document.getElementById('ContentPlaceHolder1_rfvDesc')['evaluationfunction']|"RequiredFieldValidatorEvaluateIsValid"|2|expando|document.getElementById('ContentPlaceHolder1_rfvDesc')['initialvalue']|""|36|expando|document.getElementById('ContentPlaceHolder1_rfvValidDescription')['controltovalidate']|"ContentPlaceHolder1_txtDescription"|3|expando|document.getElementById('ContentPlaceHolder1_rfvValidDescription')['focusOnError']|"t"|128|expando|document.getElementById('ContentPlaceHolder1_rfvValidDescription')['errormessage']|"Description should only contain \u0027_\u0027 as special character and alpha numeric values with max lenght of 200 characters."|6|expando|document.getElementById('ContentPlaceHolder1_rfvValidDescription')['display']|"None"|16|expando|document.getElementById('ContentPlaceHolder1_rfvValidDescription')['validationGroup']|"valForSchedule"|43|expando|document.getElementById('ContentPlaceHolder1_rfvValidDescription')['evaluationfunction']|"RegularExpressionValidatorEvaluateIsValid"|24|expando|document.getElementById('ContentPlaceHolder1_rfvValidDescription')['validationexpression']|"^[A-Za-z0-9 _]{1,200}$"|
You need to correlate at least __VIEWSTATE and __VIEWSTATEGENERATOR values with i.e. Regular Expression Extractor like you use timestamps for "ScheduleName" and "Description".
Search the web for "JMeter Correlation"- there is a plenty of information on the topic.
For .NET applications specifics you might also want to check out ASP.NET Login Testing with JMeter guide
I stumbled upon some code that has jQuery validation which is triggered after an ajax call that adds items to the DOM. The validation is working but the message is missing. just the field is highlighted. I have been playing with this for a while to get it to work, but so far no luck. Any ideas, thoughts appreciated.
$('#add-other-income-link').click(function (event) {
event.preventDefault();
var otherIncomesCount = $('#numberOfNewOtherIncomes').val();
$('div[hideCorner = yep]').show();
var url = $(this).attr('href');
if (url) {
$.ajax({
url: url,
type: 'GET',
dataType: 'html',
success: function (data) {
$('#additional-other-income').append(data);
var count = otherIncomesCount;
var id = 0;
$('#additional-other-income').find('table.other-income-table').each(function (i, item) {
id = $(item).find('input.other-income-id');
var additionalIncomeTypeIdLabel = $(item).find('label.other-income-type-id-label');
var amountLabel = $(item).find('label.other-income-amount-label');
var additionalIncomeTypeIdMenu = $(item).find('select.other-income-type-id');
var amountTextBox = $(item).find('input.other-income-amount');
var idIndexer = 'OtherIncome_' + count + '__';
var nameIndexer = 'OtherIncome[' + count + '].';
var indexer = '[' + i + ']';
id.attr('id', idIndexer + 'Id').attr('name', nameIndexer + 'Id');
additionalIncomeTypeIdLabel.attr('for', idIndexer + 'AdditionalIncomeTypeId');
amountLabel.attr('for', idIndexer + 'Amount');
additionalIncomeTypeIdMenu.attr('id', idIndexer + 'AdditionalIncomeTypeId').attr('name', nameIndexer + 'AdditionalIncomeTypeId');
amountTextBox.attr('id', idIndexer + 'Amount').attr('name', nameIndexer + 'Amount').attr('data-val', 'true');
++count;
addOtherIncomeValidation(item);
});
The validation succeeds for both required on additionalIncomeTypeIDMenu, and required and positive on amountTextBox, but the messages for both fail to show up:
function addOtherIncomeValidation(container) {
if (container) {
var additionalIncomeTypeIdMenu = $(container).find('select.other-income-type-id');
var amountTextBox = $(container).find('input.other-income-amount');
$(additionalIncomeTypeIdMenu).rules('add', {
required: true,
messages: {
required: 'Please select an income type'
}
});
$(amountTextBox).rules('add', {
required: true,
positive: true,
messages: { positive: 'must be positive number'
}
});
}
}
BTW The ajax call returns a partial EditorTemplate here, which you can see uses ValidationMessageFor.
<div class="other-income" style="margin-bottom: 10px;">
<table class="other-income-table">
<tr>
<td>
#Html.HiddenFor(x => x.Id, new { #class = "other-income-id" })
#Html.LabelFor(x => x.AdditionalIncomeTypeId, "Type:", new { #class = "other-income-type-id-label" })
<br />#Html.ValidationMessageFor(x => x.AdditionalIncomeTypeId)
</td>
<td>
#Html.LabelFor(x => x.Amount, "Amount:", new { #class = "other-income-amount-label" })
<br />#Html.ValidationMessageFor(x => x.Amount)
</td>
<td> </td>
</tr>
<tr>
<td>#Html.DropDownListFor(x => x.AdditionalIncomeTypeId, new SelectList(Model.AdditionalIncomeTypes, "Value", "Text", Model.AdditionalIncomeTypeId), "--- Select One ---", new { #class = "other-income-type-id" })</td>
<td>
#Html.EditorFor(x => x.Amount, "Money", new { AdditionalClasses = "other-income-amount" })
</td>
<td>
#{
int? otherIncomeId = null;
var removeOtherIncomeLinkClasses = "remove-other-income-link";
if (Model.Id == 0)
{
removeOtherIncomeLinkClasses += " new-other-income";
}
else
{
otherIncomeId = Model.Id;
}
}
#Html.ActionLink("Remove", "RemoveOtherIncome", "Applicant", new { applicationId = Model.ApplicationId, otherIncomeId = otherIncomeId }, new { #class = removeOtherIncomeLinkClasses })<img class="hide spinner" src="#Url.Content("~/Content/Images/ajax-loader_16x16.gif")" alt="Deleting..." style="margin-left: 5px;" />
</td>
</tr>
</table>
HTML:
<div id="OtherIncome" class="applicant-section">
<h2 class="header2">Other Income</h2>
<div class="cornerForm">
<div class="other-income" style="margin-bottom: 10px;">
<table class="other-income-table">
<tr>
<td>
<input class="other-income-id" data-val="true" data-val-number="The field Id must be a number." id="OtherIncome_0__Id" name="OtherIncome[0].Id" type="hidden" value="385" />
<label class="other-income-type-id-label" for="OtherIncome_0__AdditionalIncomeTypeId">Type:</label>
<br /><span class="field-validation-valid" data-valmsg-for="OtherIncome[0].AdditionalIncomeTypeId" data-valmsg-replace="true"></span>
</td>
<td>
<label class="other-income-amount-label" for="OtherIncome_0__Amount">Amount:</label>
<br /><span class="field-validation-valid" data-valmsg-for="OtherIncome[0].Amount" data-valmsg-replace="true"></span>
</td>
<td> </td>
</tr>
<tr>
<td><select class="other-income-type-id" data-val="true" data-val-number="The field AdditionalIncomeTypeId must be a number." id="OtherIncome_0__AdditionalIncomeTypeId" name="OtherIncome[0].AdditionalIncomeTypeId"><option value="">--- Select One ---</option>
<option value="1">Alimony</option>
<option value="2">Child Support</option>
<option value="3">Disability</option>
<option value="4">Investments</option>
<option selected="selected" value="5">Rental Income</option>
<option value="6">Retirement</option>
<option value="7">Secondary Employment</option>
<option value="8">Separate Maintenance</option>
</select></td>
<td>
<input class="money other-income-amount" data-val="true" data-val-number="The field Amount must be a number." id="OtherIncome_0__Amount" name="OtherIncome[0].Amount" style="" type="text" value="0.00" />
</td>
<td>
<a class="remove-other-income-link" href="/Applicant/RemoveOtherIncome/XNxxxxx753/385">Remove</a><img class="hide spinner" src="/Content/Images/ajax-loader_16x16.gif" alt="Deleting..." style="margin-left: 5px;" />
</td>
</tr>
</table>
</div>
<div class="other-income" style="margin-bottom: 10px;">
<table class="other-income-table">
<tr>
<td>
<input class="other-income-id" data-val="true" data-val-number="The field Id must be a number." id="OtherIncome_1__Id" name="OtherIncome[1].Id" type="hidden" value="412" />
<label class="other-income-type-id-label" for="OtherIncome_1__AdditionalIncomeTypeId">Type:</label>
<br /><span class="field-validation-valid" data-valmsg-for="OtherIncome[1].AdditionalIncomeTypeId" data-valmsg-replace="true"></span>
</td>
<td>
<label class="other-income-amount-label" for="OtherIncome_1__Amount">Amount:</label>
<br /><span class="field-validation-valid" data-valmsg-for="OtherIncome[1].Amount" data-valmsg-replace="true"></span>
</td>
<td> </td>
</tr>
<tr>
<td><select class="other-income-type-id" data-val="true" data-val-number="The field AdditionalIncomeTypeId must be a number." id="OtherIncome_1__AdditionalIncomeTypeId" name="OtherIncome[1].AdditionalIncomeTypeId"><option value="">--- Select One ---</option>
<option selected="selected" value="1">Alimony</option>
<option value="2">Child Support</option>
<option value="3">Disability</option>
<option value="4">Investments</option>
<option value="5">Rental Income</option>
<option value="6">Retirement</option>
<option value="7">Secondary Employment</option>
<option value="8">Separate Maintenance</option>
</select></td>
<td>
<input class="money other-income-amount" data-val="true" data-val-number="The field Amount must be a number." id="OtherIncome_1__Amount" name="OtherIncome[1].Amount" style="" type="text" value="22.00" />
</td>
<td>
<a class="remove-other-income-link" href="/Applicant/RemoveOtherIncome/XN42093753/412">Remove</a><img class="hide spinner" src="/Content/Images/ajax-loader_16x16.gif" alt="Deleting..." style="margin-left: 5px;" />
</td>
</tr>
</table>
</div>
<div id="additional-other-income"></div>
<input id="numberOfNewOtherIncomes" name="numberOfNewOtherIncomes" type="hidden" value="0" />
<input data-val="true" data-val-number="The field OriginalOtherIncomeTotal must be a number." id="OriginalOtherIncomeTotal" name="OriginalOtherIncomeTotal" type="hidden" value="22.0000" />
<a class="editable-link" href="/Applicant/AddOtherIncome?appId=XNxxxxx753" id="add-other-income-link">Add Other Income</a>
</div> </div>
Validation code:
$.validator.addMethod('positive', function(value, element) {
var check = true;
if (value < 0) {
check = false;
}
return this.optional(element) || check;
}, "Value must be a positive number."
);
I didn't think I would find my own answer but I did. first I have to apologize for my response to #Sparky with his request for rendered HTML. I did a "View page source" but that didn't include all the stuff which was added from the ajax call after the server delivered it's stuff. I suspect if I did include the extra DOM stuff at first, you would have pinpointed the issue sooner. My bad. Now on to the answer.
It appears that when injecting an EditorTemplate into the DOM in the way shown above, it doesn't properly process the page like you would expect in MVC. The code for #Html.ValidationMessageFor simply does not get parsed AT ALL. I am a little new to validation as you can see so I don't know why it behaves this way. To solve my problem here is what I did:
I updated my Editor Template slightly to look like this:
<div class="other-income" style="margin-bottom: 10px;">
<table class="other-income-table">
<tr>
<td>
#Html.HiddenFor(x => x.Id, new { #class = "other-income-id" })
#Html.LabelFor(x => x.AdditionalIncomeTypeId, "Type:", new { #class = "other-income-type-id-label" })
<br />
#Html.ValidationMessageFor(x=>x.AdditionalIncomeTypeId)
<span id="AdditionalIncomeTypeIdValidation"></span>
</td>
<td>
#Html.LabelFor(x => x.Amount, "Amount:", new { #class = "other-income-amount-label" })
<br />
#Html.ValidationMessageFor(x=>x.Amount)
<span id="amountValidation"></span>
</td>
<td> </td>
</tr>
<tr>
<td>#Html.DropDownListFor(x => x.AdditionalIncomeTypeId, new SelectList(Model.AdditionalIncomeTypes, "Value", "Text", Model.AdditionalIncomeTypeId), "--- Select One ---", new { #class = "other-income-type-id" })</td>
<td>
#Html.EditorFor(x => x.Amount, "Money", new { AdditionalClasses = "other-income-amount" })
</td>
<td>
#{
int? otherIncomeId = null;
var removeOtherIncomeLinkClasses = "remove-other-income-link";
if (Model.Id == 0)
{
removeOtherIncomeLinkClasses += " new-other-income";
}
else
{
otherIncomeId = Model.Id;
}
}
#Html.ActionLink("Remove", "RemoveOtherIncome", "Applicant", new { applicationId = Model.ApplicationId, otherIncomeId = otherIncomeId }, new { #class = removeOtherIncomeLinkClasses })<img class="hide spinner" src="#Url.Content("~/Content/Images/ajax-loader_16x16.gif")" alt="Deleting..." style="margin-left: 5px;" />
</td>
</tr>
</table>
Notice the new span tags, which is are added next to the #Html.ValidationMessageFor.
then in my success function from the ajax call in javascript I made these changes:
$('#add-other-income-link').click(function (event) {
event.preventDefault();
var count = $('#numberOfNewOtherIncomes').val();
$('div[hideCorner = yep]').show();
var url = $(this).attr('href');
if (url) {
$.ajax({
url: url,
type: 'GET',
dataType: 'html',
success: function (data) {
$('#additional-other-income').append(data);
var id = 0;
$('#additional-other-income').find('table.other-income-table').each(function (i, item) {
id = $(item).find('input.other-income-id');
var additionalIncomeTypeIdLabel = $(item).find('label.other-income-type-id-label');
var amountLabel = $(item).find('label.other-income-amount-label');
var additionalIncomeTypeIdMenu = $(item).find('select.other-income-type-id');
var amountTextBox = $(item).find('input.other-income-amount');
var amountValidation = $(item).find('#amountValidation');
var typeIdValidation = $(item).find('#AdditionalIncomeTypeIdValidation');
var idIndexer = 'OtherIncome_' + count + '__';
var nameIndexer = 'OtherIncome[' + count + '].';
var indexer = '[' + i + ']';
amountValidation.attr('class', 'field-validation-valid')
.attr('data-valmsg-for', nameIndexer + 'Amount')
.attr('data-valmsg-replace','true');
typeIdValidation.attr('class', 'field-validation-valid')
.attr('data-valmsg-for', nameIndexer + 'AdditionalIncomeTypeId')
.attr('data-valmsg-replace','true');
id.attr('id', idIndexer + 'Id').attr('name', nameIndexer + 'Id');
additionalIncomeTypeIdLabel.attr('for', idIndexer + 'AdditionalIncomeTypeId');
amountLabel.attr('for', idIndexer + 'Amount');
additionalIncomeTypeIdMenu.attr('id', idIndexer + 'AdditionalIncomeTypeId').attr('name', nameIndexer + 'AdditionalIncomeTypeId');
amountTextBox.attr('id', idIndexer + 'Amount').attr('name', nameIndexer + 'Amount').attr('data-val', 'true');
++count;
addOtherIncomeValidation(item);
notice I am manually adding in the missing validation spans that were not rendering. Now the validation message shows up at validation! Yay. I am not sure however that this is the best fix. It looks and smells like a hack, but I got it to work. I am sure it can be done a better way. Thanks again for interaction and feedback.