I'm developing a MCQs based Quiz system where my goal is to assist teacher in adding a new Question and Choices for that question on the same page. According to Symfony documentation, I can embed a Collection of Forms, so I tried embedding ChoiceType to Question form:
->add('answers', CollectionType::class, array(
'entry_type' => ChoiceType::class,
'allow_add' => true,
));
;
Code of new.html.twig page (new question):
<label> Choose answers : </label>
<ul class="tags" data-prototype="{{form_widget(form.answers.vars.prototype)|e('html_attr') }}">
</ul>
But I'm getting empty select input in the browser. Please suggest what could be the perfect solution in this regard?
Note:
I noticed that if I add
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
to my QuestionType I get the form with an empty select in new.html.twig
when I delete this import I get this error if I open new.html.twig :
Variable "expanded" does not exist in form_div_layout.html.twig at line 38
but I don't have any variable in my entities called 'expanded'
Choice Entity
class Choice
{
...
/**
* #var string
*
* #ORM\Column(name="answer", type="text")
*/
private $answer;
/**
* #var string
*
* #ORM\Column(name="correct", type="string", length=255)
*/
private $correct;
/**
* #var
* #ORM\ManyToOne(targetEntity="ChallengeBundle\Entity\Question",inversedBy="answers")
*/
private $question;
...
}
Question Entity:
class Question
{
...
/**
* #var
* #ORM\OneToMany(targetEntity="ChallengeBundle\Entity\Choice",mappedBy="question",cascade={"remove"})
*/
private $answers;
...
}
Choice Type:
class ChoiceType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('answer')
->add('correct')
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'ChallengeBundle\Entity\Choice'
));
}
}
1- If your goal is only to select existing Answers in Choice form you have to use EntityTypeField instead of CollectionTypeField in your ChoiceFormType :
->add('answers', EntityType::class, array(
// query choices from this entity
'class' => 'YourBundle:Question',
// use the Question.name property as the visible option string
'choice_label' => 'name',
// used to render a select box, check boxes or radios
// 'multiple' => true,
// 'expanded' => true,
));
2- But if you want to add new answers in your Choice form you have to keep CollectionTypeField as you do.
Then in you twig template when you render your Choice form you can call your Answer collection like this :
<ul class="answers" data-prototype="{{ form_widget(form.answers.vars.prototype)|e('html_attr') }}">
{% for answer in form.answers %}
<li>{{ form_row(answer.answer) }}</li>
<li>{{ form_row(answer.correct) }}</li>
{% endfor %}
</ul>
This will display first inputs empty
Finally, as the documentation says, you have to add some javascript to read html in data-prototype attribute and dynamically add new answer forms when you click a "Add a new answer" link.
Doc example (you just have to adapt this to your case) :
var $collectionHolder;
// setup an "add a tag" link
var $addTagLink = $('Add a tag');
var $newLinkLi = $('<li></li>').append($addTagLink);
jQuery(document).ready(function() {
// Get the ul that holds the collection of tags
$collectionHolder = $('ul.tags');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
$addTagLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addTagForm($collectionHolder, $newLinkLi);
});
});
There is an excellent bundle for manage the symfony embedded forms and prototype. You don't need to code the js on hand and has a lot of options. Check in here.
Hope this help you.
You need to add the JavaScript as outlined in the documentation section Allowing "new" Tags with the "Prototype".
Excerpt from the docs:
The actual code needed to make this all work can vary quite a bit, but here's one example:
function addTagForm($collectionHolder, $newLinkLi) {
// Get the data-prototype explained earlier
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
$collectionHolder.data('index', index + 1);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = $('<li></li>').append(newForm);
$newLinkLi.before($newFormLi);
}
var $collectionHolder;
// setup an "add a tag" link
var $addTagLink = $('Add a tag');
var $newLinkLi = $('<li></li>').append($addTagLink);
jQuery(document).ready(function() {
// Get the ul that holds the collection of tags
$collectionHolder = $('ul.tags');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
$addTagLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addTagForm($collectionHolder, $newLinkLi);
});
});
Thank you all for your Help
I solve my problem by creating another entity It seem that symfony framework find a confusion in the name of my form ChoiceType and Symfony\Component\Form\Extension\Core\Type\ChoiceType
Related
I have a Kendo.MVC project. The view has a model with a field of type List<>. I want to populate the List from a Javascript function. I've tried several ways, but can't get it working. Can someone explain what I'm doing wrong?
So here is my model:
public class Dashboard
{
public List<Note> ListNotes { get; set; }
}
I use the ListNotes on the view like this:
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
}
This works if I populate Model.ListNotes in the controller when the view starts...
public ActionResult DashBoard(string xsr, string vst)
{
var notes = rep.GetNotesByCompanyID(user.ResID, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
Dashboard employee = new Dashboard
{
ResID = intUser,
Type = intType,
FirstName = user.FirstName,
LastName = user.LastName,
ListNotes = listNotes
};
return View(employee);
}
... but I need to populate ListNotes in a Javascript after a user action.
Here is my javascript to make an ajax call to populate ListNotes:
function getReminders(e)
{
var userID = '#ViewBag.CurrUser';
$.ajax({
url: "/api/WoApi/GetReminders/" + userID,
dataType: "json",
type: "GET",
success: function (notes)
{
// Need to assign notes to Model.ListNotes here
}
});
}
Here's the method it calls with the ajax call. I've confirmed ListNotes does have the values I want; it is not empty.
public List<Koorsen.Models.Note> GetReminders(int id)
{
var notes = rep.GetNotesByCompanyID(id, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
foreach (Koorsen.OpenAccess.Note note in notes)
{
Koorsen.Models.Note newNote = new Koorsen.Models.Note()
{
NoteID = note.NoteID,
CompanyID = note.CompanyID,
LocationID = note.LocationID,
NoteText = note.NoteText,
NoteType = note.NoteType,
InternalNote = note.InternalNote,
NoteDate = note.NoteDate,
Active = note.Active,
AddBy = note.AddBy,
AddDate = note.AddDate,
ModBy = note.ModBy,
ModDate = note.ModDate
};
listNotes.Add(newNote);
}
return listNotes;
}
If ListNotes was a string, I would have added a hidden field and populated it in Javascript. But that didn't work for ListNotes. I didn't get an error, but the text on the screen didn't change.
#Html.HiddenFor(x => x.ListNotes)
...
...
$("#ListNotes").val(notes);
I also tried
#Model.ListNotes = notes; // This threw an unterminated template literal error
document.getElementById('ListNotes').value = notes;
I've even tried refreshing the page after assigning the value:
window.location.reload();
and refreshing the panel bar the code is in
var panelBar = $("#IntroPanelBar").data("kendoPanelBar");
panelBar.reload();
Can someone explain how to get this to work?
I don't know if this will cloud the issue, but the reason I need to populate the model in javascript with an ajax call is because Model.ListNotes is being used in a Kendo Panel Bar control and I don't want Model.ListNotes to have a value until the user expands the panel bar.
Here's the code for the panel bar:
#{
#(Html.Kendo().PanelBar().Name("IntroPanelBar")
.Items(items =>
{
items
.Add()
.Text("View Important Notes and Messages")
.Expanded(false)
.Content(
#<text>
#RenderReminders()
</text>
);
}
)
.Events(e => e
.Expand("getReminders")
)
)
}
Here's the helper than renders the contents:
#helper RenderReminders()
{
if (Model.ListNotes.Count <= 0)
{
#Html.Raw("No Current Messages");
}
else
{
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
<br />
}
}
}
The panel bar and the helpers work fine if I populate Model.ListNotes in the controller and pass Model to the view. I just can't get it to populate in the javascript after the user expands the panel bar.
Perhaps this will do it for you. I will provide a small working example I believe you can easily extend to meet your needs. I would recommend writing the html by hand instead of using the helper methods such as #html.raw since #html.raw is just a tool to generate html in the end anyways. You can write html manually accomplish what the helper methods do anyway and I think it will be easier for you in this situation. If you write the html correctly it should bind to the model correctly (which means it won't be empty on your post request model) So if you modify that html using javascript correctly, it will bind to your model correctly as well.
Take a look at some of these examples to get a better idea of what I am talking about:
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
So to answer your question...
You could build a hidden container to hold your list values like this (make sure this container is inside the form):
<div id="ListValues" style="display:none">
</div>
Then put the results your ajax post into a javascript variable (not shown).
Then in javascript do something like this:
$('form').off('submit'); //i do this to prevent duplicate bindings depending on how this page may be rendered futuristically as a safety precaution.
$('form').on('submit', function (e) { //on submit, modify the form data to include the information you want inside of your ListNotes
var data = getAjaxResults(); //data represents your ajax results. You can acquire and format that how you'd like I will use the following as an example format for how you could save the results as JSON data: [{NoteID ="1",CompanyID ="2"}]
let listLength = data.length;
for (let i = 0; i < listLength; i++) {
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].NoteID " value="' + data.NoteID +'" />')
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].CompanyID " value="' + data.CompanyID +'" />')
//for your ajax results, do this for each field on the note object
}
})
That should do it! After you submit your form, it should automatically model bind to you ListNotes! You will be able to inpsect this in your debugger on your post controller action.
I've been working through the Symfony 3 tutorial on embedding a collection of forms, and I want to extend the idea to extra nested levels. I had a look around, and there are partial answers for Symfony 2, but nothing comprehensive (and nothing for 3).
If we take the tutorials Task has many Tag example, how would I code it so it extends to: Task has many Tag has many SubTag?
So far I think I understand the Form classes:
Task:
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description');
$builder->add('tags', CollectionType::class, array(
'entry_type' => TagType::class,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Task',
));
}
}
Tag:
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
$builder->add('sub_tags', CollectionType::class, array(
'entry_type' => SubTagType::class,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Tag',
));
}
}
SubTag:
class SubTagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\SubTag',
));
}
}
And the basic Twig class:
{{ form_start(form) }}
{# render the task's only field: description #}
{{ form_row(form.description) }}
<h3>Tags</h3>
<ul class="tags">
{# iterate over each existing tag and render its only field: name #}
{% for tag in form.tags %}
<li>{{ form_row(tag.name) }}</li>
{% for sub_tag in tag.sub_tags %}
<li>{{ form_row(sub_tag.name) }}</li>
{% endfor %}
{% endfor %}
</ul>
{{ form_end(form) }}
But it's at this point I'm unsure of how the prototype and javascript will work. Could somebody explain how I'd take this next step? Is this even the right approach?
My first thought is that if we're doing additional levels, it might be smart to generalize the JS for any number of levels, since the tutorial uses very JS that can only work on a single level.
The closest working code I can find is this stack overflow answer here. However, it doesn't appear to work as described, and Im having trouble working out exactly what's wrong.
It's not any different than a regular embedded collection of forms.
However, if you want to avoid trouble with the default __NAME__ prototype colliding with a parent form's prototype string, you should take take to choose distinct values for the TagType and SubTag types.
From the Symfony Docs entry on CollectionType:
prototype_name
type: string default: name
If you have several collections in your form, or worse, nested collections you may want to change the placeholder so that unrelated placeholders are not replaced with the same value.
This can be very helpful if you want to abstract your clone actions with the javascript, like those in this article (pasted below), which - by the way - appears to target symfony3!
You might, for instance want to include the same value you pass to prototype_name, as an attr on the collection holder's html, so that you can access it dynamically, when doing the replace on the data-prototype html.
var $collectionHolder;
// setup an "add a tag" link
var $addTagLink = $('Add a tag');
var $newLinkLi = $('<li></li>').append($addTagLink);
jQuery(document).ready(function() {
// Get the ul that holds the collection of tags
$collectionHolder = $('ul.tags');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
$addTagLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addTagForm($collectionHolder, $newLinkLi);
});
function addTagForm($collectionHolder, $newLinkLi) {
// Get the data-prototype explained earlier
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
$collectionHolder.data('index', index + 1);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = $('<li></li>').append(newForm);
$newLinkLi.before($newFormLi);
}
I am using Moodle 2.7 and have the following custom field for the courses in the database table mdl_course_info_field:
Full name: School course
Shrot name: school
Type: Menu of choices
Choices:
Highschool course
Prepschool course
The target was to show the link on every course page, where under the settings the chechbox for Highschool course is used. In the file mymoodle/local/link/functions.js there is the link:
if($('#page-course-view-topcollmytheme .orangebar p')) {
$('#page-course-view-topcollmytheme #section-0 .content > .summary').append('<button class="highschoollink">Hig school course</button>');
}
How to check, if the checkbox is choosen an then to show the link on the course page?
You can use a renderer to display a course header:
https://tracker.moodle.org/browse/MDL-36048
So you could include the school link in the course header - here is an example:
In /course/format/formatname/lib.php
Add this function to class format_formatname
/**
* Display's a header at the top of the sections.
*
* #return renderable class
*/
public function course_content_header() {
global $DB, $PAGE, $USER;
if (!isset($PAGE)) {
return null;
}
// Only display if we are on the course-view page.
if (strpos($PAGE->pagetype, 'course-view-') !== 0) {
return null;
}
$sql = "SELECT d.data
FROM {course_info_field} f
JOIN {course_info_data} d ON d.fieldid = f.id AND d.courseid = :courseid
WHERE f.shortname = :shortname";
$params = array('courseid' => $this->courseid, 'shortname' => 'school');
$schoolname = $DB->get_field_sql($sql, $params);
$schoolurl = '';
// You should store the school url in the database somewhere.
// Using switch code for this example.
switch ($schoolname) {
case 'high school' :
$schoolurl = new moodle_url('http://www.schoolsite.com');
break;
...
}
return new format_formatname_coursecontentheader($schoolname, $schoolurl);
}
Also add this class to /course/format/formatname/lib.php
class format_formatname_coursecontentheader implements renderable {
/**
* School name
*
* #var string $schoolname
*/
public $schoolname;
/**
* School url
*
* #var string $schoolurl
*/
public $schoolurl;
/**
* Class storing information to be passed and displayed in the course content header
*
* #param string $schoolname
* #param moodle_url $schoolurl
*/
public function __construct($schoolname, $schoolurl) {
$this->schoolname = $schoolname;
$this->schoolurl = $fields->schoolurl;
}
}
Then in /course/format/formatname/renderer.php
Add this function to class format_formatname_renderer
/**
* Renders course header
*
* #param renderable $courseheader
* #return string
*/
public function render_format_formatname_coursecontentheader($courseheader) {
$output = '';
$schoolname = $courseheader->schoolname;
$schoolurl = $courseheader->schoolurl;
$link = html_writer::link($schoolurl, $schoolname);
$output .= html_writer::div($link, 'format-formatname-schoollink');
return $output;
}
I suggest you add the link to the course as a URL resource.
Then make sure 'conditional activities' are enabled for the site.
Finally edit the URL activity to restrict access to users who have the correct user profile field.
See https://docs.moodle.org/27/en/Conditional_activities_settings for more details.
On my magento product page I need to add a dynamic JavaScript array base on display upselling products on the product page. The goal is to change the images of the upselling products when the user change the color of the main product.
To achieve my goal I need a custom JavaScript array on every product page that give me information about crossselling product and the associated product image.
What is the best way to do this?
I try this
add a observer event in my config.xml
<controller_action_layout_load_before>
<observers>
<crossselling_product_view>
<type>singleton</type>
<class>XXXXXXXX_Crossselling_Model_Observer</class>
<method>productview</method>
</crossselling_product_view>
</observers>
</controller_action_layout_load_before>
add observer to add specific JS Code
<?php
class XXXXXXXX_Crossselling_Model_Observer {
public function productview(Varien_Event_Observer $observer) {
$product = Mage::registry('current_product');
//only on product page
if (!($product instanceof Mage_Catalog_Model_Product)) {
return;
}
$controller = $observer->getAction();
$layout = $controller->getLayout();
$block = $layout->createBlock('core/text');
$block->setText(
'<script type="text/javascript">
function main_pulsestorm_hellojavascript()
{
alert("Foo");
}
main_pulsestorm_hellojavascript();
</script>'
);
$layout->getBlock('head')->append($block);
}
}
My error:
Fatal error: Call to a member function append() on a non-object
What is my problem and is it the right way to add dynamic js code?
I would probably approach this from a different angle. Since you are only interested in interacting with data and output for the upsell block, you could change the behavior of just that block by observing its output and appending your extra JavaScript. For the purposes of brevity this answer assumes that you understand the basics of Magento extensions.
Observe the core_block_abstract_to_html_after event:
etc/config.xml
<core_block_abstract_to_html_after>
<observers>
<addCustomUpsellFormat>
<class>XXXXXXXX_Crossselling_Model_Observer</class>
<method>addCustomUpsellFormat</method>
</addCustomUpsellFormat>
</observers>
</core_block_abstract_to_html_after>
Act upon instances of Mage_Catalog_Block_Product_List_Upsell by appending the output of a new block that will read their data:
Model/Observer.php
public function addCustomUpsellFormat(Varien_Event_Observer $observer)
{
/* #var Mage_Core_Block_Abstract $block */
$block = $observer->getBlock();
if ($block instanceof Mage_Catalog_Block_Product_List_Upsell) {
/* #var Varien_Object $transport */
$transport = $observer->getTransport();
// Receive the standard output for the block.
$output = $transport->getHtml();
/* #var Mage_Core_Model_Layout $layout */
$layout = $block->getLayout();
$json = $layout->createBlock('core/template')
->setTemplate('catalog/product/list/upsell_json.phtml')
->setItems($block->getItems())
->toHtml();
// Append new JSON data to block output.
$transport->setHtml($output . $json);
}
return $this;
}
Create a template that interprets the upsell data and outputs it in your desired way, in my example above I created a template that could do something like this (my example creates a new template, so it should go into the base theme):
app/design/frontend/base/default/template/catalog/product/list/upsell_json.phtml
<?php
$_json = array(); // Add data in here to convert to JSON for output.
$_items = $this->getItems();
/* #var Mage_Catalog_Model_Product $_product */
foreach ($_items as $_product) {
$_json[$_product->getId()] = array(
'image' => (string)Mage::helper('catalog/image')->init($_product, 'image')
);
}
?>
<script type="text/javascript">var upsellData = <?php echo json_encode($_json) ?>;</script>
Use
$controller = $observer->getEvent()->getAction();
instead of
$controller = $observer->getAction();
I am new to CakePHP. I need to make a form with radio buttons and the last one is "other" where the user can type in an answer in a text box.
Is there any way FormHelper can do this that's built in?
One way I was going to do was create a radio list and a text field. When "other" is selected Javascript will show the text field. For this I don't understand how to use any other variables besides the fields in the database. How does one create a variable from a model that can be accessed from a view and the value returned for processing?
For the model I have:
class User extends AppModel {
/**
* Display field
*
* #var string
*/
public $displayField = 'title';
var $sourceOther = ' ';
var $passwordRepeat = ' ';
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['password'])) {
$this->data[$this->alias]['password'] = sha1(
$this->data[$this->alias]['password']);
}
// $this->data[$this->alias]['created']= CakeTime::gmt();
// $this->data[$this->alias]['updated']= CakeTime::gmt();
$this->data[$this->alias]['username']= $this->data[$this->alias]['email'];
return true;
In the view I have
echo $this->Form->input('mobilePhone',array(
'label'=>'Mobile or fixed phone with no spaces'));
echo $this->Form->input('alternatePhone');
echo $this->Form->input('leadSource', array(
'options'=>array('Google'=>'Google','OnlineAd'=>'Online Ad',
'Printed Media'=>'Printed Media','LetterDrop'=>'Letter Drop',
'Other'=>'Other (specify text)'),
'empty'=>'(choose one)'));
echo $this->Form->input($this->sourceOther);
...but it doesn't like sourceOther, the variable in the model. How do I get data from the view (the text box) into the user model so beforeSave can do something with it?
Thanks.
Thanks.
Sorted it out after reading "Cake PHP Application Development". It works for Cake 2.x and 1.2 (as used in the book).
In your view put:
echo $this->Form->input('sourceOther');
Then in the model you can access your variable with:
$this->data['User']['sourceOther'];
For example, use it to save "other" text field in beforesave:
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['password'])) {
$this->data[$this->alias]['password'] = sha1(
$this->data[$this->alias]['password']);
}
$this->data[$this->alias]['username'] = $this->data[$this->alias]['email'];
$this->data[$this->alias]['activation'] = md5(uniqid(rand(), true));
if ($this->data[$this->alias]['leadSource'] == 'Other') {
$this->data[$this->alias]['leadSource'] =
$this->data['User']['sourceOther'];
}
return true;
}