I wonder if someone has already figured my problem out.
I'm using Laravel 5 Full Calendar Helper in order to create a booking app.
What I want to achieve is to render the calendar on an specific day which is the booking date
For example if someone wants to book a room for 12/31/2015, he will be able to see the calendar on that date (day view), before book their room.
So far I got this
ConsultController
<?php
namespace App\Http\Controllers;
use App\Booking;
use App\Http\Requests\BookingRequest;
use App\Room;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
class ConsultController extends Controller
{
public function consult(BookingRequest $request){
// Look for the booking which matches with our search
$seek = Booking::where('room_id','=',$request->room)
->where('day','=',$request->day)
->where('start','=',$request->start)->first();
// If no booking matches, then book
if(is_null($seek)){
$book = $request;
// Get all the bookings on day requested
$bookings = Booking::where('day','=',$request->day)->get();
// creating events for the calendar
$events = [];
foreach($bookings as $booking){
$events[] = \Calendar::event(
''.$booking->room->name, //event title
false, //full day event?
$booking->day->format('Y-m-d').$booking->start, //start time (you can also use Carbon instead of DateTime)
$booking->day->format('Y-m-d').$booking->end, //end time (you can also use Carbon instead of DateTime)
$booking->id //optionally, you can specify an event ID
);
}
$events [] = \Calendar::event(
''.Room::find($book->room)->name, //event title
false,
$book->day.$book->start,
$book->day.$book->end,
0,
[
'backgroundColor' => '#ff5722'
]
);
// Adding event for the calendar
$calendar = \Calendar::addEvents($events);
return view('consult.book')->with(['calendar' => $calendar,'book'=>$book]);
}
// If a record matches then redirect back
else{
Session::flash('flash_message','Lo sentimos ese horario está ocupado');
return redirect()->back();
}
}
}
I have this view
but what I want is this view
Taking a detailed review to the documentation I found out that this helper class has a setOptions method which allows to have more control when rendering the calendar
I just added that method and reached what I was looking for
$calendar = \Calendar::addEvents($events)->setOptions([ 'defaultDate' => $book->day,'defaultView' => 'agendaDay']);
Related
In my Symfony project, I have created the table "event" and datatime field in it named start. In twig, I wish to filter and display upcoming events. So events that have passed would be visible any more.
At the moment, I used {% if event.start > date() %}. It worked to hide events that happened days before today. I wanted also to hide events that already happened today but currently it doesn't work when time has passed of the today's event.
How can I hide events that time already has passed ?
better to make a function in the repository and call it in the controller not using findAll and hide events.
// EventRepository
public function eventsList()
{
return $this->createQueryBuilder('e')
->andWhere('e.date >= :today')
->setParameter('today', new \DateTime())
->orderBy('e.id', 'DESC')
->getQuery()
->getResult()
;
}
Here is the solution :
Make sure the field is datetime.
Use {% if event.start > 'now' %}and it will take into account day (of today) and (current) time.
Here is a better solution (I precise that an address is given to events, and address has a "bigcity" registered, this why I am using LocationRepository) :
EventController.php
#[Route('/events', name: 'events')]
public function events(
Request $request,
EventRepository $eventRepository,
CategoryRepository $categoryRepository,
BigCityRepository $bigcityRepository,
LocationRepository $locationRepository
){
$category = $categoryRepository->findOneById($request->query->get('category'));
$bigcity = $bigcityRepository->findOneById($request->query->get('bigcity'));
$location = $locationRepository->findAll($request->query->get('location.bigcity'));
$events = $eventRepository->findBy(['category' => $category, 'address' => $location]);
return $this->render("front/events.html.twig", [
'events' => $events,
'category' => $category,
'bigcity' => $bigcity
]);
}
in my project I have some forms with choice types with a lot of options.
So I decided to build an autocomplete choice type based on jquery autocomplete, which adds new <option> HTML elements to the original <select> on runtime. When selected they are submitted correctly, but can't be handled within the default ChoicesToValuesTransformer, since the don't exist in my form when I create it.
How can I make symfony accept my dynamically added values?
I found this answer Validating dynamically loaded choices in Symfony 2 , where the submitted values are used to modify the form on the PRE_SUBMIT form event, but couldn't get it running for my situation. I need to change choices known to the current type instead of adding a new widget to the form
To deal with dynamically added values use 'choice_loader' option of choice type. It's new in symfony 2.7 and sadly doesn't have any documentaion at all.
Basically it's a service implementing ChoiceLoaderInterface which defines three functions:
loadValuesForChoices(array $choices, $value = null)
is called on build form and receives the preset values of object bound into the form
loadChoiceList($value = null)
is called on build view and should return the full list of choices in general
loadChoicesForValues(array $values, $value = null)
is called on form submit and receives the submitted data
Now the idea is to keep a ArrayChoiceList as private property within the choice loader. On build form loadValuesForChoices(...) is called, here we add all preset choices into our choice list so they can be displayed to the user. On build view loadChoiceList(...) is called, but we don't load anything, we just return our private choice list created before.
Now the user interacts with the form, some additional choices are loaded via an autocomplete and put into th HTML. On submit of the form the selected values are submitted and in our controller action first the form is created and afterwards on $form->handleRequest(..) loadChoicesForValues(...) is called, but the submitted values might be completly different from those which where included in the beginning. So we replace our internal choice list with a new one containing only the submitted values.
Our form now perfectly holds the data added by autocompletion.
The tricky part is, that we need a new instance of our choice loader whenever we use the form type, otherwise the internal choice list would hold a mixture of all choices.
Since the goal is to write a new autocomplete choice type, you usually would use dependency injection to pass your choice loader into the type service.
But for types this is not possible if you always need a new instance, instead we have to include it via options. Setting the choice loader in the default options does not work, since they are cached too. To solve that problem you have to write a anonymous function which needs to take the options as parameters:
$resolver->setDefaults(array(
'choice_loader' => function (Options $options) {
return AutocompleteFactory::createChoiceLoader();
},
));
Edit:
Here is a reduced version of the choice loader class:
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
class AutocompleteChoiceLoader implements ChoiceLoaderInterface
{
/** #var ChoiceListInterface */
private $choiceList;
public function loadValuesForChoices(array $choices, $value = null)
{
// is called on form creat with $choices containing the preset of the bound entity
$values = array();
foreach ($choices as $key => $choice) {
// we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value
if (is_callable($value)) {
$values[$key] = (string)call_user_func($value, $choice, $key);
}
else {
$values[$key] = $choice;
}
}
// this has to be done by yourself: array( label => value )
$labeledValues = MyLabelService::getLabels($values);
// create internal choice list from loaded values
$this->choiceList = new ArrayChoiceList($labeledValues, $value);
return $values;
}
public function loadChoiceList($value = null)
{
// is called on form view create after loadValuesForChoices of form create
if ($this->choiceList instanceof ChoiceListInterface) {
return $this->choiceList;
}
// if no values preset yet return empty list
$this->choiceList = new ArrayChoiceList(array(), $value);
return $this->choiceList;
}
public function loadChoicesForValues(array $values, $value = null)
{
// is called on form submit after loadValuesForChoices of form create and loadChoiceList of form view create
$choices = array();
foreach ($values as $key => $val) {
// we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value
if (is_callable($value)) {
$choices[$key] = (string)call_user_func($value, $val, $key);
}
else {
$choices[$key] = $val;
}
}
// this has to be done by yourself: array( label => value )
$labeledValues = MyLabelService::getLabels($values);
// reset internal choice list
$this->choiceList = new ArrayChoiceList($labeledValues, $value);
return $choices;
}
}
A basic (and probably not the best) option would be to unmap the field in your form like :
->add('field', choiceType::class, array(
...
'mapped' => false
))
In the controller, after validation, get the data and send them to the entity like this :
$data = request->request->get('field');
// OR
$data = $form->get('field')->getData();
// and finish with :
$entity = setField($data);
I am trying to modify a field collection in a node that already exists so I can change an image on the first element in an array of 3. The problem is, the hostEntity info is not set when I do a entity_load or entity_load_single so when I do a:
$field_collection_item->save(true); // with or without the true
// OR
$fc_wrapper->save(true); // with or without the true
I get the following error:
Exception: Unable to save a field collection item without a valid reference to a host entity. in FieldCollectionItemEntity->save()
When i print_r the field collection entity the hostEntity:protected fields are indeed empty. My field collection is setup as follows:
field_home_experts
Expert Image <--- Want to change this data only and keep the rest below
field_expert_image
Image
Expert Name
field_expert_name
Text
Expert Title
field_expert_title
Text
Here is the code I am trying to use to modify the existing nodes field collection:
$node = getNode(1352); // Get the node I want to modify
// There can be up to 3 experts, and I want to modify the image of the first expert
$updateItem = $node->field_home_experts[LANGUAGE_NONE][0];
if ($updateItem) { // Updating
// Grab the field collection that currently exists in the 0 spot
$fc_item = reset(entity_load('field_collection_item', array($updateItem)));
// Wrap the field collection entity in the field API wrapper
$fc_wrapper = entity_metadata_wrapper('field_collection_item', $fc_item);
// Set the new image in place of the current
$fc_wrapper->field_expert_image->set((array)file_load(4316));
// Save the field collection
$fc_wrapper->save(true);
// Save the node with the new field collection (not sure this is needed)
node_save($node);
}
Any help would be greatly appreciated, I am still quite new to Drupal as a whole (end-user or developer)
Alright so I think I have figured this out, I wrote up a function that will set a field collection values:
// $node: (obj) node object returned from node_load()
// $collection: (string) can be found in drupal admin interface:
// structure > field collections > field name
// $fields: (array) see usage below
// $index: (int) the index to the element you wish to edit
function updateFieldCollection($node, $collection, $fields = Array(), $index = 0) {
if ($node && $collection && !empty($fields)) {
// Get the field collection ID
$eid = $node->{$collection}[LANGUAGE_NONE][$index]['value'];
// Load the field collection with the ID from above
$entity = entity_load_single('field_collection_item', array($eid));
// Wrap the loaded field collection which makes setting/getting much easier
$node_wrapper = entity_metadata_wrapper('field_collection_item', $entity);
// Loop through our fields and set the values
foreach ($fields as $field => $data) {
$node_wrapper->{$field}->set($data);
}
// Once we have added all the values we wish to change then we need to
// save. This will modify the node and does not require node_save() so
// at this point be sure it is all correct as this will save directly
// to a published node
$node_wrapper->save(true);
}
}
USAGE:
// id of the node you wish to modify
$node = node_load(123);
// Call our function with the node to modify, the field collection machine name
// and an array setup as collection_field_name => value_you_want_to_set
// collection_field_name can be found in the admin interface:
// structure > field collections > manage fields
updateFieldCollection(
$node,
'field_home_experts',
array (
'field_expert_image' => (array)file_load(582), // Loads up an existing image
'field_expert_name' => 'Some Guy',
'field_expert_title' => 'Some Title',
)
);
Hope this helps someone else as I spent a whole day trying to get this to work (hopefully I won't be a noob forever in Drupal7). There may be an issue getting formatted text to set() properly but I am not sure what that is at this time, so just keep that in mind (if you have a field that has a format of filtered_html for example, not sure that will set correctly without doing something else).
Good luck!
Jake
I was still getting the error, mentioned in the question, after using the above function.
This is what worked for me:
function updateFieldCollection($node, $collection, $fields = Array(), $index = 0) {
$eid = $node->{$collection}[LANGUAGE_NONE][$index]['value'];
$fc_item = entity_load('field_collection_item', array($eid));
foreach ($fields as $field => $data) {
$fc_item[$eid]->{$field}[LANGUAGE_NONE][0]['value'] = $data;
}
$fc_item[$eid]->save(TRUE);
}
I hope this helps someone as it took me quite some time to get this working.
I have a case where we create registration for sports events.
The registration contains some fields specific to each sport. Some of which will be named similarly although they will be different for each sport. Example: "favorite position on the field":
For Basketball it would be a choice field between:
Point guard
Shooting guard
etc...
For baseball, it would be the same choice field but with some different choices available:
Pitcher
Infield
Outfield
...
When first creating the form (for display), the sport is passed as part of the data in the registration:
$registration = new Registration;
$registration->setEvent($event);
and $event->getSport(); would return the sport for that event.
So far so good, and adding a listener to the generation of my form, I can set only the fields specific to that sport:
public static function getSubscribedEvents()
{
return [FormEvents::POST_SET_DATA => 'preSetData'];
}
/**
* #param event DataEvent
*/
public function preSetData(DataEvent $event)
{
$form = $event->getForm();
if (null === $event->getData()) {
return;
}
// (The get event here means the real life sports gathering)
$sport = $event->getData()->getEvent()->getSport();
/**
* Then I customize the fields depending on the current sport
*/
}
The problem comes when the user submits this form back. In this case, $event->getData()->getEvent() is null.
The "event" (real life one) is a document_id field in the registration form (using MongoDB here).
If I listen to the ::BIND event instead of ::PRE_SET_DATA, then I can access everything, but it's too late to customize the form as it is already bound. ::PRE_BIND does the same as ::PRE_SET_DATA.
How can I correctly retrieve my Event and Sport Documents here in order to customize my form and validate it appropriately?
Why would you need an event to do such task? You can define the fields in the buildForm() action of the form class. To access the event object simply use $options['data']->getEvent()
So ... Finally found how to do this properly.It requires subscribing to two different events.
First time the form is built, some data is passed to it, therefore, the PRE_SET_DATA event contains that data and everything works fine as explained in the question.
On the moment the form is submitted, it is first created with NO data, therefore the data accessed in PRE_SET_DATA will be null. In this case we skip over the form customization:
public function preSetData(DataEvent $event)
{
$myEvent = $event->getData()->getEvent();
if (null === $myEvent) {
return;
}
$this->customizeForm();
}
This ensures that we don't run into issues when submitting the form and no data is passed, however getData() will return an empty object and not NULL.
Now, when the form is submitted, we will bind it to the data received. That's when we want to interfere. So we'll also subscribe to the PRE_BIND event:
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_BIND => 'preBind',
FormEvents::PRE_SET_DATA => 'preSetData',
];
}
In pre-bind, the data we receive is only an array of values and not an object graph.
But if we injected the object manager in our listener, then we can find our objects and work with them:
public function preBind(DataEvent $event)
{
$data = $event->getData();
$id = $data['event'];
$myEvent = $this->om
->getRepository('Acme\DemoBundle\Document\Event')
->find(new \MongoId($id));
if($myEvent === null){
$msg = 'The event %s could not be found';
throw new \Exception(sprintf($msg, $id));
}
$this->customizeForm();
}
I'm working on a codeIgniter project.I use fullCalendar to insert events. The concept that I want to implement is similar to google Calendar. When a user clicks on a day in the calendar, he inserts an event starting on the date of the clicked day.
I did an ajax call from my view and post the date to the controller.Once in the controller, all what I want to do is to set the start date with this value and to insert the event in the calendar. Here is my code (controller)
function add_event()
{
$click_date = $this->input->post('date');
$date= explode(" ", "$click_date ");
$month= (int)$date[1];
$day= (int)$date[2];
$year= (int)$date[3];
$date_start = $this->unix_timestamp($year.'-'.$month.'-'.$day);
$this->load->library('gcal');
$date_end = $this->unix_timestamp('2012-08-15');
$params = array(
'calendarId' => 'my Google calendar id',
'allday' => true,
'start' => $date_start,
'end' => $date_end,
'summary' => 'event',
'description' => "My first event"
);
$response = $this->gcal->eventInsert($params);
if($response)
{
echo "true";
}
}
Here is my unix_timestamp function :
function unix_timestamp($date)
{
$date = str_replace(array(' ', ':'), '-', $date);
$c = explode('-', $date);
$c = array_pad($c, 6, 0);
array_walk($c, 'intval');
return mktime($c[3], $c[4], $c[5], $c[1], $c[2], $c[0]);
}
On the calendar, the event is inserted but not on the right starting date. It starts on 2012-01-01 which is the default date I guess. I don't understand why my starting date isn't set correctly.
What I am doing wrong? Could someone please help me?
I found the answer. I should convert the fullCalendar date format into string before posting it to the controller. One line was missing :
date_format = $.fullCalendar.formatDate(date,"yyyy-MM-dd HH:mm:ss");`
Hope that it would help people who have the same problem
`