
Mixin' it up
In object-oriented languages, it is useful to build on one class to create a more specialized related class. For example, in the text editor, the base dialog class was extended to create an alert and confirm pop ups. What if we want to share some functionality but do not want inheritance occurring between the classes?
Aggregation can solve this problem to some extent:
class A { classb usefulObject; }
The downside is that this requires a longer reference to use:
new A().usefulObject.handyMethod();
This problem has been solved in Dart (and other languages) by having a mixin
class do this job, allowing the sharing of functionality without forced inheritance or clunky aggregation.
In Dart, a mixin
must meet the following requirements:
- No constructors can be in the class declaration.
- The base class of the
mixin
must be the Object. - No calls to a super class are made.
mixins
are really just classes that are malleable enough to fit into the class hierarchy at any point. A use case for a mixin
may be serialization fields and methods that could be required on several classes in an application and that are not part of any inheritance chain.
abstract class Serialisation { void save() { //Implementation here. } void load(String filename) { //Implementation here. } }
The with
keyword is used to declare that a class is using a mixin
:
class ImageRecord extends Record with Serialisation
If the class does not have an explicit base class, it is required to specify an Object
:
class StorageReports extends Object with Serialization
Note
In Dart, everything is an object, even basic types such as num
are objects and not primitive types. The classes int
and double
are subtypes of num
. This is important to know as other languages have different behaviors. Let's consider a real example of this:
main() { int i; print("$i"); }
In a language such as Java, the expected output would be 0; however, the output in Dart is null. If a value is expected from a variable, it is always good practice to initialize it!
For the classes Slide
and SlideShow
, we will use a mixin
from the source file lifecyclemixin.dart
to record a creation and an editing timestamp:
abstract class LifecycleTracker { DateTime _created; DateTime _edited; recordCreateTimestamp() => _created = new DateTime.now(); updateEditTimestamp() => _edited = new DateTime.now(); DateTime get created => _created; DateTime get lastEdited => _edited; }
To use the mixin
, the recordCreateTimestamp
method can be called from the constructor and the updateEditTimestamp
from the main edit method. For slides, it makes sense just to record the creation. For the SlideShow
class, both the creation and update will be tracked.
Defining the core classes
The SlideShow
class is largely a container object for a list of Slide
objects and uses the mixin LifecycleTracker
:
class SlideShow extends Object with LifecycleTracker { List<Slide> _slides; List<Slide> get slides => _slides; ...
The Slide
class stores the string for the title and a list of strings for the bullet points. The URL for any image is also stored as a string:
class Slide extends Object with LifecycleTracker { String titleText = ""; List<String> bulletPoints; String imageUrl = ""; ...
A simple constructor takes the titleText
as a parameter and initializes the bulletPoints
list.
Tip
If you want to focus on just the code when in WebStorm, double-click on the filename title of the tab to expand the source code to the entire window. Double-click again to return to the original layout.
For even more focus on the code, go to the View menu and click on Enter Distraction Free Mode.
Transforming data into HTML
To add the Slide
object instance into an HTML document, the strings need to be converted into instances of HTML elements to be added to the DOM (Document Object Model). The getSlideContents()
method constructs and returns the entire slide as a single object:
DivElement getSlideContents() { DivElement slide = new DivElement(); DivElement title = new DivElement(); DivElement bullets = new DivElement(); title.appendHtml("<h1>$titleText</h1>"); slide.append(title); if (imageUrl.length > 0) { slide.appendHtml("<img src=\"$imageUrl\" /><br/>"); } bulletPoints.forEach((bp) { if (bp.trim().length > 0) { bullets.appendHtml("<li>$bp</li>"); } }); slide.append(bullets); return slide; }
The Div
elements are constructed as objects (instances of DivElement
), while the content is added as literal HTML statements. The method appendHtml
is used for this particular task as it renders HTML tags in the text. The regular method appendText
puts the entire literal text string (including plain unformatted text of the HTML tags) into the element.
So, what exactly is the difference? The method appendHtml
evaluates the supplied HTML and adds the resultant object node to the nodes of the parent element, which is rendered in the browser as usual. The method appendText
is useful, for example, to prevent user-supplied content affecting the format of the page and preventing malicious code being injected into a web page.
Editing the presentation
When the source is updated, the presentation is updated via the onKeyUp
event. This was used in the text editor project to trigger a save to local storage.
This is carried out in the build
method of the SlideShow
class, and follows the pattern we discussed in parsing the presentation:
build(String src) { updateEditTimestamp(); _slides = new List<Slide>(); Slide nextSlide; src.split("\n").forEach((String line) { if (line.trim().length > 0) { // Title - also marks start of the next slide. if (line.startsWith("#")) { nextSlide = new Slide(line.substring(1)); _slides.add(nextSlide); } if (nextSlide != null) { if (line.startsWith("+")) { nextSlide.bulletPoints.add(line.substring(1)); } else if (line.startsWith("!")) { nextSlide.imageUrl = line.substring(1); } } } }); }
As an alternative to the startsWith
method, the square bracket []
operator could be used for line [0]
to retrieve the first character. The startsWith
method can also take a regular expression or a string to match, as well as a starting index. Refer to the dart:core
documentation for more information. For the purposes of parsing the presentation, the startsWith
method is more readable.
Displaying the current slide
The slide is displayed via the showSlide
method in slideShowApp.dart
. To preview the current slide, the current index, which is stored in the field currentSlideIndex
, is used to retrieve the desired slide object and the Div
rendering method is called:
showSlide(int slideNumber) { if (currentSlideShow.slides.length == 0) return; slideScreen.style.visibility = "hidden"; slideScreen ..nodes.clear() ..nodes.add(currentSlideShow.slides[slideNumber].getSlideContents()); rangeSlidePos.value = slideNumber.toString(); slideScreen.style.visibility = "visible"; }
The slideScreen
is a DivElement
that is then updated off screen by setting the visibility style property to hidden.
The existing content of the DivElement
is emptied out by calling nodes.clear()
and the slide content is added with nodes.add
. The range slider position is set, and finally, the DivElement
is set to visible
again.
Navigating the presentation
A button set with the familiar first, previous, next, and last slide allows the user to jump around the preview of the presentation. This is carried out by having an index built into the list of slides and stored in the field slide in the SlideShowApp
class.
The navigation buttons require being set up in an identical pattern in the constructor of the SlideShowApp
object. First, get an object reference using id
, which is the id
attribute of the element, and then attach a handler to the click event. Rather than repeat this code, a simple function can handle the process:
setButton(String id, Function clickHandler) { ButtonInputElement btn = querySelector(id); btn.onClick.listen(clickHandler); }
Because Function
is a type in Dart, functions can be passed around easily as a parameter. Let us take a look at the button that takes us to the first slide:
setButton("#btnFirst", startSlideShow); void startSlideShow(MouseEvent event) { showFirstSlide(); } void showFirstSlide() { showSlide(0); }
The event handlers do not directly change the slide; these are carried out by other methods that may be triggered by other inputs such as the keyboard.
The SlideShowApp
constructor makes use of this feature:
Function qs = querySelector; var controls = qs("#controls");
I find the querySelector
method a little long to type (though it is descriptive of what it does). With Function
being comprised of types, we can easily create a shorthand version.
The constructor spends much of its time selecting and assigning the HTML elements to member fields of the class. One of the advantages of this approach is that the DOM of the page is queried only once, and the reference is stored and reused. This is good for performance of the application as, once the application is running, querying the DOM may take much longer.
Using the min
and max
functions from the dart:math
package, the index can be kept in the range of the current list:
void showLastSlide() { currentSlideIndex = max(0, currentSlideShow.slides.length - 1); showSlide(currentSlideIndex); } void showNextSlide() { currentSlideIndex = min(currentSlideShow.slides.length - 1, ++currentSlideIndex); showSlide(currentSlideIndex); }
These convenient functions can save a great deal of if
and else if
comparisons and help make the code a good degree more readable.
The slider control is another new control in the HTML5 standard. This will allow the user to scroll though the slides in the presentation.

This control is a personal favorite of mine as it is so visual and can be used to give very interactive feedback to the user. It seemed to be a huge omission from the original form controls in the early generation of web browsers. Even with clear, widely accepted features, HTML specifications can take a long time to clear committees and make it into everyday browsers!
<input type="range" id="rngSlides" value="0"/>
The control has an onChange
event that is given a listener in the SlideShowApp
constructor:
rangeSlidepos.onChange.listen(moveToSlide);rangeSlidepos.onChange.listen(moveToSlide);
The control provides its data via a simple string value, which can be converted to an integer via the int.parse
method to be used as an index in the presentation's slide list:
void moveToSlide(Event event) { currentSlideIndex = int.parse(rangeSlidePos.value); showSlide(currentSlideIndex); }
The slider control must be kept in synchronization with any other change in its slide display, use of navigation, or change in number of slides. For example, the user may use the slider to reach the general area of the presentation, and then adjust with the Previous and Next buttons:
void updateRangeControl() { rangeSlidepos ..min = "0" ..max = (currentSlideShow.slides.length - 1).toString(); }
This method is called when the number of slides is changed, and as with working with most HTML elements, the values to be set need to be converted to strings.
Responding to keyboard events
Using the keyboard, particularly the arrow (cursor) keys, is a natural way to look through the slides in a presentation, even in the preview mode. This is carried out in the SlideShowApp
constructor.
The Textarea
used to input the presentation source will also respond to the arrow keys, so there will need to be a check to see if it is currently being used. The property activeElement
on the document will give a reference to the control with focus. This reference can be compared to the Textarea
, which is stored in the presEditor
field, so a decision can be made on whether to act on the keypress
or not.

Keyboard events, like other events, can be listened to by using a stream event listener. The listener
function is an anonymous function (the definition omits a name) that takes the KeyboardEvent
as its only parameter:
window.onKeyUp.listen((KeyboardEvent e) { if (presEditor != document.activeElement){ if (e.keyCode == 39) showNextSlide(); else if (e.keyCode == 37) showPrevSlide(); else if (e.keyCode == 38) showFirstSlide(); else if (e.keyCode == 40) showLastSlide(); } });
Note
It is a reasonable question to ask how to get the keyboard key codes required to write the switching code. One good tool is the W3C's Key and Character Codes page at http://www.w3.org/2002/09/tests/keys.html. Although this documentation is helpful with this question, it can often be faster to write the handler and print out the event that is passed in.
Showing the key help
Rather than testing the user's memory, there will be a handy reference to the keyboard shortcuts.

This is a simple Div
element that is shown and then hidden when the key (remember to press Shift, too!) is pressed again by toggling the visibility style from visible to hidden.
The event system in Dart is implemented as a stream. One of the advantages of this is that an event can easily have more than one entity listening to the class.
This is useful, for example, in a web application where some keyboard presses are valid in one context but not in another. The listen
method is an add operation (accumulative) so that the key press for help can be implemented separately. This allows a modular approach, which helps reuse, as the handlers can be specialized and added as required:
window.onKeyUp.listen((KeyboardEvent e) { print(e); //Check the editor does not have focus. if (presEditor != document.activeElement) { DivElement helpBox = qs("#helpKeyboardShortcuts"); if (e.keyCode == 191) { if (helpBox.style.visibility == "visible") { helpBox.style.visibility = "hidden"; } else { helpBox.style.visibility = "visible"; } } } });
In a game, for example, a common set of event handling may apply to the title and introduction screen, and the actual in-game screen can contain additional event handling as a superset. This can be implemented by adding and removing handlers to the relevant event stream.