diff --git a/Ooui.Forms/Renderers/WebViewRenderer.cs b/Ooui.Forms/Renderers/WebViewRenderer.cs index 381e905..496f45a 100644 --- a/Ooui.Forms/Renderers/WebViewRenderer.cs +++ b/Ooui.Forms/Renderers/WebViewRenderer.cs @@ -24,7 +24,7 @@ namespace Ooui.Forms.Renderers if (_iframe != null) { - _iframe.Src = html; + _iframe.Source = html; } } catch (Exception ex) @@ -47,7 +47,7 @@ namespace Ooui.Forms.Renderers if (_iframe != null) { - _iframe.Src = url; + _iframe.Source = url; } } catch (Exception ex) diff --git a/Ooui/Anchor.cs b/Ooui/Anchor.cs index 864011b..6ea2853 100644 --- a/Ooui/Anchor.cs +++ b/Ooui/Anchor.cs @@ -4,10 +4,9 @@ namespace Ooui { public class Anchor : Element { - string href = ""; public string HRef { - get => href; - set => SetProperty (ref href, value ?? "", "href"); + get => GetStringAttribute ("href", ""); + set => SetAttributeProperty ("href", value); } public Anchor () diff --git a/Ooui/Button.cs b/Ooui/Button.cs index 386fcd9..4102dcd 100644 --- a/Ooui/Button.cs +++ b/Ooui/Button.cs @@ -9,8 +9,8 @@ namespace Ooui { ButtonType typ = ButtonType.Submit; public ButtonType Type { - get => typ; - set => SetProperty (ref typ, value, "type"); + get => GetAttribute ("type", ButtonType.Submit); + set => SetAttributeProperty ("type", value); } public Button () diff --git a/Ooui/Canvas.cs b/Ooui/Canvas.cs index b3dc144..2c009ac 100644 --- a/Ooui/Canvas.cs +++ b/Ooui/Canvas.cs @@ -7,16 +7,14 @@ namespace Ooui CanvasRenderingContext2D context2d = new CanvasRenderingContext2D (); int gotContext2d = 0; - int width = 300; public int Width { - get => width; - set => SetProperty (ref width, value <= 0 ? 150 : value, "width"); + get => GetAttribute ("width", 300); + set => SetAttributeProperty ("width", value < 0 ? 0 : value); } - int height = 150; public int Height { - get => height; - set => SetProperty (ref height, value <= 0 ? 150 : value, "height"); + get => GetAttribute ("height", 150); + set => SetAttributeProperty ("width", value < 0 ? 0 : value); } public Canvas () diff --git a/Ooui/Element.cs b/Ooui/Element.cs index bc08de7..05626be 100644 --- a/Ooui/Element.cs +++ b/Ooui/Element.cs @@ -1,28 +1,29 @@ using System; +using System.Collections.Generic; using System.ComponentModel; namespace Ooui { public abstract class Element : Node { - string className = ""; + readonly Dictionary attributes = new Dictionary (); + public string ClassName { - get => className; - set => SetProperty (ref className, value, "className"); + get => GetStringAttribute ("class", ""); + set => SetAttributeProperty ("class", value); } public Style Style { get; private set; } = new Style (); - string title = ""; public string Title { - get => title; - set => SetProperty (ref title, value, "title"); + get => GetStringAttribute ("title", ""); + set => SetAttributeProperty ("title", value); } bool hidden = false; public bool IsHidden { - get => hidden; - set => SetProperty (ref hidden, value, "hidden"); + get => GetBooleanAttribute ("hidden"); + set => SetBooleanAttributeProperty ("hidden", value); } public event TargetEventHandler Click { @@ -102,8 +103,64 @@ namespace Ooui Style.PropertyChanged += HandleStylePropertyChanged; } - public void SetAttribute (string attributeName, string value) + protected bool SetAttributeProperty (string attributeName, object newValue, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") { + var old = GetAttribute (attributeName); + if (old != null && old.Equals (newValue)) + return false; + SetAttribute (attributeName, newValue); + OnPropertyChanged (propertyName); + return true; + } + + protected bool SetBooleanAttributeProperty (string attributeName, bool newValue, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") + { + var old = GetAttribute (attributeName) != null; + if (old != newValue) + return false; + if (newValue) + SetAttribute (attributeName, string.Empty); + else + RemoveAttribute (attributeName); + OnPropertyChanged (propertyName); + return true; + } + + protected bool UpdateAttributeProperty (string attributeName, object newValue, string propertyName) + { + lock (attributes) { + if (attributes.TryGetValue (attributeName, out var oldValue)) { + if (newValue != null && newValue.Equals (oldValue)) + return false; + } + attributes[attributeName] = newValue; + } + OnPropertyChanged (propertyName); + return true; + } + + protected bool UpdateBooleanAttributeProperty (string attributeName, bool newValue, string propertyName) + { + lock (attributes) { + var oldValue = attributes.ContainsKey (attributeName); + if (newValue == oldValue) + return false; + if (newValue) { + attributes[attributeName] = ""; + } + else { + attributes.Remove (attributeName); + } + } + OnPropertyChanged (propertyName); + return true; + } + + public void SetAttribute (string attributeName, object value) + { + lock (attributes) { + attributes[attributeName] = value; + } Send (new Message { MessageType = MessageType.SetAttribute, TargetId = Id, @@ -112,6 +169,62 @@ namespace Ooui }); } + public object GetAttribute (string attributeName) + { + lock (attributes) { + attributes.TryGetValue (attributeName, out var v); + return v; + } + } + + public T GetAttribute (string attributeName, T defaultValue) + { + lock (attributes) { + attributes.TryGetValue (attributeName, out var v); + if (v is T) { + return (T)v; + } + else { + return defaultValue; + } + } + } + + public bool GetBooleanAttribute (string attributeName) + { + lock (attributes) { + return attributes.TryGetValue (attributeName, out var _); + } + } + + public string GetStringAttribute (string attributeName, string defaultValue) + { + lock (attributes) { + if (attributes.TryGetValue (attributeName, out var v)) { + if (v == null) return "null"; + else return v.ToString (); + } + else { + return defaultValue; + } + } + } + + public void RemoveAttribute (string attributeName) + { + bool removed; + lock (attributes) { + removed = attributes.Remove (attributeName); + } + if (removed) { + Send (new Message { + MessageType = MessageType.RemoveAttribute, + TargetId = Id, + Key = attributeName, + }); + } + } + void HandleStylePropertyChanged (object sender, PropertyChangedEventArgs e) { SendSet ("style." + Style.GetJsName (e.PropertyName), Style[e.PropertyName]); diff --git a/Ooui/EventTarget.cs b/Ooui/EventTarget.cs index e3e26c1..4e8c273 100644 --- a/Ooui/EventTarget.cs +++ b/Ooui/EventTarget.cs @@ -83,12 +83,12 @@ namespace Ooui } } - protected bool SetProperty (ref T backingStore, T newValue, string attributeName, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") + protected bool SetProperty (ref T backingStore, T newValue, string jsPropertyName, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") { if (EqualityComparer.Default.Equals (backingStore, newValue)) return false; backingStore = newValue; - SendSet (attributeName, newValue); + SendSet (jsPropertyName, newValue); OnPropertyChanged (propertyName); return true; } @@ -120,12 +120,12 @@ namespace Ooui Send (Message.Call (Id, methodName, args)); } - protected void SendSet (string attributeName, object value) + protected void SendSet (string jsPropertyName, object value) { Send (new Message { MessageType = MessageType.Set, TargetId = Id, - Key = attributeName, + Key = jsPropertyName, Value = value, }); } @@ -169,6 +169,11 @@ namespace Ooui state.Add (message); }); break; + case MessageType.RemoveAttribute: + this.UpdateStateMessages (state => { + state.RemoveAll (x => x.MessageType == MessageType.SetAttribute && x.Key == message.Key); + }); + return true; case MessageType.Listen: AddStateMessage (message); break; diff --git a/Ooui/Form.cs b/Ooui/Form.cs index 809397a..897a319 100644 --- a/Ooui/Form.cs +++ b/Ooui/Form.cs @@ -6,20 +6,18 @@ namespace Ooui { string action = ""; public string Action { - get => action; - set => SetProperty (ref action, value ?? "", "action"); + get => GetStringAttribute ("action", ""); + set => SetAttributeProperty ("action", value ?? ""); } - string method = "GET"; public string Method { - get => method; - set => SetProperty (ref method, value ?? "", "method"); + get => GetStringAttribute ("method", "GET"); + set => SetAttributeProperty ("method", value ?? ""); } - string enctype = "application/x-www-form-urlencoded"; public string EncodingType { - get => enctype; - set => SetProperty (ref enctype, value ?? "", "enctype"); + get => GetStringAttribute ("enctype", "application/x-www-form-urlencoded"); + set => SetAttributeProperty ("enctype", value ?? ""); } public event TargetEventHandler Submit { diff --git a/Ooui/FormControl.cs b/Ooui/FormControl.cs index e98607e..461b301 100644 --- a/Ooui/FormControl.cs +++ b/Ooui/FormControl.cs @@ -4,16 +4,15 @@ namespace Ooui { public abstract class FormControl : Element { - string name = ""; public string Name { - get => name; - set => SetProperty (ref name, value, "name"); + get => GetStringAttribute ("name", ""); + set => SetAttributeProperty ("name", value); } bool isDisabled = false; public bool IsDisabled { - get => isDisabled; - set => SetProperty (ref isDisabled, value, "disabled"); + get => GetBooleanAttribute ("disabled"); + set => SetBooleanAttributeProperty ("disabled", value); } public FormControl (string tagName) diff --git a/Ooui/Iframe.cs b/Ooui/Iframe.cs index 587109e..26a917c 100644 --- a/Ooui/Iframe.cs +++ b/Ooui/Iframe.cs @@ -2,17 +2,15 @@ { public class Iframe : Element { - public Iframe() - : base("iframe") + public string Source { - + get => GetStringAttribute ("src", null); + set => SetAttributeProperty ("src", value); } - string src = null; - public string Src + public Iframe () + : base ("iframe") { - get => src; - set => SetProperty(ref src, value, "src"); } } } diff --git a/Ooui/Image.cs b/Ooui/Image.cs index 6bba7ff..24bff04 100644 --- a/Ooui/Image.cs +++ b/Ooui/Image.cs @@ -4,10 +4,10 @@ namespace Ooui { public class Image : Element { - string src = ""; - public string Source { - get => src; - set => SetProperty (ref src, value ?? "", "src"); + public string Source + { + get => GetStringAttribute ("src", null); + set => SetAttributeProperty ("src", value); } public Image () diff --git a/Ooui/Input.cs b/Ooui/Input.cs index 90f73d8..9574d11 100644 --- a/Ooui/Input.cs +++ b/Ooui/Input.cs @@ -7,16 +7,14 @@ namespace Ooui { public class Input : FormControl { - InputType typ = InputType.Text; public InputType Type { - get => typ; - set => SetProperty (ref typ, value, "type"); + get => GetAttribute ("type", InputType.Text); + set => SetAttributeProperty ("type", value); } - string val = ""; public string Value { - get => val; - set => SetProperty (ref val, value ?? "", "value"); + get => GetStringAttribute ("value", ""); + set => SetAttributeProperty ("value", value ?? ""); } public double NumberValue { @@ -35,37 +33,33 @@ namespace Ooui remove => RemoveEventListener ("change", value); } - string placeholder = ""; public string Placeholder { - get => placeholder; - set => SetProperty (ref placeholder, value, "placeholder"); + get => GetStringAttribute ("placeholder", ""); + set => SetAttributeProperty ("placeholder", value ?? ""); } - bool isChecked = false; public bool IsChecked { - get => isChecked; + get => GetBooleanAttribute ("checked"); set { - SetProperty (ref isChecked, value, "checked"); - TriggerEventFromMessage (Message.Event (Id, "change", isChecked)); + if (SetBooleanAttributeProperty ("checked", value)) { + TriggerEventFromMessage (Message.Event (Id, "change", IsChecked)); + } } } - double minimum = 0; public double Minimum { - get => minimum; - set => SetProperty (ref minimum, value, "min"); + get => GetAttribute ("min", 0.0); + set => SetAttributeProperty ("min", value); } - double maximum = 100; public double Maximum { - get => maximum; - set => SetProperty (ref maximum, value, "max"); + get => GetAttribute ("max", 100.0); + set => SetAttributeProperty ("max", value); } - double step = 1; public double Step { - get => step; - set => SetProperty (ref step, value, "step"); + get => GetAttribute ("step", 1.0); + set => SetAttributeProperty ("step", value); } public Input () @@ -86,10 +80,10 @@ namespace Ooui if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) { // Don't need to notify here because the base implementation will fire the event if (Type == InputType.Checkbox) { - isChecked = message.Value != null ? Convert.ToBoolean (message.Value) : false; + UpdateBooleanAttributeProperty ("checked", message.Value != null ? Convert.ToBoolean (message.Value) : false, "IsChecked"); } else { - val = message.Value != null ? Convert.ToString (message.Value) : ""; + UpdateAttributeProperty ("value", message.Value != null ? Convert.ToString (message.Value) : "", "Value"); } } return base.TriggerEventFromMessage (message); diff --git a/Ooui/Label.cs b/Ooui/Label.cs index 9dcfd7f..ca27a15 100644 --- a/Ooui/Label.cs +++ b/Ooui/Label.cs @@ -4,10 +4,9 @@ namespace Ooui { public class Label : Element { - Element htmlFor = null; public Element For { - get => htmlFor; - set => SetProperty (ref htmlFor, value, "htmlFor"); + get => GetAttribute ("for", null); + set => SetAttributeProperty ("for", value); } public Label () diff --git a/Ooui/Message.cs b/Ooui/Message.cs index 0498437..1e78fc9 100644 --- a/Ooui/Message.cs +++ b/Ooui/Message.cs @@ -48,6 +48,8 @@ namespace Ooui Set, [EnumMember (Value = "setAttr")] SetAttribute, + [EnumMember(Value = "remAttr")] + RemoveAttribute, [EnumMember(Value = "call")] Call, [EnumMember(Value = "listen")] diff --git a/Ooui/Option.cs b/Ooui/Option.cs index 86d918e..22984eb 100644 --- a/Ooui/Option.cs +++ b/Ooui/Option.cs @@ -4,22 +4,19 @@ namespace Ooui { public class Option : Element { - string val = ""; public string Value { - get => val; - set => SetProperty (ref val, value ?? "", "value"); + get => GetStringAttribute ("value", ""); + set => SetAttributeProperty ("value", value ?? ""); } - string label = ""; public string Label { - get => label; - set => SetProperty (ref label, value ?? "", "label"); + get => GetStringAttribute ("label", ""); + set => SetAttributeProperty ("label", value ?? ""); } - bool defaultSelected = false; public bool DefaultSelected { - get => defaultSelected; - set => SetProperty (ref defaultSelected, value, "defaultSelected"); + get => GetBooleanAttribute ("selected"); + set => SetBooleanAttributeProperty ("selected", value); } public Option () diff --git a/Ooui/Select.cs b/Ooui/Select.cs index c88eed4..885de9b 100644 --- a/Ooui/Select.cs +++ b/Ooui/Select.cs @@ -4,10 +4,9 @@ namespace Ooui { public class Select : FormControl { - string val = ""; public string Value { - get => val; - set => SetProperty (ref val, value ?? "", "value"); + get => GetStringAttribute ("value", ""); + set => SetAttributeProperty ("value", value ?? ""); } public event TargetEventHandler Change { @@ -35,6 +34,7 @@ namespace Ooui protected override void OnChildInsertedBefore (Node newChild, Node referenceChild) { base.OnChildInsertedBefore (newChild, referenceChild); + var val = Value; if (string.IsNullOrEmpty (val) && newChild is Option o && !string.IsNullOrEmpty (o.Value)) { val = o.Value; } @@ -43,7 +43,8 @@ namespace Ooui protected override bool TriggerEventFromMessage (Message message) { if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) { - val = message.Value != null ? Convert.ToString (message.Value) : ""; + SetAttribute ("value", message.Value != null ? Convert.ToString (message.Value) : ""); + OnPropertyChanged ("Value"); } return base.TriggerEventFromMessage (message); } diff --git a/Ooui/TextArea.cs b/Ooui/TextArea.cs index a35b962..b7cc341 100644 --- a/Ooui/TextArea.cs +++ b/Ooui/TextArea.cs @@ -20,16 +20,15 @@ namespace Ooui set => SetProperty (ref val, value ?? "", "value"); } - int rows = 2; public int Rows { - get => rows; - set => SetProperty (ref rows, value, "rows"); + get => GetAttribute ("rows", 2); + set => SetAttributeProperty ("rows", value); } int cols = 20; public int Columns { - get => cols; - set => SetProperty (ref cols, value, "cols"); + get => GetAttribute ("cols", 20); + set => SetAttributeProperty ("cols", value); } public TextArea ()