Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

SPA stuff (folded in from sharpen project - WIP)

  • Loading branch information...
commit fd053a7a41549cc89e297f942d2e33e765e1a53d 1 parent 374ae2a
@nikhilk authored
Showing with 2,063 additions and 0 deletions.
  1. +99 −0 fx/Sharpen/Core/Application.Behaviors.cs
  2. +206 −0 fx/Sharpen/Core/Application.Bindings.cs
  3. +68 −0 fx/Sharpen/Core/Application.Services.cs
  4. +103 −0 fx/Sharpen/Core/Application.Templates.cs
  5. +358 −0 fx/Sharpen/Core/Application.cs
  6. +119 −0 fx/Sharpen/Core/Behavior.cs
  7. +19 −0 fx/Sharpen/Core/BehaviorRegistration.cs
  8. +21 −0 fx/Sharpen/Core/Binder.cs
  9. +20 −0 fx/Sharpen/Core/BinderFactory.cs
  10. +49 −0 fx/Sharpen/Core/BinderManager.cs
  11. +61 −0 fx/Sharpen/Core/Bindings/BindExpression.cs
  12. +71 −0 fx/Sharpen/Core/Bindings/ContentBinder.cs
  13. +35 −0 fx/Sharpen/Core/Bindings/EventBinder.cs
  14. +49 −0 fx/Sharpen/Core/Bindings/PropertyBinder.cs
  15. +38 −0 fx/Sharpen/Core/Bindings/ValueBinder.cs
  16. +37 −0 fx/Sharpen/Core/Bindings/VisibilityBinder.cs
  17. +82 −0 fx/Sharpen/Core/Core.csproj
  18. +80 −0 fx/Sharpen/Core/Expression.cs
  19. +19 −0 fx/Sharpen/Core/ExpressionFactory.cs
  20. +39 −0 fx/Sharpen/Core/Properties/AssemblyInfo.cs
  21. +101 −0 fx/Sharpen/Core/Properties/FxCop.ruleset
  22. +183 −0 fx/Sharpen/Core/Properties/TemplateEngine.js
  23. +19 −0 fx/Sharpen/Core/ServiceRegistration.cs
  24. +18 −0 fx/Sharpen/Core/Template.cs
  25. +19 −0 fx/Sharpen/Core/TemplateEngine.cs
  26. +105 −0 fx/Sharpen/Core/Utility/OptionsParser.cs
  27. +5 −0 fx/Sharpen/Core/packages.config
  28. +20 −0 fx/Sharpen/Sharpen.sln
  29. BIN  fx/Sharpen/packages/nuget.exe
  30. +16 −0 fx/Sharpen/packages/nuget.targets
  31. +4 −0 fx/Sharpen/packages/repositories.config
View
99 fx/Sharpen/Core/Application.Behaviors.cs
@@ -0,0 +1,99 @@
+// Application.Behaviors.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Html;
+using System.Runtime.CompilerServices;
+using Sharpen.Html.Utility;
+
+namespace Sharpen.Html {
+
+ public sealed partial class Application {
+
+ /// <summary>
+ /// Creates and attaches behaviors specified on the element declaratively.
+ /// </summary>
+ /// <param name="element">The element whose behaviors should be created and attached.</param>
+ private void AttachBehaviors(Element element) {
+ string[] behaviorNames = ((string)element.GetAttribute(Application.BehaviorsAttribute)).Split(",");
+ int behaviorCount = behaviorNames.Length;
+
+ for (int i = 0; i < behaviorCount; i++) {
+ string name = behaviorNames[i].Trim();
+
+ BehaviorRegistration registration = _registeredBehaviors[name];
+ Debug.Assert(registration != null, "Unknown behavior '" + name + "'");
+
+ if (registration != null) {
+ Dictionary<string, object> options = OptionsParser.GetOptions(element, name);
+
+ // Use the Application's IoC capabilities to create behaviors.
+ // This allows satisfying dependencies behaviors have to other services,
+ // and also allows behaviors to provide or register services into the container.
+
+ Behavior behavior = (Behavior)GetObject(registration.BehaviorType);
+ behavior.Initialize(element, options);
+
+ if (registration.ServiceType != null) {
+ // Special-case the common case where a behavior represents a single
+ // service type, and auto-register it.
+ // In the case where a behavior is registering multiple service types
+ // (not so common), it can do so manually in its Initialize method.
+
+ RegisterObject(registration.ServiceType, behavior);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Detaches all behaviors associated with the specified element.
+ /// </summary>
+ /// <param name="element">The element whose behaviors are to be detached.</param>
+ private void DetachBehaviors(Element element) {
+ // Detach all behaviors by enumerating the key/value pair list of
+ // behaviors being maintained on the element, and disposing them
+ // individually.
+
+ Dictionary<string, Behavior> behaviors = Script.GetField<Dictionary<string, Behavior>>(element, Application.BehaviorsKey);
+ if (behaviors != null) {
+ IEnumerable<string> names = behaviors.Keys;
+ foreach (string name in names) {
+ Behavior behavior = behaviors[name];
+ behavior.Dispose();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Registers a behavior so it can be used declaratively in markup.
+ /// </summary>
+ /// <param name="name">The unique name of the behavior.</param>
+ /// <param name="behaviorType">The type of the behavior.</param>
+ public extern void RegisterBehavior(string name, Type behaviorType);
+
+ /// <summary>
+ /// Registers a behavior so it can be used declaratively in markup.
+ /// </summary>
+ /// <param name="name">The unique name of the behavior.</param>
+ /// <param name="behaviorType">The type of the behavior.</param>
+ /// <param name="serviceType">An option service type associated with the behavior for auto-registration with the application container.</param>
+ public void RegisterBehavior(string name, Type behaviorType, Type serviceType) {
+ Debug.Assert(String.IsNullOrEmpty(name) == false);
+ Debug.Assert(behaviorType != null);
+ Debug.Assert(typeof(Behavior).IsAssignableFrom(behaviorType));
+ Debug.Assert(_registeredBehaviors.ContainsKey(name) == false, "A behavior with name '" + name + "' was already registered.");
+
+ BehaviorRegistration registration = new BehaviorRegistration();
+ registration.BehaviorType = behaviorType;
+ registration.ServiceType = serviceType;
+
+ _registeredBehaviors[name] = registration;
+ Script.SetField(behaviorType, Application.BehaviorNameKey, name);
+ }
+ }
+}
View
206 fx/Sharpen/Core/Application.Bindings.cs
@@ -0,0 +1,206 @@
+// Application.Bindings.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Html;
+using Sharpen.Html.Bindings;
+
+namespace Sharpen.Html {
+
+ public sealed partial class Application {
+
+ private static Binder BindContent(Element element, string property, Expression expression) {
+ property = (property == "text") ? "textContent" : "innerHTML";
+ return new ContentBinder(element, property, expression);
+ }
+
+ private static Binder BindValue(Element element, string property, Expression expression) {
+ Debug.Assert((element.TagName.ToLowerCase() == "input") ||
+ (element.TagName.ToLowerCase() == "textarea") ||
+ (element.TagName.ToLowerCase() == "select"),
+ "Value can only be bound on user input elements.");
+
+ return new ValueBinder((InputElement)element, expression);
+ }
+
+ private static Binder BindVisibility(Element element, string property, Expression expression) {
+ return new VisibilityBinder(element, property, expression);
+ }
+
+ private static Expression ProcessModelExpression(object model, string expressionType, string value) {
+ Debug.Assert(Script.GetField(model, value) != null, "The model does not have a member named '" + value + '"');
+
+ if (expressionType == "exec") {
+ // TODO: Support scenarios where value is parent.method, or root.method (for invoking
+ // a method on the parent model or root model... useful in templating scenarios
+ // where items are not bound to view model but an object retrieved from the
+ // view model.
+
+ // When the expression is exec, the value is interpreted as a reference to a method
+ // on the model. An invoker function is created that when called invokes the referenced
+ // method in context of the model instance. The expression itself contains a delegate
+ // to this dynamically created invoker function.
+
+ // function _(e) {
+ // var result = this.modelMethod(e, this);
+ // if (result) { e.preventDefault(); }
+ // }
+
+ // TODO: If bound to parent/root, generated this instead:
+ // function _(e) {
+ // var model = Application.Current.GetModel(element.parentElement);
+ // var result = this.modelMethod(e, model);
+ // if (result) { e.preventDefault(); }
+ // }
+
+ string invokerCode = "var result = this." + value + "(e, this); if (result) { e.preventDefault(); }";
+ return new Expression(Delegate.Create(new Function(invokerCode, "e", "model"), model), /* canChange */ false);
+ }
+
+ // TODO: Eventually stop with the () business if we can switch over to true properties
+ // with getter/setter accessors in javascript.
+
+ // Check if the value is something like A.B.C, where A, B and C are identifiers.
+ bool propertyPathExpression = PropertyPathRegex.Test(value);
+
+ // The value represents a string that contains a script expression for properties off
+ // the model.
+ string getterCode;
+ if (propertyPathExpression) {
+ // If its a simple property path, then we parenthesize since in script, the
+ // properties are represented as functions.
+ value = value.Replace(".", "().");
+ getterCode = "return model." + value + "();";
+ }
+ else {
+ // If its not a simple property path, we're going to assume that the developer
+ // has referenced the property functions themselves, and we use as-is.
+ getterCode = "with(model) { return " + value + "; }";
+ }
+
+ Func<object, object> getter = (Func<object, object>)(object)new Function(getterCode, "model");
+
+ if (expressionType == "init") {
+ // Read-only, one-time binder... so execute the getter, and create
+ // an expression with the current value.
+ return new Expression(getter(model), /* canChange */ false);
+ }
+ else if (expressionType == "link") {
+ // Read-only, bound binder... create a BindExpression that tracks the
+ // value by observing any observables representing the property.
+ return new BindExpression(model, getter, null);
+ }
+ else {
+ // Read-write, bound binder ... must be a simple property path.
+
+ if (propertyPathExpression == false) {
+ Debug.Fail("A bind expression's value must be a property path. The expression '" + value + "' is invalid.");
+ }
+
+ string setterCode = "model." + value + "(value);";
+ Action<object, object> setter = (Action<object, object>)(object)new Function(setterCode, "model", "value");
+
+ return new BindExpression(model, getter, setter);
+ }
+ }
+
+ /// <summary>
+ /// Registers a binder factory. The supplied name prefixed with "data-" is used
+ /// as the attribute name in markup to create a binder.
+ /// </summary>
+ /// <param name="name">The name of the expression handler.</param>
+ /// <param name="factory">The factory being registered.</param>
+ public void RegisterBinder(string name, BinderFactory factory) {
+ Debug.Assert(String.IsNullOrEmpty(name) == false);
+ Debug.Assert(factory != null);
+ Debug.Assert(_registeredBinders.ContainsKey(name) == false, "A binder with name '" + name + "' was already registered.");
+
+ _registeredBinders[name] = factory;
+ }
+
+ /// <summary>
+ /// Registers an expression factory. The supplied name is used in markup to represent
+ /// an instance of the associated expression.
+ /// </summary>
+ /// <param name="name">The name of expression.</param>
+ /// <param name="factory">The factory to be used to handle the supplied name.</param>
+ public void RegisterExpression(string name, ExpressionFactory factory) {
+ Debug.Assert(String.IsNullOrEmpty(name) == false);
+ Debug.Assert(factory != null);
+ Debug.Assert(_registeredExpressions.ContainsKey(name) == false, "An expression factory with name '" + name + "' was already registered.");
+
+ _registeredExpressions[name] = factory;
+ }
+
+ private void SetupBindings(Element element, object model) {
+ Debug.Assert(element != null);
+
+ string bindings = (string)element.GetAttribute(Application.BindingsAttribute);
+ bindings.ReplaceRegex(Application.BindingsRegex, delegate(string match /*, string binderType, string expressionType, string expressionValue */) {
+ string binderType = (string)Arguments.GetArgument(1);
+ string expressionType = (string)Arguments.GetArgument(2);
+ string expressionValue = (string)Arguments.GetArgument(3);
+
+ ExpressionFactory expressionFactory = _registeredExpressions[expressionType];
+ Debug.Assert(expressionFactory != null, "Unknown expression of type '" + expressionType + "' found.");
+
+ if (expressionFactory != null) {
+ Expression expression = expressionFactory(model, expressionType, expressionValue);
+ Binder binder = null;
+
+ // TODO: Add support for binding attributes - @xxx
+
+ if (binderType.StartsWith("on.")) {
+ Debug.Assert(expression.CanChange == false, "Events cannot be bound to dynamic expressions.");
+ Debug.Assert(expression.GetValue() is Action);
+
+ binder = new EventBinder(element, binderType.Substr(3), (ElementEventListener)expression.GetValue());
+ }
+ else if (binderType.StartsWith("style.")) {
+ object style = element.Style;
+ binder = new PropertyBinder(style, binderType.Substr(6), expression);
+ }
+ else {
+ BinderFactory binderFactory = _registeredBinders[binderType];
+ if (binderFactory == null) {
+ binder = new PropertyBinder(element, binderType, expression);
+ }
+ else {
+ binder = binderFactory(element, binderType, expression);
+ }
+ }
+
+ if (binder != null) {
+ binder.Update();
+ if (expression.CanChange == false) {
+ // Since the expression value cannot change, there isn't a whole lot of need
+ // to keep the binder alive and manage it.
+ binder = null;
+ }
+ }
+
+ if (binder != null) {
+ // The binder is managed using a behavior that is attached to the element.
+ // This allows stashing the model for later retrieval, as well as a way to
+ // dispose bindings (the behavior disposes all binders it is managing).
+
+ BinderManager binderManager = (BinderManager)Behavior.GetBehavior(element, typeof(BinderManager));
+ if (binderManager == null) {
+ binderManager = new BinderManager();
+ binderManager.Initialize(element, null);
+ binderManager.Model = model;
+ }
+
+ binderManager.AddBinder(binder);
+ }
+ }
+
+ return String.Empty;
+ });
+ }
+ }
+}
View
68 fx/Sharpen/Core/Application.Services.cs
@@ -0,0 +1,68 @@
+// Application.Services.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Html;
+using Sharpen.Html.Utility;
+
+namespace Sharpen.Html {
+
+ public sealed partial class Application {
+
+ /// <summary>
+ /// Registers a service type.
+ /// </summary>
+ /// <param name="name">The name of the service.</param>
+ /// <param name="serviceImplementationType">The type providing the implementation of the service.</param>
+ /// <param name="serviceType">The type of the service.</param>
+ public void RegisterService(string name, Type serviceImplementationType, Type serviceType) {
+ Debug.Assert(String.IsNullOrEmpty(name) == false);
+ Debug.Assert(serviceImplementationType != null);
+ Debug.Assert(serviceType != null);
+ Debug.Assert(_registeredServices.ContainsKey(name) == false, "A service with name '" + name + "' was already registered.");
+
+ ServiceRegistration registration = new ServiceRegistration();
+ registration.ServiceImplementationType = serviceImplementationType;
+ registration.ServiceType = serviceType;
+
+ _registeredServices[name] = registration;
+ }
+
+ private void SetupServices() {
+ string servicesAttribute = (string)Document.Body.GetAttribute(ServicesAttribute);
+ if (String.IsNullOrEmpty(servicesAttribute)) {
+ return;
+ }
+
+ string[] serviceNames = servicesAttribute.Split(",");
+ int serviceCount = serviceNames.Length;
+
+ for (int i = 0; i < serviceCount; i++) {
+ string name = serviceNames[i];
+
+ ServiceRegistration registration = _registeredServices[name];
+ Debug.Assert(registration != null, "Unknown service '" + name + "'");
+
+ // TODO: Add support for deferred loading of service implementation, where
+ // the service gets included later, even though it is declared at the
+ // page level.
+
+ object service = GetObject(registration.ServiceImplementationType);
+
+ IInitializable initializableService = service as IInitializable;
+ if (initializableService != null) {
+ Dictionary<string, object> options = OptionsParser.GetOptions(Document.Body, name);
+ initializableService.BeginInitialization(options);
+ initializableService.EndInitialization();
+ }
+
+ RegisterObject(registration.ServiceType, service);
+ }
+ }
+ }
+}
View
103 fx/Sharpen/Core/Application.Templates.cs
@@ -0,0 +1,103 @@
+// Application.Templates.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Html;
+using Sharpen.Html.Utility;
+
+namespace Sharpen.Html {
+
+ public sealed partial class Application {
+
+ /// <summary>
+ /// Compiles a template instance from the specified template type and content.
+ /// </summary>
+ /// <param name="type">The template engine to use to compile the template.</param>
+ /// <param name="content">The template content.</param>
+ /// <param name="options">Any options to be passed to the template engine.</param>
+ /// <returns>The newly compiled template instance.</returns>
+ public Template CompileTemplate(string type, string content, Dictionary<string, object> options) {
+ Debug.Assert(String.IsNullOrEmpty(type) == false);
+ Debug.Assert(String.IsNullOrEmpty(content) == false);
+
+ TemplateEngine templateEngine = _registeredTemplateEngines[type];
+ Debug.Assert(templateEngine != null, "No template engine was found to be able to process the template typed '" + type + "'.");
+
+ if (templateEngine == null) {
+ return null;
+ }
+
+ return templateEngine(content, options);
+ }
+
+ /// <summary>
+ /// Gets a template instance from the specified template name. The specified name
+ /// is used to lookup a script element with a matching id.
+ /// </summary>
+ /// <param name="name">The name to lookup a template.</param>
+ /// <returns>The template instance if the associated element could be found and successfully parsed.</returns>
+ public Template GetTemplate(string name) {
+ Debug.Assert(String.IsNullOrEmpty(name) == false);
+
+ // TODO: Should we allow plugging in a template selector callback?
+ // We would need to have a value parameter to pass in the data value into GetTemplate.
+
+ // Check if there is a previously parsed or registered template
+ Template template = _registeredTemplates[name];
+ if (template != null) {
+ return template;
+ }
+
+ Element templateElement = Document.GetElementById(name);
+ Debug.Assert(templateElement != null, "Could not find a template element with the id '" + name + "'.");
+ if (templateElement == null) {
+ return null;
+ }
+
+ // Parse the template using the associated template engine
+ string templateType = (string)templateElement.GetAttribute(TemplateTypeAttribute);
+ Debug.Assert(String.IsNullOrEmpty(templateType) == false, "A template must have a valid data-template attribute set.");
+
+ template = CompileTemplate(templateType,
+ templateElement.TextContent,
+ OptionsParser.GetOptions(templateElement, TemplateOptionsAttribute));
+
+ // Cache the newly parsed template for future use
+ _registeredTemplates[name] = template;
+
+ return template;
+ }
+
+ /// <summary>
+ /// Registers a template with the specified name.
+ /// </summary>
+ /// <param name="name">The name of template.</param>
+ /// <param name="template">The template to be used.</param>
+ public void RegisterTemplate(string name, Template template) {
+ Debug.Assert(String.IsNullOrEmpty(name) == false);
+ Debug.Assert(template != null);
+ Debug.Assert(_registeredTemplates.ContainsKey(name) == false, "A template with name '" + name + "' was already registered.");
+
+ _registeredTemplates[name] = template;
+ }
+
+ /// <summary>
+ /// Registers a template engine. The supplied name is used to match against the
+ /// mime type attribute on a template script element.
+ /// </summary>
+ /// <param name="name">The name of template engine.</param>
+ /// <param name="engine">The engine to be used to handle the supplied name.</param>
+ public void RegisterTemplateEngine(string name, TemplateEngine engine) {
+ Debug.Assert(String.IsNullOrEmpty(name) == false);
+ Debug.Assert(engine != null);
+ Debug.Assert(_registeredTemplateEngines.ContainsKey(name) == false,
+ "A template engine with name '" + name + "' was already registered.");
+
+ _registeredTemplateEngines[name] = engine;
+ }
+ }
+}
View
358 fx/Sharpen/Core/Application.cs
@@ -0,0 +1,358 @@
+// Application.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Html;
+using System.Runtime.CompilerServices;
+using Sharpen.Html.Utility;
+
+namespace Sharpen.Html {
+
+ /// <summary>
+ /// A page-level singleton representing the current application. It provides core
+ /// services like settings, IoC, and pub/sub events-based messaging for decoupled
+ /// components.
+ /// </summary>
+ public sealed partial class Application : IApplication, IContainer, IEventManager {
+
+ internal const string ServicesAttribute = "data-services";
+
+ internal const string BehaviorNameKey = "behaviorName";
+ internal const string BehaviorsKey = "behaviors";
+ internal const string BehaviorsAttribute = "data-behavior";
+ internal const string BehaviorsSelector = "*[data-behavior]";
+
+ internal const string ModelTypeAttribute = "data-model-type";
+
+ internal const string BindingsAttribute = "data-bindings";
+ internal const string BindingsSelector = "*[data-bindings]";
+ internal static readonly RegExp BindingsRegex =
+ new RegExp(@"([a-z\.\-]+)\s*:\s*([a-z]+)\s+([^;]*)\s*\;", "gi");
+ internal static readonly RegExp PropertyPathRegex =
+ new RegExp(@"[a-z0-9\.]+", "gi");
+
+ internal const string TemplateTypeAttribute = "data-template";
+ internal const string TemplateOptionsAttribute = "template-options";
+
+ /// <summary>
+ /// The current Application instance.
+ /// </summary>
+ public static readonly Application Current = new Application();
+
+ private readonly Dictionary<string, ServiceRegistration> _registeredServices;
+ private readonly Dictionary<string, BehaviorRegistration> _registeredBehaviors;
+ private readonly Dictionary<string, ExpressionFactory> _registeredExpressions;
+ private readonly Dictionary<string, BinderFactory> _registeredBinders;
+ private readonly Dictionary<string, TemplateEngine> _registeredTemplateEngines;
+ private readonly Dictionary<string, Template> _registeredTemplates;
+
+ private Dictionary<string, object> _catalog;
+ private Dictionary<string, Dictionary<string, Callback>> _subscriptions;
+ private int _subscriptionCount;
+
+ private object _model;
+
+ static Application() {
+ Script.SetTimeout(delegate() {
+ Application.Current.ActivateFragment(Document.Body, /* contentOnly */ false);
+ }, 0);
+ }
+
+ private Application() {
+ _catalog = new Dictionary<string, object>();
+ _subscriptions = new Dictionary<string, Dictionary<string, Callback>>();
+
+ _registeredServices = new Dictionary<string, ServiceRegistration>();
+ _registeredBehaviors = new Dictionary<string, BehaviorRegistration>();
+ _registeredExpressions = new Dictionary<string, ExpressionFactory>();
+ _registeredBinders = new Dictionary<string, BinderFactory>();
+ _registeredTemplateEngines = new Dictionary<string, TemplateEngine>();
+ _registeredTemplates = new Dictionary<string, Template>();
+
+ RegisterObject(typeof(IApplication), this);
+ RegisterObject(typeof(IContainer), this);
+ RegisterObject(typeof(IEventManager), this);
+
+ RegisterBehavior(BinderManager.BehaviorName, typeof(BinderManager));
+ RegisterExpression("bind", ProcessModelExpression);
+ RegisterExpression("init", ProcessModelExpression);
+ RegisterExpression("link", ProcessModelExpression);
+ RegisterExpression("exec", ProcessModelExpression);
+ RegisterBinder("text", BindContent);
+ RegisterBinder("html", BindContent);
+ RegisterBinder("value", BindValue);
+ RegisterBinder("visibility", BindVisibility);
+ }
+
+ /// <summary>
+ /// The model object associated with the application.
+ /// </summary>
+ public object Model {
+ get {
+ return _model;
+ }
+ }
+
+ /// <summary>
+ /// Activates a specified element and its children by instantiating any
+ /// declaratively specified behaviors or bindings within the markup.
+ /// </summary>
+ /// <param name="element">The element to activate.</param>
+ /// <param name="contentOnly">Whether the element should be activated, or only its contained content.</param>
+ public extern void ActivateFragment(Element element, bool contentOnly);
+
+ /// <summary>
+ /// Activates a specified element and its children by instantiating any
+ /// declaratively specified behaviors or bindings within the markup.
+ /// </summary>
+ /// <param name="element">The element to activate.</param>
+ /// <param name="contentOnly">Whether the element should be activated, or only its contained content.</param>
+ /// <param name="model">The model to bind to.</param>
+ public void ActivateFragment(Element element, bool contentOnly, object model) {
+ Debug.Assert(element != null);
+
+ if (element == Document.Body) {
+ // Perform app-level activation (tied to body initialization)
+
+ // Setup top-level services specified declaratively.
+ SetupServices();
+
+ // Create the model declaratively associated with the application.
+ if (model == null) {
+ string modelTypeName = (string)Document.Body.GetAttribute(ModelTypeAttribute);
+ if (String.IsNullOrEmpty(modelTypeName) == false) {
+ Type modelType = Type.GetType(modelTypeName);
+ Debug.Assert(modelType != null, "Could not resolve model '" + modelTypeName + "'");
+
+ model = GetObject(modelType);
+
+ IInitializable initializableModel = model as IInitializable;
+ if (initializableModel != null) {
+ Dictionary<string, object> modelData = OptionsParser.GetOptions(element, "model");
+ initializableModel.BeginInitialization(modelData);
+ initializableModel.EndInitialization();
+ }
+ }
+ }
+
+ _model = model;
+ }
+
+ // Create expressions and bindings associated with the specified elements and
+ // the contained elements. Do this first, as this allows behaviors to look at
+ // values set as a result of bindings when they are attached.
+ if ((contentOnly == false) && element.HasAttribute(Application.BindingsAttribute)) {
+ SetupBindings(element, model);
+ }
+
+ ElementCollection boundElements = element.QuerySelectorAll(Application.BindingsSelector);
+ for (int i = 0, boundElementCount = boundElements.Length; i < boundElementCount; i++) {
+ SetupBindings(boundElements[i], model);
+ }
+
+ // Attach behaviors associated declaratively with the specified element and the
+ // contained elements.
+ if ((contentOnly == false) && element.HasAttribute(Application.BehaviorsAttribute)) {
+ AttachBehaviors(element);
+ }
+
+ ElementCollection extendedElements = element.QuerySelectorAll(Application.BehaviorsSelector);
+ for (int i = 0, extendedElementCount = extendedElements.Length; i < extendedElementCount; i++) {
+ AttachBehaviors(extendedElements[i]);
+ }
+ }
+
+ /// <summary>
+ /// Deactivates the specified element and its children by disposing any
+ /// behaviors or binding instances attached to the elements.
+ /// </summary>
+ /// <param name="element">The element to deactivate.</param>
+ /// <param name="contentOnly">Whether the element should be deactivated, or only its contained content.</param>
+ public void DeactivateFragment(Element element, bool contentOnly) {
+ Debug.Assert(element != null);
+
+ // Detach behaviors associated with the element and the contained elements.
+ if ((contentOnly == false) && element.HasAttribute(Application.BehaviorsAttribute)) {
+ DetachBehaviors(element);
+ }
+
+ ElementCollection elements = element.QuerySelectorAll(Application.BehaviorsSelector);
+ int matchingElements = elements.Length;
+
+ if (matchingElements != 0) {
+ for (int i = 0; i < matchingElements; i++) {
+ DetachBehaviors(elements[i]);
+ }
+ }
+ }
+
+ private string GetTypeKey(Type type) {
+ string key = Script.GetField<string>(type, "$key");
+ if (key == null) {
+ key = Date.Now.GetTime().ToString();
+ Script.SetField(type, "$key", key);
+ }
+
+ return key;
+ }
+
+ #region Implementation of IApplication
+
+ /// <summary>
+ /// Gets the value of the specified setting.
+ /// </summary>
+ /// <param name="name">The name of the setting.</param>
+ /// <returns>The value of the specified setting.</returns>
+ public string GetSetting(string name) {
+ // TODO: Implement access to query string settings as well as maybe
+ // a few other things like JSON data written out as page-level
+ // metadata, or maybe a JSON block in an embedded script element.
+ return null;
+ }
+
+ #endregion
+
+ #region Implementation of IContainer
+
+ /// <summary>
+ /// Gets an instance of the specified object type. An instance is created
+ /// if one has not been registered with the container. A factory method is
+ /// called if a factory has been registered or implemented on the object type.
+ /// </summary>
+ /// <param name="objectType">The type of object to retrieve.</param>
+ /// <returns>The object instance.</returns>
+ public object GetObject(Type objectType) {
+ Debug.Assert(objectType != null, "Expected an object type when creating objects.");
+
+ string catalogKey = GetTypeKey(objectType);
+ object catalogItem = _catalog[catalogKey];
+
+ if (catalogItem == null) {
+ Function objectFactory = Script.GetField(objectType, "factory") as Function;
+ if (objectFactory != null) {
+ catalogItem = objectFactory;
+ }
+ else {
+ return Script.CreateInstance(objectType);
+ }
+ }
+
+ if (catalogItem is Function) {
+ return ((Function)catalogItem).Call(null, (IContainer)this, objectType);
+ }
+
+ return catalogItem;
+ }
+
+ /// <summary>
+ /// Registers an object factory with the container. The factory is called
+ /// when the particular object type is requested. The factory can decide whether to
+ /// cache object instances it returns or not, based on its policy.
+ /// </summary>
+ /// <param name="objectType"></param>
+ /// <param name="objectFactory"></param>
+ public void RegisterFactory(Type objectType, Func<IContainer, Type, object> objectFactory) {
+ Debug.Assert(objectType != null, "Expected an object type when registering object factories.");
+ Debug.Assert(objectFactory != null, "Expected an object factory when registering object factories.");
+
+ _catalog[GetTypeKey(objectType)] = objectFactory;
+ }
+
+ /// <summary>
+ /// Registers an object instance for the specified object type.
+ /// </summary>
+ /// <param name="objectType">The type that can be used to lookup the specified object instance.</param>
+ /// <param name="objectInstance">The object instance to use.</param>
+ public void RegisterObject(Type objectType, object objectInstance) {
+ Debug.Assert(objectType != null, "Expected an object type when registering objects.");
+ Debug.Assert(objectInstance != null, "Expected an object instance when registering objects.");
+
+ _catalog[GetTypeKey(objectType)] = objectInstance;
+ }
+
+ #endregion
+
+ #region Implementation of IEventManager
+
+ /// <summary>
+ /// Raises the specified event. The type of the event arguments is used to determine
+ /// which subscribers the event is routed to.
+ /// </summary>
+ /// <param name="eventArgs">The event arguments containing event-specific data.</param>
+ public void PublishEvent(EventArgs eventArgs) {
+ Debug.Assert(eventArgs != null, "Must specify an eventArgs when raising an event.");
+
+ string eventTypeKey = GetTypeKey(eventArgs.GetType());
+
+ Dictionary<string, Callback> eventHandlerMap = _subscriptions[eventTypeKey];
+ if (eventHandlerMap != null) {
+ // TODO: Handle the case where a subscriber unsubscribes while we're iterating
+ // Should we do it here by copying/cloning the list, or should we handle it
+ // by completing unsubscribe asynchronously?
+ foreach (KeyValuePair<string, Callback> eventHandlerEntry in eventHandlerMap) {
+ eventHandlerEntry.Value(eventArgs);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Subscribes to the specified type of events. The resulting cookie can be used for
+ /// unsubscribing.
+ /// </summary>
+ /// <param name="eventType">The type of the event.</param>
+ /// <param name="eventHandler">The event handler to invoke when the specified event type is raised.</param>
+ /// <returns></returns>
+ public object SubscribeEvent(Type eventType, Callback eventHandler) {
+ Debug.Assert(eventType != null, "Must specify an event type when subscribing to events.");
+ Debug.Assert(eventHandler != null, "Must specify an event handler when subscribing to events.");
+
+ string eventTypeKey = GetTypeKey(eventType);
+
+ Dictionary<string, Callback> eventHandlerMap = _subscriptions[eventTypeKey];
+ if (eventHandlerMap == null) {
+ eventHandlerMap = new Dictionary<string, Callback>();
+ _subscriptions[eventTypeKey] = eventHandlerMap;
+ }
+
+ string eventHandlerKey = (++_subscriptionCount).ToString();
+ eventHandlerMap[eventHandlerKey] = eventHandler;
+
+ // The subscription cookie we use is an object with the two strings
+ // identifying the event handler uniquely - the event type key used to index
+ // into the top-level event handlers key/value pair list, and the handler key
+ // (as generated above) to index into the event-type-specific key/value pair list.
+ // Keep these in sync with Unsubscribe...
+
+ return new Dictionary<string, string>("type", eventTypeKey, "handler", eventHandlerKey);
+ }
+
+ /// <summary>
+ /// Unsubcribes from a previously subscribed-to event type.
+ /// </summary>
+ /// <param name="subscriptionCookie">The subscription cookie.</param>
+ public void UnsubscribeEvent(object subscriptionCookie) {
+ Debug.Assert(subscriptionCookie != null, "Must specify the subscription cookie when unsubscribing to events.");
+ Debug.Assert(Script.HasField(subscriptionCookie, "type") && Script.HasField(subscriptionCookie, "handler"),
+ "A valid subscription cookie is an object with 'type' and 'handler' fields.");
+
+ Dictionary<string, string> keys = (Dictionary<string, string>)subscriptionCookie;
+
+ Dictionary<string, Callback> eventHandlerMap = _subscriptions[keys["type"]];
+ Debug.Assert(eventHandlerMap != null, "Invalid subscription cookie.");
+ Debug.Assert(eventHandlerMap.ContainsKey(keys["handler"]), "Invalid subscription cookie.");
+
+ eventHandlerMap.Remove(keys["handler"]);
+
+ if (eventHandlerMap.Count == 0) {
+ _subscriptions.Remove(keys["type"]);
+ }
+ }
+
+ #endregion
+ }
+}
View
119 fx/Sharpen/Core/Behavior.cs
@@ -0,0 +1,119 @@
+// Behavior.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Html;
+
+namespace Sharpen.Html {
+
+ /// <summary>
+ /// Represents a script objects that can be attached to an Element to
+ /// extend its standard functionality.
+ /// </summary>
+ public abstract class Behavior : IDisposable {
+
+ private Element _element;
+
+ /// <summary>
+ /// Gets the element that this behavior is attached to.
+ /// </summary>
+ protected Element Element {
+ get {
+ Debug.Assert(_element != null, "Element is being accessed after this behavior has been disposed.");
+ return _element;
+ }
+ }
+
+ /// <summary>
+ /// Disposes a behavior by detaching itself from its associated element.
+ /// When overriding this, be sure to call the base functionality at the after the implementation
+ /// in the derived class.
+ /// </summary>
+ public virtual void Dispose() {
+ Debug.Assert(_element != null, "Behavior has already been disposed.");
+
+ // First remove the expando corresponding to this behavior on the element.
+
+ string name = Script.GetField<string>(this.GetType(), Application.BehaviorNameKey);
+ Script.DeleteField(_element, name);
+
+ // Next remove this behavior from the key/value pair list of all behaviors
+ // being maintained on the element.
+
+ Dictionary<string, Behavior> behaviors = Script.GetField<Dictionary<string, Behavior>>(_element, Application.BehaviorsKey);
+ Debug.Assert(behaviors != null);
+
+ behaviors.Remove(name);
+
+ _element = null;
+ }
+
+ /// <summary>
+ /// Gets a behavior instance attached to the specified element if one exists.
+ /// </summary>
+ /// <param name="element">The element to lookup.</param>
+ /// <param name="behaviorType">The type of the behavior to lookup.</param>
+ /// <returns></returns>
+ public static Behavior GetBehavior(Element element, Type behaviorType) {
+ Debug.Assert(element != null, "Must specify the element to inspect for a behavior instance.");
+ Debug.Assert(behaviorType != null, "Must specify a behavior type to lookup.");
+
+ // Find the first matching behavior by enumerating the key/value pair
+ // list of behaviors on the element.
+
+ Dictionary<string, Behavior> behaviors = Script.GetField<Dictionary<string, Behavior>>(element, Application.BehaviorsKey);
+ if (behaviors != null) {
+ foreach (KeyValuePair<string, Behavior> behaviorEntry in behaviors) {
+ if (behaviorType.IsInstanceOfType(behaviorEntry.Value)) {
+ return behaviorEntry.Value;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Initializes a behavior by attaching it to the specified element.
+ /// When overriding this, be sure to call the base functionality before the implementation
+ /// in the derived class.
+ /// </summary>
+ /// <param name="element">The element to attach the behavior to.</param>
+ /// <param name="options">Any initialization options that the behavior should consume.</param>
+ public virtual void Initialize(Element element, Dictionary<string, object> options) {
+ Debug.Assert(_element == null, "A behavior should be initialized only once.");
+ Debug.Assert(element != null, "Expected a valid element to initialize a behavior.");
+
+ _element = element;
+
+ // Automatically expose the behavior on the DOM element as an expando using
+ // the name of the behavior as the key.
+
+ string name = Script.GetField<string>(this.GetType(), Application.BehaviorNameKey);
+ Script.SetField(element, name, this);
+
+ // Add a 'behaviors' expando on the element which is a key/value pair
+ // list of behavior name -> behavior instance mapping.
+
+ Dictionary<string, Behavior> behaviors = Script.GetField<Dictionary<string, Behavior>>(element, Application.BehaviorsKey);
+ if (behaviors == null) {
+ behaviors = new Dictionary<string, Behavior>();
+ Script.SetField(element, Application.BehaviorsKey, behaviors);
+ }
+
+ behaviors[name] = this;
+
+ // Set data-behavior on the element if it is not already set (i.e. if the behavior
+ // is being programmatically constructed), so that if DisposeBehaviors is called,
+ // then this element is picked up via the CSS query used to find elements that have
+ // behaviors.
+ if (element.HasAttribute(Application.BehaviorsAttribute) == false) {
+ element.SetAttribute(Application.BehaviorsAttribute, String.Empty);
+ }
+ }
+ }
+}
View
19 fx/Sharpen/Core/BehaviorRegistration.cs
@@ -0,0 +1,19 @@
+// BehaviorRegistration.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Sharpen.Html {
+
+ [ScriptImport]
+ [ScriptIgnoreNamespace]
+ [ScriptName("Object")]
+ internal sealed class BehaviorRegistration {
+
+ public Type BehaviorType = null;
+ public Type ServiceType = null;
+ }
+}
View
21 fx/Sharpen/Core/Binder.cs
@@ -0,0 +1,21 @@
+// Binder.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+
+namespace Sharpen.Html {
+
+ /// <summary>
+ /// A binder is an object that performs the work of binding an expression value
+ /// with the associated DOM element.
+ /// </summary>
+ public abstract class Binder : IDisposable {
+
+ public abstract void Dispose();
+
+ internal virtual void Update() {
+ }
+ }
+}
View
20 fx/Sharpen/Core/BinderFactory.cs
@@ -0,0 +1,20 @@
+// BinderFactory.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Html;
+
+namespace Sharpen.Html {
+
+ /// <summary>
+ /// Creates an expression binder given an element, the bound property and
+ /// the expression to bind to.
+ /// </summary>
+ /// <param name="element">The element that is bound.</param>
+ /// <param name="property">The property on the element that is bound.</param>
+ /// <param name="expression">The expression to bind to.</param>
+ /// <returns>The binder that was created. If no binder needs to be created, the return value is null.</returns>
+ public delegate Binder BinderFactory(Element element, string property, Expression expression);
+}
View
49 fx/Sharpen/Core/BinderManager.cs
@@ -0,0 +1,49 @@
+// BinderManager.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Html;
+
+namespace Sharpen.Html {
+
+ internal sealed class BinderManager : Behavior {
+
+ internal const string BehaviorName = "binderManager";
+
+ private object _model;
+ private List<Binder> _binders;
+
+ public BinderManager() {
+ _binders = new List<Binder>();
+ }
+
+ public object Model {
+ get {
+ return _model;
+ }
+ set {
+ _model = value;
+ }
+ }
+
+ public void AddBinder(Binder binder) {
+ Debug.Assert(_binders != null);
+ Debug.Assert(binder != null);
+
+ _binders.Add(binder);
+ }
+
+ public override void Dispose() {
+ foreach (Binder binder in _binders) {
+ binder.Dispose();
+ }
+ _binders = null;
+
+ base.Dispose();
+ }
+ }
+}
View
61 fx/Sharpen/Core/Bindings/BindExpression.cs
@@ -0,0 +1,61 @@
+// BindExpression.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.ComponentModel;
+
+namespace Sharpen.Html.Bindings {
+
+ /// <summary>
+ /// Implementation of an expression that binds to a value off the model and
+ /// tracks changes by virtue of being an observer.
+ /// </summary>
+ internal sealed class BindExpression : Expression, IObserver {
+
+ private object _model;
+ private Func<object, object> _getter;
+ private Action<object, object> _setter;
+
+ public BindExpression(object model, Func<object, object> getter, Action<object, object> setter)
+ : base(null, /* canChange */ true) {
+ _model = model;
+ _getter = getter;
+ _setter = setter;
+
+ RetrieveValue(/* notify */ false);
+ }
+
+ public void InvalidateObserver() {
+ RetrieveValue(/* notify */ true);
+ }
+
+ private void RetrieveValue(bool notify) {
+ object value;
+
+ IDisposable observation = ObserverManager.RegisterObserver(this);
+ try {
+ value = _getter(_model);
+ UpdateValue(value, notify);
+ }
+ catch {
+ // TODO: Error handling (logging + breakpoint if debugger attached)
+ }
+ finally {
+ observation.Dispose();
+ }
+ }
+
+ public override void SetValue(object value) {
+ if (_setter != null) {
+ try {
+ _setter(_model, value);
+ }
+ catch {
+ // TODO: Error handling (logging + breakpoint if debugger attached)
+ }
+ }
+ }
+ }
+}
View
71 fx/Sharpen/Core/Bindings/ContentBinder.cs
@@ -0,0 +1,71 @@
+// ContentBinder.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Html;
+
+namespace Sharpen.Html.Bindings {
+
+ /// <summary>
+ /// Binds the content (textContent/innerHTML) of element to an expression.
+ /// </summary>
+ internal sealed class ContentBinder : PropertyBinder {
+
+ public ContentBinder(Element target, string property, Expression expression)
+ : base(target, property, expression) {
+ }
+
+ protected override void UpdateTarget(object target, string propertyName, object value) {
+ Element targetElement = (Element)target;
+
+ // Convert the value into a string if it isn't already... do this with a template
+ // if one is specified, or resort to ToString as a fallback.
+ string content = null;
+ if (value is string) {
+ content = (string)value;
+ }
+ else {
+ string templateName = (string)targetElement.GetAttribute("data-template");
+ if (String.IsNullOrEmpty(templateName) == false) {
+ Template template = Application.Current.GetTemplate(templateName);
+ if (template != null) {
+ content = template(value, /* index */ 0, /* context */ null);
+ }
+ }
+ else {
+ content = value.ToString();
+ }
+ }
+
+ // Raise contentUpdating event, so it allows any behaviors attached to this
+ // element to be notified
+ MutableEvent updatingEvent = Document.CreateEvent("Custom");
+ updatingEvent.InitCustomEvent("contentUpdating", /* canBubble */ false, /* canCancel */ false, null);
+ targetElement.DispatchEvent(updatingEvent);
+
+ // Deactivate the content, in case current content has attached behaviors or bindings.
+ Application.Current.DeactivateFragment(targetElement, /* contentOnly */ true);
+
+ // Update the element with the new value.
+ base.UpdateTarget(targetElement, propertyName, content);
+
+ // If the property that was updated was the innerHTML, then activate any
+ // binding expressions or behaviors that might have been specified within the
+ // new markup.
+ if ((String.IsNullOrEmpty(content) == false) && (propertyName == "innerHTML")) {
+ BinderManager binderManager = (BinderManager)Behavior.GetBehavior(targetElement, typeof(BinderManager));
+ if (binderManager != null) {
+ Application.Current.ActivateFragment(targetElement, /* contentOnly */ true, binderManager.Model);
+ }
+ }
+
+ // Raise contentUpdated event, so it allows any behaviors attached to this
+ // element to be notified
+ MutableEvent updatedEvent = Document.CreateEvent("Custom");
+ updatedEvent.InitCustomEvent("contentUpdated", /* canBubble */ false, /* canCancel */ false, null);
+ targetElement.DispatchEvent(updatedEvent);
+ }
+ }
+}
View
35 fx/Sharpen/Core/Bindings/EventBinder.cs
@@ -0,0 +1,35 @@
+// EventBinder.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Html;
+
+namespace Sharpen.Html.Bindings {
+
+ /// <summary>
+ /// Binds an event of an element to the value of an expression.
+ /// </summary>
+ internal sealed class EventBinder : Binder {
+
+ private Element _source;
+ private string _eventName;
+
+ private ElementEventListener _eventHandler;
+
+ public EventBinder(Element source, string eventName, ElementEventListener eventHandler) {
+ _source = source;
+ _eventName = eventName;
+ _eventHandler = eventHandler;
+ }
+
+ public override void Dispose() {
+ _source.RemoveEventListener(_eventName, _eventHandler, false);
+ }
+
+ internal override void Update() {
+ _source.AddEventListener(_eventName, _eventHandler, false);
+ }
+ }
+}
View
49 fx/Sharpen/Core/Bindings/PropertyBinder.cs
@@ -0,0 +1,49 @@
+// PropertyBinder.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Html;
+
+namespace Sharpen.Html.Bindings {
+
+ /// <summary>
+ /// This binds the property of an object to the value of an expression, and
+ /// updates the object as the expression's value changes.
+ /// </summary>
+ internal class PropertyBinder : Binder {
+
+ private object _target;
+ private string _propertyName;
+ private Expression _expression;
+
+ internal PropertyBinder(object target, string propertyName, Expression expression) {
+ _target = target;
+ _propertyName = propertyName;
+ _expression = expression;
+
+ if (_expression.CanChange) {
+ _expression.Subscribe(Update);
+ }
+ }
+
+ public override void Dispose() {
+ _target = null;
+ }
+
+ internal override void Update() {
+ if (_target != null) {
+ UpdateTarget(_target, _propertyName, _expression.GetValue());
+ }
+ }
+
+ protected void UpdateSource(object value) {
+ _expression.SetValue(value);
+ }
+
+ protected virtual void UpdateTarget(object target, string propertyName, object value) {
+ Script.SetField(target, propertyName, value);
+ }
+ }
+}
View
38 fx/Sharpen/Core/Bindings/ValueBinder.cs
@@ -0,0 +1,38 @@
+// ValueBinder.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Html;
+
+namespace Sharpen.Html.Bindings {
+
+ /// <summary>
+ /// Binds the value property of an input element to an expression.
+ /// This binder supports two-way binding.
+ /// </summary>
+ internal sealed class ValueBinder : PropertyBinder {
+
+ private InputElement _target;
+ private ElementEventListener _changeListener;
+
+ public ValueBinder(InputElement target, Expression expression)
+ : base(target, "value", expression) {
+ _target = target;
+ _changeListener = OnChanged;
+ target.AddEventListener("change", _changeListener, false);
+ }
+
+ public override void Dispose() {
+ if (_target != null) {
+ _target.RemoveEventListener("change", _changeListener, false);
+ _target = null;
+ }
+ }
+
+ private void OnChanged(ElementEvent e) {
+ UpdateSource(_target.Value);
+ }
+ }
+}
View
37 fx/Sharpen/Core/Bindings/VisibilityBinder.cs
@@ -0,0 +1,37 @@
+// VisibilityBinder.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Diagnostics;
+using System.Html;
+
+namespace Sharpen.Html.Bindings {
+
+ /// <summary>
+ /// Binds the visibility of an element to an expression.
+ /// </summary>
+ internal sealed class VisibilityBinder : PropertyBinder {
+
+ private string _visibleDisplay;
+ private bool _useDisplay;
+
+ public VisibilityBinder(Element target, string property, Expression expression)
+ : base(target, property, expression) {
+ _visibleDisplay = target.Style.Display;
+ _useDisplay = String.IsNullOrEmpty(_visibleDisplay) == false;
+ }
+
+ protected override void UpdateTarget(object target, string propertyName, object value) {
+ if (_useDisplay) {
+ string displayValue = (bool)value ? _visibleDisplay : "none";
+ base.UpdateTarget(((Element)target).Style, "display", displayValue);
+ }
+ else {
+ string visibilityValue = (bool)value ? "visible" : "hidden";
+ base.UpdateTarget(((Element)target).Style, "visibility", visibilityValue);
+ }
+ }
+ }
+}
View
82 fx/Sharpen/Core/Core.csproj
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{8C05D1B5-AF50-4CFB-8A8D-5558D4CE3FC4}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Sharpen</RootNamespace>
+ <AssemblyName>Sharpen</AssemblyName>
+ <CodeAnalysisRuleSet>Properties\FxCop.ruleset</CodeAnalysisRuleSet>
+ <GenerateScript>True</GenerateScript>
+ <GenerateResources>True</GenerateResources>
+ <MinimizeScript>True</MinimizeScript>
+ <CrunchScript>True</CrunchScript>
+ <CopyReferences>True</CopyReferences>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;CODE_ANALYSIS;SCRIPTSHARP</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <NoWarn>0028, 1591, 1684, 0626</NoWarn>
+ <DocumentationFile>bin\Debug\Sharpen.xml</DocumentationFile>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>CODE_ANALYSIS;SCRIPTSHARP</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <NoWarn>0028, 1591, 1684, 0626</NoWarn>
+ <DocumentationFile>bin\Release\Sharpen.xml</DocumentationFile>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <None Include="Properties\FxCop.ruleset" />
+ <None Include="packages.config" />
+ <Compile Include="Application.cs" />
+ <Compile Include="Application.Behaviors.cs">
+ <DependentUpon>Application.cs</DependentUpon>
+ </Compile>
+ <Compile Include="Application.Bindings.cs">
+ <DependentUpon>Application.cs</DependentUpon>
+ </Compile>
+ <Compile Include="Application.Services.cs">
+ <DependentUpon>Application.cs</DependentUpon>
+ </Compile>
+ <Compile Include="Application.Templates.cs">
+ <DependentUpon>Application.cs</DependentUpon>
+ </Compile>
+ <Compile Include="Behavior.cs" />
+ <Compile Include="BehaviorRegistration.cs" />
+ <Compile Include="ServiceRegistration.cs" />
+ <Compile Include="Binder.cs" />
+ <Compile Include="BinderFactory.cs" />
+ <Compile Include="BinderManager.cs" />
+ <Compile Include="Expression.cs" />
+ <Compile Include="ExpressionFactory.cs" />
+ <Compile Include="Template.cs" />
+ <Compile Include="TemplateEngine.cs" />
+ <Compile Include="Bindings\BindExpression.cs" />
+ <Compile Include="Bindings\ContentBinder.cs" />
+ <Compile Include="Bindings\EventBinder.cs" />
+ <Compile Include="Bindings\PropertyBinder.cs" />
+ <Compile Include="Bindings\ValueBinder.cs" />
+ <Compile Include="Bindings\VisibilityBinder.cs" />
+ <Compile Include="Utility\OptionsParser.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Reference Include="Script.Web">
+ <HintPath>..\packages\ScriptSharp.Lib.HTML.0.8\lib\Script.Web.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="Properties\TemplateEngine.js" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="..\packages\nuget.targets" />
+ <Import Project="..\packages\ScriptSharp.0.8\tools\ScriptSharp.targets" Condition="Exists('..\packages\ScriptSharp.0.8\tools\ScriptSharp.targets')" />
+</Project>
View
80 fx/Sharpen/Core/Expression.cs
@@ -0,0 +1,80 @@
+// Expression.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+
+namespace Sharpen.Html {
+
+ /// <summary>
+ /// Represents the base class for all expressions. Expressions represent
+ /// a value, and optionally a value that can change after being read (in which
+ /// case, it provides a mechanism to subscribe for changes)
+ /// </summary>
+ public class Expression {
+
+ private bool _canChange;
+ private Action _changeCallback;
+ private object _value;
+
+ /// <summary>
+ /// Initializes and instance of an Expression.
+ /// </summary>
+ /// <param name="value">The initial value of the expression.</param>
+ /// <param name="canChange">Whether the expression represents a value that can change.</param>
+ public Expression(object value, bool canChange) {
+ _value = value;
+ _canChange = canChange;
+ }
+
+ /// <summary>
+ /// Gets whether the expression represents a value that can change.
+ /// </summary>
+ public bool CanChange {
+ get {
+ return _canChange;
+ }
+ }
+
+ /// <summary>
+ /// Gets the current value of the expression.
+ /// </summary>
+ /// <returns>The current value.</returns>
+ public object GetValue() {
+ return _value;
+ }
+
+ /// <summary>
+ /// Sets the value of the expression. Most expressions are read-only and ignore
+ /// this call, but some expressions do support the ability to write back.
+ /// </summary>
+ /// <param name="value">The new value of the expression.</param>
+ public virtual void SetValue(object value) {
+ // By default expressions are read-only, so they ignore SetValue calls
+ // TODO: Should there be a IsReadOnly property?
+ }
+
+ /// <summary>
+ /// Allows a consumer of the expression to subscribe to change notifications.
+ /// </summary>
+ /// <param name="changeCallback">The callback to be invoked.</param>
+ public void Subscribe(Action changeCallback) {
+ if (_canChange) {
+ _changeCallback = changeCallback;
+ }
+ }
+
+ /// <summary>
+ /// Updates the value represented by the expression.
+ /// </summary>
+ /// <param name="value">The new value of the expression.</param>
+ /// <param name="notify">Whether to notify the subscriber of the change.</param>
+ protected void UpdateValue(object value, bool notify) {
+ _value = value;
+ if (notify && (_changeCallback != null)) {
+ _changeCallback();
+ }
+ }
+ }
+}
View
19 fx/Sharpen/Core/ExpressionFactory.cs
@@ -0,0 +1,19 @@
+// ExpressionFactory.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+
+namespace Sharpen.Html {
+
+ /// <summary>
+ /// Creates an expression given a model object and a string representation of the
+ /// expression.
+ /// </summary>
+ /// <param name="model">The model object to be used for binding purposes.</param>
+ /// <param name="expressionType">The type of expression.</param>
+ /// <param name="value">The string representation of the expression.</param>
+ /// <returns>An expression object.</returns>
+ public delegate Expression ExpressionFactory(object model, string expressionType, string value);
+}
View
39 fx/Sharpen/Core/Properties/AssemblyInfo.cs
@@ -0,0 +1,39 @@
+// AssemblyInfo.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+[assembly: AssemblyTitle("Sharpen")]
+[assembly: AssemblyDescription("Sharpen Framework for HTML5 Single Page Apps")]
+[assembly: AssemblyConfiguration("http://www.scriptsharp.com")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Script# Sharpen Framework")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+
+[assembly: ScriptAssembly("sharpen")]
+
+[assembly: ScriptTemplate(@"
+/*! {name}.js {version}
+ * {description}
+ */
+
+""use strict"";
+
+define('{name}', [{requires}], function({dependencies}) {
+ var $global = this;
+
+ {script}
+
+{include:Properties\TemplateEngine.js}
+
+ return $exports;
+});
+")]
View
101 fx/Sharpen/Core/Properties/FxCop.ruleset
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RuleSet Name="Script# Rules" Description="General Script Rules" ToolsVersion="10.0">
+ <Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
+ <Rule Id="CA1000" Action="Warning" />
+ <Rule Id="CA1001" Action="Warning" />
+ <Rule Id="CA1008" Action="Warning" />
+ <Rule Id="CA1009" Action="Warning" />
+ <Rule Id="CA1010" Action="Warning" />
+ <Rule Id="CA1011" Action="Warning" />
+ <Rule Id="CA1012" Action="Warning" />
+ <Rule Id="CA1013" Action="Warning" />
+ <Rule Id="CA1016" Action="Warning" />
+ <Rule Id="CA1018" Action="Warning" />
+ <Rule Id="CA1019" Action="Warning" />
+ <Rule Id="CA1020" Action="Warning" />
+ <Rule Id="CA1021" Action="Warning" />
+ <Rule Id="CA1023" Action="Warning" />
+ <Rule Id="CA1024" Action="Warning" />
+ <Rule Id="CA1026" Action="Warning" />
+ <Rule Id="CA1027" Action="Warning" />
+ <Rule Id="CA1028" Action="Warning" />
+ <Rule Id="CA1030" Action="Warning" />
+ <Rule Id="CA1032" Action="Warning" />
+ <Rule Id="CA1033" Action="Warning" />
+ <Rule Id="CA1034" Action="Warning" />
+ <Rule Id="CA1036" Action="Warning" />
+ <Rule Id="CA1040" Action="Warning" />
+ <Rule Id="CA1041" Action="Warning" />
+ <Rule Id="CA1043" Action="Warning" />
+ <Rule Id="CA1044" Action="Warning" />
+ <Rule Id="CA1045" Action="Warning" />
+ <Rule Id="CA1046" Action="Warning" />
+ <Rule Id="CA1047" Action="Warning" />
+ <Rule Id="CA1048" Action="Warning" />
+ <Rule Id="CA1052" Action="Warning" />
+ <Rule Id="CA1053" Action="Warning" />
+ <Rule Id="CA1058" Action="Warning" />
+ <Rule Id="CA1059" Action="Warning" />
+ <Rule Id="CA1061" Action="Warning" />
+ <Rule Id="CA1065" Action="Warning" />
+ <Rule Id="CA1500" Action="Warning" />
+ <Rule Id="CA1501" Action="Warning" />
+ <Rule Id="CA1502" Action="Warning" />
+ <Rule Id="CA1504" Action="Warning" />
+ <Rule Id="CA1505" Action="Warning" />
+ <Rule Id="CA1506" Action="Warning" />
+ <Rule Id="CA1700" Action="Warning" />
+ <Rule Id="CA1701" Action="Warning" />
+ <Rule Id="CA1702" Action="Warning" />
+ <Rule Id="CA1703" Action="Warning" />
+ <Rule Id="CA1704" Action="Warning" />
+ <Rule Id="CA1707" Action="Warning" />
+ <Rule Id="CA1708" Action="Warning" />
+ <Rule Id="CA1709" Action="Warning" />
+ <Rule Id="CA1710" Action="Warning" />
+ <Rule Id="CA1711" Action="Warning" />
+ <Rule Id="CA1712" Action="Warning" />
+ <Rule Id="CA1713" Action="Warning" />
+ <Rule Id="CA1714" Action="Warning" />
+ <Rule Id="CA1715" Action="Warning" />
+ <Rule Id="CA1716" Action="Warning" />
+ <Rule Id="CA1717" Action="Warning" />
+ <Rule Id="CA1719" Action="Warning" />
+ <Rule Id="CA1720" Action="Warning" />
+ <Rule Id="CA1721" Action="Warning" />
+ <Rule Id="CA1722" Action="Warning" />
+ <Rule Id="CA1724" Action="Warning" />
+ <Rule Id="CA1725" Action="Warning" />
+ <Rule Id="CA1726" Action="Warning" />
+ <Rule Id="CA1800" Action="Warning" />
+ <Rule Id="CA1801" Action="Warning" />
+ <Rule Id="CA1802" Action="Warning" />
+ <Rule Id="CA1804" Action="Warning" />
+ <Rule Id="CA1806" Action="Warning" />
+ <Rule Id="CA1809" Action="Warning" />
+ <Rule Id="CA1811" Action="Warning" />
+ <Rule Id="CA1812" Action="Warning" />
+ <Rule Id="CA1813" Action="Warning" />
+ <Rule Id="CA1814" Action="Warning" />
+ <Rule Id="CA1815" Action="Warning" />
+ <Rule Id="CA1819" Action="Warning" />
+ <Rule Id="CA1820" Action="Warning" />
+ <Rule Id="CA1821" Action="Warning" />
+ <Rule Id="CA2201" Action="Warning" />
+ <Rule Id="CA2202" Action="Warning" />
+ <Rule Id="CA2204" Action="Warning" />
+ <Rule Id="CA2207" Action="Warning" />
+ <Rule Id="CA2213" Action="Warning" />
+ <Rule Id="CA2214" Action="Warning" />
+ <Rule Id="CA2215" Action="Warning" />
+ <Rule Id="CA2217" Action="Warning" />
+ <Rule Id="CA2218" Action="Warning" />
+ <Rule Id="CA2219" Action="Warning" />
+ <Rule Id="CA2222" Action="Warning" />
+ <Rule Id="CA2223" Action="Warning" />
+ <Rule Id="CA2224" Action="Warning" />
+ <Rule Id="CA2226" Action="Warning" />
+ <Rule Id="CA2227" Action="Warning" />
+ <Rule Id="CA2233" Action="Warning" />
+ </Rules>
+</RuleSet>
View
183 fx/Sharpen/Core/Properties/TemplateEngine.js
@@ -0,0 +1,183 @@
+(function() {
+ var startTagRegex = /^<([^>\s\/]+)((\s+[^=>\s]+(\s*=\s*((\"[^"]*\")|(\'[^']*\')|[^>\s]+))?)*\s*\/?\s*)>/m,
+ endTagRegex = /^<\/([^>\s]+)[^>]*>/m,
+ attrsRegex = /([^=\s]+)(\s*=\s*((\"([^"]*)\")|(\'([^']*)\')|[^>\s]+))?/gm;
+
+ function sharpenTemplate(content) {
+ var _code = [];
+ var _buffer = [];
+ var _stack = [];
+
+ function writeCode(s) {
+ if (_buffer.length) {
+ var text = _buffer.join('');
+ text.length && _code.push('_text("' + _buffer.join('').replace(/"/g, '\\"') + '");');
+ _buffer = [];
+ }
+ _code.push(s);
+ }
+
+ function writeText(s) {
+ _buffer.push(s);
+ }
+
+ function writeValue(value) {
+ if (value && value.length) {
+ for (var i = 0; i < value.length; ) {
+ var beginIndex = value.indexOf("{{", i);
+ if (beginIndex < 0) {
+ break;
+ }
+ var endIndex = value.indexOf("}}", beginIndex + 2);
+ if (endIndex < 0) {
+ break;
+ }
+ writeText(value.substring(i, beginIndex));
+
+ var expr = value.substring(beginIndex + 2, endIndex).trim();
+ writeCode('_text(' + ((expr != '.') ? expr : 'i') + ');');
+ i = endIndex + 2;
+ }
+ writeText(value.substr(i).trim());
+ }
+ }
+
+ function handleStartElement(tag, attrs, closed) {
+ var frame = {};
+ _stack.push(frame);
+
+ if (attrs.loop) {
+ frame.loop = attrs.loop;
+ delete attrs.loop;
+ }
+ else if (attrs['if']) {
+ writeCode('if (' + attrs['if'] + ') {');
+ frame.conditional = true;
+ delete attrs['if'];
+ }
+ else if (attrs['elseif']) {
+ writeCode('else if (' + attrs['elseif'] + ') {');
+ frame.conditional = true;
+ delete attrs['elseif'];
+ }
+ else if (typeof attrs['else'] !== 'undefined') {
+ writeCode('else {');
+ frame.conditional = true;
+ delete attrs['else'];
+ }
+
+ writeText('<' + tag);
+ for (var attr in attrs) {
+ var value = attrs[attr];
+
+ if (!value) {
+ writeText(' ' + attr);
+ }
+ else if (value.startsWith('?{{') && value.endsWith('}}')) {
+ value = value.substr(3, value.length - 5);
+ writeCode('if (_t = (' + value + ')) {');
+ writeText(' ' + attr + '="');
+ writeCode('_text(_t);');
+ writeText('"');
+ writeCode('}');
+ }
+ else {
+ writeText(' ' + attr + '="');
+ writeValue(value);
+ writeText('"');
+ }
+ }
+ if (closed) {
+ writeText(' />', true);
+ handleEndElement(tag, closed);
+ }
+ else {
+ writeText('>');
+ frame.loop && writeCode('_loop(' + frame.loop + ', function(i) { with(i) {');
+ }
+ }
+ function handleEndElement(tag, closed) {
+ var frame = _stack.pop();
+
+ frame.loop && writeCode('}});');
+ !closed && writeText('</' + tag + '>');
+ frame.conditional && writeCode('}');
+ }
+ function handleText(s) {
+ writeValue(s);
+ }
+
+ function parseContent(content) {
+ var treatAsText = true;
+ while (content.length) {
+ if (content.substring(0, 4) == '<!--') {
+ var index = content.indexOf('-->');
+ if (index != -1) {
+ content = content.substring(index + 3);
+ treatAsText = false;
+ }
+ }
+ else if (content.substring(0, 2) == '</') {
+ if (endTagRegex.test(content)) {
+ content = RegExp.rightContext;
+
+ RegExp.lastMatch.replace(endTagRegex, function(tag, tagName) {
+ handleEndElement(tagName)
+ });
+
+ treatAsText = false;
+ }
+ }
+ else if (content.charAt(0) == '<') {
+ if (startTagRegex.test(content)) {
+ content = RegExp.rightContext;
+
+ RegExp.lastMatch.replace(startTagRegex, function(tag, tagName, rest) {
+ var closed = false;
+ var attrs = {};
+ rest.replace(attrsRegex, function(match, name, p1, p2, p3, p4, p5, p6, offset, all) {
+ if (name == '/') {
+ closed = true;
+ return;
+ }
+ attrs[name] = p5 ? p6 : (p3 ? p4 : (p1 ? p2 : null));
+ });
+
+ handleStartElement(tagName, attrs, closed);
+ });
+
+ treatAsText = false;
+ }
+ }
+
+ if (treatAsText) {
+ var index = content.indexOf("<");
+ if (index == -1) {
+ handleText(content);
+ break;
+ }
+ else {
+ handleText(content.substring(0, index));
+ content = content.substring(index);
+ }
+ }
+
+ treatAsText = true;
+ }
+ }
+
+ writeCode('var _ = [], _t;');
+ writeCode('function _text(t) { _.push(t); }');
+ writeCode('function _loop(a,f) { a && a.forEach(f); }');
+ writeCode('with (_d) {');
+
+ parseContent(content.replace(/[\r\n\t]/g, ' '));
+
+ writeCode('}');
+ writeCode('return _.join("");');
+
+ return new Function('_d', 'index', 'ctx', _code.join('\n'));
+ }
+
+ Sharpen$Html$Application.current.registerTemplateEngine('sharpen', sharpenTemplate);
+})();
View
19 fx/Sharpen/Core/ServiceRegistration.cs
@@ -0,0 +1,19 @@
+// ServiceRegistration.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Sharpen.Html {
+
+ [ScriptImport]
+ [ScriptIgnoreNamespace]
+ [ScriptName("Object")]
+ internal sealed class ServiceRegistration {
+
+ public Type ServiceImplementationType = null;
+ public Type ServiceType = null;
+ }
+}
View
18 fx/Sharpen/Core/Template.cs
@@ -0,0 +1,18 @@
+// Template.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+
+namespace Sharpen.Html {
+
+ /// <summary>
+ /// Creates an instance of the markup represented by this template.
+ /// </summary>
+ /// <param name="data">The data that is associated with the template instance.</param>
+ /// <param name="index">The index of the template if the template is being instantiated in a repeated fashion.</param>
+ /// <param name="context">Any additional contextual information that should be passed into the template.</param>
+ /// <returns>The markup instance.</returns>
+ public delegate string Template(object data, int index, object context);
+}
View
19 fx/Sharpen/Core/TemplateEngine.cs
@@ -0,0 +1,19 @@
+// TemplateEngine.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Html;
+
+namespace Sharpen.Html {
+
+ /// <summary>
+ /// Creates an instance of a template from the specified content.
+ /// </summary>
+ /// <param name="content">The content representing the template.</param>
+ /// <param name="options">Any options associated with the template.</param>
+ /// <returns>The template instance.</returns>
+ public delegate Template TemplateEngine(string content, Dictionary<string, object> options);
+}
View
105 fx/Sharpen/Core/Utility/OptionsParser.cs
@@ -0,0 +1,105 @@
+// OptionsParser.cs
+// Script#/FX/Sharpen/Core
+// This source code is subject to terms and conditions of the Apache License, Version 2.0.
+//
+
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Html;
+using System.Serialization;
+
+namespace Sharpen.Html.Utility {
+
+ internal static class OptionsParser {
+
+ // Find the following types of string sequences:
+ // Literals (true, false, null, numbers
+ // Strings (either single-quoted, or double-quoted)
+ // (note, we don't handle nested quotes)
+ // Element references (either #id, or .class)
+ // Syntax (comma for name/value separators, square brackets
+ // for arrays, curly braces for nested objects)
+ // Names (sequence of alphanumerics followed by a ':')
+ private static readonly RegExp _optionsParserRegex =
+ new RegExp("(true|false|null|-?[0-9.]+)|('[^']*'|\\\"[^\"]*\\\")|([#\\.][a-z][a-z0-9]*)|(\\,|\\[|\\]|\\{|\\})|([a-z][a-z0-9]*\\:)", "gi");
+
+ public static Dictionary<string, object> GetOptions(Element element, string name) {
+ // Options can be specified using a pseudo-JSON/css-esque syntax
+ // as the value of a data-<name> attribute on the element.
+
+ string optionsText = (string)element.GetAttribute("data-" + name);
+ if (String.IsNullOrEmpty(optionsText) == false) {
+ if (optionsText.StartsWith("{")) {
+ // Vanilla JSON object
+ return Json.ParseData<Dictionary<string, object>>(optionsText);
+ }
+
+ // Treat it as our pseudo-JSON/CSS-esque syntax which requires a bit of rewriting
+ // before it can be parsed as JSON.
+ return Parse(optionsText);
+ }
+ else {
+ // Create an empty object if no options were declaratively specified.
+ return new Dictionary<string, object>();
+ }
+ }
+
+ private static Dictionary<string, object> Parse(string optionsText) {
+ Debug.Assert(String.IsNullOrEmpty(optionsText) == false);
+
+ bool resolveElements = false;
+ optionsText = optionsText.ReplaceRegex(_optionsParserRegex,
+ delegate(string match /*, string simpleValue, string stringValue, string elementReference, string separator, string name */) {
+ string stringValue = (string)Arguments.GetArgument(2);
+ string elementReference = (string)Arguments.GetArgument(3);
+ string nameValue = (string)Arguments.GetArgument(5);
+ if (Script.IsValue(stringValue)) {
+ // Matches single and double quoted strings
+ // JSON strings are always double quoted, and additionally
+ // escape any double quotes within.
+
+ return '"' + stringValue.Substr(1, stringValue.Length - 2).Replace("\"", "\\\"") + '"';
+ }
+ else if (Script.IsValue(elementReference)) {
+ // ID references require resolution at JSON parse time
+ // Convert them to strings, so they can be parsed as valid JSON first.
+
+ resolveElements = true;
+ return '"' + elementReference + '"';
+ }
+ else if (Script.IsValue(nameValue)) {
+ // Matches a name followed by ":"
+ // In JSON, names must be double quoted
+
+ return '"' + nameValue.Substr(0, nameValue.Length - 1) + "\":";
+ }
+ return match;
+ });
+
+ // Finally turn this into a JSON object, by wrapping with curly braces
+
+ string optionsJson = "{" + optionsText + "}";
+
+ JsonParseCallback parseCallback = null;
+ if (resolveElements) {
+ parseCallback = ResolveElementReferences;
+ }
+
+ return Json.ParseData<Dictionary<string, object>>(optionsJson, parseCallback);
+ }
+
+ private static object ResolveElementReferences(string name, object value) {
+ if (value is String) {
+ if (((string)value).CharAt(0) == '#') {
+ return Document.QuerySelector((string)value);
+ }
+ else if (((string)value).CharAt(0) == '.') {
+ return Document.QuerySelectorAll((string)value);
+ }
+ }
+ return value;
+ }
+ }
+}
View
5 fx/Sharpen/Core/packages.config
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="ScriptSharp" version="0.8" targetFramework="net40" />
+ <package id="ScriptSharp.Lib.HTML" version="0.8" targetFramework="net40" />
+</packages>
View
20 fx/Sharpen/Sharpen.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{8C05D1B5-AF50-4CFB-8A8D-5558D4CE3FC4}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8C05D1B5-AF50-4CFB-8A8D-5558D4CE3FC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C05D1B5-AF50-4CFB-8A8D-5558D4CE3FC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8C05D1B5-AF50-4CFB-8A8D-5558D4CE3FC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8C05D1B5-AF50-4CFB-8A8D-5558D4CE3FC4}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
View
BIN  fx/Sharpen/packages/nuget.exe
Binary file not shown
View
16 fx/Sharpen/packages/nuget.targets
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <RestoreNugetPackages Condition="'$(RestoreNugetPackages)' == ''">false</RestoreNugetPackages>
+ <PackagesDir>$([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory)))</PackagesDir>
+ <RestoreCommand>$(MSBuildThisFileDirectory)nuget.exe install "$(MSBuildProjectDirectory)\packages.config" -o "$(PackagesDir)"</RestoreCommand>
+ <BuildDependsOn Condition="$(RestoreNugetPackages) == 'true'">
+ RestorePackages;
+ $(BuildDependsOn);
+ </BuildDependsOn>
+ </PropertyGroup>
+
+ <Target Name="RestorePackages">
+ <Exec Command="$(RestoreCommand)" LogStandardErrorAsError="true" />
+ </Target>
+</Project>
View
4 fx/Sharpen/packages/repositories.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<repositories>
+ <repository path="..\Core\packages.config" />
+</repositories>
Please sign in to comment.
Something went wrong with that request. Please try again.