Changing default map redux
Using AS3 I'm trying to create a button (admittedly I'm borrowing a lot from Nathan's (Flowing Data) Walmart code) that enables me to switch map providers.
The function to create a button...
How I've tried to instantiate it...
and with a variable in the action's place...
I've tried several other syntactical things and i've got nothin'. The only thing I haven't tried is creating an alternate makeButton function that passes a Class as action, but that might just be pure hackery.
Any thoughts?
The function to create a button...
public function makeButton(clip:Sprite, name:String, labelText:String, action:Function):Sprite
How I've tried to instantiate it...
makeButton(viewButton, 'beltline', 'beltline', map.setMapProvider(new BeltlineMapProvider));
and with a variable in the action's place...
makeButton(viewButton, 'beltline', 'beltline', BLmap);
I've tried several other syntactical things and i've got nothin'. The only thing I haven't tried is creating an alternate makeButton function that passes a Class as action, but that might just be pure hackery.
Any thoughts?
1
person has this question
I have this question, too!
Tell me when someone answers.
The more people who ask this question, the more it gets noticed.
The more people who ask this question, the more it gets noticed.
Create a customer community for your own organization
Plans starting at $19/month
-
Inappropriate?This is definitely an actionscript syntax issue, and one forum post is not the best place for me to teach it. I can recommend working through a good actionscript 3 book - the O'Reilly one by Colin Moock is pretty great. That said, I will try to explain here and at least I should give you plenty of things to Google :)
The signature of your makeButton function says that action needs to be a Function variable.
This can be confusing, because generally you call functions to perform an action, which is what's happening where you're using map.setProvider(...) with parentheses. Unfortunately this breaks because setProvider returns returns void (nothing) and void is not a Function.
Luckily though, functions in actionscript can be referred to as variables (that's the reason you sometimes see it with a capital F, Function), and this works so long as you don't use parentheses. If you put the parentheses on the end you';; call the function instead of referring to it, as you did.
However, you're probably wondering how you pass arguments into a function if you can't use brackets when referring to it as a variable. This will take a bit of explaining, which I'll try to do with some examples...
Say you have a class called DemoThing with a function called doSomething:
package com.example.demo {
public class DemoThing
{
public function doSomething() {
trace('doSomething function in DemoThing class got called');
}
}
}
You make an instance of your class like this:
var myDemoThing:DemoThing = new DemoThing();
Then you could put the doSomething function into a variable and pass it to your makeButton function like so:
var action:Function = myDemoThing.doSomething; // note no parentheses at the end
makeButton(viewButton, 'beltline', 'beltline', action)
Or you could have passed it directly, like so:
makeButton(viewButton, 'beltline', 'beltline', myDemoThing.doSomething)
// again note the lack of parentheses
If you say doSomething() it will perform the action (in this case tracing some stuff) but if you say doSomething it refers to a Function instance instead.
However in order for such a Function object to work as an event listener in actionscript 3 without causing an error, it needs to take an event as an argument. So we would modify doSomething as follows:
public function doSomething(event:Event) {
trace('doSomething function in DemoThing class got called with event:', event);
}
Finally if you actually want doSomething to, uh, do something, you need to make DemoThing know about the pieces required. Let's say DemoThing is actually called ProviderHelper and doSomething is actually called applyProvider, the final class would need to know about map and provider, so it might look like this:
package com.example.demo {
import com.modestmaps.Map;
import com.modestmaps.mapproviders.IMapProvider
public class ProviderHelper
{
public var map:Map;
public var provider:IMapProvider;
public function ProviderHelper(map:Map, provider:IMapProvider) {
this.map = map;
this.provider = provider;
}
public function applyProvider(event:Event) {
map.setProvider(provider);
}
}
}
And then you would do:
var helper:ProviderHelper = new ProviderHelper(map, new BeltlineMapProvider());
makeButton(viewButton, 'beltline', 'beltline', helper.applyProvider);
Note that you're not calling applyProvider, you're passing it into the function as a variable where (I assume) it will be used as an event listener and it will get called only when the event occurs.
I hope the method I've outlined above is clear. I'll follow-up with an alternative approach which uses an actionscript feature called closures to do away with the need for the ProviderHelper class.
I’m trying to teach
1 person says
this answers the question
-
This did the trick. It didn't work with my custom tiles, but I think that's a sync issue, those things are wonky. I do get the following warning...
"1008: return value for function 'applyProvider' has no type declaration."
But it still compiles and works perfectly. -
Functions with that warning can be fixed by adding ":void" before the opening curly brace:
public function applyProvider(event:Event):void {
map.setProvider(provider);
}
-
Inappropriate?So... closures. Hmm. Think of this as lesson 2. Make sure that the above code works for you (and let me know if I made any errors), and then once you're comfortable with it take a look at this way to achieve the same effect with less code.
Instead of creating a separate ProviderHelper class, you could pass a function directly to makeButton. Formatting the code for this is a little tricky, but it would look something like this:
makeButton(viewButton, 'beltline', 'beltline', function(event:Event):void {
trace('an anonymous function was called!');
});
Notice that the function takes an event as an argument as discussed above, to make sure it doesn't cause an error when used as an event listener. It's called an anonymous function because - ha! - it doesn't have a name. It's defined right there just before the code in makeButton executes, and assigned to the action variable inside of makeButton.
It could also have been defined as follows:
var action:Function = function(event:Event):void {
trace('a function was called!');
};
makeButton(viewButton, 'beltline', 'beltline', action);
It's not anonymous any more, but it's still a closure which means it can do special things. It gets a reference to all the variables that are in scope at the time it is created, such as map. So you can do:
var action:Function = function(event:Event):void {
map.setMapProvider(new BeltlineMapProvider());
};
makeButton(viewButton, 'beltline', 'beltline', action);
Or to use the shortest syntax:
makeButton(viewButton, 'beltline', 'beltline', function(event:Event):void {
map.setMapProvider(new BeltlineMapProvider());
});
You can see how for each button you can make a separate function to use as an action. (Bear in mind if you do this that the references to map could exist in your application longer than the buttons, which could eat up memory, so be careful if you plan on creating and destroying lots of buttons and maps this way).
If this looks a bit ugly, instead of a helper class like ProviderHelper above, you can make a providerHelper function that returns another function. (It's functions all the way down). This would look something like this:
protected function providerHelper(provider:IMapProvider):Function
{
return function(event:Event):void {
map.setMapProvider(provider);
}
}
And you'd call makeButton like this:
makeButton(viewButton, 'beltline', 'beltline', providerHelper(new BeltlineMapProvider()));
This can be tidier if you have a dozen buttons to make.
You're hopefully getting the idea by now, so try thinking about how makeButton would look if it took a provider as an argument instead of an action, and did the function stuff for itself. Or how about if makeButton was actually the constructor of a ProviderButton class, that also knew how to set the map provider?
There are many ways to skin this cat!
PS you might be wondering why it's OK to leave the parentheses off of the constructor when you're making a new instance. I think this is only a special property of new, and shouldn't be confused with the syntax for referring to a function as a variable. There's a whole other set of syntax for calling functions when they're variables, and also for referring to classes as variables, but I won't be covering those here. Moock is your man!
I’m being thorough
-
Inappropriate?Oh, actually since you mention it in your original question, the way to pass a Class into makeButton would be as follows:
var provider:Class = BeltlineMapProvider; // note no 'new' or parentheses
makeButton(viewButton, 'beltline', 'beltline', provider);
If you did this, you'd need to modify makeButton to perform the right function and class gymnastics when adding a listener to the button. This might look like:
public function makeButton(clip:Sprite, name:String, labelText:String, provider:Class):Sprite
{
var button:Sprite = new Sprite();
// do whatever you do with clip, name and labelText
button.addEventListener(MouseEvent.CLICK, function(event:MouseEvent):void
{
map.setMapProvider(new provider());
}
return button;
}
Depending on who you ask, different coders have different style guidelines for passing Classes as variables, and some would name the provider variable with a capital letter even though it's an instance, because it looks weird next to new.
Anyway, at this point I've given you a fairly thorough set of options. I wouldn't use this one, it's not really any tidier than instantiating the provider yourself, and you can see there are issues with the makeButton function still needing a reference to the map anyway, so it's not totally reusable for any button needs :)
I’m completist
-
Inappropriate?wowza! you're a super star. I'm going to work through this and let you know if I have any questions, but I should be an AS3 master by the time I've completely read these replies. ;)
THANKS a million!!!
I’m utterly grateful
Loading Profile...



EMPLOYEE