hobo 0.8.2 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/CHANGES.txt +131 -0
  2. data/Manifest +1 -2
  3. data/Rakefile +3 -3
  4. data/dryml_generators/rapid/cards.dryml.erb +1 -1
  5. data/dryml_generators/rapid/forms.dryml.erb +3 -1
  6. data/dryml_generators/rapid/pages.dryml.erb +8 -5
  7. data/hobo.gemspec +8 -8
  8. data/lib/active_record/association_collection.rb +5 -2
  9. data/lib/active_record/association_reflection.rb +14 -5
  10. data/lib/hobo.rb +1 -1
  11. data/lib/hobo/controller.rb +6 -5
  12. data/lib/hobo/dryml.rb +4 -4
  13. data/lib/hobo/dryml/taglib.rb +7 -3
  14. data/lib/hobo/dryml/template_environment.rb +7 -7
  15. data/lib/hobo/hobo_helper.rb +31 -15
  16. data/lib/hobo/include_in_save.rb +9 -1
  17. data/lib/hobo/lifecycles.rb +0 -7
  18. data/lib/hobo/lifecycles/creator.rb +1 -1
  19. data/lib/hobo/lifecycles/lifecycle.rb +8 -3
  20. data/lib/hobo/mass_assignment.rb +64 -0
  21. data/lib/hobo/model.rb +41 -17
  22. data/lib/hobo/model_controller.rb +76 -25
  23. data/lib/hobo/model_router.rb +10 -13
  24. data/lib/hobo/user.rb +13 -13
  25. data/rails_generators/hobo_model/hobo_model_generator.rb +4 -0
  26. data/rails_generators/hobo_model/templates/model.rb +1 -1
  27. data/rails_generators/hobo_rapid/hobo_rapid_generator.rb +0 -2
  28. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +180 -43
  29. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +25 -4
  30. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +0 -2
  31. data/taglibs/core.dryml +8 -5
  32. data/taglibs/rapid.dryml +9 -5
  33. data/taglibs/rapid_document_tags.dryml +1 -1
  34. data/taglibs/rapid_editing.dryml +1 -1
  35. data/taglibs/rapid_forms.dryml +108 -32
  36. data/taglibs/rapid_generics.dryml +2 -2
  37. data/taglibs/rapid_lifecycles.dryml +0 -18
  38. data/taglibs/rapid_user_pages.dryml +8 -41
  39. metadata +7 -7
  40. data/rails_generators/hobo_rapid/templates/nicEditorIcons.gif +0 -0
  41. data/rails_generators/hobo_rapid/templates/nicedit.js +0 -91
@@ -173,26 +173,23 @@ module Hobo
173
173
 
174
174
  def owner_routes
175
175
  controller.owner_actions.each_pair do |owner, actions|
176
- collection = model.reverse_reflection(owner).name
177
- owner_singular = model.reflections[owner].klass.name.underscore
178
- owner_plural = owner_singular.pluralize
179
-
176
+ collection = model.reverse_reflection(owner).name
177
+ owner_class = model.reflections[owner].klass.name.underscore
178
+
179
+ owner = owner.to_s.singularize if model.reflections[owner].macro == :has_many
180
+
181
+ collection_path = "#{owner_class.pluralize}/:#{owner_class}_id/#{collection}"
182
+
180
183
  actions.each do |action|
181
184
  case action
182
185
  when :index
183
- linkable_route("#{plural}_for_#{owner}",
184
- "#{owner_plural}/:#{owner_singular}_id/#{collection}",
185
- "index_for_#{owner}",
186
+ linkable_route("#{plural}_for_#{owner}", collection_path, "index_for_#{owner}",
186
187
  :conditions => { :method => :get })
187
188
  when :new
188
- linkable_route("new_#{singular}_for_#{owner}",
189
- "#{owner_plural}/:#{owner_singular}_id/#{collection}/new",
190
- "new_for_#{owner}",
189
+ linkable_route("new_#{singular}_for_#{owner}", "#{collection_path}/new", "new_for_#{owner}",
191
190
  :conditions => { :method => :get })
192
191
  when :create
193
- linkable_route("create_#{singular}_for_#{owner}",
194
- "#{owner_plural}/:#{owner_singular}_id/#{collection}",
195
- "create_for_#{owner}",
192
+ linkable_route("create_#{singular}_for_#{owner}", collection_path, "create_for_#{owner}",
196
193
  :conditions => { :method => :post })
197
194
  end
198
195
  end
@@ -31,21 +31,20 @@ module Hobo
31
31
  remember_token_expires_at :datetime
32
32
  end
33
33
 
34
- validates_confirmation_of :password, :if => :password_required?
34
+ validates_confirmation_of :password, :if => :new_password_required?
35
+ password_validations
36
+ validate :validate_current_password_when_changing_password
35
37
 
36
38
  # Virtual attributes for setting and changing the password
37
39
  attr_accessor :current_password, :password, :password_confirmation, :type => :password
38
40
 
39
41
 
40
- validate :validate_current_password_when_changing_password
41
-
42
42
  before_save :encrypt_password
43
43
 
44
44
  never_show *AUTHENTICATION_FIELDS
45
45
 
46
46
  attr_protected *AUTHENTICATION_FIELDS
47
47
 
48
- password_validations
49
48
 
50
49
  end
51
50
  end
@@ -55,7 +54,7 @@ module Hobo
55
54
 
56
55
  # Validation of the plaintext password
57
56
  def password_validations
58
- validates_length_of :password, :within => 4..40, :if => :password_required?
57
+ validates_length_of :password, :within => 4..40, :if => :new_password_required?
59
58
  end
60
59
 
61
60
  def login_attribute=(attr, validate=true)
@@ -98,7 +97,7 @@ module Hobo
98
97
  end
99
98
 
100
99
  def account_active?
101
- !self.class.has_lifecycle? || !'active'.in?(self.class::Lifecycle.state_names) || state == 'active'
100
+ lifecycle.active_state?
102
101
  end
103
102
 
104
103
  # Encrypts the password with the user salt
@@ -138,10 +137,6 @@ module Hobo
138
137
  true
139
138
  end
140
139
 
141
- def changing_password?
142
- crypted_password? && (password || password_confirmation) && !lifecycle.valid_key?
143
- end
144
-
145
140
  protected
146
141
  # Before filter that encrypts the password before having it stored in the database.
147
142
  def encrypt_password
@@ -151,9 +146,14 @@ module Hobo
151
146
  end
152
147
 
153
148
 
154
- # Is a password required for login? (or do we have an empty password?)
155
- def password_required?
156
- (crypted_password.blank? && password != nil) || !password.blank? || changing_password?
149
+ def changing_password?
150
+ crypted_password? && (password || password_confirmation) && !lifecycle.valid_key?
151
+ end
152
+
153
+
154
+ # Is a new password (and confirmation) required? (i.e. signing up or changing password)
155
+ def new_password_required?
156
+ (account_active? && crypted_password.blank?) || password || password_confirmation
157
157
  end
158
158
 
159
159
 
@@ -21,5 +21,9 @@ class HoboModelGenerator < Rails::Generator::NamedBase
21
21
  def banner
22
22
  "Usage: #{$0} #{spec.name} ModelName [field:type, field:type]"
23
23
  end
24
+
25
+ def max_attribute_length
26
+ attributes.*.name.*.length.max
27
+ end
24
28
 
25
29
  end
@@ -4,7 +4,7 @@ class <%= class_name %> < ActiveRecord::Base
4
4
 
5
5
  fields do
6
6
  <% for attribute in attributes -%>
7
- <%= attribute.name %> :<%= attribute.type %>
7
+ <%= "%-#{max_attribute_length}s" % attribute.name %> :<%= attribute.type %>
8
8
  <% end -%>
9
9
  timestamps
10
10
  end
@@ -14,8 +14,6 @@ class HoboRapidGenerator < Hobo::Generator
14
14
  m.file "lowpro.js", "public/javascripts/lowpro.js"
15
15
  m.file "IE7.js", "public/javascripts/IE7.js"
16
16
  m.file "blank.gif", "public/javascripts/blank.gif"
17
- m.file "nicedit.js", "public/javascripts/nicedit.js"
18
- m.file "nicEditorIcons.gif", "public/images/nicEditorIcons.gif"
19
17
  m.file "reset.css", "public/stylesheets/reset.css"
20
18
  m.file "hobo-rapid.css", "public/stylesheets/hobo-rapid.css"
21
19
  create_all(m, "themes/clean/public", "public/hobothemes/clean")
@@ -219,21 +219,6 @@ var Hobo = {
219
219
  return new Ajax.InPlaceEditor(el, Hobo.putUrl(el), opts)
220
220
  },
221
221
 
222
- nicEditorOptions: { buttonList : ['bold','italic',
223
- 'left','center','right',
224
- 'ul',
225
- 'fontFormat',
226
- 'indent','outdent',
227
- 'link','unlink',
228
- 'image', 'removeLink']},
229
-
230
- makeNicEditor: function(element) {
231
- if (!Hobo.nicEditorOptions.iconsPath) { Hobo.nicEditorOptions.iconsPath = urlBase + '/images/nicEditorIcons.gif' }
232
- var nic = new nicEditor(Hobo.nicEditorOptions)
233
- nic.panelInstance(element, {hasPanel : true})
234
- return nic.instanceById(element)
235
- },
236
-
237
222
 
238
223
  doSearch: function(el) {
239
224
  el = $(el)
@@ -242,6 +227,15 @@ var Hobo = {
242
227
  var search_results_panel = $(el.getAttribute("search-results-panel") || "search-results-panel")
243
228
  var url = el.getAttribute("search-url") || (urlBase + "/search")
244
229
 
230
+ var clear = function() { Hobo.hide(search_results_panel); el.clear() }
231
+
232
+ // Close window on [Escape]
233
+ Event.observe(el, 'keypress', function(ev) {
234
+ if (ev.keyCode == 27) clear()
235
+ });
236
+
237
+ Event.observe(search_results_panel.down('.close-button'), 'click', clear)
238
+
245
239
  var value = $F(el)
246
240
  if (Hobo.searchRequest) { Hobo.searchRequest.transport.abort() }
247
241
  if (value.length >= 3) {
@@ -348,7 +342,7 @@ var Hobo = {
348
342
 
349
343
 
350
344
  parseId: function(id) {
351
- m = id.gsub('-', '_').match(/^([a-z_]+)_([0-9]+)(?:_([a-z_]+))?$/)
345
+ m = id.gsub('-', '_').match(/^([a-z_]+)(?:_([0-9]+))?(?:_([a-z_]+))?$/)
352
346
  if (m) return { name: m[1], id: m[2], field: m[3] }
353
347
  },
354
348
 
@@ -497,7 +491,115 @@ Element.Methods.$$ = function(e, css) {
497
491
  return new Selector(css).findElements(e)
498
492
  }
499
493
 
500
- // --- has_many_through_input --- //
494
+
495
+ HoboBehavior = Class.create({
496
+
497
+ initialize: function(mainSelector, features) {
498
+ this.mainSelector = mainSelector
499
+ this.features = features
500
+ this.addEvents(mainSelector, features.events)
501
+ },
502
+
503
+ addEvents: function(parentSelector, events) {
504
+ var self = this
505
+
506
+ for (selector in events) {
507
+ fullSelector = parentSelector + ' ' + selector
508
+ var rhs = events[selector]
509
+ if (Object.isString(rhs)) {
510
+ this.addBehavior(fullSelector, this.features[rhs])
511
+ } else {
512
+ this.addEvents(fullSelector, rhs)
513
+ }
514
+ }
515
+
516
+ },
517
+
518
+ addBehavior: function(selector, handler) {
519
+ var self = this
520
+ behavior = {}
521
+ behavior[selector] = function(ev) {
522
+ self.features.element = this.up(self.mainSelector)
523
+ handler.call(self.features, ev, this)
524
+ }
525
+ Event.addBehavior(behavior)
526
+ }
527
+
528
+ })
529
+
530
+
531
+ new HoboBehavior("ul.input-many", {
532
+
533
+ events: {
534
+ "> li > div.buttons": {
535
+ ".add-item:click": 'addOne',
536
+ ".remove-item:click": 'removeOne'
537
+ }
538
+ },
539
+
540
+ addOne: function(ev, el) {
541
+ Event.stop(ev)
542
+ var ul = el.up('ul'), li = el.up('li')
543
+
544
+ var thisItem = li.down('div.input-many-item')
545
+ var newItem = "<li style='display:none'><div class='input-many-item'>" +
546
+ thisItem.innerHTML +
547
+ "</div>" +
548
+ "<div class='buttons' />" +
549
+ "</div></li>"
550
+ var newItem = DOM.Builder.fromHTML(newItem)
551
+ ul.appendChild(newItem);
552
+ $(newItem).select('input').each(function(input){ input.value="" })
553
+
554
+ this.updateButtons()
555
+ this.updateInputNames()
556
+
557
+ new Effect.BlindDown(newItem, {duration: 0.3})
558
+ },
559
+
560
+ removeOne: function(ev, el) {
561
+ Event.stop(ev)
562
+ var self = this;
563
+ var li = el.up('li')
564
+ new Effect.BlindUp(li, { duration: 0.3, afterFinish: function (ef) {
565
+ li.remove()
566
+ self.updateButtons()
567
+ self.updateInputNames()
568
+ } });
569
+ },
570
+
571
+ updateButtons: function() {
572
+ var removeButton = "<button class='remove-item'>-</button>"
573
+ var addButton = "<button class='add-item'>+</button>"
574
+
575
+
576
+ var ul = this.element
577
+ if (ul.childElements().length == 1) {
578
+ ul.down('li').down('div.buttons').innerHTML = addButton
579
+ } else {
580
+ var add = ul.selectChildren('li').selectChildren('div.buttons').down('button.add-item')
581
+ if (add) add.remove()
582
+ ul.selectChildren('li:first-child').child('div.buttons').innerHTML = removeButton
583
+ ul.selectChildren('li:last-child').child('div.buttons').innerHTML = removeButton + ' ' + addButton
584
+ }
585
+
586
+ Event.addBehavior.reload()
587
+ },
588
+
589
+ updateInputNames: function() {
590
+ var prefix = Hobo.getClassData(this.element, 'input-many-prefix')
591
+
592
+ this.element.selectChildren('li').each(function(li, index) {
593
+ li.select('*[name]').each(function(control) {
594
+ var changeId = control.id == control.name
595
+ control.name = control.name.sub(new RegExp("^" + RegExp.escape(prefix) + "\[[0-9]+\]"), prefix + '[' + index +']')
596
+ if (changeId) control.id = control.name
597
+ })
598
+ })
599
+ }
600
+
601
+ })
602
+
501
603
 
502
604
  SelectManyInput = Behavior.create({
503
605
 
@@ -588,16 +690,10 @@ Event.addBehavior({
588
690
  if (Prototype.Browser.IE) Hobo.fixSectionGroup(this);
589
691
  },
590
692
 
591
- 'textarea.html' : function(e) {
592
- if (typeof(nicEditors) != "undefined") {
593
- Hobo.makeNicEditor(this)
594
- }
595
- },
596
-
597
693
  'div.select-many.input' : SelectManyInput(),
598
694
 
599
695
  '.association-count:click' : function(e) {
600
- new Effect.ScrollTo('primary-collection', {duration: 1.0, offset: -20, transition: Effect.Transitions.sinoidal});
696
+ new Effect.ScrollTo('primary-collection', {duration: 1.0, offset: -20, transition: Effect.Transitions.sinoidal});
601
697
  Event.stop(e);
602
698
  },
603
699
 
@@ -616,38 +712,28 @@ Event.addBehavior({
616
712
  '.autocompleter' : AutocompleteBehavior(),
617
713
 
618
714
  '.string.in-place-edit, .datetime.in-place-edit, .date.in-place-edit, .integer.in-place-edit, .float.in-place.edit, big-integer.in-place-edit' :
619
- function (e) {
715
+ function (ev) {
620
716
  var ipe = Hobo._makeInPlaceEditor(this)
621
717
  ipe.getText = function() {
622
718
  return this.element.innerHTML.gsub(/<br\s*\/?>/, "\n").unescapeHTML()
623
719
  }
624
720
  },
625
721
 
626
- '.text.in-place-edit, .markdown.in-place-edit, .textile.in-place-edit' : function (e) {
722
+ '.text.in-place-edit, .markdown.in-place-edit, .textile.in-place-edit' : function (ev) {
627
723
  var ipe = Hobo._makeInPlaceEditor(this, {rows: 2})
628
724
  ipe.getText = function() {
629
725
  return this.element.innerHTML.gsub(/<br\s*\/?>/, "\n").unescapeHTML()
630
726
  }
631
727
  },
632
728
 
633
- ".html.in-place-edit" : function (e) {
634
- var nicEditPresent = typeof(nicEditor) != "undefined"
635
- var options = { rows: 2, handleLineBreaks: false, okButton: true, cancelLink: true, okText: "Save" }
636
- if (nicEditPresent) options["submitOnBlur"] = false
637
- var ipe = Hobo._makeInPlaceEditor(this, options)
638
- if (nicEditPresent) {
639
- ipe.afterEnterEditMode = function() {
640
- var editor = this._controls.editor
641
- var id = editor.id = Hobo.uid()
642
- var nicInstance = Hobo.makeNicEditor(editor)
643
- var panel = this._form.down(".nicEdit-panel")
644
- panel.appendChild(this._controls.cancel)
645
- panel.appendChild(this._controls.ok)
646
- bkLib.addEvent(this._controls.ok,'click', function () {
647
- nicInstance.saveContent()
648
- setTimeout(function() {nicInstance.remove()}, 1)
649
- })
729
+ ".html.in-place-edit" : function (ev) {
730
+ if (Hobo.makeInPlaceHtmlEditor) {
731
+ Hobo.makeInPlaceHtmlEditor(this)
732
+ } else {
733
+ var options = {
734
+ rows: 2, handleLineBreaks: false, okButton: true, cancelLink: true, okText: "Save", submitOnBlur: false
650
735
  }
736
+ var ipe = Hobo._makeInPlaceEditor(this, options)
651
737
  }
652
738
  },
653
739
 
@@ -665,3 +751,54 @@ Event.addBehavior({
665
751
 
666
752
 
667
753
  });
754
+
755
+ ElementSet = Class.create(Enumerable, {
756
+
757
+ initialize: function(array) {
758
+ this.items = array
759
+ },
760
+
761
+ _each: function(fn) {
762
+ return this.items.each(fn)
763
+ },
764
+
765
+ selectChildren: function(selector) {
766
+ return new ElementSet(this.items.invoke('selectChildren', selector).pluck('items').flatten())
767
+ },
768
+
769
+ child: function(selector) {
770
+ return this.selectChildren(selector).first()
771
+ },
772
+
773
+ select: function(selector) {
774
+ return new ElementSet(this.items.invoke('select', selector).flatten())
775
+ },
776
+
777
+ down: function(selector) {
778
+ for (var i = 0; i < this.items.length; i++) {
779
+ var match = this.items[i].down(selector)
780
+ if (match) return match
781
+ }
782
+ return null
783
+ },
784
+
785
+ size: function() {
786
+ return this.items.length
787
+ },
788
+
789
+ first: function() {
790
+ return this.items.first()
791
+ },
792
+
793
+ last: function() {
794
+ return this.items.last()
795
+ }
796
+
797
+ })
798
+
799
+ Element.addMethods({
800
+ selectChildren: function(element, selector) {
801
+ return new ElementSet(Selector.matchElements(element.childElements(), selector))
802
+ }
803
+ })
804
+
@@ -75,7 +75,7 @@ input.file_upload {
75
75
  font-size: 11px; font-weight: bold;
76
76
  }
77
77
  .button:hover {cursor: pointer;}
78
- .actions {zoom: 1; overflow: hidden; font-size: 11px;}
78
+ .actions {height: 100%; overflow: hidden; font-size: 11px;}
79
79
 
80
80
  .flash {
81
81
  margin: 0 40px 10px; padding: 10px 30px; border-width: 2px 0;
@@ -149,7 +149,7 @@ input.file_upload {
149
149
  color: black; background: #f2f2f2;
150
150
  }
151
151
  #search-results-panel .card.linkable a {color: black;}
152
- #search-spinner {position: absolute; top: 6px; right: 7px;}
152
+ #search-spinner {background:black;border:1px solid #666666;opacity:0.6;padding:2px;position:absolute;right:4px;top:6px;}
153
153
 
154
154
  .main-nav {padding: 0 30px;}
155
155
  .main-nav li {margin-right: 10px;}
@@ -222,8 +222,7 @@ form .actions {margin: 30px 0; width: 100%; text-align: center;}
222
222
  background: #f5f5f5;
223
223
  position: relative;
224
224
  }
225
- .card h4 {margin-top: 0; margin-bottom: 0;}
226
- .card .body {margin-top: 6px;}
225
+ .card h4 {margin-top: 0;}
227
226
  .card a {background: #f5f5f5;}
228
227
  .card .creation-details {
229
228
  display: block; color: #333; font-size: 11px;
@@ -287,6 +286,10 @@ div.select-many .item {
287
286
  div.select-many .item span { float: left; }
288
287
  div.select-many .item .remove-item { float: right; }
289
288
 
289
+ /* <live-search> */
290
+
291
+ #search-results-panel { postition: relative; }
292
+ #search-results-panel .close-button { cursor: pointer; text-decoration: underline; position: absolute; top: 3px; right: 6px;}
290
293
 
291
294
  /*******************************************************/
292
295
  /* these styles are for the generated front index page */
@@ -301,3 +304,21 @@ div.select-many .item .remove-item { float: right; }
301
304
  }
302
305
  .front-page ul.models li {margin-left: 0; list-style: none;}
303
306
 
307
+ ul.input-many {list-style-type: none;}
308
+ ul.input-all {list-style-type: none;}
309
+
310
+ ul.input-many > li { overflow:hidden; zoom:1;}
311
+ ul.input-many .input-many-item {float:left;}
312
+ ul.input-many div.buttons {float:left; margin-left:10px;}
313
+
314
+
315
+
316
+
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+