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
Related
im having trouble getting data from a form group. I have this formGroup called lineitemForm, and I'm wanting to format said form group in the following:
private formatTransferData() {
const depositDates = this.getDepositDates(this.lineItemsForm);
const mappedValues = this.formatFormValues(depositDates);
return this.filterFormValues(mappedValues);
}
private formatFormValues(depositDates) {
return depositDates.map((depositDate) => {
return {
effectiveDates: depositDate.controls.effectiveDates.value,
depositDate: depositDate.controls.depositDate.value,
};
});
}
I have been noticing that the function formatTransferData() breaks down when I pass depositDates into this.FormatValues(), there are two numeric variables inside of depositDates, I can console.log inside the mapping depositDate.controls.effectiveDates and see two numeric values, but as soon as I add on the .value to the end the numeric values either resolve themselves to null or a simple "", is there some sort of conflict that I am unaware of when using .value on a formGroup that contains numeric value/s?
On later reflection, the best way to go about getting values from a formGroup is by doing the following:
this.formGroup.controls['someFormGroupValue'].value
or
this.someForm.get('someFormItem').value;
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??
Here is a fiddle : http://jsfiddle.net/BNXTm/1/
As you can see, even though the new consultant has the Commercial role, the select displays Consultant instead of Commercial. How can I make the select elements display the name of the consultant's role?
value binding compare objects references to match selected value.
The Role object in the list and the selected object does not share the same reference
http://jsfiddle.net/BNXTm/4/
var c1 = new Consultant("Foo BAR", ko.utils.arrayFirst(contractViewModel.availableRoles, function(item) {
return item.tag === "Co";
}));
What you can do is create an computed which works with a plain value instead of objects:
self.role = ko.observable(role);
self.role.forSelect = ko.computed({
read: function() {
return self.role().tag;
},
write: function(newValue) {
self.role(contractViewModel.getRole(newValue));
}
});
This way role will always be one of the objects in availableRoles.
See http://jsfiddle.net/eCkL9/
I would like the autocomplete to show the entire list when the input box gets focused (no input is given). Would also like the auto complete to match substrings without having to fiddle with private variables.
At the moment the code is:
autocomplete = goog.ui.ac.createSimpleAutoComplete(
gsa.Game.gameData.teams, team2, false);
matcher=autocomplete.getMatcher();
matcher.useSimilar_=true
autocomplete.setMatcher(matcher);
Similar matches work but have to set a private variable for that (no getter or setter available).
The other one I have not been able to find out; how to show all data when no input is given (like a smart select input). So when the textbox receives focus it'll show all data since there is no filter text given. These are basic things that one would like to configure but can't find it in the API documentation.
You need to create descendants of goog.ui.ac.AutoComplete, goog.ui.ac.ArrayMatcher and goog.ui.ac.InputHandler. Also you will directly create the instance of auto complete object instead of calling goog.ui.ac.createSimpleAutoComplete.
In goog.ui.ac.AutoComplete descendant you assign custom input handler and matcher.
goog.provide('my.ui.ac.AutoComplete');
goog.require('goog.ui.ac.Renderer');
goog.require('my.ui.ac.ArrayMatcher');
goog.require('my.ui.ac.InputHandler');
my.ui.ac.AutoComplete = function(data, input, opt_multi, opt_useSimilar) {
var renderer = new goog.ui.ac.Renderer();
var matcher = new my.ui.ac.ArrayMatcher(data, !opt_useSimilar);
var inputhandler = new my.ui.ac.InputHandler(null, null, !!opt_multi, 300);
goog.ui.ac.AutoComplete.call(this, matcher, renderer, inputhandler);
inputhandler.attachAutoComplete(this);
inputhandler.attachInputs(input);
};
goog.inherits(my.ui.ac.AutoComplete, goog.ui.ac.AutoComplete);
In goog.ui.ac.ArrayMatcher descendant you need to override getPrefixMatches() method, since the default behaviour discards empty strings. So if there is an empty string, we just return the first x rows from the data.
goog.provide('my.ui.ac.ArrayMatcher');
goog.require('goog.ui.ac.ArrayMatcher');
my.ui.ac.ArrayMatcher = function(rows, opt_noSimilar) {
goog.ui.ac.ArrayMatcher.call(this, rows, opt_noSimilar);
};
goog.inherits(my.ui.ac.ArrayMatcher, goog.ui.ac.ArrayMatcher);
my.ui.ac.ArrayMatcher.prototype.getPrefixMatches = function(token, maxMatches) {
if (token == '')
{
// for empty search string, return first maxMatches rows
return this.rows_.slice(0, maxMatches);
}
else
{
return goog.base(this, 'getPrefixMatches', token, maxMatches);
}
};
In goog.ui.ac.InputHandler descendant you need to override processFocus() method, and force to show the autocomplete popup. This can be done by calling update() method with first parameter set to true.
goog.provide('my.ui.ac.InputHandler');
goog.require('goog.ui.ac.InputHandler');
my.ui.ac.InputHandler = function(opt_separators, opt_literals, opt_multi, opt_throttleTime) {
goog.ui.ac.InputHandler.call(this, opt_separators, opt_literals, opt_multi, opt_throttleTime);
};
goog.inherits(my.ui.ac.InputHandler, goog.ui.ac.InputHandler);
my.ui.ac.InputHandler.prototype.processFocus = function(target) {
goog.base(this, 'processFocus', target);
// force the autocomplete popup to show
this.update(true);
};
I want to convert a solution of <select /> box chaining I've already built to use KnockoutJS. Here's how it works now:
I have a database that is full of products that have attributes and those have values which in turn have a dependency on another selected value.
product > attributes > values > dependency
bench > length > 42" > (height == 16")
In my database we also store what values are dependent on other values. e.g. length can only be 42" if the height is 16" or something like that.
This comes from the server to a JSON object on the client that contains all of the attributes, values and dependencies for the product in a tree like form.
var product =
{
attributes:
[
values:
[
value:
{
dependencies: [{dependencyOp}]
}
]
]
};
I'll loop through each value and its dependency for the entire object and build an expression like "16 == 14 && 4 == 4" which would evaluate to false of course (14 being the selected value from another attribute). in that expression the && would be the dependencyOp in the dependencies array.
Now in my attempt I used KnockoutJS mapping plugin to get the object to be my view model but my problem is when I make a dependentObservable that needs to know what its dependant on. So now I would have to loop through every single array/object in my product variable?
If I understood your question, you're trying to get data from your server, and use it determine if the user's input is valid. If that's the case, put your data structure into a field in your viewModel, and make your dependentObservable dependent on that field.
function ViewModel() {
this.data = ko.observable();
this.input = ko.observable();
this.isValid = ko.dependentObservable(function() {
// evaluate input() against data() to determine it is valid; return a boolean
return ...;
}, this);
this.loadData = function() {
$.ajax('/data/get', {
success: function(dataFromServer) {
this.data(dataFromServer);
});
}
}
var vm = new ViewModel();
ko.applyBindings(vm);
vm.loadData();
now you can refer to this dependentObservable in a data-bind attribute like this
<input type="text" data-bind="value: input, valueupdate='afterkeydown'" />
<div data-bind="visible: isValid">shown only when data is valid...</div>