using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Metadata;
namespace Monaco.Helpers
{
///
/// Class to aid in accessing WinRT values from JavaScript.
/// Not Thread Safe.
///
[AllowForWeb]
public sealed class ParentAccessor : IDisposable
{
private WeakReference parent;
private Type typeinfo;
private Dictionary actions;
private Dictionary>> events;
private List Assemblies { get; set; } = new List();
///
/// Constructs a new reflective parent Accessor for the provided object.
///
/// Object to provide Property Access.
public ParentAccessor(IParentAccessorAcceptor parent)
{
this.parent = new WeakReference(parent);
typeinfo = parent.GetType();
actions = new Dictionary();
events = new Dictionary>>();
}
///
/// Registers an action from the .NET side which can be called from within the JavaScript code.
///
/// String Key.
/// Action to perform.
internal void RegisterAction(string name, Action action)
{
actions[name] = action;
}
///
/// Registers an event from the .NET side which can be called with the given jsonified string arguments within the JavaScript code.
///
/// String Key.
/// Event to call.
internal void RegisterEvent(string name, Func> function)
{
events[name] = function;
}
///
/// Calls an Event registered before wit hthe .
///
/// Name of event to call.
/// JSON string Parameters.
///
public IAsyncOperation CallEvent(string name, [ReadOnlyArray] string[] parameters)
{
if (events.ContainsKey(name))
{
return events[name]?.Invoke(parameters).AsAsyncOperation();
}
return (new Task(() => { return null; })).AsAsyncOperation();
}
///
/// Adds an Assembly to use for looking up types by name for .
///
/// Assembly to add.
internal void AddAssemblyForTypeLookup(Assembly assembly)
{
Assemblies.Add(assembly);
}
///
/// Calls an Action registered before with .
///
/// String Key.
/// True if method was found in registration.
public bool CallAction(string name)
{
if (actions.ContainsKey(name))
{
actions[name]?.Invoke();
return true;
}
return false;
}
///
/// Returns the winrt primative object value for the specified Property.
///
/// Property name on Parent Object.
/// Property Value or null.
public object GetValue(string name)
{
if (parent.TryGetTarget(out IParentAccessorAcceptor tobj))
{
var propinfo = typeinfo.GetProperty(name);
return propinfo?.GetValue(tobj);
}
return null;
}
public string GetJsonValue(string name)
{
if (parent.TryGetTarget(out IParentAccessorAcceptor tobj))
{
var propinfo = typeinfo.GetProperty(name);
var obj = propinfo?.GetValue(tobj);
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore
});
}
return "{}";
}
///
/// Returns the winrt primative object value for a child property off of the specified Property.
///
/// Useful for providing complex types to users of Parent but still access primatives in JavaScript.
///
/// Parent Property name.
/// Property's Property name to retrieve.
/// Value of Child Property or null.
public object GetChildValue(string name, string child)
{
if (parent.TryGetTarget(out IParentAccessorAcceptor tobj))
{
// TODO: Support params for multi-level digging?
var propinfo = typeinfo.GetProperty(name);
var prop = propinfo?.GetValue(tobj);
if (prop != null)
{
var childinfo = prop.GetType().GetProperty(child);
return childinfo?.GetValue(prop);
}
}
return null;
}
///
/// Sets the value for the specified Property.
///
/// Parent Property name.
/// Value to set.
public void SetValue(string name, object value)
{
if (parent.TryGetTarget(out IParentAccessorAcceptor tobj))
{
var propinfo = typeinfo.GetProperty(name); // TODO: Cache these?
tobj.IsSettingValue = true;
propinfo?.SetValue(tobj, value);
tobj.IsSettingValue = false;
}
}
///
/// Sets the value for the specified Property after deserializing the value as the given type name.
///
///
///
///
public void SetValue(string name, string value, string type)
{
if (parent.TryGetTarget(out IParentAccessorAcceptor tobj))
{
var propinfo = typeinfo.GetProperty(name);
var typeobj = LookForTypeByName(type);
var obj = JsonConvert.DeserializeObject(value, typeobj);
tobj.IsSettingValue = true;
propinfo?.SetValue(tobj, obj);
tobj.IsSettingValue = false;
}
}
private Type LookForTypeByName(string name)
{
// First search locally
var result = Type.GetType(name);
if (result != null)
{
return result;
}
// Search in Other Assemblies
foreach (var assembly in Assemblies)
{
foreach (var typeInfo in assembly.ExportedTypes)
{
if (typeInfo.Name == name)
{
return typeInfo;
}
}
}
return null;
}
public void Dispose()
{
if (actions != null)
{
actions.Clear();
}
actions = null;
if (events != null)
{
events.Clear();
}
events = null;
}
}
//// TODO: Find better approach than this. Issue #21.
///
/// Interface used on objects to be accessed.
///
public interface IParentAccessorAcceptor
{
///
/// Property to tell object the value is being set by ParentAccessor.
///
bool IsSettingValue { get; set; }
}
}