ReferenceInput Select Input for Filter component Form - forms

I built a custom Filter component for my List View and Im having trouble populating a Select Input of ALL available options for a property. for instance
<Form onSubmit={onSubmit} initialValues={filterValues} >
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<ReferenceInput label="Ring Id" source="ringid" reference="candidates">
<SelectInput optionText="ringid" />
</ReferenceInput>
</form>
)}
</Form>
Without building a "getMany" dataProvider Im told that I can access all of the (2,000+ ids) "ringid"s pulled in from the "getList" provider and list every ID into the SelectInput field and search in my custom Filter component.
Issues presented:
I have to hard code amount of results I can have (Default 25)
When I submit the form to Search through the filter component "Associated reference no longer appears to be available." appears and the search fails.
The "getMany" component is only half way built but it seems that ReferenceInput only wants to use "getMany"(Im told that building the backend and building code to use getMany is not an priority to build so I cant build it myself)
25 Populated IDs Screenshot
Form Error when Filter is submitted ScreenShot
So I would like some help in the right direction to populate a SelectInput of all available ids in the getList dataProvider and be sure that I can even use this input in my Filter form component. Thank you in advance for any feedback.

1: Yes, i think there's no option to add pagination to ReferenceInput, you must hardcode it, but, if your backend already supports text search, you can use an AutocompleteInput as child, allowing users to filter results:
<ReferenceInput
label="Ring Id"
source="ringid"
reference="candidates"
filterToQuery={searchText => ({ paramNameThatYourBackendExpects: searchText })}
>
<AutocompleteInput optionText="ringid" />
</ReferenceInput>
2 & 3: 2 happens because of 3. ReferenceInput only "wants" to use getMany because it also support SelectManyInput as child, for such case, it's better to get all selected options at once, than calling one by one, so, to make code simpler, ReferenceInput always use getMany. If you can't implement backend part of getMany, but can add code to your dataProvider, you can implement getMany by making multiple getOne calls:
Assuming a v3 dataProvider:
this.getMany = async (resource, params) => {
const response = {data: []}
for (const id of params.id) {
response.data.push(await this.getOne(resource, {id}))
}
return response
}
v2 is implementation-dependant, just follow the same principle.
If you can't change the dataProvider, e.g, a third-party available dataProvider, you can wrap it:
v3
const fakeGetManyDataProvider = dataProvider => ({
...dataProvider,
getMany: async (resource, params) => {
const response = {data: []}
for (const id of params.id) {
response.data.push(await dataProvider.getOne(resource, {id}))
}
return response
}
})
v2
import { GET_MANY, GET_ONE } from 'react-admin'
const fakeGetManyDataProvider = dataProvider => async (verb, resource, params) => {
if (verb === GET_MANY) {
const response = {data: []}
for (const id of params.id) {
response.data.push(await dataProvider(GET_ONE, resource, {id}))
}
return response
}
return dataProvider(verb, resource, params)
}
Please note that error handling is omitted for simplicity, react admin expects rejecteds promise instead of unhandled expections, so you must handle errors.

Related

Why React example does not use Async call to fetch json of requested page

I am following this tutorials: https://github.com/adobe/aem-sample-we-retail-journal
Here, all the child pages JSON is stored into INITIAL_STATE id. 
https://github.com/adobe/aem-sample-we-retail-journal/blob/master/react-app/src/index.js#L34
document.addEventListener('DOMContentLoaded', () => {
let jsonScript = document.getElementById("__INITIAL_STATE__");
let initialState = null;
if (jsonScript) {
initialState = JSON.parse(jsonScript.innerText);
// Remove the script element from the DOM
jsonScript.remove();
}
I want to know why it is done in this way, can someone please explain?
Instead what if I navigate on different links and then on click of each links if I call model json of that page via Async fetch and then accordingly set INITIAL_STATE id. What problem in this approach? 

How to stop the user from entering the duplicate record on default save

I have a custom module where there is an email field. Now i want to stop the user if the email is already in the database.
I want to stop the user on save button and show the error. Like when a required field goes empty.
I tried to get some help but was not able to understand it.
Note: I realized after posting this that you are using suitecrm which this answer will not be applicable toward but I will leave it in case anyone using Sugar has this question.
There are a couple of ways to accomplish this so I'll do my best to walk through them in the order I would recommend. This would apply if you are using a version of Sugar post 7.0.0.
1) The first route is to manually create an email address relationship. This approach would use the out of box features which will ensure your system only keeps track of a single email address. If that would work for your needs, you can review this cookbook article and let me know if you have any questions:
https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_9.2/Cookbook/Adding_the_Email_Field_to_a_Bean/
2) The second approach, where you are using a custom field, is to use field validation. Documentation on field validation can be found here:
https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_9.2/Cookbook/Adding_Field_Validation_to_the_Record_View/index.html
The code example I would focus on is:
https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_9.2/Cookbook/Adding_Field_Validation_to_the_Record_View/#Method_1_Extending_the_RecordView_and_CreateView_Controllers
For your example, I would imagine you would do something like this:
Create a language key for your error message:
./custom/Extension/application/Ext/Language/en_us.error_email_exists_message.php
<?php
$app_strings['ERROR_EMAIL_EXISTS_MESSAGE'] = 'This email already exists.';
Create a custom controller for the record creation (you may also want to do this in your record.js):
./custom/modules//clients/base/views/create/create.js
({
extendsFrom: 'RecordView',
initialize: function (options) {
this._super('initialize', [options]);
//reference your language key here
app.error.errorName2Keys['email_exists'] = 'ERROR_EMAIL_EXISTS_MESSAGE';
//add validation tasks
this.model.addValidationTask('check_email', _.bind(this._doValidateEmail, this));
},
_doValidateEmail: function(fields, errors, callback) {
var emailAddress = this.model.get('your_email_field');
//this may take some time so lets give the user an alert message
app.alert.show('email-check', {
level: 'process',
title: 'Checking for existing email address...'
});
//make an api call to a custom (or stock) endpoint of your choosing to see if the email exists
app.api.call('read', app.api.buildURL("your_custom_endpoint/"+emailAddress), {}, {
success: _.bind(function (response) {
//dismiss the alert
app.alert.dismiss('email-check');
//analyze your response here
if (response == '<email exists>') {
errors['your_email_field'] = errors['your_email_field'] || {};
errors['your_email_field'].email_exists = true;
}
callback(null, fields, errors);
}, this),
error: _.bind(function (response) {
//dismiss the alert
app.alert.dismiss('email-check');
//throw an error alert
app.alert.show('email-check-error', {
level: 'error',
messages: "There was an error!",
autoClose: false
});
callback(null, fields, errors);
})
});
},
})
Obviously, this isn't a fully working example but it should get you most of the way there. Hope this helps!

Algolia: How to pass refinements from a front-end to a backend?

I have a webpage that uses Algolia's React InstantSearch. It has a search bar and several refinements.
I want the user to be able to press a button and get a list of all matching results.
To get a list of all results, I need to use the Browse Index instead of the Search Index. The Browse Index allows retrieving all hits; the Search Index allows retrieval of only up to 1000 hits. However, the Browse Index should not be used in UIs. So I want to create an API endpoint my web server that uses the Browse Index in order return a list of matching hits given a search query.
I am able to successfully do this for a search query, but I can't figure out how to this for refinements.
Here is a sketch of what I have so far.
Back-end (in Ruby):
ALGOLIA_INDEX = Algolia::Index.new('Products')
class AlgoliaSearchController < ActionController::Base
def get_search_results
query = params['query']
hits = []
ALGOLIA_INDEX.browse({query: query}) do |hit|
hits << hit
end
render json: hits
end
end
Frontend:
import qs from 'qs';
import React, { useCallback, useState } from 'react';
import { InstantSearch } from 'react-instantsearch-dom';
function getSearchResults(query) {
const queryString = qs.stringify({
query,
})
return fetch(`/search_results?{queryString}`);
}
function App() {
const [searchState, setSearchState] = useState(null);
const onSearchStateChange = useCallback(searchState => {
setSearchState(searchState);
}, [searchState]);
const onClick = useCallback(() => {
console.log(getSearchResults(searchstate.query));
});
return (
<InstantSearch ... onSearchStateChange={onSearchStateChange}>
<button onClick={onClick}>Search</button>
</InstantSearch>
);
}
I can't find any resources that explain how to do search with refinements.
Things I've looked at so far:
I can try to map the searchState format to the Search API Parameters used by the Browse Index. I could write my own mapper from search state to a query, however, 1) this seems complex and I suspect I'm missing something simpler and 2) this seems like this should be open-sourced somewhere since I suspect I'm not the first to run into this issue.
There is an article, Backend InstantSearch
, that explains how to write a backend that can be plugged into the InstatSearch component. However it doesn't explain how I could do a one-off search from the search state.
You are right that this is currently not exactly straightforward. The flow to get the raw search parameters you can use for "browse" is like this:
give your custom component access to the last search results
read the state from those results
create a new helper using this state
use helper.getQuery() to get the query parameters to apply
A sandbox that illustrates this is: https://codesandbox.io/s/extending-widgets-luqd9
import React, { Component } from 'react';
import algoliaHelper from 'algoliasearch-helper';
import { connectStateResults } from 'react-instantsearch-dom';
class Downloader extends Component {
state = {
instructions: '',
};
onDownloadClick = () => {
// get the current results from "connectStateResults"
const res = this.props.searchResults;
// read the private "state" (SearchParameters) from the last results
const state = res && res._state;
// create a new "helper" with this state
const helper = algoliaHelper({}, state.index, state);
// get the query parameters to apply
const rawQuery = helper.getQuery();
this.setState({
instructions:
'Do a search backend with this:\n\nclient.browseAll(' +
JSON.stringify(rawQuery, null, 2) +
')',
});
};
render() {
return (
<React.Fragment>
<button onClick={this.onDownloadClick}>download</button>
<pre>{this.state.instructions}</pre>
</React.Fragment>
);
}
}
export default connectStateResults(Downloader)

Data return from axios cant be written to variable

I want to get an data attribute from my axios post and write it to an local variable to reuse it.
If i console.log it inside the axios .then, tha data is set, if i write it to my variable and want to use it after, it is empty.
export default {
data(){
return {
post:{},
projectId: '',
existingProjects: []
}
},
methods: {
addPost(){
//check if project exists else create
let uriProj = 'http://localhost:4000/projects/add';
this.axios.post(uriProj, {
projectName: this.post.project,
}).then(response => this.projectId = response.data.data);
console.log("project_id: "+this.projectId)
}
}
What am i doing wrong?
Another Question:
Is this the right way if i want to reuse the id in another method?
My Goal is to first create a project if it is not already in my db, then i want to reuse the id of the created or returned project model to create a new customer in my db, if the customer already has the project with the id of this project, it shouldnt be added, if it is a new one it should be added.
Has this to be done in multiple requests or is there a simple method for doing this?
I believe the issue you are seeing has to do with the asynchronous nature of network calls. When axios submits the post request it returns a Promise then the addPost function continues executing. So the projectId gets logged after it the initial value gets set, but before the network request completes. Everything inside the then() function executes once the network request has been completed so you can test by moving the console.log to be executed once the request is done. You could also output the value in the template so you can see it update {{ projectId }}
this.axios.post(uriProj, {
projectName: this.post.project,
}).then(response => {
this.projectId = response.data.data
console.log("project_id: "+this.projectId)
});
I would ideally recommend using the VueJS dev tools browser extension because it allows you to inspect the state of your Vue components without having to use console.log or add random echos to your template markup.
#jfadich is correct. I recommend using async/await instead of then, it's more intuitive to read.
async addPost(){
//check if project exists else create
let uriProj = 'http://localhost:4000/projects/add';
let resp = await this.axios.post(uriProj, {
projectName: this.post.project,
})
this.projectId = resp.data.data
console.log("project_id: "+this.projectId)
}

angular 2 patch value reactive forms returns undefined

after this..
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
let id = Number.parseInt(params['id']);
console.log('getting person with id: ', id);
this.peopleService
.get(id)
.subscribe(p => this.person = p);
});
}
why does
console.log(this.person);
return undefined?
code my code is based on this
It all works fine when I use it in the html, for example
{{person.id}}
and it works fine in my component.ts when I pass the data to a Material dialog using
data:this.activity
I've added
#input person:Person;
What do I need to do to be able to console.log(this.person) ?
The reason I'm asking is curiosity and also I need use patchValue on a reactive form and I get undefined if I add
this.personForm
// .patchValue({name:this.person.name});
I rewrote the subscribe line
.subscribe((response) =>{
this.person = response;
this.personForm
.patchValue({name:this.person.name});
});
and it works but probably not the nicest way to do it?