I want my record level data of the form to looks like this:
{
names: ['foo name', 'bar name']
}
However I am having to nest which gives me:
{
names: [ { name: 'foo name' }, { name: 'bar name' }]
}
The nest is caused by this:
<FieldArray name="names">
{({ fields }) =>
fields.map((name, index) => (
<div key={name}>
<Field
name={`${name}.name`}
component="input"
placeholder="Name"
/>
</div>
))
}
</FieldArray>
Is there a way to avoid this nesting, and get a FieldArray of just strings?
Solved it, I just had to use
name={name}
instead of
name={`${name}.name`}
Cool!
Related
Is there an easy way to customise the message that appears when the pattern: "([A-Za-z0-9\-\_]+)" argument is not satisfied and this message appears:
e.g.
<%= f.text_field :username, pattern: "([A-Za-z0-9\-\_]+)" %>
You can add a "title" attribute to append on to that message:
<%= f.text_field :username, pattern: "([A-Za-z0-9\-\_]+)" , title: "Letters, numbers, hyphen, and underscore Only"%>
This will result in the following message:
Please match the format requested: Letters, numbers, hyphen, and underscore Only
Otherwise, for more custom messages, you can use client side validation (via javascript) server side validations (via rails and ActiveModel)
For a fully custom message you must use JavaScript.
const username = document.querySelector("#user_username");
username.addEventListener("invalid", ({ target }) => {
target.setCustomValidity("Hello World!");
});
username.addEventListener("change", ({ target }) => {
target.setCustomValidity("");
});
<form>
<input id="user_username" name="user[username]" pattern="[A-Za-z0-9_-]+" />
<input type="submit" />
</form>
You could add this in your Rails view in a similar manner:
<%= f.text_field :username, pattern: "[A-Za-z0-9_-]+" %>
...
<script>
const username = document.querySelector("#user_username");
username.addEventListener("invalid", ({ target }) => {
target.setCustomValidity("Hello World!");
});
username.addEventListener("change", ({ target }) => {
target.setCustomValidity("");
});
</script>
If you need this sort of functionality more often, or want a cleaner solution you might want to write some unobtrusive JavaScript. For example:
function useCustomValidity({ target }) {
target.setCustomValidity(target.getAttribute("custom-validity"));
}
function clearCustomValidity({ target }) {
target.setCustomValidity("");
}
document.querySelectorAll("input[custom-validity]").forEach((input) => {
input.addEventListener("invalid", useCustomValidity);
input.addEventListener("change", clearCustomValidity);
});
With the above JavaScript being loaded (you need to add additional handlers when using Turbolinks). You can now have the following view:
<%= f.text_field :username, pattern: "[A-Za-z0-9_-]+", 'custom-validity': 'Hello World!' %>
See also: MDN Input: Client-side validation
I will fill the customerNumberContainers which looks like this:
this.form = new FormGroup({
customerNumberContainers: new FormArray([
new FormGroup({
contactTenant: new FormControl('', [Validators.required, Validators.minLength(2)]),
customerNumber: new FormControl('', [Validators.required, Validators.minLength(2)])
}),
]),
Therefore I do this after I get the values over
this.contactService.findContactById(this.id).subscribe(response => { ...
Set values into form:
let customerNumberContainersFormArray: FormArray = this.form.controls.customerNumberContainers as FormArray;
customerNumberContainersFormArray.controls["0"].controls.contactTenant.value = 'TestValue';
but it is not shown with:
in Controller:
get customerNumberContainers(): FormArray {
return this.form.get("customerNumberContainers") as FormArray;
}
in Template:
<div formArrayName="customerNumberContainers">
<div *ngFor="let customerNumberContainer of customerNumberContainers.controls; index as i" [formGroupName]="i">
<mat-input-container class="full-width-input">
<input matInput formControlName="contactTenant">
</mat-input-container>
</div>
Does anyone known what I am doing wrong. It seems for me that values with *ngFor arn't refreshed.
why dont You just patch whole form with model ? like this:
set up your model, for example:
export class Tenants {
id: number;
customerNumberContainers: TenantContact[];
}
export class TenantContact {
contactTenant: string;
customerNumber: string;
}
fetch it from service like u always do but it should match above models and patch whole form (or setValue)
this.contactService.findContactById(this.id).subscribe((tenats: Tenants) => {
this.form.patchValue(tenats);
});
Using the select component i am having a hard time trying to set initial values.
As the docs. says im suppose to fill in the value prop with only string|number|string[]|number[].
Now the problem with this is that i need to show text on the input and send an id value on submit, but with this you show the same value you send.
<Select
mode="multiple"
defaultValue={tags} // => would need something like tags = [{id: 1, name: "Science"}]
placeholder="Select tags"
onSearch={this.fetchTags}
onChange={this.handleChange}
style={{ width: '100%' }}
>
{tags.map(tag => <Option value={tag.id} key={tag.id}>{tag.name}</Option>)}
</Select>
So "labelInValue" is what i really needed in case anyone else experience the same problem.
You can use value instead of defaulValue. Here is a sample code from my project:
const stateTasksOptions =
this.tasksStore.filters.init.state.map(item =>
<Select.Option key={item.id} value={item.id} title={<span className={`${item.id}Label`}>{item.title}</span>}>
<span className={`${item.id}Label`}>{item.title}</span> - <span class="normal-text">{item.help}</span>
</Select.Option>
)
return (
....
<Select
mode="multiple"
value={this.tasksStore.filters.selected.state.map(d => d)}
onChange={this.handleTasksStatus}
optionLabelProp="title"
>
{stateTasksOptions}
</Select>
....
)
And some css for colorizing.
Result:
this.tasksStore.filters.init.state looks like:
state = [
{id: "done", title: "Исполнена", help: "исполнены после 24.04.2017"},
{id: "active", title: "В работе", help: "текущие задачи"},
{id: "planned", title: "Запланирована", help: "задачи на будущее"},
{id: "missed", title: "Просрочена", help: "срок исполнения истек"},
{id: "archived", title: "Архив", help: "выполнены ранее 24.04.2017"}
]
Suppose I have the following hash in one of my models named Company.
FIELDS = {
TEAM: {
'num_founders': 'Number of Founders',
'num_employees': 'Number of Employees',
'founders': {
'person_info': {
'full_name': 'Full Name',
'first_name': 'First Name',
'last_name': 'Last Name',
'location': 'Location',
'gender': 'Gender',
'biography': 'Biography',
'num_articles': 'Number of Articles'
}
}
}
}
And I have a action in my application controller which renders this hash as json:
def field_names
module_name = params[:module]
category = params[:category]
case module_name
when 'company'
render json: Company::FIELDS[category.to_sym].to_json
end
end
So now if I open localhost:3000/field_names/company/TEAM I'd get the json. But the problem I'm now facing is I need to get the sub hash of this hash too. Like I want to /field_names/company/TEAM/founders/person_info and get the respective object.
To begin with I'd split the url by slashes to get the keys. Im unable to figure out how would I use those strings to access the hash.
If I define the route like get '/field_names/:query', to: 'application#field_names
And If I hit localhost:3000/field_names/company/TEAM/founders
The action should render Company::FIELDS[:TEAM]["founders"] object which would be
'person_info': {
'full_name': 'Full Name',
'first_name': 'First Name',
'last_name': 'Last Name',
'location': 'Location',
'gender': 'Gender',
'biography': 'Biography',
'num_articles': 'Number of Articles'
}
For this my action should do something like:
def field_names
query = params[:query]
keys = query.split("/")
#keys.first::FIELDS[key2][key3]... .to_json
end
How do I achieve this? Thanks :)
How about something like this:
fields = {
TEAM: {
'num_founders': 'Number of Founders',
'num_employees': 'Number of Employees',
'founders': {
'person_info': {
'full_name': 'Full Name',
'first_name': 'First Name',
'last_name': 'Last Name',
'location': 'Location',
'gender': 'Gender',
'biography': 'Biography',
'num_articles': 'Number of Articles'
}
}
}
}.with_indifferent_access
'TEAM/founders'.split('/').each do |key|
fields = fields[key]
end
puts fields
=> {"person_info"=>{"full_name"=>"Full Name", "first_name"=>"First Name", "last_name"=>"Last Name", "location"=>"Location", "gender"=>"Gender", "biography"=>"Biography", "num_articles"=>"Number of Articles"}}
Just for console, I changed FIELDS to fields (SHOUTING_CASE is usually reserved for constants, fwiw). And, I used with_indifferent_access because your nested hashes use both strings and symbols as keys.
If you want that person_info, then:
fields = {
TEAM: {
'num_founders': 'Number of Founders',
'num_employees': 'Number of Employees',
'founders': {
'person_info': {
'full_name': 'Full Name',
'first_name': 'First Name',
'last_name': 'Last Name',
'location': 'Location',
'gender': 'Gender',
'biography': 'Biography',
'num_articles': 'Number of Articles'
}
}
}
}.with_indifferent_access
'TEAM/founders/person_info'.split('/').each do |key|
fields = fields[key]
end
puts fields
=> {"full_name"=>"Full Name", "first_name"=>"First Name", "last_name"=>"Last Name", "location"=>"Location", "gender"=>"Gender", "biography"=>"Biography", "num_articles"=>"Number of Articles"}
You probably want to put some try in there in case the url is malformed.
Also, you could abstract and simplify that bit where you go:
case module_name
when 'company'
render json: Company::FIELDS[category.to_sym].to_json
end
Assuming you have (in routes.rb):
get '/field_names/:query'
And you hit:
localhost:3000/field_names/company/TEAM/founders
Then your params should include:
{query: 'company/TEAM/founders'}
In which case you do something like:
def field_names
query_split = params[:query].split
module_name = query_split.shift.camelize
fields = "#{module_name}::FIELDS".constantize.clone.with_indifferent_access
query_split.each{|key| fields = fields[key]}
render json: fields
end
You don't have to put to_json at the end of your hash, btw.
It's a routing problem in your project. Actually, you should have a route to retrieve these fieldnames, like:
get 'field_names' => 'controller#action'
then, you can pass any parameters on your URL.
Eg.
/field_names?company=TEAM&attribute=founders
Than, those attributes would be available in your controller as
params[:company]
params[:attribute]
and so you can render only the attributes you want
I am getting an error of MethodNotAllowedHttpException in compiled.php line 8931. It was working fine but suddenly it started throwing this error. I created a view in directory resources/views/main/templates/submit-for-sale.blade.php and defined its route in routes.php as:
Route::get('/submit-for-sale', function () {
return view('main.templates.submit-for-sale');
});
This view submit-for-sale.blade.php contains a form whose form tag looks like:
<form id="submit-sale-form" action="{{route('main.submit-sale-form')}}" method="post" class="form-horizontal>
{{ csrf_field() }}
.............
</form>
View is loading perfectly.
Route to post form in routes.php
Route::post('/submit-sale-form', ['as' => 'main.submit-sale-form', 'uses' => 'MainController#submitSaleForm']);
Code in MainController.php
public function submitSaleForm(Request $request)
{
$parameters = Input::get();
foreach ([
'name' => 'Name',
'email' => 'Email',
] as $key => $label) {
if (!isset($parameters[$key]) || empty($parameters[$key])) {
return response()->json(
[
'success' => false,
'error' => "{$label} cannot be empty",
]
);
}
}
}
It worked fine 2 - 3 times same code but suddenly it started throwing this error. Your help and suggestions will be highly appreciated.
Thank You.