Conditionally set v-model in Vue - javascript

I have a series of inputs that could be either checkboxes or radio buttons, depending on a value in the data of my vue component.
In particular, I have a Question component, and questions may accept only one answer or multiple answers. I have a selected_answers array in my data, and I was thinking I could have the checkboxes target it as their v-model, while the radio buttons could target selected_answers[0]. This way, I don't have to copy-paste che input elements and just change their type and v-model.
So, my solution would look something like this:
<input
:type="question.accepts_multiple answers ? 'checkbox' : 'radio'"
:id="'ans-' + answer.id"
:value="answer.id"
v-model="question.accepts_multiple_answers ? selected_answers : selected_answers[0]"
/>
However, eslint complains about my code:
'v-model' directives require the attribute value which is valid as LHS
What's a way I can accomplish what I'm trying to do?

You cannot use any advanced code inside of v-model (just a basic string), you could export question.accepts_multiple_answers ? selected_answers : selected_answers[0] to a computed and plug the computed to the v-model.
If you need to have a setter, you will need to write a computed setter, this looks like this
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
Meanwhile, since v-model is just some sugar syntax, you could also replace it with usual :value + #input (depending of the type of the field). I do prefer to use those 2 than v-model nowadays, especially for the kind of limitations that you do have right now.

You can't really do it like that.
I recommend you to set up a variable when loading your component like that:
data()
{
return {
model_string: '',
}
}
Then you give your variable some values depending on your own conditions
created() {
if (your.first.condition) {
this.model_string = your.value;
} else if (your.other.condition) {
this.model_string = your.value;
}
}
After this, you can use it in your view as you wish
<input v-model="model_string" ..... // your attributes>

Ended up figuring it out by myself.
<input
:type="question.accepts_multiple_answers ? 'checkbox' : 'radio'"
:id="'ans-' + answer.id"
:value="question.accepts_multiple_answers ? answer.id : [answer.id]"
v-model="selected_answers"
/>

Related

Vuejs - Assign computed properties to data()

I have an input field that I would like to either be blank or populate depending on the condition
Condition A: new student, blank field
Condition B: existing student, populated field
This data is coming from the data() function or a computed: property. For example:
data () {
return {
studentName: '', // empty student name property (/new student route)
studentPersonality: ''
}
},
computed: {
...mapGetters({
getStudent // existing student object (/edit student route)
})
}
My input field should either be blank if we are arriving from the /new student route, or populate the field with the existing student info, if we're coming from the /edit route.
I can populate the input field by assigning getStudent.name to v-model as shown below.
<input type="text" v-model="getStudent.name">
...and of course clear the field by instead assigning studentName to v-model
<input ... v-model="studentName">
Challenge: How can I use getStudent.name IF it exists, but fall back on the blank studentName data() property if getStudent.name does NOT exist? I have tried:
<input ... v-model="getStudent.name || studentName">
...which seemed to work, but apparently invalid and caused console errors
'v-model' directives require the attribute value which is valid as LHS
What am I doing wrong?
There's really no need to have the input field register to different properties in your vue component.
If you want to have a computed property that is also settable, you can define it using a set & get method.
computed: {
student_name: {
get: function() {
return this.$store.getters.get_student.name
},
set: function(val) {
this.$store.commit('set_student_name');
}
}
}
One other way is to separate the value from the input change handler in the input element itself, in this case you would use the getter as you've set it
<input type="text" :value="getStudent.name" #input="update_name($event.target.value)">
And lastly, if you need to really use two different properties you can set them on a created/activated hook (and answering your original question):
created: function() {
this.studentName = this.getStudent
},
activated: function() {
this.studentName = this.getStudent
}
You'll always need to delegate the update to the store though so I would either go with the get/set computed property, or the value/update separation

Dynamically created custom form components in react

See this gist for the complete picture.
Basically I will have this form:
When you click the plus, another row should appear with a drop down for day and a time field.
I can create the code to add inputs to the form, however I'm having trouble with the individual components (selectTimeInput is a row) actually updating their values.
The onChange in the MultipleDayTimeInput is receiving the correct data, it is just the display that isn't updating. I extremely new to react so I don't know what is causing the display to not update....
I think it is because the SelectTimeInput render function isn't being called because the passed in props aren't being updated, but I'm not sure of the correct way to achieve that.
Thinking about it, does the setState need to be called in the onChange of the MultipleDayTimeInput and the input that changed needs to be removed from the this.state.inputs and readded in order to force the render to fire... this seems a little clunky to me...
When you update the display value of the inputs in state, you need to use this.setState to change the state data and cause a re-render with the new data. Using input.key = value is not the correct way.
Using State Correctly
There are three things you should know about
setState().
Do Not Modify State Directly
For example, this will not re-render a
component:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
The only place where you
can assign this.state is the constructor.
read more from Facebook directly here
I would actually suggest a little bit of a restructure of your code though. It's not really encouraged to have components as part of your state values. I would suggest having your different inputs as data objects in your this.state.inputs, and loop through the data and build each of the displays that way in your render method. Like this:
suppose you have one input in your this.state.inputs (and suppose your inputs is an object for key access):
inputs = {
1: {
selectedTime: 0:00,
selectedValue: 2
}
}
in your render, do something like this:
render() {
let inputs = Object.keys(this.state.inputs).map((key) => {
let input = this.state.inputs[key]
return (<SelectTimeInput
key={key}
name={'option_' + key}
placeholder={this.props.placeholder}
options={this.props.options}
onChange={this.onChange.bind(this, key)}
timeValue={input.selectedTime}
selectValue={input.selectedValue}
/>)
)}
return (
<div>
<button className="button" onClick={this.onAddClick}><i className="fa fa-plus" /></button>
{ inputs }
</div>
);
}
Notice how we're binding the key on the onChange, so that we know which input to update. now, in your onChange function, you just set the correct input's value with setState:
onChange(event, key) {
this.setState({
inputs: Immutable.fromJS(this.state.inputs).setIn([`${key}`, 'selectedTime'], event.target.value).toJS()
// or
inputs: Object.assign(this.state.inputs, Object.assign(this.state.inputs[key], { timeValue: event.target.value }))
})
}
this isn't tested, but basically this Immutable statement is going to make a copy of this.state.inputs and set the selectedTime value inside of the object that matches the key, to the event.target.value. State is updated now, a re-render is triggered, and when you loop through the inputs again in the render, you'll use the new time value as the timeValue to your component.
again, with the Object.assign edit, it isn't tested, but learn more [here]. 2 Basically this statement is merging a new timeValue value in with the this.state.inputs[key] object, and then merging that new object in with the entire this.state.inputs object.
does this make sense?
I modified the onChange in the MultipleDayTimeInput:
onChange(event) {
const comparisonKey = event.target.name.substring(event.target.name.length - 1);
const input = this.getInputState(comparisonKey);
input.selected = event.target.value;
input.display = this.renderTimeInput(input);
let spliceIndex = -1;
for (let i = 0; i < this.state.inputs.length; i++) {
const matches = inputFilter(comparisonKey)(this.state.inputs[i]);
if (matches) {
spliceIndex = i;
break;
}
}
if (spliceIndex < 0) {
throw 'error updating inputs';
}
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
}
The key points are:
// re render the input
input.display = this.renderTimeInput(input);
// set the state by copying the inputs and interchanging the old input with the new input....
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
Having thought about it though, input is an object reference to the input in the this.state.inputs so actually [...this.states.inputs] would have been enough??

How can I create a dynamically interpolated string in javascript?

I'm working on creating a reusable UI component and am trying to figure out how to allow the consumer of the component to provide their own template for a particular area of the component.
I'm using typescript and am trying to utilize string interpolation to accomplish this as it seemed the most appropriate course of action.
Here is what I have so far:
export class Pager {
pageNumber: number = 1;
getButtonHtml(buttonContentTemplate?: string, isDisabled?: boolean): string {
buttonContentTemlpate = buttonContentTemplate || '${this.pageNumber}';
isDisabled = isDisabled || false;
return `<button id="button-id" type="button" ${!isDisabled ? '' : disabledAttribute}>
${buttonContentTemplate}
</button>`;
}
}
I have some other methods that will update the page number based off user input/interaction, but I want it to work that when getButtonHtml gets called, the return value would be <button id="button-id" type="button">1</button>, but instead I'm getting <button id="button-id" type="button">${this.pageNumber}</button>.
Is there a way to get javascript to evaluate the string again, and interpolate the remaining place holders?
I've looked at the MDN article on this topic and think that the String.raw method might possibly be what I need to use, but I wasn't sure and no matter what I try, I haven't gotten it to work.
Any help would be greatly appreciated.
The problem is that Template literals are interpreted immediately.
What you want to do is lazy load the template. So it would be best to pass in a function that returns a string.
export class Pager {
pageNumber: number = 1;
getButtonHtml(template?: () => string, isDisabled=false): string {
template = template || function() { return this.pageNumber.toString() };
return `<button id="button-id" type="button" ${!isDisabled ? '' : disabledAttribute}>
${template()}
</button>`;
}
}
Additionally, you can take advantage of default parameters to avoid the || trick.

Knockoutjs css binding not working

(Using KnockoutJs 2.0.0)
I have a list of phone numbers in my viewmodel. Each phone number has a type (home, work, mobile, etc). I want to display an icon (based on a fontawesome class) next to each phone number.
If I hardcode the icons in the css binding, everything works:
<tbody data-binding="foreach: phoneList">
<tr>
<td><span data-bind="css: {'icon-home' : TypeId() == 1, 'icon-building': TypeId() == 2, ... , 'icon-phone': TypeId() >= 7></span></td>
...
</tbody>
I wanted to replace the hardcoded list with a call to a function. I initially tried adding the function to the parent but had no success, so then I tried adding the function directly to the phone object itself both as a function and as a ko.computed() -- but neither of these work for me.
I've dummied up some code here that demonstrates the problem. If you inspect the span element of the table items, you'll see that it almost appears as if the data-biding is treating the returned string as an array of characters and setting the class based on indexes rather than treating the returned string as a class.
I'm sure this is something completely obvious, but I've been beating my head to no avail.
A computed observable should work just fine. The problem is what what you're returning from that computed observable. You need to return the definition of classes in the same format as the hard-coded version:
me.getClass = ko.computed(function() {
return me.typeId() == 1 ? { 'mobile': true } : { 'business': true };
});
See the updated version here: http://plnkr.co/edit/qDjgMlZpXHjn5ixY3OCt
Or, you could define a custom binding to clean up the computed function a bit, though it should be noted that in this case all classes will be replaced by the output of the binding. This is probably not necessary in Knockout 3.0.0, as alluded to in the comments and other answers.
Binding:
ko.bindingHandlers.setClass = {
update: function(element, valueAccessor, allBindings) {
var value = ko.utils.unwrapObservable(valueAccessor());
element.className = value;
}
};
Observable:
me.setClass = ko.computed(function() {
return me.typeId() == 1 ? "mobile" : "business";
});
HTML:
<td data-bind="setClass: setClass, text: typeId"></td>
A version using a custom binding is here: http://plnkr.co/edit/ryaA4mIf7oh5Biu8bKj0?p=info
Fix
Example
I updated your version of KO to 3.0.
Next, I changed your ko.computed binding for getClass from:
me.getClass = ko.computed(function() { return me.typeId == 1 ? "mobile" : "business"; });
to:
me.getClass = ko.computed(function() { return this.typeId() == 1 ? "mobile" : "business"; }, me);
Note
There may be a way to do this with KO 2.0, but I couldn't find documentation for previous versions. I imagine the issue is related to syntax if the feature exists.
An alternate way to do this is use an attr data-bind, instead of using a custom binding handler to set the class on the element.
So, you would still need to use a computed to set the observable:
me.setClass = ko.computed(function() {
return me.typeId() === 1 ? "mobile" : "business";
});
Then use an attr binding to set the class on the html element:
<td data-bind="attr: { class: setClass }, text: typeId"></td>

Javascript - Overriding property (not methods) inside an Object

Let us explain the question with an example. I have a text box. The textbox (every textbox) has a property called 'value'. I want to over ride that textbox.value and comeup with and
new thing. When the text in textbox is 'ranjan' then the textbox.VALUE property returns 'ranjan'. Now I want to thus overwrite this so that when you type textbox.VALUE you get a different thing say for example, RaNjAn or say, Mr. Ranjan or whatever.
We can over ride methods using Object.PROTOTYPE property. But how can we do it for non-function objects inside object for example the 'value' property in this case.
If i need to make the question more clear, please mention.
Regards - Ranjan.
You can define custom properties for your element using Object.defineProperty
If you have a case where you need to get the value of an element as Mr. <value> for example, then this approach will be useful. Overriding standard properties may not be such a good idea.
Demo: http://jsfiddle.net/zvCGw/2/
Code:
var foo = document.getElementById('foo');
Object.defineProperty(foo, "xvalue", {
get: function() {
return 'Mr. ' + foo.value;
},
set: function(_newValue) {
foo.value = _newValue;
}
});
foo.xvalue = 'Hello';
alert(foo.xvalue);
What you are trying to do is called type augmentation. In javscript there are types of things, such as the object type, array type, etc.
You can use the prototype to augment these built in types, for example, adding a new method that can be called on any object that is of the type array:
Array.prototype.myNewMethod = function() {
//the method logic
}
Then you can call your method on any array:
[0,1,2].myNewMethod();
There is no INPUT type in JavaScript, DOM elements are classed as Objects. But you could jerry-rig something together that kind of does what you need, like this
Object.prototype.changeValue = function(el) {
if (el.tagName === "INPUT") {
return "Mr " + el.value;
}
}
var testEl = document.getElementById("test");
document.write(testEl.changeValue(testEl))
Used in conjunction with this textbox:
<input id="test" value="Dan" />
You would then get the output 'Mr Dan'
However, this is not great, it's just to illustrate the point and is just something to get you started...
I made a fiddle so you can play around with it
You can redeclare value but it will do no good ;)
This example would do that if test is a textbox
var input = document.getElementById("test");
Object.defineProperty(input, "value", {
get : function () {
return "'" + this["value"] + "'";
},
set : function (val) {
this["value"] = val;
}
});
input.value = "Hello World";
alert(input.value);
Unfortunately, "this.value" will reference the getter causing infinite recursion.
Once redefined, the original value will no longer exist so you will have crippled the element object.
At least as far as I have been able to test.
If the property you're trying to override can also be represented by an HTML attribute (e.g. an input's value), then you can use getAttribute and setAttribute.
Object.defineProperty(myInputElement, 'value', {
get: function () {
return myInputElement.getAttribute('value');
},
set: function (value) {
myInputElement.setAttribute('value', value);
}
});
Note, however, that this override itself cannot be overridden without re-implementing it.

Categories

Resources