[admin-on-rest]: Search only when search button is pressed - admin-on-rest

this is related to admin-on-rest reactJs based framework.
I need some help/pointers on enabling search only when a search button is pressed. I need to collect multiple inputs from the user and finally push a query via REST Api to the back-end when a search button is clicked.
The query searches a very large DB set and I don't want to push queries to back-end as user is typing.

Here is what you need to do.
You need to write a custom Redux Form component. Something like below. I haven't tested this code. But I hope you get the general idea. You will also need to handle all the props that are passed down by Redux Form to your component. Take a look at the Redux Form docs. Also take a look at the TextInput component of Admin-On-Rest in the source code.
const ENTER_KEY = 13;
class RenderTextField extends Component {
handleKeyDown = (event) => {
switch( event.keyCode ) {
case ENTER_KEY:
this.props.input.onChange(this.state.searchQuery);
break;
default:
this.setState({this.searchQuery: event.target.value})
}
}
render() {
return (<input onKeyDown={this.handleKeyDown}
//You will need to look at the ReduxForm docs and handle all the props that are passed to every ReduxForm component.
/>)
}
}
4) You can also use material UI components with ReduxForm.
http://redux-form.com/6.0.0-rc.1/examples/material-ui/

Related

Disable button in Nativescript Raddataform

I'm using commitMode="Immediate" and I'm trying to disable my save button when any input is invalid.
What is the recommended approach to achieve this?
I understand that I can just set a variable when using "manual" mode from my component, but I can't find any event that represents a change in validity of preferably the complete Raddataform (otherwise of a single property) when using Immediate validation.
You can do this by listening for validation events and then updating your model.
From this example, add the propertyValidated listener:
<RadDataForm #propertyValidated="onPropertyValidated" ...></RadDataForm>
Then change your state:
methods: {
onPropertyValidated({ object, propertyName, entityProperty }) {
this.$refs.button.enabled = !entityProperty.isValid;
}
}
You will probably want to keep track of all validations in this case, or you could use the complete dataform.hasValidationErrors().
This is the NS-Vue solution, but totally applicable in Angular.
Add a #propertyValidated="onValidateForm" event listener that triggers on each validation. Then you can use hasValidationErrors() on the form to see if the form is valid. The only trick is that is has to be wrapped in a setTimeout(), like so:
onValidateForm(event) {
setTimeout(() => {
this.validated = !event.object.hasValidationErrors();
console.log("form valid: " + this.validated);
}, 100);
}
For a complete solution, see this playground.

react-redux together with components status

in a react UI I have a table component. You can edit one row of the table by clicking a edit button or you can add a new record by clicking a "new-record-button". When clicking the edit button an redux-action is triggered which takes the row and sets a visible property of a modal dialog. When the "new-record-button" is clicked an action is triggered which creates a new empty data item and the same modal dialog is triggered.
In the modal dialog I have several text components with onChange method.
in this onChange-method the data-item is written.
When to user clicks a save-button the edited dataItem is saved to the database.
So my code looks like:
const mapStateToProps = (state) => ({
dataItem: state.datItemToEdit || {},
...
});
...
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
dataItem.carId = text;
break;
}
...
}
this.forceUpdate();
}
...
<TextField
...
onChange={event => this.handleTextChange(event)}
/>
I have several question regarding this approach. First I do not understand why in handleTextChange we can write to dataItem. It does work apparently.
dataItem.carId is set in the example code but I thought
const {dataItem} = this.props;
gives us a local read-only variable dataItem just to read from the props...
Next thing I think is a poor design. After reading in a book about react I think we should not write to props but only set a state.
In my example I get the the dataItem from the redux-state. The mapStateToProps maps it to the (read-only) props of the component, right?!. But I want to EDIT it. So I would have to copy it to the state of my component?
But where to do it?
Once in the state of my component I could simply call this.setState for the various text-fields and the component would render and I could abstain from forceUpdate(), right?!
Can someone explain how the redux status plays together with the component status and props for this example?
In redux or react, you shouldn't write to the props directly because you should keep your props as immutable. Redux forces us to use immutable state because state is a source of truth for the application. If the reference to state changes then only your app should render. If you'll mutate your state (objects) then the references don't get changed and your app doesn't know whether some state has been changed or not. React/Redux doesn't give you read-only objects automatically. You can mutate them anytime but as I told you, it can cause problems that Your app won't know when to re-render. If you want to have this read-only property inherently, you should probably use immutable.js
About your second question that you'll have to copy the props to the component's state and where you should do it. You should do it in the constructor of the component and you should use immutibility helper
import React from React;
import update from 'immutibility-helper';
class Modal extends React.Component {
constructor(props){
this.state = {
dataItem: dataItem,
};
}
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.state.dataItem, text); //fire a redux action to update state in redux
this.setState(update(this.state, {
dataItem: {
carId: {$set: text},
}
});
break;
}
...
}
}
}
You wouldn't have to do forceUpdate in such case because the reference to state will change and the component will re-render itself.
Also, you can use forceUpdate in your application but personally I don't find it a great idea because when React/Redux is giving you the flow of state, by using forceUpdate, you're breaking the flow.
The last question is how redux and react state plays together. That is also a matter of choice. If I have a app level state, e.g., in your case you've some app level data, you should put that in your redux state and if you have a component level things, such as opening a modal or opening a third pane. That's the convention I follow but that can really depend on how you want to exploit react and redux state.
Also, in above code, I put the redux state in component state too (because you asked where to put that) but Ideally you should fire a redux action and update in redux state. In this way, you will restrict yourself from state duplication in react and redux.
import React from React;
import {updateItem} from './actions';
class Modal extends React.Component {
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.props.dataItem, text); //fire a redux action to update state in redux
break;
}
...
}
}
}
const mapStateToProps = (state) => ({
dataItem: getDataItem(state), //get Data Item gets Data from redux state
});
export default connect(mapStateToProps, {updateItem: updateItem})(Modal);
in Actions:
updateItem = (dataItem, text) => dispatch => {
dispatch({type: 'UPDATE_ITEM', payLoad: {dataItem, text});
};
in Reducer:
export default (state = {}, action) => {
switch(action){
case 'UPDATE_ITEM': {
return {
...state,
dataItem: {
...action.dataItem,
carId: action.text,
}
};
}
}
}
In this way, your state will be pure and you don't have to worry about immutibility.
EDIT:
As constructor will be called only once, you should probably use componentWillReceiveProps so that whenever you render the component, you get the next updated props of the component. You can check whether the carId of dataItem is same or not and then update the state.
componentWillReceiveProps(nextProps){
if(nextProps.dataItem.carId !== this.props.dataItem.carId){
this.setState({dataItem: nextProps.dataItem});
}
}
You should only use redux when you want different, unrelated components in your app to know and share the specific state.
e.g. - When a user logs in to your app, you might want all components to know that user so you'll connect your different containers to the user reducer and then propagate the user to the components.
Sounds like in this case you have a classic use case for using the inner state.
You can use the parent of all TextFields to maintain all rows, edit them by index, etc.
Once you start using redux, it's really easy to make the mistake of transferring the entire state of the components to the reducers, I've been there and stopped doing it a while ago :)

Admin-on-Rest How to create a custom Show component to add Filter

I would like to create a custom Show component that can utilize the Filter component that the List component uses. I honestly don't know where to begin on this one. Can someone point me in the right direction?
This is how it is used in List:
export const LogList = (props) => (
<List {...props} perPage={100} title="Logs and Reports" filters={< FileFilter/>}>
I would like to be able to use it in Show like so:
export const archivedShow = ({ ...props }) => (
<Show title="Log" {...props} filters={<LogFilter/>} >
You can write a custom connected component. Then you can do as you wish.
You can provide actions to the show component just like any other comp.
https://marmelab.com/admin-on-rest/Show.html#actions
You can use this action to populate a field in your State (probably using a custom reducer)
Your Component can then be connected to the redux state.
something like below
class connectedReferenceInput extends Component {
render() {
<ReferenceInput source={this.props.source} >
<somecomp>
</ReferenceInput>
}
}
function mapStateToProps(state, props) {
return {
source: state.admin.somefield.source
};
}
export default connect(mapStateToProps, {
crudGetList: crudGetListAction
})(ClientInput)
There are also docs for writing a reducer in AOR docs.
MORE DETAILS:
1) somecomp is any component that you need as child of the referenceInput
2) somefield is any field that you set up to pass data between the connected filter component and the connectedReferenceInput.
Breakup of what you are doing essentially.
1) create a connected component that you will make a child of filter. This connected component shoots a redux 'Action' that changes some part of your state by a 'reducer'
Docs for both writing an action and a reducer are available in aor docs.
2) create a connectedReferenceInput (like above) this receives (through mapStateToProps) the state changes that your connectedFilterComp makes. Using this you can create a variable filter for referenceInput and display your variable data.
You are basically using redux to pass data between the filter and your component.

angular ui-grid filter on button click

I'm new in angular. We are using UI-grid for data presentation is is possible to customize filter process. I want to customize it in that way, that filtering is peformmed on button click, not on keydown?
This is idea
$scope.search = function (){
$scope.personCardGrid.useExternalFiltering = false; $scope.grid1Api.core.notifyDataChange(uiGridConstants.dataChange.ALL);
$scope.gridApi.core.refresh() $scope.personCardGrid.useExternalFiltering = true;
$scope.grid1Api.core.notifyDataChange(uiGridConstants.dataChange.ALL);
$scope.gridApi.core.refresh() }
Regards
You need to define your own headerCellTemplate for the column. In the template, add a input text box and a button too. Then, define a function in the controller and call it using the external scope which will filter the records.

How to trigger DataBinding Validation for all Controls?

I have an OpenUI5 form consisting of a number of Inputcontrols. These Inputcontrols are bound to a model using the OpenUI5 DataBinding as described in the documentation.
For example:
new sap.m.Input({
value: {
path: "/Position/Bezeichnung",
type: new sap.ui.model.type.String(null, {
minLength: 1,
maxLength: 128
})
}
})
As in the example above I'm using constraints on the stringlength.
When a User changes the Value of the Input, the Validation is triggered and according to the Validationresult one of the functions descripted here is called.
In these functions I'm setting the ValueState of the control like this:
setupValidation: function() {
var oCore = sap.ui.getCore();
oCore.attachValidationError(function (oEvent) {
oEvent.getParameter("element").setValueState(sap.ui.core.ValueState.Error);
});
oCore.attachValidationSuccess(function (oEvent) {
oEvent.getParameter("element").setValueState(sap.ui.core.ValueState.None);
});
oCore.attachFormatError(function (oEvent) {
oEvent.getParameter("element").setValueState(sap.ui.core.ValueState.Error);
});
oCore.attachParseError(function (oEvent) {
oEvent.getParameter("element").setValueState(sap.ui.core.ValueState.Error);
});
},
Let's assume the bound model variable is initial.
I'm loading the view, the property value is parsed and displayed as empty.
The Validationerror/Parseerror method is not called although the constraints are not met.
This seems to be standard behaviour of OpenUI5. Only changes in the Control will be a validated.
Now let's assume I've a submit button and the Value of the Inputcontrol is still empty. When the user hits the submit button I'd like to trigger the DataBinding Validation for all childcontrols of my view. This would validate the above mentioned input and would result in an errorstate.
My question is: How can I trigger the databinding validation for all childcontrols of my view?
There is another question on SO where the poster asks for a way to define required fields. The proposed solution is to call getValue() on the control and validate the value manually. I think this is kind of cumbersome as formating and constraint information and logic is already present.
I suggest looking into field groups.
An example here in the UI5 docs
Field Groups allow you to assign group IDs to the input fields. Then you can call all of the input fields at once. You can set the name property and required property on each <Input> separately in your view, allowing you to handle some logic when you perform validation.
You can call this.getView().getControlsByFieldGroupId("fieldGroupId"), which will return an array of the input controls. Then you can loop through the controls, pass them through your logic, and use setValueState() to show the results.
Or, you can assign the validateFieldGroup event on the parent container, which is usually a form, but can be anything like a <VBox> that contains the controls. When the users focus moves out of the field group, the event is fired. You can then use the event handler in your controller to perform the validation.
In your case, I would assign a press event to your submit button, and in the handler, call the field group by ID and loop through the controls. At the end of your function, check to see if all fields are validated before continuing.
View
<Input name="email" required="true" value="{/user/email}" fieldGroupIds="fgUser"/>
<Input name="firstName" required="false" value="{/user/firstName"} fieldGroupIds="fgUser"/>
<Button text="Submit" press="onSubmit"/>
Controller
onSubmit: function() {
var aControls = this.getView().getControlsByFieldGroupId("fgUser");
aControls.forEach(function(oControl) {
if (oControl.getRequired()) {
//do validation
oControl.setValueState("Error");
oControl.setValueStateText("Required Field");
}
if (oControl.getName() === "firstName") {
//do validation
oControl.setValueState("Success");
}
});
var bValidated = aControls.every(function(oControl) {
return oControl.getValueState() === "Success";
});
if (bValidated) {
//do submit
}
}
The concept goes like this.
Use custom types while binding, to define validations. Validation
rules go inside these custom types (in the method 'validateValue').
When Submit is pressed, loop through the control hierarchy and
validate each control in your view. (By calling 'validateValue'
method of the Custom Type).
Validator (https://github.com/qualiture/ui5-validator ) uses this concept and it is a small library to make your life easy. Its main advantage is that it recursively traverses through the control library.
Using Message Manager (using sap.ui.get.core().getMessageManager() ) is the way to show the validation messages on the UI control.
Triggering data binding validations is not possible. Rather for empty fields that are having required property true you can do a work around using jQuery.
Please refer my answer to this same problem at Checking required fields

Resources