PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/fx/Sharpen/Core/Application.Bindings.cs

http://github.com/nikhilk/scriptsharp
C# | 206 lines | 194 code | 8 blank | 4 comment | 6 complexity | 6442070631af266587ae8b5773270e85 MD5 | raw file
  1. // Application.Bindings.cs
  2. // Script#/FX/Sharpen/Core
  3. // This source code is subject to terms and conditions of the Apache License, Version 2.0.
  4. //
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.Html;
  9. using Sharpen.Bindings;
  10. namespace Sharpen {
  11. public sealed partial class Application {
  12. private static Binder BindContent(Element element, string property, Expression expression) {
  13. property = (property == "text") ? "textContent" : "innerHTML";
  14. return new ContentBinder(element, property, expression);
  15. }
  16. private static Binder BindValue(Element element, string property, Expression expression) {
  17. Debug.Assert((element.TagName.ToLowerCase() == "input") ||
  18. (element.TagName.ToLowerCase() == "textarea") ||
  19. (element.TagName.ToLowerCase() == "select"),
  20. "Value can only be bound on user input elements.");
  21. return new ValueBinder((InputElement)element, expression);
  22. }
  23. private static Binder BindVisibility(Element element, string property, Expression expression) {
  24. return new VisibilityBinder(element, property, expression);
  25. }
  26. private static Expression ProcessModelExpression(object model, string expressionType, string value) {
  27. Debug.Assert(Script.GetField(model, value) != null, "The model does not have a member named '" + value + '"');
  28. if (expressionType == "exec") {
  29. // TODO: Support scenarios where value is parent.method, or root.method (for invoking
  30. // a method on the parent model or root model... useful in templating scenarios
  31. // where items are not bound to view model but an object retrieved from the
  32. // view model.
  33. // When the expression is exec, the value is interpreted as a reference to a method
  34. // on the model. An invoker function is created that when called invokes the referenced
  35. // method in context of the model instance. The expression itself contains a delegate
  36. // to this dynamically created invoker function.
  37. // function _(e) {
  38. // var result = this.modelMethod(e, this);
  39. // if (result) { e.preventDefault(); }
  40. // }
  41. // TODO: If bound to parent/root, generated this instead:
  42. // function _(e) {
  43. // var model = Application.Current.GetModel(element.parentElement);
  44. // var result = this.modelMethod(e, model);
  45. // if (result) { e.preventDefault(); }
  46. // }
  47. string invokerCode = "var result = this." + value + "(e, this); if (result) { e.preventDefault(); }";
  48. return new Expression(Delegate.Create(new Function(invokerCode, "e", "model"), model), /* canChange */ false);
  49. }
  50. // TODO: Eventually stop with the () business if we can switch over to true properties
  51. // with getter/setter accessors in javascript.
  52. // Check if the value is something like A.B.C, where A, B and C are identifiers.
  53. bool propertyPathExpression = PropertyPathRegex.Test(value);
  54. // The value represents a string that contains a script expression for properties off
  55. // the model.
  56. string getterCode;
  57. if (propertyPathExpression) {
  58. // If its a simple property path, then we parenthesize since in script, the
  59. // properties are represented as functions.
  60. value = value.Replace(".", "().");
  61. getterCode = "return model." + value + "();";
  62. }
  63. else {
  64. // If its not a simple property path, we're going to assume that the developer
  65. // has referenced the property functions themselves, and we use as-is.
  66. getterCode = "with(model) { return " + value + "; }";
  67. }
  68. Func<object, object> getter = (Func<object, object>)(object)new Function(getterCode, "model");
  69. if (expressionType == "init") {
  70. // Read-only, one-time binder... so execute the getter, and create
  71. // an expression with the current value.
  72. return new Expression(getter(model), /* canChange */ false);
  73. }
  74. else if (expressionType == "link") {
  75. // Read-only, bound binder... create a BindExpression that tracks the
  76. // value by observing any observables representing the property.
  77. return new BindExpression(model, getter, null);
  78. }
  79. else {
  80. // Read-write, bound binder ... must be a simple property path.
  81. if (propertyPathExpression == false) {
  82. Debug.Fail("A bind expression's value must be a property path. The expression '" + value + "' is invalid.");
  83. }
  84. string setterCode = "model." + value + "(value);";
  85. Action<object, object> setter = (Action<object, object>)(object)new Function(setterCode, "model", "value");
  86. return new BindExpression(model, getter, setter);
  87. }
  88. }
  89. /// <summary>
  90. /// Registers a binder factory. The supplied name prefixed with "data-" is used
  91. /// as the attribute name in markup to create a binder.
  92. /// </summary>
  93. /// <param name="name">The name of the expression handler.</param>
  94. /// <param name="factory">The factory being registered.</param>
  95. public void RegisterBinder(string name, BinderFactory factory) {
  96. Debug.Assert(String.IsNullOrEmpty(name) == false);
  97. Debug.Assert(factory != null);
  98. Debug.Assert(_registeredBinders.ContainsKey(name) == false, "A binder with name '" + name + "' was already registered.");
  99. _registeredBinders[name] = factory;
  100. }
  101. /// <summary>
  102. /// Registers an expression factory. The supplied name is used in markup to represent
  103. /// an instance of the associated expression.
  104. /// </summary>
  105. /// <param name="name">The name of expression.</param>
  106. /// <param name="factory">The factory to be used to handle the supplied name.</param>
  107. public void RegisterExpression(string name, ExpressionFactory factory) {
  108. Debug.Assert(String.IsNullOrEmpty(name) == false);
  109. Debug.Assert(factory != null);
  110. Debug.Assert(_registeredExpressions.ContainsKey(name) == false, "An expression factory with name '" + name + "' was already registered.");
  111. _registeredExpressions[name] = factory;
  112. }
  113. private void SetupBindings(Element element, object model) {
  114. Debug.Assert(element != null);
  115. string bindings = (string)element.GetAttribute(Application.BindingsAttribute);
  116. bindings.ReplaceRegex(Application.BindingsRegex, delegate(string match /*, string binderType, string expressionType, string expressionValue */) {
  117. string binderType = (string)Arguments.GetArgument(1);
  118. string expressionType = (string)Arguments.GetArgument(2);
  119. string expressionValue = (string)Arguments.GetArgument(3);
  120. ExpressionFactory expressionFactory = _registeredExpressions[expressionType];
  121. Debug.Assert(expressionFactory != null, "Unknown expression of type '" + expressionType + "' found.");
  122. if (expressionFactory != null) {
  123. Expression expression = expressionFactory(model, expressionType, expressionValue);
  124. Binder binder = null;
  125. // TODO: Add support for binding attributes - @xxx
  126. if (binderType.StartsWith("on.")) {
  127. Debug.Assert(expression.CanChange == false, "Events cannot be bound to dynamic expressions.");
  128. Debug.Assert(expression.GetValue() is Action);
  129. binder = new EventBinder(element, binderType.Substr(3), (ElementEventListener)expression.GetValue());
  130. }
  131. else if (binderType.StartsWith("style.")) {
  132. object style = element.Style;
  133. binder = new PropertyBinder(style, binderType.Substr(6), expression);
  134. }
  135. else {
  136. BinderFactory binderFactory = _registeredBinders[binderType];
  137. if (binderFactory == null) {
  138. binder = new PropertyBinder(element, binderType, expression);
  139. }
  140. else {
  141. binder = binderFactory(element, binderType, expression);
  142. }
  143. }
  144. if (binder != null) {
  145. binder.Update();
  146. if (expression.CanChange == false) {
  147. // Since the expression value cannot change, there isn't a whole lot of need
  148. // to keep the binder alive and manage it.
  149. binder = null;
  150. }
  151. }
  152. if (binder != null) {
  153. // The binder is managed using a behavior that is attached to the element.
  154. // This allows stashing the model for later retrieval, as well as a way to
  155. // dispose bindings (the behavior disposes all binders it is managing).
  156. BinderManager binderManager = (BinderManager)Behavior.GetBehavior(element, typeof(BinderManager));
  157. if (binderManager == null) {
  158. binderManager = new BinderManager();
  159. binderManager.Initialize(element, null);
  160. binderManager.Model = model;
  161. }
  162. binderManager.AddBinder(binder);
  163. }
  164. }
  165. return String.Empty;
  166. });
  167. }
  168. }
  169. }