puffer 0.0.12 → 0.0.13

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.
@@ -5,9 +5,12 @@ module Puffer
5
5
  include Puffer::Controller::Mutate
6
6
  include Puffer::Controller::Helpers
7
7
  include Puffer::Controller::Dsl
8
+ include Puffer::Controller::Mapping
8
9
  include Puffer::Controller::Config
9
10
  include Puffer::Controller::Generated
10
11
 
12
+ define_fields :index, :show, :form, :create, :update
13
+
11
14
  respond_to :html, :js
12
15
 
13
16
  def index
@@ -5,14 +5,6 @@ module Puffer
5
5
  def self.included base
6
6
  base.class_eval do
7
7
  extend ClassMethods
8
- include ActionMethods
9
- extend ActionMethods
10
-
11
- %w(index show form create update).each do |action|
12
- class_attribute "_#{action}_fields"
13
- send "_#{action}_fields=", Puffer::Fields.new unless send("_#{action}_fields?")
14
- helper_method "#{action}_fields"
15
- end
16
8
 
17
9
  class_attribute :_members
18
10
  self._members = Puffer::Controller::Actions.new
@@ -37,11 +29,27 @@ module Puffer
37
29
  block.bind(_collections).call if block_given?
38
30
  end
39
31
 
40
- %w(index show form create update).each do |action|
41
- define_method action do |&block|
42
- @_fields = send("_#{action}_fields=", Puffer::Fields.new)
43
- block.call if block
44
- remove_instance_variable :@_fields
32
+ def define_fields *actions
33
+ actions.each do |action|
34
+ class_attribute "_#{action}_fields"
35
+ send "_#{action}_fields=", Puffer::Fields.new unless send("_#{action}_fields?")
36
+ helper_method "#{action}_fields"
37
+
38
+ self.class.instance_eval do
39
+ define_method action do |&block|
40
+ @_fields = send("_#{action}_fields=", Puffer::Fields.new)
41
+ block.call if block
42
+ remove_instance_variable :@_fields
43
+ end
44
+
45
+ define_method "#{action}_fields" do
46
+ send "_#{action}_fields"
47
+ end
48
+ end
49
+
50
+ define_method "#{action}_fields" do
51
+ self.class.send "#{action}_fields"
52
+ end
45
53
  end
46
54
  end
47
55
 
@@ -53,30 +61,6 @@ module Puffer
53
61
 
54
62
  end
55
63
 
56
- module ActionMethods
57
-
58
- def index_fields
59
- _index_fields
60
- end
61
-
62
- def show_fields
63
- _show_fields.presence || _index_fields
64
- end
65
-
66
- def form_fields
67
- _form_fields
68
- end
69
-
70
- def create_fields
71
- _create_fields.presence || _form_fields
72
- end
73
-
74
- def update_fields
75
- _update_fields.presence || _form_fields
76
- end
77
-
78
- end
79
-
80
64
  end
81
65
  end
82
66
  end
@@ -23,7 +23,7 @@ module Puffer
23
23
  end
24
24
 
25
25
  collection do
26
- get "associated_#{field}_choosing"
26
+ get "associated_#{field}_choosing", :display => false
27
27
  end
28
28
  end
29
29
 
@@ -44,8 +44,8 @@ module Puffer
44
44
  end
45
45
 
46
46
  collection do
47
- get "associated_#{field}"
48
- get "associated_#{field}_choosing"
47
+ get "associated_#{field}", :display => false
48
+ get "associated_#{field}_choosing", :display => false
49
49
  end
50
50
  end
51
51
 
@@ -18,7 +18,7 @@ module Puffer
18
18
  end
19
19
 
20
20
  def model
21
- @model ||= model_name.classify.constantize rescue nil
21
+ @model ||= model_name.classify.constantize
22
22
  end
23
23
 
24
24
  end
@@ -0,0 +1,37 @@
1
+ module Puffer
2
+ module Controller
3
+ module Mapping
4
+
5
+ def self.included base
6
+ base.class_eval do
7
+ extend ClassMethods
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ def index_fields
14
+ _index_fields
15
+ end
16
+
17
+ def show_fields
18
+ _show_fields.presence || _index_fields
19
+ end
20
+
21
+ def form_fields
22
+ _form_fields
23
+ end
24
+
25
+ def create_fields
26
+ _create_fields.presence || _form_fields
27
+ end
28
+
29
+ def update_fields
30
+ _update_fields.presence || _form_fields
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -24,6 +24,14 @@ module Puffer
24
24
  self.view_paths = temp
25
25
  end
26
26
 
27
+ def view_paths_fallbacks_prepend *args
28
+ view_paths_fallbacks args, view_paths._fallbacks
29
+ end
30
+
31
+ def view_paths_fallbacks_append *args
32
+ view_paths_fallbacks view_paths._fallbacks, args
33
+ end
34
+
27
35
  def puffer?; true; end
28
36
 
29
37
  end
@@ -2,7 +2,12 @@ module Puffer
2
2
  module Extensions
3
3
  module FormBuilder
4
4
 
5
- def puffer_field field
5
+ def puffer_field *args
6
+ field = if args.first.is_a? Puffer::Fields::Field
7
+ args.first
8
+ else
9
+ Puffer::Fields::Field.new object.class, *args
10
+ end
6
11
  input = Puffer::Inputs.map_field field
7
12
  input.new(self, @template, field).render
8
13
  end
@@ -47,7 +47,7 @@ module Puffer
47
47
  end
48
48
 
49
49
  def input_options
50
- {}
50
+ options[:html] || {}
51
51
  end
52
52
 
53
53
  def model
@@ -94,9 +94,12 @@ module Puffer
94
94
  end
95
95
  end
96
96
 
97
+ def collection_scope
98
+ parent ? parent.member.send(model_name.pluralize) : model
99
+ end
100
+
97
101
  def collection
98
- scope = parent ? parent.member.send(model_name.pluralize) : model
99
- scope.includes(includes).where(searches(params[:search])).order(order).paginate :page => params[:page]
102
+ collection_scope.includes(includes).where(searches(params[:search])).order(order).paginate :page => params[:page]
100
103
  end
101
104
 
102
105
  def member
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{puffer}
8
- s.version = "0.0.12"
8
+ s.version = "0.0.13"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["pyromaniac"]
12
- s.date = %q{2011-02-05}
12
+ s.date = %q{2011-02-07}
13
13
  s.description = %q{In Soviet Russia puffer admins you}
14
14
  s.email = %q{kinwizard@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -61,6 +61,7 @@ Gem::Specification.new do |s|
61
61
  "lib/puffer/controller/dsl.rb",
62
62
  "lib/puffer/controller/generated.rb",
63
63
  "lib/puffer/controller/helpers.rb",
64
+ "lib/puffer/controller/mapping.rb",
64
65
  "lib/puffer/controller/mutate.rb",
65
66
  "lib/puffer/engine.rb",
66
67
  "lib/puffer/extensions/activerecord.rb",
@@ -1,5 +1,5 @@
1
1
  var association_done = function(event) {
2
- current = this.first('li.current')
2
+ current = this.first('li.current');
3
3
  this.input.next('input[type=hidden]').value(current.get('data-id'));
4
4
  this.input.value(current.find('.title').first().html().stripTags()).disable();
5
5
  }
@@ -4,15 +4,618 @@
4
4
  *
5
5
  * Copyright (C) 2010 Nikolay Nemshilov
6
6
  */
7
- var Autocompleter=RightJS.Autocompleter=function(h,c){function l(a,b,d,e){if(c.Fx)if(d===undefined){d=a.options.fxName;if(e===undefined){e={duration:a.options.fxDuration,onFinish:c(a.fire).bind(a,b)};if(b==="hide")e.duration=(c.Fx.Durations[e.duration]||e.duration)/2}}if(!c.Fx||!d)a.fire(b);return a.$super(d,e)}function m(a,b,d){var e=(this.reAnchor||(this.reAnchor=new c.Element("div",{"class":"rui-re-anchor"})).insert(this)).insertTo(a,"after").position(),f=a.dimensions(),i=parseInt(a.getStyle("borderTopWidth")),
8
- j=parseInt(a.getStyle("borderLeftWidth")),q=parseInt(a.getStyle("borderRightWidth")),r=parseInt(a.getStyle("borderBottomWidth"));a=f.top-e.y+i;e=f.left-e.x+j;j=f.width-j-q;f=f.height-i-r;this.setStyle("visibility:hidden").show(null);if(b==="right")e+=j-this.size().x;else a+=f;this.moveTo(e,a);if(d)b==="left"||b==="right"?this.setHeight(f):this.setWidth(j);this.setStyle("visibility:visible").hide(null)}var g=c,n=c.$,s=c.$w,t=c.Xhr,o=c.RegExp,u=c.isArray,p=new c.Class(c.Element,{initialize:function(a){this.$super("div",
9
- {"class":"rui-spinner"});this.dots=[];for(var b=0;b<(a||4);b++)this.dots.push(new c.Element("div"));this.dots[0].addClass("glowing");this.insert(this.dots);c(this.shift).bind(this).periodical(300)},shift:function(){if(this.visible()){var a=this.dots.pop();this.dots.unshift(a);this.insert(a,"top")}}}),k=new (function(a,b){if(!b){b=a;a="DIV"}var d=new c.Class(c.Element.Wrappers[a]||c.Element,{initialize:function(e,f){this.key=e;var i=[{"class":"rui-"+e}];this instanceof c.Input||this instanceof c.Form||
10
- i.unshift(a);this.$super.apply(this,i);if(c.isString(f))f=c.$(f);if(f instanceof c.Element){this._=f._;if("$listeners"in f)f.$listeners=f.$listeners;f={}}this.setOptions(f,this);return c.Wrapper.Cache[c.$uid(this._)]=this},setOptions:function(e,f){f=f||this;c.Options.setOptions.call(this,c.Object.merge(e,eval("("+(f.get("data-"+this.key)||"{}")+")")));return this}});d=new c.Class(d,b);c.Observer.createShortcuts(d.prototype,d.EVENTS||[]);return d})("UL",{include:{show:function(a,b){this.constructor.current=
11
- this;return l(this,"show",a,b)},hide:function(a,b){this.constructor.current=null;return l(this,"show",a,b)},showAt:function(a,b,d){this.hide(null).shownAt=a=c.$(a);m.call(this,a,b,d);return this.show()},toggleAt:function(a,b,d){return this.hidden()?this.showAt(a,b,d):this.hide()}},extend:{version:"2.2.0",EVENTS:s("show hide update load select done"),Options:{url:h.location.href,param:"search",method:"get",minLength:1,threshold:200,cache:true,local:null,fxName:"slide",fxDuration:"short",spinner:"native",
12
- cssRule:"input[data-autocompleter]"}},initialize:function(a,b){this.input=n(a);this.$super("autocompleter",b).addClass("rui-dd-menu").onMousedown(this.clicked);this.input.autocompleter=this},destroy:function(){delete this.input.autocompleter;return this},prev:function(){return this.pick("prev")},next:function(){return this.pick("next")},done:function(a){if(a=a||this.first("li.current")){a.radioClass("current");this.input.setValue(g(a.html()).stripTags());this.fire("done")}return this.hide()},setOptions:function(a){this.$super(a,
13
- this.input);a=this.options;g(a.url).includes("%{search}")||(a.url+=(g(a.url).includes("?")?"&":"?")+a.param+"=%{search}")},pick:function(a){var b=this.children(),d=b.first("hasClass","current"),e=b.indexOf(d);if(a=="prev")d=e<1?b.last():b[e<0?0:e-1];else if(a=="next")d=e<0||e==b.length-1?b.first():b[e+1];return this.fire("select",{item:d.radioClass("current")})},clicked:function(a){this.done(a.stop().find("li"))},keypressed:function(){if(this.input.value().length>=this.options.minLength){this.timeout&&
14
- this.timeout.cancel();this.timeout=g(this.trigger).bind(this).delay(this.options.threshold)}else return this.hide()},trigger:function(){this.timeout=null;this.cache=this.cache||{};var a=this.input.value(),b=this.options;if(a.length<b.minLength)return this.hide();if(this.cache[a])this.suggest(this.cache[a],a);else if(u(b.local))this.suggest(this.findLocal(a),a);else this.request=t.load(b.url.replace("%{search}",encodeURIComponent(a)),{method:b.method,spinner:this.getSpinner(),onComplete:g(function(d){this.fire("load").suggest(d.text,
15
- a)}).bind(this)})},suggest:function(a,b){if(this.options.cache)this.cache[b]=a;if(g(a).blank())this.hide();else{this.update(a.replace(/<ul[^>]*>|<\/ul>/im,""));this.fire("update");if(!this._connected||this.hidden()){this.showAt(this.input,"bottom","resize");this._connected=true}}return this},findLocal:function(a){var b=new o("("+o.escape(a)+")","ig");return g(this.options.local).map(function(d){if(d.match(b))return"<li>"+d.replace(b,"<strong>$1</strong>")+"</li>"}).compact().join("")},getSpinner:function(){var a=
16
- this.options,b=a.spinner;if(b=="native"){b=a.spinner=(new p(3)).insertTo(this);b.addClass("rui-autocompleter-spinner")}b instanceof p&&m.call(b,this.input,"right","resize");return b}});n(h).on({focus:function(a){if((a=a.target)&&a instanceof c.Element&&(a.autocompleter||a.match(k.Options.cssRule)))a.autocompleter||new k(a)},blur:function(a){(a=a.target?a.target.autocompleter:null)&&a.visible()&&a.hide()},keydown:function(a){var b=a.target?a.target.autocompleter:null;if(b&&b.visible()){var d={27:"hide",
17
- 38:"prev",40:"next",13:"done"}[a.keyCode];if(d){a.stop();b[d]()}}},keyup:function(a){var b=a.target?a.target.autocompleter:null;b&&!g([9,27,37,38,39,40,13]).include(a.keyCode)&&b.keypressed(a)}});(function(){var a=h.createElement("style"),b=h.createTextNode(" *.rui-dd-menu, *.rui-dd-menu li{margin:0;padding:0;border:none;background:none;list-style:none;font-weight:normal;float:none} *.rui-dd-menu{display:none;position:absolute;z-index:9999;background:white;border:1px solid #BBB;border-radius:.2em;-moz-border-radius:.2em;-webkit-border-radius:.2em;box-shadow:#DDD .2em .2em .4em;-moz-box-shadow:#DDD .2em .2em .4em;-webkit-box-shadow:#DDD .2em .2em .4em} *.rui-dd-menu li{padding:.2em .4em;border-top:none;border-bottom:none;cursor:pointer} *.rui-dd-menu li.current{background:#DDD} *.rui-dd-menu li:hover{background:#EEE}dl.rui-dd-menu dt{padding:.3em .5em;cursor:default;font-weight:bold;font-style:italic;color:#444;background:#EEE}dl.rui-dd-menu dd li{padding-left:1.5em}div.rui-spinner,div.rui-spinner div{margin:0;padding:0;border:none;background:none;list-style:none;font-weight:normal;float:none;display:inline-block; *display:inline; *zoom:1;border-radius:.12em;-moz-border-radius:.12em;-webkit-border-radius:.12em}div.rui-spinner{text-align:center;white-space:nowrap;background:#EEE;border:1px solid #DDD;height:1.2em;padding:0 .2em}div.rui-spinner div{width:.4em;height:70%;background:#BBB;margin-left:1px}div.rui-spinner div:first-child{margin-left:0}div.rui-spinner div.glowing{background:#777}div.rui-re-anchor{margin:0;padding:0;background:none;border:none;float:none;display:inline;position:absolute;z-index:9999}.rui-autocompleter{border-top-color:#DDD !important;border-top-left-radius:0 !important;border-top-right-radius:0 !important;-moz-border-radius-topleft:0 !important;-moz-border-radius-topright:0 !important;-webkit-border-top-left-radius:0 !important;-webkit-border-top-right-radius:0 !important}.rui-autocompleter-spinner{border:none !important;background:none !important;position:absolute;z-index:9999}.rui-autocompleter-spinner div{margin-top:.2em !important; *margin-top:0.1em !important}");
18
- a.type="text/css";if(a.styleSheet)a.styleSheet.cssText=b.nodeValue;else a.appendChild(b);h.getElementsByTagName("head")[0].appendChild(a)})();return k}(document,RightJS);
7
+ var Autocompleter = RightJS.Autocompleter = (function(document, RightJS) {
8
+ /**
9
+ * This module defines the basic widgets constructor
10
+ * it creates an abstract proxy with the common functionality
11
+ * which then we reuse and override in the actual widgets
12
+ *
13
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
14
+ */
15
+
16
+ /**
17
+ * Autocompleter initializer
18
+ *
19
+ * Copyright (C) 2010 Nikolay Nemshilov
20
+ */
21
+ var R = RightJS,
22
+ $ = RightJS.$,
23
+ $w = RightJS.$w,
24
+ $E = RightJS.$E,
25
+ Xhr = RightJS.Xhr,
26
+ RegExp = RightJS.RegExp,
27
+ isArray = RightJS.isArray;
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+ /**
37
+ * The widget units constructor
38
+ *
39
+ * @param String tag-name or Object methods
40
+ * @param Object methods
41
+ * @return Widget wrapper
42
+ */
43
+ function Widget(tag_name, methods) {
44
+ if (!methods) {
45
+ methods = tag_name;
46
+ tag_name = 'DIV';
47
+ }
48
+
49
+ /**
50
+ * An Abstract Widget Unit
51
+ *
52
+ * Copyright (C) 2010 Nikolay Nemshilov
53
+ */
54
+ var AbstractWidget = new RightJS.Class(RightJS.Element.Wrappers[tag_name] || RightJS.Element, {
55
+ /**
56
+ * The common constructor
57
+ *
58
+ * @param Object options
59
+ * @param String optional tag name
60
+ * @return void
61
+ */
62
+ initialize: function(key, options) {
63
+ this.key = key;
64
+ var args = [{'class': 'rui-' + key}];
65
+
66
+ // those two have different constructors
67
+ if (!(this instanceof RightJS.Input || this instanceof RightJS.Form)) {
68
+ args.unshift(tag_name);
69
+ }
70
+ this.$super.apply(this, args);
71
+
72
+ if (RightJS.isString(options)) {
73
+ options = RightJS.$(options);
74
+ }
75
+
76
+ // if the options is another element then
77
+ // try to dynamically rewrap it with our widget
78
+ if (options instanceof RightJS.Element) {
79
+ this._ = options._;
80
+ if ('$listeners' in options) {
81
+ options.$listeners = options.$listeners;
82
+ }
83
+ options = {};
84
+ }
85
+ this.setOptions(options, this);
86
+
87
+ return (RightJS.Wrapper.Cache[RightJS.$uid(this._)] = this);
88
+ },
89
+
90
+ // protected
91
+
92
+ /**
93
+ * Catches the options
94
+ *
95
+ * @param Object user-options
96
+ * @param Element element with contextual options
97
+ * @return void
98
+ */
99
+ setOptions: function(options, element) {
100
+ element = element || this;
101
+ RightJS.Options.setOptions.call(this,
102
+ RightJS.Object.merge(options, eval("("+(
103
+ element.get('data-'+ this.key) || '{}'
104
+ )+")"))
105
+ );
106
+ return this;
107
+ }
108
+ });
109
+
110
+ /**
111
+ * Creating the actual widget class
112
+ *
113
+ */
114
+ var Klass = new RightJS.Class(AbstractWidget, methods);
115
+
116
+ // creating the widget related shortcuts
117
+ RightJS.Observer.createShortcuts(Klass.prototype, Klass.EVENTS || []);
118
+
119
+ return Klass;
120
+ }
121
+
122
+
123
+ /**
124
+ * A shared module to create textual spinners
125
+ *
126
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
127
+ */
128
+ var Spinner = new RightJS.Class(RightJS.Element, {
129
+ /**
130
+ * Constructor
131
+ *
132
+ * @param Number optional spinner size (4 by default)
133
+ * @return void
134
+ */
135
+ initialize: function(size) {
136
+ this.$super('div', {'class': 'rui-spinner'});
137
+ this.dots = [];
138
+
139
+ for (var i=0; i < (size || 4); i++) {
140
+ this.dots.push(new RightJS.Element('div'));
141
+ }
142
+
143
+ this.dots[0].addClass('glowing');
144
+ this.insert(this.dots);
145
+ RightJS(this.shift).bind(this).periodical(300);
146
+ },
147
+
148
+ /**
149
+ * Shifts the spinner elements
150
+ *
151
+ * @return void
152
+ */
153
+ shift: function() {
154
+ if (this.visible()) {
155
+ var dot = this.dots.pop();
156
+ this.dots.unshift(dot);
157
+ this.insert(dot, 'top');
158
+ }
159
+ }
160
+ });
161
+
162
+
163
+ /**
164
+ * A shared module that toggles a widget visibility status
165
+ * in a uniformed way according to the options settings
166
+ *
167
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
168
+ */
169
+ var Toggler = {
170
+ /**
171
+ * Shows the element
172
+ *
173
+ * @param String fx-name
174
+ * @param Object fx-options
175
+ * @return Element this
176
+ */
177
+ show: function(fx_name, fx_options) {
178
+ this.constructor.current = this;
179
+ return Toggler_toggle(this, 'show', fx_name, fx_options);
180
+ },
181
+
182
+ /**
183
+ * Hides the element
184
+ *
185
+ * @param String fx-name
186
+ * @param Object fx-options
187
+ * @return Element this
188
+ */
189
+ hide: function(fx_name, fx_options) {
190
+ this.constructor.current = null;
191
+ return Toggler_toggle(this, 'show', fx_name, fx_options);
192
+ },
193
+
194
+ /**
195
+ * Toggles the widget at the given element
196
+ *
197
+ * @param Element the related element
198
+ * @param String position right/bottom (bottom is the default)
199
+ * @param Boolean marker if the element should be resized to the element size
200
+ * @return Widget this
201
+ */
202
+ showAt: function(element, where, resize) {
203
+ this.hide(null).shownAt = element = RightJS.$(element);
204
+
205
+ // moves this element at the given one
206
+ Toggler_re_position.call(this, element, where, resize);
207
+
208
+ return this.show();
209
+ },
210
+
211
+ /**
212
+ * Toggles the widget at the given element
213
+ *
214
+ * @param Element the related element
215
+ * @param String position top/left/right/bottom (bottom is the default)
216
+ * @param Boolean marker if the element should be resized to the element size
217
+ * @return Widget this
218
+ */
219
+ toggleAt: function(element, where, resize) {
220
+ return this.hidden() ? this.showAt(element, where, resize) : this.hide();
221
+ }
222
+ };
223
+
224
+
225
+ /**
226
+ * toggles the element's state according to the current settings
227
+ *
228
+ * @param event String 'show' or 'hide' the event name
229
+ * @param String an optional fx-name
230
+ * @param Object an optional fx-options hash
231
+ * @return void
232
+ */
233
+ function Toggler_toggle(element, event, fx_name, fx_options) {
234
+ if (RightJS.Fx) {
235
+ if (fx_name === undefined) {
236
+ fx_name = element.options.fxName;
237
+
238
+ if (fx_options === undefined) {
239
+ fx_options = {
240
+ duration: element.options.fxDuration,
241
+ onFinish: RightJS(element.fire).bind(element, event)
242
+ };
243
+
244
+ // hide on double time
245
+ if (event === 'hide') {
246
+ fx_options.duration = (RightJS.Fx.Durations[fx_options.duration] ||
247
+ fx_options.duration) / 2;
248
+ }
249
+ }
250
+ }
251
+ }
252
+
253
+ // manually trigger the event if no fx were specified
254
+ if (!RightJS.Fx || !fx_name) { element.fire(event); }
255
+
256
+ return element.$super(fx_name, fx_options);
257
+ }
258
+
259
+ /**
260
+ * Relatively positions the current element
261
+ * against the specified one
262
+ *
263
+ * NOTE: this function is called in a context
264
+ * of another element
265
+ *
266
+ * @param Element the target element
267
+ * @param String position 'right' or 'bottom'
268
+ * @param Boolean if `true` then the element size will be adjusted
269
+ * @return void
270
+ */
271
+ function Toggler_re_position(element, where, resize) {
272
+ var anchor = this.reAnchor || (this.reAnchor =
273
+ new RightJS.Element('div', {'class': 'rui-re-anchor'}))
274
+ .insert(this),
275
+
276
+ pos = anchor.insertTo(element, 'after').position(),
277
+ dims = element.dimensions(), target = this,
278
+
279
+ border_top = parseInt(element.getStyle('borderTopWidth')),
280
+ border_left = parseInt(element.getStyle('borderLeftWidth')),
281
+ border_right = parseInt(element.getStyle('borderRightWidth')),
282
+ border_bottom = parseInt(element.getStyle('borderBottomWidth')),
283
+
284
+ top = dims.top - pos.y + border_top,
285
+ left = dims.left - pos.x + border_left,
286
+ width = dims.width - border_left - border_right,
287
+ height = dims.height - border_top - border_bottom;
288
+
289
+ // making the element to appear so we could read it's sizes
290
+ target.setStyle('visibility:hidden').show(null);
291
+
292
+ if (where === 'right') {
293
+ left += width - target.size().x;
294
+ } else { // bottom
295
+ top += height;
296
+ }
297
+
298
+ target.moveTo(left, top);
299
+
300
+ if (resize) {
301
+ if (where === 'left' || where === 'right') {
302
+ target.setHeight(height);
303
+ } else {
304
+ target.setWidth(width);
305
+ }
306
+ }
307
+
308
+ // rolling the invisibility back
309
+ target.setStyle('visibility:visible').hide(null);
310
+ }
311
+
312
+ /**
313
+ * The RightJS UI Autocompleter unit base class
314
+ *
315
+ * Copyright (C) 2009-2011 Nikolay Nemshilov
316
+ */
317
+ var Autocompleter = new Widget('UL', {
318
+ include: Toggler,
319
+
320
+ extend: {
321
+ version: '2.2.0',
322
+
323
+ EVENTS: $w('show hide update load select done'),
324
+
325
+ Options: {
326
+ url: document.location.href,
327
+ param: 'search',
328
+ method: 'get',
329
+
330
+ minLength: 1, // the minimal length when it starts work
331
+ threshold: 200, // the typing pause threshold
332
+
333
+ cache: true, // use the results cache
334
+ local: null, // an optional local search results list
335
+
336
+ fxName: 'slide', // list appearance fx name
337
+ fxDuration: 'short', // list appearance fx duration
338
+
339
+ spinner: 'native', // spinner element reference
340
+
341
+ cssRule: 'input[data-autocompleter]' // the auto-initialization css-rule
342
+ }
343
+ },
344
+
345
+ /**
346
+ * basic constructor
347
+ *
348
+ * @param mixed the input element reference, a string id or the element instance
349
+ * @param Object options
350
+ */
351
+ initialize: function(input, options) {
352
+ this.input = $(input); // KEEP IT before the super call
353
+
354
+ this
355
+ .$super('autocompleter', options)
356
+ .addClass('rui-dd-menu')
357
+ .onMousedown(this.clicked);
358
+
359
+ this.input.autocompleter = this;
360
+ },
361
+
362
+ /**
363
+ * Destructor
364
+ *
365
+ * @return Autocompleter this
366
+ */
367
+ destroy: function() {
368
+ delete(this.input.autocompleter);
369
+ return this;
370
+ },
371
+
372
+ /**
373
+ * picks the next item on the list
374
+ *
375
+ * @return Autocompleter this
376
+ */
377
+ prev: function() {
378
+ return this.pick('prev');
379
+ },
380
+
381
+ /**
382
+ * picks the next item on the list
383
+ *
384
+ * @return Autocompleter this
385
+ */
386
+ next: function() {
387
+ return this.pick('next');
388
+ },
389
+
390
+ /**
391
+ * triggers the done event, sets up the value and closes the list
392
+ *
393
+ * @return Autocompleter this
394
+ */
395
+ done: function(current) {
396
+ current = current || this.first('li.current');
397
+
398
+ if (current) {
399
+ current.radioClass('current');
400
+ this.input.setValue(R(current.html()).stripTags());
401
+ this.fire('done');
402
+ }
403
+
404
+ return this.hide();
405
+ },
406
+
407
+ // protected
408
+
409
+ // preprocessing the urls a bit
410
+ setOptions: function(options) {
411
+ this.$super(options, this.input);
412
+
413
+ options = this.options;
414
+
415
+ // building the correct url template with a placeholder
416
+ if (!R(options.url).includes('%{search}')) {
417
+ options.url += (R(options.url).includes('?') ? '&' : '?') + options.param + '=%{search}';
418
+ }
419
+ },
420
+
421
+ // works with the 'prev' and 'next' methods
422
+ pick: function(which_one) {
423
+ var items = this.children(),
424
+ current = items.first('hasClass', 'current'),
425
+ index = items.indexOf(current);
426
+
427
+ if (which_one == 'prev') {
428
+ current = index < 1 ? items.last() : items[index < 0 ? 0 : (index-1)];
429
+ } else if (which_one == 'next') {
430
+ current = index < 0 || index == (items.length - 1) ?
431
+ items.first() : items[index + 1];
432
+ }
433
+
434
+ return this.fire('select', {item: current.radioClass('current')});
435
+ },
436
+
437
+ // handles mouse clicks on the list element
438
+ clicked: function(event) {
439
+ this.done(event.stop().find('li'));
440
+ },
441
+
442
+ // handles the key-press events
443
+ keypressed: function(event) {
444
+ if (this.input.value().length >= this.options.minLength) {
445
+ if (this.timeout) {
446
+ this.timeout.cancel();
447
+ }
448
+ this.timeout = R(this.trigger).bind(this).delay(this.options.threshold);
449
+ } else {
450
+ return this.hide();
451
+ }
452
+ },
453
+
454
+ // triggers the actual action
455
+ trigger: function() {
456
+ this.timeout = null;
457
+
458
+ this.cache = this.cache || {};
459
+ var search = this.input.value(), options = this.options;
460
+
461
+ if (search.length < options.minLength) { return this.hide(); }
462
+
463
+ if (this.cache[search]) {
464
+ this.suggest(this.cache[search], search);
465
+ } else if (isArray(options.local)) {
466
+ this.suggest(this.findLocal(search), search);
467
+ } else {
468
+ this.request = Xhr.load(options.url.replace('%{search}', encodeURIComponent(search)), {
469
+ method: options.method,
470
+ spinner: this.getSpinner(),
471
+ onComplete: R(function(response) {
472
+ this.fire('load').suggest(response.text, search);
473
+ }).bind(this)
474
+ });
475
+ }
476
+ },
477
+
478
+ // updates the suggestions list
479
+ suggest: function(result_text, search) {
480
+ // saving the result in cache
481
+ if (this.options.cache) {
482
+ this.cache[search] = result_text;
483
+ }
484
+
485
+ if (!R(result_text).blank()) {
486
+ this.update(result_text.replace(/<ul[^>]*>|<\/ul>/im, ''));
487
+ this.fire('update');
488
+ if (!this._connected || this.hidden()) {
489
+ this.showAt(this.input, 'bottom', 'resize');
490
+ this._connected = true;
491
+ }
492
+ } else {
493
+ this.hide();
494
+ }
495
+
496
+ return this;
497
+ },
498
+
499
+ // performs the locals search
500
+ findLocal: function(search) {
501
+ var regexp = new RegExp("("+RegExp.escape(search)+")", 'ig');
502
+
503
+ return R(this.options.local).map(function(option) {
504
+ if (option.match(regexp)) {
505
+ return '<li>'+ option.replace(regexp, '<strong>$1</strong>') +'</li>';
506
+ }
507
+ }).compact().join('');
508
+ },
509
+
510
+ // builds a native textual spinner if necessary
511
+ getSpinner: function() {
512
+ var options = this.options, spinner = options.spinner;
513
+
514
+ if (spinner == 'native') {
515
+ spinner = options.spinner = new Spinner(3).insertTo(this);
516
+ spinner.addClass('rui-autocompleter-spinner');
517
+ }
518
+
519
+ // positioning the native spinner
520
+ if (spinner instanceof Spinner) {
521
+ Toggler_re_position.call(spinner, this.input, 'right', 'resize');
522
+ }
523
+
524
+ return spinner;
525
+ }
526
+ });
527
+
528
+
529
+ /**
530
+ * The document events hooking
531
+ *
532
+ * Copyright (C) 2009-2010 Nikolay Nemshilov
533
+ */
534
+ $(document).on({
535
+ /**
536
+ * Initializes autocompleters on-focus
537
+ *
538
+ * @param Event focus
539
+ * @return void
540
+ */
541
+ focus: function(event) {
542
+ var target = event.target;
543
+
544
+ if (target && (target instanceof RightJS.Element) && (target.autocompleter || target.match(Autocompleter.Options.cssRule))) {
545
+ if (!target.autocompleter) {
546
+ new Autocompleter(target);
547
+ }
548
+ }
549
+ },
550
+
551
+ /**
552
+ * Hides autocompleters on-blur
553
+ *
554
+ * @param Event blur
555
+ * @return void
556
+ */
557
+ blur: function(event) {
558
+ var autocompleter = event.target ? event.target.autocompleter : null;
559
+
560
+ if (autocompleter && autocompleter.visible()) {
561
+ autocompleter.hide();
562
+ }
563
+ },
564
+
565
+ /**
566
+ * Catching the basic keyboard events
567
+ * to navigate through the autocompletion list
568
+ *
569
+ * @param Event keydown
570
+ * @return void
571
+ */
572
+ keydown: function(event) {
573
+ var autocompleter = event.target ? event.target.autocompleter : null;
574
+
575
+ if (autocompleter && autocompleter.visible()) {
576
+ var method_name = ({
577
+ 27: 'hide', // Esc
578
+ 38: 'prev', // Up
579
+ 40: 'next', // Down
580
+ 13: 'done' // Enter
581
+ })[event.keyCode];
582
+
583
+ if (method_name) {
584
+ event.stop();
585
+ autocompleter[method_name]();
586
+ }
587
+ }
588
+ },
589
+
590
+ /**
591
+ * Catches the input fields keyup events
592
+ * and tries to make the autocompleter to show some suggestions
593
+ *
594
+ * @param Event keyup
595
+ * @return void
596
+ */
597
+ keyup: function(event) {
598
+ var autocompleter = event.target ? event.target.autocompleter : null;
599
+
600
+ if (autocompleter && !R([9, 27, 37, 38, 39, 40, 13]).include(event.keyCode)) {
601
+ autocompleter.keypressed(event);
602
+ }
603
+ }
604
+ });
605
+ (function() {
606
+ var style = document.createElement('style'),
607
+ rules = document.createTextNode(" *.rui-dd-menu, *.rui-dd-menu li{margin:0;padding:0;border:none;background:none;list-style:none;font-weight:normal;float:none} *.rui-dd-menu{display:none;position:absolute;z-index:9999;background:white;border:1px solid #BBB;border-radius:.2em;-moz-border-radius:.2em;-webkit-border-radius:.2em;box-shadow:#DDD .2em .2em .4em;-moz-box-shadow:#DDD .2em .2em .4em;-webkit-box-shadow:#DDD .2em .2em .4em} *.rui-dd-menu li{padding:.2em .4em;border-top:none;border-bottom:none;cursor:pointer} *.rui-dd-menu li.current{background:#DDD} *.rui-dd-menu li:hover{background:#EEE}dl.rui-dd-menu dt{padding:.3em .5em;cursor:default;font-weight:bold;font-style:italic;color:#444;background:#EEE}dl.rui-dd-menu dd li{padding-left:1.5em}div.rui-spinner,div.rui-spinner div{margin:0;padding:0;border:none;background:none;list-style:none;font-weight:normal;float:none;display:inline-block; *display:inline; *zoom:1;border-radius:.12em;-moz-border-radius:.12em;-webkit-border-radius:.12em}div.rui-spinner{text-align:center;white-space:nowrap;background:#EEE;border:1px solid #DDD;height:1.2em;padding:0 .2em}div.rui-spinner div{width:.4em;height:70%;background:#BBB;margin-left:1px}div.rui-spinner div:first-child{margin-left:0}div.rui-spinner div.glowing{background:#777}div.rui-re-anchor{margin:0;padding:0;background:none;border:none;float:none;display:inline;position:absolute;z-index:9999}.rui-autocompleter{border-top-color:#DDD !important;border-top-left-radius:0 !important;border-top-right-radius:0 !important;-moz-border-radius-topleft:0 !important;-moz-border-radius-topright:0 !important;-webkit-border-top-left-radius:0 !important;-webkit-border-top-right-radius:0 !important}.rui-autocompleter-spinner{border:none !important;background:none !important;position:absolute;z-index:9999}.rui-autocompleter-spinner div{margin-top:.2em !important; *margin-top:0.1em !important}");
608
+
609
+ style.type = 'text/css';
610
+
611
+ if(style.styleSheet) {
612
+ style.styleSheet.cssText = rules.nodeValue;
613
+ } else {
614
+ style.appendChild(rules);
615
+ }
616
+
617
+ document.getElementsByTagName('head')[0].appendChild(style);
618
+ })();
619
+
620
+ return Autocompleter;
621
+ })(document, RightJS);