Open andrewharry opened 12 years ago
Hi Andrew, This is certainly a work in progress... its actually been in a half-done state for some while.
Its going to need a good doc, and a sample, which I'll be building as well... once I get the stuff done so far into this repository. Briefly ...
First, at a conceptual level, a service is simply a component that needs to be instantiated, and can be used to fulfill a dependency of some other component, by virtue of being added to the IoC container/catalog represented by the Application instance. There are two ways to do so: imperative and declarative.
Say I have a location service represented by ILocationService, and an implementation represented by HtmlLocationService.
Imperative:
// do this somewhere early enough
Application.Current.RegisterObject(typeof(ILocationService), new HtmlLocationService());
Declarative: Interesting if you buy into the philosophy of devs building a library of components that can be wired together or composed declaratively at the application level.
First, the HtmlLocationService implementation registers itself with the application with a friendly name.
Application.Current.RegisterService("htmlLocation", typeof(HtmlLocationService),
typeof(ILocationService));
Second, the page developer then declares an instance of this service.
<body data-services="htmlLocation">
Now suppose the service can take some parameters... such as accuracy level. The service developer implements IInitializable on their class using which the framework can pass in some name/value pairs. The page developer specifies those name/value pairs using a simple syntax that is a hybrid of JSON/CSS:
<body data-services="htmlLocation" data-htmlLocation="accuracy: 'high'">
Regardless of imperative/declarative, subsequently, when other objects are created using the IoC container, their dependencies can be fulfilled. For example, a map behavior:
class MapBehavior : Behavior {
[Import] public ILocationService LocationService { get; set; }
}
and the idea is the script# compiler produces a Factory method. If you look at the Application implementation, you'll notice it looks for a "factory" method off the type, and delegates construction to it if it exists. OK, yes, the script# compiler doesn't yet know about imports, but in the interim, this factory method can be written manually as such:
class MapBehavior : Behavior {
private ILocationService _locationService;
public static MapBehavior Factory(IContainer container) {
MapBehavior behavior = new MapBehavior();
behavior._locationService = container.GetObject(typeof(ILocationService));
return behavior;
}
}
Just to give an example, here is a quick stab that illustrates both the declaration and customization using the same JSON+CSS-esque syntax:
<div data-behaviors="map"
data-map="style: 'road', interactive: true, latitude: 47.606, longitude: -122.332"></div>
Does that make sense? Feel free to add questions below, or share comments.
A bit about the scope of this framework, i.e. where I see it going (based on my experiences with building ajax frameworks, silverlight frameworks, and uses of script to build large scale apps):
Thats my thoughts on scope for this stuff. It should be general enough for many apps, but especially relevant for single-page apps. Some other areas I have in mind are around networking and data but they'll be in separate scripts (Sharpen.HTML.Network.js and Sharpen.HTML.Data.js) when they do come around into existence.
Hope that helps.
The aspect I found un intuitive is using html fragments to register components. Are you intending on the server to render partial views for this to work?
I'd have thought the more common route would be to have the script components register themselves. Or is this the chicken and egg problem... is this a method inorder to pull the right scripts from the server?
The server may generate html which is inserted into the page, or html might be generated on the client by virtue of template instantiation. In either case, the old fragment must be "deactivated" and the new one must be "activated" so behaviors, and bindings get instantiated.
begin update: Upon re-reading, I think what you're calling un-intuitive is declarative model for registering components in general. Admittedly, there is certainly an element of opinion at play here. I personally think an app should be composed of components and declarative markup (independent of whether the markup was statically authored or rendered on the server). That declarative approach should apply to not just the content but the behavior as well. One could always have a script block in the page that instantiates everything imperatively and it would be equivalent. : end update
I don't think most script components should register itself in general ... a script component doesn't know if its the particular implementation that should be instantiated (since there might be multiple candidates), or doesn't know when it should (since its dependencies might not have loaded yet, or the app might not need a service yet. The ones that might are the truly singleton ones that are dependency free (eg. Application itself).
Eventually I might want to intersect some of this with delay/on-demand loading of scripts. Not there yet. Still mulling over it. Right now, the working assumption is scripts needed for declaratively specified stuff are loaded already. So if you demand load script, then the callback triggered upon script load imperatively instantiates the script components.
Looking at my current script# project. I'm declaring in the view which controller should handle the page.
<script type="text/javascript">
var ControllerName = "CompanyController";
var ControllerData = @(Html.Raw(new JavaScriptSerializer().Serialize(Model)));
</script>
then in a global class
[IgnoreNamespace, ScriptName("window"), Imported]
public static class GlobalVariables
{
[PreserveCase, PreserveName] public static string ControllerName;
[PreserveCase, PreserveName] public static object ControllerData;
}
[GlobalMethods]
public static class Global
{
static Global() {
jQuery.OnDocumentReady(delegate {
startup();
});
}
public static void startup() {
string controllerName = !Script.IsUndefined(GlobalVariables.ControllerName) ? GlobalVariables.ControllerName : "";
ViewModel controller = null;
if (controllerName != "")
controller = (ViewModel)Application.Current.GetObject(controllerName);
if (Script.IsNullOrUndefined(controller))
controller = new SimpleModel();
ViewModel.Model = controller;
}
}
Controllers register themselves with static constructors
static CompanyController() {
Application.Current.RegisterName("CompanyController", CreateInstance);
}
static CompanyController CreateInstance() {
return new CompanyController();
}
public CompanyController() {
Company template = !Script.IsUndefined(GlobalVariables.ControllerData) ? (Company)GlobalVariables.ControllerData : new Company();
//... do something!
}
My application is build with a MVC architecture. It isn't a single page application, but isn't far off from being one. It heavily uses knockout and basically only pulls templates and models from the server.
I was hesitant to go for a full single page application at the time I started the project. If I could do over I'd probably run with it
This is what I'd imagine for your scenario:
<body data-model="CompanyController" data-model-initializer="... json ...">
Your CompanyController would implement IInitializable which has Begin/EndInitialize and now allows passing in initialization data. It would be created through the IoC container, so any dependencies it has can get resolved as well.
So you don't have to create GlobalVariables, or that startup code, since Application effectively contains that logic.
I like it, much cleaner than my current explicit mess. Although I'm not a big fan of heavily using a IoC container, they can make stepping thru code a lot more painful. But for cleaning up my hacks for getting global variables and registering major components - this feels much more polished.
Once code gets to a certain level of interconnectedness, i'm definetly seeing the benefits of using events and pub/sub.
Looking over your application code, that just leaves behaviours and the fragements - I don't know what these are for yet.
Behaviors are simply encapsulations of UI logic that can be attached to DOM elements. The example above had snippets of a MapBehavior and how it can be attached to a div to turn the div into a map.
Fragments - just a concept around DOM subtrees - for example, think body and everything within it, or a div and everything within it, etc. A fragment can be activated (i.e. behaviors in the markup instantiated) or deactivated (i.e. behaviors disposed) as DOM element's content is replaced by setting innerHTML.
I noticed the application code in the ScripSharp project and wondered if it was 'work in progress' I have used the object registration idea to good effect in on of my S# projects. But didn't quite get the services part.
As the title says.... would love to see a sample project.
Keep up the good work!