I'd like to create an admin panel for one of my projects, so I started with this tutorial:
How to Embed a Collection of Forms
Continued with creating double embedded form, so i have an object, what have objects, and these objects also have objects :D
The Doctrine mapping is good, and works, the problem:
When I click on the "Add new Property" it creates the property, but when I click on the "Add Detail" (which adds a detail to the property) it creates the Detail's fields, but one extra field too:
<label class="required">1label__</label>
The formtype codes:
For the main class
$builder
->add('forma')
;
$builder->add('properties', 'collection',array(
'type' => new PropertyType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
));
For the property class:
$builder
->add('nev')
//->add('szokokut')
;
$builder->add('details', 'collection',array(
'type' => new DetailType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
));
For the Detail class:
$builder
->add('description')
//->add('property')
;
These codes are from the buildFrom() functions.
Any idea why is there the extra field?
The first prototype:
<div id="szokokut_storebundle_szokokuttype_properties___name__"><div><label for="szokokut_storebundle_szokokuttype_properties___name___nev" class="required">Nev</label><input type="text" id="szokokut_storebundle_szokokuttype_properties___name___nev" name="szokokut_storebundle_szokokuttype[properties][__name__][nev]" required="required" maxlength="100" /></div><div><label class="required">Details</label><div id="szokokut_storebundle_szokokuttype_properties___name___details" data-prototype="<div><label class="required">__name__label__</label><div id="szokokut_storebundle_szokokuttype_properties___name___details___name__"><div><label for="szokokut_storebundle_szokokuttype_properties___name___details___name___description" class="required">Description</label><input type="text" id="szokokut_storebundle_szokokuttype_properties___name___details___name___description" name="szokokut_storebundle_szokokuttype[properties][__name__][details][__name__][description]" required="required" maxlength="100" /></div></div></div>"></div></div></div>
The second:
<div><label class="required">1label__</label><div id="szokokut_storebundle_szokokuttype_properties_1_details_1"><div><label for="szokokut_storebundle_szokokuttype_properties_1_details_1_description" class="required">Description</label><input type="text" id="szokokut_storebundle_szokokuttype_properties_1_details_1_description" name="szokokut_storebundle_szokokuttype[properties][1][details][1][description]" required="required" maxlength="100" /></div></div></div>
The problem will be here:
Its from the tutorial (with a little change) and it replaces the __name__ field in both of the prototypes :/
var $addPropertyLink = $('Tulajdonság hozzáadása');
var $newLinkLi = $('<p></p>').append($addPropertyLink);
jQuery(document).ready(function() {
propertyHolder.append($newLinkLi);
$addPropertyLink.on('click', function(e) {
e.preventDefault();
addPropertyForm(propertyHolder, $newLinkLi);
});
});
function addPropertyForm(collectionHolder, $newLinkLi) {
// Get the data-prototype we explained earlier
var prototype = collectionHolder.attr('data-prototype');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on the current collection's length.
var newForm = prototype.replace(/__name__/g, collectionHolder.children().length);
// Display the form in the page in an li
var $newFormLi = $('<p></p>').append(newForm);
$newLinkLi.before($newFormLi);
addFormDeleteLink($newFormLi);
var $addDetailLink = $('Részlet hozzáadása');
var $LinkLi = $('<p></p>').append($addDetailLink);
$newFormLi.find('div[id$=_details]').append($LinkLi);
$addDetailLink.on('click',function(e){
e.preventDefault();
addDetailForm($newFormLi.find('div[id$=_details]'),$LinkLi);
});
}
function addFormDeleteLink($FormLi) {
var $removeFormA = $('törlés');
$FormLi.append($removeFormA);
$removeFormA.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// remove the li for the tag form
$FormLi.remove();
});
}
function addDetailForm(collectionHolder,$newLinkLi){
// Get the data-prototype we explained earlier
var prototype = collectionHolder.attr('data-prototype');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on the current collection's length.
var newForm = prototype.replace(/__name__/g, collectionHolder.children().length);
// Display the form in the page in an li
var $newFormLi = $('<p></p>').append(newForm);
$newLinkLi.before($newFormLi);
addFormDeleteLink($newFormLi);
}
You should try to make sure your javascript only acts on the right set of elements, by using a good enough selector.
Show your js for a more detailed answer.
UPDATE
Look at the code
There seems to be an undocumented prototype_name option that you can change from name to whatever you want for one of your prototypes.
Related
I have a form where I'm putting the chipsData into a hidden input field called #hiddenTags I'm doing this because I don't want you use an AJAX call because I have a pre-exist form. Below is how I'm putting the chip data into the hidden input.
$("form").on("submit", function() {
var tags = M.Chips.getInstance($('.chips')).chipsData;
var sendTags = JSON.stringify(tags);
$('#hiddenTags').val( sendTags );
});
I'm sending it to the database like this: (PHP)
$this->tags = json_encode( $data['tags'] );
However, saving data like this is raising all sorts of issues. I'm using Twig to display the data.
Below is how I'm trying to display it, however with this I get an error unexpected token & in json
$('.chips').chips();
$('.chips-initial').chips({
data: {{ json }}
});
I have also tried putting the json into a hidden input and then putting it into jquery:
<input id="raw_json" type="hidden" hidden value="{{ user.tags }}">
var json = $('#raw_json').val();
$('.chips').chips();
$('.chips-initial').chips({
data: json
});
However, with this I get and error of Cannot read property 'length' of undefined
Apologies if I'm doing something stupid and/or completely wrong, any help is much appreciated.
So the answer to your problems lies in setting the values when initializing the chips object, along with parsing the JSON data.
From there just keep the text box updated on add or delete of a chip.
var chipsData = JSON.parse($("#Tags").val());
$('.chips-placeholder').chips({
placeholder: 'Enter a tag',
secondaryPlaceholder: '+Tag',
data: chipsData,
onChipAdd: (event, chip) => {
var chipsData = M.Chips.getInstance($('.chips')).chipsData;
var chipsDataJson = JSON.stringify(chipsData);
$("#Tags").val(chipsDataJson);
},
onChipSelect: () => {
},
onChipDelete: () => {
var chipsData = M.Chips.getInstance($('.chips')).chipsData;
var chipsDataJson = JSON.stringify(chipsData);
$("#Tags").val(chipsDataJson);
}
});
i've got a problem regarding the symfony collection field type.
Working with one collection type (adding, removing items) is clear due to the cookbook entry of symfony for working with collection field type. For me, the problem appears when i want to add / remove items to a collection type within a collection type. Example:
I have an addresses collection that gets added to my Contact Type class
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('addresses', 'collection', array(
'type' => new AddressType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false,
))
;
}
This addresses collection gets as type "AddressType", which looks the following:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// name = country
->add('name', 'text')
->add('cities', 'collection', array(
'type' => 'text',
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
))
;
}
My goal is now to be able to add / remove new "addresses" to the form (which is working due to the cookbook entry), BUT for each address, i also want to add / remove cities.
Question:
When following the example of the symfony page, i should add in the jquery:
/ addresses
$collectionHolder = $('#ecommerce_user_contact_addresses');
the collection holder. For the addresses, i know what to write here because the data-prototype generated in the widget in the view has that id.
But what should I write here for the inner collection?
When i write:
// cities
$collectionHolderCities = $('#ecommerce_user_contact_addresses___name___cities');
, which also comes from the data-prototype of the parent div that describes in this case the "cities" element structure", i don't know now how to add / remove new elements of that.
When calling:
var prototype = $collectionHolderCities.data('prototype');
in my 'addCityForm(..)' method, the console logs me the variable 'prototype' as undefined. When i logged the prototype in the other method 'addAddressForm', it correctly showed me the prototype structure.
What should i define as collection holder of the "inner collection" for the cities, and what do i need to change in addition to get this working ?
Address (with country name)
City
City
City
Address (with country name)
City
City
...
Regards.
EDIT:
I have implemented the js function to add a City now this way, and it is working for the first address that is displayed on document.ready. I don't know if this is the best way to handle it, any other approaches are welcome :)
I get the collection Holder in document.ready with that:
// cities
$collectionHolderCities = $('#ecommerce_user_contact_addresses_0_cities');
Then, in my addCityForm function, i do some replacement to get the newForm right:
function addCityForm($collectionHolderCities, $newLinkCity) {
// Get the data-prototype explained earlier
var prototype = $collectionHolderCities.data('prototype');
// get the new index
var index = $collectionHolderCities.data('index');
// replace 'cities_0' default value in prototype with 'cities__counter'
prototype = prototype.replace("cities_0", "cities__counter__");
prototype = prototype.replace("cities_0", "cities__counter__");
// replace '0label__' with '__counter__label__'
prototype = prototype.replace("0label__", "__counter__label__");
// replace [cities][0] with [cities][__counter__]
prototype = prototype.replace("[cities][0]", "[cities][__counter__]");
// Replace '__counter__' in the prototype's HTML to
// instead be a number based on how many items we have
var trans = $('#city-trans').val();
var newForm = prototype.replace(/__counter__/g,index);
// Replace cities0 (cities + index) with cities_0 (cities + "_" + index)
newForm = newForm.replace('cities'+index, 'cities_' + index);
newForm = newForm.replace('cities'+index, 'cities_' + index);
// var repStr = '0label__';
//newForm = newForm.replace(repStr, trans + ':');
// increase the index with one for the next item
$collectionHolderCities.data('index', index + 1);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormS = $('<span></span>').append(newForm);
$newLinkCity.before($newFormS);
// add a delete link to the new form
addCityFormDeleteLink($newFormS);
}
The problem now is :
When i add a new 'address', i cannot add cities to this new added address. There is no link or a default city input field. How can i handle that ?
Adding new cities only works for the first address that is displayed on document.ready:
jQuery(document).ready(function() {
// addresses
$collectionHolder = $('#ecommerce_user_contact_addresses');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLink);
// 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);
$addAddressLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addAddressForm($collectionHolder, $newLink);
});
// add blank collections
addAddressForm($collectionHolder, $newLink);
// add city
// cities
$collectionHolderCities = $('#ecommerce_user_contact_addresses_0_cities');
// add the "add a tag" anchor and li to the tags ul
$collectionHolderCities.append($newLinkCity);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolderCities.data('index', $collectionHolderCities.find(':input').length);
$addCityLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
addCityForm($collectionHolderCities, $newLinkCity);
});
addCityForm($collectionHolderCities, $newLinkCity);
});
The 'addAddressForm()' function is like in the symfony cookbook entry.
Don't know how to add cities now for added addresses.. :/
When a room_id is selected from the drop down, I use the JS helper to populate the security_deposit and room_rate inputs. The issue is that if I select a room_id, the room_rate_update jQuery change function stops working. However, when page loads and I don't select a room_id, I can type in the room_rate box and it's triggered, but not after I select a room_rate.
The one thing that I think may be causing this is when I select a room_id, the entire div containing the room_rate is updated. It was previously generated by the CakePHP form helper, however, I made sure that the div is replaced by the exact same name, class, id, etc... It's identical to the one that appears when the page loads
Here is the form in my view
echo $this->Form-create('Arrival');
echo $this->Form->input('room_id',array('options' => $room_numbers, 'label'=>'Room Number'));
?>
<div id="room_rate_update">
<?php
echo $this->Form->input('room_rate', array('label' => 'Room Rate (numbers only)'));
?>
</div>
<div id = "security_deposit_update">
<?php
echo $this->Form->input('security_deposit',array('label' => 'Security Deposit (numbers only)'));
?>
</div>
<?php
echo $this->Form->input('balance_upon_arrival',array('label' => 'Balance Due Upon Arrival'));
Here is the JS helper and jQuery function for the updates
//if someone changes the room_rate, this should update the deposit amount
$(document).ready(function(){
$("#ArrivalRoomRate" ).change(function() {
alert('test');
});
});
//populates the room rate when the room is selected
$this->Js->get('#ArrivalRoomId')->event('change',
$this->Js->request(array(
'controller'=>'Arrival',
'action'=>'getRoomRate'
), array(
'update'=>'#room_rate_update',
'async' => true,
'method' => 'post',
'dataExpression'=>true,
'data'=> $this->Js->serializeForm(array(
'isForm' => false,
'inline' => true
))
))
);
Here is the view for the JS helper POST request
//room_rate_update.ctp
<div class="input text required">
<label for="ArrivalRoomRate">Room Rate </label><input name="data[Arrival][room_rate]" maxlength="10" type="text" id="ArrivalRoomRate" required="required" value="<?php echo $room_rate; ?>">
</div>
So, as I stated. Prior to selecting a room_id, the "test" is triggered. After I select a value
You should have another function, for instance :
function init ()
{
$("#ArrivalRoomRate" ).change(function() {
alert('test');
});
}
After all content refreshed, then call it once. Otherwise, when all content refreshed, this event handler will be invalid for new content, coz it's partial rendering.
You can try with a delegate(aka live) event handler like following:
$(document).ready(function(){
$("body" ).on("change", "#ArrivalRoomRate", function() {
alert('test');
});
});
If you have problem with .on() with jQuery version, then you can use .live() with old jQuery like following:
$("#ArrivalRoomRate").live("click", function() {
alert("test");
});
Okay so i have a combobox with two options.
Now if one of the options is selected a new input field should appear.
echo $this->Form->input('group_id', array( 'id' => 'groupId'));
echo $this->Form->input('clientid',array( 'type' => 'hidden', 'id' => 'id_client',));
And for that i would use Jquery to check the values
<script>
$(document).ready(function () {
$("#groupId").change(function () {
if($(this).val() == 2){
// do set visible
}
})
});
</script>
My question is: how can i change the type of the field to visible? i have tried: $('#groupId').show(); also $('#clientid').get(0).type = 'text';
But didnt seem to work and i am starting to wonder if this is the best way of doing such a thing?
$(this).attr('type', 'text');
You're doing it wrong.
type="hidden" is not appropriate to hide UI elements (form fields or anything else).
You should instead use the CSS attribute display. Change your clientid input type to "text". When groupId is not 2, set display: none on your clientid input. When it's 2, set display: block.
With jQuery, you can use $('#clientid').show() and .hide().
For instance:
<select id="groupId"><!-- options... --></select>
<input type="text" id="clientId" />
<script>
$(document).ready(function () {
function showHideClient() {
var show_client = $(this).val() == 2;
$("#clientId").toggle(show_client); // hide or show
}
// we bind the "change" event to the hide/show checking
$("#groupId").change(showHideClient);
// and we call it at page load to hide the input right away if needed
showHideClient();
});
</script>
How do I dynamically add form elements using HTML_Quickform. I know how this is done using plain HTML form elements and using Javascript (see code below). I wanted to know if this is easy to do using HTML_Quickform. I am still looking at this site: http://devzone.zend.com/article/2699-Generating-and-Validating-Web-Forms-With-PEAR-HTML_QuickForm. HTML_Quickform lets you easily add validation rules. I began creating a similar HTML_Quickform code - but I could not figure out how to dynamically add form elements using HTML_Quickform:
// Our Default Form Options
$opts = array('size' => 20, 'maxlength' => 255, 'id' => 'name1', 'class' => 'clonedInput' );
$form->addElement('text', 'first_name', 'First Name', $opts);
$form->addElement('button','btnAdd', 'Add another name', array('id' => 'btnAdd'));
$form->addElement('button','btnDel', 'Remove Name', array('id' => 'btnDel') );
// Submit button
$form->addElement('image', 'register', 'images/continue.gif');
// Define filters and validation rules
$form->applyFilter('first_name', 'trim');
$form->addRule('first_name', 'Please enter your First Name', 'required', null, 'client');
$formsource = $form->toHtml();
echo $formsource;
?>
The code for the plain HTML with Javascript is as follows:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#btnAdd').click(function() {
var num = $('.clonedInput').length; // how many "duplicatable" input fields we currently have
var newNum = new Number(num + 1); // the numeric ID of the new input field being added
// create the new element via clone(), and manipulate it's ID using newNum value
var newElem = $('#input' + num).clone().attr('id', 'input' + newNum);
// manipulate the name/id values of the input inside the new element
newElem.children(':first').attr('id', 'name' + newNum).attr('name', 'name' + newNum);
// insert the new element after the last "duplicatable" input field
$('#input' + num).after(newElem);
// enable the "remove" button
$('#btnDel').attr('disabled','');
// business rule: you can only add 5 names
if (newNum == 5)
$('#btnAdd').attr('disabled','disabled');
});
$('#btnDel').click(function() {
var num = $('.clonedInput').length; // how many "duplicatable" input fields we currently have
$('#input' + num).remove(); // remove the last element
// enable the "add" button
$('#btnAdd').attr('disabled','');
// if only one element remains, disable the "remove" button
if (num-1 == 1)
$('#btnDel').attr('disabled','disabled');
});
$('#btnDel').attr('disabled','disabled');
});
</script>
<form id="myForm">
<div id="input1" style="margin-bottom:4px;" class="clonedInput">
Name: <input type="text" name="name1" id="name1" />
</div>
<div>
<input type="button" id="btnAdd" value="add another name" />
<input type="button" id="btnDel" value="remove name" />
</div>
</form>
Unless I'm misunderstanding your question, you can't. You're using HTML_Quickform to generate HTML on the server. Once you output $formsource, your php code (and thus HTML_Quickform) cease to execute, so you wouldn't be able to programmatically add anything with HTML_Quickform. So, once you output the HTML to the browser, you can only manipulate your HTML-based form with JavaScript.
One workaround would be to create some HTML form fragments with HTML_Quickform and inject those into your HTML form with AJAX.