24 min read

 In this article by David Mitchell, author of the book Dart By Example you will be introduced to the basics of how to build a presentation application using Dart.

It usually takes me more than three weeks to prepare a good impromptu speech.

Mark Twain

Presentations make some people shudder with fear, yet they are an undeniably useful tool for information sharing when used properly. The content has to be great and some visual flourish can make it stand out from the crowd.

Too many slides can make the most receptive audience yawn, so focusing the presenter on the content and automatically taking care of the visuals (saving the creator from fiddling with different animations and fonts sizes!) can help improve presentations. Compelling content still requires the human touch.

(For more resources related to this topic, see here.)

Building a presentation application

Web browsers are already a type of multimedia presentation application so it is feasible to write a quality presentation program as we explore more of the Dart language. Hopefully it will help us pitch another Dart application to our next customer.

Building on our first application, we will use a text based editor for creating the presentation content. I was very surprised how much faster a text based editor is for producing a presentation, and more enjoyable. I hope you experience such a productivity boost!

Laying out the application

The application will have two modes, editing and presentation. In the editing mode, the screen will be split into two panes. The top pane will display the slides and the lower will contain the editor, and other interface elements.

This article will focus on the core creation side of the presentation. The application will be a single Dart project.

Defining the presentation format

The presentations will be written in a tiny subset of the Markdown format which is a powerful yet simple to read text file based format (much easier to read, type and understand than HTML).

In 2004, John Gruber and the late Aaron Swartz created the Markdown language in 2004 with the goal of enabling people to write using an easy-to-read, easy-to-write plain text format.

It is used on major websites, such as GitHub.com and StackOverflow.com. Being plain text, Markdown files can be kept and compared in version control.

For more detail and background on Markdown see https://en.wikipedia.org/wiki/Markdown

A simple titled slide with bullet points would be defined as:

#Dart Language
+Created By Google
+Modern language with a familiar syntax
+Structured Web Applications
+It is Awesomely productive!

I am positive you only had to read that once! This will translate into the following HTML.

<h1>Dart Language</h1>
<li>Created By Google</li>s
<li>Modern language with a familiar syntax</li>
<li>Structured Web Applications</li>
<li>It is Awesomely productive!</li>

Markdown is very easy and fast to parse, which probably explains its growing popularity on the web. It can be transformed into many other formats.

Parsing the presentation

The content of the TextAreaHtml element is split into a list of individual lines, and processed in a similar manner to some of the features in the Text Editor application using forEach to iterate over the list. Any lines that are blank once any whitespace has been removed via the trim method are ignored.

#A New Slide Title
+The first bullet point
+The second bullet point

#The Second Slide Title
+More bullet points
!http://localhost/img/logo.png
#Final Slide
+Any questions?

For each line starting with a # symbol, a new Slide object is created.

For each line starting with a + symbol, they are added to this slides bullet point list.

For each line is discovered using a ! symbol the slide’s image is set (a limit of one per slide).

This continues until the end of the presentation source is reached.

A sample presentation

To get a new user going quickly, there will be an example presentation which can be used as a demonstration and testing the various areas of the application. I chose the last topic that came up round the family dinner table—the coconut!

#Coconut
+Member of Arecaceae family.
+A drupe - not a nut.
+Part of daily diets.
#Tree
+Fibrous root system.
+Mostly surface level.
+A few deep roots for stability.
#Yield
+75 fruits on fertile land
+30 typically
+Fibre has traditional uses
#Finally
!coconut.png
#Any Questions?

Presenter project structures

The project is a standard Dart web application with index.html as the entry point. The application is kicked off by main.dart which is linked to in index.html, and the application functionality is stored in the lib folder.

Source File

Description

sampleshows.dart    The text for the slideshow application.
 lifecyclemixin.dart  The class for the mixin.
 slideshow.dart  Data structures for storing the presentation.
 slideshowapp.dart  The application object.

Launching the application

The main function has a very short implementation.

void main() {
new SlideShowApp();
}

Note that the new class instance does not need to be stored in a variable and that the object does not disappear after that line is executed. As we will see later, the object will attach itself to events and streams, keeping the object alive for the lifetime that the page is loaded.

Building bullet point slides

The presentation is build up using two classes—Slide and SlideShow. The Slide object creates the DivElement used to display the content and the SlideShow contains a list of Slide objects.

The SlideShow object is updated as the text source is updated. It also keeps track of which slide is currently being displayed in the preview pane.

Once the number of Dart files grows in a project, the DartAnalyzer will recommend naming the library. It is good habit to name every .dart file in a regular project with its own library name.

The slideshow.dart file has the keyword library and a name next to it. In Dart, every file is a library, whether it is explicitly declared or not.

If you are looking at Dart code online you may stumble across projects with imports that look a bit strange.

#import("dart:html");

This is the old syntax for Dart’s import mechanism. If you see this it is a sign that other aspects of the code may be out of date too.

If you are writing an application in a single project, source files can be arranged in a folder structure appropriate for the project, though keeping the relatives paths manageable is advisable. Creating too many folders is probably means it is time to create a package!

Accessing private fields

In Dart, as discussed when we covered packages, the privacy is at the library level but it is still possible to have private fields in a class even though Dart does not have the keywords public, protected, and private. A simple return of a private field’s value can be performed with a one line function.

String getFirstName() => _name;

To retrieve this value, a function call is required, for example, Person.getFirstName() however it may be preferred to have a property syntax such as Person.firstName. Having private fields and retaining the property syntax in this manner, is possible using the get and set keywords.

Using true getters and setters

The syntax of Dart also supports get and set via keywords:

int get score =>score + bonus;
set score(int increase) =>score += increase * level;

Using either get/set or simple fields is down to preference. It is perfectly possible to start with simple fields and scale up to getters and setters if more validation or processing is required.

The advantage of the get and set keywords in a library, is the intended interface for consumers of the package is very clear. Further it clarifies which methods may change the state of the object and which merely report current values.

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 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 a mixin class to do this job, allowing the sharing of functionality without forced inheritance or clunky aggregation.

In Dart, a mixin must meet the requirements:

  1. No constructors in the class declaration.
  2. The base class of the mixin must be Object.
  3. 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 may be required on several classes in an application 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 Object.

class StorageReports extends Object with Serialization

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 objects 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.

If you want to focus on just-the-code when in WebStorm , double-click on 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 a 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 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 can also take a regular expression or a string to match and 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, stored in the field currentSlideIndex, is used to retrieve the desired slide object and the Div rendering method 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 which 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 familiar first, previous, next and last slide allow the user to jump around the preview of the presentation. This is carried out by having an index into the list of slides stored in the field slide in the SlideShowApp class.

Handling the button key presses

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 attaching 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);
}

As 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, which may be triggered by other inputs such as the keyboard.

Using the function type

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 a good descriptive of what it does). With Function being 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 stored and reused. This is good for performance of the application as, once the application is running, querying the DOM may take much longer.

Staying within the bounds

Using min and max function from the dart:math package, the index can be kept in 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 convenience functions can save a great deal if and else if comparisons and help make code a good degree more readable.

Using the slider control

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 which 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 to 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 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 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.

In Dart web applications, the dart:html package allows direct access to the globalwindow object from any class or function.

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 taken on whether to act on the keypress or not.

Key

Event Code

Action

Left Arrow  37  Go back a slide.
Up Arrow  38  Go to first slide. 
 Right Arrow  39  Go to next slide.
 Down Arrow  40  Go to last slide.

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();
}
});

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, to help with this but 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 which 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.

Listening twice to event streams

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 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, for example, a game, a common set of event handling may apply to title and introduction screen and the actual in game screen contains additional event handling as a superset. This could be implemented by adding and removing handlers to the relevant event stream.

Changing the colors

HTML5 provides browsers with full featured color picker (typically browsers use the native OS’s color chooser). This will be used to allow the user to set the background color of the editor application itself.

The color picker is added to the index.html page with the following HTML:

<input id="pckBackColor" type="color">

The implementation is straightforward as the color picker control provides:

InputElement cp = qs("#pckBackColor");
cp.onChange.listen(
(e) => document.body.style.backgroundColor = cp.value);

As the event and property (onChange and value) are common to the input controls the basic InputElement class can be used.

Adding a date

Most presentations are usually dated, or at least some of the jokes are! We will add a convenient button for the user to add a date to the presentation using the HTML5 input type date which provides a graphical date picker.

<input type="date" id="selDate" value="2000-01-01"/>

The default value is set in the index.html page as follows:

The valueAsDate property of the DateInputElement class provides the Date object which can be added to the text area:

void insertDate(Event event) {
DateInputElement datePicker = querySelector("#selDate");
if (datePicker.valueAsDate != null) presEditor.value =
presEditor.value +
datePicker.valueAsDate.toLocal().toString();
}

In this case, the toLocal method is used to obtain a string formatted to the month, day, year format.

Timing the presentation

The presenter will want to keep to their allotted time slot. We will include a timer in the editor to aid in rehearsal.

Introducing the stopwatch class

The Stopwatch class (from dart:core) provides much of the functionality needed for this feature, as shown in this small command line application:

main() {
Stopwatch sw = new Stopwatch();
sw.start();
print(sw.elapsed);
sw.stop();
print(sw.elapsed);
}

The elapsed property can be checked at any time to give the current duration. This is very useful class, for example, it can be used to compare different functions to see which is the fastest.

Implementing the presentation timer

The clock will be stopped and started with a single button handled by the toggleTimer method. A recurring timer will update the duration text on the screen as follows:

If the timer is running, the update Timer and the Stopwatch in field slidesTime is stopped. No update to the display is required as the user will need to see the final time:

void toggleTimer(Event event) {
if (slidesTime.isRunning) {
 slidesTime.stop();
   updateTimer.cancel();
} else {
   updateTimer = new Timer.periodic(new Duration(seconds: 1),
   (timer) {
     String seconds = (slidesTime.elapsed.inSeconds %
     60).toString();
     seconds = seconds.padLeft(2, "0");
     timerDisplay.text =
     "${slidesTime.elapsed.inMinutes}:$seconds";
 });

 slidesTime
 ..reset()
 ..start();
}
}

The Stopwatch class provides properties for retrieving the elapsed time in minutes and seconds. To format this to minutes and seconds, the seconds portion is determined with the modular division operator % and padded with the string function padLeft.

Dart’s string interpolation feature is used to build the final string, and as the elapsed and inMinutes properties are being accessed, the {} brackets are required so that the single value is returned.

Overview of slides

This provides the user with a visual overview of the slides as shown in the following screenshot:

The presentation slides will be recreated in a new full screen Div element. This is styled using the fullScreen class in the CSS stylesheet in the SlideShowApp constructor:

overviewScreen = new DivElement();
overviewScreen.classes.toggle("fullScreen");
overviewScreen.onClick.listen((e) => overviewScreen.remove());

The HTML for the slides will be identical. To shrink the slides, the list of slides is iterated over, the HTML element object obtained and the CSS class for the slide is set:

currentSlideShow.slides.forEach((s) {
aSlide = s.getSlideContents();
aSlide.classes.toggle("slideOverview");
aSlide.classes.toggle("shrink");
...

The CSS hover class is set to scale the slide when the mouse enters so a slide can be focused on for review. The classes are set with the toggle method which either adds if not present or removes if they are. The method has an optional parameter:

aSlide.classes.toggle('className', condition);

The second parameter is named shouldAdd is true if the class is always to be added and false if the class is always to be removed.

Handout notes

There is nothing like a tangible handout to give attendees to your presentation. This can be achieved with a variation of the overview display:

Instead of duplicating the overview code, the function can be parameterized with an optional parameter in the method declaration. This is declared with square brackets [] around the declaration and a default value that is used if no parameter is specified.

void buildOverview([bool addNotes = false])

This is called by the presentation overview display without requiring any parameters.

buildOverview();

This is called by the handouts display without requiring any parameters.

buildOverview(true);

If this parameter is set, an additional Div element is added for the Notes area and the CSS is adjust for the benefit of the print layout.

Comparing optional positional and named parameters

The addNotes parameter is declared as an optional positional parameter, so an optional value can be specified without naming the parameter. The first parameter is matched to the supplied value.

To give more flexibility, Dart allows optional parameters to be named. Consider two functions, the first will take named optional parameters and the second positional optional parameters.

getRecords1(String query,{int limit: 25, int timeOut: 30}) {
}

getRecords2(String query,[int limit = 80, int timeOut = 99]) {
}

The first function can be called in more ways:

getRecords1("");
getRecords1("", limit:50, timeOut:40);
getRecords1("", timeOut:40, limit:65);
getRecords1("", limit:50);
getRecords1("", timeOut:40);

getRecords2("");
getRecords2("", 90);
getRecords2("", 90, 50);

With named optional parameters, the order they are supplied is not important and has the advantage that the calling code is clearer as to the use that will be made of the parameters being passed.

With positional optional parameters, we can omit the later parameters but it works in a strict left to right order so to set the timeOut parameter to a non-default value, limit must also be supplied. It is also easier to confuse which parameter is for which particular purpose.

Summary

The presentation editor is looking rather powerful with a range of advanced HTML controls moving far beyond text boxes to date pickers and color selectors. The preview and overview help the presenter visualize the entire presentation as they work, thanks to the strong class structure built using Dart mixins and data structures using generics.

We have spent time looking at the object basis of Dart, how to pass parameters in different ways and, closer to the end user, how to handle keyboard input. This will assist in the creation of many different types of application and we have seen how optional parameters and true properties can help document code for ourselves and other developers.

Hopefully you learned a little about coconuts too.

The next step for this application is to improve the output with full screen display, animation and a little sound to capture the audiences’ attention. The presentation editor could be improved as well—currently it is only in the English language. Dart’s internationalization features can help with this.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here