Permalink
Browse files

feature: MdlComponent has 'waitForChild' now - great for slow renderi…

…ng components
  • Loading branch information...
MikeMitterer committed Apr 27, 2017
1 parent be1f13a commit 94bdfdbb758177ad2c18715048b68a26563eb9a4
@@ -88,14 +88,21 @@ class MaterialTextfield extends MdlComponent with FallbackFormatter {
/// Update text field value.
void change(final String value) {
if (value != null && value != _relaxedInput.value) {
int selStart = (_relaxedInput).selectionStart;
final bool isSelectionSupported = (_relaxedInput as dom.InputElement).type == "text";
int selStart = 0;
if(isSelectionSupported) {
selStart = (_relaxedInput).selectionStart;
}
void _placeTheCursorWhereItWasBefore(final int position) {
(_relaxedInput).setSelectionRange(position,position);
}
_relaxedInput.value = MaterialFormatter.widget(element).format(value);
_placeTheCursorWhereItWasBefore(selStart);
if(isSelectionSupported) {
_placeTheCursorWhereItWasBefore(selStart);
}
}
_updateClasses();
@@ -104,14 +104,60 @@ abstract class MdlComponent extends Object with MdlEventListener {
/// Searches for child of [element] based on the given [selector]
///
/// Shortcut to [element.querySelector]
dom.Element query(final String selector) {
/// You can turn off error logging by setting [logError] to false
dom.Element query(final String selector,{ final bool logError: true }) {
final dom.Element result = element.querySelector(selector);
if(result == null) {
if(result == null && logError) {
_logger.warning("Could not find '$selector' within $element!");
}
return result;
}
/// Waits until the first descendant element of [element] that matches the
/// specified selector is available.
///
/// [wait] defines the time between DOM-queries
/// If the child is not ready within [maxIterations] the function stops
/// with a [TimeoutException]
///
/// try {
/// final dom.CanvasElement canvas = await waitForChild<dom.CanvasElement>("canvas");
/// _chart = new Chart(canvas , _chartConfig);
///
/// } on TimeoutException catch(e) {
/// _logger.shout(e.message);
/// }
///
FutureOr<T> waitForChild<T>(final String selector,{
final Duration wait: const Duration(milliseconds: 50),
final int maxIterations: 10 }) async {
Validate.notBlank(selector);
// Maybe the child is already in the DOM then return it ASAP
T child = query(selector) as T;
if(child != null) {
return child;
}
int iterationCounter = 0;
await Future.doWhile( () async {
await new Future.delayed(wait, () {
child = query(selector) as T;
iterationCounter++;
});
return child == null && iterationCounter < maxIterations;
});
if(iterationCounter >= maxIterations) {
throw new TimeoutException(
"Could not find '$selector' within ${element}, "
"gave up after $maxIterations retries!");
}
return child;
}
//- private -----------------------------------------------------------------------------------
MdlComponent _getMdlParent(final dom.HtmlElement element) {
@@ -332,10 +332,10 @@ abstract class MaterialDialog extends Object with TemplateComponent, MdlEventLis
container.onClick.listen((final dom.MouseEvent event) {
_logger.info("click on container");
event.preventDefault();
event.stopPropagation();
if (event.target == container) {
event.preventDefault();
event.stopPropagation();
close(MdlDialogStatus.CLOSED_BY_BACKDROPCLICK);
}
});
@@ -176,7 +176,7 @@ class MaterialRepeat extends MdlTemplateComponent {
_logger.warning(
"Could not find $item in ${_MaterialRepeatConstant.WIDGET_SELECTOR}, so nothing to remove here...");
_logger.warning("Number of items in list: ${_items.length}, First: ${_items.first.name}");
_logger.warning("Number of items in list: ${_items.length}, First: ${_items.first.client_name}");
throw "Could not find $item in internal item list!";
}
@@ -192,7 +192,7 @@ class MaterialRepeat extends MdlTemplateComponent {
final int index1 = _items.indexOf(item1);
final int index2 = _items.indexOf(item2);
_logger.fine("Swap: ${item1.name} ($index1) -> ${item2.name} ($index2)");
_logger.fine("Swap: ${item1.client_name} ($index1) -> ${item2.client_name} ($index2)");
_items[index1] = item2;
_items[index2] = item1;
View
@@ -19,15 +19,23 @@
library mdl.test.unit.config;
import 'dart:async';
import 'package:logging/logging.dart';
import 'package:logging_handlers/logging_handlers_shared.dart';
import "package:mdl/mdl.dart";
void configLogging() {
Future prepareMdlTest(Future additionalRegistration()) async {
registerApplicationComponents();
await additionalRegistration();
await componentHandler().run();
}
void configLogging({final Level defaultLogLevel: Level.OFF }) {
//hierarchicalLoggingEnabled = false; // set this to true - its part of Logging SDK
// now control the logging.
// Turn off all logging first
Logger.root.level = Level.INFO;
Logger.root.level = defaultLogLevel;
Logger.root.onRecord.listen(new LogPrintHandler());
}
@@ -0,0 +1,122 @@
@TestOn("content-shell")
library test.unit.core.mdlcomponent;
import 'dart:async';
import 'dart:html' as dom;
import 'package:test/test.dart';
import 'package:di/di.dart' as di;
import 'package:mdl/mdl.dart';
import 'package:mdl/mdlmock.dart' as mdlmock;
//import 'package:logging/logging.dart';
import '../config.dart';
part '_lib/SlowComponent.dart';
main() async {
//final Logger _logger = new Logger("test.MdlComponent");
configLogging();
final DomRenderer renderer = new DomRenderer();
final dom.DivElement parent = new dom.DivElement();
final String html = '''
<div class="mdl-panel">
<button id="first" class="mdl-button mdl-ripple-effect">First button</button>
<button id="second" class="mdl-button mdl-ripple-effect">Second button</button>
<canvas width="100" height="100">Loading</canvas>
<slow-component></slow-component>
</div>
'''.trim().replaceAll(new RegExp(r"\s+")," ");
group('MdlComponent', () {
setUp(() async {
mdlmock.setUpInjector();
mdlmock.module((final di.Module module) {
//module.bind(SignalService, toImplementation: SignalServiceImpl);
});
mdlmock.mockComponentHandler(mdlmock.injector(), mdlmock.componentFactory());
await prepareMdlTest( () async {
await registerMaterialButton();
await registerSlowComponent();
await registerMdlTemplateComponents();
});
});
test('> Registration', () async {
final dom.HtmlElement element = await renderer.render(parent,html);
await componentHandler().upgradeElement(element);
final MaterialButton button = MaterialButton.widget(element.querySelector("#second"));
expect(button,isNotNull);
}); // end of 'Registration' test
test('> SlowComponent', () async {
final dom.HtmlElement element = await renderer.render(parent,html);
await componentHandler().upgradeElement(element);
final SlowComponent component = SlowComponent.widget(element.querySelector("slow-component"));
// It takes a while until DOM is rendered for SlowComponent
// (requestAnimationFrame is used for rendering)
expect(component.element.innerHtml,isEmpty);
// wait for 500ms to give requestAnimationFrame a chance to do its work
// insertElement + removes mdl-content__loading flag from element
new Future.delayed(new Duration(milliseconds: 500), expectAsync0( () {
final SlowComponent component = SlowComponent.widget(element.querySelector("slow-component"));
//_logger.info(component.element.innerHtml);
expect(component,isNotNull);
}));
}); // end of 'SlowComponent' test
test('> waitForChild', () async {
final dom.HtmlElement element = await renderer.render(parent,html);
await componentHandler().upgradeElement(element);
final SlowComponent component = SlowComponent.widget(element.querySelector("slow-component"));
// It takes a while until DOM is ready for SlowComponent (~400ms)
// (requestAnimationFrame is used for rendering)
expect(component.element.innerHtml,isEmpty);
// mdl-button is a child within <slow-component>
final dom.ButtonElement buttonInTemplate = await component.waitForChild(".mdl-button");
expect(buttonInTemplate,isNotNull);
}); // end of 'waitForChild' test
test('> Timeout', () async {
final dom.HtmlElement element = await renderer.render(parent,html);
await componentHandler().upgradeElement(element);
final SlowComponent component = SlowComponent.widget(element.querySelector("slow-component"));
// It takes a while until DOM is ready for SlowComponent (~400ms)
// (requestAnimationFrame is used for rendering)
expect(component.element.innerHtml,isEmpty);
// mdl-button is a child within <slow-component>
// Remember: Rendering takes ~400ms!
bool foundException = false;
try {
// Wait only 50ms (default wait-time (50ms) times maxIterations)
await component.waitForChild(".mdl-button", maxIterations: 1);
} on TimeoutException catch(_) {
foundException = true;
}
expect(foundException,isTrue);
}); // end of 'Timeout' test
});
// End of 'MdlComponent' group
}
// - Helper --------------------------------------------------------------------------------------
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2017, Michael Mitterer (office@mikemitterer.at),
* IT-Consulting and Development Limited.
*
* All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
part of test.unit.core.mdlcomponent;
/*
/// Basic DI configuration for this Component or Service
/// Usage:
/// class MainModule extends di.Module {
/// MainModule() {
/// install(new SlowComponentModule());
/// }
/// }
class SlowComponentModule extends di.Module {
SlowComponentModule() {
// bind(DeviceProxy);
}
}
*/
/// Controller-View for <test-component></test-component>
///
/// Use this Component to test slow rendering
@MdlComponentModel
class SlowComponent extends MdlTemplateComponent {
/// Make rendering really! slow
static const Duration RENDER_DELAY = const Duration(milliseconds: 400);
static const _SlowComponentCssClasses _cssClasses = const _SlowComponentCssClasses();
SlowComponent.fromElement(final dom.HtmlElement element,final di.Injector injector)
: super(element,injector) {
_init();
}
static SlowComponent widget(final dom.HtmlElement element) => mdlComponent(element,SlowComponent) as SlowComponent;
// Central Element - by default this is where test-component can be found (element)
// html.Element get hub => inputElement;
// - EventHandler -----------------------------------------------------------------------------
void handleButtonClick(final dom.Event event) {
event.preventDefault();
}
//- private -----------------------------------------------------------------------------------
void _init() {
// Recommended - add SELECTOR as class
element.classes.add(_SlowComponentConstant.WIDGET_SELECTOR);
// We slow down the rendering process
new Future.delayed(RENDER_DELAY, () {
render().then( (_) {
// DOM for Component is ready!
});
});
element.classes.add(_cssClasses.IS_UPGRADED);
}
//- Template -----------------------------------------------------------------------------------
@override
final String template = """
<div>
Your SlowComponent-Component works!
<button class="mdl-button mdl-js-button mdl-button--colored mdl-js-ripple-effect"
data-mdl-click="handleButtonClick(\$event)">Click me!</button>
</div>
""".trim().replaceAll(new RegExp(r"\s+")," ");
}
/// Registers the SlowComponent-Component
///
/// main() {
/// registerSlowComponent();
/// ...
/// }
///
void registerSlowComponent() {
final MdlConfig config = new MdlWidgetConfig<SlowComponent>(
_SlowComponentConstant.WIDGET_SELECTOR,
(final dom.HtmlElement element,final di.Injector injector) => new SlowComponent.fromElement(element,injector)
);
// If you want <test-component></test-component> set selectorType to SelectorType.TAG.
// If you want <div test-component></div> set selectorType to SelectorType.ATTRIBUTE.
// By default it's used as a class name. (<div class="test-component"></div>)
config.selectorType = SelectorType.TAG;
componentHandler().register(config);
}
//- private Classes ----------------------------------------------------------------------------------------------------
/// Store strings for class names defined by this component that are used in
/// Dart. This allows us to simply change it in one place should we
/// decide to modify at a later date.
class _SlowComponentCssClasses {
final String IS_UPGRADED = 'is-upgraded';
const _SlowComponentCssClasses(); }
/// Store constants in one place so they can be updated easily.
class _SlowComponentConstant {
static const String WIDGET_SELECTOR = "slow-component";
const _SlowComponentConstant();
}
Oops, something went wrong.

0 comments on commit 94bdfdb

Please sign in to comment.