radiant-fabulator-extension 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING.markdown +22 -0
- data/README.rdoc +21 -0
- data/VERSION +1 -0
- data/app/models/fabulator_context.rb +36 -0
- data/app/models/fabulator_page.rb +459 -0
- data/db/migrate/001_add_fabulator_fields.rb +9 -0
- data/db/migrate/002_create_fabulator_contexts_table.rb +17 -0
- data/fabulator_extension.rb +61 -0
- data/lib/fabulator/radiant.rb +1 -0
- data/lib/fabulator/radiant/actions.rb +89 -0
- metadata +91 -0
data/COPYING.markdown
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009-2010 Texas A&M University
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
== Fabulator
|
2
|
+
|
3
|
+
Fabulator is an extension to the Radiant CMS for creating web applications
|
4
|
+
The applications are written in an XML language that describes the set
|
5
|
+
of application views and the data that triggers a move from one view
|
6
|
+
to the next, along with initial conditions for the application and data
|
7
|
+
transformations.
|
8
|
+
|
9
|
+
== Installation
|
10
|
+
|
11
|
+
Installation is done in the usual manner for Radiant extensions.
|
12
|
+
|
13
|
+
This extension requires Radiant 0.9 or higher as well as the following
|
14
|
+
gems:
|
15
|
+
|
16
|
+
* fabulator
|
17
|
+
|
18
|
+
You may also want to add some fabulator extensions.
|
19
|
+
See http://github.com/jgsmith/
|
20
|
+
|
21
|
+
[Radiant CMS]: http://www.radiantcms.org/
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class FabulatorContext < ActiveRecord::Base
|
2
|
+
#serialize :context
|
3
|
+
belongs_to :page
|
4
|
+
belongs_to :user
|
5
|
+
|
6
|
+
def self.find_by_page(p)
|
7
|
+
if p.request.session[:user_id].blank?
|
8
|
+
c = self.first(:conditions => [
|
9
|
+
'page_id = ? AND session = ? AND (user_id IS NULL OR user_id=0)',
|
10
|
+
p.id, p.request.session[:session_id]
|
11
|
+
] )
|
12
|
+
else
|
13
|
+
c = self.first(:conditions => [
|
14
|
+
'page_id = ? AND session = ? AND user_id = ?',
|
15
|
+
p.id, p.request.session[:session_id], p.request.session[:user_id]
|
16
|
+
] )
|
17
|
+
end
|
18
|
+
if c.nil? && !p.request.session[:user_id].blank?
|
19
|
+
c = self.first(:conditions => [
|
20
|
+
'page_id = ? AND session = ?',
|
21
|
+
p.id, p.request.session[:session_id]
|
22
|
+
] )
|
23
|
+
if !c.nil? && c.user.nil?
|
24
|
+
c.update_attribute(:user_id, p.request.session[:user_id])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
p.fabulator_context = c.context unless c.nil?
|
28
|
+
return c unless c.nil?
|
29
|
+
self.new(
|
30
|
+
:context => YAML::dump(p.fabulator_context),
|
31
|
+
:page_id => p.id,
|
32
|
+
:user_id => p.request.session[:user_id],
|
33
|
+
:session => p.request.session[:session_id]
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,459 @@
|
|
1
|
+
class FabulatorPage < Page
|
2
|
+
# precompile xslt so we don't do this for *every* request
|
3
|
+
|
4
|
+
@@fabulator_xslt_file = RAILS_ROOT + '/vendor/extensions/fabulator/xslt/form.xsl'
|
5
|
+
@@fabulator_xslt = REXML::Document.new File.open(@@fabulator_xslt_file)
|
6
|
+
|
7
|
+
attr_accessor :inner_content
|
8
|
+
|
9
|
+
description %{
|
10
|
+
A Fabulator page allows you to create a simple, interactive
|
11
|
+
web application that manages data in RDF models defined in the
|
12
|
+
Fabulator > RDF Models tab of the administrative area.
|
13
|
+
}
|
14
|
+
# need a reasonable name for the XML part
|
15
|
+
XML_PART_NAME = 'extended'
|
16
|
+
|
17
|
+
after_save :set_defaults
|
18
|
+
attr_accessor :resource_ln, :c_state_machine
|
19
|
+
|
20
|
+
# create tags to access filtered data in page display
|
21
|
+
# might also create tags for form fields, etc., so it's easy to
|
22
|
+
# create a form and fill in data
|
23
|
+
|
24
|
+
def cache?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_by_url(url, live = true, clean = false)
|
29
|
+
#Rails.logger.info("find_by_url(#{url}, #{live}, #{clean})")
|
30
|
+
#Rails.logger.info("invoking super")
|
31
|
+
p = super
|
32
|
+
#Rails.logger.info("returning from super")
|
33
|
+
#Rails.logger.info("Found #{p}")
|
34
|
+
return p if !p.nil? && !p.is_a?(FileNotFoundPage)
|
35
|
+
#Rails.logger.info("Seeing if we have a resource")
|
36
|
+
|
37
|
+
url = clean_url(url) if clean
|
38
|
+
#Rails.logger.info("Our url: #{self.url}")
|
39
|
+
#Rails.logger.info("Target url: #{url}")
|
40
|
+
if url =~ %r{^#{ self.url }([-_0-9a-zA-Z]+)/?$}
|
41
|
+
#Rails.logger.info("resource: #{$1}")
|
42
|
+
self.resource_ln = $1
|
43
|
+
return self
|
44
|
+
else
|
45
|
+
return p
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def url
|
50
|
+
#Rails.logger.info("Getting url")
|
51
|
+
u = super
|
52
|
+
if !self.resource_ln.nil?
|
53
|
+
u = u + '/' + self.resource_ln
|
54
|
+
end
|
55
|
+
#Rails.logger.info("Returning '#{u}' as url")
|
56
|
+
u
|
57
|
+
end
|
58
|
+
|
59
|
+
def state_machine
|
60
|
+
if self.compiled_xml.nil? || self.compiled_xml == ''
|
61
|
+
self.c_state_machine = nil
|
62
|
+
else
|
63
|
+
self.c_state_machine = (YAML::load(self.compiled_xml) rescue nil) unless self.c_state_machine
|
64
|
+
end
|
65
|
+
self.c_state_machine
|
66
|
+
end
|
67
|
+
|
68
|
+
def fabulator_context
|
69
|
+
if @roots.nil?
|
70
|
+
@roots = { }
|
71
|
+
end
|
72
|
+
|
73
|
+
if @roots['data'].nil?
|
74
|
+
@roots['data'] = Fabulator::Expr::Node.new('data', @roots, nil, [])
|
75
|
+
ctx = Fabulator::Expr::Context.new
|
76
|
+
ctx.root = @roots['data']
|
77
|
+
ctx.traverse_path(['resource'], true).first.value = self.resource_ln if self.resource_ln
|
78
|
+
self.state_machine.init_context(ctx)
|
79
|
+
end
|
80
|
+
@roots['data']
|
81
|
+
end
|
82
|
+
|
83
|
+
def fabulator_context=(c)
|
84
|
+
fc = self.fabulator_context
|
85
|
+
@roots = { } if @roots.nil?
|
86
|
+
@roots['data'] = c
|
87
|
+
end
|
88
|
+
|
89
|
+
def headers
|
90
|
+
if @resetting_context
|
91
|
+
{
|
92
|
+
:location => self.url,
|
93
|
+
}
|
94
|
+
elsif @redirecting
|
95
|
+
{
|
96
|
+
:location => @redirecting,
|
97
|
+
}
|
98
|
+
else
|
99
|
+
{ }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def response_code
|
104
|
+
@resetting_context ? 302 : ( @redirecting ? 304 : 200 )
|
105
|
+
end
|
106
|
+
|
107
|
+
def render_snippet(p)
|
108
|
+
if p.name != XML_PART_NAME
|
109
|
+
super
|
110
|
+
else
|
111
|
+
# check if page part was updated since page -- might need to recompile
|
112
|
+
|
113
|
+
sm = self.state_machine
|
114
|
+
return '' if sm.nil?
|
115
|
+
|
116
|
+
# run state machine if POST
|
117
|
+
context = FabulatorContext.find_by_page(self)
|
118
|
+
@resetting_context = false
|
119
|
+
|
120
|
+
if request.method == :get &&
|
121
|
+
params[:reset_context]
|
122
|
+
if !context.new_record?
|
123
|
+
context.destroy
|
124
|
+
end
|
125
|
+
# redirect without the reset_context param?
|
126
|
+
@response.redirect(url,302)
|
127
|
+
@resetting_context = true
|
128
|
+
return
|
129
|
+
end
|
130
|
+
|
131
|
+
begin
|
132
|
+
sm.context = YAML::load(context.context)
|
133
|
+
if sm.context.empty?
|
134
|
+
sm.init_context(self.fabulator_context)
|
135
|
+
end
|
136
|
+
#sm.context.merge!(self.resource_ln, ['resource'] ) if self.resource_ln
|
137
|
+
if request.method == :post
|
138
|
+
sm.run(params)
|
139
|
+
# save context
|
140
|
+
@sm_missing_args = sm.missing_params
|
141
|
+
@sm_errors = sm.errors
|
142
|
+
context.context = YAML::dump(sm.context)
|
143
|
+
context.save
|
144
|
+
end
|
145
|
+
# save statemachine state
|
146
|
+
# display resulting view
|
147
|
+
rescue Fabulator::FabulatorRequireAuth => e
|
148
|
+
@redirecting = e.to_s
|
149
|
+
rescue => e
|
150
|
+
return "<p>#{e.to_s}</p><pre>" + e.backtrace.join("\n") + "</pre>"
|
151
|
+
end
|
152
|
+
return '' if @redirecting
|
153
|
+
if sm.state != XML_PART_NAME
|
154
|
+
return self.render_part(sm.state)
|
155
|
+
else
|
156
|
+
return 'Error: Fabulator application is not in a displayable state.'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def missing_args
|
162
|
+
@sm_missing_args
|
163
|
+
end
|
164
|
+
|
165
|
+
tag 'fabulator' do |tag|
|
166
|
+
tag.locals.fabulator_context = tag.locals.page.fabulator_context
|
167
|
+
tag.expand
|
168
|
+
end
|
169
|
+
|
170
|
+
desc %{
|
171
|
+
Formats the enclosed logical form markup, adds default values,
|
172
|
+
existing values, and marks errors or warnings and required fields.
|
173
|
+
}
|
174
|
+
tag 'form' do |tag|
|
175
|
+
# get xml markup of form and transform it via xslt while adding
|
176
|
+
# default values and such
|
177
|
+
# wrap the whole thing in a form tag to post back to this page
|
178
|
+
xml = tag.expand
|
179
|
+
return '' if xml.blank?
|
180
|
+
|
181
|
+
text_parser = Fabulator::Template::Parser.new
|
182
|
+
|
183
|
+
c = get_fabulator_context(tag)
|
184
|
+
root = nil
|
185
|
+
|
186
|
+
missing_args = tag.locals.page.missing_args
|
187
|
+
|
188
|
+
form_base = tag.attr['base']
|
189
|
+
if form_base.nil? || form_base == ''
|
190
|
+
root = c
|
191
|
+
form_base = c.root.path.gsub(/^.*::/, '').gsub('/', '.').gsub(/^\.+/, '')
|
192
|
+
else
|
193
|
+
root = c.nil? ? nil : c.with_root(c.eval_expression('/' + form_base.gsub('.', '/')).first)
|
194
|
+
root = c if !c.nil? && root.root.nil?
|
195
|
+
end
|
196
|
+
root = c
|
197
|
+
|
198
|
+
xml = %{<view><form>} + xml + %{</form></view>}
|
199
|
+
doc = text_parser.parse(c, xml)
|
200
|
+
|
201
|
+
# add default values
|
202
|
+
doc.add_default_values(root)
|
203
|
+
|
204
|
+
doc.to_html
|
205
|
+
end
|
206
|
+
|
207
|
+
# borrowed heavily from http://cpansearch.perl.org/src/JSMITH/Gestinanna-0.02/lib/Gestinanna/ContentProvider/XSM.pm
|
208
|
+
def add_default_values(doc, ctx)
|
209
|
+
REXML::XPath.each(doc.root, %{
|
210
|
+
//text
|
211
|
+
| //textline
|
212
|
+
| //textbox
|
213
|
+
| //editbox
|
214
|
+
| //file
|
215
|
+
| //password
|
216
|
+
| //selection
|
217
|
+
| //grid
|
218
|
+
}) do |el|
|
219
|
+
own_id = el.attribute('id')
|
220
|
+
next if own_id.nil? || own_id.to_s == ''
|
221
|
+
|
222
|
+
default = 0
|
223
|
+
is_grid = false
|
224
|
+
if el.local_name == 'grid'
|
225
|
+
default = REXML::XPath.match(el, './default | ./row/default | ./column/default')
|
226
|
+
is_grid = true
|
227
|
+
else
|
228
|
+
default = REXML::XPath.match(el, './default')
|
229
|
+
end
|
230
|
+
|
231
|
+
#missing = el.attribute('missing')
|
232
|
+
|
233
|
+
ancestors = REXML::XPath.match(el, %{
|
234
|
+
ancestor::option[@id != '']
|
235
|
+
| ancestor::group[@id != '']
|
236
|
+
| ancestor::form[@id != '']
|
237
|
+
| ancestor::container[@id != '']
|
238
|
+
})
|
239
|
+
ids = ancestors.collect{|a| a.attribute('id')}.select{|a| !a.nil? }
|
240
|
+
ids << own_id
|
241
|
+
id = ids.collect{|i| i.to_s}.join('.')
|
242
|
+
ids = id.split('.')
|
243
|
+
if !ctx.nil? && (default.is_a?(Array) && default.empty? || !default)
|
244
|
+
# create a new node 'default'
|
245
|
+
l = ctx.traverse_path(ids)
|
246
|
+
if !l.nil? && !l.empty?
|
247
|
+
if is_grid
|
248
|
+
count = (el.attribute('count').to_s rescue '')
|
249
|
+
how_many = 'multiple'
|
250
|
+
direction = 'both'
|
251
|
+
if count =~ %r{^(multiple|single)(-by-(row|column))?$}
|
252
|
+
how_many = $1
|
253
|
+
direction = $3 || 'both'
|
254
|
+
end
|
255
|
+
if direction == 'both'
|
256
|
+
l.collect{|ll| ll.value }.each do |v|
|
257
|
+
default = el.add_element('default')
|
258
|
+
default.add_text(v)
|
259
|
+
end
|
260
|
+
elsif direction == 'row' || direction == 'column'
|
261
|
+
REXML::XPath.each(el, "./#{direction}").each do |div|
|
262
|
+
id = (div.attribute('id').to_s rescue '')
|
263
|
+
next if id == ''
|
264
|
+
l.collect{|c| c.traverse_path(id)}.flatten.collect{|c| c.value }. each do |v|
|
265
|
+
default = div.add_element('default')
|
266
|
+
default.add_text(v)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
else
|
271
|
+
l.collect{|ll| ll.value }.each do |v|
|
272
|
+
default = el.add_element('default')
|
273
|
+
default.add_text(v)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
# now handle missing info for el
|
279
|
+
|
280
|
+
if !missing_args.nil? && missing_args.include?(id)
|
281
|
+
el.add_attribute('missing', '1')
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
desc %{
|
288
|
+
Defines XML namespace prefix to URI mappings.
|
289
|
+
}
|
290
|
+
tag 'xmlns' do |tag|
|
291
|
+
tag.expand
|
292
|
+
end
|
293
|
+
|
294
|
+
desc %{
|
295
|
+
Iterates through a set of data nodes.
|
296
|
+
|
297
|
+
*Usage:*
|
298
|
+
|
299
|
+
<pre><code><r:for-each select="./foo">...</r:for-each></code></pre>
|
300
|
+
}
|
301
|
+
tag 'for-each' do |tag|
|
302
|
+
selection = tag.attr['select']
|
303
|
+
c = get_fabulator_context(tag)
|
304
|
+
#Rails.logger.info("context: #{YAML::dump(c)}")
|
305
|
+
#Rails.logger.info("for-each: #{selection} from #{c}")
|
306
|
+
ns = get_fabulator_ns(tag)
|
307
|
+
items = c.nil? ? [] : c.eval_expression(selection)
|
308
|
+
sort_by = tag.attr['sort']
|
309
|
+
sort_dir = tag.attr['order'] || 'asc'
|
310
|
+
|
311
|
+
if !sort_by.nil? && sort_by != ''
|
312
|
+
parser = Fabulator::Expr::Parser.new
|
313
|
+
sort_by_f = parser.parse(sort_by, c)
|
314
|
+
items = items.sort_by { |i| c.with_root(i).eval_expression(sort_by_f).first.value }
|
315
|
+
if sort_dir == 'desc'
|
316
|
+
items.reverse!
|
317
|
+
end
|
318
|
+
end
|
319
|
+
res = ''
|
320
|
+
#Rails.logger.info("Found #{items.size} items for for-each")
|
321
|
+
items.each do |i|
|
322
|
+
next if i.empty?
|
323
|
+
tag.locals.fabulator_context = c.with_root(i)
|
324
|
+
res = res + tag.expand
|
325
|
+
end
|
326
|
+
res
|
327
|
+
end
|
328
|
+
|
329
|
+
desc %{
|
330
|
+
Selects the value and returns it in HTML.
|
331
|
+
TODO: allow escaping of HTML special characters
|
332
|
+
|
333
|
+
*Usage:*
|
334
|
+
|
335
|
+
<pre><code><r:value select="./foo" /></code></pre>
|
336
|
+
}
|
337
|
+
tag 'value' do |tag|
|
338
|
+
selection = tag.attr['select']
|
339
|
+
c = get_fabulator_context(tag)
|
340
|
+
items = c.nil? ? [] : c.eval_expression(selection)
|
341
|
+
items.collect{|i| i.to([Fabulator::FAB_NS, 'html']).value }.join('')
|
342
|
+
end
|
343
|
+
|
344
|
+
desc %{
|
345
|
+
Chooses the first test which returns content. Otherwise,
|
346
|
+
uses the 'otherwise' tag.
|
347
|
+
}
|
348
|
+
tag 'choose' do |tag|
|
349
|
+
@chosen ||= [ ]
|
350
|
+
@chosen.unshift false
|
351
|
+
ret = tag.expand
|
352
|
+
@chosen.shift
|
353
|
+
ret
|
354
|
+
end
|
355
|
+
|
356
|
+
desc %{
|
357
|
+
Renders the enclosed content if the test passes.
|
358
|
+
}
|
359
|
+
tag 'choose:when' do |tag|
|
360
|
+
return '' if @chosen.first
|
361
|
+
selection = tag.attr['test']
|
362
|
+
c = get_fabulator_context(tag)
|
363
|
+
items = c.nil? ? [] : c.eval_expression(selection)
|
364
|
+
if items.is_a?(Array)
|
365
|
+
if items.empty?
|
366
|
+
return ''
|
367
|
+
else
|
368
|
+
@chosen[0] = true
|
369
|
+
return tag.expand
|
370
|
+
end
|
371
|
+
elsif items
|
372
|
+
@chosen[0] = true
|
373
|
+
return tag.expand
|
374
|
+
end
|
375
|
+
return ''
|
376
|
+
end
|
377
|
+
|
378
|
+
desc %{
|
379
|
+
Renders the enclosed content.
|
380
|
+
}
|
381
|
+
tag 'choose:otherwise' do |tag|
|
382
|
+
return '' if @chosen.first
|
383
|
+
tag.expand
|
384
|
+
end
|
385
|
+
|
386
|
+
desc %{
|
387
|
+
Renders the inherited view.
|
388
|
+
}
|
389
|
+
tag 'inner' do |tag|
|
390
|
+
@inner_content.nil? ? '' : @inner_content
|
391
|
+
end
|
392
|
+
|
393
|
+
desc %{
|
394
|
+
Renders the parent view providing the child view as an augmentation.
|
395
|
+
}
|
396
|
+
tag 'augment' do |tag|
|
397
|
+
parent_page = self.state_machine.isa
|
398
|
+
inner = tag.expand
|
399
|
+
return inner if parent_page.nil?
|
400
|
+
parent_page.inner_content = inner
|
401
|
+
parent_page.render_part(self.state_machine.state)
|
402
|
+
end
|
403
|
+
|
404
|
+
private
|
405
|
+
|
406
|
+
def get_fabulator_ns(tag)
|
407
|
+
c = tag.locals.page
|
408
|
+
if c.nil?
|
409
|
+
c = tag.globals.page
|
410
|
+
end
|
411
|
+
#Rails.logger.info("page: #{c}")
|
412
|
+
#Rails.logger.info("state machine: #{c.state_machine}")
|
413
|
+
#Rails.logger.info("namespaces: #{c.state_machine.namespaces}")
|
414
|
+
ret = (c.state_machine.namespaces rescue { })
|
415
|
+
#Rails.logger.info("get_fabulator_ns: [#{ret}]")
|
416
|
+
ret
|
417
|
+
end
|
418
|
+
|
419
|
+
def get_fabulator_context(tag)
|
420
|
+
c = tag.locals.fabulator_context
|
421
|
+
if c.nil?
|
422
|
+
c = tag.locals.page.state_machine.fabulator_context
|
423
|
+
if c.nil?
|
424
|
+
c = tag.globals.page.state_machine.fabulator_context
|
425
|
+
end
|
426
|
+
end
|
427
|
+
# TODO: move serialization back into the model
|
428
|
+
if c.is_a?(String)
|
429
|
+
c = YAML::load(c)
|
430
|
+
end
|
431
|
+
if c.is_a?(Hash)
|
432
|
+
c = c[:data]
|
433
|
+
end
|
434
|
+
return c
|
435
|
+
end
|
436
|
+
|
437
|
+
|
438
|
+
def set_defaults
|
439
|
+
# create a part for each state in the document
|
440
|
+
# 'body' is a description/special
|
441
|
+
# 'sidebar' is reserved
|
442
|
+
|
443
|
+
# compile statemachine into a set of Ruby objects and save
|
444
|
+
# not the most efficient, but we don't usually have hundreds of states
|
445
|
+
self[:compiled_xml] = nil
|
446
|
+
@compiled_xml = nil
|
447
|
+
sm = self.state_machine
|
448
|
+
Rails.logger.info("SM: #{YAML::dump(sm)}")
|
449
|
+
return if sm.nil?
|
450
|
+
#Rails.logger.info("Ensuring the right parts are present")
|
451
|
+
|
452
|
+
sm.state_names.sort.each do |part_name|
|
453
|
+
parts.create(:name => part_name, :content => %{
|
454
|
+
<h1>View for State #{part_name}</h1>
|
455
|
+
}) unless parts.any?{ |p| p.name == part_name }
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateFabulatorContextsTable < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :fabulator_contexts do |t|
|
4
|
+
t.string :session, :null => false
|
5
|
+
t.references :page, :null => false
|
6
|
+
t.references :user
|
7
|
+
t.text :context
|
8
|
+
t.integer :lock_version, :default => 0
|
9
|
+
end
|
10
|
+
|
11
|
+
add_index :fabulator_contexts, [ :session, :page_id, :user_id ], :unique => true
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.down
|
15
|
+
drop_table :fabulator_contexts
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
$: << File.expand_path(File.dirname(__FILE__))+'/lib'
|
2
|
+
|
3
|
+
require 'fabulator/radiant'
|
4
|
+
|
5
|
+
require_dependency "#{File.expand_path(File.dirname(__FILE__))}/app/models/fabulator_page"
|
6
|
+
|
7
|
+
class FabulatorExtension < Radiant::Extension
|
8
|
+
version "1.0"
|
9
|
+
description "Applications as documents"
|
10
|
+
url "http://github.com/jgsmith/radiant-fabulator"
|
11
|
+
|
12
|
+
XML_PART_NAME = 'extended'
|
13
|
+
|
14
|
+
extension_config do |config|
|
15
|
+
config.gem 'fabulator'
|
16
|
+
config.after_initialize do
|
17
|
+
#run_something
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def activate
|
23
|
+
FabulatorPage
|
24
|
+
|
25
|
+
tab 'Fabulator' do
|
26
|
+
end
|
27
|
+
|
28
|
+
PagePart.class_eval do
|
29
|
+
before_save :compile_xml
|
30
|
+
|
31
|
+
def compile_xml
|
32
|
+
if self.page.class_name == 'FabulatorPage' &&
|
33
|
+
self.name == FabulatorExtension::XML_PART_NAME
|
34
|
+
old_compiled_xml = self.page.compiled_xml
|
35
|
+
if self.content.nil? || self.content == ''
|
36
|
+
self.page.compiled_xml = nil
|
37
|
+
else
|
38
|
+
# compile
|
39
|
+
#isa = Fabulator::ActionLib.get_local_attr(doc.root, Fabulator::FAB_NS, 'is-a')
|
40
|
+
isa = nil
|
41
|
+
sm = nil
|
42
|
+
if isa.nil?
|
43
|
+
sm = Fabulator::Core::StateMachine.new.compile_xml(self.content)
|
44
|
+
else
|
45
|
+
supersm_page = self.page.find_by_url(isa)
|
46
|
+
if supersm_page.nil? || supersm_page.is_a?(FileNotFoundPage) || !supersm_page.is_a?(FabulatorPage) || supersm_page.state_machine.nil?
|
47
|
+
raise "File Not Found: unable to find #{isa}"
|
48
|
+
end
|
49
|
+
sm = supersm_page.state_machine.clone
|
50
|
+
sm.compile_xml(self.content)
|
51
|
+
end
|
52
|
+
self.page.compiled_xml = YAML::dump(sm)
|
53
|
+
end
|
54
|
+
if old_compiled_xml != self.page.compiled_xml
|
55
|
+
self.page.save
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'fabulator/radiant/actions'
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'fabulator/action_lib'
|
2
|
+
require 'fabulator/radiant/actions/require_auth'
|
3
|
+
|
4
|
+
module Fabulator
|
5
|
+
RADIANT_NS="http://dh.tamu.edu/ns/fabulator/radiant/1.0#"
|
6
|
+
class FabulatorRequireAuth < StandardError
|
7
|
+
def initialize(message = "") super; end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Radiant
|
11
|
+
module Actions
|
12
|
+
class Lib
|
13
|
+
include Fabulator::ActionLib
|
14
|
+
|
15
|
+
register_namespace RADIANT_NS
|
16
|
+
|
17
|
+
#action 'require-auth', Fabulator::Radiant::Actions::RequireAuth
|
18
|
+
|
19
|
+
#register_type 'user', {
|
20
|
+
#}
|
21
|
+
|
22
|
+
register_type 'page', {
|
23
|
+
:ops => {
|
24
|
+
:children => Proc.new { |p| p.value.children.collect { |c| Lib.page_to_node(c, p) } },
|
25
|
+
},
|
26
|
+
}
|
27
|
+
|
28
|
+
# page parts are attributes of a page
|
29
|
+
# as are @name, @breadcrumb, @description, @keywords
|
30
|
+
# @layout, @page-type, @status
|
31
|
+
# slug is the node name
|
32
|
+
# page parts have attributes @filter
|
33
|
+
register_type 'page-part', {
|
34
|
+
:to => [
|
35
|
+
{ :type => [ FAB_NS, 'string' ],
|
36
|
+
:weight => 1.0,
|
37
|
+
:convert => Proc.new { |p| p.anon_node(p.value, [ FAB_NS, 'string' ]) }
|
38
|
+
}
|
39
|
+
],
|
40
|
+
}
|
41
|
+
|
42
|
+
# 'radiant' axis
|
43
|
+
axis 'radiant' do |ctx|
|
44
|
+
# returns the root 'Home' page for the site
|
45
|
+
# children are addressable by their slug
|
46
|
+
Lib.page_to_node(Page.find_by_parent_id(nil), ctx)
|
47
|
+
end
|
48
|
+
|
49
|
+
function 'find', [ RADIANT_NS, 'page' ] do |ctx, args|
|
50
|
+
args[0].collect { |a|
|
51
|
+
Lib.page_to_node(Page.find_by_parent_id(nil).find_by_url(a.to_s), ctx)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
function 'current-user' do |ctx, args|
|
56
|
+
u = UserActionObserver.current_user
|
57
|
+
if !u.nil?
|
58
|
+
n = ctx.root.anon_node(u.id) #, [ RADIANT_NS, 'user' ])
|
59
|
+
n.set_attribute('admin', u.admin?)
|
60
|
+
Rails.logger.info("Returning: #{YAML::dump(n)}")
|
61
|
+
return [ n ]
|
62
|
+
else
|
63
|
+
return [ ]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.page_to_node(p, ctx)
|
68
|
+
return nil if p.nil?
|
69
|
+
p_node = ctx.root.anon_node(p, [ RADIANT_NS, 'page' ])
|
70
|
+
p_node.name = p.slug
|
71
|
+
p.parts.each do |pp|
|
72
|
+
pp_node = ctx.root.anon_node(pp.content, [ RADIANT_NS, 'page-part' ])
|
73
|
+
pp_node.name = pp.name
|
74
|
+
#pp_node.set_attribute('filter', pp.filter)
|
75
|
+
p_node.set_attribute(pp.name, pp_node)
|
76
|
+
end
|
77
|
+
p_node.set_attribute('title', p.title)
|
78
|
+
p_node.set_attribute('breadcrumb', p.breadcrumb)
|
79
|
+
p_node.set_attribute('description', p.description)
|
80
|
+
p_node.set_attribute('keywords', p.keywords)
|
81
|
+
#p_node.set_attribute('layout', p.layout)
|
82
|
+
#p_node.set_attribute('page-type', p.page_type)
|
83
|
+
#p_node.set_attribute('status', p.status)
|
84
|
+
p_node
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: radiant-fabulator-extension
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- James Smith
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-08-10 00:00:00 +00:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: fabulator
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 27
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 0
|
33
|
+
- 2
|
34
|
+
version: 0.0.2
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: Creates a Fabulator page type allowing applications to be built using the fabulator engine.
|
38
|
+
email: jgsmith@tamu.edu
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files:
|
44
|
+
- README.rdoc
|
45
|
+
files:
|
46
|
+
- COPYING.markdown
|
47
|
+
- README.rdoc
|
48
|
+
- VERSION
|
49
|
+
- app/models/fabulator_context.rb
|
50
|
+
- app/models/fabulator_page.rb
|
51
|
+
- db/migrate/001_add_fabulator_fields.rb
|
52
|
+
- db/migrate/002_create_fabulator_contexts_table.rb
|
53
|
+
- fabulator_extension.rb
|
54
|
+
- lib/fabulator/radiant.rb
|
55
|
+
- lib/fabulator/radiant/actions.rb
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: http://github.com/jgsmith/radiant-fabulator
|
58
|
+
licenses: []
|
59
|
+
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options:
|
62
|
+
- --charset=UTF-8
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
hash: 3
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.3.7
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Fabulator Extension for Radiant CMS
|
90
|
+
test_files: []
|
91
|
+
|