Silverstripe DataObject - I want to add drag and drop ordering to a current Class that extends dataobject like what exists for pages. So when dropped it updates an OrderBy field for all the dataobjects in that view. I created the class and can freely edit one item at a time, but a simple drag and drop ordering would make it so much easier but I can not see any such extensions currently on Dataobjects only on Pages.
In SilverStripe 3.1 there are a few excellent modules that give you this sort of functionality. Two of these modules are SortableGridField and GridFieldExtensions.
To get this working you need to install one of these modules, add a sort field to your custom DataObject class and add the module sort object component to your GridFieldConfig.
SortableGridField
The SortableGridField module is specifically to allow sorting functionality for objects on a GridField.
To get this working you need to add a sort field to your custom DataObject class and add GridFieldSortableRows() as a component to your GridField.
For the following examples I will use HomePage as the page with a has_many relationship to a Slide DataObject.
Slide
class Slide extends DataObject
{
private static $db = array (
'Title' => 'HTMLText',
'SortOrder' => 'Int'
);
private static $has_one = array (
'HomePage' => 'HomePage'
);
private static $summary_fields = array(
'Title' => 'Title'
);
private static $default_sort = 'SortOrder ASC';
private static $singular_name = 'Slide';
private static $plural_name = 'Slides';
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->removeByName('SortOrder');
return $fields;
}
}
HomePage
class HomePage extends Page {
private static $has_many = array (
'Slides' => 'Slide'
);
public function getCMSFields()
{
$fields = parent::getCMSFields();
$slidesFieldConfig = GridFieldConfig_RecordEditor::create();
$slidesFieldConfig->addComponent(new GridFieldSortableRows('SortOrder'));
$slidesField = GridField::create(
'Slides',
'Slide',
$this->Slides(),
$slidesFieldConfig
);
$fields->addFieldToTab('Root.Slides', $slidesField);
return $fields;
}
}
GridFieldExtensions
The GridFieldExtensions module contains GridFieldOrderableRows to control the sort order on a GridField, just like the SortableGridField module. It also has other useful GridField tools.
To get this working you need to add a sort field to your custom DataObject class and add GridFieldOrderableRows() as a component to your GridField.
Your code would be just like the above example except the component you add to your GridFieldConfig is GridFieldOrderableRows:
public function getCMSFields()
{
$fields = parent::getCMSFields();
$slidesFieldConfig = GridFieldConfig_RecordEditor::create();
$slidesFieldConfig->addComponent(new GridFieldOrderableRows('SortOrder'));
...
}
Related
Before i start, Note that I'm learning symfony so keep that in mind ! I just want to understand how it works.
Here's what i am trying to achieve :
I would like to make a working crud example of entities inheritance using doctrine. So this is how my example looks like :
Abstract Parent class : character
Child class 1 : Magician
Child class 2 : Warrior
Child class 3 : Archer
So after reading some documentation i decided to use the STI (Single Table Inheritance) of Doctrine.
Parent class :
/**
* Character
*
* #ORM\Table(name="character")
* #ORM\Entity(repositoryClass="AppBundle\Repository\CharacterRepository")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"magician_db" = "Magician", "warrior_db" = "Warrior", "archer_db" = "Archer"})
*/
abstract class Character{
protected id;
protected name;
public function getId();
public function getName();
public function setName();
}
Child Class 1 :
class Warrior extends Character{
protected armor;
public function battleShout();
}
Child Class 2:
class Magician extends Character{
protected silk;
public function spellAnnounce();
}
Child Class 3:
class Archer extends Character{
protected leather;
public function arrows();
}
I managed to create the table in my db, and i successfully loaded my fixtures for tests purposes. I also made my main view work (listing all characters).
My Problem :
Now i want to be able to create, edit & delete a specific character in the list with a single form. So for example i would have a 'type' select field where i can select 'warrior' , 'magician' or 'archer' and then i would be able to fill in the specific fields of the chosen entity. So let's say i choose 'warrior' in the form, then i would like to be able to set the armor property (along with the parents one of course) and persist it in the database.
I don't know how to do it since my parent class is abstract so i can't create a form based on that object.
Thx in advance for your help, i really need it !
PS: If there is a better solution / implementation don't hesitate !
The easiest way is to provide all fields and to remove them according to the 'type' value.
To do that you have to implement the logic on the client side (for displaying purpose) and server side (so that the removed fields cannot be changed in your entity).
On the client side :
Use javascript to hide the types which can't be set for each 'type' change (you can use JQuery and the .hide() function).
On the server side:
Add a PRE_BIND event to your form type, to remove the fields from the form :
http://symfony.com/doc/current/components/form/form_events.html#a-the-formevents-pre-submit-event
Your Form should look like :
// ...
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
$form = $formFactory->createBuilder()
->add('type', ChoiceType::class)
->add('armor')
->add('silk')
->add('leather')
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$submittedData = $event->getData();
$form = $event->getForm();
switch($submittedData['type'])
{
case 'warrior':
$form->remove('silk');
$form->remove('leather');
break;
case 'magician':
$form->remove('armor');
$form->remove('leather');
break;
case 'archer':
$form->remove('armor');
$form->remove('silk');
break;
default:
throw new ...;
}
})
->getForm();
// ...
EDIT
To deal with Single Table Inheritance, you can't use an abstract class, the base class must be a normal entity.
In your form, just set the class as AppBundle\Character.
In your controller action which creates the character, you must initiate your entity with something like this :
if($request->isMethod('POST')){
// form has been submitted
switch($request->get('type'))
{
case 'warrior':
$entity = new Warrior();
...
}
}
else{
// form has not been submitted, default : Warrior
$entity = new Warrior();
}
By editing and removing the character, you can directly deal with the Character Entity.
I recommand to not let the user change the type by edit, see Doctrine: Update discriminator for SINGLE_TABLE Inheritance
i have a question on mongodb, model cakephp and relationships.
I'd create the following relations:
User -> hasMany -> City
City -> belongsTo -> User
In MongoDB, I have two tables:
users
cities (with key user_id)
In cakephp, I have 2 model:
User.php
class User extends Model {
public $name = 'User';
public $actsAs = array('Containable');
public $hasMany = array ('City');
..
}
and:
City.php
class City extends Model {
public $name = 'City';
public $actsAs = array('Containable');
public $belongsTo = array('User');
..
}
In my controller I use :
$user = $this->User->find('all');
but it doesn't work. In sql dump, cakephp uses a find only on tbl users.
Why? Where I wrong?
I normally place recursive to -1 and containable in app model, so it applies to all models you create unless you override specifically.
class AppModel extends Model {
public $actsAs = array('Containable');
public $recursive = -1;
}
Your relationships are fine, although I usually add className and foreignKey just to be safe and clear. In your controller you should do something like this:
$users = $this->User->find('all', array(
'contain' => array(
'City'
)
));
Recursive will prevent any associated records being included by default, this is good as sometimes you do not need the recursive data and extra data will help slow down your application.
Next adding contain into your find call may seem like a chore but it will be clear and concise what you are querying, any 3rd party developer will understand exactly what you are doing if they know how to use Cake. Hope this helps.
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?
So I've created myself a custom form element which has a custom view helper. Now I want to be able to set certain parameters/variables on this form element and be able to access them in my element's view helper. How can I do that?
Here's an example of what I am talking about:
adding the element to the form:
$element = new My_Form_Element_Picker('elementname');
$element->setFoobar('hello');
// or
$form->addElement('Picker', 'elementname', array('foobar' => 'hello'));
form element:
class My_Form_Element_Picker extends Zend_Form_Element_Xhtml
{
public $helper = 'pickerElement';
}
view helper:
class My_View_Helper_PickerElement extends Zend_View_Helper_FormElement
{
public function pickerElement($name, $value = null, $attribs = null)
{
//now I want to check if the 'foobar' option was set, otherwise use a default value
$foobar = 'default';
}
}
There is a fourth optional argument to the view helper that might do the trick for you.
if you define your view helper like this:
public function pickerElement( $name, $value=null, $attribs=null, $options=null ) { }
And then inside your actual form element you define it like this:
class My_Form_Element_Picker extends Zend_Form_Element_Xhtml {
public $helper = 'pickerElement';
public $options = array();
public function setFoobar( $foobar ) {
$this->options['foobar'] = $foobar;
}
}
You will find that the options are passed into the view helper and can be used.
This code is from memory so please forgive any mistakes, this method definitely works for me though.
I have separate db_table classes for books, book_sections and users (system end users). in book table has columns for book_title, section_id(book_section) , data_entered_user_id(who entered book info).
go to this url to see the image(I'm not allow to post images bacause I'm new to stackoverflow)
img685.imageshack.us/img685/9978/70932283.png
in the backend of the system I added a form to edit existing book (get book id from GET param and fill the relevant data to the form). form has elements for book_title, book_section and data_entered_user.
to get exciting book data to "edit book form" I join book_section and user tables with book table to get book_section_name and username(of data_entered_user: read only- display on side bar)
go to this url to see the image(I'm not allow to post images bacause I'm new to stackoverflow)
img155.imageshack.us/img155/2947/66239915.jpg
In class App_Model_Book extends Zend_Db_Table_Abstract
public function getBookData($id){
$select = $this->select();
$select->setIntegrityCheck(false);
$select->from('book', array('id','section_id','data_entered_user_id',...));
$select->joinInner('section','book.section_id = section.id',array('section_name' =>'section.name' ));
$select->joinInner(array('date_entered_user' => 'user'),'book.date_entered_user_id = date_entered_user.id',array('date_entered_user_name' =>'date_entered_user.user_name' ));
$select->where("book.id = ?",$id);
return $this->fetchRow($select);
}
public function updateBookData($id,$title,$section_id,...)
{
$existingRow = $this->fetchRow($this->select()->where('id=?',$id));
$existingRow->title = $title;
$existingRow->section_id = $section_id;
//....
$existingRow->save();
}
In Admin_BookController -> editAction()
$editForm = new Admin_Form_EditBook();
$id = $this->_getParam('id', false);
$bookTable = new App_Model_Book();
$book_data = $bookTable ->getBookData($id);
//set values on form to print on form when edit book
$editForm->book_title->setValue($book_data->title);
$editForm->book_section->setValue($book_data->section_name);
//........
//If form data valid
if($this->getRequest()->isPost() && $editForm->isValid($_POST)){
$bookTable = new App_Model_Book();
$bookTable ->updateBookData(
$id,
//get data from submitted form
$editForm->getValue('title'),
//....
);
when editing an exsiting book
get data from getBookData() method on App_Model_Book class
If form data is valid after submiting edited data, save data with updateBookData() method on App_Model_Book class
but I saw that if I created a custom
Db_Table_Row(extends Zend_Db_Table_Row)
class for book table with
book_section_name and
data_entered_user_name I can use it
for get existing book data and save it
after editing book data without
creating new Book(db_table) class and without calling updateBookData() to save updated data.But I don't know which code I should write on custom
Db_Table_Row(extends Zend_Db_Table_Row)
class.
I think you can understand my problem, in simple
how to write a custom db_table_row class to create a custom row with data
form 2 joined tables for a perticular db_table class ?
I'm new to zend framewok and to stackoverflow. forgive me if you confused with my first question.
Thanks again.
1) at your db_table class create field which contain row class, for example:
protected $_rowClass = 'App_Model_Db_Books_Row';
and add reference map for parent tables:
protected $_referenceMap = array(
'Section' => array(
'columns' => 'sectionId',
'refTableClass' => 'App_Model_Db_Sections',
'refColumns' => 'id'
),
'User' => array(
'columns' => 'userId',
'refTableClass' => 'App_Model_Db_Users',
'refColumns' => 'id'
)
);
2) at row class you must define variables for parent tables:
protected $section;
protected $user;
In such cases i create method called "load":
public function load()
{
$this->section = $this->findParentRow('App_Model_Db_Sections');
$this->user = $this->findParentRow('App_Model_Db_Users');
}
And in constructor i call this method:
public function __construct(array $config = array())
{
parent::__construct($config);
$this->load();
}
As a result after:
$book_data = $bookTable ->getBookData($id);
you can access data from reference table, for example:
$book_data->section->name = "Book Section";
$book_data->section->save();