9

I have a private message bundle/entity that allows my users to send messages between them.

Its fields are as follows:

/**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
protected $id;

/**
 * @var string
 * @Assert\NotBlank(message="private_message.title.blank")
 * @ORM\Column(name="title", type="string", length=50)
 */
protected $title;

/**
 * @Assert\NotBlank(message="private_message.receiver.blank")
 * @AcmeAssert\IsHimself(message="private_message.receiver.himself", groups={"new"})
 * @ORM\ManyToOne(targetEntity="MedAppBundle\Entity\User")
 * @ORM\JoinColumn(referencedColumnName="id")
 */
protected $receiver;
/**
 * @ORM\ManyToOne(targetEntity="MedAppBundle\Entity\User")
 * @ORM\JoinColumn(referencedColumnName="id")
 */
protected $sender;

/**
 * @var string
 * @Assert\NotBlank(message="private_message.content.blank")
 * @ORM\Column(name="content", type="string")
 */
protected $content;

/**
 * @var \DateTime
 *
 * @ORM\Column(name="sentAt", type="datetime")
 */
protected $sentAt;


/**
 * @var boolean
 *
 * @ORM\Column(name="isSpam", type="boolean")
 */
protected $isSpam = false;


/**
 * @var \DateTime
 *
 * @ORM\Column(name="seenAt", type="datetime",nullable=true)
 */
protected $seenAt = null;

/**
 * @ORM\ManyToOne(targetEntity="PrivateMessageBundle\Entity\Message",inversedBy="replies")
 * @ORM\JoinColumn(referencedColumnName="id",nullable=true)
 */
protected $replyof;

/**
 * @ORM\OneToMany(targetEntity="PrivateMessageBundle\Entity\Message", mappedBy="replyof")
 **/
private $replies;

public function __construct() {
    $this->replies = new ArrayCollection();
}

Notice the replyof field, it references to another message, and the replies one references to an array of messages. If replyof is null, then the message is not a reply of any message.

I have a twig template with a macro that displays a user's message and all the replies of that message. What I'd like to do is have a reply textfield under each of these, exactly like Gmail has, that allows me to add a reply to each message.

But when I add it to the template, only one is rendered because it has one single Id. How can I add a reply form after each reply? What their FormType should look like?

Here is also my twig template:

  {% macro displayReply(reply,replyform) %}
        {% import _self as macros %}


        {# <li> id: {{ reply.id }} </li>
        <li> sent by: {{ reply.sender }} </li>
        <li> title: {{ reply.title }} </li>
        <li> content: {{ reply.content }} </li>
        <li> date: {{ reply.sentAt|date('d-m-Y H:i:s') }} </li>
        <a  href="{{ path('private_message_new',{'msg':reply.id}) }}">  reply  </a>
        <hr> #}
        <div class="panel panel-default">
            <div class="panel-body">
                <div class="message-info">
                    <input type="hidden" name="messageid" id="messageId" value="{{ reply.id }}">

                    <div class="message-title clearfix">
                        <h4 class="pull-left">{{ reply.title }}</h4>
                    </div>
                    <hr class="lite-line">
                    <div class="message-sender clearfix">
                        <div class="pull-left sender">
                            {{ reply.sender }}
                        </div>
                        <div class="pull-right">
                            to <b>{{ (reply.receiver==app.user)?'me':reply.receiver }}</b> on <span
                                    class="message-timestamp">{{ reply.sentAt|date('F d, Y H:i:s') }}</span>
                            <a class="btn btn-start-order" role="button"
                               href="{{ path('private_message_new',{'msg':reply.id}) }}">Reply</a>
                        </div>

                    </div>
                    <hr class="lite-line">
                    <div class="message-box clearfix">
                        <span>{{ reply.content | replace({"<script>" : "", "</script>" : ""}) | raw }}</span>
                    </div>

                    {{ form_start(replyform) }}
                    <input type="submit">
                    {{ form_end(replyform) }}

                </div>
            </div>
        </div>
        {% for reply in reply.replies %}

            {% if loop.first %}<div>{% endif %}
            {{ macros.displayReply(reply) }}
            {% if loop.last %}</div>{% endif %}

        {% endfor %}
    {% endmacro %}

    {% import _self as macros %}
    {# use the macro #}

    <div class="message-back">
        <a class="btn btn-start-order-dark btn-block" role="button"
           href="{{ path('private_message',{'page':'inbox'}) }}">
            <span class="fa fa-undo"></span> Go back
        </a>
    </div>

    <div class="messages">
        <div class="panel panel-default">
            <div class="panel-body">
                <div class="message-info">
                    <input type="hidden" name="messageid" id="messageId" value="{{ message.id }}">

                    <div class="message-title clearfix">
                        <h4 class="pull-left">{{ message.title }}</h4>
                    </div>
                    <hr class="lite-line">
                    <div class="message-sender clearfix">
                        <div class="pull-left sender">
                            {{ message.sender }}
                        </div>
                        <div class="pull-right">
                            to <b>{{ (message.receiver==app.user)?'me':message.receiver }}</b> on <span
                                    class="message-timestamp">{{ message.sentAt|date('F d, Y H:i:s') }}</span> <a
                                    class="btn btn-start-order" role="button"
                                    href="{{ path('private_message_new',{'msg':message.id}) }}">Reply</a>
                        </div>
                    </div>
                    <hr class="lite-line">
                    <div class="message-box clearfix">
                        <span>{{ message.content | replace({"<script>" : "", "</script>" : ""}) | raw }}</span>
                    </div>

                    {{ form_start(replyform) }}
                    <input type="submit">
                    {{ form_end(replyform) }}
                </div>
            </div>
        </div>
    </div>
    {% for reply in message.replies %}

        {% if loop.first %}<div class="replies">{% endif %}
        {{ macros.displayReply(reply ,replyform) }}
        {% if loop.last %}</div>{% endif %}
    {% endfor %}

Notice that I first display the message, then apply the macro to it that displays all its replies as a tree. It will display the replies's replies, too, in a recursive manner, all the way until the leaf nodes. I add a 'replyform' after each, but I'm not sure how the FormType should be.

My reply form type is like this, but I'm pretty sure it is wrong.

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('title')
        ->add('content', 'textarea')
      ;

}

As for the other fields of the reply, I take care of them in the controller. I think I should be doing this after receiving the message from the form though. Something like this, and get the title, content and replyof from the formdata.

   $messages = $this->getDoctrine()->getRepository('PrivateMessageBundle:Message');
    $isforme = $messages->findOneBy(array('receiver' => $this->getUser(), 'id' => $msg));
    $message = new Message();
    $message->setSender($this->getUser());
    $message->setSentAt(new \Datetime('now'));
    $message->setReplyof($isforme);
    $message->setReceiver($isforme->getSender());
    $form = $this->createForm(new MessageReplyType($em), $message);

EDIT

Ok, so I made something that works, by adding a hidden field and hardcoding multiple forms instead of using FormTypes, but I still think that this can be done in a better, more reusable way.

                    <form name="privatemessagebundle_message" method="post" action="" id="{{ reply.id }}">
                        <div><label for="privatemessagebundle_message_title" class="required">Title</label><input
                                    type="text" id="privatemessagebundle_message_title"
                                    name="privatemessagebundle_message[title]" required="required" maxlength="50"></div>
                        <div><label for="privatemessagebundle_message_content" class="required">Content</label><textarea
                                    id="privatemessagebundle_message_content"
                                    name="privatemessagebundle_message[content]" required="required"></textarea></div>
                        <input type="hidden" id="privatemessagebundle_message_replyof"
                               name="privatemessagebundle_message[replyof]" value="{{ reply.id }}">
                        <input type="submit">
                        <input type="hidden" id="privatemessagebundle_message__token"
                               name="privatemessagebundle_message[_token]"
                               value="{{ csrf_token('privatemessagebundle_message') }}">
                    </form>

Anyone got any better ideas?

2
  • Seems like the textarea can better be initialized using Javascript, so you only need one single form. That form could have the replyTo id as a hidden field. That way you only need one textarea right? Commented Sep 24, 2015 at 12:53
  • That would require a bit more JS as replyTo has to change value every time you click on a reply button, too. It's also a bit more vulnerable to CSRF. I think my solution, while complicated, is a bit more elegant, but yours is also pretty good. Commented Sep 24, 2015 at 13:31

1 Answer 1

6

I did it! I used the answer from this question.

Since I'm using foreach loops and they might be a bit low on performance, anyone with a better idea is welcomed. There is still the bounty to receive.

I'm creating a form for each of my forms through createNamedBuilder. They will have different names, thus different id's and Symfony will render them all. Then, I can render them where I want and handle their request just fine through their unique id taken from the database.

 $genforms = $this->genReplyForms($isforme); // run the function for my message
            $forms_views = $genforms['views']; // pass to the view
            $forms= $genforms['forms']; // handle request... 

This is the function that generated the form. It recursively generates them for each reply of my message.

    public function genReplyForms(Message $message)
{

    $id = $message->getId();

    $msgreply[$id] = new Message();

    $forms[$id] = $this->container
        ->get('form.factory')
        ->createNamedBuilder('form_'.$id, new MessageReplyType(), $msgreply[$id])
        ->getForm();

    $forms_views[$id] = $forms[$id]->createView();


    $result = array(array(), array());

    $result['forms'][$id] = $forms[$id];
    $result['views'][$id] = $forms_views[$id];


    if (sizeof($message->getReplies())) {

        foreach ($message->getReplies() as $reply) {

            $child = $this->genReplyForms($reply);

            $result['forms'] = $result['forms'] + $child['forms'];
            $result['views'] = $result['views'] + $child['views'];

        }

    }

    return $result;
}

MessageReplyType needs just user input. Everything else is handled in the controller

        $builder
        ->add('title')
        ->add('content', 'textarea')
    ;

Also, my simplified twig. I've simplified the macro call, too. Was doing an unnecessary foreach loop for the first message instead of simply passing it to the macro.

   {% macro displayReply(reply, forms) %}
    {% import _self as macros %}


    {# <li> id: {{ reply.id }} </li>
    <li> sent by: {{ reply.sender }} </li>
    <li> title: {{ reply.title }} </li>
    <li> content: {{ reply.content }} </li>
    <li> date: {{ reply.sentAt|date('d-m-Y H:i:s') }} </li>
    <a  href="{{ path('private_message_new',{'msg':reply.id}) }}">  reply  </a>
    <hr> #}
    <div class="panel panel-default">
        <div class="panel-body">
            <div class="message-info">
                <input type="hidden" name="messageid" id="messageId" value="{{ reply.id }}">

                <div class="message-title clearfix">
                    <h4 class="pull-left">{{ reply.title }}</h4>
                </div>
                <hr class="lite-line">
                <div class="message-sender clearfix">
                    <div class="pull-left sender">
                        {{ reply.sender }}
                    </div>
                    <div class="pull-right">
                        to <b>{{ (reply.receiver==app.user)?'me':reply.receiver }}</b> on <span
                                class="message-timestamp">{{ reply.sentAt|date('F d, Y H:i:s') }}</span>
                        {# <a class="btn btn-start-order" role="button"
                           href="{{ path('private_message_new',{'msg':reply.id}) }}">Reply</a> #}
                    </div>

                </div>
                <hr class="lite-line">
                <div class="message-box clearfix">
                    <span>{{ reply.content | replace({"<script>" : "", "</script>" : ""}) | raw }}</span>
                </div>


                {{ form_start(forms[reply.id]) }}
                <input type="submit">
                {{ form_end(forms[reply.id]) }}



                {# NU STERGE! #}
                {#
                <form name="privatemessagebundle_message" method="post" action="" id="{{ reply.id }}">
                    <div><label for="privatemessagebundle_message_title" class="required">Title</label><input
                                type="text" id="privatemessagebundle_message_title"
                                name="privatemessagebundle_message[title]" required="required" maxlength="50"></div>
                    <div><label for="privatemessagebundle_message_content" class="required">Content</label><textarea
                                id="privatemessagebundle_message_content"
                                name="privatemessagebundle_message[content]" required="required"></textarea></div>
                    <input type="hidden" id="privatemessagebundle_message_replyof"
                           name="privatemessagebundle_message[replyof]" value="{{ reply.id }}">
                    <input type="submit">
                    <input type="hidden" id="privatemessagebundle_message__token"
                           name="privatemessagebundle_message[_token]"
                           value="{{ csrf_token('privatemessagebundle_message') }}"></form>#}
                {# NU STERGE! #}

            </div>
        </div>
    </div>
    {% for reply in reply.replies %}

        {% if loop.first %}<div>{% endif %}
        {{ macros.displayReply(reply,forms) }}
        {% if loop.last %}</div>{% endif %}

    {% endfor %}
{% endmacro %}

{% import _self as macros %}
{# use the macro #}

<div class="message-back">
    <a class="btn btn-start-order-dark btn-block" role="button"
       href="{{ path('private_message',{'page':'inbox'}) }}">
        <span class="fa fa-undo"></span> Go back
    </a>
</div>

<div class="replies">
    {{ macros.displayReply(message, forms) }}
</div>

Again, I'm still looking for better or more efficient alternatives, so please do post them.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.