官术网_书友最值得收藏!

Building the dialog package

The pub packaging system is a great resource for sharing finished packages online. If we want to keep a package local and private, we can create it locally and use it in much the same manner.

The package project structure

The package project is structured in a similar manner to a web application project, with a pubspec.yaml file being the main project file. Instead of a web folder, there is a lib folder containing the simple_dialog.dart file that declares our library and defines what is exposed outside the project:

The package project structure

This file contains any classes, functions, and references to other files—in this case, the two files in the src folder. The first line in simple_dialog.dart with the keyword library states the name and declares that this is a library. Note that it is not surrounded by any quotation marks:

library simple_dialog;
import 'dart:html';
part 'src/dialog_base.dart';
part 'src/prefabs.dart';

The next section contains the imports required for all the files that are being exposed by this package declaration in the part declarations. Open either of the files in the src folder and you will see part of simple_dialog declared on the first line.

Adding a local package reference

To reference the library, go to the TextEditor project's pubspec.yaml and open it in the editor. Click on the source tab to see the plain yaml text file. There is currently no graphical interface for the local reference, so it has to be added by hand:

dependencies:
browser: any
intl: any
simple_dialog:
path: ..\simple_dialog

The path is relative to the root project directory, and, as this is a local filesystem reference, the path separator must match the operating system, so we use \ (backslash) for Windows and / (forward slash) for Linux and Mac.

The package is then referenced in editor.dart with a package prefix:

import 'dart:convert';
import 'dart:html';
import 'package:simple_dialog/simple_dialog.dart';

It operates in exactly the same way as a remotely installed package, with the added advantage that it automatically refreshes if changes are made.

Note

To avoid any problems with relative paths (which make many people's heads hurt!), if possible, keep projects in the same directory so that the relative path is always the same. Alternatively, full paths can be used, although this may introduce problems when the code is on different computers.

To verify that the path is right, look in the Project tab, expand the Packages folder, and the package should be listed with its source.

Path dependencies are useful for local development, but will not work when sharing projects with the wider world.

Understanding the package scope

Dart operates under a library scoping system. All items in a package are available throughout—there are no private or protected assets at this level. However, items inside the library can be kept private to external code.

To achieve the equivalent of a private field, a field of a class can be marked private if it has a preceding _ (underscore) character. Any project referencing the library can only use the assets that the package has exposed.

For example, the TextEditor project can use the Dialog class that is public by default and access the content field, but not the _visible field.

Defining the base dialog box

The dialog core is defined in the Dialog class in the dialog_base.dart file. This will contain all the foundational functionality, such as the dialog frame and the OK and CANCEL buttons. More specialized dialog boxes will inherit (extend) this class and modify it for their purposes.

The package will contain common dialogs for the following:

  • A simple alert dialog box
  • An application about dialog box
  • Confirmation dialog from the user

The alert dialog box

This is the simplest of all the dialogs. It will show a line of text and allow the user to press the OK button so that the dialog box is dismissed:

void alert(String dlgTitle, String prompt, int width, int height) {
  Dialog dlg = new Dialog(dlgTitle, prompt, width, height, true, false);
  dlg.show();
}

The dialog box does not return any value, and to make this as easy as possible for the user, it is a single function call. This will be used to display the result of the word-count feature.

The About dialog box

The standard About box for an application contains a small amount of information and a link to a website. This dialog is visually identical to the Alert box apart from the addition of the hyperlink, and as this involves changing the dialog interface elements, this will be implemented in a new class called AboutDialog:

The About dialog box

This is created from the showAbout method of the TextEditor object:

void showAbout(MouseEvent event) { 
  AboutDialog textEditorAbout = new AboutDialog(AppTitle, 
  "TextEditor for the Web", "http://www.packtpub.com", "Homepage", 300, 200); 
  textEditorAbout.show(); 
} 

The AboutDialog box inherits from the Dialog class and calls the constructor of the Dialog class via super, which calls the constructor to form the foundation of the object:

  AboutDialog(String titleText, String bodyText, this.linkUrl, this.linkText, int width, int height)
: super(titleText, bodyText, width, height, true, false){

The elements for the box are created and added to the nodes member of the content DivElement:

content..nodes.insert(0, new BRElement()) 
  ..nodes.insert(0, new BRElement()) 
  ..append(new BRElement()) 
  ..append(new BRElement()) 
  ..append(link) 
  ..style.textAlign = "center"; 

The cascade operator keeps the code compact and focused on setting up the different components of the dialog.

Using the confirmation dialog box

This dialog presents OK and CANCEL options, and calls a specified function if the former option is clicked. This is used in the TextEditor class clearEditor method:

Using the confirmation dialog box

This is implemented by using the base Dialog class directly:

void confirm(String dlgTitle, String prompt, int w, int h, Function action) { 
  Dialog dlg = new Dialog(dlgTitle, prompt, w, h); 
  dlg.show(action); 
}

The application can configure and display this dialog with a single line:

confirm(AppTitle, "Are you sure you want to clear the text?",
400, 120,
performClear);

A function is passed to the show method that is performed if the user presses the OK button.

Counting words using a list

Word count is an important feature for students, technical writers, and competition entrants. Our customers see this as a required feature, too.

In the event handler, we want to calculate the word count and throw the dialog on screen for the user to see. This is a simple, short-running task, so the implementation can exist directly in the event handler. As the output is simple, the alert dialog can be used.

To determine the word count, Dart's List data structure and String classes can be used. The first step is to remove any punctuation characters that may affect our count. This is declared in custom_dialogs.dart as an unmodifiable const string as it can be used for other word-based features:

const String punctuation = ",.-!\"";

This is used in the showWordCount method of the TextEditor class.

Once the punctuation is removed, the string can be split into a List. Dart supports generics, so the types of general data structure can be optionally specified, in this case with <String>. The list of words is then filtered to clean out any non-word entries remaining.

Once we have obtained the list of words, the final word count can be obtained by using the length property of the list.

The Word Frequency feature

Our customers would like a feature to help with writing quality. Overuse of particular words can make a text repetitive and hard to read. To determine the frequency of word usage, Dart's Map (sometimes called an associative array or dictionary), data structure class, and String features can be used:

The Word Frequency feature

Depending on the number of different words used, the content areas may be allowed to scroll, as the dialog cannot grow to the size of any data.

Generics are used again to specify the types for the key and value; in this case, String for the key and int for the value:

Map<String, int> wordFreqCounts = {};
String out = "";

for (var character in punctuation.split(''))
  text = text.replaceAll(character, " ");
text = text.toLowerCase();

List<String> words = text.split(" ");
words
  ..removeWhere((word) => word == " ")
  ..removeWhere((word) => word.length == 0)
  ..forEach((word) {
    if (wordFreqCounts.containsKey(word))
      wordFreqCounts[word] = wordFreqCounts[word] + 1;
    else wordFreqCounts[word] = 1;
  });

wordFreqCounts
    .forEach((k, v) => out += ("<tr><td>$k</td><td>$v</td></tr>"));

The processing of the text is similar to the word count, where a list of individual words is built up. Once the list is filtered, forEach is used to iterate through the list and count the occurrences of each word. The forEach method is used again to build the output HTML, with the map's key and value parameters being passed to the function.

Understanding the typing of Dart code

The types used so far for the Map and List help the design-time tools, such as the Dart Analyzer and WebStorm, which allows us to write code by providing the code completion and validating assignments.

When run in Dartium with the checked mode enabled (the default setting), types are checked at runtime. In the final environment, most likely a web browser such as Firefox or Internet Explorer, the typing information is not used and does not affect the execution speed of the application.

Dart coding recommendations advise using types on all public-facing code. For example, a class in a package can use private var variables, but methods require and return typed data. This aids developers calling the code in understanding what is required, and helps create better documentation.

The file download feature

It can be very useful to have an actual file, so we will add a feature to let the user download and save the contents of the editor to their local device. This is possible with the custom data attributes feature in HTML5, which supports text as one of the formats:

  void downloadFileToClient(String filename, String text) {
    AnchorElement tempLink = document.createElement('a');
    tempLink
      ..attributes['href'] =
      'data:text/plain;charset=utf-8,' + Uri.encodeComponent(text)
      ..attributes['download'] = filename
      ..click();
  }

The process is straightforward: an anchor element is created and its target is set as the encoded text data. The click event is then fired and the download proceeds as if it were being downloaded from a web server.

The clock feature

Everyone has deadlines, so keeping an eye on the clock is important. The dart:async package can help with this task by using a Timer class to trigger and update. This can be carried out in one line in main.dart file of the TextEditor project:

new Timer.periodic(newTimer.periodic(new Duration(seconds: 1),
(timer) => querySelector("#clock").text =
(new DateFormat('HH:mm'))
              .format(new DateTime.now())
                    );

The Timer constructor being called here (Timer.periodic) is a specially named constructor; in this case, the periodic version is being used. There are numerous variations of Timer objects. Named constructors allow the classes to have instances easily created for a particular purpose as they do not require additional initialization configuration before they are ready to use.

A periodic Timer will fire an event every duration period; in this case, one second. The remainder of the line declares the function that is called. Dart has a shorthand of => for functions that are a single line (which are named arrow functions), thus avoiding the use of curly brackets. The querySelector function is used to get a reference to the element on the web page with an ID of clock where the time will be displayed.

The current time is obtained and formatted into hours and minutes for display. As the clock will run for the lifetime of the application and does not require any interaction, no variable is required to store a reference to the object.

That was a busy line of code, and very powerful, but we are not finished examining it yet! There has been no extra thread or process created. This is not a user-initiated event. We will take a look at the what and when of the Dart task queue.

Executing Dart code

The internal architecture of the Dart VM has a single-threaded execution model that is event-driven. In Dart terminology, this is called an isolate. The execution of a Dart program places each event (for example, a function call) into one of two queues. The VM then takes the next event and executes it.

The first queue is the main event queue, and the second is called the microtask queue. This second queue is a higher priority, but is reserved, as the name implies, for short, small tasks, such as creating a new event or setting a value:

Executing Dart code

This execution detail is hidden from the Dart developer for the most part. However, it is important to understand that execution of Dart code is highly asynchronous. The aim of this is to allow applications to continue to be responsive, while still performing longer tasks such as calculations or accessing remote resources.

The advantage of such architecture is the complete removal of the need for any locking or synchronization during program execution. Other virtual machines have to do this, and this greatly affects complexity and performance. For example, Python's GIL (Global Interpreter Lock) has been debated for years, with many alternatives suggested and experimentally implemented.

Multi-processing the VM

The design of the Dart VM allows multiple isolates to run simultaneously, which can be running across different threads, CPU cores, and, in the web browser, web workers. They do not share any resources such as memory, and their sole form of communication is through a messaging system.

The class designer

The customers want a feature that will appeal to programmers. They would like a way for the user to quickly sketch out ideas for a class, including details such as the class name, fields, and methods.

Building a more complicated dialog

To handle this feature, a custom dialog will be required—GenClassDialog in custom_dialogs.dart. This will inherit (extend) the Dialog class, and handle the OK button click event handler itself. Unlike the other dialogs so far in the application, this dialog must return a piece of data back to the text editor:

Building a more complicated dialog

The input controls are one TextInputElement and two TextAreaElements. The placeholder property, available in modern web browsers, will be used to guide the user on what to enter, and, as a bonus, they are convenient space savers, as we do need to add extra elements to label the inputs:

String className = name.value;
String fieldsSrc = "";
String methodsSrc = "";

List<String> classFields = fields.value.split("\n");
List<String> classMethods = methods.value.split("\n");

classFields.forEach((field) => fieldsSrc += "    var $field;\n");
classMethods.forEach((method) => methodsSrc += "    $method(){};\n");

The text is retrieved from the controls and split by line into lists. The method forEach is then used to traverse the list and build the source code.

Constructing the class

The template of a simple class in the GenClassDialog makeClass method has placeholders for the generated source code of the fields and methods. This class code template is declared as a multi-line string using three double-quote characters. Dart supports string interpolation, which helps when putting values of variables into strings:

result = """   /// The $className Class.
class $className {

$fieldsSrc

    $className(){}

$methodsSrc

   }
    """;

Variables can be embedded in strings prefixed by a $ sign. These are then replaced with the actual value of the variable when the string is used.

It is also possible to insert more complicated code, such as an objects property, "The word is ${text.length} characters long". Note the use of curly brackets around the expression being evaluated.

Understanding the flow of events

The dialog for creating the class source code is created and handled by the TextEditor class located in the editor.dart source file.

The sequence of events to display the dialog display is as follows:

  1. The showClassGen event handler constructs the class and puts it on screen, and the function completes, leaving the dialog visible to the user.
  2. The user fills in the details and hits the OK button. This event triggers the OK pressed method on the ClassGenDialog.
  3. The text is extracted from the fields and the source code in the ClassGenDialog makeClass method. Then, the resultHandler is called.
  4. The resultHandler is the createClassCode method on the editor that sets the TextAreaElement as the constructed source code.

Launching the application

To launch the application and try the text editor application for yourself, select index.html in the Project tab, right-click to bring up the context menu, and click on Run 'index.html'. This Run command is also available in the Run menu on the main WebStorm menu bar.

主站蜘蛛池模板: 石嘴山市| 南开区| 贵阳市| 大庆市| 宁陕县| 岳池县| 新田县| 辽源市| 阳曲县| 陇南市| 剑川县| 龙井市| 额尔古纳市| 永川市| 罗城| 泗水县| 美姑县| 特克斯县| 金塔县| 安龙县| 连州市| 奇台县| 荔浦县| 石城县| 北流市| 渑池县| 连城县| 新平| 武隆县| 永春县| 观塘区| 嵊泗县| 北川| 保康县| 夏河县| 定陶县| 图木舒克市| 邹城市| 彰武县| 巴东县| 错那县|