hobo 0.8.2 → 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|