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.
- data/CHANGES.txt +131 -0
- data/Manifest +1 -2
- data/Rakefile +3 -3
- data/dryml_generators/rapid/cards.dryml.erb +1 -1
- data/dryml_generators/rapid/forms.dryml.erb +3 -1
- data/dryml_generators/rapid/pages.dryml.erb +8 -5
- data/hobo.gemspec +8 -8
- data/lib/active_record/association_collection.rb +5 -2
- data/lib/active_record/association_reflection.rb +14 -5
- data/lib/hobo.rb +1 -1
- data/lib/hobo/controller.rb +6 -5
- data/lib/hobo/dryml.rb +4 -4
- data/lib/hobo/dryml/taglib.rb +7 -3
- data/lib/hobo/dryml/template_environment.rb +7 -7
- data/lib/hobo/hobo_helper.rb +31 -15
- data/lib/hobo/include_in_save.rb +9 -1
- data/lib/hobo/lifecycles.rb +0 -7
- data/lib/hobo/lifecycles/creator.rb +1 -1
- data/lib/hobo/lifecycles/lifecycle.rb +8 -3
- data/lib/hobo/mass_assignment.rb +64 -0
- data/lib/hobo/model.rb +41 -17
- data/lib/hobo/model_controller.rb +76 -25
- data/lib/hobo/model_router.rb +10 -13
- data/lib/hobo/user.rb +13 -13
- data/rails_generators/hobo_model/hobo_model_generator.rb +4 -0
- data/rails_generators/hobo_model/templates/model.rb +1 -1
- data/rails_generators/hobo_rapid/hobo_rapid_generator.rb +0 -2
- data/rails_generators/hobo_rapid/templates/hobo-rapid.js +180 -43
- data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +25 -4
- data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +0 -2
- data/taglibs/core.dryml +8 -5
- data/taglibs/rapid.dryml +9 -5
- data/taglibs/rapid_document_tags.dryml +1 -1
- data/taglibs/rapid_editing.dryml +1 -1
- data/taglibs/rapid_forms.dryml +108 -32
- data/taglibs/rapid_generics.dryml +2 -2
- data/taglibs/rapid_lifecycles.dryml +0 -18
- data/taglibs/rapid_user_pages.dryml +8 -41
- metadata +7 -7
- data/rails_generators/hobo_rapid/templates/nicEditorIcons.gif +0 -0
- data/rails_generators/hobo_rapid/templates/nicedit.js +0 -91
data/lib/hobo/model_router.rb
CHANGED
@@ -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
|
177
|
-
|
178
|
-
|
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
|
data/lib/hobo/user.rb
CHANGED
@@ -31,21 +31,20 @@ module Hobo
|
|
31
31
|
remember_token_expires_at :datetime
|
32
32
|
end
|
33
33
|
|
34
|
-
validates_confirmation_of :password, :if => :
|
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 => :
|
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
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
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
|
|
@@ -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
|
-
|
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
|
-
|
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 (
|
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 (
|
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 (
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
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 {
|
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:
|
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;
|
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
|
+
|