hobo 0.8.2 → 0.8.3

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