Вы уже сталкивались в своей практике с замечательным плагином для symfony 1.2-1.4 – sfDoctrineJCroppablePlugin? Если нет, то в двух словах поясню что это такое. Итак, sfDoctrineJCroppablePlugin (он устанавливается в паре с еще одним очень полезным плагином – sfImageTransformPlugin) позволяет быстро и просто добавить в вашу форму аплоад картинки и прицепить к нему jquery-плагин jcroppable. Последний позволяет выбирать область на изображении, которая будет использоваться для превью (это нужно тогда, когда под превью отведено по дизайну строго определенное место, например 100х100 пискелей).
Итак, когда мы работаем с изображениями в рамках одной формы – все отлично. Но вот, нам потребовалось встроить (embed) наши картинки в другую форму (именно картинкИ, думаю с одной картинкой проблем не будет). Вот тут то и начинается веселье.
Конкретно, веселье заключается в том, что sfDoctrineJCroppable не понимает что его встроили и считает, что каждое изображение отображается самостоятельно. Кроме того, неправильно определяются идентификаторы hidden полей с координатами углов (x1, y1, x2, y2). Как следствие – javascript ошибка и неработающий jcroppable (в остальном плагин вроде фунциклирует, но ставился он преимущественно из-за быстрой интеграции с jcroppable!).
Давайте покопаемся во внутренностях плагина, чтобы понять что там пошло не так.
Виджет формируется в классе sfWidgetFormInputFileInputImageJCroppable. Наше внимание должен привлечь метод getJCropJS(), который собственно и осуществляет рендеринг javascript-кода:
// plugins/sfDoctrineJCroppablePlugin/lib/widget/sfWidgetFormInputFileInputImageJCroppable.class.php:128
private function getJCropJS() {
$idStub = $this->getIdStub();
$ratio = $this->getOption('image_ratio') ? 'aspectRatio: ' . $this->getOption('image_ratio') . ',' : '';
$js = "
<script language="Javascript">
jQuery(document).ready(function(){
jQuery('#{$idStub}_img').Jcrop({
$ratio
setSelect: [document.getElementById('{$idStub}_x1').value,
document.getElementById('{$idStub}_y1').value,
document.getElementById('{$idStub}_x2').value,
document.getElementById('{$idStub}_y2').value
],
onChange: _jCropUpdateCoords" . ucfirst($idStub) . ",
onSelect: _jCropUpdateCoords" . ucfirst($idStub) . "
});
//...
Тут фигурирует вызов соседней функции getIdStub():
//plugins/sfDoctrineJCroppablePlugin/lib/widget/sfWidgetFormInputFileInputImageJCroppable.class.php:161
private function getIdStub() {
$form = $this->getOption('form');
$separator = '';
$imageName = $this->getOption('image_field');
$tableName = str_replace("[%s]", '', $form->getWidgetSchema()->getNameFormat());
if ($form->getOption('embedded'))
{
$parentTableName = $form->getOption('parent_model')->getTable()->getTableName();
$separator = 'embedded_' . $tableName . '_' . $tableName .
'_' . $this->getOption('invoker')->getId();
$idStub = $parentTableName . '_' . $separator . '_' . $imageName;
}
else
{
$idStub = $tableName . '_' . $imageName;
}
return $idStub;
}
Уже интереснее! getIdStub() на основе некоей опции “embedded” обещает выдавать два различных результата. Казалось бы бери – да действуй. Ну чтоже, давайте подсунем оную опцию в наш виджет. Итак, класс создания коллекции фото:
// lib/form/doctrine/PropertyPhotoCollectionForm.class.php
class PropertyPhotoCollectionForm extends sfForm
{
public function configure()
{
if ( !$property = $this->getOption( "property" ) )
{
throw new InvalidArgumentException( "You must provide a property object." );
}
for ( $i = 0; $i < $this->getOption( "size", 4 ); $i++ )
{
$property_photo = ( $property->PropertyPhoto[$i] ) ? $property->PropertyPhoto[$i] : new PropertyPhoto();
$property_photo->PropertyCatalog = $property;
$form = new PropertyPhotoForm(
$property_photo,
array(
"embedded" => true,
"parent_model" => new PropertyCatalog(),
"embed_form_name" => "photos",
"embed_id" => $i,
)
);
$this->embedForm( $i, $form);
}
}
Отлично. Однако, обратите внимание, что помимо оции embedded я добавил еще parent_model, embedded_form_name, embed_id. Они появились в результате отладки, посему они будут фигурировать в коде, представленном ниже. Теперь надо модифицировать виджет, чтобы он с этими опциями не ронял приложение (в основном из-за parent_model):
// plugins/sfDoctrineJCroppablePlugin/lib/widget/sfWidgetFormInputFileInputImageJCroppable.class.php
private function getIdStub()
{
sfContext::getInstance()->getLogger()->debug( "[sfWidgetFormInputFileInputImageJCroppable::getIdStub]" );
$form = $this->getOption('form');
$separator = '';
$imageName = $this->getOption( 'image_field' );
$tableName = str_replace("[%s]", '', $form->getWidgetSchema()->getNameFormat());
if ( $form->getOption( 'embedded' ) )
{
$parentTableName = $form->getOption('parent_model')->getTable()->getTableName();
$idStub = sprintf(
"%s_%s_%s_%s",
$form->getOption('parent_model')->getTable()->getTableName(),
$form->getOption('embed_form_name'),
$form->getOption('embed_id'),
$imageName
);
}
else
{
$idStub = $tableName . '_' . $imageName;
}
return $idStub;
}
Как видите, основные изменения коснулись ветвления if ( $form->getOption( 'embedded' ) ) – как раз тут должны создаваться IDs для встроенных форм. Однако, не все так безоблачно как хотелось бы. На самом деле перестал корректно работать FileEditable виджет, поэтому я тупо закомментировал генерацию delete опции в методе render в ветке для embedded формы. Благо она не сильно была нужна:
// plugins/sfDoctrineJCroppablePlugin/lib/widget/sfWidgetFormInputFileInputImageJCroppable.class.php
public function render($name, $value = null, $attributes = array(), $errors = array())
{
//...
// гдето в районе строки 90
if ( $form->getOption('embedded', false) && $form->getOption('parent_model', false) )
{
//$delete = $form->getOption('parent_model')->getDeleteLinkFor($object); // Эту строку и закомментировали
$deleteLabel = '';
}
else
{
$delete = $this->renderTag('input', array_merge(array('type' => 'checkbox', 'name' => $deleteName), $attributes));
$deleteLabel = $this->renderContentTag('label', $this->getOption('delete_label'), array_merge(array('for' => $this->generateId($deleteName))));
}
// ...
}
Итого, имеем кривоватую подпорку для того чтобы получить нормально работающий виджет. Я запросил разработчика где можно запостить решение или описать проблему, но он не ответил. Жаль. По возможности старайтесь избегать встраивания форм с JCroppable, если не хотите писать полноценный патч
Ну и собственно результат выглядит примерно так:
Успехов!
P.S. Если найдете более элегантное решение – маякните плиз!
