Merge pull request #73 from praeclarum/prerender
Prerender HTML to help SEO and load time
This commit is contained in:
		
						commit
						01c452acfd
					
				| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Text;
 | 
					using System.Text;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Ooui.AspNetCore
 | 
					namespace Ooui.AspNetCore
 | 
				
			||||||
| 
						 | 
					@ -21,13 +22,35 @@ namespace Ooui.AspNetCore
 | 
				
			||||||
            var response = context.HttpContext.Response;
 | 
					            var response = context.HttpContext.Response;
 | 
				
			||||||
            response.StatusCode = 200;
 | 
					            response.StatusCode = 200;
 | 
				
			||||||
            response.ContentType = "text/html; charset=utf-8";
 | 
					            response.ContentType = "text/html; charset=utf-8";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (element.WantsFullScreen) {
 | 
				
			||||||
 | 
					                element.Style.Width = GetCookieDouble (context.HttpContext.Request.Cookies, "oouiWindowWidth", 32, 640, 10000);
 | 
				
			||||||
 | 
					                element.Style.Height = GetCookieDouble (context.HttpContext.Request.Cookies, "oouiWindowHeight", 24, 480, 10000);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var sessionId = WebSocketHandler.BeginSession (context.HttpContext, element);
 | 
					            var sessionId = WebSocketHandler.BeginSession (context.HttpContext, element);
 | 
				
			||||||
            var html = UI.RenderTemplate (WebSocketHandler.WebSocketPath + "?id=" + sessionId, title: title);
 | 
					            var initialHtml = element.OuterHtml;
 | 
				
			||||||
 | 
					            var html = UI.RenderTemplate (WebSocketHandler.WebSocketPath + "?id=" + sessionId, title: title, initialHtml: initialHtml);
 | 
				
			||||||
            var htmlBytes = Encoding.UTF8.GetBytes (html);
 | 
					            var htmlBytes = Encoding.UTF8.GetBytes (html);
 | 
				
			||||||
            response.ContentLength = htmlBytes.Length;
 | 
					            response.ContentLength = htmlBytes.Length;
 | 
				
			||||||
            using (var s = response.Body) {
 | 
					            using (var s = response.Body) {
 | 
				
			||||||
                await s.WriteAsync (htmlBytes, 0, htmlBytes.Length).ConfigureAwait (false);
 | 
					                await s.WriteAsync (htmlBytes, 0, htmlBytes.Length).ConfigureAwait (false);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static double GetCookieDouble (IRequestCookieCollection cookies, string key, double min, double def, double max)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (cookies.TryGetValue (key, out var s)) {
 | 
				
			||||||
 | 
					                if (double.TryParse (s, out var d)) {
 | 
				
			||||||
 | 
					                    if (d < min) return min;
 | 
				
			||||||
 | 
					                    if (d > max) return max;
 | 
				
			||||||
 | 
					                    return d;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return def;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                return def;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,8 @@ namespace Ooui.Forms.Renderers
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        protected virtual bool ManageNativeControlLifetime => true;
 | 
					        protected virtual bool ManageNativeControlLifetime => true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							protected override bool HtmlNeedsFullEndElement => TagName == "div";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public ViewRenderer (string tagName = "div")
 | 
					        public ViewRenderer (string tagName = "div")
 | 
				
			||||||
            : base (tagName)
 | 
					            : base (tagName)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@ namespace Ooui.Forms.Renderers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (_iframe != null)
 | 
					                if (_iframe != null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _iframe.Src = html;
 | 
					                    _iframe.Source = html;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (Exception ex)
 | 
					            catch (Exception ex)
 | 
				
			||||||
| 
						 | 
					@ -47,7 +47,7 @@ namespace Ooui.Forms.Renderers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (_iframe != null)
 | 
					                if (_iframe != null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _iframe.Src = url;
 | 
					                    _iframe.Source = url;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (Exception ex)
 | 
					            catch (Exception ex)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -62,6 +62,8 @@ namespace Ooui.Forms
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected override bool HtmlNeedsFullEndElement => TagName == "div";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public VisualElementRenderer (string tagName = "div") : base (tagName)
 | 
					        public VisualElementRenderer (string tagName = "div") : base (tagName)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _propertyChangedHandler = OnElementPropertyChanged;
 | 
					            _propertyChangedHandler = OnElementPropertyChanged;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,10 +4,9 @@ namespace Ooui
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class Anchor : Element
 | 
					    public class Anchor : Element
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        string href = "";
 | 
					 | 
				
			||||||
        public string HRef {
 | 
					        public string HRef {
 | 
				
			||||||
            get => href;
 | 
					            get => GetStringAttribute ("href", "");
 | 
				
			||||||
            set => SetProperty (ref href, value ?? "", "href");
 | 
					            set => SetAttributeProperty ("href", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Anchor ()
 | 
					        public Anchor ()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,8 +9,8 @@ namespace Ooui
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        ButtonType typ = ButtonType.Submit;
 | 
					        ButtonType typ = ButtonType.Submit;
 | 
				
			||||||
        public ButtonType Type {
 | 
					        public ButtonType Type {
 | 
				
			||||||
            get => typ;
 | 
					            get => GetAttribute ("type", ButtonType.Submit);
 | 
				
			||||||
            set => SetProperty (ref typ, value, "type");
 | 
					            set => SetAttributeProperty ("type", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Button ()
 | 
					        public Button ()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,16 +7,14 @@ namespace Ooui
 | 
				
			||||||
        CanvasRenderingContext2D context2d = new CanvasRenderingContext2D ();
 | 
					        CanvasRenderingContext2D context2d = new CanvasRenderingContext2D ();
 | 
				
			||||||
        int gotContext2d = 0;
 | 
					        int gotContext2d = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int width = 300;
 | 
					 | 
				
			||||||
        public int Width {
 | 
					        public int Width {
 | 
				
			||||||
            get => width;
 | 
					            get => GetAttribute ("width", 300);
 | 
				
			||||||
            set => SetProperty (ref width, value <= 0 ? 150 : value, "width");
 | 
					            set => SetAttributeProperty ("width", value < 0 ? 0 : value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int height = 150;
 | 
					 | 
				
			||||||
        public int Height {
 | 
					        public int Height {
 | 
				
			||||||
            get => height;
 | 
					            get => GetAttribute ("height", 150);
 | 
				
			||||||
            set => SetProperty (ref height, value <= 0 ? 150 : value, "height");
 | 
					            set => SetAttributeProperty ("height", value < 0 ? 0 : value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Canvas ()
 | 
					        public Canvas ()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
var debug = false;
 | 
					var debug = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const nodes = {};
 | 
					const nodes = {};
 | 
				
			||||||
 | 
					const hasText = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let socket = null;
 | 
					let socket = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,10 +36,27 @@ function getSize () {
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function setCookie (name, value, days) {
 | 
				
			||||||
 | 
					    var expires = "";
 | 
				
			||||||
 | 
					    if (days) {
 | 
				
			||||||
 | 
					        var date = new Date ();
 | 
				
			||||||
 | 
					        date.setTime(date.getTime () + (days*24*60*60*1000));
 | 
				
			||||||
 | 
					        expires = "; expires=" + date.toUTCString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    document.cookie = name + "=" + (value || "")  + expires + "; path=/";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function saveSize (s) {
 | 
				
			||||||
 | 
					    setCookie ("oouiWindowWidth", s.width, 7);
 | 
				
			||||||
 | 
					    setCookie ("oouiWindowHeight", s.height, 7);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Main entrypoint
 | 
					// Main entrypoint
 | 
				
			||||||
function ooui (rootElementPath) {
 | 
					function ooui (rootElementPath) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var initialSize = getSize ();
 | 
					    var initialSize = getSize ();
 | 
				
			||||||
 | 
					    saveSize (initialSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var wsArgs = (rootElementPath.indexOf("?") >= 0 ? "&" : "?") +
 | 
					    var wsArgs = (rootElementPath.indexOf("?") >= 0 ? "&" : "?") +
 | 
				
			||||||
        "w=" + initialSize.width + "&h=" + initialSize.height;
 | 
					        "w=" + initialSize.width + "&h=" + initialSize.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -123,12 +141,22 @@ function getNode (id) {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getOrCreateElement (id, tagName) {
 | 
				
			||||||
 | 
					    var e = document.getElementById (id);
 | 
				
			||||||
 | 
					    if (e) {
 | 
				
			||||||
 | 
					        if (e.firstChild && e.firstChild.nodeType == Node.TEXT_NODE)
 | 
				
			||||||
 | 
					            hasText[e.id] = true;
 | 
				
			||||||
 | 
					        return e;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return document.createElement (tagName);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function msgCreate (m) {
 | 
					function msgCreate (m) {
 | 
				
			||||||
    const id = m.id;
 | 
					    const id = m.id;
 | 
				
			||||||
    const tagName = m.k;
 | 
					    const tagName = m.k;
 | 
				
			||||||
    const node = tagName === "#text" ?
 | 
					    const node = tagName === "#text" ?
 | 
				
			||||||
        document.createTextNode ("") :
 | 
					        document.createTextNode ("") :
 | 
				
			||||||
        document.createElement (tagName);
 | 
					        getOrCreateElement (id, tagName);
 | 
				
			||||||
    if (tagName !== "#text")
 | 
					    if (tagName !== "#text")
 | 
				
			||||||
        node.id = id;
 | 
					        node.id = id;
 | 
				
			||||||
    nodes[id] = node;
 | 
					    nodes[id] = node;
 | 
				
			||||||
| 
						 | 
					@ -164,6 +192,17 @@ function msgSetAttr (m) {
 | 
				
			||||||
    if (debug) console.log ("SetAttr", node, m.k, m.v);
 | 
					    if (debug) console.log ("SetAttr", node, m.k, m.v);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function msgRemAttr (m) {
 | 
				
			||||||
 | 
					    const id = m.id;
 | 
				
			||||||
 | 
					    const node = getNode (id);
 | 
				
			||||||
 | 
					    if (!node) {
 | 
				
			||||||
 | 
					        console.error ("Unknown node id", m);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    node.removeAttribute(m.k);
 | 
				
			||||||
 | 
					    if (debug) console.log ("RemAttr", node, m.k);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function msgCall (m) {
 | 
					function msgCall (m) {
 | 
				
			||||||
    const id = m.id;
 | 
					    const id = m.id;
 | 
				
			||||||
    const node = getNode (id);
 | 
					    const node = getNode (id);
 | 
				
			||||||
| 
						 | 
					@ -173,6 +212,12 @@ function msgCall (m) {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const isJQuery = m.k.startsWith ("$.");
 | 
					    const isJQuery = m.k.startsWith ("$.");
 | 
				
			||||||
    const target = isJQuery ? $(node) : node;
 | 
					    const target = isJQuery ? $(node) : node;
 | 
				
			||||||
 | 
					    if (m.k === "insertBefore" && m.v[0].nodeType == Node.TEXT_NODE && m.v[1] == null && hasText[id]) {
 | 
				
			||||||
 | 
					        // Text is already set so it clear it first
 | 
				
			||||||
 | 
					        if (target.firstChild)
 | 
				
			||||||
 | 
					            target.removeChild (target.firstChild);
 | 
				
			||||||
 | 
					        delete hasText[id];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    const f = isJQuery ? target[m.k.slice(2)] : target[m.k];
 | 
					    const f = isJQuery ? target[m.k.slice(2)] : target[m.k];
 | 
				
			||||||
    if (debug) console.log ("Call", node, f, m.v);
 | 
					    if (debug) console.log ("Call", node, f, m.v);
 | 
				
			||||||
    const r = f.apply (target, m.v);
 | 
					    const r = f.apply (target, m.v);
 | 
				
			||||||
| 
						 | 
					@ -227,6 +272,9 @@ function processMessage (m) {
 | 
				
			||||||
        case "setAttr":
 | 
					        case "setAttr":
 | 
				
			||||||
            msgSetAttr (m);
 | 
					            msgSetAttr (m);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
					        case "remAttr":
 | 
				
			||||||
 | 
					            msgRemAttr (m);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
        case "call":
 | 
					        case "call":
 | 
				
			||||||
            msgCall (m);
 | 
					            msgCall (m);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,8 @@ namespace Ooui
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class Div : Element
 | 
					    public class Div : Element
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        protected override bool HtmlNeedsFullEndElement => true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Div ()
 | 
					        public Div ()
 | 
				
			||||||
            : base ("div")
 | 
					            : base ("div")
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										164
									
								
								Ooui/Element.cs
								
								
								
								
							
							
						
						
									
										164
									
								
								Ooui/Element.cs
								
								
								
								
							| 
						 | 
					@ -1,28 +1,29 @@
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.ComponentModel;
 | 
					using System.ComponentModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Ooui
 | 
					namespace Ooui
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public abstract class Element : Node
 | 
					    public abstract class Element : Node
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        string className = "";
 | 
					        readonly Dictionary<string, object> attributes = new Dictionary<string, object> ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string ClassName {
 | 
					        public string ClassName {
 | 
				
			||||||
            get => className;
 | 
					            get => GetStringAttribute ("class", "");
 | 
				
			||||||
            set => SetProperty (ref className, value, "className");
 | 
					            set => SetAttributeProperty ("class", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Style Style { get; private set; } = new Style ();
 | 
					        public Style Style { get; private set; } = new Style ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        string title = "";
 | 
					 | 
				
			||||||
        public string Title {
 | 
					        public string Title {
 | 
				
			||||||
            get => title;
 | 
					            get => GetStringAttribute ("title", "");
 | 
				
			||||||
            set => SetProperty (ref title, value, "title");
 | 
					            set => SetAttributeProperty ("title", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bool hidden = false;
 | 
					        bool hidden = false;
 | 
				
			||||||
        public bool IsHidden {
 | 
					        public bool IsHidden {
 | 
				
			||||||
            get => hidden;
 | 
					            get => GetBooleanAttribute ("hidden");
 | 
				
			||||||
            set => SetProperty (ref hidden, value, "hidden");
 | 
					            set => SetBooleanAttributeProperty ("hidden", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public event TargetEventHandler Click {
 | 
					        public event TargetEventHandler Click {
 | 
				
			||||||
| 
						 | 
					@ -102,8 +103,64 @@ namespace Ooui
 | 
				
			||||||
            Style.PropertyChanged += HandleStylePropertyChanged;
 | 
					            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 {
 | 
					            Send (new Message {
 | 
				
			||||||
                MessageType = MessageType.SetAttribute,
 | 
					                MessageType = MessageType.SetAttribute,
 | 
				
			||||||
                TargetId = Id,
 | 
					                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<T> (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)
 | 
					        void HandleStylePropertyChanged (object sender, PropertyChangedEventArgs e)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            SendSet ("style." + Style.GetJsName (e.PropertyName), Style[e.PropertyName]);
 | 
					            SendSet ("style." + Style.GetJsName (e.PropertyName), Style[e.PropertyName]);
 | 
				
			||||||
| 
						 | 
					@ -130,5 +243,38 @@ namespace Ooui
 | 
				
			||||||
                    return base.SaveStateMessageIfNeeded (message);
 | 
					                    return base.SaveStateMessageIfNeeded (message);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected virtual bool HtmlNeedsFullEndElement => false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public override void WriteOuterHtml (System.Xml.XmlWriter w)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            w.WriteStartElement (TagName);
 | 
				
			||||||
 | 
					            w.WriteAttributeString ("id", Id);
 | 
				
			||||||
 | 
					            var style = Style.ToString ();
 | 
				
			||||||
 | 
					            if (style.Length > 0) {
 | 
				
			||||||
 | 
					                w.WriteAttributeString ("style", style);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            lock (attributes) {
 | 
				
			||||||
 | 
					                foreach (var a in attributes) {
 | 
				
			||||||
 | 
					                    var value = (a.Value == null) ? "null" : Convert.ToString (a.Value, System.Globalization.CultureInfo.InvariantCulture);
 | 
				
			||||||
 | 
					                    w.WriteAttributeString (a.Key, value);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            WriteInnerHtml (w);
 | 
				
			||||||
 | 
					            if (HtmlNeedsFullEndElement) {
 | 
				
			||||||
 | 
					                w.WriteFullEndElement ();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                w.WriteEndElement ();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public virtual void WriteInnerHtml (System.Xml.XmlWriter w)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var children = Children;
 | 
				
			||||||
 | 
					            foreach (var c in children) {
 | 
				
			||||||
 | 
					                c.WriteOuterHtml (w);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -83,12 +83,12 @@ namespace Ooui
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected bool SetProperty<T> (ref T backingStore, T newValue, string attributeName, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
 | 
					        protected bool SetProperty<T> (ref T backingStore, T newValue, string jsPropertyName, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (EqualityComparer<T>.Default.Equals (backingStore, newValue))
 | 
					            if (EqualityComparer<T>.Default.Equals (backingStore, newValue))
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            backingStore = newValue;
 | 
					            backingStore = newValue;
 | 
				
			||||||
            SendSet (attributeName, newValue);
 | 
					            SendSet (jsPropertyName, newValue);
 | 
				
			||||||
            OnPropertyChanged (propertyName);
 | 
					            OnPropertyChanged (propertyName);
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -120,12 +120,12 @@ namespace Ooui
 | 
				
			||||||
            Send (Message.Call (Id, methodName, args));
 | 
					            Send (Message.Call (Id, methodName, args));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected void SendSet (string attributeName, object value)
 | 
					        protected void SendSet (string jsPropertyName, object value)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Send (new Message {
 | 
					            Send (new Message {
 | 
				
			||||||
                MessageType = MessageType.Set,
 | 
					                MessageType = MessageType.Set,
 | 
				
			||||||
                TargetId = Id,
 | 
					                TargetId = Id,
 | 
				
			||||||
                Key = attributeName,
 | 
					                Key = jsPropertyName,
 | 
				
			||||||
                Value = value,
 | 
					                Value = value,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -169,6 +169,11 @@ namespace Ooui
 | 
				
			||||||
                        state.Add (message);
 | 
					                        state.Add (message);
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
 | 
					                case MessageType.RemoveAttribute:
 | 
				
			||||||
 | 
					                    this.UpdateStateMessages (state => {
 | 
				
			||||||
 | 
					                        state.RemoveAll (x => x.MessageType == MessageType.SetAttribute && x.Key == message.Key);
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    return true;
 | 
				
			||||||
                case MessageType.Listen:
 | 
					                case MessageType.Listen:
 | 
				
			||||||
                    AddStateMessage (message);
 | 
					                    AddStateMessage (message);
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								Ooui/Form.cs
								
								
								
								
							
							
						
						
									
										14
									
								
								Ooui/Form.cs
								
								
								
								
							| 
						 | 
					@ -6,20 +6,18 @@ namespace Ooui
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        string action = "";
 | 
					        string action = "";
 | 
				
			||||||
        public string Action {
 | 
					        public string Action {
 | 
				
			||||||
            get => action;
 | 
					            get => GetStringAttribute ("action", "");
 | 
				
			||||||
            set => SetProperty (ref action, value ?? "", "action");
 | 
					            set => SetAttributeProperty ("action", value ?? "");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		string method = "GET";
 | 
					 | 
				
			||||||
		public string Method {
 | 
							public string Method {
 | 
				
			||||||
			get => method;
 | 
					            get => GetStringAttribute ("method", "GET");
 | 
				
			||||||
			set => SetProperty (ref method, value ?? "", "method");
 | 
					            set => SetAttributeProperty ("method", value ?? "");
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		string enctype = "application/x-www-form-urlencoded";
 | 
					 | 
				
			||||||
		public string EncodingType {
 | 
							public string EncodingType {
 | 
				
			||||||
			get => enctype;
 | 
					            get => GetStringAttribute ("enctype", "application/x-www-form-urlencoded");
 | 
				
			||||||
			set => SetProperty (ref enctype, value ?? "", "enctype");
 | 
					            set => SetAttributeProperty ("enctype", value ?? "");
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public event TargetEventHandler Submit {
 | 
							public event TargetEventHandler Submit {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,16 +4,15 @@ namespace Ooui
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public abstract class FormControl : Element
 | 
					    public abstract class FormControl : Element
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        string name = "";
 | 
					 | 
				
			||||||
        public string Name {
 | 
					        public string Name {
 | 
				
			||||||
            get => name;
 | 
					            get => GetStringAttribute ("name", "");
 | 
				
			||||||
            set => SetProperty (ref name, value, "name");
 | 
					            set => SetAttributeProperty ("name", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bool isDisabled = false;
 | 
					        bool isDisabled = false;
 | 
				
			||||||
        public bool IsDisabled {
 | 
					        public bool IsDisabled {
 | 
				
			||||||
            get => isDisabled;
 | 
					            get => GetBooleanAttribute ("disabled");
 | 
				
			||||||
            set => SetProperty (ref isDisabled, value, "disabled");
 | 
					            set => SetBooleanAttributeProperty ("disabled", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public FormControl (string tagName)
 | 
					        public FormControl (string tagName)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,17 +2,15 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class Iframe : Element
 | 
					    public class Iframe : Element
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        public string Source
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            get => GetStringAttribute ("src", null);
 | 
				
			||||||
 | 
					            set => SetAttributeProperty ("src", value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Iframe ()
 | 
					        public Iframe ()
 | 
				
			||||||
            : base ("iframe")
 | 
					            : base ("iframe")
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        string src = null;
 | 
					 | 
				
			||||||
        public string Src
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            get => src;
 | 
					 | 
				
			||||||
            set => SetProperty(ref src, value, "src");
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,10 +4,10 @@ namespace Ooui
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class Image : Element
 | 
					    public class Image : Element
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        string src = "";
 | 
					        public string Source
 | 
				
			||||||
        public string Source {
 | 
					        {
 | 
				
			||||||
            get => src;
 | 
					            get => GetStringAttribute ("src", null);
 | 
				
			||||||
            set => SetProperty (ref src, value ?? "", "src");
 | 
					            set => SetAttributeProperty ("src", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Image ()
 | 
					        public Image ()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,16 +7,14 @@ namespace Ooui
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class Input : FormControl
 | 
					    public class Input : FormControl
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        InputType typ = InputType.Text;
 | 
					 | 
				
			||||||
        public InputType Type {
 | 
					        public InputType Type {
 | 
				
			||||||
            get => typ;
 | 
					            get => GetAttribute ("type", InputType.Text);
 | 
				
			||||||
            set => SetProperty (ref typ, value, "type");
 | 
					            set => SetAttributeProperty ("type", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        string val = "";
 | 
					 | 
				
			||||||
        public string Value {
 | 
					        public string Value {
 | 
				
			||||||
            get => val;
 | 
					            get => GetStringAttribute ("value", "");
 | 
				
			||||||
            set => SetProperty (ref val, value ?? "", "value");
 | 
					            set => SetAttributeProperty ("value", value ?? "");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public double NumberValue {
 | 
					        public double NumberValue {
 | 
				
			||||||
| 
						 | 
					@ -35,37 +33,33 @@ namespace Ooui
 | 
				
			||||||
            remove => RemoveEventListener ("change", value);
 | 
					            remove => RemoveEventListener ("change", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        string placeholder = "";
 | 
					 | 
				
			||||||
        public string Placeholder {
 | 
					        public string Placeholder {
 | 
				
			||||||
            get => placeholder;
 | 
					            get => GetStringAttribute ("placeholder", "");
 | 
				
			||||||
            set => SetProperty (ref placeholder, value, "placeholder");
 | 
					            set => SetAttributeProperty ("placeholder", value ?? "");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bool isChecked = false;
 | 
					 | 
				
			||||||
        public bool IsChecked {
 | 
					        public bool IsChecked {
 | 
				
			||||||
            get => isChecked;
 | 
					            get => GetBooleanAttribute ("checked");
 | 
				
			||||||
            set {
 | 
					            set {
 | 
				
			||||||
                SetProperty (ref isChecked, value, "checked");
 | 
					                if (SetBooleanAttributeProperty ("checked", value)) {
 | 
				
			||||||
                TriggerEventFromMessage (Message.Event (Id, "change", isChecked));
 | 
					                    TriggerEventFromMessage (Message.Event (Id, "change", IsChecked));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        double minimum = 0;
 | 
					 | 
				
			||||||
        public double Minimum {
 | 
					        public double Minimum {
 | 
				
			||||||
            get => minimum;
 | 
					            get => GetAttribute ("min", 0.0);
 | 
				
			||||||
            set => SetProperty (ref minimum, value, "min");
 | 
					            set => SetAttributeProperty ("min", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        double maximum = 100;
 | 
					 | 
				
			||||||
        public double Maximum {
 | 
					        public double Maximum {
 | 
				
			||||||
            get => maximum;
 | 
					            get => GetAttribute ("max", 100.0);
 | 
				
			||||||
            set => SetProperty (ref maximum, value, "max");
 | 
					            set => SetAttributeProperty ("max", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        double step = 1;
 | 
					 | 
				
			||||||
        public double Step {
 | 
					        public double Step {
 | 
				
			||||||
            get => step;
 | 
					            get => GetAttribute ("step", 1.0);
 | 
				
			||||||
            set => SetProperty (ref step, value, "step");
 | 
					            set => SetAttributeProperty ("step", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Input ()
 | 
					        public Input ()
 | 
				
			||||||
| 
						 | 
					@ -86,10 +80,10 @@ namespace Ooui
 | 
				
			||||||
            if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) {
 | 
					            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
 | 
					                // Don't need to notify here because the base implementation will fire the event
 | 
				
			||||||
                if (Type == InputType.Checkbox) {
 | 
					                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 {
 | 
					                else {
 | 
				
			||||||
                    val = message.Value != null ? Convert.ToString (message.Value) : "";
 | 
					                    UpdateAttributeProperty ("value", message.Value != null ? Convert.ToString (message.Value) : "", "Value");
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return base.TriggerEventFromMessage (message);
 | 
					            return base.TriggerEventFromMessage (message);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,10 +4,9 @@ namespace Ooui
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class Label : Element
 | 
					    public class Label : Element
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Element htmlFor = null;
 | 
					 | 
				
			||||||
        public Element For {
 | 
					        public Element For {
 | 
				
			||||||
            get => htmlFor;
 | 
					            get => GetAttribute<Element> ("for", null);
 | 
				
			||||||
            set => SetProperty (ref htmlFor, value, "htmlFor");
 | 
					            set => SetAttributeProperty ("for", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Label ()
 | 
					        public Label ()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,6 +48,8 @@ namespace Ooui
 | 
				
			||||||
        Set,
 | 
					        Set,
 | 
				
			||||||
        [EnumMember (Value = "setAttr")]
 | 
					        [EnumMember (Value = "setAttr")]
 | 
				
			||||||
        SetAttribute,
 | 
					        SetAttribute,
 | 
				
			||||||
 | 
					        [EnumMember(Value = "remAttr")]
 | 
				
			||||||
 | 
					        RemoveAttribute,
 | 
				
			||||||
        [EnumMember(Value = "call")]
 | 
					        [EnumMember(Value = "call")]
 | 
				
			||||||
        Call,
 | 
					        Call,
 | 
				
			||||||
        [EnumMember(Value = "listen")]
 | 
					        [EnumMember(Value = "listen")]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										19
									
								
								Ooui/Node.cs
								
								
								
								
							
							
						
						
									
										19
									
								
								Ooui/Node.cs
								
								
								
								
							| 
						 | 
					@ -180,5 +180,24 @@ namespace Ooui
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public virtual string OuterHtml {
 | 
				
			||||||
 | 
					            get {
 | 
				
			||||||
 | 
					                using (var stream = new System.IO.MemoryStream ()) {
 | 
				
			||||||
 | 
					                    var settings = new System.Xml.XmlWriterSettings {
 | 
				
			||||||
 | 
					                        OmitXmlDeclaration = true,
 | 
				
			||||||
 | 
					                        ConformanceLevel = System.Xml.ConformanceLevel.Fragment,
 | 
				
			||||||
 | 
					                        CloseOutput = false,
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                    using (var w = System.Xml.XmlWriter.Create (stream, settings)) {
 | 
				
			||||||
 | 
					                        WriteOuterHtml (w);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    stream.Position = 0;
 | 
				
			||||||
 | 
					                    return new System.IO.StreamReader (stream).ReadToEnd ();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public abstract void WriteOuterHtml (System.Xml.XmlWriter w);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,22 +4,19 @@ namespace Ooui
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class Option : Element
 | 
					    public class Option : Element
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        string val = "";
 | 
					 | 
				
			||||||
        public string Value {
 | 
					        public string Value {
 | 
				
			||||||
            get => val;
 | 
					            get => GetStringAttribute ("value", "");
 | 
				
			||||||
            set => SetProperty (ref val, value ?? "", "value");
 | 
					            set => SetAttributeProperty ("value", value ?? "");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        string label = "";
 | 
					 | 
				
			||||||
        public string Label {
 | 
					        public string Label {
 | 
				
			||||||
            get => label;
 | 
					            get => GetStringAttribute ("label", "");
 | 
				
			||||||
            set => SetProperty (ref label, value ?? "", "label");
 | 
					            set => SetAttributeProperty ("label", value ?? "");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bool defaultSelected = false;
 | 
					 | 
				
			||||||
        public bool DefaultSelected {
 | 
					        public bool DefaultSelected {
 | 
				
			||||||
            get => defaultSelected;
 | 
					            get => GetBooleanAttribute ("selected");
 | 
				
			||||||
            set => SetProperty (ref defaultSelected, value, "defaultSelected");
 | 
					            set => SetBooleanAttributeProperty ("selected", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Option ()
 | 
					        public Option ()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,10 +4,9 @@ namespace Ooui
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class Select : FormControl
 | 
					    public class Select : FormControl
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        string val = "";
 | 
					 | 
				
			||||||
        public string Value {
 | 
					        public string Value {
 | 
				
			||||||
            get => val;
 | 
					            get => GetStringAttribute ("value", "");
 | 
				
			||||||
            set => SetProperty (ref val, value ?? "", "value");
 | 
					            set => SetAttributeProperty ("value", value ?? "");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public event TargetEventHandler Change {
 | 
					        public event TargetEventHandler Change {
 | 
				
			||||||
| 
						 | 
					@ -35,6 +34,7 @@ namespace Ooui
 | 
				
			||||||
        protected override void OnChildInsertedBefore (Node newChild, Node referenceChild)
 | 
					        protected override void OnChildInsertedBefore (Node newChild, Node referenceChild)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            base.OnChildInsertedBefore (newChild, referenceChild);
 | 
					            base.OnChildInsertedBefore (newChild, referenceChild);
 | 
				
			||||||
 | 
					            var val = Value;
 | 
				
			||||||
            if (string.IsNullOrEmpty (val) && newChild is Option o && !string.IsNullOrEmpty (o.Value)) {
 | 
					            if (string.IsNullOrEmpty (val) && newChild is Option o && !string.IsNullOrEmpty (o.Value)) {
 | 
				
			||||||
                val = o.Value;
 | 
					                val = o.Value;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,8 @@ namespace Ooui
 | 
				
			||||||
        protected override bool TriggerEventFromMessage (Message message)
 | 
					        protected override bool TriggerEventFromMessage (Message message)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) {
 | 
					            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);
 | 
					            return base.TriggerEventFromMessage (message);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -399,7 +399,7 @@ namespace Ooui
 | 
				
			||||||
                    o.Append (head);
 | 
					                    o.Append (head);
 | 
				
			||||||
                    o.Append (p.Key);
 | 
					                    o.Append (p.Key);
 | 
				
			||||||
                    o.Append (":");
 | 
					                    o.Append (":");
 | 
				
			||||||
                    o.Append (String.Format (System.Globalization.CultureInfo.InvariantCulture, "{0}", p.Value));
 | 
					                    o.Append (Convert.ToString (p.Value, System.Globalization.CultureInfo.InvariantCulture));
 | 
				
			||||||
                    head = ";";
 | 
					                    head = ";";
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,18 +20,19 @@ namespace Ooui
 | 
				
			||||||
            set => SetProperty (ref val, value ?? "", "value");
 | 
					            set => SetProperty (ref val, value ?? "", "value");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int rows = 2;
 | 
					 | 
				
			||||||
        public int Rows {
 | 
					        public int Rows {
 | 
				
			||||||
            get => rows;
 | 
					            get => GetAttribute ("rows", 2);
 | 
				
			||||||
            set => SetProperty (ref rows, value, "rows");
 | 
					            set => SetAttributeProperty ("rows", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int cols = 20;
 | 
					        int cols = 20;
 | 
				
			||||||
        public int Columns {
 | 
					        public int Columns {
 | 
				
			||||||
            get => cols;
 | 
					            get => GetAttribute ("cols", 20);
 | 
				
			||||||
            set => SetProperty (ref cols, value, "cols");
 | 
					            set => SetAttributeProperty ("cols", value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected override bool HtmlNeedsFullEndElement => true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public TextArea ()
 | 
					        public TextArea ()
 | 
				
			||||||
            : base ("textarea")
 | 
					            : base ("textarea")
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					@ -53,5 +54,10 @@ namespace Ooui
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return base.TriggerEventFromMessage (message);
 | 
					            return base.TriggerEventFromMessage (message);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public override void WriteInnerHtml (System.Xml.XmlWriter w)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            w.WriteString (val ?? "");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,5 +20,10 @@ namespace Ooui
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Text = text;
 | 
					            Text = text;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public override void WriteOuterHtml (System.Xml.XmlWriter w)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            w.WriteString (text);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,10 @@ namespace Ooui
 | 
				
			||||||
  <style>@Styles</style>
 | 
					  <style>@Styles</style>
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
<div id=""ooui-body"" class=""container-fluid""></div>
 | 
					
 | 
				
			||||||
 | 
					<div id=""ooui-body"" class=""container-fluid"">
 | 
				
			||||||
 | 
					@InitialHtml
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script type=""text/javascript"" src=""https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js""></script>
 | 
					<script type=""text/javascript"" src=""https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js""></script>
 | 
				
			||||||
<script type=""text/javascript"" src=""https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js""></script>
 | 
					<script type=""text/javascript"" src=""https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js""></script>
 | 
				
			||||||
| 
						 | 
					@ -390,9 +393,9 @@ namespace Ooui
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static string RenderTemplate (string webSocketPath, string title = "")
 | 
					        public static string RenderTemplate (string webSocketPath, string title = "", string initialHtml = "")
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return Template.Replace ("@WebSocketPath", webSocketPath).Replace ("@Styles", rules.ToString ()).Replace ("@Title", title);
 | 
					            return Template.Replace ("@WebSocketPath", webSocketPath).Replace ("@Styles", rules.ToString ()).Replace ("@Title", title).Replace ("@InitialHtml", initialHtml);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class DataHandler : RequestHandler
 | 
					        class DataHandler : RequestHandler
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,8 +58,8 @@ namespace Tests
 | 
				
			||||||
            Assert.AreEqual (480, c.Height);
 | 
					            Assert.AreEqual (480, c.Height);
 | 
				
			||||||
            c.Width = 0;
 | 
					            c.Width = 0;
 | 
				
			||||||
            c.Height = -100;
 | 
					            c.Height = -100;
 | 
				
			||||||
            Assert.AreEqual (150, c.Width);
 | 
					            Assert.AreEqual (0, c.Width);
 | 
				
			||||||
            Assert.AreEqual (150, c.Height);
 | 
					            Assert.AreEqual (0, c.Height);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,95 @@
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if NUNIT
 | 
				
			||||||
 | 
					using NUnit.Framework;
 | 
				
			||||||
 | 
					using TestClassAttribute = NUnit.Framework.TestFixtureAttribute;
 | 
				
			||||||
 | 
					using TestMethodAttribute = NUnit.Framework.TestCaseAttribute;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					using Microsoft.VisualStudio.TestTools.UnitTesting;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using Ooui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Tests
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    [TestClass]
 | 
				
			||||||
 | 
					    public class WriteHtmlTests
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        System.Text.RegularExpressions.Regex idre = new System.Text.RegularExpressions.Regex ("\\sid=\"[^\"]*\"");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        string OuterHtmlWithoutIds (Element e)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return idre.Replace (e.OuterHtml, "");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [TestMethod]
 | 
				
			||||||
 | 
					        public void TextAreaWithTextStyled ()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var e = new TextArea {
 | 
				
			||||||
 | 
					                Value = "Hello World!",
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            e.Style.BackgroundColor = "#18f";
 | 
				
			||||||
 | 
					            Assert.AreEqual ("<textarea style=\"background-color:#18f\">Hello World!</textarea>", OuterHtmlWithoutIds (e));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [TestMethod]
 | 
				
			||||||
 | 
					        public void TextAreaEmptyStyled ()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var e = new TextArea ();
 | 
				
			||||||
 | 
					            e.Style.BackgroundColor = "#18f";
 | 
				
			||||||
 | 
					            Assert.AreEqual ("<textarea style=\"background-color:#18f\"></textarea>", OuterHtmlWithoutIds (e));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [TestMethod]
 | 
				
			||||||
 | 
					        public void Style ()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var e = new Div ();
 | 
				
			||||||
 | 
					            e.Style.BackgroundColor = "#18f";
 | 
				
			||||||
 | 
					            Assert.AreEqual ("<div style=\"background-color:#18f\"></div>", OuterHtmlWithoutIds (e));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [TestMethod]
 | 
				
			||||||
 | 
					        public void TwoGrandChildren ()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var e = new Div (new Div (new Anchor (), new Anchor ()), new Paragraph ());
 | 
				
			||||||
 | 
					            Assert.AreEqual ("<div><div><a /><a /></div><p /></div>", OuterHtmlWithoutIds (e));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [TestMethod]
 | 
				
			||||||
 | 
					        public void Child ()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var e = new Div (new Anchor ());
 | 
				
			||||||
 | 
					            Assert.AreEqual ("<div><a /></div>", OuterHtmlWithoutIds (e));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [TestMethod]
 | 
				
			||||||
 | 
					        public void TextChild ()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var e = new Paragraph ("Hello world!");
 | 
				
			||||||
 | 
					            Assert.AreEqual ("<p>Hello world!</p>", OuterHtmlWithoutIds (e));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [TestMethod]
 | 
				
			||||||
 | 
					        public void IdIsFirst ()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var e = new Anchor ();
 | 
				
			||||||
 | 
					            Assert.IsTrue (e.OuterHtml.StartsWith ("<a id=\""));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [TestMethod]
 | 
				
			||||||
 | 
					        public void EmptyElement ()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var e = new Anchor ();
 | 
				
			||||||
 | 
					            Assert.AreEqual ("<a />", OuterHtmlWithoutIds (e));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [TestMethod]
 | 
				
			||||||
 | 
					        public void AnchorHRef ()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var e = new Anchor {
 | 
				
			||||||
 | 
					                HRef = "http://google.com"
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            Assert.AreEqual ("<a href=\"http://google.com\" />", OuterHtmlWithoutIds (e));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue