voltron-crop 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,363 @@
1
+ /*
2
+ jQuery Simple Slider
3
+
4
+ Copyright (c) 2012 James Smith (http://loopj.com)
5
+
6
+ Licensed under the MIT license (http://mit-license.org/)
7
+ */
8
+
9
+ var __slice = [].slice,
10
+ __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
11
+
12
+ (function($, window) {
13
+ var SimpleSlider;
14
+ SimpleSlider = (function() {
15
+
16
+ function SimpleSlider(input, options) {
17
+ var ratio,
18
+ _this = this;
19
+ this.input = input;
20
+ this.defaultOptions = {
21
+ animate: true,
22
+ snapMid: false,
23
+ classPrefix: null,
24
+ classSuffix: null,
25
+ theme: null,
26
+ highlight: false
27
+ };
28
+ this.settings = $.extend({}, this.defaultOptions, options);
29
+ if (this.settings.theme) {
30
+ this.settings.classSuffix = "-" + this.settings.theme;
31
+ }
32
+ this.input.hide();
33
+ this.slider = $("<div>").addClass("slider" + (this.settings.classSuffix || "")).css({
34
+ position: "relative",
35
+ userSelect: "none",
36
+ boxSizing: "border-box"
37
+ }).insertBefore(this.input);
38
+ if (this.input.attr("id")) {
39
+ this.slider.attr("id", this.input.attr("id") + "-slider");
40
+ }
41
+ this.track = this.createDivElement("track").css({
42
+ width: "100%"
43
+ });
44
+ if (this.settings.highlight) {
45
+ this.highlightTrack = this.createDivElement("highlight-track").css({
46
+ width: "0"
47
+ });
48
+ }
49
+ this.dragger = this.createDivElement("dragger");
50
+ this.slider.css({
51
+ minHeight: this.dragger.outerHeight(),
52
+ marginLeft: this.dragger.outerWidth() / 2,
53
+ marginRight: this.dragger.outerWidth() / 2
54
+ });
55
+ this.track.css({
56
+ marginTop: this.track.outerHeight() / -2
57
+ });
58
+ if (this.settings.highlight) {
59
+ this.highlightTrack.css({
60
+ marginTop: this.track.outerHeight() / -2
61
+ });
62
+ }
63
+ this.dragger.css({
64
+ marginTop: this.dragger.outerHeight() / -2,
65
+ marginLeft: this.dragger.outerWidth() / -2
66
+ });
67
+ this.track.mousedown(function(e) {
68
+ return _this.trackEvent(e);
69
+ });
70
+ if (this.settings.highlight) {
71
+ this.highlightTrack.mousedown(function(e) {
72
+ return _this.trackEvent(e);
73
+ });
74
+ }
75
+ this.dragger.mousedown(function(e) {
76
+ if (e.which !== 1) {
77
+ return;
78
+ }
79
+ _this.dragging = true;
80
+ _this.dragger.addClass("dragging");
81
+ _this.domDrag(e.pageX, e.pageY);
82
+ return false;
83
+ });
84
+ $("body").mousemove(function(e) {
85
+ if (_this.dragging) {
86
+ _this.domDrag(e.pageX, e.pageY);
87
+ return $("body").css({
88
+ cursor: "pointer"
89
+ });
90
+ }
91
+ }).mouseup(function(e) {
92
+ if (_this.dragging) {
93
+ _this.dragging = false;
94
+ _this.dragger.removeClass("dragging");
95
+ return $("body").css({
96
+ cursor: "auto"
97
+ });
98
+ }
99
+ });
100
+ this.pagePos = 0;
101
+ if (this.input.val() === "") {
102
+ this.value = this.getRange().min;
103
+ this.input.val(this.value);
104
+ } else {
105
+ this.value = this.nearestValidValue(this.input.val());
106
+ }
107
+ this.setSliderPositionFromValue(this.value);
108
+ ratio = this.valueToRatio(this.value);
109
+ this.input.trigger("slider:ready", {
110
+ value: this.value,
111
+ ratio: ratio,
112
+ position: ratio * this.slider.outerWidth(),
113
+ el: this.slider
114
+ });
115
+ }
116
+
117
+ SimpleSlider.prototype.createDivElement = function(classname) {
118
+ var item;
119
+ item = $("<div>").addClass(classname).css({
120
+ position: "absolute",
121
+ top: "50%",
122
+ userSelect: "none",
123
+ cursor: "pointer"
124
+ }).appendTo(this.slider);
125
+ return item;
126
+ };
127
+
128
+ SimpleSlider.prototype.setRatio = function(ratio) {
129
+ var value;
130
+ ratio = Math.min(1, ratio);
131
+ ratio = Math.max(0, ratio);
132
+ value = this.ratioToValue(ratio);
133
+ this.setSliderPositionFromValue(value);
134
+ return this.valueChanged(value, ratio, "setRatio");
135
+ };
136
+
137
+ SimpleSlider.prototype.setValue = function(value) {
138
+ var ratio;
139
+ value = this.nearestValidValue(value);
140
+ ratio = this.valueToRatio(value);
141
+ this.setSliderPositionFromValue(value);
142
+ return this.valueChanged(value, ratio, "setValue");
143
+ };
144
+
145
+ SimpleSlider.prototype.trackEvent = function(e) {
146
+ if (e.which !== 1) {
147
+ return;
148
+ }
149
+ this.domDrag(e.pageX, e.pageY, true);
150
+ this.dragging = true;
151
+ return false;
152
+ };
153
+
154
+ SimpleSlider.prototype.domDrag = function(pageX, pageY, animate) {
155
+ var pagePos, ratio, value;
156
+ if (animate == null) {
157
+ animate = false;
158
+ }
159
+ pagePos = pageX - this.slider.offset().left;
160
+ pagePos = Math.min(this.slider.outerWidth(), pagePos);
161
+ pagePos = Math.max(0, pagePos);
162
+ if (this.pagePos !== pagePos) {
163
+ this.pagePos = pagePos;
164
+ ratio = pagePos / this.slider.outerWidth();
165
+ value = this.ratioToValue(ratio);
166
+ this.valueChanged(value, ratio, "domDrag");
167
+ if (this.settings.snap) {
168
+ return this.setSliderPositionFromValue(value, animate);
169
+ } else {
170
+ return this.setSliderPosition(pagePos, animate);
171
+ }
172
+ }
173
+ };
174
+
175
+ SimpleSlider.prototype.setSliderPosition = function(position, animate) {
176
+ if (animate == null) {
177
+ animate = false;
178
+ }
179
+ if (animate && this.settings.animate) {
180
+ this.dragger.animate({
181
+ left: position
182
+ }, 200);
183
+ if (this.settings.highlight) {
184
+ return this.highlightTrack.animate({
185
+ width: position
186
+ }, 200);
187
+ }
188
+ } else {
189
+ this.dragger.css({
190
+ left: position
191
+ });
192
+ if (this.settings.highlight) {
193
+ return this.highlightTrack.css({
194
+ width: position
195
+ });
196
+ }
197
+ }
198
+ };
199
+
200
+ SimpleSlider.prototype.setSliderPositionFromValue = function(value, animate) {
201
+ var ratio;
202
+ if (animate == null) {
203
+ animate = false;
204
+ }
205
+ ratio = this.valueToRatio(value);
206
+ return this.setSliderPosition(ratio * this.slider.outerWidth(), animate);
207
+ };
208
+
209
+ SimpleSlider.prototype.getRange = function() {
210
+ if (this.settings.allowedValues) {
211
+ return {
212
+ min: Math.min.apply(Math, this.settings.allowedValues),
213
+ max: Math.max.apply(Math, this.settings.allowedValues)
214
+ };
215
+ } else if (this.settings.range) {
216
+ return {
217
+ min: parseFloat(this.settings.range[0]),
218
+ max: parseFloat(this.settings.range[1])
219
+ };
220
+ } else {
221
+ return {
222
+ min: 0,
223
+ max: 1
224
+ };
225
+ }
226
+ };
227
+
228
+ SimpleSlider.prototype.nearestValidValue = function(rawValue) {
229
+ var closest, maxSteps, range, steps;
230
+ range = this.getRange();
231
+ rawValue = Math.min(range.max, rawValue);
232
+ rawValue = Math.max(range.min, rawValue);
233
+ if (this.settings.allowedValues) {
234
+ closest = null;
235
+ $.each(this.settings.allowedValues, function() {
236
+ if (closest === null || Math.abs(this - rawValue) < Math.abs(closest - rawValue)) {
237
+ return closest = this;
238
+ }
239
+ });
240
+ return closest;
241
+ } else if (this.settings.step) {
242
+ maxSteps = (range.max - range.min) / this.settings.step;
243
+ steps = Math.floor((rawValue - range.min) / this.settings.step);
244
+ if ((rawValue - range.min) % this.settings.step > this.settings.step / 2 && steps < maxSteps) {
245
+ steps += 1;
246
+ }
247
+ return steps * this.settings.step + range.min;
248
+ } else {
249
+ return rawValue;
250
+ }
251
+ };
252
+
253
+ SimpleSlider.prototype.valueToRatio = function(value) {
254
+ var allowedVal, closest, closestIdx, idx, range, _i, _len, _ref;
255
+ if (this.settings.equalSteps) {
256
+ _ref = this.settings.allowedValues;
257
+ for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) {
258
+ allowedVal = _ref[idx];
259
+ if (!(typeof closest !== "undefined" && closest !== null) || Math.abs(allowedVal - value) < Math.abs(closest - value)) {
260
+ closest = allowedVal;
261
+ closestIdx = idx;
262
+ }
263
+ }
264
+ if (this.settings.snapMid) {
265
+ return (closestIdx + 0.5) / this.settings.allowedValues.length;
266
+ } else {
267
+ return closestIdx / (this.settings.allowedValues.length - 1);
268
+ }
269
+ } else {
270
+ range = this.getRange();
271
+ return (value - range.min) / (range.max - range.min);
272
+ }
273
+ };
274
+
275
+ SimpleSlider.prototype.ratioToValue = function(ratio) {
276
+ var idx, range, rawValue, step, steps;
277
+ if (this.settings.equalSteps) {
278
+ steps = this.settings.allowedValues.length;
279
+ step = Math.round(ratio * steps - 0.5);
280
+ idx = Math.min(step, this.settings.allowedValues.length - 1);
281
+ return this.settings.allowedValues[idx];
282
+ } else {
283
+ range = this.getRange();
284
+ rawValue = ratio * (range.max - range.min) + range.min;
285
+ return this.nearestValidValue(rawValue);
286
+ }
287
+ };
288
+
289
+ SimpleSlider.prototype.valueChanged = function(value, ratio, trigger) {
290
+ var eventData;
291
+ if (value.toString() === this.value.toString()) {
292
+ return;
293
+ }
294
+ this.value = value;
295
+ eventData = {
296
+ value: value,
297
+ ratio: ratio,
298
+ position: ratio * this.slider.outerWidth(),
299
+ trigger: trigger,
300
+ el: this.slider
301
+ };
302
+ return this.input.val(value).trigger($.Event("change", eventData)).trigger("slider:changed", eventData);
303
+ };
304
+
305
+ return SimpleSlider;
306
+
307
+ })();
308
+ $.extend($.fn, {
309
+ simpleSlider: function() {
310
+ var params, publicMethods, settingsOrMethod;
311
+ settingsOrMethod = arguments[0], params = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
312
+ publicMethods = ["setRatio", "setValue"];
313
+ return $(this).each(function() {
314
+ var obj, settings;
315
+ if (settingsOrMethod && __indexOf.call(publicMethods, settingsOrMethod) >= 0) {
316
+ obj = $(this).data("slider-object");
317
+ return obj[settingsOrMethod].apply(obj, params);
318
+ } else {
319
+ settings = settingsOrMethod;
320
+ return $(this).data("slider-object", new SimpleSlider($(this), settings));
321
+ }
322
+ });
323
+ }
324
+ });
325
+ return $(function() {
326
+ return $("[data-slider]").each(function() {
327
+ var $el, allowedValues, settings, x;
328
+ $el = $(this);
329
+ settings = {};
330
+ allowedValues = $el.data("slider-values");
331
+ if (allowedValues) {
332
+ settings.allowedValues = (function() {
333
+ var _i, _len, _ref, _results;
334
+ _ref = allowedValues.split(",");
335
+ _results = [];
336
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
337
+ x = _ref[_i];
338
+ _results.push(parseFloat(x));
339
+ }
340
+ return _results;
341
+ })();
342
+ }
343
+ if ($el.data("slider-range")) {
344
+ settings.range = $el.data("slider-range").split(",");
345
+ }
346
+ if ($el.data("slider-step")) {
347
+ settings.step = $el.data("slider-step");
348
+ }
349
+ settings.snap = $el.data("slider-snap");
350
+ settings.equalSteps = $el.data("slider-equal-steps");
351
+ if ($el.data("slider-theme")) {
352
+ settings.theme = $el.data("slider-theme");
353
+ }
354
+ if ($el.attr("data-slider-highlight")) {
355
+ settings.highlight = $el.data("slider-highlight");
356
+ }
357
+ if ($el.data("slider-animate") != null) {
358
+ settings.animate = $el.data("slider-animate");
359
+ }
360
+ return $el.simpleSlider(settings);
361
+ });
362
+ });
363
+ })(this.jQuery || this.Zepto, this);
@@ -0,0 +1,233 @@
1
+ //= require voltron
2
+ //= require cropit
3
+ //= require simple-slider
4
+
5
+ Voltron.addModule('Crop', function(){
6
+
7
+ var Crop = function(options, config){
8
+
9
+ var _positioned = false;
10
+
11
+ var configDefaults = {
12
+ preview: $('<div />', { class: 'cropit-preview' }),
13
+ input: $('<input />', { type: 'file', class: 'cropit-input' }),
14
+ zoom: $('<input />', { type: 'text', class: 'cropit-zoom' }),
15
+ fileInput: null,
16
+ container: null,
17
+ image: null
18
+ };
19
+
20
+ var cropDefaults = {
21
+ imageBackground: true,
22
+ imageBackgroundBorderWidth: 100,
23
+ width: 300,
24
+ height: 300,
25
+ onImageLoading: function(){
26
+ var el = this.$fileInput,
27
+ crop = el.data('crop');
28
+
29
+ // If the crop class exists on the element, store the current zoom value so
30
+ // we can pick it up later in onImageLoaded callback
31
+ if(crop){
32
+ Voltron.dispatch('crop:loading', { crop: crop, element: el.get(0) });
33
+ el.data('last_zoom', parseFloat(crop.getZoom().val()));
34
+ }
35
+ },
36
+ onImageLoaded: function(){
37
+ // Reset the zoom value to what it was before,
38
+ // then trigger a change event on the input so the image is properly resized
39
+ var el = this.$fileInput,
40
+ crop = el.data('crop'),
41
+ zoom = el.data('last_zoom');
42
+
43
+ if(crop && zoom){
44
+ Voltron.dispatch('crop:loaded', { crop: crop, element: el.get(0), data: { zoom: zoom } });
45
+ crop.getZoom().simpleSlider('setRatio', zoom);
46
+ crop.getZoom().data('sliderObject').input.val(zoom).trigger('change');
47
+ crop.setPosition();
48
+ }
49
+ }
50
+ };
51
+
52
+ config = $.extend(configDefaults, config);
53
+ options = $.extend(cropDefaults, options);
54
+
55
+ return {
56
+ initialize: function(){
57
+ // When the form is submitted, gather the x, y, width, height, zoom inputs
58
+ this.getFileInput().closest('form').on('submit', $.proxy(this.update, this))
59
+ // Add the crop html
60
+ this.getFileInput().before(this.getCropContainer());
61
+ // Initiate the zoom slider
62
+ this.getZoom().simpleSlider();
63
+
64
+ // If a cached file is present (form was submitted, but page re-rendered, possibly due to failed validation), include the input
65
+ if(this.getFileInput().data('crop-cache')){
66
+ this.getCropContainer().prepend($('<input />', { type: 'hidden', name: this.getName('cache'), value: this.getFileInput().data('crop-cache') }));
67
+ }
68
+
69
+ // If a zoom level was passed, set the slider to the specified zoom level
70
+ if(this.getFileInput().data('crop-zoom')){
71
+ this.getZoom().data('sliderObject').input.val(this.getFileInput().data('crop-zoom')).trigger('change');
72
+ }
73
+
74
+ // Include self on the file input element's data
75
+ this.getFileInput().data('crop', this);
76
+ },
77
+
78
+ update: function(){
79
+ var dimensions = this.getDimensions(),
80
+ zoom = parseFloat(this.getZoom().val());
81
+
82
+ this.getCropContainer().find('.' + this.getFieldName() + '-crop-dimension-x').val(dimensions.x);
83
+ this.getCropContainer().find('.' + this.getFieldName() + '-crop-dimension-y').val(dimensions.y);
84
+ this.getCropContainer().find('.' + this.getFieldName() + '-crop-dimension-w').val(dimensions.w);
85
+ this.getCropContainer().find('.' + this.getFieldName() + '-crop-dimension-h').val(dimensions.h);
86
+ this.getCropContainer().find('.' + this.getFieldName() + '-crop-zoom').val(zoom);
87
+ },
88
+
89
+ getDimensions: function(){
90
+ var imageBg = this.getCropObject().$bg,
91
+ borderWidth = this.getCropObject().bgBorderWidthArray,
92
+ cropImage = this.getCropContainer().cropit('imageSize'),
93
+ cropWindow = this.getCropContainer().cropit('previewSize'),
94
+ zoom = this.getCropContainer().cropit('zoom'),
95
+ cropPosition = imageBg.position(),
96
+ cropPercentX = Math.abs(cropPosition.left-borderWidth[3])/(cropImage.width*zoom),
97
+ cropPercentY = Math.abs(cropPosition.top-borderWidth[0])/(cropImage.height*zoom),
98
+ cropPercentW = cropWindow.width/(cropImage.width*zoom),
99
+ cropPercentH = cropWindow.height/(cropImage.height*zoom),
100
+ positionX = Math.abs(cropPosition.left-borderWidth[3]),
101
+ positionY = Math.abs(cropPosition.top-borderWidth[0]),
102
+ cropX = cropImage.width*cropPercentX,
103
+ cropY = cropImage.height*cropPercentY,
104
+ cropW = cropImage.width*cropPercentW,
105
+ cropH = cropImage.height*cropPercentH;
106
+
107
+ return { x: cropX, y: cropY, w: cropW, h: cropH };
108
+ },
109
+
110
+ setPosition: function(){
111
+ // Only set the position of the image once, after the initial image is loaded
112
+ // For additionl uploads the default positioning should be used
113
+ if(!_positioned){
114
+ var cropImage = this.getCropContainer().cropit('imageSize'),
115
+ x = this.getFileInput().data('crop-x'),
116
+ y = this.getFileInput().data('crop-y'),
117
+ zoom = this.getCropContainer().cropit('zoom');
118
+
119
+ x = ((cropImage.width*zoom)*x)/cropImage.width;
120
+ y = ((cropImage.height*zoom)*y)/cropImage.height;
121
+
122
+ if(!isNaN(parseFloat(x)) && !isNaN(parseFloat(y))){
123
+ this.getCropContainer().cropit('offset', { x: -x, y: -y });
124
+ }
125
+ _positioned = true;
126
+ }
127
+ },
128
+
129
+ getName: function(field){
130
+ return $(config.fileInput).attr('name').replace(/([a-z0-9_]+)\]$/i, '$1_' + field + ']');
131
+ },
132
+
133
+ getFieldName: function(){
134
+ return $(config.fileInput).attr('name').replace(/.*\[([a-z0-9_]+)\]$/i, '$1');
135
+ },
136
+
137
+ getImage: function(){
138
+ return this.getCropContainer().cropit('imageSrc');
139
+ },
140
+
141
+ setImage: function(path){
142
+ this.getCropContainer().cropit('imageSrc', Voltron.getBaseUrl() + path);
143
+ },
144
+
145
+ getZoom: function(){
146
+ return $(config.zoom);
147
+ },
148
+
149
+ getFileInput: function(){
150
+ return $(config.fileInput);
151
+ },
152
+
153
+ getPreview: function(){
154
+ return $(config.preview);
155
+ },
156
+
157
+ getDimensionInput: function(){
158
+ var content = $('<div />');
159
+ content.append($('<input />', { type: 'hidden', name: this.getName('x'), class: this.getFieldName() + '-crop-dimension-x' }));
160
+ content.append($('<input />', { type: 'hidden', name: this.getName('y'), class: this.getFieldName() + '-crop-dimension-y' }));
161
+ content.append($('<input />', { type: 'hidden', name: this.getName('w'), class: this.getFieldName() + '-crop-dimension-w' }));
162
+ content.append($('<input />', { type: 'hidden', name: this.getName('h'), class: this.getFieldName() + '-crop-dimension-h' }));
163
+ content.append($('<input />', { type: 'hidden', name: this.getName('zoom'), class: this.getFieldName() + '-crop-zoom' }));
164
+ return content.html();
165
+ },
166
+
167
+ getConfig: function(){
168
+ return $.extend(options, {
169
+ imageState: { src: config.image },
170
+ minZoom: 0,
171
+ maxZoom: 2,
172
+ freeMove: false,
173
+ $preview: this.getPreview(),
174
+ $fileInput: this.getFileInput(),
175
+ $zoomSlider: this.getZoom()
176
+ });
177
+ },
178
+
179
+ getCropContainer: function(){
180
+ if(config.container === null){
181
+ config.container = $('<div />', { class: 'cropit-container' });
182
+ config.container.append(this.getDimensionInput());
183
+ config.container.append(this.getPreview());
184
+ config.container.append($('<div />', { class: 'zoom-container' }).append(this.getZoom()));
185
+
186
+ config.container.cropit(this.getConfig());
187
+
188
+ var borderWidth = config.container.data('cropit').bgBorderWidthArray;
189
+
190
+ this.getPreview().css({
191
+ marginTop: borderWidth[0],
192
+ marginRight: borderWidth[1],
193
+ marginBottom: borderWidth[2],
194
+ marginLeft: borderWidth[3]
195
+ });
196
+ }
197
+ return config.container;
198
+ },
199
+
200
+ getCropObject: function(){
201
+ return this.getCropContainer().data('cropit');
202
+ }
203
+ };
204
+ };
205
+
206
+ return {
207
+ initialize: function(){
208
+ $('input[data-crop-image]').each(function(){
209
+ var crop = Voltron('Crop/new', $(this).data('crop-options'), $.extend({ fileInput: this, image: $(this).data('crop-image') }, $(this).data('crop-config')));
210
+ crop.initialize();
211
+ });
212
+ },
213
+
214
+ new: function(options, config){
215
+ return new Crop(options, config);
216
+ },
217
+
218
+ // START: Compatibility with Voltron Upload module
219
+ // Ensures the crop container is created before the Upload dropzone is instantiated.
220
+ onBeforeModuleInitializeUpload: function(o){
221
+ this.initialize();
222
+ },
223
+
224
+ // Updates the crop image when an upload is completed
225
+ onUploadComplete: function(o){
226
+ var crop = $(o.element).data('crop');
227
+ if(o.data.uploads && crop){
228
+ crop.setImage(o.data.uploads.first().url);
229
+ }
230
+ }
231
+ // END: Compatibility with Voltron Upload module
232
+ };
233
+ }, true);