MR-262 - Dynamic components in brail using RenderComponent
[castle.git] / MonoRail / Castle.MonoRail.Views.Brail / BrailBase.cs
blobb829e68a367438a42417f283e9ad52fdc113a8a4
1 // Copyright 2004-2007 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 using System.Diagnostics;
16 using System.Text;
18 namespace Castle.MonoRail.Views.Brail
20 using System;
21 using System.Collections;
22 using System.IO;
23 using Castle.MonoRail.Framework;
24 /// <summary>
25 ///Base class for all the view scripts, this is the class that is responsible for
26 /// support all the behind the scenes magic such as variable to PropertyBag trasnlation,
27 /// resources usage, etc.
28 /// </summary>
29 public abstract class BrailBase
31 private TextWriter outputStream;
33 /// <summary>
34 /// This is used by layout scripts only, for outputing the child's content
35 /// </summary>
36 protected TextWriter childOutput;
37 private Hashtable properties;
38 /// <summary>
39 /// used to hold the ComponentParams from the view, so their views/sections could access them
40 /// </summary>
41 private IList viewComponentsParameters;
43 protected IRailsEngineContext context;
44 protected Controller __controller;
45 protected BooViewEngine viewEngine;
47 /// <summary>
48 /// usually used by the layout to refer to its view, or a subview to its parent
49 /// </summary>
50 protected BrailBase parent;
52 /// <summary>
53 /// Initializes a new instance of the <see cref="BrailBase"/> class.
54 /// </summary>
55 /// <param name="viewEngine">The view engine.</param>
56 /// <param name="output">The output.</param>
57 /// <param name="context">The context.</param>
58 /// <param name="__controller">The controller.</param>
59 public BrailBase(BooViewEngine viewEngine, TextWriter output, IRailsEngineContext context, Controller __controller)
61 this.viewEngine = viewEngine;
62 this.outputStream = output;
63 this.context = context;
64 this.__controller = __controller;
65 InitProperties(context, __controller);
68 /// <summary>
69 /// Runs this instance, this is generated by the script
70 /// </summary>
71 public abstract void Run();
74 /// <summary>
75 ///The path of the script, this is filled by AddBrailBaseClassStep
76 /// and is used for sub views
77 /// </summary>
78 public virtual string ScriptDirectory
80 get { return viewEngine.ViewRootDir; }
83 /// <summary>
84 /// Gets the view engine.
85 /// </summary>
86 /// <value>The view engine.</value>
87 public BooViewEngine ViewEngine
89 get { return viewEngine; }
92 /// <summary>
93 /// Output the subview to the client, this is either a relative path "SubView" which
94 /// is relative to the current /script/ or an "absolute" path "/home/menu" which is
95 /// actually relative to ViewDirRoot
96 /// </summary>
97 /// <param name="subviewName"></param>
98 public void OutputSubView(string subviewName)
100 OutputSubView(subviewName, new Hashtable());
103 /// <summary>
104 /// Similiar to the OutputSubView(string) function, but with a bunch of parameters that are used
105 /// just for this subview. This parameters are /not/ inheritable.
106 /// </summary>
107 /// <param name="subviewName"></param>
108 /// <param name="parameters"></param>
109 public void OutputSubView(string subviewName, IDictionary parameters)
111 OutputSubView(subviewName, outputStream, parameters);
114 /// <summary>
115 /// Outputs the sub view to the writer
116 /// </summary>
117 /// <param name="subviewName">Name of the subview.</param>
118 /// <param name="writer">The writer.</param>
119 /// <param name="parameters">The parameters.</param>
120 public void OutputSubView(string subviewName, TextWriter writer, IDictionary parameters)
122 string subViewFileName = GetSubViewFilename(subviewName);
123 BrailBase subView = viewEngine.GetCompiledScriptInstance(subViewFileName, writer, context, __controller);
124 subView.SetParent(this);
125 foreach (DictionaryEntry entry in parameters)
127 subView.properties[entry.Key] = entry.Value;
129 subView.Run();
132 /// <summary>
133 /// Get the sub view file name, if the subview starts with a '/'
134 /// then the filename is considered relative to ViewDirRoot
135 /// otherwise, it's relative to the current script directory
136 /// </summary>
137 /// <param name="subviewName"></param>
138 /// <returns></returns>
139 public string GetSubViewFilename(string subviewName)
141 //absolute path from Views directory
142 if (subviewName[0] == '/')
143 return subviewName.Substring(1) + viewEngine.ViewFileExtension;
144 return Path.Combine(ScriptDirectory, subviewName) + viewEngine.ViewFileExtension;
147 /// <summary>
148 /// this is called by ReplaceUnknownWithParameters step to create a more dynamic experiance
149 /// any uknown identifier will be translate into a call for GetParameter('identifier name').
150 /// This mean that when an uknonwn identifier is in the script, it will only be found on runtime.
151 /// </summary>
152 /// <param name="name"></param>
153 /// <returns></returns>
154 public object GetParameter(string name)
156 ParameterSearch search = GetParameterInternal(name);
157 if (search.Found == false)
158 throw new RailsException("Parameter '" + name + "' was not found!");
159 return search.Value;
162 /// <summary>
163 /// this is called by ReplaceUnknownWithParameters step to create a more dynamic experiance
164 /// any uknown identifier with the prefix of ? will be translated into a call for
165 /// TryGetParameter('identifier name without the ? prefix').
166 /// This method will return null if the value it not found.
167 /// </summary>
168 /// <param name="name"></param>
169 /// <returns></returns>
170 public object TryGetParameter(string name)
172 ParameterSearch search = GetParameterInternal(name);
173 return search.Value;
176 /// <summary>
177 /// Gets the parameter - implements the logic for searching parameters.
178 /// </summary>
179 /// <param name="name">The name.</param>
180 /// <returns></returns>
181 private ParameterSearch GetParameterInternal(string name)
183 //temporary syntax to turn @variable to varaible, imitating :symbol in ruby
184 if (name.StartsWith("@"))
185 return new ParameterSearch(name.Substring(1), true);
186 if (viewComponentsParameters != null)
188 foreach (IDictionary viewComponentProperties in viewComponentsParameters)
190 if (viewComponentProperties.Contains(name))
191 return new ParameterSearch(viewComponentProperties[name], true);
194 if (properties.Contains(name))
195 return new ParameterSearch(properties[name], true);
196 if (parent != null)
197 return parent.GetParameterInternal(name);
198 return new ParameterSearch(null, false);
201 /// <summary>
202 /// Sets the parent.
203 /// </summary>
204 /// <param name="myParent">My parent.</param>
205 public void SetParent(BrailBase myParent)
207 parent = myParent;
210 /// <summary>
211 /// Allows to check that a parameter was defined
212 /// </summary>
213 /// <param name="name"></param>
214 /// <returns></returns>
215 public bool IsDefined(string name)
217 ParameterSearch search = GetParameterInternal(name);
218 return search.Found;
221 /// <summary>
222 /// Gets the flash.
223 /// </summary>
224 /// <value>The flash.</value>
225 public Flash Flash
227 get { return context.Flash; }
230 /// <summary>
231 /// Gets the output stream.
232 /// </summary>
233 /// <value>The output stream.</value>
234 public TextWriter OutputStream
236 get { return outputStream; }
239 /// <summary>
240 /// This is required because we may want to replace the output stream and get the correct
241 /// behavior from components call RenderText() or RenderSection()
242 /// </summary>
243 public IDisposable SetOutputStream(TextWriter newOutputStream)
245 ReturnOutputStreamToInitialWriter disposable = new ReturnOutputStreamToInitialWriter(OutputStream, this);
246 outputStream = newOutputStream;
247 return disposable;
250 /// <summary>
251 /// Gets or sets the child output.
252 /// </summary>
253 /// <value>The child output.</value>
254 public TextWriter ChildOutput
256 get { return childOutput; }
257 set { childOutput = value; }
260 /// <summary>
261 /// Gets the properties.
262 /// </summary>
263 /// <value>The properties.</value>
264 public IDictionary Properties
266 get { return properties; }
269 /// <summary>
270 /// Note that this will overwrite any existing property.
271 /// </summary>
272 public void AddProperty(string name, object item)
274 properties[name] = item;
277 /// <summary>
278 /// Adds the view component newProperties.
279 /// This will be included in the parameters searching, note that this override
280 /// the current parameters if there are clashing.
281 /// The search order is LIFO
282 /// </summary>
283 /// <param name="newProperties">The newProperties.</param>
284 public void AddViewComponentProperties(IDictionary newProperties)
286 if (viewComponentsParameters == null)
287 viewComponentsParameters = new ArrayList();
288 viewComponentsParameters.Insert(0, newProperties);
291 /// <summary>
292 /// Removes the view component properties, so they will no longer be visible to the views.
293 /// </summary>
294 /// <param name="propertiesToRemove">The properties to remove.</param>
295 public void RemoveViewComponentProperties(IDictionary propertiesToRemove)
297 if (viewComponentsParameters == null)
298 return;
299 viewComponentsParameters.Remove(propertiesToRemove);
301 public void RenderComponent(string componentName)
303 RenderComponent(componentName, new Hashtable());
305 public void RenderComponent(string componentName, IDictionary parameters)
307 BrailViewComponentContext componentContext =
308 new BrailViewComponentContext(this, null, componentName, OutputStream,
309 new Hashtable(StringComparer.InvariantCultureIgnoreCase));
310 this.AddViewComponentProperties(componentContext.ComponentParameters);
311 IViewComponentFactory componentFactory = (IViewComponentFactory)this.context.GetService(typeof(IViewComponentFactory));
312 ViewComponent component = componentFactory.Create(componentName);
313 component.Init(this.context, componentContext);
314 component.Render();
315 if (componentContext.ViewToRender != null)
317 this.OutputSubView("/" + componentContext.ViewToRender, componentContext.ComponentParameters);
319 this.RemoveViewComponentProperties(componentContext.ComponentParameters);
323 /// <summary>
324 /// Initialize all the properties that a script may need
325 /// One thing to note here is that resources are wrapped in ResourceToDuck wrapper
326 /// to enable easy use by the script
327 /// </summary>
328 /// <param name="myContext"></param>
329 /// <param name="myController"></param>
330 private void InitProperties(IRailsEngineContext myContext, Controller myController)
332 properties = new Hashtable(
333 #if DOTNET2
334 StringComparer.InvariantCultureIgnoreCase
335 #else
336 CaseInsensitiveHashCodeProvider.Default,
337 CaseInsensitiveComparer.Default
338 #endif
340 properties.Add("Controller", myController);
341 properties.Add("request", myContext.Request);
342 properties.Add("response", myContext.Response);
343 properties.Add("session", myContext.Session);
345 if (myController.Resources != null)
347 foreach (object key in myController.Resources.Keys)
349 properties.Add(key, new ResourceToDuck(myController.Resources[key]));
353 foreach (DictionaryEntry entry in myController.Helpers)
355 properties.Add(entry.Key, entry.Value);
358 foreach (string key in myController.Params.AllKeys)
360 if (key == null)
361 continue;
362 properties[key] = myContext.Params[key];
365 foreach (DictionaryEntry entry in myContext.Flash)
367 properties[entry.Key] = entry.Value;
370 foreach (DictionaryEntry entry in myController.PropertyBag)
372 properties[entry.Key] = entry.Value;
375 properties["siteRoot"] = myContext.ApplicationPath;
378 private class ParameterSearch
380 private bool found;
381 private object value;
383 public bool Found
385 get { return found; }
388 public object Value
390 get { return value; }
393 public ParameterSearch(object value, bool found)
395 this.found = found;
396 this.value = value;
400 private class ReturnOutputStreamToInitialWriter : IDisposable
402 private TextWriter initialWriter;
403 private BrailBase parent;
405 public ReturnOutputStreamToInitialWriter(TextWriter initialWriter, BrailBase parent)
407 this.initialWriter = initialWriter;
408 this.parent = parent;
411 public void Dispose()
413 parent.outputStream = initialWriter;