image_picker 0.5

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 @@
1
+ 10/23/08 - Initial version [Matthew Bass]
@@ -0,0 +1,16 @@
1
+ Copyright (c) 2007-2008 Matthew Bass (http://matthewbass.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
4
+ and associated documentation files (the "Software"), to deal in the Software without
5
+ restriction, including without limitation the rights to use, copy, modify, merge, publish,
6
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
7
+ Software is furnished to do so, subject to the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be included in all copies or
10
+ substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
13
+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
15
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,73 @@
1
+ = image_picker
2
+
3
+ A simple, customizable image browser.
4
+
5
+ == Installation
6
+
7
+ Install the gem directly:
8
+
9
+ sudo gem install pelargir-image_picker --source=http://gems.github.com
10
+
11
+ Or install the gem in your Rails project as a plugin:
12
+
13
+ script/plugin install git://github.com/pelargir/image_picker.git
14
+
15
+ Or clone the project:
16
+
17
+ git clone git://github.com/pelargir/image_picker.git
18
+
19
+ Then install the required image, JS, and CSS files into your Rails project:
20
+
21
+ rake image_picker:install
22
+
23
+ == Usage
24
+
25
+ First, add the picker to your view and pass it the name of the image
26
+ being picked. This name is used to create a hidden field that stores
27
+ the database ID of the image that gets picked:
28
+
29
+ <%= image_picker :avatar %>
30
+
31
+ Now tell your controller that you'll be picking images, and which
32
+ ActiveRecord model you'll be using:
33
+
34
+ class SomeController < ActionController::Base
35
+ image_picker :model => Image
36
+ end
37
+
38
+ The ActiveRecord class must respond to three method calls:
39
+
40
+ thumbnail: the path to the image's thumbnail (e.g. /images/foo_small.gif)
41
+ title: a friendly title for the image
42
+ description: a description of the image (optional)
43
+
44
+ That's it! The picker will show up in your view and, once you've
45
+ picked an image, its database ID will be set as the value of the
46
+ hidden field "<name>_id" where <name> is the name you passed
47
+ to the image_picker helper in your view. The hidden field can
48
+ then be submitted via a form or referenced in JavaScript.
49
+
50
+ == Other Options
51
+
52
+ The controller helper accepts any arguments you can pass to an
53
+ ActiveRecord finder. For example:
54
+
55
+ class SomeController < ActionController::Base
56
+ image_picker :model => Image, :order => "created_at DESC"
57
+ end
58
+
59
+ If the will_paginate plugin is installed, the image picker will
60
+ use it automatically. You can pass pagination options to the
61
+ controller helper as well. For example:
62
+
63
+ class SomeController < ActionController::Base
64
+ image_picker :model => Image, :per_page => 6
65
+ end
66
+
67
+ == Resources
68
+
69
+ Repository: http://github.com/pelargir/image_picker/
70
+ Blog: http://matthewbass.com
71
+ Author: Matthew Bass
72
+
73
+ Extraction work sponsored by Terralien
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the image_picker plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the image_picker plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'image_picker'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,14 @@
1
+ # Generated by the asset_copier plugin
2
+ # http://github.com/pelargir/asset_copier
3
+ #
4
+ # Files that have been removed from the plugin and should also be removed from
5
+ # any Rails projects that use the plugin should be listed here, one entry
6
+ # per line. For example:
7
+ #
8
+ # public/javascripts/foo.js
9
+ # app/views/foo/bar.erb.html
10
+ #
11
+ # Adding the paths above to this file would ensure that foo.js and bar.erb.html
12
+ # both get removed from the target Rails project the next time the plugin's
13
+ # files are copied over.
14
+
@@ -0,0 +1,21 @@
1
+ var ImagePicker = {
2
+ pick:function(field, image_id, image_title, image_url) {
3
+ $(field + '_id').value = image_id;
4
+ $(field).src = image_url;
5
+ $(field).alt = image_title;
6
+ Modalbox.hide();
7
+ },
8
+
9
+ clear:function(field, image_url) {
10
+ $(field + '_id').value = '';
11
+ $(field).src = image_url;
12
+ $(field).alt = 'No_image';
13
+ Modalbox.hide();
14
+ },
15
+
16
+ open_picker:function(params) {
17
+ url = $H(params).unset('url');
18
+ Modalbox.show(url + '?' + $H(params).toQueryString(),
19
+ {title: 'Select an image...', width: 520, afterLoad: ImagePicker.setup});
20
+ }
21
+ };
@@ -0,0 +1,573 @@
1
+ /*
2
+ ModalBox - The pop-up window thingie with AJAX, based on prototype and script.aculo.us.
3
+
4
+ Copyright Andrey Okonetchnikov (andrej.okonetschnikow@gmail.com), 2006-2007
5
+ All rights reserved.
6
+
7
+ VERSION 1.6.0
8
+ Last Modified: 12/13/2007
9
+ */
10
+
11
+ if (!window.Modalbox)
12
+ var Modalbox = new Object();
13
+
14
+ Modalbox.Methods = {
15
+ overrideAlert: false, // Override standard browser alert message with ModalBox
16
+ focusableElements: new Array,
17
+ currFocused: 0,
18
+ initialized: false,
19
+ active: true,
20
+ options: {
21
+ title: "ModalBox Window", // Title of the ModalBox window
22
+ overlayClose: true, // Close modal box by clicking on overlay
23
+ width: 500, // Default width in px
24
+ height: 90, // Default height in px
25
+ overlayOpacity: .65, // Default overlay opacity
26
+ overlayDuration: .25, // Default overlay fade in/out duration in seconds
27
+ slideDownDuration: .5, // Default Modalbox appear slide down effect in seconds
28
+ slideUpDuration: .5, // Default Modalbox hiding slide up effect in seconds
29
+ resizeDuration: .25, // Default resize duration seconds
30
+ inactiveFade: true, // Fades MB window on inactive state
31
+ transitions: true, // Toggles transition effects. Transitions are enabled by default
32
+ loadingString: "Please wait. Loading...", // Default loading string message
33
+ closeString: "Close window", // Default title attribute for close window link
34
+ closeValue: "&times;", // Default string for close link in the header
35
+ params: {},
36
+ method: 'get', // Default Ajax request method
37
+ autoFocusing: true, // Toggles auto-focusing for form elements. Disable for long text pages.
38
+ aspnet: false // Should be use then using with ASP.NET costrols. Then true Modalbox window will be injected into the first form element.
39
+ },
40
+ _options: new Object,
41
+
42
+ setOptions: function(options) {
43
+ Object.extend(this.options, options || {});
44
+ },
45
+
46
+ _init: function(options) {
47
+ // Setting up original options with default options
48
+ Object.extend(this._options, this.options);
49
+ this.setOptions(options);
50
+
51
+ //Create the overlay
52
+ this.MBoverlay = new Element("div", { id: "MB_overlay", opacity: "0" });
53
+
54
+ //Create DOm for the window
55
+ this.MBwindow = new Element("div", {id: "MB_window", style: "display: none"}).update(
56
+ this.MBframe = new Element("div", {id: "MB_frame"}).update(
57
+ this.MBheader = new Element("div", {id: "MB_header"}).update(
58
+ this.MBcaption = new Element("div", {id: "MB_caption"})
59
+ )
60
+ )
61
+ );
62
+ this.MBclose = new Element("a", {id: "MB_close", title: this.options.closeString, href: "#"}).update("<span>" + this.options.closeValue + "</span>");
63
+ this.MBheader.insert({'bottom':this.MBclose});
64
+
65
+ this.MBcontent = new Element("div", {id: "MB_content"}).update(
66
+ this.MBloading = new Element("div", {id: "MB_loading"}).update(this.options.loadingString)
67
+ );
68
+ this.MBframe.insert({'bottom':this.MBcontent});
69
+
70
+ // Inserting into DOM. If parameter set and form element have been found will inject into it. Otherwise will inject into body as topmost element.
71
+ // Be sure to set padding and marging to null via CSS for both body and (in case of asp.net) form elements.
72
+ var injectToEl = this.options.aspnet ? $(document.body).down('form') : $(document.body);
73
+ injectToEl.insert({'top':this.MBwindow});
74
+ injectToEl.insert({'top':this.MBoverlay});
75
+
76
+ // Initial scrolling position of the window. To be used for remove scrolling effect during ModalBox appearing
77
+ this.initScrollX = window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft;
78
+ this.initScrollY = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
79
+
80
+ //Adding event observers
81
+ this.hideObserver = this._hide.bindAsEventListener(this);
82
+ this.kbdObserver = this._kbdHandler.bindAsEventListener(this);
83
+ this._initObservers();
84
+
85
+ this.initialized = true; // Mark as initialized
86
+ },
87
+
88
+ show: function(content, options) {
89
+ if(!this.initialized) this._init(options); // Check for is already initialized
90
+
91
+ this.content = content;
92
+ this.setOptions(options);
93
+
94
+ if(this.options.title) // Updating title of the MB
95
+ $(this.MBcaption).update(this.options.title);
96
+ else { // If title isn't given, the header will not displayed
97
+ $(this.MBheader).hide();
98
+ $(this.MBcaption).hide();
99
+ }
100
+
101
+ if(this.MBwindow.style.display == "none") { // First modal box appearing
102
+ this._appear();
103
+ this.event("onShow"); // Passing onShow callback
104
+ }
105
+ else { // If MB already on the screen, update it
106
+ this._update();
107
+ this.event("onUpdate"); // Passing onUpdate callback
108
+ }
109
+ },
110
+
111
+ hide: function(options) { // External hide method to use from external HTML and JS
112
+ if(this.initialized) {
113
+ // Reading for options/callbacks except if event given as a pararmeter
114
+ if(options && typeof options.element != 'function') Object.extend(this.options, options);
115
+ // Passing beforeHide callback
116
+ this.event("beforeHide");
117
+ if(this.options.transitions)
118
+ Effect.SlideUp(this.MBwindow, { duration: this.options.slideUpDuration, transition: Effect.Transitions.sinoidal, afterFinish: this._deinit.bind(this) } );
119
+ else {
120
+ $(this.MBwindow).hide();
121
+ this._deinit();
122
+ }
123
+ } else throw("Modalbox is not initialized.");
124
+ },
125
+
126
+ _hide: function(event) { // Internal hide method to use with overlay and close link
127
+ event.stop(); // Stop event propaganation for link elements
128
+ /* Then clicked on overlay we'll check the option and in case of overlayClose == false we'll break hiding execution [Fix for #139] */
129
+ if(event.element().id == 'MB_overlay' && !this.options.overlayClose) return false;
130
+ this.hide();
131
+ },
132
+
133
+ alert: function(message){
134
+ var html = '<div class="MB_alert"><p>' + message + '</p><input type="button" onclick="Modalbox.hide()" value="OK" /></div>';
135
+ Modalbox.show(html, {title: 'Alert: ' + document.title, width: 300});
136
+ },
137
+
138
+ _appear: function() { // First appearing of MB
139
+ if(Prototype.Browser.IE && !navigator.appVersion.match(/\b7.0\b/)) { // Preparing IE 6 for showing modalbox
140
+ window.scrollTo(0,0);
141
+ this._prepareIE("100%", "hidden");
142
+ }
143
+ this._setWidth();
144
+ this._setPosition();
145
+ if(this.options.transitions) {
146
+ $(this.MBoverlay).setStyle({opacity: 0});
147
+ new Effect.Fade(this.MBoverlay, {
148
+ from: 0,
149
+ to: this.options.overlayOpacity,
150
+ duration: this.options.overlayDuration,
151
+ afterFinish: function() {
152
+ new Effect.SlideDown(this.MBwindow, {
153
+ duration: this.options.slideDownDuration,
154
+ transition: Effect.Transitions.sinoidal,
155
+ afterFinish: function(){
156
+ this._setPosition();
157
+ this.loadContent();
158
+ }.bind(this)
159
+ });
160
+ }.bind(this)
161
+ });
162
+ } else {
163
+ $(this.MBoverlay).setStyle({opacity: this.options.overlayOpacity});
164
+ $(this.MBwindow).show();
165
+ this._setPosition();
166
+ this.loadContent();
167
+ }
168
+ this._setWidthAndPosition = this._setWidthAndPosition.bindAsEventListener(this);
169
+ Event.observe(window, "resize", this._setWidthAndPosition);
170
+ },
171
+
172
+ resize: function(byWidth, byHeight, options) { // Change size of MB without loading content
173
+ var wHeight = $(this.MBwindow).getHeight();
174
+ var wWidth = $(this.MBwindow).getWidth();
175
+ var hHeight = $(this.MBheader).getHeight();
176
+ var cHeight = $(this.MBcontent).getHeight();
177
+ var newHeight = ((wHeight - hHeight + byHeight) < cHeight) ? (cHeight + hHeight - wHeight) : byHeight;
178
+ if(options) this.setOptions(options); // Passing callbacks
179
+ if(this.options.transitions) {
180
+ new Effect.ScaleBy(this.MBwindow, byWidth, newHeight, {
181
+ duration: this.options.resizeDuration,
182
+ afterFinish: function() {
183
+ this.event("_afterResize"); // Passing internal callback
184
+ this.event("afterResize"); // Passing callback
185
+ }.bind(this)
186
+ });
187
+ } else {
188
+ this.MBwindow.setStyle({width: wWidth + byWidth + "px", height: wHeight + newHeight + "px"});
189
+ setTimeout(function() {
190
+ this.event("_afterResize"); // Passing internal callback
191
+ this.event("afterResize"); // Passing callback
192
+ }.bind(this), 1);
193
+
194
+ }
195
+
196
+ },
197
+
198
+ resizeToContent: function(options){
199
+
200
+ // Resizes the modalbox window to the actual content height.
201
+ // This might be useful to resize modalbox after some content modifications which were changed ccontent height.
202
+
203
+ var byHeight = this.options.height - this.MBwindow.offsetHeight;
204
+ if(byHeight != 0) {
205
+ if(options) this.setOptions(options); // Passing callbacks
206
+ Modalbox.resize(0, byHeight);
207
+ }
208
+ },
209
+
210
+ resizeToInclude: function(element, options){
211
+
212
+ // Resizes the modalbox window to the camulative height of element. Calculations are using CSS properties for margins and border.
213
+ // This method might be useful to resize modalbox before including or updating content.
214
+
215
+ var el = $(element);
216
+ var elHeight = el.getHeight() + parseInt(el.getStyle('margin-top')) + parseInt(el.getStyle('margin-bottom')) + parseInt(el.getStyle('border-top-width')) + parseInt(el.getStyle('border-bottom-width'));
217
+ if(elHeight > 0) {
218
+ if(options) this.setOptions(options); // Passing callbacks
219
+ Modalbox.resize(0, elHeight);
220
+ }
221
+ },
222
+
223
+ _update: function() { // Updating MB in case of wizards
224
+ $(this.MBcontent).update("");
225
+ this.MBcontent.appendChild(this.MBloading);
226
+ $(this.MBloading).update(this.options.loadingString);
227
+ this.currentDims = [this.MBwindow.offsetWidth, this.MBwindow.offsetHeight];
228
+ Modalbox.resize((this.options.width - this.currentDims[0]), (this.options.height - this.currentDims[1]), {_afterResize: this._loadAfterResize.bind(this) });
229
+ },
230
+
231
+ loadContent: function () {
232
+ if(this.event("beforeLoad") != false) { // If callback passed false, skip loading of the content
233
+ if(typeof this.content == 'string') {
234
+ var htmlRegExp = new RegExp(/<\/?[^>]+>/gi);
235
+ if(htmlRegExp.test(this.content)) { // Plain HTML given as a parameter
236
+ this._insertContent(this.content.stripScripts());
237
+ this._putContent(function(){
238
+ this.content.extractScripts().map(function(script) {
239
+ return eval(script.replace("<!--", "").replace("// -->", ""));
240
+ }.bind(window));
241
+ }.bind(this));
242
+ } else // URL given as a parameter. We'll request it via Ajax
243
+ new Ajax.Request( this.content, { method: this.options.method.toLowerCase(), parameters: this.options.params,
244
+ onSuccess: function(transport) {
245
+ var response = new String(transport.responseText);
246
+ this._insertContent(transport.responseText.stripScripts());
247
+ this._putContent(function(){
248
+ response.extractScripts().map(function(script) {
249
+ return eval(script.replace("<!--", "").replace("// -->", ""));
250
+ }.bind(window));
251
+ });
252
+ }.bind(this),
253
+ onException: function(instance, exception){
254
+ Modalbox.hide();
255
+ throw('Modalbox Loading Error: ' + exception);
256
+ }
257
+ });
258
+
259
+ } else if (typeof this.content == 'object') {// HTML Object is given
260
+ this._insertContent(this.content);
261
+ this._putContent();
262
+ } else {
263
+ Modalbox.hide();
264
+ throw('Modalbox Parameters Error: Please specify correct URL or HTML element (plain HTML or object)');
265
+ }
266
+ }
267
+ },
268
+
269
+ _insertContent: function(content){
270
+ $(this.MBcontent).hide().update("");
271
+ if(typeof content == 'string') {
272
+ setTimeout(function() { // Hack to disable content flickering in Firefox
273
+ this.MBcontent.update(content);
274
+ }.bind(this), 1);
275
+ } else if (typeof content == 'object') { // HTML Object is given
276
+ var _htmlObj = content.cloneNode(true); // If node already a part of DOM we'll clone it
277
+ // If clonable element has ID attribute defined, modifying it to prevent duplicates
278
+ if(content.id) content.id = "MB_" + content.id;
279
+ /* Add prefix for IDs on all elements inside the DOM node */
280
+ $(content).select('*[id]').each(function(el){ el.id = "MB_" + el.id; });
281
+ this.MBcontent.appendChild(_htmlObj);
282
+ this.MBcontent.down().show(); // Toggle visibility for hidden nodes
283
+ if(Prototype.Browser.IE) // Toggling back visibility for hidden selects in IE
284
+ $$("#MB_content select").invoke('setStyle', {'visibility': ''});
285
+ }
286
+ },
287
+
288
+ _putContent: function(callback){
289
+ // Prepare and resize modal box for content
290
+ if(this.options.height == this._options.height) {
291
+ setTimeout(function() { // MSIE sometimes doesn't display content correctly
292
+ Modalbox.resize(0, $(this.MBcontent).getHeight() - $(this.MBwindow).getHeight() + $(this.MBheader).getHeight(), {
293
+ afterResize: function(){
294
+ this.MBcontent.show().makePositioned();
295
+ this.focusableElements = this._findFocusableElements();
296
+ this._setFocus(); // Setting focus on first 'focusable' element in content (input, select, textarea, link or button)
297
+ setTimeout(function(){ // MSIE fix
298
+ if(callback != undefined)
299
+ callback(); // Executing internal JS from loaded content
300
+ this.event("afterLoad"); // Passing callback
301
+ }.bind(this),1);
302
+ }.bind(this)
303
+ });
304
+ }.bind(this), 1);
305
+ } else { // Height is defined. Creating a scrollable window
306
+ this._setWidth();
307
+ this.MBcontent.setStyle({overflow: 'auto', height: $(this.MBwindow).getHeight() - $(this.MBheader).getHeight() - 13 + 'px'});
308
+ this.MBcontent.show();
309
+ this.focusableElements = this._findFocusableElements();
310
+ this._setFocus(); // Setting focus on first 'focusable' element in content (input, select, textarea, link or button)
311
+ setTimeout(function(){ // MSIE fix
312
+ if(callback != undefined)
313
+ callback(); // Executing internal JS from loaded content
314
+ this.event("afterLoad"); // Passing callback
315
+ }.bind(this),1);
316
+ }
317
+ },
318
+
319
+ activate: function(options){
320
+ this.setOptions(options);
321
+ this.active = true;
322
+ $(this.MBclose).observe("click", this.hideObserver);
323
+ if(this.options.overlayClose)
324
+ $(this.MBoverlay).observe("click", this.hideObserver);
325
+ $(this.MBclose).show();
326
+ if(this.options.transitions && this.options.inactiveFade)
327
+ new Effect.Appear(this.MBwindow, {duration: this.options.slideUpDuration});
328
+ },
329
+
330
+ deactivate: function(options) {
331
+ this.setOptions(options);
332
+ this.active = false;
333
+ $(this.MBclose).stopObserving("click", this.hideObserver);
334
+ if(this.options.overlayClose)
335
+ $(this.MBoverlay).stopObserving("click", this.hideObserver);
336
+ $(this.MBclose).hide();
337
+ if(this.options.transitions && this.options.inactiveFade)
338
+ new Effect.Fade(this.MBwindow, {duration: this.options.slideUpDuration, to: .75});
339
+ },
340
+
341
+ _initObservers: function(){
342
+ $(this.MBclose).observe("click", this.hideObserver);
343
+ if(this.options.overlayClose)
344
+ $(this.MBoverlay).observe("click", this.hideObserver);
345
+ if(Prototype.Browser.IE)
346
+ Event.observe(document, "keydown", this.kbdObserver);
347
+ else
348
+ Event.observe(document, "keypress", this.kbdObserver);
349
+ },
350
+
351
+ _removeObservers: function(){
352
+ $(this.MBclose).stopObserving("click", this.hideObserver);
353
+ if(this.options.overlayClose)
354
+ $(this.MBoverlay).stopObserving("click", this.hideObserver);
355
+ if(Prototype.Browser.IE)
356
+ Event.stopObserving(document, "keydown", this.kbdObserver);
357
+ else
358
+ Event.stopObserving(document, "keypress", this.kbdObserver);
359
+ },
360
+
361
+ _loadAfterResize: function() {
362
+ this._setWidth();
363
+ this._setPosition();
364
+ this.loadContent();
365
+ },
366
+
367
+ _setFocus: function() {
368
+ /* Setting focus to the first 'focusable' element which is one with tabindex = 1 or the first in the form loaded. */
369
+ if(this.focusableElements.length > 0 && this.options.autoFocusing == true) {
370
+ var firstEl = this.focusableElements.find(function (el){
371
+ return el.tabIndex == 1;
372
+ }) || this.focusableElements.first();
373
+ this.currFocused = this.focusableElements.toArray().indexOf(firstEl);
374
+ firstEl.focus(); // Focus on first focusable element except close button
375
+ } else if($(this.MBclose).visible())
376
+ $(this.MBclose).focus(); // If no focusable elements exist focus on close button
377
+ },
378
+
379
+ _findFocusableElements: function(){ // Collect form elements or links from MB content
380
+ this.MBcontent.select('input:not([type~=hidden]), select, textarea, button, a[href]').invoke('addClassName', 'MB_focusable');
381
+ return this.MBcontent.select('.MB_focusable');
382
+ },
383
+
384
+ _kbdHandler: function(event) {
385
+ var node = event.element();
386
+ switch(event.keyCode) {
387
+ case Event.KEY_TAB:
388
+ event.stop();
389
+
390
+ /* Switching currFocused to the element which was focused by mouse instead of TAB-key. Fix for #134 */
391
+ if(node != this.focusableElements[this.currFocused])
392
+ this.currFocused = this.focusableElements.toArray().indexOf(node);
393
+
394
+ if(!event.shiftKey) { //Focusing in direct order
395
+ if(this.currFocused == this.focusableElements.length - 1) {
396
+ this.focusableElements.first().focus();
397
+ this.currFocused = 0;
398
+ } else {
399
+ this.currFocused++;
400
+ this.focusableElements[this.currFocused].focus();
401
+ }
402
+ } else { // Shift key is pressed. Focusing in reverse order
403
+ if(this.currFocused == 0) {
404
+ this.focusableElements.last().focus();
405
+ this.currFocused = this.focusableElements.length - 1;
406
+ } else {
407
+ this.currFocused--;
408
+ this.focusableElements[this.currFocused].focus();
409
+ }
410
+ }
411
+ break;
412
+ case Event.KEY_ESC:
413
+ if(this.active) this._hide(event);
414
+ break;
415
+ case 32:
416
+ this._preventScroll(event);
417
+ break;
418
+ case 0: // For Gecko browsers compatibility
419
+ if(event.which == 32) this._preventScroll(event);
420
+ break;
421
+ case Event.KEY_UP:
422
+ case Event.KEY_DOWN:
423
+ case Event.KEY_PAGEDOWN:
424
+ case Event.KEY_PAGEUP:
425
+ case Event.KEY_HOME:
426
+ case Event.KEY_END:
427
+ // Safari operates in slightly different way. This realization is still buggy in Safari.
428
+ if(Prototype.Browser.WebKit && !["textarea", "select"].include(node.tagName.toLowerCase()))
429
+ event.stop();
430
+ else if( (node.tagName.toLowerCase() == "input" && ["submit", "button"].include(node.type)) || (node.tagName.toLowerCase() == "a") )
431
+ event.stop();
432
+ break;
433
+ }
434
+ },
435
+
436
+ _preventScroll: function(event) { // Disabling scrolling by "space" key
437
+ if(!["input", "textarea", "select", "button"].include(event.element().tagName.toLowerCase()))
438
+ event.stop();
439
+ },
440
+
441
+ _deinit: function()
442
+ {
443
+ this._removeObservers();
444
+ Event.stopObserving(window, "resize", this._setWidthAndPosition );
445
+ if(this.options.transitions) {
446
+ Effect.toggle(this.MBoverlay, 'appear', {duration: this.options.overlayDuration, afterFinish: this._removeElements.bind(this) });
447
+ } else {
448
+ this.MBoverlay.hide();
449
+ this._removeElements();
450
+ }
451
+ $(this.MBcontent).setStyle({overflow: '', height: ''});
452
+ },
453
+
454
+ _removeElements: function () {
455
+ $(this.MBoverlay).remove();
456
+ $(this.MBwindow).remove();
457
+ if(Prototype.Browser.IE && !navigator.appVersion.match(/\b7.0\b/)) {
458
+ this._prepareIE("", ""); // If set to auto MSIE will show horizontal scrolling
459
+ window.scrollTo(this.initScrollX, this.initScrollY);
460
+ }
461
+
462
+ /* Replacing prefixes 'MB_' in IDs for the original content */
463
+ if(typeof this.content == 'object') {
464
+ if(this.content.id && this.content.id.match(/MB_/)) {
465
+ this.content.id = this.content.id.replace(/MB_/, "");
466
+ }
467
+ this.content.select('*[id]').each(function(el){ el.id = el.id.replace(/MB_/, ""); });
468
+ }
469
+ /* Initialized will be set to false */
470
+ this.initialized = false;
471
+ this.event("afterHide"); // Passing afterHide callback
472
+ this.setOptions(this._options); //Settings options object into intial state
473
+ },
474
+
475
+ _setWidth: function () { //Set size
476
+ $(this.MBwindow).setStyle({width: this.options.width + "px", height: this.options.height + "px"});
477
+ },
478
+
479
+ _setPosition: function () {
480
+ $(this.MBwindow).setStyle({left: Math.round((Element.getWidth(document.body) - Element.getWidth(this.MBwindow)) / 2 ) + "px"});
481
+ },
482
+
483
+ _setWidthAndPosition: function () {
484
+ $(this.MBwindow).setStyle({width: this.options.width + "px"});
485
+ this._setPosition();
486
+ },
487
+
488
+ _getScrollTop: function () { //From: http://www.quirksmode.org/js/doctypes.html
489
+ var theTop;
490
+ if (document.documentElement && document.documentElement.scrollTop)
491
+ theTop = document.documentElement.scrollTop;
492
+ else if (document.body)
493
+ theTop = document.body.scrollTop;
494
+ return theTop;
495
+ },
496
+ _prepareIE: function(height, overflow){
497
+ $$('html, body').invoke('setStyle', {width: height, height: height, overflow: overflow}); // IE requires width and height set to 100% and overflow hidden
498
+ $$("select").invoke('setStyle', {'visibility': overflow}); // Toggle visibility for all selects in the common document
499
+ },
500
+ event: function(eventName) {
501
+ if(this.options[eventName]) {
502
+ var returnValue = this.options[eventName](); // Executing callback
503
+ this.options[eventName] = null; // Removing callback after execution
504
+ if(returnValue != undefined)
505
+ return returnValue;
506
+ else
507
+ return true;
508
+ }
509
+ return true;
510
+ }
511
+ };
512
+
513
+ Object.extend(Modalbox, Modalbox.Methods);
514
+
515
+ if(Modalbox.overrideAlert) window.alert = Modalbox.alert;
516
+
517
+ Effect.ScaleBy = Class.create();
518
+ Object.extend(Object.extend(Effect.ScaleBy.prototype, Effect.Base.prototype), {
519
+ initialize: function(element, byWidth, byHeight, options) {
520
+ this.element = $(element)
521
+ var options = Object.extend({
522
+ scaleFromTop: true,
523
+ scaleMode: 'box', // 'box' or 'contents' or {} with provided values
524
+ scaleByWidth: byWidth,
525
+ scaleByHeight: byHeight
526
+ }, arguments[3] || {});
527
+ this.start(options);
528
+ },
529
+ setup: function() {
530
+ this.elementPositioning = this.element.getStyle('position');
531
+
532
+ this.originalTop = this.element.offsetTop;
533
+ this.originalLeft = this.element.offsetLeft;
534
+
535
+ this.dims = null;
536
+ if(this.options.scaleMode=='box')
537
+ this.dims = [this.element.offsetHeight, this.element.offsetWidth];
538
+ if(/^content/.test(this.options.scaleMode))
539
+ this.dims = [this.element.scrollHeight, this.element.scrollWidth];
540
+ if(!this.dims)
541
+ this.dims = [this.options.scaleMode.originalHeight,
542
+ this.options.scaleMode.originalWidth];
543
+
544
+ this.deltaY = this.options.scaleByHeight;
545
+ this.deltaX = this.options.scaleByWidth;
546
+ },
547
+ update: function(position) {
548
+ var currentHeight = this.dims[0] + (this.deltaY * position);
549
+ var currentWidth = this.dims[1] + (this.deltaX * position);
550
+
551
+ currentHeight = (currentHeight > 0) ? currentHeight : 0;
552
+ currentWidth = (currentWidth > 0) ? currentWidth : 0;
553
+
554
+ this.setDimensions(currentHeight, currentWidth);
555
+ },
556
+
557
+ setDimensions: function(height, width) {
558
+ var d = {};
559
+ d.width = width + 'px';
560
+ d.height = height + 'px';
561
+
562
+ var topd = Math.round((height - this.dims[0])/2);
563
+ var leftd = Math.round((width - this.dims[1])/2);
564
+ if(this.elementPositioning == 'absolute' || this.elementPositioning == 'fixed') {
565
+ if(!this.options.scaleFromTop) d.top = this.originalTop-topd + 'px';
566
+ d.left = this.originalLeft-leftd + 'px';
567
+ } else {
568
+ if(!this.options.scaleFromTop) d.top = -topd + 'px';
569
+ d.left = -leftd + 'px';
570
+ }
571
+ this.element.setStyle(d);
572
+ }
573
+ });
@@ -0,0 +1,95 @@
1
+ #MB_overlay {
2
+ position: absolute;
3
+ margin: auto;
4
+ top: 0; left: 0;
5
+ width: 100%; height: 100%;
6
+ z-index: 9999;
7
+ background-color: #000!important;
8
+ }
9
+ #MB_overlay[id] { position: fixed; }
10
+
11
+ #MB_window {
12
+ position: absolute;
13
+ top: 0;
14
+ border: 0 solid;
15
+ text-align: left;
16
+ z-index: 10000;
17
+ }
18
+ #MB_window[id] { position: fixed!important; }
19
+
20
+ #MB_frame {
21
+ position: relative;
22
+ background-color: #EFEFEF;
23
+ height: 100%;
24
+ }
25
+
26
+ #MB_header {
27
+ margin: 0;
28
+ padding: 0;
29
+ }
30
+
31
+ #MB_content {
32
+ padding: 6px .75em;
33
+ overflow: auto;
34
+ }
35
+
36
+ #MB_caption {
37
+ font: bold 100% "Lucida Grande", Arial, sans-serif;
38
+ text-shadow: #FFF 0 1px 0;
39
+ padding: .5em 2em .5em .75em;
40
+ margin: 0;
41
+ text-align: left;
42
+ }
43
+
44
+ #MB_close {
45
+ display: block;
46
+ position: absolute;
47
+ right: 5px; top: 4px;
48
+ padding: 2px 3px;
49
+ font-weight: bold;
50
+ text-decoration: none;
51
+ font-size: 13px;
52
+ }
53
+ #MB_close:hover {
54
+ background: transparent;
55
+ }
56
+
57
+ #MB_loading {
58
+ padding: 1.5em;
59
+ text-indent: -10000px;
60
+ background: transparent url(spinner.gif) 50% 0 no-repeat;
61
+ }
62
+
63
+ /* Color scheme */
64
+ #MB_frame {
65
+ padding-bottom: 7px;
66
+ -webkit-border-radius: 7px;
67
+ -moz-border-radius: 7px;
68
+ border-radius: 7px;
69
+ }
70
+ #MB_window {
71
+ background-color: #EFEFEF;
72
+ color: #000;
73
+ -webkit-box-shadow: 0 8px 64px #000;
74
+ -moz-box-shadow: 0 0 64px #000;
75
+ box-shadow: 0 0 64px #000;
76
+
77
+ -webkit-border-radius: 7px;
78
+ -moz-border-radius: 7px;
79
+ border-radius: 7px;
80
+ }
81
+ #MB_content { border-top: 1px solid #F9F9F9; }
82
+ #MB_header {
83
+ background-color: #DDD;
84
+ border-bottom: 1px solid #CCC;
85
+ }
86
+ #MB_caption { color: #000 }
87
+ #MB_close { color: #777 }
88
+ #MB_close:hover { color: #000 }
89
+
90
+
91
+ /* Alert message */
92
+ .MB_alert {
93
+ margin: 10px 0;
94
+ text-align: center;
95
+ }
@@ -0,0 +1,34 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "image_picker"
3
+ s.version = "0.5"
4
+ s.date = "2008-10-23"
5
+ s.summary = "A simple, customizable image browser."
6
+ s.email = "pelargir@gmail.com"
7
+ s.homepage = "http://github.com/pelargir/image_picker"
8
+ s.description = "A simple, customizable image browser."
9
+ s.has_rdoc = true
10
+ s.authors = ["Matthew Bass"]
11
+ s.files = [
12
+ "CHANGELOG",
13
+ "deleted_files",
14
+ "files/public/images/no_image.jpg",
15
+ "files/public/images/spinner.gif",
16
+ "files/public/javascripts/image_picker.js",
17
+ "files/public/javascripts/modalbox.js",
18
+ "files/public/stylesheets/modalbox.css",
19
+ "image_picker.gemspec",
20
+ "init.rb",
21
+ "install.rb",
22
+ "lib/image_picker.rb",
23
+ "lib/image_picker/asset_copier.rb",
24
+ "lib/image_picker/controller.rb",
25
+ "lib/image_picker/link_renderer.rb",
26
+ "MIT-LICENSE",
27
+ "Rakefile",
28
+ "README",
29
+ "tasks/asset_copier.rake",
30
+ "templates/open_picker.html.erb"
31
+ ]
32
+ s.rdoc_options = ["--main", "README"]
33
+ s.extra_rdoc_files = ["README"]
34
+ end
data/init.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'image_picker'
2
+ require 'image_picker/controller'
3
+ require 'image_picker/link_renderer'
4
+ require 'image_picker/asset_copier'
5
+
6
+ if RAILS_ENV == "development"
7
+ ImagePicker::AssetCopier.copy "image_picker"
8
+ elsif RAILS_ENV == "production"
9
+ ImagePicker::AssetCopier.warn "image_picker"
10
+ end
@@ -0,0 +1,20 @@
1
+ # Generated by the asset_copier plugin
2
+ # http://github.com/pelargir/asset_copier
3
+
4
+ require 'rake'
5
+
6
+ begin
7
+ puts "============================================================="
8
+ puts "Attempting to install required files into your application..."
9
+ puts "============================================================="
10
+ RAKE_FILE = File.expand_path(File.join(File.dirname(__FILE__), 'tasks', 'asset_copier.rake'))
11
+ load RAKE_FILE
12
+
13
+ Rake::Task['image_picker:install'].invoke
14
+ puts "=========================================================="
15
+ puts "Success!"
16
+ puts "=========================================================="
17
+ rescue Exception => ex
18
+ puts "FAILED TO INSTALL REQUIRED FILES. PLEASE RUN rake image_picker:install."
19
+ puts "EXCEPTION: #{ex}"
20
+ end
@@ -0,0 +1,11 @@
1
+ module ImagePicker
2
+ def image_picker(field)
3
+ url = url_for :action => "open_picker"
4
+ html = stylesheet_link_tag "modalbox"
5
+ html += javascript_include_tag :defaults, "image_picker", "modalbox"
6
+ html += link_to_function image_tag("no_image.jpg", :id => field), "ImagePicker.open_picker({field:'#{field}', url:'#{url}'})"
7
+ html += hidden_field_tag "#{field}_id"
8
+ end
9
+ end
10
+
11
+ ActionView::Base.send :include, ImagePicker
@@ -0,0 +1,90 @@
1
+ # Generated by the asset_copier plugin
2
+ # http://github.com/pelargir/asset_copier
3
+
4
+ require 'find'
5
+ require 'digest/md5'
6
+
7
+ module ImagePicker
8
+ class AssetCopier
9
+ @source = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'files'))
10
+ @destination = RAILS_ROOT
11
+ @deleted_files = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'deleted_files'))
12
+ class << self
13
+ attr_accessor :source, :destination, :deleted_files
14
+ end
15
+
16
+ def self.copy(plugin_name)
17
+ begin
18
+ each_path do |path, dest_path, short_path|
19
+ if File.directory?(path)
20
+ unless File.exists?(dest_path)
21
+ FileUtils.mkdir_p(dest_path)
22
+ log "Creating directory #{short_path} for #{plugin_name}"
23
+ end
24
+ elsif !compare(path, dest_path)
25
+ FileUtils.cp(path, dest_path)
26
+ log "Copying #{short_path} from #{plugin_name}"
27
+ end
28
+ end
29
+ rescue Exception => e
30
+ log "Error trying to copy files: #{e.inspect}"
31
+ raise e
32
+ end
33
+ print_deletion_warnings(plugin_name)
34
+ end
35
+
36
+ def self.warn(plugin_name)
37
+ each_path do |path, dest_path, short_path|
38
+ next if File.directory?(path)
39
+ reinstall = false
40
+ if File.exists?(dest_path)
41
+ unless compare(path, dest_path)
42
+ log "WARNING: #{short_path} is out of date and needs to be reinstalled"
43
+ reinstall = true
44
+ end
45
+ else
46
+ reinstall = true
47
+ log "WARNING: #{short_path} is missing and needs to be installed"
48
+ end
49
+ log "WARNING: Please run rake #{plugin_name}:install" if reinstall
50
+ end
51
+ print_deletion_warnings(plugin_name)
52
+ end
53
+
54
+ def self.compare(file1, file2)
55
+ File.exists?(file1) && File.exists?(file2) &&
56
+ Digest::MD5.hexdigest(File.read(file1)) == Digest::MD5.hexdigest(File.read(file2))
57
+ end
58
+
59
+ def self.print_deletion_warnings(plugin_name)
60
+ File.open(deleted_files, "r") do |f|
61
+ f.readlines.reject { |l| l =~ /^#/ || l.strip.blank? }.each do |l|
62
+ log "WARNING: #{l} is no longer required by the #{plugin_name} plugin " <<
63
+ "and can can be safely removed" if File.exists?(l)
64
+ end
65
+ end
66
+ end
67
+
68
+ def self.paths
69
+ returning [] do |paths|
70
+ Find.find(source) do |path|
71
+ Find.prune if path =~ /\/\..+/
72
+ Find.prune if path =~ /(CVS|.svn|.git)/
73
+ paths << path
74
+ end
75
+ end
76
+ end
77
+
78
+ def self.each_path
79
+ paths.each do |path|
80
+ dest_path = path.gsub(source, destination)
81
+ short_path = dest_path.gsub("#{destination}/", "")
82
+ yield path, dest_path, short_path
83
+ end
84
+ end
85
+
86
+ def self.log(msg)
87
+ puts msg
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,28 @@
1
+ module ImagePicker
2
+ module Controller
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def image_picker(options={})
9
+ define_method "open_picker" do
10
+ model = options.delete(:model)
11
+ @field = params[:field]
12
+ @images = defined?(WillPaginate) ? model.paginate(options.merge(:page => params[:page])) : model.all(options)
13
+ render :file => "#{RAILS_ROOT}/vendor/plugins/image_picker/templates/open_picker.html.erb"
14
+ end
15
+
16
+ define_method "pick" do
17
+ image = Image.find(params[:id])
18
+ field = params[:field]
19
+ render :update do |page|
20
+ page.call "parent.ImagePicker.pick", field, image.id, image.title, image.thumbnail
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ ActionController::Base.send :include, ImagePicker::Controller
@@ -0,0 +1,9 @@
1
+ if defined?(WillPaginate)
2
+ module ImagePicker
3
+ class LinkRenderer < WillPaginate::LinkRenderer
4
+ def page_link(page, text, attributes = {})
5
+ @template.link_to_function text, "ImagePicker.open_picker({field:$('target_field').value, page:'#{page}', terms:$('terms').value});"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # Generated by the asset_copier plugin
2
+ # http://github.com/pelargir/asset_copier
3
+
4
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/image_picker/asset_copier')
5
+
6
+ namespace :image_picker do
7
+ desc "Install files required by image_picker"
8
+ task :install do
9
+ ImagePicker::AssetCopier.copy "image_picker"
10
+ end
11
+ end
@@ -0,0 +1,38 @@
1
+ <div style="width: 500px">
2
+ <%= hidden_field_tag :target_field, @field %>
3
+ <%= hidden_field_tag :default_image, "/images/no_image.jpg" %>
4
+
5
+ <div style="float:right">
6
+ <%= link_to_function "clear image", "ImagePicker.clear($('target_field').value, $('default_image').value);", :id => "clear" %>
7
+ </div>
8
+
9
+ <% if defined?(WillPaginate) -%>
10
+ <div style="text-align:center; clear:both;">
11
+ <%= will_paginate @images, :renderer => ImagePicker::LinkRenderer %>
12
+ </div>
13
+ <% end -%>
14
+
15
+ <% if @images.any? -%>
16
+ <table border="0", cellpadding="10" cellspacing="5">
17
+ <% @images.in_groups_of(3) do |e| -%>
18
+ <tr>
19
+ <% e.compact.each do |i| -%>
20
+ <td style="text-align:center" valign="top">
21
+ <%= link_to_remote image_tag(i.thumbnail, :alt => i.title), :url => {:action => "pick", :field => @field, :id => i}, :method => :post %>
22
+ <br/>
23
+ <strong><%= i.title %></strong>
24
+ <% if i.respond_to?(:description) -%>
25
+ <br/>
26
+ <%= i.description %>
27
+ <% end -%>
28
+ </td>
29
+ <% end -%>
30
+ </tr>
31
+ <% end -%>
32
+ </table>
33
+ <% else -%>
34
+ <div style="margin:10px; text-align:center; font-style:italic;">
35
+ Nothing found.
36
+ </div>
37
+ <% end -%>
38
+ </div>
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: image_picker
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.5"
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Bass
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-23 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A simple, customizable image browser.
17
+ email: pelargir@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - CHANGELOG
26
+ - deleted_files
27
+ - files/public/images/no_image.jpg
28
+ - files/public/images/spinner.gif
29
+ - files/public/javascripts/image_picker.js
30
+ - files/public/javascripts/modalbox.js
31
+ - files/public/stylesheets/modalbox.css
32
+ - image_picker.gemspec
33
+ - init.rb
34
+ - install.rb
35
+ - lib/image_picker.rb
36
+ - lib/image_picker/asset_copier.rb
37
+ - lib/image_picker/controller.rb
38
+ - lib/image_picker/link_renderer.rb
39
+ - MIT-LICENSE
40
+ - Rakefile
41
+ - README
42
+ - tasks/asset_copier.rake
43
+ - templates/open_picker.html.erb
44
+ has_rdoc: true
45
+ homepage: http://github.com/pelargir/image_picker
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --main
51
+ - README
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.4
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: A simple, customizable image browser.
73
+ test_files: []
74
+