This is the FormEvents class from Symfony2 repository on github. It's linked from the main article, How to Dynamically Generate Forms Using Form Events.
Anyone konws exactly when these events are called in the flow?
namespace Symfony\Component\Form;
/**
* #author Bernhard Schussek <bernhard.schussek#symfony.com>
*/
final class FormEvents
{
const PRE_BIND = 'form.pre_bind';
const POST_BIND = 'form.post_bind';
const PRE_SET_DATA = 'form.pre_set_data';
const POST_SET_DATA = 'form.post_set_data';
const BIND_CLIENT_DATA = 'form.bind_client_data';
const BIND_NORM_DATA = 'form.bind_norm_data';
const SET_DATA = 'form.set_data';
}
There are two types of events:
DataEvent - read-only access to the form data. 'Pre' and 'Post' events are read-only.
FilterDataEvent - event that allows the form data to be modified.
form.pre_bind
DataEvent triggered before data is bound to the form. Triggered by Symfony\Component\Form\Form::bind()
form.post_bind
DataEvent triggered after data is bound to the form. Triggered by Symfony\Component\Form\Form::bind()
form.pre_set_data
DataEvent triggered before fields are filled with default data. Triggered by Symfony\Component\Form\Form::setData()
form.post_set_data
DataEvent triggered after fields are filled with default data. Triggered by Symfony\Component\Form\Form::setData()
form.bind_client_data
FilterDataEvent triggered before data is bound to the form. Triggered by Symfony\Component\Form\Form::bind()
form.bind_norm_data
FilterDataEvent triggered after data has been normalized. Triggered by Symfony\Component\Form\Form::bind(). See Symfony\Component\Form\Extension\Core\EventListener\FixUrlProtocolListener (added by the UrlType for an example)
form.set_data
FilterDataEvent triggered while default data is being bound. Triggered by Symfony\Component\Form\Form::setData()
I'd recommend poking around the Form class itself to get a better feel for when these events are triggered, and how you can use them.
Related
My component needs data from the API.
It lets the store dispatches an action, but before dispatching this action some other data needs to be obtained first. Thus, dispatched action is dependent on data that needs to be fetched before the actual action is called and the fetched data is passed to the action.
this.d1 = this.store.pipe(select(selectData1));
this.d2 = this.store.pipe(select(selectData2));
// ...
// dispatch the actions below to and pass identifier (id) in order to get its object from the API and put the fetched data in store.
this.store.dispatch(action1({data1Id}));
this.store.dispatch(action1({data2Id}));
// dispatch the actual action and pass this.d1 and this.d2
this.store.dispatch(targetAction({this.d1, this.d2}));
What is the best practise for the situation described above?
One way you could do this is to subscribe to this.d1 and this.d2 and then dispatch the target action when they emit:
combineLatest([this.d1 this.d2])
.subscribe(([d1, d2]) => {
if (d1 && d2) {
this.store.dispatch(targetAction(d1, d2));
}
})
However, it might be better to use an effect for this:
https://ngrx.io/guide/effects
I have a web application which, after login, displays landing screen with the navigation pane on the left. Also, during the onInit event, I call the getUserData() which collects additional data about the user (such as roles and saves them into the model).
This navigation is of the type sap.tnt.NavigationListItem and is loaded from the model (data are hardcoded in App.controller.js). In App.view.xml, it looks like this
<tnt:NavigationListItem text="{appView>title}"
items="{path: 'appView>items', templateShareable: true}"
visible="{path: 'appView>neededRole', formatter:'.myFormatter'}">
Now, I would like to make an improvement - to show some items in the navigation list only to the users which have sufficient roles.
As you can see above, I set the formatter for the 'visible' property of the NavigationListItem. It checks the role necessary to display the NavigationListItem ('needed role'), compares it with the array of roles assigned to the user and if there is a match, shows the menu item
myFormatter: function(role) {
const oModel = this.getView().getModel('appView');
return oModel.oData.userData.roles.some(x => x.roleId === role);
}
The problem is that when the myFormatter function is running, getUserData() hasn't finished yet and the model doesn't yet contain necessary roles array of the user - as as reason, all menu items are hidden. What I need to achieve is to make sure that MyFormatter runs ONLY AFTER the getUserData() has finished (and while myFormatter will run repeatedly, getUserData must run only once). How can I achieve it? getUserData() is asynchronous and no matter if I place it into onInit or beforeRendering, it finishes only after myFormatter collected the empty array from the model.
Thanks a million
Your formatter will run first when the view gets initialized, this is part of the lifecycle.
Then it will run each time the 'needRole' entry is explicitly (via model.setProperty) modified
It seems in your code that your formatter actually uses another data from the model: 'roles'
So you could just bind your formatter to both model entries like that:
<tnt:NavigationListItem
text="{appView>title}"
items="{
path: 'appView>items',
templateShareable: true
}"
visible="{
parts: ['appView>neededRole', 'appView>/userData/roles'],
formatter:'.myFormatter'
}">
and modify your formatter to
myFormatter: function(role, roles) {
return (roles || []).some(x => x.roleId === role);
}
Then your formatter will trigger when role or roles get modified in the model.
As a side note : a formatter is meant to format data, not compute things. A better option would be to directly create an 'entryVisible' entry in the model that you could then bind on your NavigationListItem (I know formatters do the job, but they also trigger a lot of rerenders you dont need)
I use event listener for change data dynamically based on user inputs. Each time I use PRE_SET_DATA and PRE_SUBMIT events for set data and fields choices. Here is the simple example of actions from PRE_SUBMIT:
// Pre set share locations by share day
if (array_key_exists('shares', $data)) {
foreach ($data['shares'] as $key => $share) {
if ($share['pickUpDay'] !== null) {
$shareType = $form->get('shares')->get($key);
$locations = $this->em->getRepository('AppBundle:Member\Location')->getLocationsByDay($client, $data['shares'][$key]['pickUpDay']);
$this->addLocationField($shareType, $locations);
}
}
}
Not matter what inside addLocationField function, it works right.
When I do $form->get('shares'), its my collection field, then I need to ->get(child) of this collection and set fields data and choices straight to this child. By when I add collection dynamically, Symfony shows error:
Child "n" does not exist.
And this problem happens only when I try to get data of new collection that was added dynamically. So I can't get to a collection field and change choices, so I receive error that my new value is not in a choice list.
Interesting that $data['shares'] have all data for new collection elements, but $form->get('shares') haven`t:
var_dump(count($event->getData()['shares'])) - return 1;
var_dump(count($form->get('shares'))) - return 0;
Is that mean that my PRE_SUBMIT works before Symfony collection functionality happen?
Someone know how to fix it?
I know your question is "old" and you probably found a solution but you were in the right direction when you said :
Is that mean that my PRE_SUBMIT works before Symfony collection functionality happen?
Your new collection is not submitted yet and it is not present in the model see this part of the doc
To make what you want to, you should use the SUBMIT event
NB : You can't add any field on POST_SUBMIT
As a scenario, the user can click a button to create a list of timestamps that shows the corresponding times when the clicks are made. User can also click on an item on the list to remove an item.
In terms of the store, there's a counter state that keeps track of how many times the button has been clicked, and then there's another state that keeps track of a list of timestamps. And each item on list state has an id field that derive from the counter state. So one part of the store depends on another part.
As an attempt, I dispatch one action, and both reducers handle the same action, and it works fine, but only that it's not DRY. Before dispatching, I have to add 1 to the counter state in order to get the new id which I use as the action payload, after dispatching, I add 1 to the counter state again to return the new counter state. That's repeating myself.
What's the general standard way of handling a problem of this nature?
The general simple way is to use thunks. You need to setup a middleware, check out the docs:
https://github.com/gaearon/redux-thunk
This allows you to dispatch a function instead of a simple action. Within that function, you can access state and dispatch as many times as you want.
In your scenario, you would first increment the counter, then retrieve the length to get your new id, and then dispatch another action to create a timestamp.
Some imaginary code for your action creators:
// basic action creators to be handled in your reducers
function incrementCounter(){
return { type: 'INCREMENT'}
}
function createTimestamp(id){
return { type: 'CREATE_TS', id }
}
// this is the thunk
function incrementAndTimestamp(){
return (dispatch, getState) => {
// increment the counter
dispatch(incrementCounter())
// generate an "id" from the resulting state
const newId = getState().counter.length
// and use that id to further update your state
dispatch(createTimestamp(newId))
}
}
You will need to handle those 2 different actions in your reducers and you have now two separate pieces of code. The thunk is the glue that dispatches, gets the data from one part, and uses it to affect the other part.
Is there a way to invoke the value event handler only on value change in Firebase? If I just add the value event handler without any special code, it will be called once on reading the initial value and then on any value change. I want to avoid invoking the event handler on reading the initial value.
One of the use cases where I need this functionality is that I am working on an app where user can add tasks for some other users. The other users can either accept or reject a task. There is a server which is monitoring these tasks on Firebase and it sends a push notification to the creator of the task whenever a user accepts or rejects a task i.e. whenever there is a change in the status of task.
I have tried two solutions.
Solution 1:
Have a map and add an entry to the map when the value event handler is called if it is not already present and if the the entry is present, then do the work required on value change. This solution works but I need this in several different cases and I have to create this map in all such cases and I didn't find it to be a convenient solution.
Solution 2:
Add the value event handler like this
ref.on('value', (snapshot) => {
console.log('Initial call ', this.initialCall);
if(!this.initialCall) {
// Do work
} else {
this.initialCall = false;
}
}, {initialCall: true}) // {initialCall: true} is the context which is provided as this in the event handler
Here my thinking was that I can check if the event handler is called for the initial value by checking if this.initialCall is set to true. If it is, then set the this.initialCall to false and then in the subsequent invocations, this.initialCall should be false and I can do the work required for a value change. Using this solution what I observed was that the this.initialCall was undefined the first time the handler is called and then it was set to true and then it was true for all value change event handler invocations for all the tasks, not just the one for which it was set to true.
With regards to solution 2, I am not a JS expert and it is possible that due to some gap in my JS knowledge, I am doing something wrong due to which it doesn't work as I expected.
Please let me know if you have an idea/solution which can be used to call the value event handler only on value change.
A value event in Firebase is invoked immediately with the current value and then whenever the value changes. If you only care abut when the value changes, you can simply ignore the initial event:
var isInitialValue = true;
ref.on('value', function(snapshot) {
if (isInitialValue) {
isInitialValue = false;
}
else {
// TODO: handle subsequent changes
}
});