Merge pull request #168 from limefrogyank/minimal
(mostly) Implementation of NavigationPage
This commit is contained in:
commit
6a2ee75782
|
@ -25,6 +25,7 @@ using Xamarin.Forms;
|
|||
[assembly: ExportRenderer (typeof (Switch), typeof (SwitchRenderer))]
|
||||
[assembly: ExportRenderer (typeof (TimePicker), typeof (TimePickerRenderer))]
|
||||
[assembly: ExportRenderer (typeof (WebView), typeof (WebViewRenderer))]
|
||||
[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer))]
|
||||
[assembly: ExportImageSourceHandler (typeof (FileImageSource), typeof (FileImageSourceHandler))]
|
||||
[assembly: ExportImageSourceHandler (typeof (StreamImageSource), typeof (StreamImagesourceHandler))]
|
||||
[assembly: ExportImageSourceHandler (typeof (UriImageSource), typeof (ImageLoaderSourceHandler))]
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Internals;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ooui.Forms.Renderers
|
||||
{
|
||||
public class NavigationPageRenderer : VisualElementRenderer<NavigationPage>
|
||||
{
|
||||
private Stack<DefaultRenderer> backElementStack = new Stack<DefaultRenderer>();
|
||||
private Stack<DefaultRenderer> forwardElementStack = new Stack<DefaultRenderer>();
|
||||
// required hack to make sure a browser nav event (back and forward buttons) don't trigger the commands to pushState or go back on the browser AGAIN
|
||||
bool ignoreNavEventFlag = false;
|
||||
|
||||
private string ns;
|
||||
private string assemblyName;
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if (e.OldElement != null)
|
||||
{
|
||||
e.OldElement.PushRequested -= OnPushRequested;
|
||||
e.OldElement.PopRequested -= OnPopRequested;
|
||||
e.OldElement.PopToRootRequested -= OnPopToRootRequested;
|
||||
e.OldElement.InternalChildren.CollectionChanged -= OnChildrenChanged;
|
||||
e.OldElement.PropertyChanged -= OnElementPropertyChanged;
|
||||
}
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
e.NewElement.PushRequested += OnPushRequested;
|
||||
e.NewElement.PopRequested += OnPopRequested;
|
||||
e.NewElement.PopToRootRequested += OnPopToRootRequested;
|
||||
e.NewElement.InternalChildren.CollectionChanged += OnChildrenChanged;
|
||||
e.NewElement.PropertyChanged += OnElementPropertyChanged;
|
||||
|
||||
GetAssemblyInfoForRootPage();
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool TriggerEventFromMessage(Message message)
|
||||
{
|
||||
if (message.TargetId == "window" && message.Key == "hashchange" && message.Value is Newtonsoft.Json.Linq.JObject k)
|
||||
{
|
||||
ProcessHash((string)k["hash"]);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return base.TriggerEventFromMessage(message);
|
||||
}
|
||||
|
||||
//signaling doesn't seem to work until child has been inserted into document, so wait for this and then adjust the hash for direct navigation
|
||||
protected override void OnChildInsertedBefore(Node newChild, Node referenceChild)
|
||||
{
|
||||
base.OnChildInsertedBefore(newChild, referenceChild);
|
||||
|
||||
var index = this.Children.IndexOf(newChild);
|
||||
if (index - 1 >= 0)
|
||||
(this.Children[index - 1] as Element).Style.Display = "none";
|
||||
|
||||
this.backElementStack.Push(newChild as DefaultRenderer);
|
||||
|
||||
if (this.ignoreNavEventFlag)
|
||||
this.ignoreNavEventFlag = false;
|
||||
else
|
||||
{
|
||||
this.forwardElementStack.Clear();
|
||||
this.NativeView.Document.Window.Call("history.pushState", null, null, GenerateFullHash());
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnChildRemoved(Node child)
|
||||
{
|
||||
base.OnChildRemoved(child);
|
||||
|
||||
if (this.Children.Count > 0)
|
||||
(this.Children.Last() as Element).Style.Display = "block";
|
||||
|
||||
DefaultRenderer popped = this.backElementStack.Pop();
|
||||
this.forwardElementStack.Push(child as DefaultRenderer);
|
||||
|
||||
if (this.ignoreNavEventFlag)
|
||||
this.ignoreNavEventFlag = false;
|
||||
else
|
||||
this.NativeView.Document.Window.Call("history.back");
|
||||
}
|
||||
|
||||
//only called when user types url manually OR clicks forward or back in the browser OR very beginning of navigation (backStack has first element already though)
|
||||
private void ProcessHash(string fullHash)
|
||||
{
|
||||
IEnumerable<string> browserPageArray;
|
||||
if (fullHash.Length > 0)
|
||||
browserPageArray = fullHash.Split('/').Select(x => x == "#" ? this.backElementStack.Last().Element.GetType().Name : x);
|
||||
else
|
||||
browserPageArray = new string[] { this.backElementStack.Last().Element.GetType().Name };
|
||||
|
||||
// if current hash doesn't match up with the current page displayed (which is the first/most recent one in the backHashStack)
|
||||
if (backElementStack.First().Element.GetType().Name != browserPageArray.Last())
|
||||
{
|
||||
// see if the last hash item is in the backStack (nav backwards) OR in the forward stack (nav forwards) OR not there at all (regenerate stack)
|
||||
var lastPageFromBackStack = backElementStack.FirstOrDefault(x => x.Element.GetType().Name == browserPageArray.Last());//.Cast<(Page page, string hash)?>().FirstOrDefault();
|
||||
var lastPageFromForwardStack = forwardElementStack.FirstOrDefault(x => x.Element.GetType().Name == browserPageArray.Last());//.Cast<(Page page, string hash)?>().FirstOrDefault();
|
||||
|
||||
// just need to adjust internal stack and page view
|
||||
// case clicked back button
|
||||
if (lastPageFromBackStack != null)
|
||||
{
|
||||
var peeked = this.backElementStack.Peek();
|
||||
this.ignoreNavEventFlag = true;
|
||||
this.Element.PopAsync();
|
||||
}
|
||||
// case for clicking forward
|
||||
else if (lastPageFromForwardStack != null)
|
||||
{
|
||||
var popped = this.forwardElementStack.Pop();
|
||||
this.ignoreNavEventFlag = true;
|
||||
|
||||
// *** This should work, but the result is a page that doesn't format correctly. Instead, we'll have to create new pages from scratch.
|
||||
//this.Element.PushAsync(popped.Element as Page);
|
||||
this.Element.PushAsync((Page)Activator.CreateInstance(Type.GetType($"{this.ns}.{popped.Element.GetType().Name}, {this.assemblyName}")));
|
||||
}
|
||||
// case for someone typing in url from scratch with hash
|
||||
else
|
||||
{
|
||||
//assume someone put the url into the browser manually from scratch
|
||||
//first replace the current state with # only.
|
||||
this.Document.Window.Call("history.replaceState", null, null, GenerateFullHash());
|
||||
foreach (var pageName in browserPageArray.Where(x=> x != backElementStack.Last().Element.GetType().Name))
|
||||
{
|
||||
var pageTypeName = WebUtility.HtmlDecode(pageName);
|
||||
var pageInstance = Activator.CreateInstance(Type.GetType($"{this.ns}.{pageTypeName}, {this.assemblyName}"));
|
||||
this.Element.PushAsync(pageInstance as Page);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reflection needs more than just the name to create an instance from a string.
|
||||
private void GetAssemblyInfoForRootPage()
|
||||
{
|
||||
if (this.Element.Navigation.NavigationStack.Count > 0)
|
||||
{
|
||||
var page = this.Element.Navigation.NavigationStack.Last();
|
||||
var type = page.GetType();
|
||||
this.ns = type.Namespace;
|
||||
this.assemblyName = type.Assembly.FullName;
|
||||
}
|
||||
}
|
||||
|
||||
// This is where you would normally set back button state based on what's in the stack.
|
||||
private void OnChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// Not needed for browser... back button is always available.
|
||||
}
|
||||
|
||||
private void OnPopToRootRequested(object sender, NavigationRequestedEventArgs e)
|
||||
{
|
||||
e.Realize = true;
|
||||
}
|
||||
|
||||
private void OnPopRequested(object sender, NavigationRequestedEventArgs e)
|
||||
{
|
||||
e.Realize = true;
|
||||
}
|
||||
|
||||
// This is where you would draw the new contents.
|
||||
private void OnPushRequested(object sender, NavigationRequestedEventArgs e)
|
||||
{
|
||||
e.Realize = true;
|
||||
}
|
||||
|
||||
private string GenerateFullHash()
|
||||
{
|
||||
string hashString = "";
|
||||
bool started = false;
|
||||
foreach (var i in this.backElementStack.Reverse())
|
||||
{
|
||||
if (started)
|
||||
hashString += "/" + i.Element.GetType().Name;
|
||||
else
|
||||
{
|
||||
started = true;
|
||||
hashString += "#";
|
||||
}
|
||||
}
|
||||
return hashString;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -59,6 +59,20 @@ function saveSize (s) {
|
|||
setCookie ("oouiWindowHeight", s.height, 7);
|
||||
}
|
||||
|
||||
function initializeNavigation() {
|
||||
monitorHashChanged();
|
||||
const em = {
|
||||
m: "event",
|
||||
id: "window",
|
||||
k: "hashchange",
|
||||
v: window.location
|
||||
};
|
||||
saveSize(em.v);
|
||||
const ems = JSON.stringify(em);
|
||||
send(ems);
|
||||
if (debug) console.log("Event", em);
|
||||
}
|
||||
|
||||
// Main entrypoint
|
||||
function ooui (rootElementPath) {
|
||||
|
||||
|
@ -77,6 +91,7 @@ function ooui (rootElementPath) {
|
|||
|
||||
socket.addEventListener ("open", function (event) {
|
||||
console.log ("Web socket opened");
|
||||
initializeNavigation();
|
||||
});
|
||||
|
||||
socket.addEventListener ("error", function (event) {
|
||||
|
@ -109,9 +124,27 @@ function oouiWasm (mainAsmName, mainNamespace, mainClassName, mainMethodName, as
|
|||
Module.entryPoint = { "a": mainAsmName, "n": mainNamespace, "t": mainClassName, "m": mainMethodName };
|
||||
Module.assemblies = assemblies;
|
||||
|
||||
initializeNavigation();
|
||||
monitorSizeChanges (1000/30);
|
||||
}
|
||||
|
||||
function monitorHashChanged() {
|
||||
function hashChangeHandler() {
|
||||
const em = {
|
||||
m: "event",
|
||||
id: "window",
|
||||
k: "hashchange",
|
||||
v: window.location
|
||||
};
|
||||
saveSize(em.v);
|
||||
const ems = JSON.stringify(em);
|
||||
send(ems);
|
||||
if (debug) console.log("Event", em);
|
||||
}
|
||||
|
||||
window.addEventListener("hashchange", hashChangeHandler, false);
|
||||
}
|
||||
|
||||
function monitorSizeChanges (millis)
|
||||
{
|
||||
var resizeTimeout;
|
||||
|
@ -213,6 +246,17 @@ function msgRemAttr (m) {
|
|||
if (debug) console.log ("RemAttr", node, m.k);
|
||||
}
|
||||
|
||||
function getCallerProperty(target, accessorStr) {
|
||||
const arr = accessorStr.split('.');
|
||||
var caller = target;
|
||||
var property = target;
|
||||
arr.forEach(function (v) {
|
||||
caller = property;
|
||||
property = caller[v];
|
||||
});
|
||||
return [caller, property];
|
||||
}
|
||||
|
||||
function msgCall (m) {
|
||||
const id = m.id;
|
||||
const node = getNode (id);
|
||||
|
@ -227,9 +271,10 @@ function msgCall (m) {
|
|||
target.removeChild (target.firstChild);
|
||||
delete hasText[id];
|
||||
}
|
||||
const f = target[m.k];
|
||||
//const f = target[m.k];
|
||||
const f = getCallerProperty(target, m.k);
|
||||
if (debug) console.log ("Call", node, f, m.v);
|
||||
const r = f.apply (target, m.v);
|
||||
const r = f[1].apply (f[0], m.v);
|
||||
if (typeof m.rid === 'string' || m.rid instanceof String) {
|
||||
nodes[m.rid] = r;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Samples.Navigation.NavigationFirstPage"
|
||||
Title="The First Page">
|
||||
<ContentPage.Content>
|
||||
<StackLayout>
|
||||
<Label Text="This is the first page."
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="CenterAndExpand" />
|
||||
<Button Clicked="Button_Clicked"
|
||||
Text="Navigate to Second Page" />
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Samples.Navigation
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class NavigationFirstPage : ContentPage
|
||||
{
|
||||
public NavigationFirstPage ()
|
||||
{
|
||||
InitializeComponent ();
|
||||
|
||||
}
|
||||
|
||||
private void Button_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
(this.Parent as NavigationPage).PushAsync(new Navigation.NavigationSecondPage());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Samples.Navigation.NavigationSecondPage"
|
||||
Title="The Second Page">
|
||||
<ContentPage.Content>
|
||||
<StackLayout>
|
||||
<Label Text="This is the second page."
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="CenterAndExpand" />
|
||||
<Button Clicked="BackButton_Clicked"
|
||||
Text="Go back to First Page" />
|
||||
<Button Clicked="Button_Clicked"
|
||||
Text="Navigate to Third Page" />
|
||||
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Samples.Navigation
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class NavigationSecondPage : ContentPage
|
||||
{
|
||||
public NavigationSecondPage ()
|
||||
{
|
||||
InitializeComponent ();
|
||||
}
|
||||
|
||||
private void BackButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
(this.Parent as NavigationPage).PopAsync();
|
||||
}
|
||||
|
||||
private void Button_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
(this.Parent as NavigationPage).PushAsync(new Navigation.NavigationThirdPage());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Samples.Navigation.NavigationThirdPage"
|
||||
Title="The Third Page">
|
||||
<ContentPage.Content>
|
||||
<StackLayout>
|
||||
<Label Text="This is the third page."
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="CenterAndExpand" />
|
||||
<Button Clicked="BackButton_Clicked"
|
||||
Text="Go back to Second Page" />
|
||||
<Button Clicked="RootButton_Clicked"
|
||||
Text="Go back to Root (1st page)" />
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Samples.Navigation
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class NavigationThirdPage : ContentPage
|
||||
{
|
||||
public NavigationThirdPage ()
|
||||
{
|
||||
InitializeComponent ();
|
||||
}
|
||||
|
||||
private void BackButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
(this.Parent as NavigationPage).PopAsync(false);
|
||||
}
|
||||
|
||||
private void RootButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
(this.Parent as NavigationPage).PopToRootAsync(false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
using Ooui;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Samples.Navigation
|
||||
{
|
||||
public class NavigationSample : ISample
|
||||
{
|
||||
public string Title => "Xamarin.Forms Navigation XAML";
|
||||
|
||||
public Ooui.Element CreateElement()
|
||||
{
|
||||
var page = new Navigation.NavigationFirstPage();
|
||||
var root = new NavigationPage(page);
|
||||
return root.GetOouiElement();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue