diff --git a/modules/examples/pubspec.yaml b/modules/examples/pubspec.yaml
index 342dbbfd34..a8b7247c56 100644
--- a/modules/examples/pubspec.yaml
+++ b/modules/examples/pubspec.yaml
@@ -2,6 +2,7 @@ name: examples
environment:
sdk: '>=1.10.0 <2.0.0'
dependencies:
+ observe: '^0.13.1'
angular2: '^<%= packageJson.version %>'
angular2_material: '^<%= packageJson.version %>'
browser: '^0.10.0'
@@ -29,8 +30,9 @@ transformers:
- web/src/key_events/index.dart
- web/src/sourcemap/index.dart
- web/src/todo/index.dart
- - web/src/model_driven_forms/index.dart
- web/src/order_management/index.dart
+ - web/src/model_driven_forms/index.dart
+ - web/src/observable_models/index.dart
- web/src/person_management/index.dart
- web/src/template_driven_forms/index.dart
# These entrypoints are disabled until cross-package urls are working (issue #2982)
@@ -53,6 +55,7 @@ transformers:
- web/src/sourcemap/index.dart
- web/src/todo/index.dart
- web/src/model_driven_forms/index.dart
+ - web/src/observable_models/index.dart
- web/src/order_management/index.dart
- web/src/person_management/index.dart
- web/src/template_driven_forms/index.dart
diff --git a/modules/examples/src/observable_models/app.dart b/modules/examples/src/observable_models/app.dart
new file mode 100644
index 0000000000..86badd307c
--- /dev/null
+++ b/modules/examples/src/observable_models/app.dart
@@ -0,0 +1,40 @@
+library benchmarks.src.naive_infinite_scroll.app;
+
+import "package:angular2/src/facade/collection.dart" show List, ListWrapper;
+import "package:angular2/directives.dart" show NgIf, NgFor;
+import "scroll_area.dart" show ScrollAreaComponent;
+import "package:angular2/angular2.dart" show Component, Directive, View, IterableDiffers, SkipSelf, Binding;
+import "package:angular2/src/directives/observable_list_diff.dart" show ObservableListDiffFactory;
+import 'package:observe/observe.dart' show ObservableList;
+
+createDiffers(IterableDiffers parent) {
+ return IterableDiffers.create([const ObservableListDiffFactory()], parent);
+}
+
+const binding = const Binding(IterableDiffers,
+ toFactory: createDiffers, deps: const [ const[IterableDiffers, const SkipSelf()]]);
+
+@Component(
+ selector: "scroll-app",
+ bindings: const [binding]
+)
+@View(directives: const [ScrollAreaComponent, NgIf, NgFor], template: '''
+
+
+
+
+
+
Following tables are only here to add weight to the UI:
+
+
+
''')
+class App {
+ List scrollAreas;
+ App() {
+ var scrollAreas = [];
+ for (var i = 0; i < 300; i++) {
+ scrollAreas.add(i);
+ }
+ this.scrollAreas = new ObservableList.from(scrollAreas);
+ }
+}
diff --git a/modules/examples/src/observable_models/cells.dart b/modules/examples/src/observable_models/cells.dart
new file mode 100644
index 0000000000..064f126391
--- /dev/null
+++ b/modules/examples/src/observable_models/cells.dart
@@ -0,0 +1,135 @@
+library benchmarks.src.naive_infinite_scroll.cells;
+
+import "package:angular2/src/facade/collection.dart"
+ show List, ListWrapper, Map;
+import "common.dart"
+ show Company, Opportunity, Offering, Account, CustomDate, STATUS_LIST;
+import "package:angular2/directives.dart" show NgFor;
+import "package:angular2/angular2.dart" show Component, Directive, View;
+
+class HasStyle {
+ int cellWidth;
+ HasStyle() {}
+ set width(int w) {
+ this.cellWidth = w;
+ }
+}
+@Component(
+ selector: "company-name",
+ properties: const ["width: cell-width", "company"],
+ changeDetection: "ON_PUSH_OBSERVE"
+)
+@View(
+ directives: const [],
+ template: '''{{company.name}}
'''
+)
+class CompanyNameComponent extends HasStyle {
+ Company company;
+}
+@Component(
+ selector: "opportunity-name",
+ properties: const ["width: cell-width", "opportunity"],
+ changeDetection: "ON_PUSH_OBSERVE"
+)
+@View(
+ directives: const [],
+ template: '''{{opportunity.name}}
'''
+)
+class OpportunityNameComponent extends HasStyle {
+ Opportunity opportunity;
+}
+@Component(
+ selector: "offering-name",
+ properties: const ["width: cell-width", "offering"],
+ changeDetection: "ON_PUSH_OBSERVE"
+)
+@View(
+ directives: const [],
+ template: '''{{offering.name}}
'''
+)
+class OfferingNameComponent extends HasStyle {
+ Offering offering;
+}
+class Stage {
+ String name;
+ bool isDisabled;
+ String backgroundColor;
+ Function apply;
+}
+@Component(
+ selector: "stage-buttons",
+ properties: const ["width: cell-width", "offering"],
+ changeDetection: "ON_PUSH_OBSERVE"
+)
+@View(directives: const [NgFor], template: '''
+
+
+
''')
+class StageButtonsComponent extends HasStyle {
+ Offering _offering;
+ List stages;
+ Offering get offering {
+ return this._offering;
+ }
+ set offering(Offering offering) {
+ this._offering = offering;
+ this._computeStageButtons();
+ }
+ setStage(Stage stage) {
+ this._offering.status = stage.name;
+ this._offering.name = this._offering.name + "!";
+ this._computeStageButtons();
+ }
+ _computeStageButtons() {
+ var disabled = true;
+ this.stages = ListWrapper.clone(STATUS_LIST.map((status) {
+ var isCurrent = this._offering.status == status;
+ var stage = new Stage();
+ stage.name = status;
+ stage.isDisabled = disabled;
+ stage.backgroundColor = disabled ? "#DDD" : isCurrent ? "#DDF" : "#FDD";
+ if (isCurrent) {
+ disabled = false;
+ }
+ return stage;
+ }).toList());
+ }
+}
+@Component(
+ selector: "account-cell",
+ properties: const ["width: cell-width", "account"],
+ changeDetection: "ON_PUSH_OBSERVE"
+)
+@View(directives: const [], template: '''
+ ''')
+class AccountCellComponent extends HasStyle {
+ Account account;
+}
+@Component(
+ selector: "formatted-cell",
+ properties: const ["width: cell-width", "value"],
+ changeDetection: "ON_PUSH_OBSERVE"
+)
+@View(
+ directives: const [],
+ template: '''{{formattedValue}}
''')
+class FormattedCellComponent extends HasStyle {
+ String formattedValue;
+ set value(value) {
+ if (value is CustomDate) {
+ this.formattedValue =
+ '''${ value . month}/${ value . day}/${ value . year}''';
+ } else {
+ this.formattedValue = value.toString();
+ }
+ }
+}
diff --git a/modules/examples/src/observable_models/common.dart b/modules/examples/src/observable_models/common.dart
new file mode 100644
index 0000000000..2da81ed071
--- /dev/null
+++ b/modules/examples/src/observable_models/common.dart
@@ -0,0 +1,241 @@
+library benchmarks.src.naive_infinite_scroll.common;
+
+import "package:angular2/src/facade/math.dart" show Math;
+import "package:angular2/src/facade/collection.dart";
+import 'package:observe/observe.dart';
+import 'dart:collection';
+import 'dart:async';
+
+var ITEMS = 1000;
+var ITEM_HEIGHT = 40;
+var VISIBLE_ITEMS = 17;
+var HEIGHT = ITEMS * ITEM_HEIGHT;
+var VIEW_PORT_HEIGHT = ITEM_HEIGHT * VISIBLE_ITEMS;
+var COMPANY_NAME_WIDTH = 100;
+var OPPORTUNITY_NAME_WIDTH = 100;
+var OFFERING_NAME_WIDTH = 100;
+var ACCOUNT_CELL_WIDTH = 50;
+var BASE_POINTS_WIDTH = 50;
+var KICKER_POINTS_WIDTH = 50;
+var STAGE_BUTTONS_WIDTH = 220;
+var BUNDLES_WIDTH = 120;
+var DUE_DATE_WIDTH = 100;
+var END_DATE_WIDTH = 100;
+var AAT_STATUS_WIDTH = 100;
+var ROW_WIDTH = COMPANY_NAME_WIDTH +
+ OPPORTUNITY_NAME_WIDTH +
+ OFFERING_NAME_WIDTH +
+ ACCOUNT_CELL_WIDTH +
+ BASE_POINTS_WIDTH +
+ KICKER_POINTS_WIDTH +
+ STAGE_BUTTONS_WIDTH +
+ BUNDLES_WIDTH +
+ DUE_DATE_WIDTH +
+ END_DATE_WIDTH +
+ AAT_STATUS_WIDTH;
+var STATUS_LIST = ["Planned", "Pitched", "Won", "Lost"];
+var AAT_STATUS_LIST = ["Active", "Passive", "Abandoned"];
+// Imitate Streamy entities.
+
+// Just a non-trivial object. Nothing fancy or correct.
+class CustomDate {
+ num year;
+ num month;
+ num day;
+ CustomDate(num y, num m, num d) {
+ this.year = y;
+ this.month = m;
+ this.day = d;
+ }
+ CustomDate addDays(num days) {
+ var newDay = this.day + days;
+ var newMonth = this.month + Math.floor(newDay / 30);
+ newDay = newDay % 30;
+ var newYear = this.year + Math.floor(newMonth / 12);
+ return new CustomDate(newYear, newMonth, newDay);
+ }
+ static CustomDate now() {
+ return new CustomDate(2014, 1, 28);
+ }
+}
+class RawEntity extends Object
+ with MapMixin
+ implements ObservableMap {
+ ObservableMap _data = new ObservableMap();
+
+ @override
+ Iterable get keys => _data.keys;
+
+ @override
+ void clear() {
+ _data.clear();
+ }
+
+ @override
+ operator [](String key) {
+ if (!key.contains('.')) {
+ return _data[key];
+ }
+ var pieces = key.split('.');
+ var last = pieces.removeLast();
+ var target = _resolve(pieces, this);
+ if (target == null) {
+ return null;
+ }
+ return target[last];
+ }
+
+ @override
+ operator []=(String key, value) {
+ if (!key.contains('.')) {
+ _data[key] = value;
+ return;
+ }
+ var pieces = key.split('.');
+ var last = pieces.removeLast();
+ var target = _resolve(pieces, this);
+ target[last] = value;
+ }
+
+ dynamic get(String name) { return this[name]; }
+
+ set(String name, dynamic value) { this[name] = value; }
+
+ @override
+ remove(String key) {
+ if (!key.contains('.')) {
+ return _data.remove(key);
+ }
+ var pieces = key.split('.');
+ var last = pieces.removeLast();
+ var target = _resolve(pieces, this);
+ return target.remove(last);
+ }
+
+ _resolve(List pieces, start) {
+ var cur = start;
+ for (var i = 0; i < pieces.length; i++) {
+ cur = cur[pieces[i]];
+ if (cur == null) {
+ return null;
+ }
+ }
+ return cur;
+ }
+
+ @override
+ Stream> get changes => _data.changes;
+ @override
+ bool get hasObservers => _data.hasObservers;
+ @override
+ bool deliverChanges() => _data.deliverChanges();
+ @override
+ notifyPropertyChange(Symbol field, Object oldValue, Object newValue) =>
+ _data.notifyPropertyChange(field, oldValue, newValue);
+ @override
+ void notifyChange(ChangeRecord record) {
+ _data.notifyChange(record);
+ }
+
+ @override
+ void observed() {
+ _data.observed();
+ }
+
+ @override
+ void unobserved() {
+ _data.observed();
+ }
+}
+class Company extends RawEntity {
+ String get name {
+ return this.get("name");
+ }
+ set name(String val) {
+ this.set("name", val);
+ }
+}
+class Offering extends RawEntity {
+ String get name {
+ return this.get("name");
+ }
+ set name(String val) {
+ this.set("name", val);
+ }
+ Company get company {
+ return this.get("company");
+ }
+ set company(Company val) {
+ this.set("company", val);
+ }
+ Opportunity get opportunity {
+ return this.get("opportunity");
+ }
+ set opportunity(Opportunity val) {
+ this.set("opportunity", val);
+ }
+ Account get account {
+ return this.get("account");
+ }
+ set account(Account val) {
+ this.set("account", val);
+ }
+ num get basePoints {
+ return this.get("basePoints");
+ }
+ set basePoints(num val) {
+ this.set("basePoints", val);
+ }
+ num get kickerPoints {
+ return this.get("kickerPoints");
+ }
+ set kickerPoints(num val) {
+ this.set("kickerPoints", val);
+ }
+ String get status {
+ return this.get("status");
+ }
+ set status(String val) {
+ this.set("status", val);
+ }
+ String get bundles {
+ return this.get("bundles");
+ }
+ set bundles(String val) {
+ this.set("bundles", val);
+ }
+ CustomDate get dueDate {
+ return this.get("dueDate");
+ }
+ set dueDate(CustomDate val) {
+ this.set("dueDate", val);
+ }
+ CustomDate get endDate {
+ return this.get("endDate");
+ }
+ set endDate(CustomDate val) {
+ this.set("endDate", val);
+ }
+ String get aatStatus {
+ return this.get("aatStatus");
+ }
+ set aatStatus(String val) {
+ this.set("aatStatus", val);
+ }
+}
+class Opportunity extends RawEntity {
+ String get name {
+ return this.get("name");
+ }
+ set name(String val) {
+ this.set("name", val);
+ }
+}
+class Account extends RawEntity {
+ num get accountId {
+ return this.get("accountId");
+ }
+ set accountId(num val) {
+ this.set("accountId", val);
+ }
+}
diff --git a/modules/examples/src/observable_models/index.dart b/modules/examples/src/observable_models/index.dart
new file mode 100644
index 0000000000..7585d7daf9
--- /dev/null
+++ b/modules/examples/src/observable_models/index.dart
@@ -0,0 +1,14 @@
+library benchmarks.src.naive_infinite_scroll.index;
+
+import "package:angular2/bootstrap.dart" show bootstrap;
+import "app.dart" show App;
+import "package:angular2/src/core/compiler/view_pool.dart"
+ show APP_VIEW_POOL_CAPACITY;
+import "package:angular2/di.dart" show bind;
+
+main() {
+ bootstrap(App, createBindings());
+}
+List createBindings() {
+ return [bind(APP_VIEW_POOL_CAPACITY).toValue(100000)];
+}
diff --git a/modules/examples/src/observable_models/index.html b/modules/examples/src/observable_models/index.html
new file mode 100644
index 0000000000..15ad2e6ee9
--- /dev/null
+++ b/modules/examples/src/observable_models/index.html
@@ -0,0 +1,13 @@
+
+
+
+ AngularDart Scrolling Benchmark
+
+
+
+
+
+
+
+
+
diff --git a/modules/examples/src/observable_models/random_data.dart b/modules/examples/src/observable_models/random_data.dart
new file mode 100644
index 0000000000..5c74edaa56
--- /dev/null
+++ b/modules/examples/src/observable_models/random_data.dart
@@ -0,0 +1,86 @@
+library benchmarks.src.naive_infinite_scroll.random_data;
+
+import "package:angular2/src/facade/lang.dart" show StringWrapper;
+import "package:angular2/src/facade/collection.dart" show List, ListWrapper;
+import "common.dart"
+ show
+ CustomDate,
+ Offering,
+ Company,
+ Opportunity,
+ Account,
+ STATUS_LIST,
+ AAT_STATUS_LIST;
+
+List generateOfferings(int count) {
+ var res = [];
+ for (var i = 0; i < count; i++) {
+ res.add(generateOffering(i));
+ }
+ return res;
+}
+Offering generateOffering(int seed) {
+ var res = new Offering();
+ res.name = generateName(seed++);
+ res.company = generateCompany(seed++);
+ res.opportunity = generateOpportunity(seed++);
+ res.account = generateAccount(seed++);
+ res.basePoints = seed % 10;
+ res.kickerPoints = seed % 4;
+ res.status = STATUS_LIST[seed % STATUS_LIST.length];
+ res.bundles = randomString(seed++);
+ res.dueDate = randomDate(seed++);
+ res.endDate = randomDate(seed++, res.dueDate);
+ res.aatStatus = AAT_STATUS_LIST[seed % AAT_STATUS_LIST.length];
+ return res;
+}
+Company generateCompany(int seed) {
+ var res = new Company();
+ res.name = generateName(seed);
+ return res;
+}
+Opportunity generateOpportunity(int seed) {
+ var res = new Opportunity();
+ res.name = generateName(seed);
+ return res;
+}
+Account generateAccount(int seed) {
+ var res = new Account();
+ res.accountId = seed;
+ return res;
+}
+var names = [
+ "Foo",
+ "Bar",
+ "Baz",
+ "Qux",
+ "Quux",
+ "Garply",
+ "Waldo",
+ "Fred",
+ "Plugh",
+ "Xyzzy",
+ "Thud",
+ "Cruft",
+ "Stuff"
+];
+String generateName(int seed) {
+ return names[seed % names.length];
+}
+var offsets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+CustomDate randomDate(int seed, [CustomDate minDate = null]) {
+ if (minDate == null) {
+ minDate = CustomDate.now();
+ }
+ return minDate.addDays(offsets[seed % offsets.length]);
+}
+var stringLengths = [5, 7, 9, 11, 13];
+var charCodeOffsets = [0, 1, 2, 3, 4, 5, 6, 7, 8];
+String randomString(int seed) {
+ var len = stringLengths[seed % 5];
+ var str = "";
+ for (var i = 0; i < len; i++) {
+ str += StringWrapper.fromCharCode(97 + charCodeOffsets[seed % 9] + i);
+ }
+ return str;
+}
diff --git a/modules/examples/src/observable_models/scroll_area.dart b/modules/examples/src/observable_models/scroll_area.dart
new file mode 100644
index 0000000000..0171163f2b
--- /dev/null
+++ b/modules/examples/src/observable_models/scroll_area.dart
@@ -0,0 +1,71 @@
+library benchmarks.src.naive_infinite_scroll.scroll_area;
+
+import "package:angular2/src/facade/collection.dart" show ListWrapper;
+import "package:angular2/src/facade/math.dart" show Math;
+import "package:angular2/angular2.dart" show Component, Directive, View;
+import "common.dart"
+ show
+ Offering,
+ ITEMS,
+ ITEM_HEIGHT,
+ VISIBLE_ITEMS,
+ VIEW_PORT_HEIGHT,
+ ROW_WIDTH,
+ HEIGHT;
+import "random_data.dart" show generateOfferings;
+import "scroll_item.dart" show ScrollItemComponent;
+import "package:angular2/directives.dart" show NgFor;
+
+@Component(selector: "scroll-area", changeDetection: "ON_PUSH_OBSERVE")
+@View(directives: const [ScrollItemComponent, NgFor], template: '''
+ ''')
+class ScrollAreaComponent {
+ List _fullList;
+ List visibleItems;
+ num viewPortHeight;
+ var paddingDiv;
+ var innerDiv;
+ ScrollAreaComponent() {
+ this._fullList = generateOfferings(ITEMS);
+ this.visibleItems = [];
+ this.viewPortHeight = VIEW_PORT_HEIGHT;
+ this.onScroll(null);
+ }
+ onScroll(evt) {
+ var scrollTop = 0;
+ if (evt != null) {
+ var scrollDiv = evt.target;
+ if (this.paddingDiv == null) {
+ this.paddingDiv = scrollDiv.querySelector("#padding");
+ }
+ if (this.innerDiv == null) {
+ this.innerDiv = scrollDiv.querySelector("#inner");
+ this.innerDiv.style.setProperty("width", '''${ ROW_WIDTH}px''');
+ }
+ scrollTop = scrollDiv.scrollTop;
+ }
+ var iStart = Math.floor(scrollTop / ITEM_HEIGHT);
+ var iEnd = Math.min(iStart + VISIBLE_ITEMS + 1, this._fullList.length);
+ var padding = iStart * ITEM_HEIGHT;
+ if (this.innerDiv != null) {
+ this.innerDiv.style.setProperty("height", '''${ HEIGHT - padding}px''');
+ }
+ if (this.paddingDiv != null) {
+ this.paddingDiv.style.setProperty("height", '''${ padding}px''');
+ }
+ this.visibleItems = ListWrapper.slice(this._fullList, iStart, iEnd);
+ }
+}
diff --git a/modules/examples/src/observable_models/scroll_item.dart b/modules/examples/src/observable_models/scroll_item.dart
new file mode 100644
index 0000000000..124ae17fec
--- /dev/null
+++ b/modules/examples/src/observable_models/scroll_item.dart
@@ -0,0 +1,116 @@
+library benchmarks.src.naive_infinite_scroll.scroll_item;
+
+import "cells.dart"
+ show
+ CompanyNameComponent,
+ OpportunityNameComponent,
+ OfferingNameComponent,
+ StageButtonsComponent,
+ AccountCellComponent,
+ FormattedCellComponent;
+import "package:angular2/angular2.dart" show Component, Directive, View;
+import "common.dart"
+ show
+ Offering,
+ ITEM_HEIGHT,
+ COMPANY_NAME_WIDTH,
+ OPPORTUNITY_NAME_WIDTH,
+ OFFERING_NAME_WIDTH,
+ ACCOUNT_CELL_WIDTH,
+ BASE_POINTS_WIDTH,
+ KICKER_POINTS_WIDTH,
+ STAGE_BUTTONS_WIDTH,
+ BUNDLES_WIDTH,
+ DUE_DATE_WIDTH,
+ END_DATE_WIDTH,
+ AAT_STATUS_WIDTH;
+
+@Component(selector: "scroll-item", properties: const ["offering"], changeDetection: "ON_PUSH_OBSERVE")
+@View(
+ directives: const [
+ CompanyNameComponent,
+ OpportunityNameComponent,
+ OfferingNameComponent,
+ StageButtonsComponent,
+ AccountCellComponent,
+ FormattedCellComponent
+],
+ template: '''
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
''')
+class ScrollItemComponent {
+ Offering offering;
+ num itemHeight;
+ ScrollItemComponent() {
+ this.itemHeight = ITEM_HEIGHT;
+ }
+ get companyNameWidth {
+ return COMPANY_NAME_WIDTH;
+ }
+ get opportunityNameWidth {
+ return OPPORTUNITY_NAME_WIDTH;
+ }
+ get offeringNameWidth {
+ return OFFERING_NAME_WIDTH;
+ }
+ get accountCellWidth {
+ return ACCOUNT_CELL_WIDTH;
+ }
+ get basePointsWidth {
+ return BASE_POINTS_WIDTH;
+ }
+ get kickerPointsWidth {
+ return KICKER_POINTS_WIDTH;
+ }
+ get stageButtonsWidth {
+ return STAGE_BUTTONS_WIDTH;
+ }
+ get bundlesWidth {
+ return BUNDLES_WIDTH;
+ }
+ get dueDateWidth {
+ return DUE_DATE_WIDTH;
+ }
+ get endDateWidth {
+ return END_DATE_WIDTH;
+ }
+ get aatStatusWidth {
+ return AAT_STATUS_WIDTH;
+ }
+}
diff --git a/modules/examples/src/observable_models/url_params_to_form.js b/modules/examples/src/observable_models/url_params_to_form.js
new file mode 100644
index 0000000000..60adbd3ff8
--- /dev/null
+++ b/modules/examples/src/observable_models/url_params_to_form.js
@@ -0,0 +1,20 @@
+// helper script that will read out the url parameters
+// and store them in appropriate form fields on the page
+(function() {
+ var regex = /(\w+)=(\w+)/g;
+ var search = decodeURIComponent(location.search);
+ while (match = regex.exec(search)) {
+ var name = match[1];
+ var value = match[2];
+ var els = document.querySelectorAll('input[name="'+name+'"]');
+ var el;
+ for (var i=0; i