I'm building an API using FOSRestBundle for adding products to a basket. For the sake of keeping this example simple we have a range of products which come in different sizes.
I'd like to be able to specify the size code in the JSON request. For example:
{
"product": 3,
"size": "S"
}
(I'd also like to use the product code instead of the database ID, but that's for another day!)
Other parts of the project I have done similar tasks using data transformers, but these were simpler forms where the values didn't change based on other fields selected values.
So my current basket form...
class BasketAddType extends AbstractType
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product', 'entity', [
'class' => 'CatalogueBundle:Product',
]);
$builder->get('product')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
}
public function onPostSubmit(FormEvent $event)
{
// this will be Product entity
$form = $event->getForm();
$this->addElements($form->getParent(), $form->getData());
}
protected function addElements(FormInterface $form, Product $product = null)
{
if (is_null($product)) {
$sizes = [];
} else {
$sizes = $product->getSizes();
}
$form
->add('size', 'size', [
'choices' => $sizes
]);
}
public function getName()
{
return '';
}
}
The custom size form type I'm using above is so I can add the model transformer. As found in this answer https://stackoverflow.com/a/19590707/3861815
class SizeType extends AbstractType
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new SizeTransformer($this->em));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'class' => 'CatalogueBundle\Entity\Size'
]);
}
public function getParent()
{
return 'entity';
}
public function getName()
{
return 'size';
}
}
And finally the transformer.
class SizeTransformer implements DataTransformerInterface
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function transform($size)
{
if (null === $size) {
return '';
}
return $size->getCode();
}
public function reverseTransform($code)
{
// WE NEVER GET HERE?
$size = $this->em->getRepository('CatalogueBundle:Size')
->findOneByCode($code);
if (null === $size) {
throw new TransformationFailedException('No such size exists');
}
return $size;
}
}
So I did a quick exit; in reverseTransform and it's never fired so I will always get an error on the size element about it being invalid.
What would be the best way to getting a data transformer onto the size field here?
So the problem was that I was using an entity type instead of a text type when using the model data transformer.
Here is my working code albeit probably not perfect, the primary form
class BasketAddType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product', 'entity', [
'class' => 'CatalogueBundle:Product'
]);
$builder->get('product')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
}
public function onPostSubmit(FormEvent $event)
{
$form = $event->getForm()->getParent();
$product = $event->getForm()->getData();
$form
->add('size', 'size', [
'sizes' => $product->getSizes()->toArray() // getSizes() is an ArrayCollection
);
}
public function getName()
{
return '';
}
}
My custom form size type which applies the model transformer with the provided size options.
class SizeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new SizeTransformer($options['sizes']));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired([
'sizes'
]);
}
public function getParent()
{
return 'text';
}
public function getName()
{
return 'size';
}
}
And finally the size transformer.
class SizeTransformer implements DataTransformerInterface
{
protected $sizes;
public function __construct(array $sizes)
{
$this->sizes = $sizes;
}
public function transform($size)
{
if (null === $size) {
return '';
}
return $size->getCode();
}
public function reverseTransform($code)
{
foreach ($this->sizes as $size) {
if ($size->getCode() == $code) {
return $size;
}
}
throw new TransformationFailedException('No such size exists');
}
}
This solution wouldn't work too well if there were a high number of sizes available for each product. Guess if that was the case I'd need to pass both the EntityManager and the product into the transformer and query the DB accordingly.
This is a dependent fields
I have a Product entity wich in relation with a Category entity which in relation with a Collection entity
here is my code for the add product form
class ProductsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$propertyPathToCategory = 'category';
$builder
->add('title')
->add('description','textarea')
->add('collection','entity', array(
'class' => 'FMFmBundle:Collections',
'empty_value' => 'Collection',
'choice_label' => 'title'
))
->addEventSubscriber(new AddCategoryFieldSubscriber($propertyPathToCategory));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FM\FmBundle\Entity\Products'
));
}
public function getName()
{
return 'fm_fmbundle_products';
}
}
the AddCategoryFieldSubscriber
//Form/EventListener
class AddCategoryFieldSubscriber implements EventSubscriberInterface
{
private $propertyPathToCategory;
public function __construct($propertyPathToCategory)
{
$this->propertyPathToCategory = $propertyPathToCategory;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
);
}
private function addCategoryForm($form, $collection_id)
{
$formOptions = array(
'class' => 'FMFmBundle:Categories',
'empty_value' => 'Category',
'label' => 'Category',
'attr' => array(
'class' => 'Category_selector',
),
'query_builder' => function (EntityRepository $repository) use ($collection_id) {
$qb = $repository->createQueryBuilder('category')
->innerJoin('category.collection', 'collection')
->where('collection.id = :collection')
->setParameter('collection', $collection_id)
;
return $qb;
}
);
$form->add($this->propertyPathToCategory, 'entity', $formOptions);
}
public function preSetData(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
$accessor = PropertyAccess::createPropertyAccessor();
$category = $accessor->getValue($data, $this->propertyPathToCategory);
$collection_id = ($category) ? $category->getCollection()->getId() : null;
$this->addCategoryForm($form, $collection_id);
}
public function preSubmit(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
$collection_id = array_key_exists('collection', $data) ? $data['collection'] : null;
$this->addCategoryForm($form, $collection_id);
}
}
add new action in the controller
public function SelectCategoryAction(Request $request)
{
$collection_id = $request->get('collecton_id');
$em = $this->getDoctrine()->getManager();
$categories = $em->getRepository('FMFmBundle:Categories')->findByCollection($collection_id);
$Jcategories=array();
foreach($categories as $category){
$Jcategories[]=array(
'id' => $category->getId(),
'title' => $category->getTitle()
);
}
return new JsonResponse($Jcategories);
}
add new route for the action
select_category:
path: /selectcategory
defaults: { _controller: FMFmBundle:Product:SelectCategory }
and some ajax
$("#collectionSelect").change(function(){
var data = {
collecton_id: $(this).val()
};
$.ajax({
type: 'post',
url: 'selectcategory',
data: data,
success: function(data) {
var $category_selector = $('#categorySelect');
$category_selector.empty();
for (var i=0, total = data.length; i < total; i++)
$category_selector.append('<option value="' + data[i].id + '">' + data[i].title + '</option>');
}
});
});
References:
Dependent Forms
Related
I have created a custom type like this:
private $selectedCountryId = 0;
public function configureOptions(OptionsResolver $resolver)
{
$queryBuilder = function (CountryRepository $er) {
return $er->createQueryBuilder('c')
->where('c.active = 1')
->orWhere('c.id = :sid')
->orderBy('c.ord', 'ASC')
->addOrderBy('c.name', 'ASC')
->setParameter('sid', $this->selectedCountryId)
;
};
$resolver->setDefaults(array(
'query_builder' => $queryBuilder,
'class' => Country::class,
));
}
public function getParent()
{
return EntityType::class;
}
public function getBlockPrefix()
{
return 'CountryType';
}
What I am trying to do is to modify the selectedCountryId parameter depending on the instance of an Entity:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(
FormEvents::PRE_SET_DATA, array($this, 'onPreSetData')
);
}
public function onPreSetData(FormEvent $event)
{
if ($event->getData() instanceof Country) {
$this->selectedCountryId = $event->getData()->getId();
}
}
But the setOptions is already called at this stage and I dont't know how to modify query builder here.
Generally what I am trying to achieve is allow given country on the dropdown if it was already stored against given entity (and de-activated later).
In that case, you still have one opportunity to modify the query builder on PRE_SET_DATA event, because the choice list is not built until the form view is created.
This should do the trick:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
if (null !== $country = $event->getData()) {
// at this point the option is already resolved
// so it'll return the QueryBuilder instance
$qb = $event->getForm()->getConfig()->getOption('query_builder');
$qb->orWhere('c = :country')->setParameter('country', $country);
}
});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => Country::class,
'query_builder' => function (CountryRepository $r) {
return $r->createQueryBuilder('c')
->where('c.active = 1')
->orderBy('c.ord', 'ASC')
->addOrderBy('c.name', 'ASC')
;
},
]);
}
It'll only work for instances of objects.
I have entity1 and entity2.
In the entity1's form, I am displaying a choice list where the options are comming from entity2.
I want to save the selected choice as string inside a column in entity1's table, but I dont want to create any relations between the tables.
How Should I do that?
class Entity1 {
/**
* #ORM\Column(type="string")
*/
private $historico;
}
class Entity2 {
/**
* #ORM\Column(type="string")
*/
private $description;
}
Entity1FormType.php
$builder->add('historico', EntityType::class, [
'class' => Entity2::class,
'choice_label' => 'description',
'choice_value' => 'description',
'placeholder' => ''
]);
The choices display fine, but when I submit I get the following error:
Expected argument of type "string", "App\Entity\Entity2" given.
If I use 'mapped' => false, the input submit as null.
How do I convert the entity object to string?
Help a symfony noob :)
If you use mapped => false you have to fetch the data manually in your controller after the form is submitted.
so you will have something like this:
public function postYourFormAction(Request $request)
{
$entity1 = new Entity1();
$form = $this->createForm(Entity1Type::class $entity1);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$entity1 = $form->getData;
$historico = $form->get('historico')->getData();
$entity1->setHistorico($historico);
$em->persist($entity1);
$em->flush();
}
}
This can be done with data transformers so you would not have to unmap fields.
Your form could be as below. Note the choice value getting the string
class OrderType extends AbstractType
{
public function __construct(private ItemToStringTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('itemCode', EntityType::class, [
'class' => Item::class,
'autocomplete' => true,
'required' => true,
'choice_value' => function (?Item $entity) {
return $entity ? $entity->getCode() : '';
},
// validation message if the data transformer fails
'invalid_message' => 'That is not a valid Item Code',
]);
$builder->get('accountCode')->addModelTransformer($this->transformer);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}
And then the data transformer can be as below
class ItemToStringTransformer implements DataTransformerInterface
{
public function __construct(private EntityManagerInterface $entityManager)
{
}
//transforming item object to string
public function reverseTransform($item): ?string
{
if (null === $item) {
return null;
}
return $item->getCode();
}
// transforming string to item object
public function transform($itemCode): ?Item
{
if (!$itemCode) {
return null;
}
$item = $this->entityManager
->getRepository(Item::class)
// query for the glCode with this id
->findOneBy(['code' => $itemCode])
;
if (null === $item) {
// causes a validation error
// this message is not shown to the user
// see the invalid_message option
throw new TransformationFailedException(sprintf('Item code "%s" does not exist!', $itemCode));
}
return $item;
}
}
You can read further in the symfony documentation https://symfony.com/doc/current/form/data_transformers.html#example-2-transforming-an-issue-number-into-an-issue-entity
I'm using an eventSubscriber to dynamically load a field (Paciente) into a form, in its preSubmit function I need to get in addition to the ID of the paciente, the dni of paciente. The id I can get directly, but the dni need to bring the entity, and I do not know how I can do it from here.
My event in question is as follows:
class AddHistoriaClinicaFieldSubscriber implements EventSubscriberInterface
{
private $propertyPathToHistoriaClinica;
public function __construct($propertyPathToHistoriaClinica)
{
$this->propertyPathToHistoriaClinica = $propertyPathToHistoriaClinica;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::POST_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
);
}
private function addHistoriaClinicaForm($form, $paciente_id)
{
$formOptions = array(
'class' => 'BiobancoBundle:HistoriaClinica',
'empty_value' => '-- SELECCIONAR HISTORIA CLINICA --',
'label' => 'Historia Clínica',
'attr' => array(
'class' => 'historia_clinica_selector',
),
'query_builder' => function (EntityRepository $repository) use ($paciente_id) {
$qb = $repository->createQueryBuilder('h')
->innerJoin('h.paciente', 'p')
->where('p.id = :p')
->setParameter('p', $paciente_id)
;
return $qb;
},
);
$form->add($this->propertyPathToHistoriaClinica, 'entity', $formOptions);
}
public function preSetData(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
$accessor = PropertyAccess::createPropertyAccessor();
$h = $accessor->getValue($data, $this->propertyPathToHistoriaClinica);
$paciente_id = ($h) ? $h->getPaciente()->getNumeroIdentificacion() : null;
$this->addHistoriaClinicaForm($form, $paciente_id);
}
public function preSubmit(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
$paciente_id = array_key_exists('paciente', $data) ? $data['paciente'] : null;
//HERE IS WHERE I NEED TO OBTAIN THE DNI, TO PASS IT TO THE FORM
//dump($data);die();
$this->addHistoriaClinicaForm($form, $paciente_id);
}
}
EDIT 1 OF METHOD preSubmit with LifecycleEventArgs :
use Doctrine\ORM\Event\LifecycleEventArgs;
...
public function preSubmit(FormEvent $event, LifecycleEventArgs $args)
{
$data = $event->getData();
$form = $event->getForm();
$paciente_id = array_key_exists('paciente', $data) ? $data['paciente'] : null;
dump($args->getEntityManager()->getRepository("BiobancoBundle:Paciente")->find($paciente_id));die();
$this->addHistoriaClinicaForm($form, $paciente_id);
}
ERROR in line declaration of method:
Catchable Fatal Error: Argument 2 passed to BiobancoBundle\Form\EventListener\AddHistoriaClinicaFieldSubscriber::preSubmit() must be an instance of Doctrine\ORM\Event\LifecycleEventArgs, string given.
create a factory to inject the EntityManager or Repository into your subscriber.
class AddHistoriaClinicaFieldSubscriberFactory
{
public static function create($entityManager)// typehint this
{
// You could retrieve the repo here, so you don't pass the whole em to the instance
$instance = new AddHistoriaClinicaFieldSubscriber($entityManager);
// ...
return $instance;
}
}
Register it
# app/config/services.yml
services:
# ...
app.add_historia_clinica_field_subscriber_factory:
class: YOURNAMESPACE\AddHistoriaClinicaFieldSubscriberFactory
app.add_historia_clinica_field_subscriber:
class: YOURNAMESPACE\AddHistoriaClinicaFieldSubscriber
factory: 'add_historia_clinica_field_subscriber_factory:create'
arguments: ['#doctrine.orm.default_entity_manager']
tags:
- { name: WHATEVERYOUHAVEHERE }
And add a constructor to you Subscriber
class AddHistoriaClinicaFieldSubscriber implements EventSubscriberInterface
{
// ...
protected $entityManager;
public function __construct($entityManager) {// typehint this
$this->entityManager = $entityManager;
}
// ...
}
Let me know if this is clear enough
For more info check: http://symfony2-document.readthedocs.io/en/latest/cookbook/service_container/factories.html
I'm currently trying to implement a key-pair value for a form type, which is used together with the FOSRestBundle to allow for sending a request like the following:
{
"user": {
"username": "some_user",
"custom_fields": {
"telephone": "07777",
"other_custom_field": "other custom value"
}
}
}
The backend for this is represented as follows:
User
id, username, customFields
CustomUserField
id, field
CustomUserFieldValue
user_id, field_id, value
I've currently made a custom form as follows:
<?php
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username')
->add(
'custom_fields',
'user_custom_fields_type'
)
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Acme\DemoBundle\Entity\User',
'csrf_protection' => false,
)
);
}
public function getName()
{
return 'user';
}
}
And my user_custom_fields_type:
<?php
class CustomUserFieldType extends AbstractType
{
private $em;
/**
* #param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$fields = $this->em->getRepository('AcmeDemoBundle:CustomUserField')->findAll();
foreach($fields as $field) {
$builder->add($field->getField(), 'textarea');
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'invalid_message' => 'The selected custom field does not exist'
)
);
}
public function getParent()
{
return 'collection';
}
public function getName()
{
return 'user_custom_fields_type';
}
}
This keeps giving me the error that there are extra fields. Which are the ones I've added in the CustomUserFieldType. How can I get this working?
Note: this is a simplified version of the actual code, I've tried removing all the irrelevant code.
You need to use form listeners to add dynamic fields to your forms:
http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html
In your case a PRE_SET_DATA should suffice. Something like this:
$em = $this->em;
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ( $em )
{
// get the form
$form = $event->getForm();
// fetch your data here
$fields = $em->getRepository('AcmeDemoBundle:CustomUserField')->findAll();
foreach($fields as $field)
{
// make sure you add the new fields to $form and not $builder from this event
$form->add($field->getField(), 'textarea');
}
});
I had exactly the same issue and finally solved it by parsing the custom fields manually. If there is another solution, please share :)
In the UserType form:
$builder->addEventListener(FormEvents::POST_SET_DATA,
function (FormEvent $event) use ( $oEm, $oUser )
{
$oForm = $event->getForm();
$aFields = $oEm->getRepository('MyDBBundle:CustomUserField')->findAll();
/** #var CustomUserField $oField */
foreach($aFields as $oField)
{
$oForm->add(
'custom__'.$oField->getKey(),
$oField->getType(),
array(
'label' => $oField->getField(),
'mapped' => false,
'required' => false
)
);
/** #var CustomUserFieldValue $oFieldValue */
$oFieldValue = $oEm->getRepository('MyDBBundle:CustomUserFieldValue')->findOneBy(array('user' => $oUser, 'field' => $oField));
if(null !== $oFieldValue) {
$oForm->get('custom__' . $oField->getKey())->setData($oFieldValue->getValue());
}
}
}
);
Then, in your controller action which handles the request of the submitted form:
// Handle custom user fields
foreach($oForm->all() as $sKey => $oFormData)
{
if(strstr($sKey, 'custom__'))
{
$sFieldKey = str_replace('custom__', '', $sKey);
$oField = $oEm->getRepository('MyDBBundle:CustomUserField')->findOneBy(array('key' => $sFieldKey));
/** #var CustomUserFieldValue $oFieldValue */
$oFieldValue = $oEm->getRepository('MyDBBundle:CustomUserFieldValue')->findOneBy(array('user' => $oEntity, 'field' => $oField));
if($oFieldValue === null)
{
$oFieldValue = new CustomUserFieldValue();
$oFieldValue->setUser($oEntity);
$oFieldValue->setField($oField);
}
$oFieldValue->setValue($oFormData->getData());
$oEm->persist($oFieldValue);
}
}
(Assuming that there is both a "field" property and a "key" in the CustomUserField entity; key is a unique, spaceless identifier for your field and field is the human friendly and readable field label.)
This works so hope it can be helpful. However, wondering if someone has a better solution. :)
Here is the thing.
I want to make 2 forms in one view. One is bound to an entity and the other is a file type where I want to put .csv and catch the informations inside to fill the first entity.
I've already created the first one but I can't figure how to create the second and integrate it correctly in the same view so they don't fight each other. Here is my files (the csv process is not here yet)
My controller:
public function adminAction()
{
$form = $this->createForm(new StudentsType, null);
$formHandler = new StudentsHandler($form, $this->get('request'), $this->getDoctrine()->getEntityManager());
if( $formHandler->process() )
{
return $this->redirect( $this->generateUrl('EnsgtiEnsgtiBundle_ens'));
}
return $this->render('EnsgtiEnsgtiBundle:Appli:admin.html.twig', array(
'form' => $form->createView(),
));
}
First form:
class StudentsType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('nom', 'text')->setRequired(false)
->add('prenom', 'text')
->add('email', 'email')
->add('codeEtape', 'text')
->add('file', new StudentsListType)->SetRequired(false);
}
public function getName()
{
return 'ensgti_ensgtibundle_studentstype';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Ensgti\EnsgtiBundle\Entity\Students',
);
}
}
The Handler:
class StudentsHandler
{
protected $form;
protected $request;
protected $em;
public function __construct(Form $form, Request $request, EntityManager $em)
{
$this->form = $form;
$this->request = $request;
$this->em = $em;
}
public function process()
{
if( $this->request->getMethod() == 'POST' )
{
$this->form->bindRequest($this->request);
if( $this->form->isValid() )
{
$this->onSuccess($this->form->getData());
return true;
}
}
return false;
}
public function onSuccess(Students $students)
{
$this->em->persist($students);
$this->em->flush();
}
}
Second Form:
class StudentsListType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('', 'file');
}
public function getName()
{
return 'ensgti_ensgtibundle_studentslisttype';
}
}
The Handler:
class StudentsListHandler
{
protected $form;
protected $request;
protected $em;
public function __construct(Form $form, Request $request, EntityManager $em)
{
$this->form = $form;
$this->request = $request;
$this->em = $em;
}
public function process()
{
if( $this->request->getMethod() == 'POST' )
{
$this->form->bindRequest($this->request);
if( $this->form->isValid() )
{
//$this->onSuccess($this->form->getData());
print_r($this->form->getData());
return true;
}
}
return false;
}
public function onSuccess(/*StudentsList $studentsList*/)
{
//$this->em->persist($studentsList);
//$this->em->flush();
echo 'Petit test';
}
}
With this code I get this:
Neither property "file" nor method "getFile()" nor method "isFile()" exists in class "Ensgti\EnsgtiBundle\Entity\Students"
Which is normal since I'm bind to an entity with no file type. I'm a beginner so I keep reading docs but it's still hard for me to understand everything. Is there a way I can make 2 forms in the same view with one that is not bind to an entity but that will persist on it anyway via csv treatment??
The problem is that the form expects file to be a property of the bound entity. You just need to set the property_path to false. (Reference) e.g.
->add('file', new StudentsListType, array('property_path' => false))
Edit:
If it would make more sense to have the 2 forms completely separate, then remove the above ->add(/*...*/) entirely and just pass both forms to the view from the controller. You can then render each one individually.
use <iframe> to do that. Here is the link how to use it, just put in src atribute url to needed page. And you may display 2 pages in one layout. And this is how it looks