1 // Copyright 2004-2007 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
;
18 namespace Castle
.MonoRail
.Views
.Brail
21 using System
.Collections
;
23 using Castle
.MonoRail
.Framework
;
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.
29 public abstract class BrailBase
31 private TextWriter outputStream
;
34 /// This is used by layout scripts only, for outputing the child's content
36 protected TextWriter childOutput
;
37 private Hashtable properties
;
39 /// used to hold the ComponentParams from the view, so their views/sections could access them
41 private IList viewComponentsParameters
;
43 protected IRailsEngineContext context
;
44 protected Controller __controller
;
45 protected BooViewEngine viewEngine
;
48 /// usually used by the layout to refer to its view, or a subview to its parent
50 protected BrailBase parent
;
53 /// Initializes a new instance of the <see cref="BrailBase"/> class.
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
);
69 /// Runs this instance, this is generated by the script
71 public abstract void Run();
75 ///The path of the script, this is filled by AddBrailBaseClassStep
76 /// and is used for sub views
78 public virtual string ScriptDirectory
80 get { return viewEngine.ViewRootDir; }
84 /// Gets the view engine.
86 /// <value>The view engine.</value>
87 public BooViewEngine ViewEngine
89 get { return viewEngine; }
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
97 /// <param name="subviewName"></param>
98 public void OutputSubView(string subviewName
)
100 OutputSubView(subviewName
, new Hashtable());
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.
107 /// <param name="subviewName"></param>
108 /// <param name="parameters"></param>
109 public void OutputSubView(string subviewName
, IDictionary parameters
)
111 OutputSubView(subviewName
, outputStream
, parameters
);
115 /// Outputs the sub view to the writer
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
;
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
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
;
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.
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!");
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.
168 /// <param name="name"></param>
169 /// <returns></returns>
170 public object TryGetParameter(string name
)
172 ParameterSearch search
= GetParameterInternal(name
);
177 /// Gets the parameter - implements the logic for searching parameters.
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);
197 return parent
.GetParameterInternal(name
);
198 return new ParameterSearch(null, false);
204 /// <param name="myParent">My parent.</param>
205 public void SetParent(BrailBase myParent
)
211 /// Allows to check that a parameter was defined
213 /// <param name="name"></param>
214 /// <returns></returns>
215 public bool IsDefined(string name
)
217 ParameterSearch search
= GetParameterInternal(name
);
224 /// <value>The flash.</value>
227 get { return context.Flash; }
231 /// Gets the output stream.
233 /// <value>The output stream.</value>
234 public TextWriter OutputStream
236 get { return outputStream; }
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()
243 public IDisposable
SetOutputStream(TextWriter newOutputStream
)
245 ReturnOutputStreamToInitialWriter disposable
= new ReturnOutputStreamToInitialWriter(OutputStream
, this);
246 outputStream
= newOutputStream
;
251 /// Gets or sets the child output.
253 /// <value>The child output.</value>
254 public TextWriter ChildOutput
256 get { return childOutput; }
257 set { childOutput = value; }
261 /// Gets the properties.
263 /// <value>The properties.</value>
264 public IDictionary Properties
266 get { return properties; }
270 /// Note that this will overwrite any existing property.
272 public void AddProperty(string name
, object item
)
274 properties
[name
] = item
;
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
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
);
292 /// Removes the view component properties, so they will no longer be visible to the views.
294 /// <param name="propertiesToRemove">The properties to remove.</param>
295 public void RemoveViewComponentProperties(IDictionary propertiesToRemove
)
297 if (viewComponentsParameters
== null)
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
);
315 if (componentContext
.ViewToRender
!= null)
317 this.OutputSubView("/" + componentContext
.ViewToRender
, componentContext
.ComponentParameters
);
319 this.RemoveViewComponentProperties(componentContext
.ComponentParameters
);
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
328 /// <param name="myContext"></param>
329 /// <param name="myController"></param>
330 private void InitProperties(IRailsEngineContext myContext
, Controller myController
)
332 properties
= new Hashtable(
334 StringComparer
.InvariantCultureIgnoreCase
336 CaseInsensitiveHashCodeProvider
.Default
,
337 CaseInsensitiveComparer
.Default
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
)
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
381 private object value;
385 get { return found; }
390 get { return value; }
393 public ParameterSearch(object value, bool found
)
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
;