What is the Dart "Expando" feature about, what does it do?
Asked Answered
T

3

34

Have been seeing the term "Expando" used recently with Dart. Sounds interesting. The API did not provide much of a clue to me.

An example or two could be most helpful!

(Not sure if this is related, but I am most anxious for a way to add methods (getters) and/or variables to a class. Hoping this might be a key to solving this problem. (hint: I am using the Nosuchmethod method now and want to be able to return the value of the unfound method.))

Thanks in advance,

_swarmii

Texas answered 13/11, 2012 at 9:11 Comment(0)
T
22

Expandos allow you to associate objects to other objects. One very useful example of this is an HTML DOM element, which cannot itself be sub-classed. Let's make a top-level expando to add some functionality to an element - in this case a Function signature given in the typedef statement:

typedef CustomFunction(int foo, String bar);

Expando<CustomFunction> domFunctionExpando = new Expando<CustomFunction>();

Now to use it:

main(){
   // Assumes dart:html is imported
   final myElement = new DivElement();

   // Use the expando on our DOM element.
   domFunctionExpando[myElement] = someFunc;

   // Now that we've "attached" the function to our object,
   // we can call it like so:
   domFunctionExpando[myElement](42, 'expandos are cool');
}

void someFunc(int foo, String bar){
  print('Hello. $foo $bar');
}
Tippet answered 13/11, 2012 at 16:0 Comment(2)
What is different between this and Map<Element, CustomFunction> ?Epoch
@ZdeněkMlčoch An Expando doesn't prevent garbage collection of the keys! In this regard it could be compared to JavaScript's WeakMap.Colonial
T
25

Just to clarify the difference between expando and maps: as reported in the groups, expando has weak references.
This means that a key can be garbage collected even if it's still present in the expando (as long as there are no other references to it).

For all other intents and purposes it's a map.

Torero answered 9/11, 2014 at 1:8 Comment(1)
Expando has a very reduced API compared to Map though. You can't iterate through values, or do much else other than use the square bracket notation [] to access elements. You remove elements via expando[obj] = null, because there is no .remove method.Zurkow
T
22

Expandos allow you to associate objects to other objects. One very useful example of this is an HTML DOM element, which cannot itself be sub-classed. Let's make a top-level expando to add some functionality to an element - in this case a Function signature given in the typedef statement:

typedef CustomFunction(int foo, String bar);

Expando<CustomFunction> domFunctionExpando = new Expando<CustomFunction>();

Now to use it:

main(){
   // Assumes dart:html is imported
   final myElement = new DivElement();

   // Use the expando on our DOM element.
   domFunctionExpando[myElement] = someFunc;

   // Now that we've "attached" the function to our object,
   // we can call it like so:
   domFunctionExpando[myElement](42, 'expandos are cool');
}

void someFunc(int foo, String bar){
  print('Hello. $foo $bar');
}
Tippet answered 13/11, 2012 at 16:0 Comment(2)
What is different between this and Map<Element, CustomFunction> ?Epoch
@ZdeněkMlčoch An Expando doesn't prevent garbage collection of the keys! In this regard it could be compared to JavaScript's WeakMap.Colonial
B
1

I played with it a little bit. Here's what I've got.

import 'dart:html';

const String cHidden = 'hidden';

class ExpandoElement {
  static final Expando<ExpandoElement> expando =
      new Expando<ExpandoElement>("ExpandoElement.expando");

  final Element element;

  const ExpandoElement._expand(this.element);

  static Element expand(Element element) {
    if (expando[element] == null)
      expando[element] = new ExpandoElement._expand(element);
    return element;
  }

//  bool get hidden => element.hidden; // commented out to test noSuchMethod()
  void set hidden(bool hidden) {
    if (element.hidden = hidden)
      element.classes.add(cHidden);
    else
      element.classes.remove(cHidden);
  }

  noSuchMethod(InvocationMirror invocation) => invocation.invokeOn(element);
}
final Expando<ExpandoElement> x = ExpandoElement.expando;
Element xquery(String selector) => ExpandoElement.expand(query(selector));

final Element input = xquery('#input');

void main() {
  input.classes.remove(cHidden);
  assert(!input.classes.contains(cHidden));

  input.hidden = true;
  assert(x[input].hidden); // Dart Editor warning here, but it's still true
  assert(!input.classes.contains(cHidden)); // no effect

  input.hidden = false;
  assert(!x[input].hidden); // same warning, but we'll get input.hidden via noSuchMethod()
  assert(!input.classes.contains(cHidden));

  x[input].hidden = true;
  assert(input.hidden); // set by the setter of ExpandoElement.hidden
  assert(input.classes.contains(cHidden)); // added by the setter
  assert(x[input].hidden);
  assert(x[input].classes.contains(cHidden)); // this is input.classes

  x[input].hidden = false;
  assert(!input.hidden); // set by the setter
  assert(!input.classes.contains(cHidden)); // removed by the setter
  assert(!x[input].hidden);
  assert(!x[input].classes.contains(cHidden));

  // confused?
  assert(input is Element);
  assert(x[input] is! Element); // is not
  assert(x[input] is ExpandoElement);
  assert(x is Expando<ExpandoElement>);
}
Bini answered 19/3, 2013 at 9:8 Comment(2)
On the day I post this, Dart version is r19425Bini
if (element.hidden = hidden) <-- Not sure but this looks very suspicious, care to elaborate?Judejudea

© 2022 - 2024 — McMap. All rights reserved.