Skip to content

Commit 0413220

Browse files
committed
feature #178 [Cropperjs] Rely on Stimulus values instead of custom attributes (tgalopin, weaverryan)
This PR was merged into the 2.x branch. Discussion ---------- [Cropperjs] Rely on Stimulus values instead of custom attributes | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | Tickets | - | License | MIT Follows #177 Commits ------- 5ad7e32 Putting all Cropper.js options under cropper_options e3327eb WIP 2f0df8b [Cropperjs] Rely on Stimulus values instead of custom attributes
2 parents 4fb8deb + 5ad7e32 commit 0413220

File tree

6 files changed

+68
-343
lines changed

6 files changed

+68
-343
lines changed

src/Cropperjs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- Support for `stimulus` version 2 was removed and support for `@hotwired/stimulus`
66
version 3 was added. See the [@symfony/stimulus-bridge CHANGELOG](https://github.com/symfony/stimulus-bridge/blob/main/CHANGELOG.md#300)
77
for more details.
8+
- The individual Cropper.js options in `CropperType` were moved under
9+
a single `cropper_options` option.
810
- Support added for Symfony 6
911

1012
## 1.3

src/Cropperjs/Form/CropperType.php

Lines changed: 3 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -34,38 +34,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
3434
'error_bubbling' => true,
3535
'attr' => [
3636
'data-controller' => trim(($options['attr']['data-controller'] ?? '').' symfony--ux-cropperjs--cropper'),
37-
'data-public-url' => $options['public_url'],
38-
'data-view-mode' => $options['view_mode'],
39-
'data-drag-mode' => $options['drag_mode'],
40-
'data-aspect-ratio' => $options['aspect_ratio'] ?: false,
41-
'data-initial-aspect-ratio' => $options['initial_aspect_ratio'] ?: false,
42-
'data-responsive' => $options['responsive'],
43-
'data-restore' => $options['restore'],
44-
'data-check-cross-origin' => $options['check_cross_origin'],
45-
'data-check-orientation' => $options['check_orientation'],
46-
'data-modal' => $options['modal'],
47-
'data-guides' => $options['guides'],
48-
'data-center' => $options['center'],
49-
'data-highlight' => $options['highlight'],
50-
'data-background' => $options['background'],
51-
'data-auto-crop' => $options['auto_crop'],
52-
'data-auto-crop-area' => $options['auto_crop_area'],
53-
'data-movable' => $options['movable'],
54-
'data-rotatable' => $options['rotatable'],
55-
'data-scalable' => $options['scalable'],
56-
'data-zoomable' => $options['zoomable'],
57-
'data-zoom-on-touch' => $options['zoom_on_touch'],
58-
'data-zoom-on-wheel' => $options['zoom_on_wheel'],
59-
'data-wheel-zoom-ratio' => $options['wheel_zoom_ratio'],
60-
'data-crop-box-movable' => $options['crop_box_movable'],
61-
'data-crop-box-resizable' => $options['crop_box_resizable'],
62-
'data-toggle-drag-mode-on-dblclick' => $options['toggle_drag_mode_on_dblclick'],
63-
'data-min-container-width' => $options['min_container_width'],
64-
'data-min-container-height' => $options['min_container_height'],
65-
'data-min-canvas-width' => $options['min_canvas_width'],
66-
'data-min-canvas-height' => $options['min_canvas_height'],
67-
'data-min-crop-box-width' => $options['min_crop_box_width'],
68-
'data-min-crop-box-height' => $options['min_crop_box_height'],
37+
'data-symfony--ux-cropperjs--cropper-public-url-value' => $options['public_url'],
38+
'data-symfony--ux-cropperjs--cropper-options-value' => json_encode($options['cropper_options']),
6939
],
7040
])
7141
;
@@ -85,107 +55,11 @@ public function configureOptions(OptionsResolver $resolver)
8555
{
8656
$resolver->setRequired('public_url');
8757
$resolver->setAllowedTypes('public_url', 'string');
88-
89-
$resolver->setDefined([
90-
'view_mode',
91-
'drag_mode',
92-
'aspect_ratio',
93-
'initial_aspect_ratio',
94-
'responsive',
95-
'restore',
96-
'check_cross_origin',
97-
'check_orientation',
98-
'modal',
99-
'guides',
100-
'center',
101-
'highlight',
102-
'background',
103-
'auto_crop',
104-
'auto_crop_area',
105-
'movable',
106-
'rotatable',
107-
'scalable',
108-
'zoomable',
109-
'zoom_on_touch',
110-
'zoom_on_wheel',
111-
'wheel_zoom_ratio',
112-
'crop_box_movable',
113-
'crop_box_resizable',
114-
'toggle_drag_mode_on_dblclick',
115-
'min_container_width',
116-
'min_container_height',
117-
'min_canvas_width',
118-
'min_canvas_height',
119-
'min_crop_box_width',
120-
'min_crop_box_height',
121-
]);
122-
123-
$resolver->setAllowedTypes('view_mode', ['int']);
124-
$resolver->setAllowedTypes('drag_mode', ['string']);
125-
$resolver->setAllowedTypes('aspect_ratio', ['double', 'null']);
126-
$resolver->setAllowedTypes('initial_aspect_ratio', ['double', 'null']);
127-
$resolver->setAllowedTypes('responsive', ['bool']);
128-
$resolver->setAllowedTypes('restore', ['bool']);
129-
$resolver->setAllowedTypes('check_cross_origin', ['bool']);
130-
$resolver->setAllowedTypes('check_orientation', ['bool']);
131-
$resolver->setAllowedTypes('modal', ['bool']);
132-
$resolver->setAllowedTypes('guides', ['bool']);
133-
$resolver->setAllowedTypes('center', ['bool']);
134-
$resolver->setAllowedTypes('highlight', ['bool']);
135-
$resolver->setAllowedTypes('background', ['bool']);
136-
$resolver->setAllowedTypes('auto_crop', ['bool']);
137-
$resolver->setAllowedTypes('auto_crop_area', ['float']);
138-
$resolver->setAllowedTypes('movable', ['bool']);
139-
$resolver->setAllowedTypes('rotatable', ['bool']);
140-
$resolver->setAllowedTypes('scalable', ['bool']);
141-
$resolver->setAllowedTypes('zoomable', ['bool']);
142-
$resolver->setAllowedTypes('zoom_on_touch', ['bool']);
143-
$resolver->setAllowedTypes('zoom_on_wheel', ['bool']);
144-
$resolver->setAllowedTypes('wheel_zoom_ratio', ['float']);
145-
$resolver->setAllowedTypes('crop_box_movable', ['bool']);
146-
$resolver->setAllowedTypes('crop_box_resizable', ['bool']);
147-
$resolver->setAllowedTypes('toggle_drag_mode_on_dblclick', ['bool']);
148-
$resolver->setAllowedTypes('min_container_width', ['int']);
149-
$resolver->setAllowedTypes('min_container_height', ['int']);
150-
$resolver->setAllowedTypes('min_canvas_width', ['int']);
151-
$resolver->setAllowedTypes('min_canvas_height', ['int']);
152-
$resolver->setAllowedTypes('min_crop_box_width', ['int']);
153-
$resolver->setAllowedTypes('min_crop_box_height', ['int']);
58+
$resolver->setDefault('cropper_options', []);
15459

15560
$resolver->setDefaults([
15661
'label' => false,
15762
'data_class' => Crop::class,
158-
'view_mode' => 0,
159-
'drag_mode' => 'crop',
160-
'initial_aspect_ratio' => null,
161-
'aspect_ratio' => null,
162-
'responsive' => true,
163-
'restore' => true,
164-
'check_cross_origin' => true,
165-
'check_orientation' => true,
166-
'modal' => true,
167-
'guides' => true,
168-
'center' => true,
169-
'highlight' => true,
170-
'background' => true,
171-
'auto_crop' => true,
172-
'auto_crop_area' => 0.8,
173-
'movable' => false,
174-
'rotatable' => true,
175-
'scalable' => false,
176-
'zoomable' => false,
177-
'zoom_on_touch' => true,
178-
'zoom_on_wheel' => true,
179-
'wheel_zoom_ratio' => 0.1,
180-
'crop_box_movable' => true,
181-
'crop_box_resizable' => true,
182-
'toggle_drag_mode_on_dblclick' => true,
183-
'min_container_width' => 200,
184-
'min_container_height' => 100,
185-
'min_canvas_width' => 0,
186-
'min_canvas_height' => 0,
187-
'min_crop_box_width' => 0,
188-
'min_crop_box_height' => 0,
18963
]);
19064
}
19165

src/Cropperjs/README.md

Lines changed: 15 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ class HomeController extends AbstractController
4444
$form = $this->createFormBuilder(['crop' => $crop])
4545
->add('crop', CropperType::class, [
4646
'public_url' => '/public/url/to/the/image.jpg',
47-
'aspect_ratio' => 2000 / 1500,
47+
'cropper_options' => [
48+
'aspectRatio' => 2000 / 1500,
49+
],
4850
])
4951
->getForm()
5052
;
@@ -68,49 +70,7 @@ class HomeController extends AbstractController
6870
}
6971
```
7072

71-
You can pass the following options to the `CropperType` field:
72-
73-
```php
74-
$form = $this->createFormBuilder(['crop' => $crop])
75-
->add('crop', CropperType::class, [
76-
'public_url' => '/public/url/to/the/image.jpg',
77-
'view_mode' => 1,
78-
'drag_mode' => 'move',
79-
'initial_aspect_ratio' => 2000 / 1800,
80-
'aspect_ratio' => 2000 / 1800,
81-
'responsive' => true,
82-
'restore' => true,
83-
'check_cross_origin' => true,
84-
'check_orientation' => true,
85-
'modal' => true,
86-
'guides' => true,
87-
'center' => true,
88-
'highlight' => true,
89-
'background' => true,
90-
'auto_crop' => true,
91-
'auto_crop_area' => 0.1,
92-
'movable' => true,
93-
'rotatable' => true,
94-
'scalable' => true,
95-
'zoomable' => true,
96-
'zoom_on_touch' => true,
97-
'zoom_on_wheel' => true,
98-
'wheel_zoom_ratio' => 0.2,
99-
'crop_box_movable' => true,
100-
'crop_box_resizable' => true,
101-
'toggle_drag_mode_on_dblclick' => true,
102-
'min_container_width' => 200,
103-
'min_container_height' => 100,
104-
'min_canvas_width' => 0,
105-
'min_canvas_height' => 0,
106-
'min_crop_box_width' => 0,
107-
'min_crop_box_height' => 0,
108-
])
109-
->getForm()
110-
;
111-
```
112-
113-
These options are associated to [the Cropper.js options](https://github.com/fengyuanchen/cropperjs/blob/master/README.md#options).
73+
These `cropper_options` can be any [the Cropper.js option](https://github.com/fengyuanchen/cropperjs/blob/main/README.md#options).
11474

11575
Once created in PHP, a crop form is a normal form, meaning you can display it using Twig
11676
as you would normally with any form:
@@ -130,14 +90,22 @@ import { Controller } from '@hotwired/stimulus';
13090

13191
export default class extends Controller {
13292
connect() {
93+
this.element.addEventListener('cropperjs:pre-connect', this._onPreConnect);
13394
this.element.addEventListener('cropperjs:connect', this._onConnect);
13495
}
13596

13697
disconnect() {
13798
// You should always remove listeners when the controller is disconnected to avoid side effects
99+
this.element.removeEventListener('cropperjs:pre-connect', this._onConnect);
138100
this.element.removeEventListener('cropperjs:connect', this._onConnect);
139101
}
140102

103+
_onPreConnect(event) {
104+
// The cropper has not yet been created and options can be modified
105+
console.log(event.detail.options);
106+
console.log(event.detail.img);
107+
}
108+
141109
_onConnect(event) {
142110
// The cropper was just created and you can access details from the event
143111
console.log(event.detail.cropper);
@@ -158,7 +126,9 @@ Then in your form, add your controller as an HTML attribute:
158126
$form = $this->createFormBuilder(['crop' => $crop])
159127
->add('crop', CropperType::class, [
160128
'public_url' => '/public/url/to/the/image.jpg',
161-
'aspect_ratio' => 2000 / 1800,
129+
'cropper_options' => [
130+
'aspectRatio' => 2000 / 1800,
131+
],
162132
'attr' => ['data-controller' => 'mycropper'],
163133
])
164134
->getForm()

src/Cropperjs/Resources/assets/src/controller.ts

Lines changed: 18 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,68 +11,41 @@
1111

1212
import { Controller } from '@hotwired/stimulus';
1313
import Cropper from 'cropperjs';
14+
import CropEvent = Cropper.CropEvent;
15+
16+
export default class CropperController extends Controller {
17+
static values = {
18+
publicUrl: String,
19+
options: Object,
20+
};
1421

15-
export default class extends Controller {
1622
connect() {
1723
// Create image view
1824
const img = document.createElement('img');
1925
img.classList.add('cropperjs-image');
20-
img.src = this.element.getAttribute('data-public-url');
21-
22-
const parent = this.element.parentNode;
23-
parent.appendChild(img);
24-
25-
// Build the cropper
26-
const options = {
27-
viewMode: parseInt(this.element.getAttribute('data-view-mode')),
28-
dragMode: this.element.getAttribute('data-drag-mode'),
29-
responsive: this.element.hasAttribute('data-responsive'),
30-
restore: this.element.hasAttribute('data-restore'),
31-
checkCrossOrigin: this.element.hasAttribute('data-check-cross-origin'),
32-
checkOrientation: this.element.hasAttribute('data-check-orientation'),
33-
modal: this.element.hasAttribute('data-modal'),
34-
guides: this.element.hasAttribute('data-guides'),
35-
center: this.element.hasAttribute('data-center'),
36-
highlight: this.element.hasAttribute('data-highlight'),
37-
background: this.element.hasAttribute('data-background'),
38-
autoCrop: this.element.hasAttribute('data-auto-crop'),
39-
autoCropArea: parseFloat(this.element.getAttribute('data-auto-crop-area')),
40-
movable: this.element.hasAttribute('data-movable'),
41-
rotatable: this.element.hasAttribute('data-rotatable'),
42-
scalable: this.element.hasAttribute('data-scalable'),
43-
zoomable: this.element.hasAttribute('data-zoomable'),
44-
zoomOnTouch: this.element.hasAttribute('data-zoom-on-touch'),
45-
zoomOnWheel: this.element.hasAttribute('data-zoom-on-wheel'),
46-
wheelZoomRatio: parseFloat(this.element.getAttribute('data-wheel-zoom-ratio')),
47-
cropBoxMovable: this.element.hasAttribute('data-crop-box-movable'),
48-
cropBoxResizable: this.element.hasAttribute('data-crop-box-resizable'),
49-
toggleDragModeOnDblclick: this.element.hasAttribute('data-toggle-drag-mode-on-dblclick'),
50-
minContainerWidth: parseInt(this.element.getAttribute('data-min-container-width')),
51-
minContainerHeight: parseInt(this.element.getAttribute('data-min-container-height')),
52-
minCanvasWidth: parseInt(this.element.getAttribute('data-min-canvas-width')),
53-
minCanvasHeight: parseInt(this.element.getAttribute('data-min-canvas-height')),
54-
minCropBoxWidth: parseInt(this.element.getAttribute('data-min-crop-box-width')),
55-
minCropBoxHeight: parseInt(this.element.getAttribute('data-min-crop-box-height')),
56-
};
26+
img.src = (this as any).publicUrlValue;
5727

58-
if (this.element.getAttribute('data-aspect-ratio')) {
59-
options.aspectRatio = parseFloat(this.element.getAttribute('data-aspect-ratio'));
28+
const parent = (this.element as HTMLInputElement).parentNode;
29+
if (!parent) {
30+
throw new Error('Missing parent node for Cropperjs');
6031
}
6132

62-
if (this.element.getAttribute('data-initial-aspect-ratio')) {
63-
options.initialAspectRatio = parseFloat(this.element.getAttribute('data-initial-aspect-ratio'));
64-
}
33+
parent.appendChild(img);
6534

35+
const options = this.optionsValue;
36+
this._dispatchEvent('cropperjs:pre-connect', { options, img });
37+
38+
// Build the cropper
6639
const cropper = new Cropper(img, options);
6740

6841
img.addEventListener('crop', (event) => {
69-
this.element.value = JSON.stringify(event.detail);
42+
(this.element as HTMLInputElement).value = JSON.stringify((event as CropEvent).detail);
7043
});
7144

7245
this._dispatchEvent('cropperjs:connect', { cropper, options, img });
7346
}
7447

75-
_dispatchEvent(name, payload = null, canBubble = false, cancelable = false) {
48+
_dispatchEvent(name: string, payload: any = null, canBubble = false, cancelable = false) {
7649
const userEvent = document.createEvent('CustomEvent');
7750
userEvent.initCustomEvent(name, canBubble, cancelable, payload);
7851

0 commit comments

Comments
 (0)