Skip to content

Commit 2f0df8b

Browse files
committed
[Cropperjs] Rely on Stimulus values instead of custom attributes
1 parent 8f9e8e7 commit 2f0df8b

File tree

4 files changed

+218
-168
lines changed

4 files changed

+218
-168
lines changed

src/Cropperjs/Form/CropperType.php

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,38 +34,38 @@ 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-view-mode-value' => $options['view_mode'],
39+
'data-symfony--ux-cropperjs--cropper-drag-mode-value' => $options['drag_mode'],
40+
'data-symfony--ux-cropperjs--cropper-aspect-ratio-value' => $options['aspect_ratio'] ?: false,
41+
'data-symfony--ux-cropperjs--cropper-initial-aspect-ratio-value' => $options['initial_aspect_ratio'] ?: false,
42+
'data-symfony--ux-cropperjs--cropper-responsive-value' => $options['responsive'],
43+
'data-symfony--ux-cropperjs--cropper-restore-value' => $options['restore'],
44+
'data-symfony--ux-cropperjs--cropper-check-cross-origin-value' => $options['check_cross_origin'],
45+
'data-symfony--ux-cropperjs--cropper-check-orientation-value' => $options['check_orientation'],
46+
'data-symfony--ux-cropperjs--cropper-modal-value' => $options['modal'],
47+
'data-symfony--ux-cropperjs--cropper-guides-value' => $options['guides'],
48+
'data-symfony--ux-cropperjs--cropper-center-value' => $options['center'],
49+
'data-symfony--ux-cropperjs--cropper-highlight-value' => $options['highlight'],
50+
'data-symfony--ux-cropperjs--cropper-background-value' => $options['background'],
51+
'data-symfony--ux-cropperjs--cropper-auto-crop-value' => $options['auto_crop'],
52+
'data-symfony--ux-cropperjs--cropper-auto-crop-area-value' => $options['auto_crop_area'],
53+
'data-symfony--ux-cropperjs--cropper-movable-value' => $options['movable'],
54+
'data-symfony--ux-cropperjs--cropper-rotatable-value' => $options['rotatable'],
55+
'data-symfony--ux-cropperjs--cropper-scalable-value' => $options['scalable'],
56+
'data-symfony--ux-cropperjs--cropper-zoomable-value' => $options['zoomable'],
57+
'data-symfony--ux-cropperjs--cropper-zoom-on-touch-value' => $options['zoom_on_touch'],
58+
'data-symfony--ux-cropperjs--cropper-zoom-on-wheel-value' => $options['zoom_on_wheel'],
59+
'data-symfony--ux-cropperjs--cropper-wheel-zoom-ratio-value' => $options['wheel_zoom_ratio'],
60+
'data-symfony--ux-cropperjs--cropper-crop-box-movable-value' => $options['crop_box_movable'],
61+
'data-symfony--ux-cropperjs--cropper-crop-box-resizable-value' => $options['crop_box_resizable'],
62+
'data-symfony--ux-cropperjs--cropper-toggle-drag-mode-on-dblclick-value' => $options['toggle_drag_mode_on_dblclick'],
63+
'data-symfony--ux-cropperjs--cropper-min-container-width-value' => $options['min_container_width'],
64+
'data-symfony--ux-cropperjs--cropper-min-container-height-value' => $options['min_container_height'],
65+
'data-symfony--ux-cropperjs--cropper-min-canvas-width-value' => $options['min_canvas_width'],
66+
'data-symfony--ux-cropperjs--cropper-min-canvas-height-value' => $options['min_canvas_height'],
67+
'data-symfony--ux-cropperjs--cropper-min-crop-box-width-value' => $options['min_crop_box_width'],
68+
'data-symfony--ux-cropperjs--cropper-min-crop-box-height-value' => $options['min_crop_box_height'],
6969
],
7070
])
7171
;

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

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,68 +11,75 @@
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+
viewMode: Number,
20+
dragMode: String,
21+
responsive: Boolean,
22+
restore: Boolean,
23+
checkCrossOrigin: Boolean,
24+
checkOrientation: Boolean,
25+
modal: Boolean,
26+
guides: Boolean,
27+
center: Boolean,
28+
highlight: Boolean,
29+
background: Boolean,
30+
autoCrop: Boolean,
31+
autoCropArea: Number,
32+
movable: Boolean,
33+
rotatable: Boolean,
34+
scalable: Boolean,
35+
zoomable: Boolean,
36+
zoomOnTouch: Boolean,
37+
zoomOnWheel: Boolean,
38+
wheelZoomRatio: Number,
39+
cropBoxMovable: Boolean,
40+
cropBoxResizable: Boolean,
41+
toggleDragModeOnDblclick: Boolean,
42+
minContainerWidth: Number,
43+
minContainerHeight: Number,
44+
minCanvasWidth: Number,
45+
minCanvasHeight: Number,
46+
minCropBoxWidth: Number,
47+
minCropBoxHeight: Number,
48+
aspectRatio: Number,
49+
initialAspectRatio: Number,
50+
};
1451

15-
export default class extends Controller {
1652
connect() {
1753
// Create image view
1854
const img = document.createElement('img');
1955
img.classList.add('cropperjs-image');
20-
img.src = this.element.getAttribute('data-public-url');
56+
img.src = (this as any).publicUrlValue;
57+
58+
const parent = (this.element as HTMLInputElement).parentNode;
59+
if (!parent) {
60+
throw new Error('Missing parent node for Cropperjs');
61+
}
2162

22-
const parent = this.element.parentNode;
2363
parent.appendChild(img);
2464

2565
// 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-
};
57-
58-
if (this.element.getAttribute('data-aspect-ratio')) {
59-
options.aspectRatio = parseFloat(this.element.getAttribute('data-aspect-ratio'));
60-
}
61-
62-
if (this.element.getAttribute('data-initial-aspect-ratio')) {
63-
options.initialAspectRatio = parseFloat(this.element.getAttribute('data-initial-aspect-ratio'));
66+
let options: any = {};
67+
for (let name in CropperController.values) {
68+
if ((this as any)['has' + name.charAt(0).toUpperCase() + name.slice(1) + 'Value']) {
69+
options[name] = (this as any)[name + 'Value'];
70+
}
6471
}
6572

6673
const cropper = new Cropper(img, options);
6774

6875
img.addEventListener('crop', (event) => {
69-
this.element.value = JSON.stringify(event.detail);
76+
(this.element as HTMLInputElement).value = JSON.stringify((event as CropEvent).detail);
7077
});
7178

7279
this._dispatchEvent('cropperjs:connect', { cropper, options, img });
7380
}
7481

75-
_dispatchEvent(name, payload = null, canBubble = false, cancelable = false) {
82+
_dispatchEvent(name: string, payload: any = null, canBubble = false, cancelable = false) {
7683
const userEvent = document.createEvent('CustomEvent');
7784
userEvent.initCustomEvent(name, canBubble, cancelable, payload);
7885

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

Lines changed: 77 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,51 @@ import CropperjsController from '../src/controller';
1717
// Controller used to check the actual controller was properly booted
1818
class CheckController extends Controller {
1919
connect() {
20-
this.element.addEventListener('cropperjs:connect', () => {
20+
this.element.addEventListener('cropperjs:connect', (event: any) => {
2121
this.element.classList.add('connected');
22+
23+
expect(event.detail.cropper.options).toStrictEqual({
24+
viewMode: 1,
25+
dragMode: 'move',
26+
initialAspectRatio: 2,
27+
aspectRatio: 1,
28+
data: null,
29+
preview: '',
30+
responsive: true,
31+
restore: true,
32+
checkCrossOrigin: true,
33+
checkOrientation: true,
34+
modal: true,
35+
guides: true,
36+
center: true,
37+
highlight: true,
38+
background: true,
39+
autoCrop: true,
40+
autoCropArea: 0.1,
41+
movable: true,
42+
rotatable: true,
43+
scalable: true,
44+
zoomable: true,
45+
zoomOnTouch: true,
46+
zoomOnWheel: true,
47+
wheelZoomRatio: 0.2,
48+
cropBoxMovable: true,
49+
cropBoxResizable: true,
50+
toggleDragModeOnDblclick: true,
51+
minCanvasWidth: 3,
52+
minCanvasHeight: 4,
53+
minCropBoxWidth: 5,
54+
minCropBoxHeight: 6,
55+
minContainerWidth: 1,
56+
minContainerHeight: 2,
57+
ready: null,
58+
cropstart: null,
59+
cropmove: null,
60+
cropend: null,
61+
crop: null,
62+
zoom: null,
63+
publicUrl: 'https://symfony.com/logos/symfony_black_02.png'
64+
});
2265
});
2366
}
2467
}
@@ -30,46 +73,46 @@ const startStimulus = () => {
3073
};
3174

3275
describe('CropperjsController', () => {
33-
let container;
76+
let container: any;
3477

3578
beforeEach(() => {
3679
container = mountDOM(`
3780
<div id="form_photo" class="cropperjs">
3881
<input type="hidden" id="form_photo_options" name="form[photo][options]"
3982
data-testid="input"
4083
data-controller="check cropperjs"
41-
data-public-url="https://symfony.com/logos/symfony_black_02.png"
42-
data-view-mode="1"
43-
data-drag-mode="move"
44-
data-aspect-ratio="1"
45-
data-initial-aspect-ratio="2"
46-
data-responsive="data-responsive"
47-
data-restore="data-restore"
48-
data-check-cross-origin="data-check-cross-origin"
49-
data-check-orientation="data-check-orientation"
50-
data-modal="data-modal"
51-
data-guides="data-guides"
52-
data-center="data-center"
53-
data-highlight="data-highlight"
54-
data-background="data-background"
55-
data-auto-crop="data-auto-crop"
56-
data-auto-crop-area="0.1"
57-
data-movable="data-movable"
58-
data-rotatable="data-rotatable"
59-
data-scalable="data-scalable"
60-
data-zoomable="data-zoomable"
61-
data-zoom-on-touch="data-zoom-on-touch"
62-
data-zoom-on-wheel="data-zoom-on-wheel"
63-
data-wheel-zoom-ratio="0.2"
64-
data-crop-box-movable="data-crop-box-movable"
65-
data-crop-box-resizable="data-crop-box-resizable"
66-
data-toggle-drag-mode-on-dblclick="data-toggle-drag-mode-on-dblclick"
67-
data-min-container-width="1"
68-
data-min-container-height="2"
69-
data-min-canvas-width="3"
70-
data-min-canvas-height="4"
71-
data-min-crop-box-width="5"
72-
data-min-crop-box-height="6" />
84+
data-cropperjs-public-url-value="https://symfony.com/logos/symfony_black_02.png"
85+
data-cropperjs-view-mode-value="1"
86+
data-cropperjs-drag-mode-value="move"
87+
data-cropperjs-aspect-ratio-value="1"
88+
data-cropperjs-initial-aspect-ratio-value="2"
89+
data-cropperjs-responsive-value="data-responsive"
90+
data-cropperjs-restore-value="data-restore"
91+
data-cropperjs-check-cross-origin-value="data-check-cross-origin"
92+
data-cropperjs-check-orientation-value="data-check-orientation"
93+
data-cropperjs-modal-value="data-modal"
94+
data-cropperjs-guides-value="data-guides"
95+
data-cropperjs-center-value="data-center"
96+
data-cropperjs-highlight-value="data-highlight"
97+
data-cropperjs-background-value="data-background"
98+
data-cropperjs-auto-crop-value="data-auto-crop"
99+
data-cropperjs-auto-crop-area-value="0.1"
100+
data-cropperjs-movable-value="data-movable"
101+
data-cropperjs-rotatable-value="data-rotatable"
102+
data-cropperjs-scalable-value="data-scalable"
103+
data-cropperjs-zoomable-value="data-zoomable"
104+
data-cropperjs-zoom-on-touch-value="data-zoom-on-touch"
105+
data-cropperjs-zoom-on-wheel-value="data-zoom-on-wheel"
106+
data-cropperjs-wheel-zoom-ratio-value="0.2"
107+
data-cropperjs-crop-box-movable-value="data-crop-box-movable"
108+
data-cropperjs-crop-box-resizable-value="data-crop-box-resizable"
109+
data-cropperjs-toggle-drag-mode-on-dblclick-value="data-toggle-drag-mode-on-dblclick"
110+
data-cropperjs-min-container-width-value="1"
111+
data-cropperjs-min-container-height-value="2"
112+
data-cropperjs-min-canvas-width-value="3"
113+
data-cropperjs-min-canvas-height-value="4"
114+
data-cropperjs-min-crop-box-width-value="5"
115+
data-cropperjs-min-crop-box-height-value="6" />
73116
</div>
74117
`);
75118
});

0 commit comments

Comments
 (0)