• R/O
  • SSH
  • HTTPS

tenarai: 提交


Commit MetaInfo

修訂132 (tree)
時間2008-11-02 05:20:26
作者junkikuchi

Log Message

updated.

Change Summary

差異

--- trunk/init_model.rb (revision 131)
+++ trunk/init_model.rb (revision 132)
@@ -1,215 +1,155 @@
11
22 CONFIG[:init_model] = {
3- 'service' => [
3+ 'admin_service' => [
44 {
5- :name => 'yui-layout',
5+ :name => 'admin',
66 :class => 'Service',
7- :row => {'name' => 'yui-layout'},
8- :relation => {'default_view' => 'view:html.yui.layout'},
7+ :row => {'name' => 'admin'},
8+ :relation => {'default_view' => 'admin_view:admin'},
99 },
10- {
11- :name => 'yui-treeview',
12- :class => 'Service',
13- :row => {'name' => 'yui-treeview'},
14- :relation => {'default_view' => 'view:html.yui.treeview'},
15- },
1610 ],
1711
18- 'view' => [
12+ 'admin_view' => [
1913 {
20- :name => 'html',
14+ :name => 'admin',
2115 :class => 'View',
22- :row => {'name' => 'html'},
23- :relation => {'resource' => 'widget:html'},
16+ :row => {'name' => 'admin'},
17+ :relation => {'model' => 'admin_widget:html'},
2418 },
2519 {
26- :name => 'html.yui',
20+ :name => 'view',
2721 :class => 'View',
28- :row => {'name' => 'yui'},
29- :relation => {'resource' => 'widget:html'},
22+ :row => {'name' => 'view'},
23+ :relation => {'model' => 'admin_widget:view'},
3024 },
3125 {
32- :name => 'html.yui.layout',
26+ :name => 'view.json',
3327 :class => 'View',
34- :row => {'name' => 'layout'},
35- :relation => {'resource' => 'widget:html'},
28+ :row => {'name' => 'view-json'},
29+ :relation => {'model' => 'admin_widget:view'},
3630 },
3731 {
38- :name => 'html.yui.treeview',
32+ :name => 'view.widget',
3933 :class => 'View',
40- :row => {'name' => 'treeview'},
41- :relation => {'resource' => 'widget:html'},
34+ :row => {'name' => 'widget'},
35+ :relation => {'model' => 'admin_widget:view.widget'},
4236 },
37+ {
38+ :name => 'view.widget.json',
39+ :class => 'View',
40+ :row => {'name' => 'widget-json'},
41+ :relation => {'model' => 'admin_widget:view.widget'},
42+ },
4343 ],
4444
45- 'widget' => [
45+ 'admin_widget' => [
4646 {
4747 :name => 'html',
4848 :class => 'Widget::HTML',
49- :relation => {'view' => 'view:html'},
50- :css => [
51- ['*_font_text-align', 'left'],
52- ['ul li_margin_margin-left', '10px']
53- ],
49+ :relation => {'view' => 'admin_view:admin'},
5450 },
5551 {
56- :name => 'html.yui',
57- :class => 'Widget::Yahoo::UI::Loader',
58- :relation => {'view' => 'view:html.yui'},
52+ :name => 'html.google_ajax_loader',
53+ :class => 'Widget::HTML::JavaScript',
54+ :relation => {'view' => 'admin_view:admin'},
55+ :property => {
56+ :src => 'http://www.google.com/jsapi',
57+ },
5958 },
6059 {
61- :name => 'html.yui.reset_css',
62- :class => 'Widget::Yahoo::UI::ResetCSS',
63- :relation => {'view' => 'view:html.yui'},
64- },
65- {
66- :name => 'html.yui.layout',
67- :class => 'Widget::Yahoo::UI::Layout',
68- :relation => {'view' => 'view:html.yui.layout'},
69- :css => [
70- ['.yui-layout-unit-top_background_background-color', '#c3d9ff'],
71- ['.yui-layout-unit-right_background_background-color', '#e1ecfe'],
72- ['.yui-layout-unit-bottom_background_background-color', '#c3d9ff'],
73- ['.yui-layout-unit-left_background_background-color', '#e1ecfe'],
74- ['.yui-layout-unit .yui-resize-handle-r .yui-layout-resize-knob_background_background-color', '#c3d9ff'],
75- ['.yui-layout-unit .yui-resize-handle-r .yui-layout-resize-knob_layout_position', 'absolute'],
76- ['.yui-layout-unit .yui-resize-handle-r .yui-layout-resize-knob_size_height', '100%'],
77- ['.yui-layout-unit .yui-resize-handle-r .yui-layout-resize-knob_size_width', '5px'],
78- ['.yui-layout-unit .yui-resize-handle-r .yui-layout-resize-knob_layout_top', '0px'],
79- ['.yui-layout-unit .yui-resize-handle-r .yui-layout-resize-knob_layout_left', '0px'],
80- ['.yui-layout-unit .yui-resize-handle-l .yui-layout-resize-knob_background_background-color', '#c3d9ff'],
81- ['.yui-layout-unit .yui-resize-handle-l .yui-layout-resize-knob_layout_position', 'absolute'],
82- ['.yui-layout-unit .yui-resize-handle-l .yui-layout-resize-knob_size_height', '100%'],
83- ['.yui-layout-unit .yui-resize-handle-l .yui-layout-resize-knob_size_width', '5px'],
84- ['.yui-layout-unit .yui-resize-handle-l .yui-layout-resize-knob_layout_top', '0px'],
85- ['.yui-layout-unit .yui-resize-handle-l .yui-layout-resize-knob_layout_left', '0px'],
86- ],
60+ :name => 'html.yahoo_ui_loader',
61+ :class => 'Widget::HTML::JavaScript',
62+ :relation => {'view' => 'admin_view:admin'},
8763 :property => {
88- :top_body => "'hd'",
89- :top_height => '50',
90-
91- :right_body => "'right'",
92- :right_width => '200',
93- :right_resize => 'true',
94- :right_scroll => 'true',
95- :right_gutter => "'0 0 0 10px'",
96-
97- :bottom_body => "'ft'",
98- :bottom_height => '25',
99-
100- :left_body => "'left'",
101- :left_width => '200',
102- :left_resize => 'true',
103- :left_scroll => 'true',
104-
105- :center_body => "'center'"
106- }
64+ :src => 'http://yui.yahooapis.com/2.6.0/build/yuiloader/yuiloader-min.js',
65+ },
10766 },
10867 {
109- :name => 'html.doc',
110- :class => 'Widget::Panel',
111- :relation => {'view' => 'view:html.yui.layout'},
68+ :name => 'html.yahoo_ui_reset_css',
69+ :class => 'Widget::HTML::CSS',
70+ :relation => {'view' => 'admin_view:admin'},
11271 :property => {
113- :html_attr_id => 'doc3'
72+ :src => 'http://yui.yahooapis.com/2.6.0/build/reset-fonts-grids/reset-fonts-grids.css',
11473 }
11574 },
11675 {
117- :name => 'html.doc.hd',
118- :class => 'Widget::Panel',
119- :relation => {'view' => 'view:html.yui.layout'},
76+ :name => 'html.admin_css',
77+ :class => 'Widget::HTML::CSS',
78+ :relation => {'view' => 'admin_view:admin'},
12079 :property => {
121- :html_attr_id => 'hd'
80+ :src => 'http://bonnou.com/s/admin.css',
12281 }
12382 },
12483 {
125- :name => 'html.doc.bd',
126- :class => 'Widget::Panel',
127- :relation => {'view' => 'view:html.yui.layout'},
84+ :name => 'html.admin_js',
85+ :class => 'Widget::HTML::JavaScript',
86+ :relation => {'view' => 'admin_view:admin'},
12887 :property => {
129- :html_attr_id => 'bd'
130- }
88+ :src => 'http://bonnou.com/s/admin.js',
89+ },
13190 },
13291 {
133- :name => 'html.doc.bd.yui-main',
92+ :name => 'html.top',
13493 :class => 'Widget::Panel',
135- :relation => {'view' => 'view:html.yui.layout'},
94+ :relation => {'view' => 'admin_view:admin'},
13695 :property => {
137- :html_attr_id => 'yui-main'
96+ :html_attr_id => 'top'
13897 }
13998 },
14099 {
141- :name => 'html.doc.bd.yui-main.yui-b',
100+ :name => 'html.right',
142101 :class => 'Widget::Panel',
143- :relation => {'view' => 'view:html.yui.layout'},
102+ :relation => {'view' => 'admin_view:admin'},
144103 :property => {
145- :html_attr_class => 'yui-b'
104+ :html_attr_id => 'right'
146105 }
147106 },
148107 {
149- :name => 'html.doc.bd.yui-main.yui-b.yui-gb',
108+ :name => 'html.bottom',
150109 :class => 'Widget::Panel',
151- :relation => {'view' => 'view:html.yui.layout'},
110+ :relation => {'view' => 'admin_view:admin'},
152111 :property => {
153- :html_attr_class => 'yui-gb'
112+ :html_attr_id => 'bottom'
154113 }
155114 },
156115 {
157- :name => 'html.doc.bd.yui-main.yui-b.yui-gb.left',
116+ :name => 'html.left',
158117 :class => 'Widget::Panel',
159- :relation => {'view' => 'view:html.yui.layout'},
118+ :relation => {'view' => 'admin_view:admin'},
160119 :property => {
161- :html_attr_id => 'left',
162- :html_attr_class => 'yui-u first'
120+ :html_attr_id => 'left'
163121 }
164122 },
165123 {
166- :name => 'html.doc.bd.yui-main.yui-b.yui-gb.center',
124+ :name => 'html.center',
167125 :class => 'Widget::Panel',
168- :relation => {'view' => 'view:html.yui.layout'},
126+ :relation => {'view' => 'admin_view:admin'},
169127 :property => {
170- :html_attr_id => 'center',
171- :html_attr_class => 'yui-u'
128+ :html_attr_id => 'center'
172129 }
173130 },
131+
174132 {
175- :name => 'html.doc.bd.yui-main.yui-b.yui-gb.right',
176- :class => 'Widget::Panel',
177- :relation => {'view' => 'view:html.yui.layout'},
178- :property => {
179- :html_attr_id => 'right',
180- :html_attr_class => 'yui-u'
181- }
133+ :name => 'view',
134+ :class => 'Widget::View',
135+ :relation => {'view' => 'admin_view:view'},
136+ :property => {:model => 'admin_view'},
182137 },
183138 {
184- :name => 'html.doc.ft',
185- :class => 'Widget::Panel',
186- :relation => {'view' => 'view:html.yui.layout'},
187- :property => {
188- :html_attr_id => 'ft'
189- }
139+ :name => 'view.json',
140+ :class => 'Widget::View::JSON',
141+ :relation => {'view' => 'admin_view:view.json'},
190142 },
191-
192143 {
193- :name => 'html.yui.treeview',
194- :class => 'Widget::Yahoo::UI::TreeView',
195- :relation => {'view' => 'view:html.yui.treeview'},
144+ :name => 'view.widget',
145+ :class => 'Widget::Widget',
146+ :relation => {'view' => 'admin_view:view.widget'},
147+ :property => {:model => 'admin_widget'},
196148 },
197- ],
198-
199- 'content' => [
200- ],
201-
202- 'admin' => [
203149 {
204- :name => 'admin',
205- :class => 'User',
206- :row => {
207- 'name' => 'admin',
208- '_password' => Tenarai::ClassLoader['User'].digest('admin')
209- },
210- :relation => {},
150+ :name => 'view.widget.json',
151+ :class => 'Widget::Widget::JSON',
152+ :relation => {'view' => 'admin_view:view.widget.json'},
211153 },
212154 ],
213-
214- 'user' => [],
215155 }
--- trunk/lib/gmap_polyline_encoder.rb (nonexistent)
+++ trunk/lib/gmap_polyline_encoder.rb (revision 132)
@@ -0,0 +1,394 @@
1+#--
2+#
3+# Utility for creating Google Maps Encoded GPolylines
4+#
5+# License: You may distribute this code under the same terms as Ruby itself
6+#
7+# Author: Joel Rosenberg
8+#
9+# ( Drawing from the official example pages as well as Mark McClure's work )
10+#
11+# == Example
12+#
13+# data = [
14+# [ 37.4419, -122.1419],
15+# [ 37.4519, -122.1519],
16+# [ 37.4619, -122.1819],
17+# ]
18+#
19+# encoder = GMapPolylineEncoder.new()
20+# result = encoder.encode( data )
21+#
22+# javascript << " var myLine = new GPolyline.fromEncoded({\n"
23+# javascript << " color: \"#FF0000\",\n"
24+# javascript << " weight: 10,\n"
25+# javascript << " opacity: 0.5,\n"
26+# javascript << " zoomFactor: #{result[:zoomFactor]},\n"
27+# javascript << " numLevels: #{result[:numLevels]},\n"
28+# javascript << " points: \"#{result[:points]}\",\n"
29+# javascript << " levels: \"#{result[:levels]}\"\n"
30+# javascript << " });"
31+#
32+# == Methods
33+#
34+# Constructor args (all optional):
35+# :numLevels (default 18)
36+# :zoomFactor (default 2)
37+# :reduce: Reduce points (default true)
38+# :escape: Escape backslashes (default true)
39+#
40+# encode( points ) method
41+# points (required): array of longitude, latitude pairs
42+#
43+# returns hash with keys :points, :levels, :zoomFactor, :numLevels
44+#
45+# == Background
46+#
47+# Description: http://www.google.com/apis/maps/documentation/#Encoded_Polylines
48+# API: http://www.google.com/apis/maps/documentation/reference.html#GPolyline
49+# Hints: http://www.google.com/apis/maps/documentation/polylinealgorithm.html
50+#
51+# Example Javascript for instantiating an encoded polyline:
52+# var encodedPolyline = new GPolyline.fromEncoded({
53+# color: "#FF0000",
54+# weight: 10,
55+# points: "yzocFzynhVq}@n}@o}@nzD",
56+# levels: "BBB",
57+# zoomFactor: 32,
58+# numLevels: 4
59+# });
60+#
61+# == Changes
62+#
63+# 08.14.2007 - Release 0.2
64+# Doug Fales pointed out a null pointer exception bug in the zoom
65+# factor implementation
66+#
67+# 06.29.2007 - Release 0.1
68+# Profiling showed that distance() accounted for 50% of the time when
69+# processing McClure's British coast data. By moving the distance
70+# calculation into encode(), we can cache a few of the calculations
71+# (magnitude) and eliminate the overhead of the function call. This
72+# reduced the time to encode by ~ 30%
73+#
74+# 06.21.2007 Implementing the Doublas-Peucker algorithm for removing superflous
75+# points as per Mark McClure's design:
76+# http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/
77+#
78+# 10.14.2006 Cleaned up (and finally grasped) zoom levels
79+#
80+# 09.2006 First port of the official example's javascript. Ignoring zoom
81+# levels for now, showing points at all zoom levels
82+#
83+#++
84+
85+class GMapPolylineEncoder
86+ attr_accessor :reduce, :escape #zoomFactor and numLevels need side effects
87+ attr_reader :zoomFactor, :numLevels
88+
89+ # The minimum distance from the line that a point must exceed to avoid
90+ # elimination under the DP Algorithm.
91+ @@dp_threshold = 0.00001
92+
93+ def initialize(options = {})
94+ # There are no required parameters
95+
96+ # Nice defaults
97+ @numLevels = options.has_key?(:numLevels) ? options[:numLevels] : 18
98+ @zoomFactor = options.has_key?(:zoomFactor) ? options[:zoomFactor] : 2
99+
100+ # Calculate the distance thresholds for each zoom level
101+ calculate_zoom_breaks()
102+
103+ # By default we'll simplify the polyline unless told otherwise
104+ @reduce = ! options.has_key?(:reduce) ? true : options[:reduce]
105+
106+ # Escape by default; most people are using this in a web context
107+ @escape = ! options.has_key?(:escape) ? true : options[:escape]
108+
109+ end
110+
111+ def numLevels=( new_num_levels )
112+ @numLevels = new_num_levels
113+ # We need to recalculate our zoom breaks
114+ calculate_zoom_breaks()
115+ end
116+
117+ def zoomFactor=( new_zoom_factor )
118+ @zoomFactor = new_zoom_factor
119+ # We need to recalculate our zoom breaks
120+ calculate_zoom_breaks()
121+ end
122+
123+ def encode( points )
124+
125+ #
126+ # This is an implementation of the Douglas-Peucker algorithm for simplifying
127+ # a line. You can thing of it as an elimination of points that do not
128+ # deviate enough from a vector. That threshold for point elimination is in
129+ # @@dp_threshold. See
130+ #
131+ # http://everything2.com/index.pl?node_id=859282
132+ #
133+ # for an explanation of the algorithm
134+ #
135+
136+ max_dist = 0 # Greatest distance we measured during the run
137+ stack = []
138+ distances = Array.new(points.size)
139+
140+ if(points.length > 2)
141+ stack << [0, points.size-1]
142+
143+ while(stack.length > 0)
144+ current_line = stack.pop()
145+ p1_idx = current_line[0]
146+ pn_idx = current_line[1]
147+ pb_dist = 0
148+ pb_idx = nil
149+
150+ x1 = points[p1_idx][0]
151+ y1 = points[p1_idx][1]
152+ x2 = points[pn_idx][0]
153+ y2 = points[pn_idx][1]
154+
155+ # Caching the line's magnitude for performance
156+ magnitude = Math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
157+ magnitude_squared = magnitude ** 2
158+
159+ # Find the farthest point and its distance from the line between our pair
160+ for i in (p1_idx+1)..(pn_idx-1)
161+
162+ # Refactoring distance computation inline for performance
163+ #current_distance = compute_distance(points[i], points[p1_idx], points[pn_idx])
164+
165+ #
166+ # This uses Euclidian geometry. It shouldn't be that big of a deal since
167+ # we're using it as a rough comparison for line elimination and zoom
168+ # calculation.
169+ #
170+ # TODO: Implement Haversine functions which would probably bring this to
171+ # a snail's pace (ehhhh)
172+ #
173+
174+ px = points[i][0]
175+ py = points[i][1]
176+
177+ current_distance = nil
178+
179+ if( magnitude == 0 )
180+ # The line is really just a point
181+ current_distance = Math.sqrt((x2-px)**2 + (y2-py)**2)
182+ else
183+
184+ u = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1))) / magnitude_squared
185+
186+ if( u <= 0 || u > 1 )
187+ # The point is closest to an endpoint. Find out which one
188+ ix = Math.sqrt((x1 - px)**2 + (y1 - py)**2)
189+ iy = Math.sqrt((x2 - px)**2 + (y2 - py)**2)
190+ if( ix > iy )
191+ current_distance = iy
192+ else
193+ current_distance = ix
194+ end
195+ else
196+ # The perpendicular point intersects the line
197+ ix = x1 + u * (x2 - x1)
198+ iy = y1 + u * (y2 - y1)
199+ current_distance = Math.sqrt((ix - px)**2 + (iy - py)**2)
200+ end
201+ end
202+
203+ # See if this distance is the greatest for this segment so far
204+ if(current_distance > pb_dist)
205+ pb_dist = current_distance
206+ pb_idx = i
207+ end
208+ end
209+
210+ # See if this is the greatest distance for all points
211+ if(pb_dist > max_dist)
212+ max_dist = pb_dist
213+ end
214+
215+ if(pb_dist > @@dp_threshold)
216+ # Our point, Pb, that had the greatest distance from the line, is also
217+ # greater than our threshold. Process again using Pb as a new
218+ # start/end point. Record this distance - we'll use it later when
219+ # creating zoom values
220+ distances[pb_idx] = pb_dist
221+ stack << [p1_idx, pb_idx]
222+ stack << [pb_idx, pn_idx]
223+ end
224+
225+ end
226+ end
227+
228+ # Force line endpoints to be included (sloppy, but faster than checking for
229+ # endpoints in encode_points())
230+ distances[0] = max_dist
231+ distances[distances.length-1] = max_dist
232+
233+ # Create Base64 encoded strings for our points and zoom levels
234+ points_enc = encode_points( points, distances)
235+ levels_enc = encode_levels( points, distances, max_dist)
236+
237+ # Make points_enc an escaped string if desired.
238+ # We should escape the levels too, in case google pulls a switcheroo
239+ @escape && points_enc && points_enc.gsub!( /\\/, '\\\\\\\\' )
240+
241+
242+ # Returning a hash. Yes, I am a Perl programmer
243+ return {
244+ :points => points_enc,
245+ :levels => levels_enc,
246+ :zoomFactor => @zoomFactor,
247+ :numLevels => @numLevels,
248+ }
249+
250+ end
251+
252+ private
253+
254+ def calculate_zoom_breaks()
255+ # Calculate the distance thresholds for each zoom level
256+ @zoom_level_breaks = Array.new(@numLevels);
257+
258+ for i in 0..(@numLevels-1)
259+ @zoom_level_breaks[i] = @@dp_threshold * (@zoomFactor ** ( @numLevels-i-1));
260+ end
261+
262+ return
263+ end
264+
265+ def encode_points( points, distances )
266+ encoded = ""
267+
268+ plat = 0
269+ plon = 0
270+
271+ #points.each do |point| # Gah, need the distances.
272+ for i in 0..(points.size() - 1)
273+ if(! @reduce || distances[i] != nil )
274+ point = points[i]
275+ late5 = (point[0] * 1e5).floor();
276+ lone5 = (point[1] * 1e5).floor();
277+
278+ dlat = late5 - plat
279+ dlon = lone5 - plon
280+
281+ plat = late5;
282+ plon = lone5;
283+
284+ # I used to need this for some reason
285+ #encoded << encodeSignedNumber(Fixnum.induced_from(dlat)).to_s
286+ #encoded << encodeSignedNumber(Fixnum.induced_from(dlon)).to_s
287+ encoded << encodeSignedNumber(dlat).to_s
288+ encoded << encodeSignedNumber(dlon).to_s
289+ end
290+ end
291+
292+ return encoded
293+
294+ end
295+
296+ def encode_levels( points, distances, max_dist )
297+
298+ encoded = "";
299+
300+ # Force startpoint
301+ encoded << encodeNumber(@numLevels - 1)
302+
303+ if( points.size() > 2 )
304+ for i in 1..(points.size() - 2)
305+ distance = distances[i]
306+ if( ! @reduce || distance != nil)
307+ computed_level = 0
308+
309+ while (distance and (distance < @zoom_level_breaks[computed_level])) do
310+ computed_level += 1
311+ end
312+
313+ encoded << encodeNumber( @numLevels - computed_level - 1 )
314+ end
315+ end
316+ end
317+
318+ # Force endpoint
319+ encoded << encodeNumber(@numLevels - 1)
320+
321+ return encoded;
322+
323+ end
324+
325+ def compute_distance( point, lineStart, lineEnd )
326+
327+ #
328+ # Note: This has been refactored to encode() inline for performance and
329+ # computation caching
330+ #
331+
332+ px = point[0]
333+ py = point[1]
334+ x1 = lineStart[0]
335+ y1 = lineStart[1]
336+ x2 = lineEnd[0]
337+ y2 = lineEnd[1]
338+
339+ distance = nil
340+
341+ magnitude = Math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
342+
343+ if( magnitude == 0 )
344+ return Math.sqrt((x2-px)**2 + (y2-py)**2)
345+ end
346+
347+ u = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1))) / (magnitude**2)
348+
349+ if( u <= 0 || u > 1 )
350+ # The point is closest to an endpoint. Find out which
351+ ix = Math.sqrt((x1 - px)**2 + (y1 - py)**2)
352+ iy = Math.sqrt((x2 - px)**2 + (y2 - py)**2)
353+ if( ix > iy )
354+ distance = iy
355+ else
356+ distance = ix
357+ end
358+ else
359+ # The perpendicular point intersects the line
360+ ix = x1 + u * (x2 - x1)
361+ iy = y1 + u * (y2 - y1)
362+ distance = Math.sqrt((ix - px)**2 + (iy - py)**2)
363+ end
364+
365+ return distance
366+ end
367+
368+ def encodeSignedNumber(num)
369+ # Based on the official google example
370+
371+ sgn_num = num << 1
372+
373+ if( num < 0 )
374+ sgn_num = ~(sgn_num)
375+ end
376+
377+ return encodeNumber(sgn_num)
378+ end
379+
380+ def encodeNumber(num)
381+ # Based on the official google example
382+
383+ encoded = "";
384+
385+ while (num >= 0x20) do
386+ encoded << ((0x20 | (num & 0x1f)) + 63).chr;
387+ num = num >> 5;
388+ end
389+
390+ encoded << (num + 63).chr;
391+ return encoded;
392+ end
393+
394+end
--- trunk/lib/simple-json.rb (nonexistent)
+++ trunk/lib/simple-json.rb (revision 132)
@@ -0,0 +1,285 @@
1+# = Simple JSON parser & builder
2+#
3+# Author:: Chihiro Ito
4+# License:: Public domain (unlike other files)
5+# Support:: http://groups.google.com/group/webos-goodies/
6+#
7+# This file contains two simple JSON processing classes. JsonParser
8+# converts a JSON string to an array or a hash. JsonBuilder performs
9+# vice versa. These classes are standard compliant and are designed for
10+# stability and reliability. Especially JsonParser has UTF-8 validation
11+# functionality so you can avoid some kind of security attack.
12+
13+require 'strscan'
14+
15+
16+# = Simple JSON parser
17+#
18+# This class converts a JSON string to an array or a hash. If *json_str*
19+# contains a JSON form string, you can convert it like below.
20+#
21+# ruby_obj = JsonParser.new.parse(json_str)
22+#
23+# If *json_str* has one or more invalid UTF-8 sequence, JsonParser throws
24+# exception by default. You can change this behavior to replacing with an
25+# arbitrary unicode character. See below for details.
26+class JsonParser
27+
28+ #:stopdoc:
29+ Debug = false
30+ Name = 'JsonParser'
31+ ERR_IllegalSyntax = "[#{Name}] Syntax error"
32+ ERR_IllegalUnicode = "[#{Name}] Illegal unicode sequence"
33+
34+ StringRegex = /\s*"((?:\\.|[^"\\])*)"/n
35+ ValueRegex = /\s*(?:
36+ (true)|(false)|(null)| # 1:true, 2:false, 3:null
37+ (?:\"((?:\\.|[^\"\\])*)\")| # 4:String
38+ ([-+]?\d+\.\d+(?:[eE][-+]?\d+)?)| # 5:Float
39+ ([-+]?\d+)| # 6:Integer
40+ (\{)|(\[))/xn # 7:Hash, 8:Array
41+ #:startdoc:
42+
43+ # Create a new instance of JsonParser. *options* can contain these values.
44+ # [:validation]
45+ # If set to false, UTF-8 validation is disabled. true by default.
46+ # [:surrogate]
47+ # If set to false, surrogate pair support is disabled. true by default.
48+ # [:malformed_chr]
49+ # An invalid sequence in JSON string will be replaced with this value.
50+ # If set to nil, An exception will be thrown in this situation.
51+ # nil by default.
52+ def initialize(options = {})
53+ @default_validation = options.has_key?(:validation) ? options[:validation] : true
54+ @default_surrogate = options.has_key?(:surrogate) ? options[:surrogate] : true
55+ @default_malformed_chr = options.has_key?(:malformed_chr) ? options[:malformed_chr] : nil
56+ end
57+
58+ # Convert *str* to an array or hash.
59+ # [str]
60+ # A JSON form string. This must be encoded using UTF-8.
61+ # [options]
62+ # Same as new.
63+ def parse(str, options = {})
64+ @enable_validation = options.has_key?(:validation) ? options[:validation] : @default_validation
65+ @enable_surrogate = options.has_key?(:surrogate) ? options[:surrogate] : @default_surrogate
66+ @malformed_chr = options.has_key?(:malformed_chr) ? options[:malformed_chr] : @default_malformed_chr
67+ @malformed_chr = @malformed_chr[0] if String === @malformed_chr
68+ @scanner = StringScanner.new(str)
69+ obj = case get_symbol[0]
70+ when ?{ then parse_hash
71+ when ?[ then parse_array
72+ else raise err_msg(ERR_IllegalSyntax)
73+ end
74+ @scanner = nil
75+ obj
76+ end
77+
78+ private #---------------------------------------------------------
79+
80+ def validate_string(str, malformed_chr = nil)
81+ code = 0
82+ rest = 0
83+ range = nil
84+ ucs = []
85+ str.each_byte do |c|
86+ if rest <= 0
87+ case c
88+ when 0x01..0x7f then rest = 0 ; ucs << c
89+ when 0xc0..0xdf then rest = 1 ; code = c & 0x1f ; range = 0x00080..0x0007ff
90+ when 0xe0..0xef then rest = 2 ; code = c & 0x0f ; range = 0x00800..0x00ffff
91+ when 0xf0..0xf7 then rest = 3 ; code = c & 0x07 ; range = 0x10000..0x10ffff
92+ else ucs << handle_malformed_chr(malformed_chr)
93+ end
94+ elsif (0x80..0xbf) === c
95+ code = (code << 6) | (c & 0x3f)
96+ if (rest -= 1) <= 0
97+ if !(range === code) || (0xd800..0xdfff) === code
98+ code = handle_malformed_chr(malformed_chr)
99+ end
100+ ucs << code
101+ end
102+ else
103+ ucs << handle_malformed_chr(malformed_chr)
104+ rest = 0
105+ end
106+ end
107+ ucs << handle_malformed_chr(malformed_chr) if rest > 0
108+ ucs.pack('U*')
109+ end
110+
111+ def handle_malformed_chr(chr)
112+ raise err_msg(ERR_IllegalUnicode) unless chr
113+ chr
114+ end
115+
116+ def err_msg(err)
117+ err + (Debug ? " #{@scanner.string[[0, @scanner.pos - 8].max,16].inspect}" : "")
118+ end
119+
120+ def unescape_string(str)
121+ str = str.gsub(/\\(["\\\/bfnrt])/n) do
122+ $1.tr('"\\/bfnrt', "\"\\/\b\f\n\r\t")
123+ end.gsub(/(\\u[0-9a-fA-F]{4})+/n) do |matched|
124+ seq = matched.scan(/\\u([0-9a-fA-F]{4})/n).flatten.map { |c| c.hex }
125+ if @enable_surrogate
126+ seq.each_index do |index|
127+ if seq[index] && (0xd800..0xdbff) === seq[index]
128+ n = index + 1
129+ raise err_msg(ERR_IllegalUnicode) unless seq[n] && 0xdc00..0xdfff === seq[n]
130+ seq[index] = 0x10000 + ((seq[index] & 0x03ff) << 10) + (seq[n] & 0x03ff)
131+ seq[n] = nil
132+ end
133+ end.compact!
134+ end
135+ seq.pack('U*')
136+ end
137+ str = validate_string(str, @malformed_chr) if @enable_validation
138+ str
139+ end
140+
141+ def get_symbol
142+ raise err_msg(ERR_IllegalSyntax) unless @scanner.scan(/\s*(.)/n)
143+ @scanner[1]
144+ end
145+
146+ def peek_symbol
147+ @scanner.match?(/\s*(.)/n) ? @scanner[1] : nil
148+ end
149+
150+ def parse_string
151+ raise err_msg(ERR_IllegalSyntax) unless @scanner.scan(StringRegex)
152+ unescape_string(@scanner[1])
153+ end
154+
155+ def parse_value
156+ raise err_msg(ERR_IllegalSyntax) unless @scanner.scan(ValueRegex)
157+ case
158+ when @scanner[1] then true
159+ when @scanner[2] then false
160+ when @scanner[3] then nil
161+ when @scanner[4] then unescape_string(@scanner[4])
162+ when @scanner[5] then @scanner[5].to_f
163+ when @scanner[6] then @scanner[6].to_i
164+ when @scanner[7] then parse_hash
165+ when @scanner[8] then parse_array
166+ else raise err_msg(ERR_IllegalSyntax)
167+ end
168+ end
169+
170+ def parse_hash
171+ obj = {}
172+ if peek_symbol[0] == ?} then get_symbol ; return obj ; end
173+ while true
174+ index = parse_string
175+ raise err_msg(ERR_IllegalSyntax) unless get_symbol[0] == ?:
176+ value = parse_value
177+ obj[index] = value
178+ case get_symbol[0]
179+ when ?} then return obj
180+ when ?, then next
181+ else raise err_msg(ERR_IllegalSyntax)
182+ end
183+ end
184+ end
185+
186+ def parse_array
187+ obj = []
188+ if peek_symbol[0] == ?] then get_symbol ; return obj ; end
189+ while true
190+ obj << parse_value
191+ case get_symbol[0]
192+ when ?] then return obj
193+ when ?, then next
194+ else raise err_msg(ERR_IllegalSyntax)
195+ end
196+ end
197+ end
198+
199+end
200+
201+# = Simple JSON builder
202+#
203+# This class converts an Ruby object to a JSON string. you can convert
204+# *ruby_obj* like below.
205+#
206+# json_str = JsonBuilder.new.build(ruby_obj)
207+#
208+# *ruby_obj* must satisfy these conditions.
209+# - It must support to_s method, otherwise must be an array, a hash or nil.
210+# - All keys of a hash must support to_s method.
211+# - All values of an array or a hash must satisfy all conditions mentioned above.
212+#
213+# If the *ruby_obj* is not an array or a hash, it will be converted to an array
214+# with a single element.
215+class JsonBuilder
216+
217+ #:stopdoc:
218+ Name = 'JsonBuilder'
219+ ERR_NestIsTooDeep = "[#{Name}] Array / Hash nested too deep."
220+ ERR_NaN = "[#{Name}] NaN and Infinite are not permitted in JSON."
221+ #:startdoc:
222+
223+ # Create a new instance of JsonBuilder. *options* can contain these values.
224+ # [:max_nest]
225+ # If Array / Hash is nested more than this value, an exception would be thrown.
226+ # 64 by default.
227+ # [:nan]
228+ # NaN is replaced with this value. If nil or false, an exception will be thrown.
229+ # nil by default.
230+ def initialize(options = {})
231+ @default_max_nest = options.has_key?(:max_nest) ? options[:max_nest] : 64
232+ @default_nan = options.has_key?(:nan) ? options[:nan] : nil
233+ end
234+
235+ # Convert *obj* to a JSON form string.
236+ # [obj]
237+ # A ruby object. this object must satisfy all conditions mentioned above.
238+ # [options]
239+ # Same as new.
240+ def build(obj, options = {})
241+ @max_nest = options.has_key?(:max_nest) ? options[:max_nest] : @default_max_nest
242+ @nan = options.has_key?(:nan) ? options[:nan] : @default_nan
243+ case obj
244+ when Array then build_array(obj, 0)
245+ when Hash then build_object(obj, 0)
246+ else build_array([obj], 0)
247+ end
248+ end
249+
250+ private #---------------------------------------------------------
251+
252+ def escape(str)
253+ str.gsub(/[^\x20-\x21\x23-\x5b\x5d-\xff]/n) do |chr|
254+ if chr[0] != 0 && (index = "\"\\/\b\f\n\r\t".index(chr[0]))
255+ "\\" + '"\\/bfnrt'[index, 1]
256+ else
257+ sprintf("\\u%04x", chr[0])
258+ end
259+ end
260+ end
261+
262+ def build_value(obj, level)
263+ case obj
264+ when Integer, TrueClass, FalseClass then obj.to_s
265+ when Float then raise ERR_NaN unless obj.finite? || (obj = @nan) ; obj.to_s
266+ when NilClass then 'null'
267+ when Array then build_array(obj, level + 1)
268+ when Hash then build_object(obj, level + 1)
269+ else "\"#{escape(obj.to_s)}\""
270+ end
271+ end
272+
273+ def build_array(obj, level)
274+ raise ERR_NestIsTooDeep if level >= @max_nest
275+ '[' + obj.map { |item| build_value(item, level) }.join(',') + ']'
276+ end
277+
278+ def build_object(obj, level)
279+ raise ERR_NestIsTooDeep if level >= @max_nest
280+ '{' + obj.map do |item|
281+ "#{build_value(item[0].to_s,level)}:#{build_value(item[1],level)}"
282+ end.join(',') + '}'
283+ end
284+
285+end
--- trunk/main.rb (revision 131)
+++ trunk/main.rb (revision 132)
@@ -21,13 +21,37 @@
2121 @config = config
2222 end
2323
24+ def dispatch(models, request, response)
25+ _nil, name, *params = if request['path_info'].nil?
26+ '/'
27+ else
28+ if request['path_info'].empty?
29+ request['script_name']
30+ else
31+ request['path_info']
32+ end
33+ end.split('/')
34+
35+ name ||= 'index'
36+
37+ if name == 'admin'
38+ service = models['admin_service']
39+ view = models['admin_view']
40+ else
41+ service = models['user_service']
42+ view = models['user_view']
43+ end
44+
45+ service.dispatch(name, view, models, params, request, response)
46+ end
47+
2448 def server(name)
25- model do |m|
26- m['service'].pub_dir = @config[:server][:pub_dir]
49+ db do |models|
2750 Tenarai::Server.new(@config[:server]) do |request, response|
2851 begin
29- m['service'].dispatch(m, request, response)
30- m.refresh
52+ response.code = 404
53+ dispatch(models, request, response)
54+ models.refresh
3155 rescue
3256 p $!
3357 $!.backtrace.each do |val|
@@ -50,36 +74,36 @@
5074 server('mongrel')
5175 end
5276
53- def model(&block)
77+ def db(&block)
5478 Tenarai::DB.new(@config[:db]) do |db|
55- block.call(Tenarai::DB::Container.new(db, @config[:model]))
79+ block.call(Tenarai::DB::Container.new(db, @config[:models]))
5680 end
5781 end
5882
5983 def install
60- model do |m|
61- m.create
84+ db do |models|
85+ models.create
6286 end
6387 end
6488
6589 def uninstall
66- model do |m|
67- m.drop
90+ db do |models|
91+ models.drop
6892 end
6993 end
7094
7195 def load
72- model do |m|
96+ db do |models|
7397 map = {}
74- create_row(m, map, @config[:init_model])
75- update_property(m, map, @config[:init_model])
98+ create_row(models, map, @config[:init_model])
99+ update_property(models, map, @config[:init_model])
76100 end
77101 end
78102
79- def create_row(model, map, init_model)
103+ def create_row(models, map, init_model)
80104 init_model.each do |key, val|
81105 val.each do |v|
82- obj = Tenarai::ClassLoader[v[:class]].new(model[key], v[:row] || {})
106+ obj = Tenarai::ClassLoader[v[:class]].new(models[key], v[:row] || {})
83107 obj.save
84108
85109 map[key] ||= {}
@@ -101,7 +125,7 @@
101125 end
102126 private :create_row
103127
104- def update_property(model, map, init_model)
128+ def update_property(models, map, init_model)
105129 init_model.each do |key, val|
106130 val.each do |v|
107131 v[:relation].each do |col, rel|
--- trunk/pub/admin.css (nonexistent)
+++ trunk/pub/admin.css (revision 132)
@@ -0,0 +1,40 @@
1+/* Copyright (C) 2008 Jun Kikuchi <kikuchi@bonnou.com> */
2+
3+* {
4+ text-align: left;
5+}
6+
7+h2 {
8+ background-color: #e1ecfe;
9+ padding: 5px;
10+}
11+
12+#right h2 {
13+ padding-left: 10px;
14+}
15+
16+.yui-layout-unit-top {
17+ background-color: #c3d9ff;
18+}
19+
20+.yui-layout-unit-bottom {
21+ background-color: #c3d9ff;
22+}
23+
24+.yui-layout-unit .yui-resize-handle-r .yui-layout-resize-knob {
25+ background-color: #c3d9ff;
26+ height: 100%;
27+ left: 0px;
28+ position: absolute;
29+ top: 0px;
30+ width: 5px;
31+}
32+
33+.yui-layout-unit .yui-resize-handle-l .yui-layout-resize-knob {
34+ background-color: #c3d9ff;
35+ height: 100%;
36+ left: 0px;
37+ position: absolute;
38+ top: 0px;
39+ width: 5px;
40+}
--- trunk/pub/admin.js (nonexistent)
+++ trunk/pub/admin.js (revision 132)
@@ -0,0 +1,249 @@
1+/* Copyright (C) 2008 Jun Kikuchi <kikuchi@bonnou.com> */
2+
3+(function() {
4+ var config = {
5+ view: {
6+ view: {
7+ url: '/admin/view-json/',
8+ },
9+ widget: {
10+ url: '/admin/widget-json/'
11+ }
12+ }
13+ }
14+
15+ google.load('jquery', '1.2');
16+ google.setOnLoadCallback(function() {
17+ (new YAHOO.util.YUILoader({
18+ require: ['layout', 'resize', 'treeview', 'tabview'],
19+ onSuccess: function() {
20+ var Dom = YAHOO.util.Dom;
21+ var Event = YAHOO.util.Event;
22+
23+ Event.onDOMReady(function() {
24+ (new App(config)).render();
25+ });
26+ }
27+ })).insert();
28+ });
29+
30+ /*
31+ * App
32+ */
33+ var App = function() {
34+ this.init.apply(this, arguments);
35+ }
36+
37+ App.prototype = {
38+ init: function(config) {
39+ this.layout = new App.Layout(this);
40+ this.client = new App.View(this, config.view);
41+ },
42+
43+ render: function() {
44+ this.layout.render();
45+ this.client.render();
46+ }
47+ }
48+
49+ /*
50+ * App.Layout
51+ */
52+ App.Layout = function() {
53+ this.init.apply(this, arguments);
54+ }
55+
56+ App.Layout.prototype = {
57+ init: function(app) {
58+ this.app = app;
59+
60+ this.units = {
61+ top: {
62+ position: 'top',
63+ body: 'top',
64+ height: 50
65+ },
66+ right: {
67+ position: 'right',
68+ body: 'right',
69+ width: 200,
70+ resize: true,
71+ scroll: true
72+ },
73+ bottom: {
74+ position: 'bottom',
75+ body: 'bottom',
76+ height: 25
77+ },
78+ left: {
79+ position: 'left',
80+ body: 'left',
81+ width: 200,
82+ resize: true,
83+ scroll: true
84+ },
85+ center: {
86+ position: 'center',
87+ body: 'center'
88+ }
89+ };
90+
91+ this.layout = new YAHOO.widget.Layout({
92+ units: [
93+ this.units.top,
94+ this.units.right,
95+ this.units.bottom,
96+ this.units.left,
97+ this.units.center
98+ ]
99+ });
100+ },
101+
102+ render: function() {
103+ this.layout.render();
104+ }
105+ }
106+
107+ /*
108+ * App.View
109+ */
110+ App.View = function() {
111+ this.init.apply(this, arguments);
112+ }
113+
114+ App.View.prototype = {
115+ init: function(app, config) {
116+ this.app = app;
117+ this.config = config;
118+
119+ /* View Tree */
120+ $('#left').append('<h2>View</h2>');
121+ $('#left').append('<div id="view"><div id="view-tree"></div></div>');
122+ this.view_tree = new App.View.ViewTree(
123+ this,
124+ 'view-tree',
125+ config.view.url
126+ );
127+
128+ // to make view tree resizable
129+ new YAHOO.util.Resize(
130+ 'view',
131+ {
132+ handles: ['b'],
133+ ratio: false,
134+ setSize: false
135+ }
136+ );
137+ $('#view').css('overflow', 'auto');
138+ $('#view .yui-resize-handle-b').css('background-color', '#c3d9ff');
139+
140+ /* Widget Tree */
141+ $('#left').append('<h2>Widget</h2>');
142+ $('#left').append('<div id="widget-tree"></div>');
143+ this.widget_tree = new App.View.WidgetTree(
144+ this,
145+ 'widget-tree',
146+ config.widget.url
147+ );
148+
149+ /* Property Table */
150+ $('#right').append('<h2>Property</h2>');
151+ $('#right').append('<div id="property"></div>');
152+
153+ /* Style Table */
154+ $('#right').append('<h2>Style</h2>');
155+ $('#right').append('<div id="style"></div>');
156+ },
157+
158+ render: function() {
159+ this.view_tree.render();
160+ this.widget_tree.render();
161+ },
162+
163+ on_click_view: function(id) {
164+ this.widget_tree.update(id);
165+ },
166+
167+ on_click_widget: function(id) {
168+ alert(id);
169+ }
170+ }
171+
172+ /*
173+ * App.View.ViewTree
174+ */
175+ App.View.ViewTree = function() {
176+ this.init.apply(this, arguments);
177+ }
178+
179+ App.View.ViewTree.prototype = {
180+ init: function(client, tag_id, url) {
181+ this.client = client;
182+ this.tag_id = tag_id;
183+ this.url = url
184+
185+ this.tree = new YAHOO.widget.TreeView(this.tag_id);
186+ this.tree.subscribe('labelClick', function(node) {
187+ client.on_click_view(node.data.id);
188+ });
189+ this.tree.subscribe('expand', function(node) { return false; });
190+ this.tree.subscribe('collapse', function(node) { return false; });
191+ },
192+
193+ update: function() {
194+ var tree = this.tree;
195+
196+ $.ajax({
197+ dataType: 'json',
198+ url: this.url,
199+ success: function(data, textStatus) {
200+ tree.removeChildren(tree.getRoot());
201+ tree.buildTreeFromObject(data);
202+ tree.render();
203+ }
204+ });
205+ },
206+
207+ render: function() {
208+ this.update();
209+ }
210+ }
211+
212+ /*
213+ * App.View.WidgetTree
214+ */
215+ App.View.WidgetTree = function() {
216+ this.init.apply(this, arguments);
217+ }
218+
219+ App.View.WidgetTree.prototype = {
220+ init: function(client, tag_id, url) {
221+ this.client = client;
222+ this.tag_id = tag_id;
223+ this.url = url;
224+
225+ this.tree = new YAHOO.widget.TreeView(this.tag_id);
226+ this.tree.subscribe('labelClick', function(node) {
227+ client.on_click_widget(node.data.id);
228+ });
229+ this.tree.subscribe('expand', function(node) { return false; });
230+ this.tree.subscribe('collapse', function(node) { return false; });
231+ },
232+
233+ update: function(view_id) {
234+ var tree = this.tree;
235+
236+ $.ajax({
237+ dataType: 'json',
238+ url: this.url + view_id,
239+ success: function(data, textStatus) {
240+ tree.removeChildren(tree.getRoot());
241+ tree.buildTreeFromObject(data);
242+ tree.render();
243+ }
244+ });
245+ },
246+
247+ render: function() {}
248+ }
249+})();
--- trunk/config.rb (revision 131)
+++ trunk/config.rb (revision 132)
@@ -14,36 +14,49 @@
1414 :address => '0.0.0.0',
1515 :port => 8080,
1616 #:port => 8080..8085,
17- :pub_dir => "#{BASE_DIR}/pub",
17+ #:pub_dir => "#{BASE_DIR}/pub",
1818 :app_dir => "#{BASE_DIR}/webapp",
1919 }
2020
21-CONFIG[:model] = {
21+CONFIG[:models] = {
2222 :table => {
23- 'service' => {:row => 'Service', :class => 'ServiceTable'},
24- 'user' => {:row => 'User', :class => 'UserTable' },
25- 'usersession' => {:row => 'Session', :class => 'SessionTable'},
26- 'admin' => {:row => 'User', :class => 'UserTable' },
27- 'adminsession' => {:row => 'Session', :class => 'SessionTable'},
28- 'content' => {:row => 'Content'},
29- 'view' => {:row => 'View' },
30- 'widget' => {:row => 'Widget' },
23+ 'admin_account' => {:row => 'User', :class => 'UserTable' },
24+ 'admin_service' => {:row => 'Service', :class => 'ServiceTable'},
25+ 'admin_session' => {:row => 'Session', :class => 'SessionTable'},
26+ 'admin_view' => {:row => 'View', :class => 'ViewTable' },
27+ 'admin_widget' => {:row => 'Widget' },
28+ 'admin_content' => {:row => 'Content'},
29+
30+ 'user_account' => {:row => 'User', :class => 'UserTable' },
31+ 'user_service' => {:row => 'Service', :class => 'ServiceTable'},
32+ 'user_session' => {:row => 'Session', :class => 'SessionTable'},
33+ 'user_view' => {:row => 'View' },
34+ 'user_widget' => {:row => 'Widget' },
35+ 'user_content' => {:row => 'Content'},
3136 },
3237
3338 :relation => [
34- ['service.default_view', 'view' ],
35- ['user.content', 'content' ],
36- ['user.view', 'view' ],
37- ['user.session', 'usersession.user' ],
38- ['admin.content', 'content' ],
39- ['admin.view', 'view' ],
40- ['admin.session', 'adminsession.user'],
41- ['content.parent', 'content.node' ],
42- ['content.view', 'view' ],
43- ['view.parent', 'view.node' ],
44- ['view.resource', 'widget' ],
45- ['widget.parent', 'widget.node' ],
46- ['widget.view', 'view.widget' ],
39+ ['admin_account.content', 'admin_content' ],
40+ ['admin_account.view', 'admin_view' ],
41+ ['admin_account.session', 'admin_session.user'],
42+ ['admin_service.default_view', 'admin_view' ],
43+ ['admin_content.parent', 'admin_content.node'],
44+ ['admin_content.view', 'admin_view' ],
45+ ['admin_view.parent', 'admin_view.node' ],
46+ ['admin_view.model', 'admin_widget' ],
47+ ['admin_widget.parent', 'admin_widget.node' ],
48+ ['admin_widget.view', 'admin_view.widget' ],
49+
50+ ['user_account.content', 'user_content' ],
51+ ['user_account.view', 'user_view' ],
52+ ['user_account.session', 'user_session.user'],
53+ ['user_service.default_view', 'user_view' ],
54+ ['user_content.parent', 'user_content.node'],
55+ ['user_content.view', 'user_view' ],
56+ ['user_view.parent', 'user_view.node' ],
57+ ['user_view.model', 'user_widget' ],
58+ ['user_widget.parent', 'user_widget.node' ],
59+ ['user_widget.view', 'user_view.widget' ],
4760 ],
4861 }
4962
--- trunk/webapp/Service.rb (revision 131)
+++ trunk/webapp/Service.rb (revision 132)
@@ -35,7 +35,7 @@
3535 column Tenarai::DB::Reference.new('default_view')
3636 index Tenarai::DB::Index.new('name_idx', :cols => ['name'], :unique => true)
3737
38- attr_reader :model
38+ attr_reader :models
3939 attr_reader :request
4040 attr_reader :response
4141
@@ -51,12 +51,12 @@
5151 build_uri(view, nil, query)
5252 end
5353
54- def dispatch(model, param, request, response)
55- @model = model
54+ def dispatch(models, view, params, request, response)
55+ @models = models
5656 @request = request
5757 @response = response
58- @view = @model['view'].fetch(param.shift) || default_view
59- @view.render(self, param)
58+ @view = view.fetch_by_name(params.shift) || default_view
59+ @view.render(self, params)
6060 rescue Service::Error => err
6161 err.exec(self)
6262 end
--- trunk/webapp/Widget/Resource.rb (revision 131)
+++ trunk/webapp/Widget/Resource.rb (nonexistent)
@@ -1,42 +0,0 @@
1-#
2-# Copyright (C) 2008 Jun Kikuchi <kikuchi@bonnou.com>
3-#
4-
5-Tenarai::ClassLoader.load_class('Widget')
6-
7-class Widget
8- class Resource < ::Widget
9- extend Tenarai::ClassLoader
10-
11- property :model
12-
13- attr_reader :content
14- attr_reader :model
15-
16- def init(builder, service, _id=nil)
17- super(builder, service)
18-
19- if property[:model].nil?
20- property[:model] = self.class.name.split('::').pop.downcase
21- end
22-
23- @model = @service.models[property[:model]]
24- @content = @model.fetch(_id)
25- end
26-
27- def uri(view, *params, &block)
28- query = Tenarai::CGI::Query.new
29- block.call(query) if block_given?
30- build_uri(view, params, query)
31- end
32-
33- def widget_uri(path, query, params=[])
34- params = [@content.id] if params.empty? && @content
35- if view.resource.path.include?(self)
36- path << params.join(',')
37- else
38- query[html_id] = params.join(',')
39- end
40- end
41- end
42-end
--- trunk/webapp/Widget/Yahoo/UI/Layout.rb (revision 131)
+++ trunk/webapp/Widget/Yahoo/UI/Layout.rb (revision 132)
@@ -124,9 +124,9 @@
124124 var Event = YAHOO.util.Event;
125125
126126 Event.onDOMReady(function() {
127- new YAHOO.widget.Layout({
127+ (new YAHOO.widget.Layout({
128128 units: [#{units}]
129- }).render();
129+ })).render();
130130 });
131131 })();
132132 END
--- trunk/webapp/Widget/Yahoo/UI/TreeView.rb (revision 131)
+++ trunk/webapp/Widget/Yahoo/UI/TreeView.rb (revision 132)
@@ -31,13 +31,13 @@
3131 head.code <<-END
3232 (function() {
3333 (new YAHOO.widget.TreeView('#{html_id}', [
34- {type:"text", label:"List 0", children: [
35- {type:"text", label:"List 0-0", children: [
34+ {type:"text", label:"List 0", expanded: true, children: [
35+ {type:"text", label:"List 0-0", expanded: true, children: [
3636 "item 0-0-0",
3737 "item 0-0-1"
3838 ]},
39- {type:"text", label:"item 0-1", children: [
40- {type:"text", label:"elsewhere", href:"www.elsewhere.com", target:"_new", title:"go elsewhere", children: [
39+ {type:"text", label:"item 0-1", expanded: true, children: [
40+ {type:"text", label:"elsewhere", expanded: true, children: [
4141 "item 0-1-0",
4242 "item 0-1-1"
4343 ]}
--- trunk/webapp/Widget/Widget/JSON.rb (nonexistent)
+++ trunk/webapp/Widget/Widget/JSON.rb (revision 132)
@@ -0,0 +1,36 @@
1+#
2+# Copyright (C) 2008 Jun Kikuchi <kikuchi@bonnou.com>
3+#
4+
5+Tenarai::ClassLoader.load_class('Widget::Widget')
6+
7+require 'simple-json'
8+
9+class Widget
10+ class Widget
11+ class JSON < ::Widget
12+ extend Tenarai::ClassLoader
13+
14+ def render
15+ nodes = []
16+ map = {}
17+ model.each do |val|
18+ node = {
19+ 'type' => 'text',
20+ 'label' => val.class.name,
21+ 'expanded' => true,
22+ 'id' => val.id
23+ }
24+ map[val.id] = node
25+ if val.parent.nil?
26+ nodes << node
27+ else
28+ map[val.parent.id]['children'] ||= []
29+ map[val.parent.id]['children'] << node
30+ end
31+ end
32+ @service.response.body << JsonBuilder.new.build(nodes)
33+ end
34+ end
35+ end
36+end
--- trunk/webapp/Widget/Widget.rb (revision 131)
+++ trunk/webapp/Widget/Widget.rb (revision 132)
@@ -2,21 +2,21 @@
22 # Copyright (C) 2007, 2008 Jun Kikuchi <kikuchi@bonnou.com>
33 #
44
5-Tenarai::ClassLoader.load_class('Widget::Resource')
5+Tenarai::ClassLoader.load_class('Widget::Model')
66
77 class Widget
8- class Widget < Widget::Resource
8+ class Widget < Widget::Model
99 extend Tenarai::ClassLoader
1010
11- resource 'Widget::View'
11+ model 'Widget::View'
1212
1313 def init(builder, service, id=nil)
1414 super
15- @content ||= resource.content.widget.root
15+ @content ||= model.content.widget.root
1616 end
1717
1818 def each(&block)
19- resource.content.widget.each(&block)
19+ model.content.widget.each(&block)
2020 end
2121 end
2222 end
--- trunk/webapp/Widget/Model.rb (nonexistent)
+++ trunk/webapp/Widget/Model.rb (revision 132)
@@ -0,0 +1,41 @@
1+#
2+# Copyright (C) 2008 Jun Kikuchi <kikuchi@bonnou.com>
3+#
4+
5+Tenarai::ClassLoader.load_class('Widget')
6+
7+class Widget
8+ class Model < ::Widget
9+ extend Tenarai::ClassLoader
10+
11+ property :model
12+
13+ attr_reader :models
14+ attr_reader :content
15+
16+ def init(builder, service, _id=nil)
17+ super(builder, service)
18+ @models = service.models[property[:model]]
19+ @content = if @models
20+ @models.fetch(_id)
21+ else
22+ nil
23+ end
24+ end
25+
26+ def uri(view, *params, &block)
27+ query = Tenarai::CGI::Query.new
28+ block.call(query) if block_given?
29+ build_uri(view, params, query)
30+ end
31+
32+ def widget_uri(path, query, params=[])
33+ params = [@content.id] if params.empty? && @content
34+ if view.resource.path.include?(self)
35+ path << params.join(',')
36+ else
37+ query[html_id] = params.join(',')
38+ end
39+ end
40+ end
41+end
--- trunk/webapp/Widget/View/JSON.rb (nonexistent)
+++ trunk/webapp/Widget/View/JSON.rb (revision 132)
@@ -0,0 +1,38 @@
1+#
2+# Copyright (C) 2008 Jun Kikuchi <kikuchi@bonnou.com>
3+#
4+
5+Tenarai::ClassLoader.load_class('Widget::View')
6+
7+require 'simple-json'
8+
9+class Widget
10+ class View
11+ class JSON < ::Widget
12+ extend Tenarai::ClassLoader
13+
14+ def render
15+ nodes = []
16+ model.models.query('WHERE parent is NULL ORDER BY name') do |view|
17+ nodes << build_tree(view)
18+ end
19+ @service.response.body << JsonBuilder.new.build(nodes)
20+ end
21+
22+ def build_tree(view)
23+ node = {
24+ 'type' => 'text',
25+ 'label' => view.name,
26+ 'expanded' => true,
27+ 'id' => view.id
28+ }
29+ view.node.each do |val|
30+ node['children'] ||= []
31+ node['children'] << build_tree(val)
32+ end
33+ node
34+ end
35+ private :build_tree
36+ end
37+ end
38+end
--- trunk/webapp/Widget/View.rb (revision 131)
+++ trunk/webapp/Widget/View.rb (revision 132)
@@ -2,10 +2,10 @@
22 # Copyright (C) 2007, 2008 Jun Kikuchi <kikuchi@bonnou.com>
33 #
44
5-Tenarai::ClassLoader.load_class('Widget::Resource')
5+Tenarai::ClassLoader.load_class('Widget::Model')
66
77 class Widget
8- class View < Widget::Resource
8+ class View < Widget::Model
99 extend Tenarai::ClassLoader
1010
1111 def init(builder, service, id=nil)
--- trunk/webapp/Widget/Google/AJAX/Load.rb (revision 131)
+++ trunk/webapp/Widget/Google/AJAX/Load.rb (revision 132)
@@ -2,23 +2,21 @@
22 # Copyright (C) 2008 Jun Kikuchi <kikuchi@bonnou.com>
33 #
44 #
5-Tenarai::ClassLoader.load_class('Widget::Widget::Google::AJAX')
5+Tenarai::ClassLoader.load_class('Widget::Google::AJAX')
66
77 class Widget
8- class Widget
9- class Google
10- class AJAX
11- class Load
12- extend Tenarai::ClassLoader
8+ class Google
9+ class AJAX
10+ class Load < ::Widget
11+ extend Tenarai::ClassLoader
1312
14- property :name
15- property :version
13+ property :name
14+ property :version
1615
17- def render_html(head, body)
18- head.tag('script', 'type' => 'text/javascript').code(
19- "google.load('#{property[:name]}', '#{property[:version]}');"
20- )
21- end
16+ def render_html(head, body)
17+ head.tag('script', 'type' => 'text/javascript').code(
18+ "google.load('#{property[:name]}', '#{property[:version]}');"
19+ )
2220 end
2321 end
2422 end
--- trunk/webapp/Widget/Google/AJAX.rb (revision 131)
+++ trunk/webapp/Widget/Google/AJAX.rb (revision 132)
@@ -5,26 +5,25 @@
55 Tenarai::ClassLoader.load_class('Widget')
66
77 class Widget
8- class Widget
9- class Google
10- class AJAX < ::Widget
11- extend Tenarai::ClassLoader
8+ class Google
9+ class AJAX < ::Widget
10+ extend Tenarai::ClassLoader
1211
13- SRC = 'http://www.google.com/jsapi'
12+ SRC = 'http://www.google.com/jsapi'
1413
15- property :key
14+ property :key
1615
17- def render_html(head, body)
18- head.tag(
19- 'script',
20- 'type' => 'text/javascript',
21- 'src' => if property[:key].empty?
22- Widget::Widget::Jsapi::SRC
23- else
24- Widget::Widget::Jsapi::SRC + "?key=#{key}"
25- end
26- ).text('')
27- end
16+ def render_html(head, body)
17+ head.tag(
18+ 'script',
19+ 'type' => 'text/javascript',
20+ 'src' => if property[:key].to_s.empty?
21+ Widget::Google::AJAX::SRC
22+ else
23+ Widget::Google::AJAX::SRC + "?key=#{key}"
24+ end
25+ ).text('')
26+ super
2827 end
2928 end
3029 end
--- trunk/webapp/ViewTable.rb (nonexistent)
+++ trunk/webapp/ViewTable.rb (revision 132)
@@ -0,0 +1,19 @@
1+#
2+# Copyright (C) 2008 Jun Kikuchi <kikuchi@bonnou.com>
3+#
4+
5+class ViewTable < Tenarai::DB::Table
6+ extend Tenarai::ClassLoader
7+
8+ class << self
9+ def create_row(class_name, table, row)
10+ Tenarai::ClassLoader[class_name].new(table, row)
11+ end
12+ end
13+
14+ def fetch_by_name(name)
15+ query('WHERE name = ?', name) do |view|
16+ return view
17+ end
18+ end
19+end
--- trunk/webapp/Widget.rb (revision 131)
+++ trunk/webapp/Widget.rb (revision 132)
@@ -9,7 +9,7 @@
99
1010 meta[:property] = {}
1111 meta[:css] = Tenarai::Container.new
12- meta[:resource] = nil
12+ meta[:model] = nil
1313
1414 class << self
1515 def create_row(class_name, table, row)
@@ -33,13 +33,13 @@
3333 css 'a:hover'
3434 end
3535
36- def resource(klass)
37- meta[:resource] = klass
36+ def model(klass)
37+ meta[:model] = klass
3838 end
3939
40- def fetch_resource(widget)
40+ def fetch_model(widget)
4141 widget.path.reverse.each do |val|
42- return val if val.class.name == meta[:resource]
42+ return val if val.class.name == meta[:model]
4343 end
4444 nil
4545 end
@@ -46,8 +46,8 @@
4646
4747 def inherited(subclass)
4848 super
49- subclass.meta[:resource] = subclass.name.sub(/\w+?$/, '').sub(/::+?$/, '')
50- subclass.meta[:resource] = nil if subclass.meta[:resource].empty?
49+ subclass.meta[:model] = subclass.name.sub(/\w+?$/, '').sub(/::+?$/, '')
50+ subclass.meta[:model] = nil if subclass.meta[:model].empty?
5151 end
5252 end
5353
@@ -54,12 +54,14 @@
5454 column Tenarai::DB::Reference.new('view')
5555 column Tenarai::DB::Serialize.new('property')
5656 column Tenarai::DB::Serialize.new('css')
57- index Tenarai::DB::Index.new('build_idx', :cols => ['view', 'min'])
57+ index Tenarai::DB::Index.new('build_idx', :cols => ['view', 'min', 'max'])
5858
5959 property :html_attr_id
6060 property :html_attr_class
6161
62- attr_reader :resource
62+ attr_reader :builder
63+ attr_reader :service
64+ attr_reader :model
6365
6466 def initialize(table, row={})
6567 super
@@ -67,26 +69,26 @@
6769 self.css ||= Tenarai::Container.new
6870 end
6971
70- def init(builder, service, *param)
71- @builder = builder
72- @service = service
73- @resource = self.class.fetch_resource(self) || @service
72+ def init(builder, service, *params)
73+ @builder = builder
74+ @service = service
75+ @model = self.class.fetch_model(self) || @service
7476 end
7577
7678 def each_widget(&block)
77- @builder.each(self, &block)
79+ builder.each(self, &block)
7880 end
7981
80- def uri(view, *param, &block)
81- resource.uri(view, *param, &block)
82+ def uri(view, *params, &block)
83+ model.uri(view, *params, &block)
8284 end
8385
84- def build_uri(view, param, query)
85- @service.build_uri(
86+ def build_uri(view, params, query)
87+ service.build_uri(
8688 view,
8789 path.inject([]) do |ret, val|
8890 if val.equal?(self)
89- val.widget_uri(ret, query, param)
91+ val.widget_uri(ret, query, params)
9092 else
9193 val.widget_uri(ret, query)
9294 end
@@ -96,7 +98,7 @@
9698 )
9799 end
98100
99- def widget_uri(path, query, param=[])
101+ def widget_uri(path, query, params=[])
100102 end
101103
102104 def css_id(val=nil)
@@ -162,9 +164,9 @@
162164 end
163165 end
164166
165- def get(param=Tenarai::CGI::Query.new)
167+ def get(params=Tenarai::CGI::Query.new)
166168 end
167169
168- def post(param=Tenari::CGI::Query.new)
170+ def post(params=Tenari::CGI::Query.new)
169171 end
170172 end
--- trunk/webapp/ServiceTable.rb (revision 131)
+++ trunk/webapp/ServiceTable.rb (revision 132)
@@ -5,57 +5,17 @@
55 class ServiceTable < Tenarai::DB::Table
66 extend Tenarai::ClassLoader
77
8- MIME = {
9- '.html' => 'text/html',
10- '.htm' => 'text/html',
11- '.txt' => 'text/plain',
12- '.js' => 'text/javascript',
13- '.css' => 'text/css',
14- '.gif' => 'image/gif',
15- '.swf' => 'application/x-shockwave-flash',
16- }
17-
18- attr_accessor :pub_dir
19-
20- def fetch_by_name(name)
21- query('WHERE name = ?', name) do |service|
22- return service
8+ class << self
9+ def create_row(class_name, table, row)
10+ Tenarai::ClassLoader[class_name].new(table, row)
2311 end
2412 end
2513
26- def dispatch(model, request, response)
27- path_info = if request['path_info'].nil?
28- '/'
29- else
30- if request['path_info'].empty?
31- request['script_name']
32- else
33- request['path_info']
34- end
35- end
36-
37- _nil, name, *param = path_info.split('/')
38-
39- if s = fetch_by_name(name || 'index')
14+ def dispatch(name, view, models, params, request, response)
15+ response.code = 404
16+ query('WHERE name = ?', name) do |service|
4017 response.code = 200
41- s.dispatch(model, param, request, response)
42- else
43- response.code = 404
44-
45- return if @pub_dir.nil?
46-
47- dir = File.expand_path(@pub_dir)
48- file = File.expand_path(dir + path_info)
49-
50- return unless (%r!^#{dir}!.match(file) && File.file?(file))
51-
52- content_type = MIME[File.extname(file).downcase]
53-
54- unless content_type.nil?
55- response.code = 200
56- response['content-type'] = content_type
57- response.body << File.open(file).read
58- end
18+ service.dispatch(models, view, params, request, response)
5919 end
6020 end
6121 end
--- trunk/webapp/View/Builder.rb (revision 131)
+++ trunk/webapp/View/Builder.rb (revision 132)
@@ -2,7 +2,7 @@
22 # Copyright (C) 2007, 2008 Jun Kikuchi <kikuchi@bonnou.com>
33 #
44
5-Tenarai::ClassLoader.load_class('Widget::Resource')
5+Tenarai::ClassLoader.load_class('Widget::Model')
66
77 class View
88 class Builder
@@ -49,7 +49,7 @@
4949 else
5050 widget[nil] = val
5151 end
52- if val.is_a?(::Widget::Resource) && view.resource.path.include?(val)
52+ if val.is_a?(::Widget::Model) && view.model.path.include?(val)
5353 val.init(self, service, param.shift)
5454 else
5555 if rs.key?(val)
--- trunk/webapp/View.rb (revision 131)
+++ trunk/webapp/View.rb (revision 132)
@@ -44,7 +44,7 @@
4444 end
4545
4646 column Tenarai::DB::String.new('name', :length => 255)
47- column Tenarai::DB::Reference.new('resource')
47+ column Tenarai::DB::Reference.new('model')
4848 column Tenarai::DB::Reference.new(
4949 'widget',
5050 :multiple => true,
@@ -51,7 +51,7 @@
5151 :class => ::View::Widget
5252 )
5353
54- def build(service, param)
54+ def build(service, params)
5555 ws, rs = {}, {}
5656
5757 if service.request.method == 'post'
@@ -74,7 +74,7 @@
7474
7575 widget.each do |val|
7676 if val.path.size == 1
77- val.init(service, *param.to_s.split(',').map do |val|
77+ val.init(service, *params.to_s.split(',').map do |val|
7878 Tenarai::CGI.decode(val)
7979 end)
8080 else
@@ -86,17 +86,17 @@
8686 self
8787 end
8888
89- def render(service, param=[])
90- Tenarai::ClassLoader['View::Builder'].new(self, service, param).render
89+ def render(service, params=[])
90+ Tenarai::ClassLoader['View::Builder'].new(self, service, params).render
9191 end
9292
93- alias :_resource :resource
93+ alias :_model :model
9494
95- def resource
96- _resource || parent.resource
95+ def model
96+ _model || parent.model
9797 end
9898
99- def resource=(val)
100- _resource = val
99+ def model=(val)
100+ _model = val
101101 end
102102 end
Show on old repository browser