In AS3, load a text file into a string - actionscript-3

I've been trying to load a text file into a string variable. The text file named text.txt containing successful. Here's the code:
public class Main extends Sprite
{
private var text:String = "text";
private var textLoader:URLLoader = new URLLoader();
public function Main() {
textLoader.addEventListener(Event.COMPLETE, onLoaded);
function onLoaded(e:Event):void {
trace("Before 1: " + text); //output: text
trace("Before 2: " + e.target.data); //output: successful
text = e.target.data;
trace("After 1: " + text); //output: successful - yay!!! it worked
}
textLoader.load(new URLRequest("text.txt"));
trace("After 2: " + text); //output: text - what??? nothing happened??? but it just worked
}
}
Output:
After 2: text
Before 1: text
Before 2: successful
After 1: successful

You are facing a synchronous vs asynchronous problem
The function onLoaded is called asynchronously by textLoader when Event.COMPLETE is dispatched, as opposed to "After 2" which is called directly after textLoader.load.
What you must keep in mind is textLoader.load is non-blocking which means "After 2" is probably (you can assume always) executed before onLoaded.
If at this point of the answer you're still confused, I'd say that loading a file takes time and executing an instruction may vary in time but is mostly infinitely shorter than it takes to load a file (imagine this file is 4go large). You cannot predict what will happen, maybe the disk is already very busy you may need to wait! But you could use this precious time to do something else totally independant from the text file, and that is why it is sometimes made asynchronously by programming languages (php for example loads a file synchronously).
Next step
Now that I have explained that "After 2" does not really exist, you have to use "After 1"
as an entry point but nothing helps you from making a function named afterLoad which you would call like this
public function Main() {
textLoader.addEventListener(Event.COMPLETE, onLoaded);
function onLoaded(e:Event):void {
trace("Before 1: " + text); //output: text
trace("Before 2: " + e.target.data); //output: successful
text = e.target.data;
afterLoad();
}
textLoader.load(new URLRequest("text.txt"));
}
}
private function afterLoad():void {
trace("After: " + text); // it should work now :)
}

Related

how to load dynamic text sharedObject when flash is reopened in Actionscript 3?

I created a simple username page, in frame 1, there is a button and input text
like the code below
stop();
var SOlastFrame: SharedObject = SharedObject.getLocal("save_frame2");
var shared_data: String
next.addEventListener(MouseEvent.CLICK, gotomyNextFrame);
function gotomyNextFrame(e: MouseEvent): void {
shared_data = inputName.text
nextFrame()
SOlastFrame.data.lastframe = currentFrame;
SOlastFrame.flush();
}
if (SOlastFrame.data.lastframe != null) {
gotoAndStop(SOlastFrame.data.lastframe);
}
i saved the last frame by adding this code
SOlastFrame.data.lastframe = currentFrame;
SOlastFrame.flush();
so that I can jump to the last frame I opened, I added this code
if (SOlastFrame.data.lastframe != null) {
gotoAndStop(SOlastFrame.data.lastframe);
}
at frame 2, I put dynamic text
with code like this
var SOnameUser: SharedObject = SharedObject.getLocal("saveName");
SOnameUser.data.yourName = shared_data;
SOnameUser.flush();
trace(SOnameUser.data.yourName);
userName.text = "Hello " + shared_data;
if (shared_data != null) {
userName.text = shared_data;
SOnameUser.data.yourName = shared_data;
}
I think it works, dynamic text is saved successfully.
If I reopen it, it will go directly to frame 2 because of the execution result SOlastFrame.data.lastframe.
the problem is that the username that I saved earlier turns to null.
how can i load dynamic text on shareobject when SOlastFrame.data.lastframe is executed.
or
how to load sharedObject when flash is reopened
You get it like that because when you start 2-nd time you go straight to the frame 2 where the shared_data is empty. I think you should re-organize... well, everything.
// Frame 1: Splash (you don't have it).
// First of all, use a single SO rather than two.
var SO:SharedObject = SharedObject.getLocal("my.save");
// Check if there are saved fields.
if (SO.data.userName && SO.data.lastFrame)
{
// If there are saved credentials, the user
// won't even see the Login Frame.
gotoAndStop(SO.data.lastFrame);
}
else
{
// If there are no saved credentials — proceed to the Login Frame.
nextFrame();
}
Then, this is your frame 1.
// Frame 2: Login.
stop();
// You don't actually need to SO.flush() every time,
// it's a (feeble) measure against sudden crashes.
SO.data.lastFrame = currentFrame;
// Your code, for the most part, but simpler.
next.addEventListener(MouseEvent.CLICK, gotoNext);
function gotoNext(e:MouseEvent):void
{
// You don't need any additional variables
// to temporarily store the user name.
SO.data.userName = inputName.text;
nextFrame();
}
Then, here we go.
// Frame 3: only logged (via Login Frame or via SO data)
// users reach this point.
stop();
// In case we moved from the Login Frame.
SO.data.lastFrame = currentFrame;
// The only thing left to do.
userName.text = "Hello " + SO.data.userName;

Actionscript: SWF file does not record data to txt, while the compiler does

I am trying to create a psychological experiment in ActionScript. The performance of the participants is to be stored in a separate .csv file. I have written this code (apparently, instead of "This is the text to write" there is going to be a data array, but the problem appears with this code on equal parts)
import flash.net.FileReference;
import flash.events.Event;
var hint:String = "Please give a name for the file";
var labelling:String;
experiment_label.addEventListener(KeyboardEvent.KEY_UP, enter_pressed)
function enter_pressed (event:KeyboardEvent):void{
if (event.keyCode == 13) {
labelling = experiment_label.text;
addEventListener(Event.ENTER_FRAME,saveFile);
var ss:String = "this is text to write";
var fileRef:FileReference;
fileRef = new FileReference();
function saveFile(event:Event):void
{
fileRef.save(ss, labelling+".csv");
removeEventListener(Event.ENTER_FRAME, saveFile);
}
}
}
The problem I am facing is as follows: when I run it in from under Flash, it operates perfectly, and the save window does pop up. However, if I run an .swf file separately, it just ignores saving command.
Could you kindly suggest, what can I do? Or maybe I should use a different approach to saving altogether?
You current code will give you an Error #2176 (use a try ... catch to catch it) because FileReference.save()
is not called in response to a user action, such as a mouse event or keypress event.
To avoid that, you have to remove the Event.ENTER_FRAME event listener, even you don't need it :
function on_KeyUp(event:KeyboardEvent):void
{
if (event.keyCode == 13)
{
save_File();
}
}
function save_File():void
{
try
{
var fileRef:FileReference = new FileReference();
fileRef.save('some text here', 'my_file.csv');
}
catch (e:Error)
{
trace(e.toString());
}
}
Hope that can help.

how to get a function to activate which is ment to be used with

I have this code were I take from a external XML file a Link of an Image load it with ...
<mx:Label
id="textboxLink" text=""/>
private function loadRemoteImg(url:String):void {
textboxLink.text
.....
loader, completeHandler etc.
Save Image(), with ByteArray, JPEGEcoder and then to the location - filestream etc.
This works all fine yet it is only possible (due to supposedly Flash Player 10 onwards) by MouseEvent so of a Click of a button!
As mentioned it works all fine, but I really would need this to activate on start up like in a creationComplete or else!
Any help or any ideas would be appriciated! regards aktell
Ah, sorry, I thought you were just trying to load the image; I didn't see that you were trying to save it as well.
For both of the following cases, you'll need to load the image as Binary:
var urlLoader:URLLoader = new URLLoader(new URLRequest( myURL ));
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
...
This is because if we load it normally (using a Loader), then what we'll get is a Bitmap object, i.e. Flash's representation of your image, rather than the jpg/png data itself. Loading it using this method will give you a ByteArray when it's loaded.
If you're using AIR, you should be able to use the FileStream class: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/filesystem/FileStream.html
Something like:
// NOTE: you can use any of the other File constants to get your path (e.g.
// documentsDirectory, desktopDirectory...
// myImageFileName is the name of your image, e.g. "myPic.jpg"
var file:File = File.applicationStorageDirectory.resolvePath( myImageFileName );
var fs:FileStream = new FileStream;
try
{
fs.open( file, FileMode.WRITE );
fs.writeBytes( imageBinaryData ); // imageBinaryData is a ByteArray
fs.close();
}
catch ( e:Error )
{
trace( "Can't save image: " + e.errorID + ": " + e.name + ": " + e.message );
}
If you're using Flash, then the only way you can save something without user interaction, is through a SharedObject. This will mean that you data won't be available outside the app (it'll be a .sol file), but depending on how you're doing it, this might not be a problem.
// get our shared object - if you're saving a lot of images, then you might need another shared
// object, whose name you know, that stores the names of the other images
var so:SharedObject = null;
try { so = SharedObject.getLocal( this.m_name ); }
catch ( e:Error )
{
trace( "Couldn't get shared object: " + e.errorID + ": " + e.name + ": " + e.message );
return;
}
// NOTE: it's possible you may need a registerClassAlias on the ByteArray before this
so.data["image"] = imageBinaryData;
// save the lso
try { so.flush( minDiskSpace ); }
catch ( e:Error )
{
trace( "Couldn't save the lso: " + e.errorID + ": " + e.name + ": " + e.message );
}
To actually use your image later, load your file(in binary mode)/get your SharedObject, and convert the saved binary to an image:
var l:Loader = new Loader;
l.contentLoaderInfo.addEventListener( Event.COMPLETE, this._onConvertComplete ); // you could probably also listen for the IO_ERROR event
try
{
// NOTE: you can pass a LoaderContext to the loadBytes methods
l.loadBytes( imageBinaryData );
}
catch( e:Error )
{
trace( "Couldn't convert image: " + e.errorID + ": " + e.name + ": " + e.message );
}
...
// called when our loader has finished converting our image
private function _onConvertComplete( e:Event ):void
{
// remove the event listeners
var loaderInfo:LoaderInfo = ( e.target as LoaderInfo );
loaderInfo.removeEventListener( Event.COMPLETE, this._onConvertComplete );
// get our image
var bitmap:Bitmap = loaderInfo.content;
this.addChild( bitmap );
}
If you can't use any of those methods, then you're going to have to have some sort of user interaction (e.g. mouse click). (Out of curiosity, have you tried just dispatching a MouseEvent.CLICK on the relevant object, to see if it would work?)

Execution order of GS files in a Project

Where can I read documentation concerning the execution order rules for GS files?
To dimension the problem I created two trivial objects, each in their own file.
1_File.gs
var ObjB = new Object();
ObjB.sayName = "[" + ObjA.sayName + "]";
0_File.gs
var ObjA = new Object();
ObjA.sayName = " I'm A ";
A call such as ...
Logger.log(ObjA.sayName + " : " + ObjB.sayName);
... gets the error ...
TypeError: Cannot read property "sayName" from undefined.
If I move the code from 1_File.gs into 0_File.gs, and vice versa, then there is no error and the log shows correctly ...
I'm A : [ I'm A ]
Renaming 0_File.gs to 2_File.gs doesn't affect execution order either, so I assume that order depends on which file gets created first.
Is there no concept of "include" or "import" that would allow me to make order of execution explicit?
Where can I read documentation concerning the execution order rules for GS files?
There is no such documentation and I think will not be any time published. In similar way, an initialization order of the static variables in C++ is also undefined and depends on compiler/linker.
Is there no concept of "include" or "import" that would allow me to make order of execution explicit?
Yes, there is no "includes", "imports" and even "modules", but there are libraries.
Also there is a workaround by using a closure. Bellow is a sample code. By executing the test function the log contains c.d. The idea is to have in all gs files a function started with init. In these functions all global variables are instanced. The anonymous closure is executed during the Code.gs file instancing and calls all "init" functions of all gs files.
Code.gs
var c;
function callAllInits_() {
var keys = Object.keys(this);
for (var i = 0; i < keys.length; i++) {
var funcName = keys[i];
if (funcName.indexOf("init") == 0) {
this[funcName].call(this);
}
}
}
(function() {
callAllInits_();
c = { value : 'c.' + d.value };
})();
function test() {
Logger.log(c.value);
}
d.gs
var d;
function initD() {
d = { value : 'd' };
};
I tackled this problem by creating a class in each file and making sure that each class is instantiated in the original Code.gs (which I renamed to _init.gs). Instantiating each class acts as a form of include and makes sure everything is in place before executing anything.
_init.gs:
// These instances can now be referred to in all other files
var Abc = new _Abc();
var Menu = new _Menu();
var Xyz = new _Xyz();
var Etc = new _Etc();
// We need the global context (this) in order to dynamically add functions to it
Menu.createGlobalFunctions(this);
function onInstall(e) {
onOpen(e);
}
function onOpen(e) {
Menu.build();
}
And classes usually look like this:
menu.gs:
function _Menu() {
this.build = function() {
...
}
...
}
If you have more than one level of inheritance, you need to give the init functions names like init000Foo, init010Bar, and init020Baz, and then sort the init functions by name before executing. This will ensure init000Foo gets evaluated first, then Bar, then Baz.
function callAllInits() {
var keys = Object.keys(this);
var inits = new Array();
for (var i = 0; i < keys.length; i += 1) {
var funcName = keys[i];
if (funcName.indexOf("init") == 0) {
inits.push(funcName);
}
}
inits.sort();
for (var i = 0; i < inits.length; i += 1) {
// To see init order:
// Logger.log("Initializing " + inits[i]);
this[inits[i]].call(this);
}
}
The other answers (i.e., don't write any top-level code which references objects in other files) describe the ideal way to avoid this problem. However, if you've already written a lot of code and rewriting it is not feasible, there is a workaround:
Google App Script appears to load code files in the order they were created. The oldest file first, followed by the next, and the most recently created file last. This is the order displayed in the editor when "Sort files alphabetically" is unchecked.
Thus, if you have the files in this order:
Code.gs
1_File.gs (depends on 0_File.gs)
0_File.gs
An easy fix is to make a copy of 1_File.gs and then delete the original, effectively moving it to the end of the list.
Click the triangle next to 1_File.gs and select "Make a copy"
Code.gs
1_File.gs
0_File.gs
1_File copy.gs
Click the triangle next to 1_File.gs and select "Delete"
Code.gs
0_File.gs
1_File copy.gs
Click the triangle next to 1_File copy.gs and select "Rename", then remove the " copy" from the end.
Code.gs
0_File.gs
1_File.gs
Now 0_File.gs is loaded before 1_File.gs.
This works for me as of December 2021. Quite likely, the other answers are outdated.
You can easily fix this. When you look at the scripts in the "Files" section of the web editor, you see they have an order. Files are evaluated in the order they appear there. Clicking on the three dots to the right of a file name brings up a menu that allows you to move a file up or down.
There is no such order in Google Apps Script. It purely depends on where you have these objects declared and how your function is invoked.
Can you explain a bit about how and when your Logger.log() code will be invoked.
Also, when do you declare your objects objA and objB ?
These will help us provide a better answer
here is how I would do this...
main
function include(filename) {
return ContentService.createTextOutput(filename);
}
function main() {
include('Obj A');
include('Obj B');
Logger.log(ObjA.sayName + " : " + ObjB.sayName);
}
Obj A
var ObjA = new Object();
ObjA.sayName = " I'm A ";
Obj B
var ObjB = new Object();
ObjB.sayName = "[" + ObjA.sayName + "]";

JSLint writing constructors that reference static variables

I'm writing a display class in Javascript (using jQuery) which may be instantiated before a web page has loaded. If the page isn't ready when the constructor is called, the instance is added to a static instances field for the class, which is iterated over when the page has loaded:
function MemDisplay(ready_callback) {
this.readyCallback = ready_callback;
if (MemDisplay.ready) {
this.linkToPage();
} else {
MemDislay.instances.push(this);
}
}
//this makes sure that the ready callback can be sent when the page has loaded
MemDisplay.ready = false;
MemDisplay.instances = [];
$(document).ready(function () {
var i;
MemDisplay.ready = true;
for (i = 0; i < MemDisplay.instances.length; i += 1) {
MemDisplay.instances[i].linkToPage();
} });
//example truncated for brevity
When I run this through JSLint, I get this error:
Problem at line 25 character 9:
'MemDislay' is not defined.
MemDislay.instances.push(this);
I need to reference MemDisplay.instances in the constructor, but the constructor is where MemDisplay is defined, so I'm puzzled about how to make this work while fitting within JSLint's guidelines. Is there a better way to do this? Should I just ignore JSLint in this instance?
JSLint here is actually highlighting a broader issue with the code without saying so.
You are referencing a class (MemDisplay) but never instantiating it as an object. I.e. you are treating the class like an already-instantiated object.
I've created a very simple equivalent to what you are trying to achieve (also at this JSFiddle)
function MyClass(p1, p2){
this.param1 = p1; //class member/property - use this to access internally.
if (this.param1 === 1){ //you might want to consider doing this as part of some setter method
alert("test");
}
this.MyMethod = function(){ //class method/function
alert("MyMethod Called");
};
}
var myObj = new MyClass(1,2); //instantiate
alert(myObj.param1); //get value of object member (you can set as well)
myObj.MyMethod(); //call a method
It'll take a bit of reorgansiation, but by declaring the values up front, you can get make JSLint happy.
My brain must have figured this out while I slept: the trick is to attach the field to the prototype, which seems pretty obvious now that I've thought of it, since that's what you have to do to define class methods.
The following checks out in JSLint, and demonstrates the sharing of a field between all instances of MyClass (or see this code on jsfiddle):
/*global alert */
function MyClass(name) {
this.name = name;
MyClass.prototype.field += 1;
}
MyClass.prototype.field = 0;
MyClass.prototype.myMethod = function () {
alert(this.name + "'s class's field is " + MyClass.prototype.field);
};
var myObj = new MyClass("first");
myObj.myMethod();
var myOtherObj = new MyClass("second");
myObj.myMethod();
myOtherObj.myMethod();
I'm not sure if there's a prettier way to do it, as having 'prototype' all over the place feels a bit excessive, on the other hand it could be a good thing because it makes it clear that prototype.field does not belong to the instance.