React generating UI from object - console.log works but nothing generated - javascript

I have code that generates my react UI, and it works with a list of languages to generate checkboxes for language selection. For example when in state
languages = {English: true, French: false}
however when i change it to an object which contains the values from the DB, I get no error, but nothing loads.
[
{
"language_name": "English",
"lang_num": 1,
"checked": false
},
{
"language_name": "Mandarin Chinese",
"lang_num": 2,
"checked": false
},
]
The code is:
{
Object.entries(this.props.languages).forEach(([key, value]) => {
console.log(this.props.languages[key].language_name),
<label id="checkbox-margin">
<input
type="checkbox"
value={this.props.languages[key].language_name}
checked={this.props.languages[key].checked}
onChange={this.handleLangClick}
/> {this.props.languages[key].language_name}
</label>
}
)
The console.log lists each language string fine, i get them all printed in the console, but nothing is generated on the UI. Any idea why?
Thanks!

Try to use the map function to iterate through the array:
{this.props.languages ? this.props.languages.map(language => {
return (
<label id="checkbox-margin" key={language.lang_num}>
<input
type="checkbox"
value={language.language_name}
checked={language.checked}
onChange={this.handleLangClick}
/> {language.language_name}
</label>
)}) : 'Loading languages...'
}

Related

v-if span not displaying when errors are filled vuejs

so I have an error array in data and whenever user focuses out of an input it checks if its empty. If it is empty it add's an object to the error array like so:
[
"0": {
"product_name": {
"message": "to polje je obvezno"
},
"barcode": {
"message": "to polje je obvezno"
}
},
"1": {
"barcode": {
"message": "to polje je obvezno"
}
},
"2": {
"product_name": {
"message": "to polje je obvezno"
}
}
]
so the 0,1,2 stand for the index of the item because I have a v-for loop and then product_name or barcode stand for the input in that item/index.(component is at the end of the post if you need it). So now I am trying to display an error when product_name or barcode exists.
I am trying like this:
<span class="tooltip"
v-if="errors && errors[index] && errors[index]['product_name']" style="left: 5px">
test123 (this is product_name error, above index is the current index in v-for so 0 or 1 or 2...)
</span>
<span class="tooltip"
v-if="errors && errors[index] && errors[index]['product_name']"style="left: 5px">
test123 (this is barcode error, above index is the current index in v-for so 0 or 1 or 2...)
</span>
but it doesnt display the span
component:
<tr v-for="(item, index) in documentItems" :key="item.id">
<td>{{index + 1}}.</td>
<td>
<div>
<textarea v-model="item.barcode"
#focusout="checkInput('barcode',index)"
cols="15" rows="2">
</textarea>
<span v-if="errors && errors[index] && errors[index]['barcode']">
test123
</span>
</div>
</td>
<td>
<div>
<textarea v-model="item.product_name"
#focusout="checkInput('product_name',index)"
cols="15" rows="2">
</textarea>
<span v-if="errors && errors[index] && errors[index]['product_name']">
test123
</span>
</div>
</td>
</tr>
EDIT: is it possible that my checkInput is the problem? this is how I created errors:
checkInput(name, itemIndex){
if(this.documentItems[itemIndex][name] == null){
this.errors[itemIndex][name] = { message: 'to polje je obvezno'}
};
//testing
console.log(this.errors[itemIndex][name]); //works
if(this.errors[1]['product_name']){
console.log("yes"); //works
}
},
EDIT2:
the spans show if I define error object like so:
errors: {
0: {
barcode: '',
product_name: ''
},
1: {
barcode: '',
product_name: ''
}
},
but if I do it with a for loop span don't show (I made a for loop in method where I retrive all the documentItems and gets fired on mounted()):
for(var i = 0;i < response.data.documentItems[0].length;i++){
this.errors[i] = {
barcode: '',
product_name: '',
}
}
Your problem roots in a vue reactivity caveat mentioned in their documentation.
https://v2.vuejs.org/v2/guide/reactivity.html#For-Objects
Vue will create proxy-like objects (a pattern similar to Observer using Object.defineProperty) for every field that is defined in your data function before anything runs, when you manually add fields using this.foo = bar (or something similar), if 'foo' key is not already available in your data field, vue will not make it reactive, hence it will not update your DOM when it changes.
You can achieve what you want in a couple of workarounds.
First way which also mentioned in their documentations is to create whole errors object with Object.assign or spread syntax and re-assign your field in data.
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
This solution is similar to treating a field like its immutable
So you can fix your checkInput method with the following change:
checkInput(name, itemIndex){
if(this.documentItems[itemIndex][name] == null){
const newErrorForName = { [name]: { message: 'to polje je obvenzo' }};
this.errors = Object.assign({}, {...this.errors, [itemIndex]: newErrorForName })
};
//testing
console.log(this.errors[itemIndex][name]); //works
if(this.errors[1]['product_name']){
console.log("yes"); //works
}
},
This is because vue cant understand manual object property add/delete.
Your second way is to use an array for errors instead of an object.
This is probably a better idea since your errors object is really an array.
it has fixed integer zero based indexes!

How do I find a radio button by value to check it with another element - VueJS

I apologize for the title being a little hard to understand. I had a hard time explaining it in one line. But here's what I'm trying to do.
I'm developing a screen within my app that supports a barcode gun reader. Barcode guns can only interact with textfields. And then through a text field(hidden) I can pass a custom barcode that instructs the UI to do something. Here is the UI explanation for clarity:
I have a radio button group with 2 options (yes and no)
I have a hidden textfield to accept the barcode gun read
I have a barcode for "yes" and another for "no"
If I scan the "yes" barcode, the radio button option with value = "Yes", should be checked
If I scan the "no" barcode, the radio button option with value = "No", should be checked
I initially thought that by changing the v-model to the correct value, it will do it, but it didn't check it. Likewise, by changing the v-model.value to true or false it will check to its appropriate value. But no cigar.
My idea on how this would work is by (pseudocode)
if querySelector with name ragrouphidden.value = "Yes" then find the option whose value is Yes and option.checked = true
else if querySelector with name ragrouphidden.value = "No" then find the option whose value is No and option.checked = true
The "find" part is what eludes me, or maybe there is an easier way.
Here's some relevant code
Template
<div>
<q-input
class="hidden-field"
v-model="ragrouphidden"
name="ragrouphidden"
#change="raSelectOption()">
</q-input>
<div>
<label class="col-6 text-weight-medium">Mark for Remote Adjudication</label>
<div>
<q-option-group
v-model="ragroup"
:options="raoptions"
#check="raRules($event.target.value)"/>
</div>
</div>
</div>
Script
data() {
return {
ragrouphidden: "",
ragroup: null,
raoptions: [
{
label: "Yes",
value: true
},
{
label: "No",
value: false
}
],
}
},
methods: {
raSelectOption() {
setTimeout(() => {
let hasFocus = document.querySelector("input[name=ragrouphidden]");
hasFocus.focus();
}, 500);
if (
document.querySelector("input[name=ragrouphidden]").value === "*yes*"
) {
this.ragroup.value = true; //this is what I need
} else if (
document.querySelector("input[name=ragrouphidden]").value === "*no*"
) {
this.ragroup.value = false; //This as well
}
},
}
Hopefully it makes sense to you guys. Thanks in advance.
You don't need to use ragroup.value to set the model value here. You can simply do this.ragroup = true; and vue will automatically set the q-option-group selected value for you behind the scene.
A simple demo with dynamic checkbox:
var demo = new Vue({
el: '#demo',
data: {
checked: [],
categories: [{ Id: 1 }, { Id: 2 }]
},
mounted(){ this.checked = [2] }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="demo">
<ul>
<li v-for="c in categories">
<input type="checkbox" :value="c.Id" :id="c.Id" v-model="checked" />
{{c.Id}}
</li>
</ul>
</div>

vue.js VeeValidate - custom validator correctly compiles errors but does not toggle error class

I've written a custom validator like this:
created () {
this.$validator.extend('validateCert', {
getMessage: (field) => {
return field + ' is not a valid certificate';
},
validate: (value) => {
return value.startsWith('-----BEGIN CERTIFICATE-----') && value.endsWith('-----END CERTIFICATE-----');
}
});
}
I've attached it to a text area inside a modal:
<div class="pb-3 mr-4">
<b-form-textarea
type="text"
v-validate="'required|validateCert'"
data-vv-validate-on="change"
v-model.trim="signedCerts[index]"
data-vv-scope="uploadCert"
:name="'certificate_' + index"
:class="[{'is-invalid': errors.has('certificate_' + index)}]"
rows="15"/>
<fr-validation-error :validatorErrors="errors" :fieldName="'certificate_' + index"></fr-validation-error>
</div>
Then - on button click I do the following:
uploadCerts (event) {
let idmInstance = this.getRequestService(),
body = {
fromCSR: true,
certs: _.each(this.signedCerts, (cert) => {
JSON.stringify(cert);
})
};
this.$validator.validateAll('uploadCert').then((valid) => {
// Prevent modal from closing
event.preventDefault();
if (valid) { // some more logic
If I inspect the computed errors object, I will see my failed validation:
{
"items": [
{
"id": "19",
"field": "certificate_0",
"msg": "certificate_0 is not a valid certificate",
"rule": "validateCert",
"scope": "uploadCert",
"regenerate": {
"_custom": {
"type": "function",
"display": "<span>ƒ</span> regenerate()"
}
}
}
]
}
and the value of 'valid' (either true or false) is accurate at all times. I'm just not seeing my error classes being triggered.
Hard to completely answer the question because part of it depends on what happens in fr-validation-error, but I think the problem is how you're using scopes.
When you define data-vv-scope="uploadCert" that means that every reference to field-name has to be prefaced by uploadCert. in errors. So when you specify in your :class that errors.has('certificates_'+index), you have to change that to errors.has('uploadCert.certificates_'+index).
Here's how it would look in full (minus the bootstrap-vue and multiple fields bits):
<textarea
v-validate="'required|validateCert'"
data-vv-validate-on="change"
data-vv-scope="uploadCert"
v-model.trim="signedCert"
name="certificate"
class="form-control"
:class="{ 'is-invalid': errors.has('uploadCert.certificate') }"
rows="10"
>
</textarea>
Full working example for one certificate upload field: https://codesandbox.io/s/z2owy0r2z3

Vue.js checkboxes not working with model that uses Laravel SparkForm

I have the following checkboxes set up:
<label v-for="service in team.services">
<input type="checkbox" v-model="form.services" :id="service.name" :value="service.id"/>
</label>
These are displayed correctly but when checking one checkbox they all get checked as the form.services model gets set to false / true.
However, when changing the model to another data attribute e.g. 'services' everything works as expected. Any reason why this isn't working within SparkForm?
Example data:
data: function() {
return {
form: new SparkForm({
userId: null,
services: [] // always only gets set as true / false
}),
services: [], // works fine
}
},
new Vue({
el: '#app',
data(){
return {
form : {
services : []
},
team : {
services : [
{
"name" : "Service name #1",
"id" : 1
},
{
"name" : "Service name #2",
"id" : 2
}
]
}
}
}
});
<div id="app">
<label v-for="service in team.services">
<input type="checkbox" v-model="form.services" :value="service.id"/>{{service.name}}
</label>
{{form.services}}
</div>

ngstrap typeahead unordered multiple keyword search

I having an issue with ngstrap typeahead with the scenario below:
var companyItem= [
{
"item_id": 1,
"item_name": "mobile phone middle nokia",
"company_id": 1,
},
{
"item_id": 2,
"item_name": "mobile phone iphone",
"company_id": 1,
},
{
"item_id": 8,
"item_name": "mobile phone samsung",
"company_id": 1,
},
{
"item_id": 9,
"item_name": "apple watch",
"company_id": 1,
}
]
My Markup :
<input type="text" class="form-control" name="itemName" id="itemName" ng-model="item.itemName" data-min-length="0" bs-options="item as item.item_name for item in companyItem | filter:{item_name:$viewValue}:customCompare" bs-typeahead="setCustomerData" data-watch-options="true" data-container="body" autocomplete="off" ng-readonly="readOnly" required>
and my scripts is :
$scope.customCompare = function(itemName, viewValue) {
var keyword = viewValue.split(" ");
var partialMatch = false;
for(var i=0;i<keyword.length;i++){
console.log('keyword'+i+' '+keyword[i]);
console.log('itemName '+itemName);
console.log('keyword[i].indexOf(itemName) > -1 '+itemName.indexOf(keyword[i].toString()));
if(itemName.indexOf(keyword[i].toString()) > -1){
console.log(' <<>>---------------');
partialMatch =true;
}
}
return partialMatch;
}
I've try to search with keyword 'mobile iphone' in the input text but there're no result.
This return true as I'm write in the console log but the record not showing
Anyway if 'phone iphone' it's working like default typeahead
Anything I'm done wrong or this approach is not working
https://plnkr.co/edit/3iJwREetLMnTup24Sbtd
Thanks in Advance.
I got another solution as I saw "filter: 'bsAsyncFilter'" in typeahead.js so I override in my js by bypass the filter because now I'm using async data through api :
function CustomTypeaheadFilter ($filter) {
return function(array, expression, comparator) {
if(array && angular.isFunction(array.then)) {
return array.then(function(results) {
console.log(results);
// return $filter('filter')(results, expression, comparator)
return results;
});
} else {
//return $filter('filter')(array, expression, comparator);
return array;
}
}
};
Mark up:
<input type="text" class="form-control" name="itemName" id="itemName" ng-model="item.itemName" data-min-length="0" bs-options="item as item.item_name for item in getItemfromDB($viewValue)" bs-typeahead="setCustomerData" data-filter="CustomTypeaheadFilter" autocomplete="off" required>
filter option is not declare in ngstrap document, I'm posted the issue on the github anyway hope ngstrap will raise this option to the document then.
Ng-strap adds a default filter that uses a default comparator after your item as item.item_name for item in companyItem | filter:{item_name:$viewValue}:customCompare.
One hacky solution is to bypass the default filter.
$scope.alwaysTrue = function() { return true; }
<input ... bs-options="item as item.item_name for item in companyItem | filter:{item_name:$viewValue}:customCompare" data-comparator="alwaysTrue" ...>
A cleaner solution would be to set data-comparator="customCompare". Sadly, that doesn't work because here contains :$viewValue, not :{item_name: $viewValue}. So, customCompare never gets to process a whole object.
The API can and should be improved, and you should open an issue about it on github.

Categories