I'm using Google Web Tools, and have a JsArray, which I'm populating with data from JSON. I'm able to modify items within the array and add items to it, but I can't figure out how to remove an item from it. I'm looking for something similar to the pop() method in JavaScript.
I can add an item to the array by using the set(index,value) method with an index that's out of the array's range, so I tried using set(index,null) to remove it, but the array still has the item, it's just null. (i.e. the array's length is unchanged.)
I'm currently using a hacky method whereby I create a new array, and copy all of the elements except the last one from the old to the new, but I'm hoping I don't have to live with that, because it's ugly.
private final JsArray<JsArrayInteger> popItemFromArray(
JsArray<JsArrayInteger> oldArray) {
// the createEmpty... method is a native method which returns eval("[]")
JsArray<JsArrayInteger> newArray = createEmptyIntIntArray();
for (int i = 0; i < oldArray.length() - 1; i++) {
newArray.set(i, oldArray.get(i));
}
return newArray;
}
There's no pop(), but there's... shift() :)
Either that or extend the JsArray class, something like this (not tested, but you should get the idea):
public class JsArrayPop<T extends JavaScriptObject> extends JsArray<T> {
protected JsArrayPop() {
}
public final native T pop() /*-{
return this.pop();
}-*/;
}
Related
I'm trying to iterate through the properties of a custom class, however, the methods provided by Adobe appear to not work. I'm not receiving compile or run-time errors.
Class
package {
public dynamic class enum {
public var foo:Number = 123;
public function enum() {
this.setPropertyIsEnumerable("foo", true);
if (this.propertyIsEnumerable("foo") == false) {
trace("foo:" + foo + " is not enumerable.")
}
}
}
}
// outputs "foo:bar is not enumerable."
Implementaiton
var test:enum = new enum();
for (var property:String in test) {
trace(property);
}
// outputs nothing
I try to keep my code fast and flexible, so it's really frustrating when you must change the class to Dynamic just to be able to use for ... in on the properties. Jackson Dunstan's testing confirms that this can be 400x slower than static class properties, but those have to be explicitly referenced (impractical for property agnostic methods), or use reflection of the class (computationally expensive) to be accessible.
The only way I've found to sidestep the whole issue is to use dynamically declared variables... which is pointless since at that point using setPropertyIsEnumerable(prop, true) is superfluous; all dynamically created properties already are enumerable. Additionally, dynamic variables cannot be strongly datatyped, and performance goes out the window.
For example...
Class
package {
public dynamic class enum {
public var foo:String = "apple";
public function enum(){
this.dynamicVar = "orange";
this.dynamicProp = "banana";
this.setPropertyIsEnumerable("foo", true);
this.setPropertyIsEnumerable("dynamicProp", false);
}
}
}
Implementation
var test:enum = new enum();
for (var key:String in test) {
trace(key + ": " + test[key]); // dynamicVar: 1
}
// outputs "dynamicVar: orange"
Now that the class is dynamic, we see that only one of our 3 test properties are being iterated. There should be 2.
It almost feels like Adobe wants us to adopt bad programming habits. Words fail me...
Non-dynamic classes do not provide enumerable properties or methods.
As stated in the description of the link you provided.
Sets the availability of a dynamic property for loop operations.
I think you might want to refactor your code on this approach.
I have never had to loop over a classes properties like you are doing here.
If you want to track items dynamically you should use an associative array and track them that way not at the class level like you are doing.
And if you want strong data typing then use a vector.
I was looking at reasons why my 2-way binding hasn't been working for iOS development using MVVMCross. I'm using UITextViews embedding in custom cells of a tableView.
I've looked at this question:
How do I do two-way binding to a UITextView control using mvvmcross?
and there was mention on how 2-way binding with UITextViews isn't supported in Vnext, but it is in beta with V3 (Hot Tuna). I am using Hot Tuna, and got the binaries approx. June 12th.
The following line of code is how I'm binding my cell.
this.CreateBinding (_cells[2]).For (cll => cll.FieldDescription).To ((TimesheetViewModel vm) => vm.AccountID).Mode(MvxBindingMode.TwoWay).Apply ();
_cells[] is an array of custom class cells
Here's the property of FieldDescription in my custom cell class: (FieldDescriptionLabel is my UITextView)
public string FieldDescription
{
get
{
return FieldDescriptionLabel.Text;
}
set
{
FieldDescriptionLabel.Text = value;
}
}
My UITextView binds one way; I do see the information populated from my viewModel, but when I change something in the UITextView, the ViewModel doesn't reflect those changes.
So the main question: Is 2-way binding working for UITextView in MVVMCross Hot Tuna? If yes, any ideas on what I'm doing wrong in my implementation?
Appreciate it!
In order for two-way binding to work, mvvmcross needs to know when the ui value changes.
There are a couple of ways to do this.
Perhaps the easiest way, given your current setup, is to add a public event EventHandler FieldDescriptionChanged to your cell and to make sure this event is fired every time the text view changes - e.g. with code like.
public event EventHandler FieldDescriptionChanged;
public string FieldDescription
{
get
{
return FieldDescriptionLabel.Text;
}
set
{
FieldDescriptionLabel.Text = value;
}
}
public override void AwakeFromNib()
{
base.AwakeFromNib();
FieldDescriptionLabel.Changed += (s,e) => {
var handler = FieldDescriptionChanged;
if (handler != null)
handler(this, EventArgs.Empty);
};
}
Alternatively, you could try basing your cell on the Mvx table view cells which have an inherent DataContext. If you do this, then you can bind directly to the UITextView with and then using data-binding within the cell's context.
This approach is shown in the N+1 tutorials - for example in N=6.5 - http://slodge.blogspot.co.uk/2013/05/n6-books-over-network-n1-days-of.html - where the cell ends up with a constructor like:
public BookCell (IntPtr handle) : base (handle)
{
_loader = new MvxImageViewLoader(() => MainImage);
this.DelayBind(() => {
var set = this.CreateBindingSet<BookCell, BookSearchItem> ();
set.Bind(TitleLabel).To (item => item.volumeInfo.title);
set.Bind (AuthorLabel).To (item => item.volumeInfo.authorSummary);
set.Bind (_loader).To (item => item.volumeInfo.imageLinks.thumbnail);
set.Apply();
});
}
Using this approach, you would simply need to bind the cell's data context - e.g. something like:
this.CreateBinding (_cells[2]).For (cll => cll.DataContext).To ((TimesheetViewModel vm) => vm).TwoWay().Apply ();
So, I have successfully grabbed a value out of an XML document and set it into a separate class called "AddCommas." The trace functions have shown me that it sets properly.
For more details, my objective is to take the language indicator ("fr" for french or "en" for english), set it inside the appropriate class and into a variable I will use. Now, I am using this variable to be used in an if statement; which will help me format a number properly (commas, decimals, spaces) per the clients request.
However, my problem is when I try to get the value to use it. It always comes back as Null. I have placed traces all over my program trying to pinpoint when this happens, but I cannot find it. Here's the code...
The pull from the XML file and into the set (this works fine, but I am adding it for your benefit in case I missed something)
public var commaHold = new AddCommas();
localLanguage = xmlObj.localLanguage;
trace("localLanguage + " + localLanguage);
commaHold.setLanguage(localLanguage); // Set Language
//More code follows...
This is the set function istelf...
public function setLanguage(localLanguage:String){
langHold = localLanguage;
trace("Set Language = " + langHold); //This always shows a successful set
}
Now am I wrong in thinking that in AS3, once langHold in my AddCommas class has been set I should be able to use it without calling a get within the function I am using the If Statement in, right? Such as this?
var language = langHold;
if (language == "en"){
trace("Language is = " + language); // More code follows afterwards and as of now, this shows NULL
Now, I have attempted plenty of Get functions to add the language variable in the call itself to this function and it's always the same. Am I missing some fundamentals here?
Thank you very much for your time.
If you expect a string comparison you need to use quotes, unless en is a String variable since langHold is a String, like:
if (language == "en"){
Consider modifying the set function to use the as3 keyword like:
private var _language:String;
public function set language(value:String):void {
_language = value;
//do other stuff here if necessary, put a breakpoint on the line above
}
public function get language():String{
return _language;
//put a breakpoint on the line above
}
You should be able to see when any instance of your class has the property changed. The only other issue I can think of is it is not the same instance of the class and therefore doesn't share the property value you set earlier. In the debugger you can check the "hashCode" or "address" it shows for this to see if it changes when it hits the breakpoints.
Here's a sample Singleton structure in AS3 (this all goes in one file):
package com.shaunhusain.singletonExample
{
public class SingletonExample
{
private static var instance:SingletonExample;
public static function getIntance():SingletonExample
{
if( instance == null ) instance = new SingletonExample( new SingletonEnforcer() );
return instance;
}
/**
*
* #param se Blocks creation of new managers instead use static method getInstance
*/
public function SingletonExample(se:SingletonEnforcer)
{
}
}
}
internal class SingletonEnforcer {public function SingletonEnforcer(){}}
using this single shared instance from any other class would look something like this:
private var singletonInstance:SingletonExample = SingletonExample.getInstance();
ShaunHusain's theory of using a Singleton was the perfect solution I needed. However, his code gave me a bizarre 1061 error and my format and code appeared to be error free. Regardless, I looked up another way to use a Singleton as follows that worked perfectly for me. Honestly, Shaun's code should work for anyone and I have no idea why it wasn't. I am perfectly willing to admit that it was probably a typo on my end that I just did not see.
I ended up embedding the Set and Get within the Singletons class and used it as an intermediary to hold the information I needed. It worked perfectly.
package chart {
import chart.*;
//
public class StaticInstance {
private static var instance:StaticInstance;
private static var allowInstantiation:Boolean;
private var language:String;
public static function getInstance():StaticInstance {
if (instance == null) {
allowInstantiation = true;
instance = new StaticInstance();
allowInstantiation = false;
}
return instance;
}
public function StaticInstance():void {
if (!allowInstantiation) {
throw new Error("Error: Instantiation failed: Use StaticInsance.getInstance() instead of new.");
}
}
public function setLanguage(_language:String):void{
language = _language;
trace("language set = " + language);
}
public function getLanguage():String{
return language;
}
}
}
This code allowed me to hold the data and call upon it again from two different classes. It's a very hack job instead of just being able to pass on the variable from function to function, but in my case we didn't create this file, we are modifying it and attempting to do things beyond the original scope of the project.
Thanks again for your help Shaun! I hope this helps other people!
Is there a way to pass an object reference to a component directly from the property/component parameter window? Using the [Inspectible] tag only allows me to input strings and not actual object references.
For example, I have a custom component called "knob" which should hold a reference to a door on the stage which it opens. I know this can be easily done in code with "knob.door = someDoor;" but since there are many objects in the scene I would prefer if I could do it visually trough the properties window.
I don't think you can do this. Your best bet is to pass in a string identifier (perhaps a whole dot-separated path if your clips are deeply nested), and then implement code inside your custom component to find that item by name.
I've got a custom component which lays itself out relative to horizontal and vertical predecessor components, so I do this:
protected var horizontalPredecessor:String = "";
[Inspectable(name = "HorizontalPredecessor", type = String, defaultValue="")]
public function set HorizontalPredecessor($value:String):void
{
horizontalPredecessor = $value;
drawNow();
}
override protected function draw():void
{
if (parent)
{
if (horizontalPredecessor != "")
{
var hp:DisplayObject = parent.getChildByName(horizontalPredecessor);
if (hp)
{
x = hp.y + hp.height + horizontalSpacing;
}
}
}
}
... which is made easy because all these components share the same parent.
Alternatively, if there's only one door, you could make it a singleton, and give it a static reference, like this:
public class Door
{
private static var _singleton:Door;
public static function get Singleton():Door
{
if(!_door) _door = new Door();
return _door;
}
}
Then your handle can just refer to Door.Singleton and you don't have to worry about passing anything in. Alternatively, you could have a static array of Doors in the Door class, and give your handle an index number to link it to a specific Door.
I am currently working with some code that my co-worker wrote. Here is a simplified look at it:
The People class:
package model{
public class People extends Array{ // NOTE: it is not dynamic
public function toXML():XML {
var out:XML = <people/>;
for each(var per:Person in this){
out.appendChild(per.toXML());
}
return out;
}
}
}
Which is basicly an Array of Persons:
package model{
public class Person {
public var name:String;
public var phoneNumber:String;
public function Person(name:String, phoneNumber:String){
this.name = name;
this.phoneNumber = phoneNumber;
}
public function toXML():XML {
var xml:XML = <person/>;
xml.#name = name;
xml.#phone = phoneNumber;
return xml;
}
}
}
This is basicly how my co-worker is using the code:
var people:People = new People();
people.push(new Person("Jake", "902 825-4444"));
people.push(new Person("Bob", "514 444-3333"));
return people.toXML().toXMLString();
Note: The he adds Person objects but he never looks at what is in the People Array except to print out the XML
Fast-forward (do people still know that this means?) to the present. I now need to look inside the People class and do something like this:
var people:People = ... init and fill with Person objects
for(var i:int=0; i<people.length(); i++){
doSomething(people[i]); // <-- Error thrown here.
}
Unfortionatly this throws this error:
ReferenceError: Error #1069: Property 0 not found on model.People and there is no default value.
at runner::Runner$/newUse()
at ExtendsArray/start()
at ExtendsArray/___ExtendsArray_Application1_initialize()
at flash.events::EventDispatcher/dispatchEventFunction()
What should I do?
Edit, Aside: Extending Array is not my doing this is part of our old model. We are moving away from the old model because it is full of garbage like this. I just need to grab this stuff from the old model to convert it into the new model. I would like to tear this code out of our product but the cost of doing that is probably not warranted.
What should I do?
Use only class methods to access and set items in your "array", don't use Array-specific syntax. And/or make the class dynamic.
EDIT I think you can leave everything as is and avoid making your class dynamic by defining only one additional method for item access (if it's not there for some reason). Something like:
public functon getItem(index:uint):*
{
if (index >= length) {
return null;
}
return this.slice(index, index+1)[0];
// this creates a redundant array on the fly, sorry.
}
// usage:
var people:People = ... init and fill with Person objects
for(var i:int=0; i<people.length(); i++){
doSomething(people.getItem(i));
}
And I know that is not the way it's meant to be answered on stackoverwlow, but... I can't hold it. ))
Anything extends Array -- is a heresy. If I see that in production code, I'll immediatelly proceed to initiating a "purge the unclean" sequence.
Just try googling for the subject a little bit, and you will see, that almost no one has got alive and well from an activitiy like that.
The main rational reason why this abomination is not meant to exist (aside form it being a heresy) is that you can not use array access [], you can not instantiate it like a normal array with items and you can not set items through array syntax [] and be notified of the changes somewhere in your class. But it is an Array by the right of birth, so any of your fellow developers not familiar with the subject may try to use it like a normal Array, because it's quite natural. And then they'll probably post another question on stackoverflow.
So, if you are still limited to just using class methods, what's the point in extending anyway? Whay not use neat aggregation/composition or proxy ways?
It's amazing, but there's even an article on extending Array on adobe.com. What they do not mention there is that you are likely to start burning in hell right away.
Array.length is a getter: it returns an int. people.length() is the same as calling 3(). I don't know how it worked when you tested that.
It looks like you'd be better off implementing something like IList and have addItem push into a Vector.<Person>. That will guarantee that you only have Person objects.
You should probably should not be extending Array. If you want to extend anything, extend Proxy (You can even use the ProxyArray class example with a Vector.<Person>). Extending top level classes (with the exception of Object) is often an invitation for confusion.
If you really, really want to extend Array, you have to make the class dynamic -- you are arbitrarily assigning and removing properties.
This looks like it works:
var s:String = "";
for each(var per:Person in people){
s += per.name + " ";
}
The People class has to be scripted as public dynamic class since it is extending the Array class.
From this Adobe help article: A subclass of Array should use the dynamic attribute, just as the Array class does. Otherwise, your subclass will not function properly.
You used the "push" function which apparently created an associative array.
Associative arrays can not be called by index. They can also not be reversed or have their order changed.
You need to access them by using the for..in loop
for (var key:String in people) {
trace("person : " + (people[key] as person).name);
}
// or as you found out the for each.. in
for each(var person:Person in people){
trace("person : " + person.name);
}
The arr.length() function of an associative array will always be 0 and you saw that with your error.
//Try this example you will see how an array can act like an array as we know it and an object.
var a:Array = [];
a[0] = true;
a.push(true);
a.push(true);
a["foo"] = true;
a.push(true);
a.push(true);
a.bar = true;
trace("for loop\n");
for(var i:int = 0, ilen:int = a.length ; i < ilen ; i++){
trace(i,a[int(i)]);
}
trace("\nfor...in loop\n");
for(var key:String in a){
trace(key,a[key]);
}