FileOpenDialog Extension for CSHTML5

JS-Support @Userware
Site Admin
Posts: 1142
Joined: Tue Apr 08, 2014 3:42 pm

FileOpenDialog Extension for CSHTML5

Postby JS-Support @Userware » Fri Mar 04, 2016 10:36 am

Dear CSHTML5 users,

I am pleased to inform you of the release of the FileOpenDialog Extension for CSHTML5.


It adds the ability to let the user pick a file using the "Open File..." dialog of the browser.

It can also be used for example in conjunction with the ZipFile Extension to load a ZIP file. Note: if you are looking for a way to save files to disk, you can use the FileSaver Extension.


It requires CSHTML5 v1.0 Beta 7.2 or newer.

UPDATE (November 16, 2017): Added support for getting a "DataURI" from a binary file (useful for reading pictures) + Added example of loading a picture (requires v1.0 Beta 12.6 or newer) + Added "FileName" property (thanks to TaterJuice) + Added code for displaying an "OpenFile" ChildWindow.
UPDATE (September 13, 2017): Improved cross-browser compatibility.


To use, simply add a new class named "FileOpenDialog.cs" to your project, and copy/paste the following code:

Code: Select all

using System;
using System.Collections.Generic;
#if SLMIGRATION
using System.Windows;
using System.Windows.Controls;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif

//------------------------------------
// This is an extension for C#/XAML for HTML5 (www.cshtml5.com)
//
// It requires Beta 7.2 or newer.
//
// It adds the ability to let the user pick a file using the
// "Open File..." dialog of the browser.
//
// It can also be used for example in conjunction with the ZipFile
// Extension to load a ZIP file. Note: if you are looking for
// a way to save files to disk, you can use the FileSaver Extension.
//
// The extension works by adding an <input type='file'> tag to the
// HTML DOM, which will cause the "Open file..." dialog to appear,
// and by listening to its "change" event in order to get a reference
// to the selected file. it returns a JavaScript blob. If the file
// is a plain text file, it will also return a string with the text
// content of the file.
//
// This extension is licensed under the open-source MIT license:
// https://opensource.org/licenses/MIT
//
// Copyright 2017 Userware / CSHTML5
//------------------------------------

namespace CSHTML5.Extensions.FileOpenDialog
{
    public class ControlForDisplayingTheFileOpenDialog : Control
    {
        public event EventHandler<FileOpenedEventArgs> FileOpened;

        private ResultKind _resultKind;
        private string _resultKindStr;
        public ResultKind ResultKind
        {
            get { return _resultKind; }
            set
            {
                _resultKind = value;
                _resultKindStr = value.ToString();
            }
        }

        public ControlForDisplayingTheFileOpenDialog()
        {
            ResultKind = FileOpenDialog.ResultKind.Text; //Note: this is to set the default value of the property.

            CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this,
                "<input type='file'>");

            this.Loaded += ControlForDisplayingAFileOpenDialog_Loaded;
        }

        void ControlForDisplayingAFileOpenDialog_Loaded(object sender, RoutedEventArgs e)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            Action<object> onFileOpened = (result) =>
            {
                if (this.FileOpened != null)
                    this.FileOpened(this, new FileOpenedEventArgs(result, this.ResultKind));
            };
            string resultKindStr = _resultKindStr; // Note: this is here to "capture" the variable so that it can be used inside the "addEventListener" below.

            // Apply the "Filter" property to limit the choice of the user to the specified extensions:
            SetFilter(this.Filter);

            // Listen to the "change" property of the "input" element, and call the callback:
            CSHTML5.Interop.ExecuteJavaScript(@"
                $0.addEventListener(""change"", function(e) {
                    if(!e) {
                      e = window.event;
                    }
                    var fullPath = $0.value;
                    var filename = '';
                    if (fullPath) {
                        var startIndex = (fullPath.indexOf('\\') >= 0 ? fullPath.lastIndexOf('\\') : fullPath.lastIndexOf('/'));
                        filename = fullPath.substring(startIndex);
                        if (filename.indexOf('\\') === 0 || filename.indexOf('/') === 0) {
                            filename = filename.substring(1);
                        }
                    }
                    var input = e.target;
                    var file = input.files[0];
                    var reader = new FileReader();
                    reader.onload = function() {
                      var callback = $1;
                      var result = reader.result;
                      //if (file.type == 'text/plain')
                      callback(result);
                    };
                    var resultKind = $3;
                    if (resultKind == 'DataURL') {
                      reader.readAsDataURL(file);
                    }
                    else {
                      reader.readAsText(file);
                    }
                    var isRunningInTheSimulator = $2;
                    if (isRunningInTheSimulator) {
                        alert(""The file open dialog is not supported in the Simulator. Please test in the browser instead."");
                    }
                });", inputElement, onFileOpened, CSHTML5.Interop.IsRunningInTheSimulator, resultKindStr);
        }

        void SetFilter(string filter)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            // Process the filter list to convert the syntax from XAML to HTML5:
            // Example of syntax in Silverlight: Image Files (*.bmp, *.jpg)|*.bmp;*.jpg|All Files (*.*)|*.*
            // Example of syntax in HTML5: .gif, .jpg, .png, .doc
            string[] splitted = filter.Split('|');
            List<string> itemsKept = new List<string>();
            if (splitted.Length == 1)
            {
                itemsKept.Add(splitted[0]);
            }
            else
            {
                for (int i = 1; i < splitted.Length; i += 2)
                {
                    itemsKept.Add(splitted[i]);
                }
            }
            string filtersInHtml5 = String.Join(",", itemsKept).Replace("*", "").Replace(";", ",");

            // Apply the filter:
            if (!string.IsNullOrWhiteSpace(filtersInHtml5))
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = $1", inputElement, filtersInHtml5);
            }
            else
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = """"", inputElement);
            }
        }


        public string Filter
        {
            get { return (string)GetValue(FilterProperty); }
            set { SetValue(FilterProperty, value); }
        }
        public static readonly DependencyProperty FilterProperty =
            DependencyProperty.Register("Filter", typeof(string), typeof(ControlForDisplayingTheFileOpenDialog), new PropertyMetadata("", Filter_Changed));

        static void Filter_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = (ControlForDisplayingTheFileOpenDialog)d;
            if (CSharpXamlForHtml5.DomManagement.IsControlInVisualTree(control))
            {
                control.SetFilter((e.NewValue ?? "").ToString());
            }
        }
    }

    public class FileOpenedEventArgs : EventArgs
    {
        /// <summary>
        /// Only available if the property "ResultKind" was set to "Text"
        /// </summary>
        [Obsolete]
        public readonly string PlainTextContent;

        /// <summary>
        /// Only available if the property "ResultKind" was set to "Text".
        /// </summary>
        public readonly string Text;

        /// <summary>
        /// Only available if the property "ResultKind" was set to "DataURL".
        /// </summary>
        public readonly string DataURL;

        public FileOpenedEventArgs(object result, ResultKind resultKind)
        {
            if (resultKind == ResultKind.Text)
            {
                this.Text = this.PlainTextContent = (result ?? "").ToString();
            }
            else if (resultKind == ResultKind.DataURL)
            {
                this.DataURL = (result ?? "").ToString();
            }
            else
            {
                throw new NotSupportedException();
            }
        }
    }

    public enum ResultKind
    {
        Text, DataURL
    }
}



The extension works by adding an <input type='file'> tag to the HTML DOM, which will cause the "Open file..." dialog to appear, and by listening to its "change" event in order to get a reference to the selected file. it returns a JavaScript blob. If the file is a plain text file, it will also return a string with the text content of the file.


To use the code:
- Add the following control to your XAML:

Code: Select all

<Page
    ...
    xmlns:extensions="using:CSHTML5.Extensions.FileOpenDialog"
    ...
>
    <Canvas>
        <extensions:ControlForDisplayingTheFileOpenDialog FileOpened="OnFileOpened" Filter="*.txt,*.csv" ResultKind="Text"/>
    </Canvas>
</Page>

- Add the following code to your C# (code-behind):

Code: Select all

async void OnFileOpened(object sender, CSHTML5.Extensions.FileOpenDialog.FileOpenedEventArgs e)
{
    MessageBox.Show(e.Text);
}



If you wish to load a picture file, add an <Image x:Name="MyImageControl"/> control to your XAML, and use the following code (it requires v1.0 Beta 12.6 or newer):
- XAML:

Code: Select all

<extensions:ControlForDisplayingTheFileOpenDialog FileOpened="OnFileOpened" Filter=" (.jpg;*.png)|*.jpg;*.png|All Files (*.*)|*.*" ResultKind="DataURL"/>

- Add the following code to your C# (code-behind):

Code: Select all

async void OnFileOpened(object sender, CSHTML5.Extensions.FileOpenDialog.FileOpenedEventArgs e)
{
   string dataURL = e.DataURL;
   BitmapImage bitmapimage = new BitmapImage();
   bitmapimage.SetSource(dataURL );
   MyImageControl.Source = bitmapimage;
}



If you wish to display a ChildWindow with an "Open file" button, create a new CSHTML5 UserControl named "OpenDialogChildWindow" and use the following code:

- XAML code:

Code: Select all

<ChildWindow
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Width="350"
            Height="240"
            xmlns:fileopendialog="clr-namespace:CSHTML5.Extensions.FileOpenDialog"
            x:Class="CSHTML5.Extensions.FileOpenDialog.OpenDialogChildWindow">
    <StackPanel Margin="30">
        <TextBlock Text="To open a file, click the following button:" TextWrapping="Wrap"/>
        <fileopendialog:ControlForDisplayingTheFileOpenDialog x:Name="FileOpenDialog" Margin="0,20,0,0" Filter=""/>
        <Button Content="OK" HorizontalAlignment="Center" Margin="0,20,0,0" Click="ButtonOK_Click"/>
    </StackPanel>
</ChildWindow>


-C# code:

Code: Select all

using System.Windows;
using System.Windows.Controls;

namespace CSHTML5.Extensions.FileOpenDialog
{
    public partial class OpenDialogChildWindow : ChildWindow
    {
        public OpenDialogChildWindow()
        {
            InitializeComponent();

            this.Title = "Open File";
            this.FileOpenDialog.FileOpened += FileOpenDialog_FileOpened;
        }

        void FileOpenDialog_FileOpened(object sender, CSHTML5.Extensions.FileOpenDialog.FileOpenedEventArgs e)
        {
            this.Result = e;
        }

        public CSHTML5.Extensions.FileOpenDialog.ResultKind ResultKind
        {
            get { return this.FileOpenDialog.ResultKind; }
            set { this.FileOpenDialog.ResultKind = value; }
        }

        public CSHTML5.Extensions.FileOpenDialog.FileOpenedEventArgs Result { get; private set; }

        public string Filter
        {
            get { return FileOpenDialog.Filter; }
            set { FileOpenDialog.Filter = value; }
        }

        private void ButtonOK_Click(object sender, RoutedEventArgs e)
        {
            if (this.Result != null)
            {
                this.DialogResult = true;
            }
            else
            {
                this.DialogResult = false;
            }
        }
    }
}



For security reason, the user needs to click the control in order for the Open File Dialog to appear.

You can also see an example of use with binary content by looking at the ZipFile Extension.

Regards,
JS-Support

rkmore
Posts: 55
Joined: Mon Dec 07, 2015 1:53 pm

Accessing the camera

Postby rkmore » Mon Apr 04, 2016 4:53 am

To access the camera using this extension change:

Code: Select all

            CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this,
                "<input type='file'>");


to

Code: Select all

            CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this,
                "<input type='file' accept='image/*' capture='camera'>");


RKM

JS-Support @Userware
Site Admin
Posts: 1142
Joined: Tue Apr 08, 2014 3:42 pm

Re: FileOpenDialog Extension for CSHTML5

Postby JS-Support @Userware » Mon Apr 04, 2016 9:06 am

Thanks a lot RKM for your contribution.

JS-Support @Userware
Site Admin
Posts: 1142
Joined: Tue Apr 08, 2014 3:42 pm

Re: FileOpenDialog Extension for CSHTML5

Postby JS-Support @Userware » Wed Sep 13, 2017 1:36 am

UPDATE: The FileOpenDialog extension has just been updated with the following features:
- Improved cross-browser compatibility.

Regards,
JS-Support

TaterJuice
Posts: 147
Joined: Thu Mar 16, 2017 5:40 am
Contact:

Re: FileOpenDialog Extension for CSHTML5

Postby TaterJuice » Tue Sep 26, 2017 9:15 am

Non-Official Fork/Update by TaterJuice
(Edit: Removed, please see Update #2, below. Fixed a crucial bug with getting the Base64 Encoded Content)
Last edited by TaterJuice on Sun Oct 15, 2017 2:34 pm, edited 1 time in total.

JS-Support @Userware
Site Admin
Posts: 1142
Joined: Tue Apr 08, 2014 3:42 pm

Re: FileOpenDialog Extension for CSHTML5

Postby JS-Support @Userware » Tue Sep 26, 2017 11:36 pm

Thanks a lot TaterJuice for the update!

TaterJuice
Posts: 147
Joined: Thu Mar 16, 2017 5:40 am
Contact:

Re: FileOpenDialog Extension for CSHTML5

Postby TaterJuice » Sun Oct 15, 2017 2:27 pm

Non-Official Fork by TaterJuice
Update #2

Description:
I misunderstood the original implementation of the JS FileReader by JS-Admin and consequently, my JavascriptBlobToBase64StringConverter was not working.
This update will read the file content as base64 encoded string with basic mime-type metadata, and NO LONGER attempts to read the file as text.

Changelog:
1. Removed JavasCriptBlobToBase64StringConverter
2. Replaced default implementation of OpenFile dialogue, from ReadAsText to ReadAsDataURL (and fixed base64string implementation)
3. Modified FileOpenedEventArgs, removed PlainTextContent property, Added base64EncodedContentMetaData (JS DataURL Mime-Type string)

args.base64EncodedContentMetaData property format looks like this: "data:image/png;base64"
For more, see https://developer.mozilla.org/en-US/docs/Web/API/FileReader

Code: Select all

//SEE BOTTOM FOR NEW IMPLEMENTATION

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
#if SLMIGRATION
using System.Windows;
using System.Windows.Controls;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif

//------------------------------------
// This is an extension for C#/XAML for HTML5 (www.cshtml5.com)
//
// It requires Beta 7.2 or newer.
//
// It adds the ability to let the user pick a file using the
// "Open File..." dialog of the browser.
//
// It can also be used for example in conjunction with the ZipFile
// Extension to load a ZIP file. Note: if you are looking for
// a way to save files to disk, you can use the FileSaver Extension.
//
// The extension works by adding an <input type='file'> tag to the
// HTML DOM, which will cause the "Open file..." dialog to appear,
// and by listening to its "change" event in order to get a reference
// to the selected file. it returns a JavaScript blob. If the file
// is a plain text file, it will also return a string with the text
// content of the file.
//
// This extension is licensed under the open-source MIT license:
// https://opensource.org/licenses/MIT
//
// Copyright 2016 Userware / CSHTML5
//------------------------------------

/*
Edit by TaterJuice, 9-26-2017
Changelog:
1. Added "FileOpenedEventArgs.FileName (string)" property to class FileOpenedEventArgs
2. Added class JSBlobToBase64StringConverter to convert file content to base64 string (so the content can be used in a multi-part post, or transmitted to a webservice as a base64 encoded string)

Edit by TaterJuice, 10-15-2017
Changelog:
1. Removed JavasCriptBlobToBase64StringConverter
2. Replaced default implementation of OpenFile dialogue, from ReadAsText to ReadAsDataURL (and fixed base64string implementation)
3. Modified FileOpenedEventArgs, removed PlainTextContent property, Added base64EncodedContentMetaData (JS DataURL Mime-Type string)

args.base64EncodedContentMetaData property format looks like this: "data:image/png;base64"
For more, see https://developer.mozilla.org/en-US/docs/Web/API/FileReader

namespace CSHTML5.Extensions.FileOpenDialog
{
    public class ControlForDisplayingTheFileOpenDialog : Control
    {
        public event EventHandler<FileOpenedEventArgs> FileOpened;

        public ControlForDisplayingTheFileOpenDialog()
        {
            CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this,
                "<input type='file' style='font-size: 0px; width: 100%; height: 100%; opacity: 0; filter: alpha(opacity = 0);' />");

            this.Loaded += ControlForDisplayingAFileOpenDialog_Loaded;
        }
        void ControlForDisplayingAFileOpenDialog_Loaded(object sender, RoutedEventArgs e)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            Action<string, string, string> onFileOpened = (base64encodedContent, mimetypeString, filename) =>
            {
                if (this.FileOpened != null)
                    this.FileOpened(this, new FileOpenedEventArgs(base64encodedContent, mimetypeString, filename));
            };

            // Apply the "Filter" property to limit the choice of the user to the specified extensions:
            SetFilter(this.Filter);

            // Listen to the "change" property of the "input" element, and call the callback:
            CSHTML5.Interop.ExecuteJavaScript(@"
                $0.addEventListener(""change"", function(e){
                    if(!e){
                      e = window.event;
                    }
                    var fullPath = $0.value;
                    var filename = '';
                    if (fullPath) {
                        var startIndex = (fullPath.indexOf('\\') >= 0 ? fullPath.lastIndexOf('\\') : fullPath.lastIndexOf('/'));
                        filename = fullPath.substring(startIndex);
                        if (filename.indexOf('\\') === 0 || filename.indexOf('/') === 0) {
                            filename = filename.substring(1);
                        }
                    }
                    var input = e.target;
                    var file = input.files[0];
                    var reader = new FileReader();
                    reader.onload = function(){
                      var dataUrl = reader.result;
                      var mimeTypeString = dataUrl.split(',')[0];
                      var base64 = dataUrl.split(',')[1];
                      $1(base64, mimeTypeString, filename);
                    };
                    //reader.readAsText(file);
                    reader.readAsDataURL(file);
                    var isRunningInTheSimulator = $2;
                    if (isRunningInTheSimulator)
                        alert(""The file open dialog is not supported in the Simulator. Please test in the browser instead."");
                });", inputElement, onFileOpened, CSHTML5.Interop.IsRunningInTheSimulator);
        }

        void SetFilter(string filter)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            // Process the filter list to convert the syntax from XAML to HTML5:
            // Example of syntax in Silverlight: Image Files (*.bmp, *.jpg)|*.bmp;*.jpg|All Files (*.*)|*.*
            // Example of syntax in HTML5: .gif, .jpg, .png, .doc
            string[] splitted = filter.Split('|');
            List<string> itemsKept = new List<string>();
            if (splitted.Length == 1)
            {
                itemsKept.Add(splitted[0]);
            }
            else
            {
                for (int i = 1; i < splitted.Length; i += 2)
                {
                    itemsKept.Add(splitted[i]);
                }
            }
            string filtersInHtml5 = String.Join(",", itemsKept).Replace("*", "").Replace(";", ",");

            // Apply the filter:
            if (!string.IsNullOrWhiteSpace(filtersInHtml5))
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = $1", inputElement, filtersInHtml5);
            }
            else
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = """"", inputElement);
            }
        }

        public string Filter
        {
            get { return (string)GetValue(FilterProperty); }
            set { SetValue(FilterProperty, value); }
        }
        public static readonly DependencyProperty FilterProperty =
            DependencyProperty.Register("Filter", typeof(string), typeof(ControlForDisplayingTheFileOpenDialog), new PropertyMetadata("", Filter_Changed));

        static void Filter_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = (ControlForDisplayingTheFileOpenDialog)d;
            if (CSharpXamlForHtml5.DomManagement.IsControlInVisualTree(control))
            {
                control.SetFilter((e.NewValue ?? "").ToString());
            }
        }
    }

    public class FileOpenedEventArgs : EventArgs
    {
        public readonly string FileName;

        public readonly string base64EncodedContent;

        public readonly string base64EncodedContentMetaData;

        public FileOpenedEventArgs(string base64content, string mimetypeString, string filename)
        {
            this.FileName = filename;
            this.base64EncodedContent = base64content;
            this.base64EncodedContentMetaData = mimetypeString;
        }
    }
}




Now you can use the control like this:

Code: Select all

<Page
    ...
    xmlns:extensions="using:CSHTML5.Extensions.FileOpenDialog"
    ...
>
    <Canvas>
        <extensions:ControlForDisplayingTheFileOpenDialog FileOpened="OnFileOpened" Filter="*.txt,*.csv"/>
    </Canvas>
</Page>


And add this to your code-behind:

Code: Select all

async void OnFileOpened(object sender, CSHTML5.Extensions.FileOpenDialog.FileOpenedEventArgs e)
{
    MessageBox.Show(String.Format("Filename = {0}", e.FileName));
    MessageBox.Show(String.Format("MimeTypeString = {0}", e.base64EncodedContentMetaData));
    MessageBox.Show(String.Format("Base64 Content = {0}", e.base64EncodedContent));
}

JS-Support @Userware
Site Admin
Posts: 1142
Joined: Tue Apr 08, 2014 3:42 pm

Re: FileOpenDialog Extension for CSHTML5

Postby JS-Support @Userware » Sun Oct 15, 2017 11:46 pm

Thanks a lot TaterJuice!

JS-Support @Userware
Site Admin
Posts: 1142
Joined: Tue Apr 08, 2014 3:42 pm

Re: FileOpenDialog Extension for CSHTML5

Postby JS-Support @Userware » Thu Nov 16, 2017 8:46 am

UPDATE: The FileOpenDialog code of the original post has just been updated with the following changes:
- Added support for getting a "DataURI" from a binary file (useful for reading pictures for example) (thanks to TaterJuice)
- Added example of loading a picture (requires v1.0 Beta 12.6 or newer)
- Added "FileName" property (thanks to TaterJuice)
- Added code for displaying an "OpenFile" ChildWindow

Thanks.
Regards,
JS-Support

fangeles
Posts: 52
Joined: Wed Jan 16, 2019 12:48 am

Re: FileOpenDialog Extension for CSHTML5 [BUG]

Postby fangeles » Thu Apr 04, 2019 9:10 pm

The web browser is detected as "Simulator" by the OpenFileDialog in Bridge version.


Is it on the CSHTML5 v2.0 or the FileOpenDialog Extension?

Thanks.

User avatar
rthomas
Posts: 10
Joined: Mon Feb 18, 2019 7:03 am
Location: Lille, France

Re: FileOpenDialog Extension for CSHTML5

Postby rthomas » Tue May 07, 2019 9:21 am

Hi,

Another fork with XAML / Web compatible code.
This mean you can use it to create exe or html version and it behaves the same.

XAML didn't change.

Remi

Code: Select all

using System;
using System.Collections.Generic;
#if !CSHTML5
using System.Windows;
using System.Windows.Controls;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#endif

//------------------------------------
// This is an extension for C#/XAML for HTML5 (www.cshtml5.com)
//
// It requires Beta 7.2 or newer.
//
// It adds the ability to let the user pick a file using the
// "Open File..." dialog of the browser.
//
// It can also be used for example in conjunction with the ZipFile
// Extension to load a ZIP file. Note: if you are looking for
// a way to save files to disk, you can use the FileSaver Extension.
//
// The extension works by adding an <input type='file'> tag to the
// HTML DOM, which will cause the "Open file..." dialog to appear,
// and by listening to its "change" event in order to get a reference
// to the selected file. it returns a JavaScript blob. If the file
// is a plain text file, it will also return a string with the text
// content of the file.
//
// This extension is licensed under the open-source MIT license:
// https://opensource.org/licenses/MIT
//
// Copyright 2017 Userware / CSHTML5
//------------------------------------

namespace qfWeb
{
    public enum ResultKind
    {
        Text, DataURL
    }
    public class ControlForDisplayingTheFileOpenDialog : UserControl
    {
        public event EventHandler<FileOpenedEventArgs> FileOpened;

        private ResultKind _resultKind;
        private string _resultKindStr;
        public ResultKind ResultKind
        {
            get { return _resultKind; }
            set
            {
                _resultKind = value;
                _resultKindStr = value.ToString();
            }
        }

        public ControlForDisplayingTheFileOpenDialog()
        {
            this.ResultKind = ResultKind.Text; //Note: this is to set the default value of the property.
#if CSHTML5
            CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this,
                "<input type='file'>");
#endif
            this.Loaded += ControlForDisplayingAFileOpenDialog_Loaded;
        }

#if CSHTML5
        public string Filter
        {
            get { return (string)GetValue(FilterProperty); }
            set { SetValue(FilterProperty, value); }
        }
        void ControlForDisplayingAFileOpenDialog_Loaded(object sender, RoutedEventArgs e)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            Action<object> onFileOpened = (result) =>
            {
                if (this.FileOpened != null)
                    this.FileOpened(this, new FileOpenedEventArgs(result, this.ResultKind));
            };
            string resultKindStr = _resultKindStr; // Note: this is here to "capture" the variable so that it can be used inside the "addEventListener" below.

            // Apply the "Filter" property to limit the choice of the user to the specified extensions:
            SetFilter(this.Filter);

            // Listen to the "change" property of the "input" element, and call the callback:
            CSHTML5.Interop.ExecuteJavaScript(@"
                $0.addEventListener(""change"", function(e) {
                    if(!e) {
                      e = window.event;
                    }
                    var fullPath = $0.value;
                    var filename = '';
                    if (fullPath) {
                        var startIndex = (fullPath.indexOf('\\') >= 0 ? fullPath.lastIndexOf('\\') : fullPath.lastIndexOf('/'));
                        filename = fullPath.substring(startIndex);
                        if (filename.indexOf('\\') === 0 || filename.indexOf('/') === 0) {
                            filename = filename.substring(1);
                        }
                    }
                    var input = e.target;
                    var file = input.files[0];
                    var reader = new FileReader();
                    reader.onload = function() {
                      var callback = $1;
                      var result = reader.result;
                      //if (file.type == 'text/plain')
                      callback(result);
                    };
                    var resultKind = $3;
                    if (resultKind == 'DataURL') {
                      reader.readAsDataURL(file);
                    }
                    else {
                      reader.readAsText(file);
                    }
                    var isRunningInTheSimulator = $2;
                    //if (isRunningInTheSimulator) {
                    //    alert(""The file open dialog is not supported in the Simulator. Please test in the browser instead."");
                    //}
                });", inputElement, onFileOpened, CSHTML5.Interop.IsRunningInTheSimulator, resultKindStr);
        }

        void SetFilter(string filter)
        {
            // Get the reference to the "input" element:
            var inputElement = CSHTML5.Interop.GetDiv(this);

            // Process the filter list to convert the syntax from XAML to HTML5:
            // Example of syntax in Silverlight: Image Files (*.bmp, *.jpg)|*.bmp;*.jpg|All Files (*.*)|*.*
            // Example of syntax in HTML5: .gif, .jpg, .png, .doc
            string[] splitted = filter.Split('|');
            List<string> itemsKept = new List<string>();
            if (splitted.Length == 1)
            {
                itemsKept.Add(splitted[0]);
            }
            else
            {
                for (int i = 1; i < splitted.Length; i += 2)
                {
                    itemsKept.Add(splitted[i]);
                }
            }
            string filtersInHtml5 = String.Join(",", itemsKept).Replace("*", "").Replace(";", ",");

            // Apply the filter:
            if (!string.IsNullOrWhiteSpace(filtersInHtml5))
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = $1", inputElement, filtersInHtml5);
            }
            else
            {
                CSHTML5.Interop.ExecuteJavaScript(@"$0.accept = """"", inputElement);
            }
        }

        public static readonly DependencyProperty FilterProperty =
            DependencyProperty.Register("Filter", typeof(string), typeof(ControlForDisplayingTheFileOpenDialog), new PropertyMetadata("", Filter_Changed));

        static void Filter_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = (ControlForDisplayingTheFileOpenDialog)d;
            if (CSharpXamlForHtml5.DomManagement.IsControlInVisualTree(control))
            {
                control.SetFilter((e.NewValue ?? "").ToString());
            }
        }
#else
        public string Filter { get; set; }

        TextBlock m_fileName = new TextBlock() { Text = "No file chosen", Margin = new Thickness(5,0,0,0) };
        void ControlForDisplayingAFileOpenDialog_Loaded(object sender, RoutedEventArgs e)
        {
            StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal };
            Button b = new Button();
            TextBlock label = new TextBlock() { Text = "Choose file" };
            b.Content = label;
            b.Click += B_Click;
            sp.Children.Add(b);
            sp.Children.Add(m_fileName);
            AddChild(sp);
        }

        private void B_Click(object sender, RoutedEventArgs e)
        {
            string[] filters = Filter.Split(',');
            string newFilter = "";
            string sep = "";
            foreach (string f in filters)
            {
                newFilter += sep + f.Replace("*.", "") + " files|" + f;
                sep = "|";
            }
            // newFilter = "Image files (*.bmp, *.jpg)|*.bmp;*.jpg|All files (*.*)|*.*";
            Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog() { Filter = newFilter };
            if (ofd.ShowDialog().GetValueOrDefault())
            {
                if (FileOpened != null)
                {
                    m_fileName.Text = System.IO.Path.GetFileName(ofd.FileName);
                    FileOpenedEventArgs foea = null;
                    if (ResultKind == ResultKind.Text)
                        foea = new FileOpenedEventArgs(System.IO.File.ReadAllText(ofd.FileName), ResultKind);
                    else if (ResultKind == ResultKind.DataURL)
                        foea = new FileOpenedEventArgs(Convert.ToBase64String(System.IO.File.ReadAllBytes(ofd.FileName)), ResultKind);
                    FileOpened(this, foea);
                }
            }
        }
#endif
    }
    public class FileOpenedEventArgs : EventArgs
    {
        /// <summary>
        /// Only available if the property "ResultKind" was set to "Text".
        /// </summary>
        public readonly string Text;

        /// <summary>
        /// Only available if the property "ResultKind" was set to "DataURL".
        /// </summary>
        public readonly string DataURL;

        public FileOpenedEventArgs(object result, ResultKind resultKind)
        {
            if (resultKind == ResultKind.Text)
            {
                this.Text = (result ?? "").ToString();
            }
            else if (resultKind == ResultKind.DataURL)
            {
                this.DataURL = (result ?? "").ToString();
            }
            else
            {
                throw new NotSupportedException();
            }
        }
    }
}

ScottM
Posts: 38
Joined: Wed Mar 27, 2019 7:04 am

Re: FileOpenDialog Extension for CSHTML5

Postby ScottM » Mon May 13, 2019 2:27 pm

I've been playing with implementing this, and noticed an oddity, I think. With all three sets of code (original revised, TaterJuice revised, and rthomas version), when I run in the simulator (2.0.0-alpha25-040), the filter is set properly:

FileOpenSimulator.png
FileOpenSimulator.png (50.07 KiB) Viewed 42021 times


But when I run in the browser (in my case, Edge), the filter is not respected.

FileOpenEdge.png
FileOpenEdge.png (70.08 KiB) Viewed 42021 times


Have I blundered? Or is Edge a problem?

Thanks in advance for any pointers.

ScottM
Posts: 38
Joined: Wed Mar 27, 2019 7:04 am

Re: FileOpenDialog Extension for CSHTML5

Postby ScottM » Fri May 17, 2019 12:06 pm

Following on to the above, I do, filter issue notwithstanding, have the file contents available.

Now, what to do with it? I'd like, if possible, to do two things:

1. Send an email with form responses and the uploaded file attached.

2. Save the uploaded file and form responses to a folder on the server.

I realize I'm asking for more server-side integration than the thick client, but I thought I would find examples here in the forum on how to do that. I ran several searches, but came up empty.

Any suggestions?

ETA: I looked for MailMessage that's in the WPF stack, but no dice here, unless I am missing something.


Return to “Extensions and Plugins for CSHTML5”

Who is online

Users browsing this forum: No registered users and 1 guest