創建自定義微件

創建自定義微件

Creating a custom widget

In this recipe, we'll be covering how to leverage pieces of Dojo and the Dijit framework to create your own custom widgets, specifically covering use of dijit/_WidgetBase and dijit/_TemplatedMixin to quickly and easily set up your widget.

在本教程中,我們將會涉及如何使用Dojo和dijit框架構建自定義的微件,重點介紹使用 dijit/_WidgetBase 和 dijit/_TemplatedMixin 快速簡便的構建你的微件。

Introduction

介紹

The Dojo Toolkit ships with the Dijit framework, which is a set of graphical controls called widgets. We can build graphical user interfaces with these widgets.

Dijit框架是Dojo中的重要模塊,包含一系列的被稱爲微件的圖形控件。我們通過這些微件來創建圖形用戶界面。

You may require a specific widget that is not provided by Dojo. In this case, you can use Dijit's core in order to build this widget with more ease.

你可能需要一些特定的微件,這些微件可能Dojo中沒有包含。這種情況,你可以使用dojo Core 來創建這個模塊。

Setup

設置

For our scenario, let's say that we have a data source somewhere, in JSON format, listing a series of authors, such as those who've penned a tutorial for Dojo. We happen to have that, and it looks something like this:

對於本案例,比方我們有一個數據源,它是JSON格式的,內容包含作者清單,例如爲Dojo寫教程的作者清單。我們剛好有這些,這個數據源看起來像下面這樣:

[
    {
        "name": "Brian Arnold",
        "avatar": "/includes/authors/brian_arnold/avatar.jpg",
        "bio": "Brian Arnold is a software engineer at SitePen, Inc., ..."
    },
    /* More authors here... */
]

We also know that we want our end result to live in a page, somewhere like this:

我們也知道我們希望最終的結果呈現在一個頁面中,像下面的這樣:

<body>
    <!-- Headers and whatnot -->
    <h2>Authors</h2>
    <div id="authorContainer">
        <!-- Authors go here! -->
    </div>
</body>

We'll also say that we want it to be a little fancy—perhaps we get a background color to fade in as we mouse over it. Eventually, we want it to look something like this:

我們想讓結果更有趣一些-比方當鼠標滑過時我們可以呈現一個背景色。最終,我們希望結果看起來像下面這樣:

Solution

解決方案

We can create our own widget by following a simple series of steps.

我們可以按照如下的步驟來創建我們自己的微件:

  1. Create some file structure for our custom widget 爲自定義微件創建文件框架
  2. Create markup that will represent an individual author創建顯示單個作者的標籤
  3. Augment our author markup to make it a Dijit template 爲作者籤添加變量使之成爲一個Dijit模板
  4. Create our widget class using declare 使用declare 創建微件
  5. Style as appropriate 添加樣式美化

Step 1: Create a file structure for our custom widget

第一步:爲微件創建一個文件結構

While this step is arguably optional, it's generally considered a good practice to have a proper file structure for your custom Dijit work (or custom code in general). In this case "myApp" is the folder that will house all of our "custom" code — by "custom" we mean code written specifically for this app. General-purpose and 3rd-party libraries (like dojo, dijit, etc.) would be in folders that are siblings of "myApp". This name is completely up to you, but use something meaningful, like the name of your organization, or the application that this widget will be a part of. We like to group our widgets together, so we'll create a folder named "widget" directly under "myApp". We'll call our new widget AuthorWidget — it's module id will be myApp/widget/AuthorWidget. Widgets often use external resources, so we'll add some folders under the "widget" folder to organize them — css, images, and templates. Our eventual structure looks like this:

這個步驟是可選的,不過在微件中包含一個合適的文件來描述自定義Dijit微件是一種好的編程實踐。在本例中,“myApp”文件夾包含所有的“自定義”代碼-“自定義”指爲本app所寫的代碼。基礎模塊和第三方開發包(例如dojo、dijit等)位於myApp文件夾的同級目錄中。這個文件夾的名字由你自己決定,請使用一個有意義的名字,例如你的組織的名字,或者微件所在程序的名字。我們喜歡把我們的微件組織到一塊,因此會在myApp目錄下創建一個“widget”文件夾。我們將我們的新微件命名爲AuthorWidget-模塊名爲“myApp/widget/AuthorWidget”。微件經常使用外部資源,因此我們在widget文件夾下添加一個文件夾來管理這些資源-css、圖片、模板。最終的結構如下:

We haven't actually created any files yet in our custom space - just some hierarchy.

到目前爲止我們只是創建了文件夾層級,還沒有爲微件添加任何文件。

Step 2: Create markup that will represent an individual author

第二步:創建展示一個作者信息的標籤

Now that we have some structure to store our pieces, let's create some simple markup that represents an individual author. For your first widget, it's likely going to be simplest to just set up a basic page where you directly put in some sample values.

現在已經有了文件夾,我們來創建一個簡單的標籤展示單個作者信息。對於我們的第一個微件,最好能簡單到只建立一個基本的頁面,只需添加簡單的數據。

When you're working out a template, you should always create one parent wrapping element that contains all of the other elements. This element can be whatever you want, but it's important to have just one root element. For our data, we'll use a div as our wrapping element. We'll put in our author's name using an H3 element, the image using an img element, and then our bio inside of a p element.

當規劃一個模板時,你必須創建一個頂層包裝元素以包含所有其他元素。這個元素可以是任意的你希望的,保證只有一個跟根元素即可。對於我們的數據,我們使用一個div作爲根標籤。我們使用H3標籤包含作者名,使用image包含作者圖片,使用p包含作者的簡歷。

<div>
    <h3>Brian Arnold</h3>
    <img src="/includes/authors/brian_arnold/avatar.jpg">
    <p>Brian Arnold is a software engineer at SitePen, Inc., ...</p>
</div>

Step 3: Augment our author markup to make it a Dijit template

第三步:擴展作者標籤使之成爲一個模板

When using dijit/_TemplatedMixin, you can adjust your markup in a variety of ways:

使用dijit/_TemplatedMixin,你有多種方式來調整標籤。

  • You can have values from your widget automatically inserted
  • 你可以讓微件中的值自定填充
  • You can designate elements in your template as Attach Points, giving you a programmatic reference to that node in the widget
  • 你可以指定模板中的元素爲Attach Point,這樣就可以微件中的元素增加一個程序訪問入口。
  • You can set up methods to be called on DOM events related to specific nodes
  • 你可以爲指定的節點配置dom事件響應方法。

For our purposes, we're not worried about events right now — but we definitely want to take advantage of some of the automatic insertion. We're going to create a file in our hierarchy, under myApp/widget/templates/ named AuthorWidget.html. It's basically the markup defined above, but with some simple additions.

目前來說,我們還不關心事件,-我們現在僅關心自動注入。我們現在要在上面的文件目錄myApp/widget/templates/下創建文件AuthorWidget.html。它基本上是上面定義的標籤,不過增加了一些簡單的內容。

<div>
    <h3 data-dojo-attach-point="nameNode">${name}</h3>
    <img class="${baseClass}Avatar" src="" data-dojo-attach-point="avatarNode">
    <p data-dojo-attach-point="bioNode">${!bio}</p>
</div>

There are a few things to note as to what's going on here:

有幾點需要注意:

  • We can use a ${attribute} syntax to directly insert some values, like our name.
  • 我們可以使用${attribute}語法來直接插入數據,例如本例中作者名字。
  • We can use a ${!attribute} syntax to directly insert some values from our widget as well, like we're doing with bio. The major distinction between ${attribute} and ${!attribute} in this case is that our bio contains HTML, and we want to avoid having dijit/_TemplatedMixin perform automatic escaping on the inserted content.
  • 我們也可以使用${!attribute}語法在微件中來插入某些值,像插入簡歷信息那樣。在本例中,兩者最大的區別在於,簡歷中包含HTML標籤。我們想避免dijit/_TemplatedMixin對插入的內容執行自動轉義。
  • All dijit/_WidgetBase-based widgets have a baseClass property by default, and by leaning on that, we can provide a custom class to our avatar.
  • 默認情況下,所有的基於dijit/_WidgetBase的微件都有一個baseClass屬性,基於此,我們可以爲我們的微件提供一個自定義類。
  • We've given all nodes an attach point, meaning that in our widget code, we can use that name to reference that node directly. Think of it kind of like doing some getElementById type work without needing IDs, where we set up references in advance — so with an instance of AuthorWidget, we could use myAuthor.nameNode to directly reference the H3 DOM node for that widget.
  • 每個節點我們都添加了一個attach point屬性,這意味着在我們的微件代碼中,我們可以使用這個名稱查找到該節點。這類似於getElementById,不需要提供Id,我們提前設置在那兒設置引用,這樣有了AuthorWidget的實例,我們就可以使用myAuthor.nameNode來直接獲取微件的H3 Dom節點。

You might have noticed that we haven't set the avatar's source directly. What happens if we have an author that doesn't have an avatar specified? We don't want to show a broken image. We'll handle the default value for that when we create our widget, which we'll do now!

你可能已經注意到我們沒有爲頭像設置圖片源。如果一個作者沒有設置頭像會出現什麼情況?我們不想呈現一個沒有圖片情況下的破碎圖片。我們將會在創建微件時設置頭像的默認圖片,現在我們就開始做。

Step 4: Create our widget class using dojo/_base/declare

第四步:使用dojo/_base/declare創建微件類

At this point, in our file structure above, we're going to create a file named AuthorWidget.js in the widget folder. We'll also add a default avatar image. Our file structure is starting to fill out a little! After this step, it'll be much more filled out. We'll be doing the bulk of our work at this point.

現在,我們將要之前的目錄結構的widget文件夾中創建一個名爲AuthorWidget.js的文件,我們還將添加一個默認圖標。我們的文件框架開始完善。在本步驟完成後,會更加完善。大部分工作都會在本步完成。

Now we can simply build our widget! The following code would go in your AuthorWidget.js file.

現在我們可以簡單地構建我們的微件!以下代碼需要添加到AuthorWidget.js文件中。

// myApp/widget/AuthorWidget.js
define(["dojo/_base/declare","dijit/_WidgetBase", "dijit/_TemplatedMixin"],
    function(declare, _WidgetBase, _TemplatedMixin){
        return declare([_WidgetBase, _TemplatedMixin], {
        });
}); // and that's it!

Using declare we easily create our custom AuthorWidget from dijit/_WidgetBase and dijit/_TemplatedMixin. Now, if we stopped here, this wouldn't work. We need to add a few custom properties for our widget, as well as set up some default values for properties left unspecified in the options passed when the widget is instantiated. Here's the setup for our declaration, now with properties.

使用declare我們方便的創建了基於dijit/_WidgetBase 、 dijit/_TemplatedMixin的自定義的AuthorWidget。現在,如果不繼續添加代碼的話,還沒辦法運行。我們需要爲我們的微件添加一些自定義的屬性,併爲屬性配置默認值,一些未設置的選項在微件初始化sh 。以下就是我們的聲明,現在它已經贈加了一些屬性。

define([
    "dojo/_base/declare",
    "dojo/_base/fx",
    "dojo/_base/lang",
    "dojo/dom-style",
    "dojo/mouse",
    "dojo/on",
    "dijit/_WidgetBase",
    "dijit/_TemplatedMixin",
    "dojo/text!./templates/AuthorWidget.html"
], function(declare, baseFx, lang, domStyle, mouse, on, _WidgetBase, _TemplatedMixin, template){
    return declare([_WidgetBase, _TemplatedMixin], {
        // Some default values for our author一些author的默認值
        // These typically map to whatever you're passing to the constructor這通常映射到你傳遞給構造函數的值
        name: "No Name",
        // Using require.toUrl, we can get a path to our AuthorWidget's space require.toUrl定位到AuthorWidget所在目錄
        // and we want to have a default avatar, just in case以防萬一,我們設置一個默認頭像
        avatar: require.toUrl("./images/defaultAvatar.png"),
        bio: "",

        // Our template - important!我們的模板-重要!
        templateString: template,

        // A class to be applied to the root node in our template 應用於末班中根節點的class
        baseClass: "authorWidget",

        // A reference to our background animation 背景動畫變量
        mouseAnim: null,

        // Colors for our background animation 背景動畫顏色
        baseBackgroundColor: "#fff",
        mouseBackgroundColor: "#def"
    });
});

We have several things going on here, so let's break it down.

我們這裏有幾點要說明。

  • We start off with some properties relevant to our author - namebioavatar - setting default values. By using require.toUrl, we can get a path to where our AuthorWidget is located, and tap into the images folder under there.
  • 我們開始爲作者的幾個屬性-name、bio、avatar設置了默認值。使用require.toUrl,我們可以獲取到AuthorWidget所在的目錄,並通過此定位到圖片目錄。
  • Using the templateString property and dojo/text, we specify our template's contents.
  • 使用templateString屬性和dojo/text我們指定了模板的內容。
  • We set our baseClass. This will be applied to our root node, which in our case is the div in our template.
  • 我們設置了baseClass 樣式。這個值將會添加到我們的根節點上,在本例中即模板中的div。
  • We set up a reference for our animation, to be worked with in a moment, as well as a couple of colors for the animation.
  • 我們設置了一個動畫的變量,等會我們就會用到,以及動畫需要用到的兩種顏色值。

This is all well and good, and if we stopped here, it would actually work as a very basic widget that displayed information. However, we can add in a couple of methods to make things a bit safer. We're going to add:

這很好,如果我們止步於此,它會作爲一個基本的小部件顯示信息。不過,我們可以添加幾個方法使之更安全。我們將要添加:

  • some logic in postCreate (the most commonly extended lifecycle method from _WidgetBase)
  • 在postCreate方法(繼承自_WidgetBase的生命週期中最常見的方法)中添加部分邏輯
  • a custom property setter for the avatar property
  • 爲avatar(頭像)添加一個自定義的屬性設置器
  • a utility function to make it easy to change background color.
  • 一個更改背景色的工具函數

Let's visit each one of those.

我們一一道來:

The postCreate method is where we want to put the bulk of our work. It's called once our widget's DOM structure is ready, but before it's been inserted into the page. It's typically the best place to put any sort of initialization code.

postCreate中我們將會添加我們的大部分代碼。微件的Dom結構初始化完成後會調用一次這個方法,此時微件還未添加到頁面上。這通常是放置我們的初始化代碼的最好的地方。

postCreate: function(){
    // Get a DOM node reference for the root of our widget獲取微件根Dom節點
    var domNode = this.domNode;

    // Run any parent postCreate processes - can be done at any point調用父類postCreate方法,可以在任意位置調用
    this.inherited(arguments);

    // Set our DOM node's background color to white -設置根節點背景顏色爲白色
    // smoothes out the mouseenter/leave event animations保證移入移出事件動畫平滑
    domStyle.set(domNode, "backgroundColor", this.baseBackgroundColor);
    // Set up our mouseenter/leave events 設置鼠標移入移出事件
    // Using dijit/Destroyable's "own" method ensures that event handlers are unregistered when the widget is destroyed使用own方法確保回調函數在微件銷燬時解除綁定
    // Using dojo/mouse normalizes the non-standard mouseenter/leave events across browsers使用dojo/mouse實現跨瀏覽器的鼠標移入移出事件
    // Passing a third parameter to lang.hitch allows us to specify not only the context,將lang.hitch作爲第三個參數使得我們不僅能夠設置上下文
    // but also the first parameter passed to _changeBackground還可以設置傳給_changeBackground方法的第一個參數
    this.own(
        on(domNode, mouse.enter, lang.hitch(this, "_changeBackground", this.mouseBackgroundColor)),
        on(domNode, mouse.leave, lang.hitch(this, "_changeBackground", this.baseBackgroundColor))
    );
}

Here, we're setting some style based on our baseBackgroundColor property, and then setting up some onmouseenter/onmouseleave events, so that as people mouse over the DOM node, our custom _changeBackground function is called. Let's take a look at that:

現在,我們要通過baseBackgroundColor屬性設置一些樣式,之後再設置鼠標移入移出事件,這樣當用戶的鼠標移入微件的Dom節點,自定義的_changeBackground方法就會執行。我們來看一下:

_changeBackground: function(newColor) {
    // If we have an animation, stop it如果已經在動畫中,先停止該動畫
    if (this.mouseAnim) {
        this.mouseAnim.stop();
    }

    // Set up the new animation配置新動畫
    this.mouseAnim = baseFx.animateProperty({
        node: this.domNode,
        properties: {
            backgroundColor: newColor
        },
        onEnd: lang.hitch(this, function() {
            // Clean up our mouseAnim property 清理mouseAnim屬性
            this.mouseAnim = null;
        })
    }).play();
}

Why is this method called _changeBackground and not just changeBackground? It is named with a leading underscore to indicate that users of this widget should treat the method as though it were private, and not something to be used directly. It's a common Dojo pattern to prefix methods or objects with an underscore in order to indicate that they shouldn't be used directly. It doesn't actively stop users from using those methods, but is a useful implicit indication of sorts that "Hey, this isn't meant for general use".

爲什麼這個方法名爲_changeBackground而非changeBackground?這個方法名以_開頭標明微件的用戶需要將其視爲一個private方法,不能直接調用。這是dojo中一種通用的方式,在方法或變量名稱之前添加_來表明這是一個私有變量不能直接調用。雖說實際上用戶還是能夠調用它,但是這是一個有用的隱式表示“嘿,這不是一般使用的”。

We're checking our mouseAnim property to see if there's an animation there, and if we have something there, we're calling stop to stop it, as a means of being safe. Then, we simply set up the new animation and save it back into mouseAnim, then start it playing. This example is very similar to an effect as demonstrated in the Animation tutorial, though with some different colors.

我們檢測mouseAnim屬性查看是否已經有一個動畫,如果是,則執行動畫的stop方法停止該動畫,以保證安全。之後,配置新的動畫並賦給mouseAnim變量,並執行動畫。這與Animation教程中展示的例子很像,只是顏色不同。

Finally, remember how we had concerns earlier that a user might not have an avatar, and we set up a default one? We can set up a custom setter function for attributes, which will automatically be called any time that value is set, either when the widget is being created or if someone calls myWidget.set("avatar", somePath). The name of the method is special, and maps to the name of the property—in this case, for avatar, our name will be _setAvatarAttr.

最後,記不記得我們關注過一個用戶可能沒有頭像,我們給頭像賦了一個默認值的事?我們可以爲屬性添加一個自定義的設置器方法,在微件創建或使用myWidget.set("avatar",somPath)使得avatar屬性改變時,這個方法會自動調用。方法的名字是特別的,與屬性名對應-在本例中,作爲avatar的設置器,方法名應該是_setAvatarAttr.

_setAvatarAttr: function(imagePath) {
    // We only want to set it if it's a non-empty string當imagePath不爲空時纔會對avatar賦值
    if (imagePath != "") {
        // Save it on our widget instance - note that保存到微件實例中-注意
        // we're using _set, to support anyone using我們使用_set,支持所有使用該微件的用戶
        // our widget's Watch functionality, to watch values change監聽方法執行,監測變量改變
        this._set("avatar", imagePath);

        // Using our avatarNode attach point, set its src value通過attach Point方式設置image節點的src
        this.avatarNode.src = imagePath;
    }
}

Starting with Dojo 1.6, all dijit/_WidgetBase widgets include dojo/Stateful in their inheritance chain, which means that users can actively watch for value changes. We use _set within our setter to ensure that all watch calls are properly fired, and then we use our avatarNode attach point to set the image's src attribute to the value being set. By wrapping it in a check for the string not being empty, we're trying to avoid cases where the avatar property may be there, but with nothing but an empty string. This way, we get our default image if we have no value, with a bit of a safety check.

使用Dojo1.6的話,所有的基於dijit/_WidgetBase的微件都包含dojo/Stateful,意味着用戶可以監測變量值改變。我們在設置器中使用_set方法保證所有的watch都可以執行,之後我們使用avatarNode attchPoint 來設置image圖片的src屬性。在設置之前我們會監測imagePath是否是空值,避免爲圖片設置空值。這樣,當avatar沒有值時默認值就起作用了,增加了一個安全驗證。

To use the widget, we do the following:

爲了使用微件,我們需要按一下方式操作:

<div id="authorContainer"></div>
require(["dojo/request", "dojo/dom", "dojo/_base/array", "myApp/widget/AuthorWidget", "dojo/domReady!"],
    function(request, dom, arrayUtil, AuthorWidget){
    // Load up our authors 加載作者數據
    request("myApp/data/authors.json", {
        handleAs: "json"
    }).then(function(authors){
        // Get a reference to our container 添加一個指向container的變量
        var authorContainer = dom.byId("authorContainer");

        arrayUtil.forEach(authors, function(author){
            // Create our widget and place it 創建微件並添加到頁面上
            var widget = new AuthorWidget(author).placeAt(authorContainer);
        });
    });
});

With all of these items in place, we have a working widget! However, as you can see, it's not exactly pretty yet.

這些都做完之後,我們就有了一個可以正常運行的微件!不過,如你所見,略醜。

View Demo

Step 5: Style as appropriate

第五步:使用樣式美化

One of the benefits of using dijit/_WidgetBase, as mentioned above, is that it gives us a baseClass that we can work off of for styling purposes. Using that, we can create some fairly simple styling. We have a folder under our AuthorWidget space just for css, so let's create an AuthorWidget.css file in there.

上面已經提到過的,使用dijit/_WidgetBase的一個好處就是,我們可以使用baseClass屬性來設置樣式。使用該屬性,我們可以創建一些簡單的樣式。在AuthorWidget中我們有一個專門的放css文件的目錄,我們可以在此創建一個AuthorWidget.css文件。

/* myApp/widget/css/AuthorWidget.css */
.authorWidget {
    border: 1px solid black;
    width: 400px;
    padding: 10px;
    overflow: hidden; /* I hear this helps clear floats inside */
}

.authorWidget h3 {
    font-size: 1.5em;
    font-style: italic;
    text-align: center;
    margin: 0px;
}

.authorWidgetAvatar {
    float: left;
    margin: 4px 12px 6px 0px;
    max-width: 75px;
    max-height: 75px;
}

Since we know our baseClass is authorWidget, we can work off of that. Also, if you recall, in the template, we set a class on the avatar of ${baseClass}Avatar, so we can use authorWidgetAvatar in our styling.

已知我們的baseClass值爲authorWidget,我們可以對其進行配置。同時,如果在模板中再次調用,我們設置avatar的class爲${baseClass}Avatar,因此將使用authorWidgetAvatar。

Now, with that in place, we just need to add the CSS to our head on our page, and we have a nicer looking author list!

現在,這些都完成之後,我們只需要將css文件添加到頁面的head標籤中,我們就有了一個好看點的作者列表了!

View Demo

Summary

總結:

As you can see, using dijit/_WidgetBase and dijit/_TemplatedMixin makes it fairly easy to create a custom widget. We were able to quickly set up a template, and with a little bit of work, we were able to create our AuthorWidget class based off of dijit/_WidgetBase and dijit/_TemplatedMixin.

It's worth noting that most widgets in Dijit itself are built using these same tools, and we've only scratched the surface of what they can do. Be sure to follow through some of the links below for more information!

如你所見,基於dijit/_WidgetBase 和 dijit/_TemplatedMixin 創建自定義微件那是相當的簡單。我們快速的配置了模板,再加上很少量的工作,我們就創建了基於dijit/_WidgetBase 和 dijit/_TemplatedMixin的AuthorWidget類。

值得注意的是Dijit中大多數的微件也都是基於這些工具創建的,我們只是瞭解了一點皮毛。一定要通過下面的連接繼續學習。

Resources

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章