Sortable lists with Symfony2 reusable - mongodb

I've rewritten this entry of the Symfony1 Cookbook http://symfony.com/legacy/doc/cookbook/1_2/en/sortable to Symfony2. My first objetive is the classic sortable list:
All the documents(MongoDB) have a position field which is integer type with the order they will be show.
I've created a service class Sortable where are the following method:
class Sortable
{
//Return the position of the document
public function getByPosition($position = 1, $document, $site, $item = null){ /**/ }
//Return all elements sorted by position
public function getAllByPosition($document, $site){/**/}
//Return the max position of the items within a parent item
public function getMaxPosition($document, $site, $parent = null){/**/}
//Swap the position between $itemOne and $itemTwo
public function swapWith($itemOne, $itemTwo, $document){/**/}
//Set the new position => +1
public function executeUp($document, $id){/**/}
//Set the new position => -1
public function executeDown($document, $id){/**/}
//Persist document with the new position
public function save($item, $document){/**/}
}
This works fine but the main problem is that this class is hardly reusable.
-$document var is the name of Document in database to use for example on createQueryBuilder('MyBundle'.$document)
-$site var is the site of my app because each user has a site.
-$parent var is of type $document and it's the parent of the documents of the same type.
The problem for me, this is hardly reusable and I've to call all methods above in the controller action and then checking the position in Twig template. I want to achieve a twig extension which call to my service and do all the logic that I do in the controller and twig. Also that it works with whatever Document.
How I can get this?

Related

Is it possible to refresh stale elements?

I have a search page that contains a table that gets populated with search results after the user presses the search button. The page object wraps the rows of the search results table in a custom HtmlElement class.
I'm getting a stale element exception when accessing the results table - as you'd expect because it was just refreshed by an ajax request.
I've worked around it by returning a new instance of the page object after performing the search but I'd rather just recreate the search results field. Is there any way to do this?
Example:
#FindBy(css = "[data-viewid='Table1'] .dojoxGridRow")
List<ActivityPerformanceRow> results;
// ...
public void search() {
search.click()
waitForAjaxToComplete();
// If it was a standard WebElement list I'd do something like this:
results = driver.findElements(By.cssSelector(
"[data-viewid='Table1'] .dojoxGridRow"));
}
After a bit of playing around I came up with this solution - it works well but doesn't deal with HtmlElement name property. Given I don't use it that I'm aware of I'm ignoring it for now...
public <T extends HtmlElement> List<T> findElements(Class<T> elementClass, By by) {
List<T> elements = new LinkedList<T>();
for (WebElement element : driver.findElements(by)) {
elements.add(HtmlElementLoader.createHtmlElement(elementClass, element, null));
}
return elements;
}

Problems with Symfony embedded forms

I am trying to achieve the following scenario:
Auction and Category entities (many-to-one). The Category entity has a one-to-many relationship to CategoryAttribute entity which allows an unlimited number of attributes of various types to be added to the category. Let's say a Cars category will have Make and Year attributes. The CategoryAttribute entity has widgetType property which defines how to render the attribute (input, select etc.), attributeValues which is the data for the attribute (populating the select etc.) and isRequired property to tell whether a property is required or not. So far so good. Managing the attributes was piece of cake BUT:
On the Auction side of things I want when the user selects a given category from the list to render all the attributes for that category to be filled in. This is translated to an related AuctionAttribute entity (many-to-one to the auction in attributes property in the class). The AuctionAttribute has reference to the CategoryAttribute, a attributeValue to hold the input or selected value.
Well, the whole AJAX request and fill in of the attributes for a selected category was not a problem. The problem(s) arise when I submit the form. Basically there are two issues.
How do we bind the attributes part of the form to the actual form for validation. Let's say we have Car category selected and Make attribute is required, how do we validate that this attribute?
How do we bind the attributes input to AuctionAttribute entity in that form?
I know that for embedded forms I need to hook up to the FormEvents::PRE_SUBMIT event but I am not sure how to transform the attribute to an Entity in there.
In terms of code, I have the following:
When getting the attributes for a category, I create a AuctionAttributeFormType and render it into a twig form helper and return the HTML back in the AJAX request:
$form = $this->createForm(new Type\AuctionAttributeFormType(), null, array('csrf_protection' => false));
foreach ($categoryAttributes as $attribute) {
$form->add('attribute_'.$attribute->getId(), $attribute->getWidgetType(), array('label' => $attribute->getName(), 'required' => $attribute->isRequired());
}
When the Auction form is submitted, I hook to the PRE_SUBMIT event and when whether there is a attribute submitted and it belongs to the set of attributes of the category but this is as far as I went before I got stuck:
$builder->addEventListener(
Form\FormEvents::PRE_SUBMIT, function (Form\FormEvent $event) {
$auction = $event->getData();
if (null !== $auction['category']) {
$categoryAttributes = $this
->repository
->findAttributesForCategory($auction['category'])
->getResult();
if (count($categoryAttributes) > 0) {
$attribute_values = array();
foreach ($categoryAttributes as $attribute) {
if (isset($auction['attribute_' . $attribute->getId()])) {
$attribute_values[$attribute->getId()] = $auction['attribute_' . $attribute->getId()];
}
}
}
}
}
);
I need to get the values from attribute_values array into AuctionAttribute entities bound to the Auction entity. Any idea how this could be achieved. I think it should be done through some kind of data transformer but I am not sure to what to transform that data - should it be a form->add field, or directly touch the Auction entity which is filled in with data.
Any suggestions?
EDIT:
I made it work with the use of Model transformer but now there is another problem, when editing the record, if there is more than one attribute, only the first one is populated with data. Here is a sample gist of the code:
https://gist.github.com/SvetlinStaev/86e066a865478e40718c
My suggestion is NOT to convert the submitted data via Event Listeners, but to use a Data Transformer, which you attach to a form field like so:
$formBuilder->add(
$formBuilder
->create('FIELD_NAME', 'FIELD_TYPE', [
... FIELD_OPTIONS ...
])
->addModelTransformer(new SomeModelTransformer())
)
And the "SomeModelTransformer" class should look like this:
class SeatingToNumberTransformer implements DataTransformerInterface
{
/**
* Transforms the object from the norm data to model data
* The norm data is the field value. Say you have an integer field, $normDataObject would be an int.
* In your case: you need to instantiate several new AuctionAttribute objects and persist them maybe
*/
public function transform($normDataObject)
{
$transformedObject = $this->someTransformAction($normDataObject);
return $transformedObject;
}
/**
* Reverts the transform
* in your case: from AuctionAttribute to int
*/
public function reverseTransform($modelDataObject)
{
$transformedObject = $this->someOtherTransformAction($modelDataObject);
return $transformedObject;
}
}
More info can be found here
If you need more help, just let me know.

mapping the data of the child form on the parent form symfony2

I have created a Form Type that adds an autocomplete feature for Entities, however it requires some configuration for every Entity, i.e: I have to pass the configuration to the options array, so I decided to make a new FormType for each Entity using the AutoCompleteType I created and reuse them.However I want these Formtypes i.e: the ones for each particular Entity, to return the Entity when getData() is called on it, what happens now is that I have to first retrieve the field of ParentForm containing the AutoCompleteType then call getData() to retrieve my Entity.How can I map this information directly on the ParentForm?
//the FormType of Some Entity using the AutoComplete
...
class SomeEntityAutoCompleteType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array options){
$builder->add('some_entity', 'entity_autocomplete', array(...));
}
}
//the controller
public function someAction(){
$form = $this->get('form.factory')->create(new SomeEntityAutoCompleteType());
...
//I want the below line to return my entity
$form->getData();
//but I have to use this one right now
$form['some_entity']->getData()
}
note: I haven't actually tested the other approach but from what I understand of the Symfony Form Component it should be the way I described;
I solved it by setting the parent type of my SomeEntityAutoCompleteType to the main autocomplete type I had created and configuring the options using the setDefaultOptions() method.
//SomeEntityAutoCompleteType
public function setDefaultOption(OptionsResolverInterface $resolver){
$resolver->setDefaults(...);
}
public function getParent(){
return "autocomplete_type";//this is the main autocomplete type I mentioned
}

how to convert Enum to IEnumerable<SelectListItem>

UPDATE 1
I want to populate a listboxfor mvc helper to render list items in View. I don't know how to pass the second parameter to the helper, which accepts IENumerableSelectListItem SelectionList.
Html.ListBoxFor(model => model.SelectedDate, Model.DateList, new { })
I am having a property in my model class,
public IEnumerableSelectListItem DateList { get; set; }
and somehow i need to populate DateList from my controller action method, which fills up all the other model properties and passes it to a partial view where i have the helper. DateList should be getting the values from an Enum DateListEnum
Any ideas on how to go about doing this. Also let me know how do i ensure that a single list item is selected at a time...i am really new to all these..and its been quite some time i am working on this...but nothing came up so far....
I am having below code in my controller action method,
model.DateList = getDateList();
which calls this method,
public IEnumerableSelectListItem getDateList()
{
IEnumerableSelectListItem values = Enum.GetValues(typeof(DateListEnum));
//this one doesn't work because of casting issues and this is where i want to convert Enum Values to IEnumerableSelectListItem
return values;
}
UPDATE 2
i have the listbox working and its displaying in the UI based upon below code
` IEnumerable<SelectListItem> values = Enum.GetNames(typeof(ColumnFormattingDateFormats)).Cast<ColumnFormattingDateFormats>().Select(p => new SelectListItem()
{
Text = p.ToString(),
Value = ((int)p).ToString()
}).ToList();`
But now i am having few other problems,
the enum that i am using is
`public enum ColumnFormattingDateFormats : int
{
[StringValue("Wednesday, March 14, 2001")]
FULLDATE = 0,
[StringValue("3/14")]
MMDDWSLASH = 1,
[StringValue("3/14/01")]
MMDDYYWSLASH = 2,
[StringValue("03/14/01")]
MMDDYYWMMZEROPREFIXWSLASH = 3
}`
and my helper looks like this,
Html.ListBoxFor(model => model.SelectedDate, Model.DateList, new { })
1> How do i pass the selected item to the listboxfor helper?
is there a way to pass the selected item to the helper through the property
[DataMember]
public ColumnFormattingDateFormats SelectedDate{ get; set; }
in my model class? to begin with i am passing
this.SelectedDate= ColumnFormattingDateFormats.FULLDATE;
to the default constructor in my model class...but for some reason the first parameter model => model.SelectedDatehas some problem with that..throws null exception...
2> how do i ensure [StringValue] from Enum gets displayed in UI listbox element and not the Enum Text for ex. FULLDATE should not be getting displayed, instead "Wednesday, March 14, 2001" should be?
3> How do i ensure that a single item is selected without using livequery?
My brains are all fried up now....any directions anyone?????
how about:
Enum.GetValues(typeof(MyEnum))
.Cast<MyEnum>()
.Select(p => new SelectListItem()
{
Text = p.ToString(),
Value = ((int) p).ToString()
})
.ToList();

Form fields are reset on validation error

I have a rather complex form in the way that the number of form fields is flexibel. In short, the model object is a TLabel (TranslationLabel) that contains a Map of values (translations). Language here is an enum so the idea is that the number of fields (text areas) for which a translation is given depends on the values in this enum.
This is my form (simplified):
public class TranslationEditForm extends Form {
private final static List<Language> LANGUAGES = newArrayList(Language.values());
public TranslationEditForm(String id, final TranslationLabelView label) {
super(id, new CompoundPropertyModel<TranslationLabelView>(label));
ListView<Language> textAreas = new ListView<Language>("translationRepeater", LANGUAGES) {
#Override
protected void populateItem(final ListItem<Language> itemLang) {
//loop through the languages and create 1 textarea per language
itemLang.add(new Label("language", itemLang.getModelObject().toString()));
Model<String> textModel = new Model<String>() {
#Override
public String getObject() {
//return the value for current language
return label.getValue(itemLang.getModelObject());
}
#Override
public void setObject(String object) {
//set the value for current language
label.getTranslations().put(itemLang.getModelObject(), object);
}
};
itemLang.add(new TextArea<String>("value", textModel).setRequired(true));
}
};
//add the repeater containing a textarea per language to the form
this.add(textAreas);
}
}
Now, it works fine, 1 text area is created per language and its value is also set nicely; even more when changed the model gets updated as intended.
If you submit the form after emptying a text area (so originally there was a value) then of course there is a validation error (required). Normal (wicket) behaviour would be that the invalid field is still empty but for some reason the original value is reset and I don't understand why.
If I override onError like this:
#Override
protected void onError() {
this.updateFormComponentModels();
}
then it is fine, the value of the field is set to the submitted value (empty) instead of the original value.
Any idea what is causing this? What is wicket failing to do because the way I've set up the form (because with a simple form/model this is working fine as are the wicket examples)?
Posted as answer, so the question can be marked as solved:
ListView does recreate all its items at render time. This means that the validation will be broken. Have a look at API doc of the ListView
Calling setReuseItems() on the ListView solves this.
Regards,
Bert