voltron-crop 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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);