AS3 add callbacks for listeners - actionscript-3

lately I've been asked to change a small flash app i made to work with external callbacks instead of using the embedded buttons.
I tried to automate the process and came up with this function:
/**
* Add an External callback for all PUBLIC methods that listen to 'listenerType' in 'listenersHolder'.
* #param listenersHolder - the class that holds the listeners we want to expose.
* #param listenerType - a String with the name of the event type we want to replace with the external callback.
*/
public static function addCallbacksForListeners(listenersHolder:*, listenerType:String):void
{
// get the holder description
var description:XML = describeType(listenersHolder);
// go over the methods
for each(var methodXML:XML in description..method)
{
// go over the methods parameters
for each(var parameterXML:XML in methodXML..parameter)
{
// look for the requested listener type
var parameterType:String = parameterXML.#type;
if (parameterType.indexOf(listenerType) > -1)
{
var methodName:String = methodXML.#name;
trace("Found: " + methodName);
// get the actual method
var method:Function = listenersHolder[methodName];
// add the callback
try
{
ExternalInterface.addCallback(methodName, method);
trace("A new callback was added for " + methodName);
}
catch (err:Error)
{
trace("Error adding callback for " + methodName);
trace(err.message);
}
}
}
}
before using this function I had to change the listener's function to Public, add null default parameter and of course remove/hide the visuals.
for example, from :
private function onB1Click(e:MouseEvent):void
to :
public function onB1Click(e:MouseEvent = null):void
add this line to the init/onAddedToStage function:
addCallbacksForListeners(this, "MouseEvent");
and remove the button from stage or just comment the line that adds it.
My question is: can you find a better/ more efficient way to do that ?
any feedback is welcomed..

Maybe you should make the javascript to call a single method with different functionName param. Then you only need to add one callback and you don't need to make all those functions public.
ExternalInterface.addCallback('callback', onCallback);
public function onCallback(res:Object)
{
var functionName:String = res.functionName;
this[functionName]();
}

Related

D3.js brushend function not properly called

I made a module that draws a bar chart including a d3.svg.brush(). Part of my code is
Bar.prototype.init = function ( ) {
//...
this.brush = d3.svg.brush()
.y( this.y)
.on("brushend", this.brushend);
console.log(this.brushend); // works
}
Bar.prototype.brushend = function() {
console.log(this.brush); // undefined. why?
}
To access this.* values, I cannot make brushend function a normal function by using function brushend() or var brushend = function().
How should I call it properly?
D3 will call the event handler provided for its brushend event much like event handlers for normal DOM events will get called. For standard events this will reference the element the event occured upon. This rule does also apply to D3's brush event handlers where this references the group containing the DOM elements of the brush. Obviously, this group doesn't have a property brush you could reference by using this.brush from with the handler.
A workaround would be to save your reference by using a closure:
Bar.prototype.init = function ( ) {
//...
this.brush = d3.svg.brush()
.y(this.y)
.on("brushend", this.brushend()); // Get handler by calling factory
}
// A factory returning the handler function while saving 'this'.
Bar.prototype.brushend = function() {
var self = this; // Save a reference to the real 'this' when the factory is called.
return function() { // Return the handler itself.
// By referencing 'self' the function has access to the preserved
// value of 'this' when the handler is called later on.
console.log(self.brush);
};
}

Can I develop an wizard application (sequentially submit forms)?

In my understanding now, only one doGet() can trigger unique doPost() in a Google Apps Script application.
I would like to perform a Software Publisher System that user upload the file or fill up revision information in forms and push submit to the next step. The final page will show the input information, send email to guys and complete all operation.
But how do I enter next form after the submit button pushed?
I have tried a method that creating the 2nd step and 3rd step forms in the doPost(), and using try...catch to difference which step form triggered the current step, like the following code.
(Because any steps can't get the callback item throw by non-previous step, then it arises an exception)
It works very well but I think it doesn't make sens and very silly. Have any better solutions? Thanks, please.
//---------------------------------------------------------------------------
function doGet(e)
{
var app = UiApp.createApplication().setTitle("AP Publisher");
createFileUploadForm(app);
return app;
}
//---------------------------------------------------------------------------
function doPost(e)
{
var app = UiApp.getActiveApplication();
try {
// 2nd step form
var fileBlob = e.parameter.thefile;
createRevisionForm();
}
catch(error) {
try {
// 3rd step form
createConfirmForm(e);
}
catch(error2) {
//Complete
sendMail(e);
modifySitePageContent(e);
saveHistoryFile(e);
showConfirmedInfo(e);
}
}
return app;
}
This answer is copied entirely from create a new page in a form dynamically, based on data of the prev. page.
Using the UiApp service, you have one doGet() and one doPost() function... but here's a way to extend them to support a dynamic multi-part form. (The example code is borrowed from this answer.)
Your doGet() simply builds part1 of your form. In the form, however, you need to identify your form by name, like this:
var form = app.createFormPanel().setId("emailCopyForm");
You doPost() then, will pass off handling of the post operation to different functions, depending on which form has been submitted. See below. (Also included: reportFormParameters (), a default handler that will display all data collected by a form part.)
/**
* doPost function with multi-form handling. Individual form handlers must
* return UiApp instances.
*/
function doPost(eventInfo) {
var app;
Logger.log("Form ID = %s", eventInfo.parameter.formId);
// Call appropriate handler for the posted form
switch (eventInfo.parameter.formId) {
case 'emailCopyForm':
app = postEmailCopyForm(eventInfo);
break;
default:
app = reportFormParameters (eventInfo);
break;
}
return app;
}
/**
* Debug function - returns a UiInstance containing all parameters from the
* provided form Event.
*
* Example of use:
* <pre>
* function doPost(eventInfo) {
* return reportFormParameters(eventInfo);
* }
* </pre>
*
* #param {Event} eventInfo Event from UiApp Form submission
*
* #return {UiInstance}
*/
function reportFormParameters (eventInfo) {
var app = UiApp.getActiveApplication();
var panel = app.createVerticalPanel();
panel.add(app.createLabel("Form submitted"));
for (var param in eventInfo.parameter) {
switch (param) {
// Skip the noise; these keys are used internally by UiApp
case 'lib':
case 'appId':
case 'formId':
case 'token':
case 'csid':
case 'mid':
break;
// Report parameters named in form
default:
panel.add(app.createLabel(" - " + param + " = " + eventInfo.parameter[param]));
break;
}
}
app.add(panel);
return app;
}
To generate each form part, subsequent form handlers can use the data retrieved in previous parts to dynamically add new Form objects to the ui.
I think it would be simpler to use 3 (or more) different panels in your doGet function with all the items you need and to play with their visibility.
At first only the 1rst panel would be visible and, depending on user input (using client Handlers to handle that) show the next ones (and eventually hide the first one).
In the end the submit button will call the doPost and get all data from the doGet.
First a tip of my hat to Mogsdad. His post(s) were guiding lights in the darkly documented path that led me here. Here is some working code
that demonstrates a multiple page form, i.e. it does the initial doGet() and then lets you advance back and forth doing multiple doPost()'s. All this is done in a single getForm() function called by both the standard doGet() and the doPost() functions.
// Muliple page form using Google Apps Script
function doGet(eventInfo) {return GUI(eventInfo)};
function doPost(eventInfo) {return GUI(eventInfo)};
function GUI (eventInfo) {
var n = (eventInfo.parameter.state == void(0) ? 0 : parseInt(eventInfo.parameter.state));
var ui = ((n == 0)? UiApp.createApplication() : UiApp.getActiveApplication());
var Form;
switch(n){
case 0: {
Form = getForm(eventInfo,n); // Use identical forms for demo purpose only
} break;
case 1: {
Form = getForm(eventInfo,n); // In reality, each form would differ but...
} break;
default: {
Form = getForm(eventInfo,n) // each form must abide by (implement) the hidden state variable
} break;
}
return ui.add(Form);
};
function getForm(eventInfo,n) {
var ui = UiApp.getActiveApplication();
// Increment the ID stored in a hidden text-box
var state = ui.createTextBox().setId('state').setName('state').setValue(1+n).setVisible(true).setEnabled(false);
var H1 = ui.createHTML("<H1>Form "+n+"</H1>");
var H2 = ui.createHTML(
"<h2>"+(eventInfo.parameter.formId==void(0)?"":"Created by submission of form "+eventInfo.parameter.formId)+"</h2>");
// Add three submit buttons to go forward, backward and to validate the form
var Next = ui.createSubmitButton("Next").setEnabled(true).setVisible(true);
var Back = ui.createSubmitButton("Back").setEnabled(n>1).setVisible(true);
var Validate = ui.createSubmitButton("Validate").setEnabled(n>0).setVisible(true);
var Buttons = ui.createHorizontalPanel().add(Back).add(Validate).add(Next);
var Body = ui.createVerticalPanel().add(H1).add(H2).add(state).add(Buttons).add(getParameters(eventInfo));
var Form = ui.createFormPanel().setId((n>0?'doPost[':'doGet[')+n+']').add(Body);
// Add client handlers using setText() to adjust state prior to form submission
// NB: Use of the .setValue(val) and .setValue(val,bool) methods give runtime errors!
var onClickValidateHandler = ui.createClientHandler().forTargets(state).setText(''+(parseInt(n)));
var onClickBackHandler = ui.createClientHandler().forTargets(state).setText(''+(parseInt(n)-1));
Validate.addClickHandler(onClickValidateHandler);
Back.addClickHandler(onClickBackHandler);
// Add a client handler executed prior to form submission
var onFormSubmit = ui.createClientHandler()
.forTargets(state).setEnabled(true) // Enable so value gets included in post parameters
.forTargets(Body).setStyleAttribute("backgroundColor","#EEE");
Form.addSubmitHandler(onFormSubmit);
return Form;
}
function getParameters(eventInfo) {
var ui = UiApp.getActiveApplication();
var panel = ui.createVerticalPanel().add(ui.createLabel("Parameters: "));
for( p in eventInfo.parameter)
panel.add(ui.createLabel(" - " + p + " = " + eventInfo.parameter[p]));
return panel;
}
The code uses a single "hidden" state (here visualized in a TextBox) and multiple SubmitButton's to allow the user to advance forward and backward through the form sequence, as well as to validate the contents of the form. The two extra SubmitButton's are "rewired" using ClientHandler's that simply modify the hidden state prior to form submission.
Notes
Note the use of the .setText(value) method in the client handler's. Using the Chrome browser I get weird runtime errors if I switch to either of the TextBox's .setValue(value) or .setValue(value, fireEvents) methods.
I tried (unsuccessfully) to implement this logic using a Script Property instead of the hidden TextBox. Instead of client handlers, this requires using server handlers. The behavior is erratic, suggesting to me that the asynchronous server-side events are occurring after the form submission event.

AS3 Passing Variable Parameters to a generic Function Menu / SubItems

I'm no code genius, but a fan of action script.
Can you help me on this:
I have a function that depending on the object selected, will call event listeners to a set of 'sub-items' that are already on stage (I want to reuse this subitems with changed parameters upon click, instead of creating several instances and several code).
So for each selected 'case' I have to pass diferent variables to those 'sub-items', like this:
function fooMenu(event:MouseEvent):void {
switch (event.currentTarget.name)
{
case "btUa1" :
trace(event.currentTarget.name);
// a bunch of code goes here
//(just cleaned to easy the view)
/*
HELP HERE <--
here is a way to pass the variables to those subitems
*/
break;
}
}
function fooSub(event:MouseEvent):void
{
trace(event.target.data);
trace(event.currentTarget.name);
// HELP PLEASE <-> How can I access the variables that I need here ?
}
btUa1.addEventListener(MouseEvent.CLICK, fooMenu);
btUa2.addEventListener(MouseEvent.CLICK, fooMenu);
btTextos.addEventListener(MouseEvent.CLICK, fooSub);
btLegislacao.addEventListener(MouseEvent.CLICK, fooSub);
Anyone to help me please?
Thank very much in advance. :)
(I'm not sure I got your question right, and I haven't developed in AS3 for a while.)
If you want to simply create function with parameters which will be called upon a click (or other event) you can simply use this:
btUa1.addEventListener(MouseEvent.CLICK, function() {
fooMenu(parameters);
});
btUa2.addEventListener(MouseEvent.CLICK, function() {
fooMenu(other_parameters)
}):
public function fooMenu(...rest):void {
for(var i:uint = 0; i < rest.length; i++)
{
// creating elements
}
}
If you want to call event listeners assigned to something else you can use DispatchEvent
btnTextos.dispatchEvent(new MouseEvent(MouseEvent.CLICK))
Remember, you can't use btTextos.addEventListener(MouseEvent.CLICK, carregaConteudo("jocasta")); because the 2nd parameter you pass while adding Eventlistener will be considered as function itself - there are two proper ways to use addEventListener:
1:
function doSomething(event:MouseEvent):void
{
// function code
}
element.addEventListener(MouseEvent.CLICK, doSomething); //notice no brackets
2:
element.addEventListener(MouseEvent.CLICK, function() { // function code });
So:
function fooSub(event:MouseEvent, bla:String):void
{
trace(event.currentTarget.name+" - "+bla);
// bla would be a clip name.
}
codebtTextos.addEventListener(MouseEvent.CLICK, function(e:MouseEvent) { fooSub(e, "jocasta") } );
Or try something like this if you want content to be dynamically generated:
btUa1.addEventListener(MouseEvent.CLICK, function() {
createMenu(1);
});
btUa2.addEventListener(MouseEvent.CLICK, function() {
createMenu(2);
});
function createMenu(id):void
{
// Switching submenu elements
switch (id)
{
case 1:
createSubmenu([myFunc1, myFunc2, myFunc3]); // dynamically creating submenus in case you need more of them than u already have
break;
case 2:
createSubmenu([myFunc4, myFunc5, myFunc6, myFunc7]);
break;
default:
[ and so on ..]
}
}
function createSubmenu(...rest):void {
for (var i:uint = 0; i < rest.length; i++)
{
var mc:SubItem = new SubItem(); // Subitem should be an MovieClip in library exported for ActionScript
mc.addEventListener(MouseEvent.CLICK, rest[i] as function)
mc.x = i * 100;
mc.y = 0;
this.addChild(mc);
}
}
Your question is rather vague; what "variables" do you want to "pass"? And what do you mean by "passing the variable to a sub item"? Usually "passing" means invoking a function.
If you can be more specific on what exactly your trying to do that would be helpful. In the meantime, here are three things that may get at what you want:
You can get any member of any object using bracket notation.
var mc:MovieClip = someMovieClip;
var xVal:Number = mc.x; // The obvious way
xVal = mc["x"]; // This works too
var propName:String = "x";
xVal = mc[propName] ; // So does this.
You can refer to functions using variables
function echo(message:String):void {
trace(message);
}
echo("Hello"); // The normal way
var f:Function = echo;
f("Hello"); // This also works
You can call a function with all the arguments in an array using function.apply
// Extending the above example...
var fArgs:Array = ["Hello"];
f.apply(fArgs); // This does the same thing
Between these three things (and the rest parameter noted by another poster) you can write some very flexible code. Dynamic code comes at a performance cost for sure, but as long as the frequency of calls is a few hundred times per second or less you'll never notice the difference.

How to Getting the Variable from Function in as3 without using get ,set method?

Hai am Getting trouble to retrive the values from function(addText).i Called from another function onFullScreen().I dont know how Can i do this,Kindly Help me?Here i attach my Code
private function addText()
{
nc = new NetConnection();
nc.addEventListener(NetStatusEvent.NET_STATUS, ncOnStatus);
function ncOnStatus(infoObject:NetStatusEvent)
{
trace("nc: "+infoObject.info.code+" ("+infoObject.info.description+")");
if (infoObject.info.code == "NetConnection.Connect.Success")
{
initSharedObject(chatSharedObjectName);
}
}
function formatMessage(chatData:Object)
{
trace("room"+chatData.message);
number = chatData.txtalign;//i want to retrive the value of number
number.toString();
return number;
}
function syncEventHandler(ev:SyncEvent)
{
var infoObj:Object = ev.changeList;
// if first time only show last 4 messages in the list
if (lastChatId == 0)
{
lastChatId = Number(textchat_so.data["lastChatId"]) - 1;
if (lastChatId < 0)
lastChatId = 0;
}
}
function connectSharedObject(soName:String)
{
textchat_so = SharedObject.getRemote(soName, nc.uri)
// add new message to the chat box as they come in
textchat_so.addEventListener(SyncEvent.SYNC, syncEventHandler)
textchat_so.connect(nc)
}
function connectSharedObjectRes(soName:String)
{
connectSharedObject(soName)
trace(soName)
}
function initSharedObject(soName:String)
{
// initialize the shared object server side
nc.call("initSharedObject", new Responder(connectSharedObjectRes), soName)
}
}
i using the variable in another function ,but I cannot retrive the Value.
private function onFullScreen(event:FullScreenEvent):void
{
mediaContainer.addMediaElement(alert);
alert.alert("Error",number);// if i cannot retrive the value hnumber here
}
The addText() method is asynchronous, meaning that you can't simply call it , you need to wait for the event listener to return a value.
I'm not sure why you would feel the need to enclose all these functions, it's not very legible and I doubt it's necessary. You're also missing quite a few semi colons...
In any case , I couldn't see where the formatMessage() method was called, it seems that's the only place where the "number" variable gets defined.
You could create a variable outside the scope of the functions.
private var num:int;
Then in your addText function, assign a value to the variable:
num = infoObject.info.code;
Then in your onFullScreen function, access the num variable:
alert.alert("Error", num);

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.