accessing local files?

Monkey Targets Forums/HTML5/accessing local files?

Paul - Taiphoz(Posted 2017) [#1]
don't suppose anyone has any code for reading text files off of the local harddrive?


Xaron(Posted 2017) [#2]
That won't work in HTML5 because of this sandbox protection. HTML5 apps are simply not allowed to access anything outside as far as I know.


Beaker(Posted 2017) [#3]
It can be done, but it has to happen under user control (they have to click a button and select the file) and then you might have to upload it to the same server as your app before you can access it (from there). I can't remember the exact details.

More here:
http://www.w3schools.com/jsref/dom_obj_fileupload.asp
Also, search for FileReader.


MonkeyPlotter(Posted 2017) [#4]
That won't work in HTML5 because of this sandbox protection. HTML5 apps are simply not allowed to access anything outside as far as I know.


I'm reading text files okally dokally.... in HTML5, looking for an example for you now...

You don't need to upload the files to a server, they can be accessed from the user's device within the following example. As Beaker says, you need to select a 'browse' button first.... in my example below anyhow.

[EDIT] There might be a bit much to chew on, but this code will load three separate 'text' files and do a bit of data parsing:

(incidentally, I was trying to load an image of the user's choice using the following code. After a bit of digging it appears as though MonkeyX creates a small database of images at compile time - I haven't been able to find a work around to achieve my goal of loading a users image after compiling and have given up on that for now.... hope the following helps you out anyways ;)

Strict

' Preprocessor related:
#MOJO_AUTO_SUSPEND_ENABLED = False

' Imports:
Import mojo

Import brl.databuffer
Import brl.datastream

' Check if we're using HTML5:
#If TARGET = "html5"
	Import dom
	
	Import "native/file_to_databuffer.js"
	
	' External bindings (JavaScript):
	Extern
	
	' Functions:
	
	' File-to-DataBuffer:
	Function LoadFile:DataBuffer(F:File, B_Out:DataBuffer)="loadFile"
	
	' DOM:
	Function log:Void(e:Event) = "window.console.log"
	Function log:Void(f:FileList) = "window.console.log"
	Function log:Void(f:File) = "window.console.log"
	'Function log:Void(o:Object) = "window.console.log"
	
	' Classes:
	Class File
		' Fields:
		Field lastModified:Int
		Field lastModifiedDate:String
		Field name:String
		Field size:Int
		Field fileName:String
		Field fileSize:Int
		Field type:String
	End
	
	Class FileList Extends DOMObject
		' Fields:
		Field length:Int
		
		' Methods:
		Method item:File(index:Int)
	End
	
	Class HTMLFileInputElement Extends HTMLInputElement = "HTMLInputElement"
		' Fields:
		Field files:FileList
	End
	
	Public	
	
	Class AllDataHolder
		
		' Globals
	
		Field AllDataList:String[]
		
	End
	
	
	
	' Classes:
	Class EventRepeater Extends EventListener
		' Constructor(s):
		Method New(Callback:EventHandler)
			Self.Callback = Callback
		End
		
		' Methods:
		Method handleEvent:Int(event:Event)
			Return Callback.HandleEvent(event)
		End
		
		' Fields:
		Field Callback:EventHandler
	End	
	
	' Functions:
	Function AddFileRequester:HTMLFileInputElement(listener:EventListener, node:dom.Node)
		Local input:= HTMLFileInputElement(document.createElement("input")) ' HTMLInputElement
		
		input.type = "file"
		input.addEventListener("change", listener)
		
		node.appendChild(input)
		
		Return input
	End
	
	Function AddButton:HTMLInputElement(name:String, listener:EventListener, node:dom.Node)
		Local button:= document.createElement("input")
		
		button.setAttribute("type", "button")
		button.setAttribute("name", name)
		button.setAttribute("value", name)
		button.addEventListener("click", listener)
		
		node.appendChild(button)
		
		Return HTMLInputElement(button)
	End
#Else
	#Error "Please build this application with the HTML5 target."
#End

' Interfaces:
Interface EventHandler
	' Methods:
	Method HandleEvent:Int(event:Event)
End

' Classes:
Class FileApp Extends App Implements EventHandler
	' Constant variable(s):
	Const FILES_NEEDED:= 3
	
	' Methods:
	Method OnCreate:Int()
		SetUpdateRate(30) ' 0 ' 60
		
		Self.running = False
		Self.filesQueued = 0
		
		' This is a dummy object used to get around the limitations of DOM:
		Self.repeater = New EventRepeater(Self)
		
		Self.bodyNode = dom.Node(Element(document.getElementsByTagName("body").item(0)).getElementsByTagName("div").item(0))
		
		Self.fileButtons = New HTMLFileInputElement[FILES_NEEDED]
		Self.files = New DataBuffer[FILES_NEEDED] ' Self.fileButtons.Length
		
		For Local I:= 0 Until fileButtons.Length ' FILES_NEEDED
			Self.fileButtons[I] = AddFileRequester(repeater, bodyNode)
		Next
		
		#If CONFIG = "debug"
			MakeRunButton()
		#End
		
		Return 0
	End
	
	'============  Start of the Event handler - how do I invoke a file requester associated
	'============  with one of 'my buttons' (as opposed to the procedurally generated 'Browse'
	'============  button ........... - Specifically trying to associate a file requester
	'============  with the 'LoadMyMap' button.
	
	Method HandleEvent:Int(event:Event)
		Select event.type
			Case "change"
				Local fileButton:= GetFileButton(event.target)
				
				If (fileButton <> Null And files.Length > filesQueued) Then
					Local f:File = fileButton.files.item(0)
					
					Print("New file: ~q" + f.name + "~q - " + f.size + " bytes")
					
					files[filesQueued] = LoadFile(f, New DataBuffer())
					
					filesQueued += 1
				Endif
			Case "click"
				If (event.target = runButton) Then
					'Local runButton:= Self.runButton
					
					OnRunButtonPressed()
					
				Else If (event.target = LoadMyMapButton)
				
					OnLoadMyMapButtonPressed()
					
				Else If (event.target = scaleUpButton)
				
					OnScaleUpButtonPressed()
					
				Else If (event.target = scaleDownButton)
				
					OnScaleDownButtonPressed()
					
				Endif
				
			End Select
			
			Return 1
		End
		
		
		Method OnFilesLoaded:Void()
		If (runButton <> Null) Then
			Return
		Endif
		
		'Calls to generate required buttons
		'Note: Require Bool to do this ONCE
		
		If generateButtons = 0
		
		generateButtons = 1
		
		MakeLoadMyMapButton()
		
		MakeScaleUpButton()
		
		MakeScaleDownButton()
		
		Endif	
		
	End
	
		
	'===============Load My Image in button
	Method MakeLoadMyMapButton:Void()
							
		LoadMyMapButton = AddButton("Load My Map", repeater, bodyNode)
							
		Return
	
	End
						
	Method OnLoadMyMapButtonPressed:Void()
						
		Print("Map Picker Button Pressed: TO DO, suss how to open a file browser to pick image from users device....")
									
		'-------------  Attempt to open a file browser
									
		'Local fileButton:= GetFileButton(event.target)
									
		'If (fileButton <> Null And files.Length > filesQueued) Then
		'	Local f:File = fileButton.files.item(0)
										
		'	Print("New file: ~q" + f.name + "~q - " + f.size + " bytes")
										
		'	files[filesQueued] = LoadFile(f, New DataBuffer())
										
		'	filesQueued += 1
		'Endif
		
				'===============map out button
									
	Return
					
	End
	
	Method OnScaleUpButtonPressed:Void()
	
		If ScaleFloat <= 20 Then ScaleFloat = ScaleFloat+0.2
		
				Print("Scale Up Button Pressed: ")+ScaleFloat
		
		Return
	End
	
	Method OnScaleDownButtonPressed:Void()
		
		If ScaleFloat >= 0.2 Then ScaleFloat = ScaleFloat-0.2
		
				If ScaleFloat <= 0.2 Then ScaleFloat = ScaleFloat-0.05
		
				Print("Scale Down Button Pressed: ")+ScaleFloat
						
		Return
		
	End
	
	Method MakeScaleUpButton:Void()
		'If (scaleUpButton <> Null) Then
		'	Return
		'Endif
		
		scaleUpButton = AddButton("Zoom Image In", repeater, bodyNode)
		
		Return
	End
	
	Method MakeScaleDownButton:Void()
		'If (scaleDownButton <> Null) Then
		'	Return
		'Endif
		
		scaleDownButton = AddButton("Zoom Image Out", repeater, bodyNode)
		
		Return
	End
	
	Method OnRunButtonPressed:Void()
		If (running Or Not AllFilesLoaded) Then
			Return
		Endif
		
		running = True
		
		Print("Starting the application properly.")
		
		Return
	End
	
	Method MakeRunButton:Void()
		If (runButton <> Null) Then
			Return
		Endif
		
		runButton = AddButton("Show the data racing", repeater, bodyNode)
		
		Return
	End
	
	
	
	Method GetFileButton:HTMLFileInputElement(target:EventTarget)
		For Local I:= 0 Until fileButtons.Length
			If (fileButtons[I] = target) Then ' EventTarget(...)
				Return fileButtons[I]
			Endif
		Next
		
		Return Null
	End
	
	Method OnUpdate:Int()
		If (Not running) Then
			If (AllFilesLoaded) Then
				OnFilesLoaded()  'calling the readOnce within OnFilesLoaded.....

			Endif
			
			Return 0
		Endif	
		
		If (MouseHit(MOUSE_LEFT)) Then
		
			'If (Not readOnce)
		
			'ReadLoadedFiles()
			
			'If readOnce=True
			'Print "returned from ReadLoaded Files, readOnce is: TRUE"
			'Endif
			
			
			'If (Not running)

			'Print("Parsing Files Now.... Please Wait.")
			'ParseLoadedFiles()
			'DebugParsedFiles()

			'Else
			'	Print ("Already parsed loaded files...")
			'	Print ("So what is calling the parser again....")
			'Endif
			
			'Endif
			
			DrawText("Detected left mouse hit",8.0,32.0)
			
			
		Endif
		
		If (running And readOnce)
						DisplayParsedFiles()
						
						'Print "Called DisplayParsedFiles()..."
						
		Else
			If running
				Print ("DEBUG: running is: TRUE")
			Else
				Print("DEBUG: runing is FALSE")
			Endif
		
			If readOnce
				Print ("DEBUG: readonce is TRUE")
			Else
				Print ("DEBUG: readonce is FALSE")						
			Endif
						
		Endif
		
		
		
		Return 0
	End
	
	Method OnRender:Int()
		Cls()
		
		'###########################
		'this is the issue I have with footprints - the rendering of the square has to happen here!!!!
		'no biggy really.....
		
		'2 Nov 16: sorting footprints to go in correct cardinal directions
		'##########################
		
		Local i:Int = 1
		
		Local xarray:Int[5000]
		Local yarray:Int[5000]
		
		Local xct:Int = 1
		Local yct:Int = 1
		
		Local plotredfoots:Int = 1
		
		Local howlong:Int = 1
		
		If (Not running) Then
			If (AllFilesLoaded) Then
				DrawText("All files have been loaded.", 8.0, 8.0)
				
				DrawText("Now to attempt to load and display an image for subsequent manipulation", 8.0, 19.0)
				
			Endif
			
			DrawImage map,xmap,ymap,MapRotate,MapZoom,MapZoom
		
		Return 0
		
		Else
		' This basically never happens.
		DrawText("Waiting for TCX files to be uploaded / imported; " + FilesCompleted + " completed.", 8.0, 8.0)
		
		
		Endif
		
		Return 0
		
	End
	
	
	Method ReadLoadedFiles:Void()
		For Local I:= 0 Until files.Length
			Local buffer:= files[I]
			Local inputStream:= New DataStream(buffer)
			
			Print("")
			Print("Parsing file #" + (I+1) + ":")
			Print("Last file length was:" +(lastNumberOfLines))
			Print("")	
			
			While (Not inputStream.Eof())
				' Read the current line.
				Local line:= inputStream.ReadLine()
				
				'Print("Line: " + line)
				
				'Print("     1_3_5_7_9_1_3_5_9_1_3_5_9_1_3_5_9")
				
				'Copy line into an array for parsing
		
				AllDataList = AllDataList.Resize(AllDataList.Length() + 1)
				AllDataList[AllDataList.Length() - 1] = line
				numberOfLines = numberOfLines + 1
				
				'required to store where file 2 and file 3 start within the AllDataList buffer
				lastNumberOfLines = numberOfLines
				
				If I = 0
					
					fileOneEnd = numberOfLines
				
				
				Endif
				
				If I = 1
				
					fileTwoEnd = numberOfLines
				
				Endif
				
				If I = 2
				
					fileThreeEnd = numberOfLines
				
				endif

			Wend
			
			inputStream.Close()
			
			Print "fileEndCounts -----------"
			Print "fileOneEnd = " + fileOneEnd
			Print "fileTwoEnd = " + fileTwoEnd
			Print "fileThreeEnd = " + fileThreeEnd
				
			readOnce = True
			
		Next
		
		Print(" ")
		Print("========== End of parsing files===========")
		Print("  Number of lines read:  "+numberOfLines)
		Print(" ")
		
		If readOnce=True
			Print ("In ReadLoadedFiles: readOnce is TRUE")
		Endif
		
		
		Return
	End
	
	
	Method ParseLoadedFiles:Void()
	
	
	End
	
	Method	DisplayParsedFiles:Void()
		
		'debug
		'If displayOnce
		
	End Method
	
	Method AllFilesLoaded:Bool() Property
		Return ((filesQueued = FILES_NEEDED) And FileBuffersLoaded)
	End
	
	Method FileBuffersLoaded:Bool() Property
		For Local I:= 0 Until files.Length
			Local file:= files[I]
			
			If (file <> Null And file.Length = 0) Then
				Return False
			Endif
		Next
		
		Return True
	End
	
	Method FilesCompleted:Int() Property
		Local count:= 0
		
		For Local I:= 0 Until files.Length
			Local file:= files[I]
			
			If (file <> Null And file.Length <> 0) Then
				count += 1
			Endif
		Next
		
		Return count
	End
	
'======================================================
' Fields
'======================================================
	
	Field fileButtons:HTMLFileInputElement[]
	Field runButton:HTMLInputElement
	
	Field scaleUpButton:HTMLInputElement
	Field scaleDownButton:HTMLInputElement
	
	Field LoadMyMapButton:HTMLInputElement
	
	Field running:Bool
	
	Field readOnce:Bool
	
	Field generateButtons:Int = 0
	
	Field AllDataList:String[]
	
	Field quitButton:HTMLInputElement
	
	Field bodyNode:dom.Node
	
	Field files:DataBuffer[]
	
	Field filesQueued:Int
	Field repeater:EventRepeater

	'Image Vars
		
	Field map:Image
	
	Global MapSwitch:Int=0
	Global MapRotate:Float=0.0
	Global MapZoom:Float=1.0

	Global xmap:Int=40
	Global ymap:Int=40
	Global ScaleFloat:Float=1.0
	
End

' Functions:
Function Main:Int()
	New FileApp()
	
	Return 0
End



you'll need to create a 'native' directory, at the same level as your monkey.data folder, with the following javascript function (file_to_databuffer.js) in it :--

function loadFile(file, buf)
{
	var reader = new FileReader();

	reader.onloadend = function ()
	{
		var rawData = reader.result;

		if (rawData == null)
		{
			return;
		}
		
		buf._Init(rawData);
	}

	reader.readAsArrayBuffer(file);
	
	return buf
}



The 'LoadMyMap' button doesn't do as advertised just yet - if anyone can help with this it'd be appreciated ;)


Paul - Taiphoz(Posted 2017) [#5]
bookmarked, will look at it when i hit that point thanks for posting this.