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: ''' +
+ + {{account.accountId}} + +
''') +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